├── .gitignore
├── CHANGELOG.md
├── DOCUMENTATION.md
├── LICENSE.md
├── README.md
├── TODO.md
├── VERSION
├── bin
└── ofexport
├── build-scripts
├── dump-test-data
├── examples
├── generate-sources
├── generate_help.py
├── integration-test
├── pretty_json.py
└── unit-test
├── documentation
├── Browser-thumb.jpg
├── GeekToolScreenGrab-thumb.jpg
├── Hazel.md
├── HazelRules.png
├── HazelRulesScript.png
├── TaskPaper-thumb.jpg
├── cal.png
├── diagram
│ ├── Android.jpeg
│ ├── Diagram.graffle
│ │ ├── data.plist
│ │ ├── image1.jpeg
│ │ ├── image10.jpeg
│ │ ├── image11.png
│ │ ├── image12.jpeg
│ │ ├── image13.jpeg
│ │ ├── image14.jpeg
│ │ ├── image15.jpeg
│ │ ├── image16.png
│ │ ├── image17.jpeg
│ │ ├── image18.jpg
│ │ ├── image20.png
│ │ ├── image3.jpeg
│ │ ├── image4.jpeg
│ │ ├── image5.jpeg
│ │ ├── image6.jpeg
│ │ ├── image7.jpeg
│ │ ├── image8.jpeg
│ │ └── image9.png
│ ├── Diagram.png
│ ├── DropBox.jpeg
│ ├── Firefox.jpeg
│ ├── FoldingText.jpeg
│ ├── GeekTool.jpeg
│ ├── Hazel.jpeg
│ ├── Marked.jpeg
│ ├── MindNode.png
│ ├── Note.png
│ ├── OmniFocus.jpeg
│ ├── OmniOutliner.png
│ ├── TaskPaper.jpeg
│ ├── WebCloud.jpeg
│ ├── WinPhone.jpeg
│ ├── cog.png
│ ├── iCal.jpeg
│ └── text.jpeg
└── examples.md
├── install.sh
├── ofexport.json
├── pom.xml
├── release
├── src
├── main
│ └── python
│ │ ├── .gitignore
│ │ ├── attrib_convert.py
│ │ ├── cmd_parser.py
│ │ ├── cupboard
│ │ └── of_to_tags.py
│ │ ├── datematch.py
│ │ ├── fmt_template.py
│ │ ├── help.py
│ │ ├── ofexport.py
│ │ ├── omnifocus.py
│ │ ├── plugin_html.py
│ │ ├── plugin_ics.py
│ │ ├── plugin_json.py
│ │ ├── plugin_markdown.py
│ │ ├── plugin_opml.py
│ │ ├── plugin_taskpaper.py
│ │ ├── plugin_text.py
│ │ ├── treemodel.py
│ │ ├── typeof.py
│ │ ├── util.py
│ │ ├── visitors.py
│ │ └── word_cloud.py
└── test
│ ├── data
│ ├── db-1-C.ft
│ ├── db-1-C.html
│ ├── db-1-C.json
│ ├── db-1-C.md
│ ├── db-1-C.opml
│ ├── db-1-C.taskpaper
│ ├── db-1-c1.taskpaper
│ ├── db-1-c2.taskpaper
│ ├── db-1-c3.taskpaper
│ ├── db-1-c4.taskpaper
│ ├── db-1-f1.taskpaper
│ ├── db-1-f2.taskpaper
│ ├── db-1-f3.taskpaper
│ ├── db-1-f4.taskpaper
│ ├── db-1-flat.markdown
│ ├── db-1-html-lite.html
│ ├── db-1-p1.taskpaper
│ ├── db-1-p2.taskpaper
│ ├── db-1-p3.taskpaper
│ ├── db-1-p4.taskpaper
│ ├── db-1-stdout.text
│ ├── db-1-t1.taskpaper
│ ├── db-1-t2.taskpaper
│ ├── db-1-t3.taskpaper
│ ├── db-1-t4.taskpaper
│ ├── db-1-taskpaper-lite.taskpaper
│ ├── db-1-tasks1.taskpaper
│ ├── db-1-tasks2.taskpaper
│ ├── db-1.ft
│ ├── db-1.html
│ ├── db-1.ics
│ ├── db-1.json
│ ├── db-1.md
│ ├── db-1.opml
│ ├── db-1.taskpaper
│ ├── db-1.text
│ ├── db-2-C.taskpaper
│ ├── db-2.json
│ ├── db-3-Context-flatten.taskpaper
│ ├── db-3-Context-prune.taskpaper
│ ├── db-3-Folder-flatten.taskpaper
│ ├── db-3-Folder-prune.taskpaper
│ ├── db-3-Project-flatten.taskpaper
│ ├── db-3-Project-prune.taskpaper
│ ├── db-3-Project-sort.taskpaper
│ ├── db-3-Task-flatten.taskpaper
│ ├── db-3-any-flatten.taskpaper
│ ├── db-3.json
│ ├── db-4-Context-sort.taskpaper
│ ├── db-4-Folder-sort.taskpaper
│ ├── db-4-Project-sort.taskpaper
│ ├── db-4.json
│ ├── db-5.json
│ ├── ex1-worms-last-week.taskpaper
│ ├── ex2-ham-today.taskpaper
│ ├── ex3-not-worms.taskpaper
│ ├── ex4-worms-or-ham-today.taskpaper
│ ├── ex5-work-this-week-flat.taskpaper
│ ├── ex8-due-or-flagged.taskpaper
│ ├── ex9.ics
│ └── help.txt
│ └── python
│ ├── .gitignore
│ ├── attrib_convert_test.py
│ ├── cmd_parser_test.py
│ ├── datematch_test.py
│ ├── ofexport_test.py
│ ├── plugin_ics_test.py
│ ├── test_helper.py
│ ├── treemodel_test.py
│ ├── types_test.py
│ ├── util_test.py
│ └── visitors_test.py
└── templates
├── flat.json
├── help-template.txt
├── html-lite.json
├── html-start.txt
├── html.json
├── ics.json
├── markdown-lite.json
├── markdown.json
├── opml.json
├── taskpaper-lite.json
├── taskpaper.json
├── text.json
└── todotxt.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .pydevproject
3 | *.pyc
4 | target
5 | .DS_Store
6 | example-bashrc
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes #
2 |
3 | ## 3.0.5 (2014-02-23)
4 |
5 | - Adding estimatedMinutes and dateAdded as a task parameters that can be used in templates.
6 |
7 | ## 3.0.4 (2013-06-25)
8 |
9 | - Small bugfix that sometimes causes crash when reading formatted task notes.
10 |
11 | ## 3.0.3 (2013-06-07)
12 |
13 | - Fixed but that caused exported allday calendar items to appear a day late.
14 |
15 | ## 3.0.2 (2013-06-03)
16 |
17 | - Fixed bug with TaskPaper template, removed project tag from project (always itself!).
18 | - Fixed bug that caused tasks not have a project attribute.
19 | - Added support for Inbox items (ofexport considers Inbox items to be in a project called "Inbox").
20 |
21 | ## 3.0.1 (2013-05-30)
22 |
23 | - Made default format when printing to stdout configurable.
24 | - Reformatted json config using "recommended" layout (bleh!) with a BBEdit plugin http://bbeditextras.org/wiki/index.php?title=Text_Filters
25 | - Moved db search path into config.
26 | - Added config for default "%of cal" directives.
27 | - Fixed a bug where I wasn't using the persistentIdentifier as the task id.
28 |
29 | ## 3.0.0 (2013-05-27)
30 |
31 | - Introduced a plugin model.
32 | - Changed format of templates to support plugins better.
33 | - Added a global config file: ofexport.json for plugins and file associations.
34 | - Add **id** and **type** attributes to a few templates.
35 |
36 | ## 2.1.6 (2013-05-20)
37 |
38 | - Tweaks to date sorting so that items with no dates appear below those that do.
39 |
40 | ## 2.1.5 (2013-05-18)
41 |
42 | - Tweaks to sort algorithm to return underlying order when selected attributes of two items are equal.
43 | - Added a --tasks filter to eliminate everything but tasks grouped under a single Tasks project/context.
44 |
45 | ## 2.1.4 (2013-05-14)
46 |
47 | - Updated the installation instructions and added **install.sh** as a post-download script.
48 | - Fixes to markdown template "hashes" now an attribute.
49 |
50 | ## 2.1.3 (2013-05-13)
51 |
52 | - Added next as a filterable attribute of tasks.
53 |
54 | ## 2.1.2 (2013-05-10)
55 |
56 | - Added status of a project/context as a field that can be filtered on or used in a template.
57 |
58 | ## 2.1.1 (2013-05-08)
59 |
60 | - Added ability to set the start/due time of a calendar entry separately from OF start/due.
61 | - Bug fix to calendar allday feature where UTC adjustment could push calendar entry to wrong day.
62 | - Improved ics formatting so OmniFocus link in URL field rather than DESCRIPTION - making it clickable.
63 |
64 | ## 2.1.0 (2013-05-05)
65 |
66 | - Added Calendar (ics) export.
67 | - Allowed filtering on note text.
68 |
69 | ## 2.0.3 (2013-05-03)
70 |
71 | - Fix for a nasty performance bug relating to notes
72 |
73 | ## 2.0.2 (2013-05-03)
74 |
75 | - Changed OPML format so note text appears as an OmniOutliner block note, not a sequence of sub-nodes.
76 | - Bug fix: notes weren't being escaped in OPML or HTML leading to invalid format.
77 |
78 | ## 2.0.1 (2013-05-01)
79 |
80 | - Added notes from the OmniFocus database.
81 | - Better logging.
82 | - Prints to standard out if no file specified.
83 | - Resolved some utf-8 issues.
84 |
85 | ## 2.0.0 (2013-04-29)
86 |
87 | - New expression parsing engine for complex queries.
88 | - Thumbnails in the documentation.
89 | - Added dateFormat to templates.
90 | - Bugfixes.
91 |
92 | ## 1.1.0 (2013-04-21)
93 |
94 | - Customisable templates for formatting the output.
95 | - Big internal changes to support templates.
96 | - Much improved tests.
97 | - Added json as an output format.
98 | - Added json as an alternative input format.
99 |
100 | ## 1.0.5 (2013-04-18) ##
101 | - Fewer and more modular command line options.
102 | - Big internal changes to the filter mechanism.
103 | - Much simpler filter building from command line arguments.
104 | - Added link mode (-l) to add links to OmniFocus from TaskPaper documents.
105 | - Added tags for projects/contexts in TaskPaper report.
106 |
107 | ## 1.0.4 (2013-04-15) ##
108 |
109 | - Added Context mode.
110 | - Improved test script.
111 | - Filter performance improvement.
112 | - Improved flattening algorithm to work in Context mode.
113 | - Added "No Context" context that all tasks without a context get added to.
114 |
115 | ## 1.0.3 (2013-04-13) ##
116 |
117 | - Big reworking of filter logic to squash a design bug.
118 | - Added a alphabetic project/folder sorting filter.
119 | - Added -i/-e simple filters that searches all text types.
120 | - Added --Fi/--Fe simple filters that work on any flagged type.
121 | - More documentation.
122 |
123 | ## 1.0.2 (2013-12) ##
124 |
125 | - Added the Apache V2 License to the source and documentation.
126 | - Fix a bug caused by allowing newlines in task titles.
127 | - Improved the documentation
128 |
129 | ## 1.0.1 (2013-04-10) ##
130 |
131 | - Extended date filters to recognise month names.
132 | - Extended date filters to recognise "any" and "none" for date matching.
133 | - Lots of extra testing and bug fixing around date matching.
134 |
135 | ## 1.0.0 (2013-04-09) ##
136 |
137 | - Added task filters for flagged, start and due
138 | - Added project filters for flagged, start, completion and due
139 | - Reworking date logic to be more human allow "next tuesday", "last week" etc.
140 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # License #
2 |
3 | Copyright 2013 Paul Sidnell
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing,software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # No Longer Supported #
2 |
3 | **Note [ofexport2](https://github.com/psidnell/ofexport2) is now available.**
4 |
5 | If that's not to your liking then the original ofexport will stay here but I won't be updating it any further.
6 |
7 | There are several forks of the code you might want to look at.
8 |
9 | The original documentation is [here](https://github.com/psidnell/ofexport/blob/master/DOCUMENTATION.md).
10 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # V3.0.5
2 |
3 |
4 | # Backlog
5 |
6 | - Command Line
7 | - Recipies/macros - names bundles of command line args
8 | - GUI
9 | - tkinter user interface? What would it look like?
10 | - Useability
11 | - useability - any quick wins? command line shortcuts? -DF? canned filters?
12 | - Database
13 | - What about task availability? Where is it stored?
14 | - Try ofexport with OF 2
15 | - Where are inbox items stored in in the OF DB?
16 | Can I catch them with "No Context"
17 | - Can I detect a if a project/context is paused?
18 | - Attributes
19 | - childcount as an attribute
20 | - Can I determine availability?
21 | - "type" should be a template variable
22 | - $date and $time variables in templates
23 | - Add item depth as filterable parameter
24 | - Templates
25 | - Create a "dump" template that dumps everything.
26 | - Have a default template in code such that an empty template file still works
27 | - Put file extension associations in the templates
28 | - Tag Clouds
29 | - Check out PyTagCloud
30 | https://github.com/atizo/PyTagCloud
31 |
32 | - Clouds myself: http://stackoverflow.com/questions/3180779/html-tag-cloud-in-python
33 | - Integrations
34 | - Log to DayOne? - how?
35 | - Sorting
36 | - Add "natural" sort - i.e. sort by the order from the OF DB (this is done initially already)
37 | - Documentation
38 | - Write up geektool integration - create section on integration with other tools - by objective e.g. tasks on the desktop, add to features - SCREENSHOT
39 | - Document How I use it
40 | - document any new filter variables
41 | - Architecture
42 | - Tips on perspectives and casting blobs - investigate
43 | forums.omnigroup.com/showthread.php?t=29538
44 | - Assertions to prevent mis-wiring of type hierarchies
45 | - More general config - e.g. global date format?
46 | - A filter that prepends type to items - for debugging
47 | - Scan for #xxx in the text and add tp tag?
48 | - Filter to merge projects folders etc if they have same name?
49 | - Allow +-3d, 2w etc?
50 | - Dump OF schema programatically
51 | - Create a taggable dump of projects as files with links to corresponing Omnifocus entities - for hazel and openmeta scripts
52 | file format: fld-fld-…-proj
53 | - Read other file types such as Taskpaper/OPML (done json)
54 | - Resolve utf8/ascii issues - not sure what's going on - redirecting stdout "changes things" as does invocation from applescript?!?!?!
55 | - look for OFEXPORT_HOME in the environment first
56 | - Testing
57 | - test all the assertions properly with unit tests (done most of them)
58 | - Install it myself separately from my dev environment
59 | - Update the paths accordingly
60 | - Releasing
61 | - Save released versions, tag? Make script
62 | - From Google Chrome - markokaestner/ofcloud · GitHub
63 | https://github.com/markokaestner/ofcloud
64 | - ondue-30min?
65 | - macros
66 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 3.0.5
2 |
--------------------------------------------------------------------------------
/bin/ofexport:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | ##################################################################################
4 | # CHANGE "OFEXPORT_HOME" TO MATCH WHERE YOU'VE INSTALLED THE ofexport/... FOLDER #
5 | ##################################################################################
6 |
7 | if [ -z "$OFEXPORT_HOME" ]; then
8 | echo
9 | echo OOPS!
10 | echo
11 | echo You don\'t have an OFEXPORT_HOME environment variable set.
12 | echo
13 | echo Installation instructions can be found here: 'https://github.com/psidnell/ofexport/blob/master/DOCUMENTATION.md#downloadinstallation'
14 | echo
15 | exit 1
16 | fi
17 |
18 | if [ ! -f $OFEXPORT_HOME/src/main/python/ofexport.py ]; then
19 | echo
20 | echo OOPS!
21 | echo
22 | echo OFEXPORT_HOME=$OFEXPORT_HOME
23 | echo "\"$OFEXPORT_HOME\"" does not seem to the correct installation location,
24 | echo
25 | echo Installation instructions can be found here: 'https://github.com/psidnell/ofexport/blob/master/DOCUMENTATION.md#downloadinstallation'
26 | echo
27 | exit 1
28 | fi
29 |
30 | python $OFEXPORT_HOME/src/main/python/ofexport.py "$@"
31 |
32 |
--------------------------------------------------------------------------------
/build-scripts/dump-test-data:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ofexport -f='^Test' -C -c="^Test" -o ~/Desktop/OF.json "$@"
3 |
--------------------------------------------------------------------------------
/build-scripts/examples:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | #set -o xtrace
4 | #C
5 | #C This file contains the examples as tests an then creates the examples.md file from itself
6 | #C Lines beginning with "set", "diff", '#!', '#C' are ignored
7 | #C Lines beginning with '#' have the # removed
8 |
9 | ## Usage Examples
10 |
11 | #C ofexport -f='^Test' -C -c="^Test" -o src/test/data/db-5.json
12 |
13 | #This produces a document containing all tasks completed last week from any folder with "Worms" in it's title:
14 |
15 | ofexport -f=Worms -t "done='last week'" -a prune -o target/ex1-worms-last-week.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
16 | diff target/ex1-worms-last-week.taskpaper src/test/data/ex1-worms-last-week.taskpaper
17 |
18 | #This uses a little regular expression magic to create a document containing all tasks completed today from any folder with the exact name "Ham":
19 |
20 | ofexport -f='^Ham$' -t done='today' -a prune -o target/ex2-ham-today.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
21 | diff target/ex2-ham-today.taskpaper src/test/data/ex2-ham-today.taskpaper
22 |
23 | #This produces a document containing all tasks completed today from any folder that does NOT have "Worms" in it's title:
24 |
25 | ofexport -E -f=Worms -a prune -o target/ex3-not-worms.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
26 | diff target/ex3-not-worms.taskpaper src/test/data/ex3-not-worms.taskpaper
27 |
28 | #This uses a little regular expression magic to create a document containing all tasks completed today from any folder with the exact name "Worms" or "Ham":
29 |
30 | ofexport -f='^Worms$|^Ham$' -t done='today' -a prune -o target/ex4-worms-or-ham-today.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
31 | diff target/ex4-worms-or-ham-today.taskpaper src/test/data/ex4-worms-or-ham-today.taskpaper
32 |
33 | #This produces a document containing all tasks completed this week from any folder with "Worms" in it's title and then flattens/simplifies the indenting:
34 |
35 | ofexport -f=Worms -a flatten -a prune -o target/ex5-work-this-week-flat.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
36 | diff target/ex5-work-this-week-flat.taskpaper src/test/data/ex5-work-this-week-flat.taskpaper
37 |
38 | #This produces the report of what I have yet to do on this project:
39 |
40 | ofexport -o TODO.md -f='"ofexport"' -f flatten -a prune -E -a done=any
41 |
42 | #This produces the report of all uncompleted tasks that are flagged or due soon:
43 |
44 | ofexport -E -a done=any -I -t "flagged or (due='to tomorrow')" -o target/ex8-due-or-flagged.taskpaper --debug now=2013-04-29 -i src/test/data/db-5.json
45 | diff target/ex8-due-or-flagged.taskpaper src/test/data/ex8-due-or-flagged.taskpaper
46 |
47 | #This produces a calendar file containing any items with start/due dates:
48 |
49 | ofexport -o target/ex9.ics --debug now=2013-04-29 -i src/test/data/db-5.json -i src/test/data/db-5.json
50 | diff target/ex9.ics src/test/data/ex9.ics
51 |
52 | cat build-scripts/examples | egrep -v -e '^cat|^diff|^#C|^#!' -e '^set' | sed -e 's/^#//' -e 's/--debug.*//' -e 's/target/\/tmp/' > documentation/examples.md
53 |
54 |
--------------------------------------------------------------------------------
/build-scripts/generate-sources:
--------------------------------------------------------------------------------
1 | python build-scripts/generate_help.py $@ > src/main/python/help.py
2 | echo $1 > VERSION
3 |
--------------------------------------------------------------------------------
/build-scripts/generate_help.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import re
3 | import string
4 | import sys
5 | import datetime
6 |
7 | regexp = re.compile ('\{\{|\}\}')
8 |
9 | def process_line (line, short_opts, long_opts):
10 | reassembled = []
11 | pieces = regexp.split (line)
12 | if pieces != None:
13 | for piece in pieces:
14 | if piece.startswith ('--'):
15 | long_opts.append (string.replace (piece, '-', ''))
16 | piece = string.replace (piece, '=', '')
17 | elif piece.startswith ('-'):
18 | short_opts.append(string.replace (piece, '-', ''))
19 | piece = string.replace (piece, ':', '')
20 | reassembled.append (piece)
21 | return ''.join (reassembled)
22 |
23 | instream=codecs.open('templates/help-template.txt', 'r', 'utf-8')
24 |
25 | short_opts = []
26 | long_opts = []
27 |
28 | print "# THIS FILE IS AUTOGENERATED"
29 | print
30 | print 'def print_help ():'
31 | print " print 'Version: " + sys.argv[1] + ' ' + datetime.date.strftime(datetime.date.today(), '%Y-%m-%d') + "'"
32 | for line in instream:
33 | processed_line = process_line (line.rstrip (), short_opts, long_opts)
34 | if len (processed_line.strip()) > 0:
35 | print " print '" + processed_line + "'"
36 | else:
37 | print " print"
38 |
39 | print
40 | print "SHORT_OPTS = '" + ''.join(short_opts) + "'"
41 | print "LONG_OPTS = ['" + "','".join(long_opts) + "']"
42 |
43 | instream.close ()
--------------------------------------------------------------------------------
/build-scripts/pretty_json.py:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/python
2 | import fileinput
3 | import json
4 | if __name__ == "__main__":
5 | jsonStr = ''
6 | for a_line in fileinput.input():
7 | jsonStr = jsonStr + ' ' + a_line.strip()
8 | jsonObj = json.loads(jsonStr)
9 | print json.dumps(jsonObj, sort_keys=True, indent=4)
10 |
--------------------------------------------------------------------------------
/build-scripts/unit-test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -o xtrace
4 | set -e
5 | export PYTHONPATH=`pwd`/src/main/python
6 |
7 | ################
8 | echo UNIT TESTS
9 | ################
10 |
11 | python -m unittest discover -s src/test/python -p '*_test.py'
--------------------------------------------------------------------------------
/documentation/Browser-thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/Browser-thumb.jpg
--------------------------------------------------------------------------------
/documentation/GeekToolScreenGrab-thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/GeekToolScreenGrab-thumb.jpg
--------------------------------------------------------------------------------
/documentation/Hazel.md:
--------------------------------------------------------------------------------
1 | # Hazel Integration
2 |
3 | ## Rules
4 |
5 | Currently OmniFocus keeps it's database in one of two places, depending on how it was purchased:
6 |
7 | * Library/Caches/com.omnigroup.OmniFocus/OmniFocusDatabase2
8 | * Library/Caches/com.omnigroup.OmniFocus.MacAppStore/OmniFocusDatabase2
9 |
10 | Add the correct path as a watched directory in Hazel and then set up the rules:
11 |
12 | 
13 |
14 | Here I have attempted to avoid the rule being run too frequently or too quickly after OmniFocus has saved the file to avoid the script running when I'm doing a lot work in OmniFocus. If the script runs too busily it can slow your machine down and cause OmniFocus problems by holding a lock on it's database for long periods. This recipe seems to be a workable compromise.
15 |
16 | ## Script
17 |
18 | 
19 |
20 | Scripts run from Hazel don't execute in the same environment as your command line and your **$PATH** variable may not be set.
21 |
22 | Depending on how your environment is set up, you may need to explicitly source your **.bashrc** (as above) or your **.bash_profile** (if you have one) in the embedded script or any sub-script it's referencing.
23 |
24 | Embedding diagnostics in your hazel embedded script to dump the environment might help if you're running into trouble:
25 |
26 | env > ~/Desktop/env.txt
27 |
--------------------------------------------------------------------------------
/documentation/HazelRules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/HazelRules.png
--------------------------------------------------------------------------------
/documentation/HazelRulesScript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/HazelRulesScript.png
--------------------------------------------------------------------------------
/documentation/TaskPaper-thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/TaskPaper-thumb.jpg
--------------------------------------------------------------------------------
/documentation/cal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/cal.png
--------------------------------------------------------------------------------
/documentation/diagram/Android.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Android.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image1.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image10.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image10.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image11.png
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image12.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image12.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image13.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image13.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image14.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image14.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image15.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image15.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image16.png
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image17.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image17.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image18.jpg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image20.png
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image3.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image4.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image4.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image5.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image5.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image6.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image6.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image7.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image7.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image8.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image8.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.graffle/image9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.graffle/image9.png
--------------------------------------------------------------------------------
/documentation/diagram/Diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Diagram.png
--------------------------------------------------------------------------------
/documentation/diagram/DropBox.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/DropBox.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Firefox.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Firefox.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/FoldingText.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/FoldingText.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/GeekTool.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/GeekTool.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Hazel.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Hazel.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/Marked.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Marked.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/MindNode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/MindNode.png
--------------------------------------------------------------------------------
/documentation/diagram/Note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/Note.png
--------------------------------------------------------------------------------
/documentation/diagram/OmniFocus.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/OmniFocus.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/OmniOutliner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/OmniOutliner.png
--------------------------------------------------------------------------------
/documentation/diagram/TaskPaper.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/TaskPaper.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/WebCloud.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/WebCloud.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/WinPhone.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/WinPhone.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/cog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/cog.png
--------------------------------------------------------------------------------
/documentation/diagram/iCal.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/iCal.jpeg
--------------------------------------------------------------------------------
/documentation/diagram/text.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/psidnell/ofexport/644c63c6769c7da1db8ab9fce84a94d2be3dddc9/documentation/diagram/text.jpeg
--------------------------------------------------------------------------------
/documentation/examples.md:
--------------------------------------------------------------------------------
1 | set -o xtrace
2 |
3 | # Usage Examples
4 |
5 |
6 | This produces a document containing all tasks completed last week from any folder with "Worms" in it's title:
7 |
8 | ofexport -f=Worms -t "done='last week'" -a prune -o /tmp/ex1-worms-last-week.taskpaper
9 |
10 | This uses a little regular expression magic to create a document containing all tasks completed today from any folder with the exact name "Ham":
11 |
12 | ofexport -f='^Ham$' -t done='today' -a prune -o /tmp/ex2-ham-today.taskpaper
13 |
14 | This produces a document containing all tasks completed today from any folder that does NOT have "Worms" in it's title:
15 |
16 | ofexport -E -f=Worms -a prune -o /tmp/ex3-not-worms.taskpaper
17 |
18 | This uses a little regular expression magic to create a document containing all tasks completed today from any folder with the exact name "Worms" or "Ham":
19 |
20 | ofexport -f='^Worms$|^Ham$' -t done='today' -a prune -o /tmp/ex4-worms-or-ham-today.taskpaper
21 |
22 | This produces a document containing all tasks completed this week from any folder with "Worms" in it's title and then flattens/simplifies the indenting:
23 |
24 | ofexport -f=Worms -a flatten -a prune -o /tmp/ex5-work-this-week-flat.taskpaper
25 |
26 | This produces the report of what I have yet to do on this project:
27 |
28 | ofexport -o TODO.md -f='"ofexport"' -f flatten -a prune -E -a done=any
29 |
30 | This produces the report of all uncompleted tasks that are flagged or due soon:
31 |
32 | ofexport -E -a done=any -I -t "flagged or (due='to tomorrow')" -o /tmp/ex8-due-or-flagged.taskpaper
33 |
34 | This produces a calendar file containing any items with start/due dates:
35 |
36 | ofexport -o /tmp/ex9.ics
37 |
38 |
39 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | function intro () {
6 | clear
7 | echo INTRODUCTION
8 | cat LICENSE.MD | grep -v '# Lic'
9 | echo
10 | echo What this installer does:
11 | echo
12 | echo " "1. Make sure you\'re running an appropriate version of python.
13 | echo " "2. Sets execute permission on `pwd`/bin/ofexport.
14 | echo " "3. Prints instructions on how to update your environment to add this command to your \$PATH variable.
15 | echo
16 | echo You can re-run this script as many times as you like to get the instructions again.
17 | echo
18 | echo More help can be found here:
19 | echo
20 | echo 'https://github.com/psidnell/ofexport/blob/master/DOCUMENTATION.md'
21 | echo
22 | echo If you\'re happy with this type return to continue.
23 | echo Otherwise if you\'re in any way unsure type ctrl + C to quit or simply exit the Terminal app.
24 | read
25 | }
26 |
27 | function python_check () {
28 | PVERSION=`python -V 2>&1 | sed -e 's/.* //'`
29 | if [[ $PVERSION != 2.7.* ]]; then
30 | clear
31 | echo WARNING: PYTHON PVERSION CHECK FAILURE
32 | echo
33 | echo You have python version $PVERSION
34 | echo
35 | echo ofexport was tested against python 2.7.2 which comes with Mountain Lion and
36 | echo while it requires at least some revision of 2.7 it may work with newer versions.
37 | echo
38 | echo Type return to continue of ctrl + C to exit or simply exit the Terminal app.
39 | exit 1
40 | fi
41 | }
42 |
43 |
44 | function fix_permissions () {
45 | chmod +x bin/ofexport
46 | }
47 |
48 | function create_example_bashrc () {
49 | echo > example-bashrc
50 | echo export OFEXPORT_HOME=\"`pwd`\" >> example-bashrc
51 | echo export PATH=\$PATH:\"\$OFEXPORT_HOME/bin\" >> example-bashrc
52 | }
53 | function upgrade () {
54 | clear
55 | fix_permissions
56 | create_example_bashrc
57 | echo UPGRADE INSTRUCTIONS
58 | echo
59 | echo It looks like everything is already correctly configured.
60 | echo
61 | }
62 |
63 | function upgrade_and_move () {
64 | clear
65 | fix_permissions
66 | create_example_bashrc
67 | echo CHANGING INSTALL LOCATION INSTRUCTIONS
68 | echo
69 | echo It looks like you\'re installing to a new location since you
70 | echo already have your OFEXPORT_HOME environment variable set to:
71 | echo
72 | echo OFEXPORT_HOME=\"$OFEXPORT_HOME\"
73 | echo
74 | echo If you want to use this new location you will have to edit your \".bashrc\"
75 | echo file and modify it to contain the following two lines instead:
76 | cat example-bashrc
77 | echo
78 | echo You may re-run this installer once you have modified your environment
79 | echo and it should detect the correct configuration.
80 | echo
81 | }
82 |
83 | function install () {
84 | clear
85 | fix_permissions
86 | create_example_bashrc
87 | echo FRESH INSTALL INSTRUCTIONS
88 | echo
89 | echo 1. You will need the following two lines in a file called \".bashrc\"
90 | echo " "\(if you have one\) in your home folder:
91 | cat example-bashrc
92 | echo
93 | echo " "You can update/create the file yourself or type:
94 | echo
95 | echo 'cat example-bashrc >> ~/.bashrc'
96 | echo
97 | echo 2. To refresh your current environment with these changes type:
98 | echo
99 | echo '. ~/.bashrc'
100 | echo
101 | echo 3. Finally as a test type:
102 | echo
103 | echo ofexport -?
104 | echo
105 | echo " "and it should print it\'s help.
106 | echo
107 | echo You may re-run this installer once you have modified your environment
108 | echo and it should detect the correct configuration.
109 | echo
110 | }
111 |
112 | ########################################
113 |
114 | intro
115 |
116 | python_check
117 |
118 | if [ "$OFEXPORT_HOME" == "`pwd`" ]; then
119 | upgrade
120 | elif [ -n "$OFEXPORT_HOME" ]; then
121 | upgrade_and_move
122 | else
123 | install
124 | fi
125 |
--------------------------------------------------------------------------------
/ofexport.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_search_path": [
3 | "/Library/Containers/com.omnigroup.OmniFocus2/Data/Library/Caches/com.omnigroup.OmniFocus2/OmniFocusDatabase2",
4 | "/Library/Caches/com.omnigroup.OmniFocus/OmniFocusDatabase2",
5 | "/Library/Caches/com.omnigroup.OmniFocus.MacAppStore/OmniFocusDatabase2"
6 | ],
7 | "file_types": {
8 | "HTML": {
9 | "plugin": "html",
10 | "suffixes": [
11 | "htm",
12 | "html"
13 | ],
14 | "template": "html"
15 | },
16 | "ICS": {
17 | "_1": "time_control_default can contain the magic that you can put in a note to control when the item appears in the calendar",
18 | "_2": "The time_control_default is only used if there is no '%of cal' directive in the node",
19 | "plugin": "ics",
20 | "suffixes": [
21 | "ics"
22 | ],
23 | "template": "ics",
24 | "time_control_default": "ignored unless it is of the right format"
25 | },
26 | "JSON": {
27 | "call_fn": "json",
28 | "plugin": "json",
29 | "suffixes": [
30 | "json"
31 | ]
32 | },
33 | "MARKDOWN": {
34 | "plugin": "markdown",
35 | "suffixes": [
36 | "md",
37 | "markdown",
38 | "ft",
39 | "foldingtext"
40 | ],
41 | "template": "markdown"
42 | },
43 | "OPML": {
44 | "plugin": "opml",
45 | "suffixes": [
46 | "opml"
47 | ],
48 | "template": "opml"
49 | },
50 | "STDOUT": {
51 | "plugin": "text",
52 | "suffixes": [
53 | ""
54 | ],
55 | "template": "text"
56 | },
57 | "TASKPAPER": {
58 | "plugin": "taskpaper",
59 | "suffixes": [
60 | "tp",
61 | "taskpaper",
62 | "todo"
63 | ],
64 | "template": "taskpaper"
65 | },
66 | "TODOTXT": {
67 | "plugin": "todotxt",
68 | "suffixes": [
69 | "todotxt"
70 | ],
71 | "template": "todotxt"
72 | },
73 | "TEXT": {
74 | "plugin": "text",
75 | "suffixes": [
76 | "text",
77 | "txt"
78 | ],
79 | "template": "text"
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
' + self.escape(item.name) + ''
55 | print >>out, ''
56 | print >>out, ''
57 | out.close ()
58 | def escape (self, val):
59 | return val.replace('"','"').replace('&','&').replace('<','<').replace('>','>')
60 |
61 | if __name__ == "__main__":
62 |
63 | root_projects_and_folders, root_contexts = build_model (find_database ())
64 |
65 | traverse_list (GenerateTagDBVisitor (), root_projects_and_folders)
66 | traverse_list (GenerateTagDBVisitor (), root_contexts, project_mode=False)
--------------------------------------------------------------------------------
/src/main/python/datematch.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | import re
18 | from datetime import datetime, timedelta
19 |
20 | def hunt_for_day (now, dow, forward, match_today = False):
21 | direction = -1
22 | start = 1
23 | if forward:
24 | direction = 1
25 | if match_today:
26 | start = 0
27 | for days in range (start, 8):
28 | next_date = now + timedelta(days=days*direction)
29 | next_dow = next_date.strftime ('%A').lower()
30 | if next_dow.startswith (dow):
31 | return next_date
32 | return None
33 |
34 | DATE_FMT = '%Y-%m-%d'
35 |
36 | def find_first_of_month (now):
37 | year = now.year
38 | month = now.month
39 | return datetime.strptime(str(year) + '-' + str(month) + '-01', DATE_FMT)
40 |
41 | def find_next_month (now):
42 | in_next_month = find_first_of_month (now) + timedelta(days=33)
43 | return find_first_of_month (in_next_month)
44 |
45 | def find_prev_month (now):
46 | in_last_month = find_first_of_month (now) + timedelta(days=-1)
47 | return find_first_of_month (in_last_month)
48 |
49 | def find_end_of_month (now):
50 | first_of_this_month = find_first_of_month (now)
51 | first_of_next_month = find_next_month (first_of_this_month)
52 | return first_of_next_month - timedelta (days=1)
53 |
54 | def hunt_for_month (now, month_to_find, forward, match_this_month = False):
55 | month = None
56 | if not match_this_month:
57 | if forward:
58 | month = find_next_month (now)
59 | else:
60 | month = find_prev_month (now)
61 | else:
62 | month = find_first_of_month (now)
63 |
64 | for i in range (0, 12):
65 | i = i # stop warning
66 | month_name = month.strftime ('%B').lower()
67 | if month_name.startswith (month_to_find):
68 | return month
69 | if forward:
70 | month = find_next_month (month)
71 | else:
72 | month = find_prev_month (month)
73 | return None
74 |
75 | def find_monday_this_week (now):
76 | return hunt_for_day (now, 'mo', False, match_today=True)
77 |
78 | def find_monday_next_week (now):
79 | return hunt_for_day (now, 'mo', True, match_today=False)
80 |
81 | def find_january_this_year (now):
82 | year = now.year
83 | return datetime.strptime(str(year) + '-01-01', '%Y-%m-%d')
84 |
85 | def date_from_string (now, date_str):
86 | if date_str == "today":
87 | return now
88 | elif date_str == 'yesterday':
89 | return now + timedelta(days=-1)
90 | elif date_str == 'tomorrow':
91 | return now + timedelta(days=1)
92 | elif re.match ('^next (mo|tu|we|th|fr|sa|su)', date_str) != None:
93 | dow = date_str[4:].strip ()
94 | next_monday = find_monday_next_week (now)
95 | return hunt_for_day (next_monday, dow, True, match_today=True)
96 | elif re.match ('^last (mo|tu|we|th|fr|sa|su)', date_str) != None:
97 | dow = date_str[4:].strip ()
98 | this_monday = find_monday_this_week (now)
99 | return hunt_for_day (this_monday, dow, False)
100 | elif re.match ('^(mo|tu|we|th|fr|sa|su)', date_str) != None:
101 | dow = date_str.strip ()
102 | monday = find_monday_this_week (now)
103 | return hunt_for_day (monday, dow, True, match_today=True)
104 | elif re.match ('^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', date_str) != None:
105 | month = date_str.strip ()
106 | jan = find_january_this_year (now)
107 | return hunt_for_month (jan, month, True, match_this_month=True)
108 | elif re.match ('^next (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', date_str) != None:
109 | month_str = date_str[4:].strip ()
110 | jan = find_january_this_year (now)
111 | month = hunt_for_month (jan, month_str, True, match_this_month=True).month
112 | year = now.year
113 | return datetime.strptime(str(year + 1) + '-' + str(month) + '-01', '%Y-%m-%d')
114 | elif re.match ('^last (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', date_str) != None:
115 | month_str = date_str[4:].strip ()
116 | jan = find_january_this_year (now)
117 | return hunt_for_month (jan, month_str, False, match_this_month=False)
118 | elif re.match ('^[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]$', date_str) != None:
119 | return datetime.strptime(date_str, '%Y-%m-%d')
120 | else:
121 | return None
122 |
123 | def tidy_space_separated_fields (string):
124 | # eliminate multiple spaces
125 | elements = string.split ()
126 | return ' '.join(elements)
127 |
128 | def process_date_specifier (now, date_spec):
129 | date_spec = tidy_space_separated_fields (date_spec).lower()
130 |
131 | if date_spec == 'none':
132 | return (None, None, date_spec)
133 | if date_spec == 'any':
134 | return (None, None, date_spec)
135 |
136 | match_date = date_from_string (now, date_spec)
137 | if match_date != None:
138 | # We've found a single stand alone date, not a range.
139 | # But if it's a month we want to convert it into a range
140 |
141 | if re.match ('(next |last )?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', date_spec) != None:
142 | return (match_date, find_end_of_month (match_date), date_spec)
143 | else:
144 | return (match_date, match_date, date_spec)
145 |
146 | if date_spec == 'this week':
147 | mon = find_monday_this_week (now)
148 | sun = hunt_for_day (now, 'sun', True, match_today=True)
149 | return (mon, sun, date_spec)
150 | elif date_spec == 'next week':
151 | mon = find_monday_this_week (now) + timedelta(days=7)
152 | sun = hunt_for_day (now, 'sun', True, match_today=True) + timedelta(days=7)
153 | return (mon, sun, date_spec)
154 | elif date_spec == 'last week':
155 | mon = find_monday_this_week (now) - timedelta(days=7)
156 | sun = hunt_for_day (now, 'sun', True, match_today=True) - timedelta(days=7)
157 | return (mon, sun, date_spec)
158 | elif date_spec.startswith ('from '):
159 | date_str = date_spec[4:].strip()
160 | start = date_from_string (now, date_str)
161 | return (start, None, date_spec)
162 | elif date_spec.startswith ('to '):
163 | date_str = date_spec[2:].strip()
164 | end = date_from_string (now, date_str)
165 | if re.match ('.*(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', date_str) != None:
166 | end = find_end_of_month (end)
167 | return (None, end, date_spec)
168 | elif re.search (' to ', date_spec) != None:
169 | elements = str.split (date_spec, ' to ')
170 | elements = [x.strip() for x in elements if len (x) > 0]
171 | start = date_from_string(now, elements[0])
172 | end = date_from_string(now, elements[1])
173 | if re.match ('.*(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', elements[1]) != None:
174 | end = find_end_of_month (end)
175 | return (start, end, date_spec)
176 | else:
177 | raise Exception ('I don\'t think "' + date_spec + '" is any kind of date specification I recognise')
178 |
179 | def match_date_against_range (thedate, date_range):
180 | if date_range == None:
181 | return thedate != None
182 | start, end, spec = date_range
183 | if spec == 'none':
184 | return thedate == None
185 | elif spec == 'any':
186 | return thedate != None
187 | elif thedate == None:
188 | return False
189 | elif start != None and end != None:
190 | return thedate.date() >= start.date() and thedate.date() <= end.date ()
191 | elif start != None:
192 | return thedate.date() >= start.date()
193 | else:
194 | return thedate.date() <= end.date ()
195 |
196 | def date_range_to_str (rng):
197 | start, end, spec = rng
198 | if spec == 'none' or spec == 'any':
199 | return spec
200 | elif start == None and end != None:
201 | return '..' + end.strftime (DATE_FMT)
202 | elif start != None and end == None:
203 | return start.strftime (DATE_FMT) + '..'
204 | elif start == end:
205 | return start.strftime (DATE_FMT)
206 | else:
207 | return start.strftime (DATE_FMT) + '..' + end.strftime (DATE_FMT)
--------------------------------------------------------------------------------
/src/main/python/fmt_template.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 | import os
17 | from string import Template
18 | from treemodel import Visitor, traverse_list
19 | import codecs
20 | import logging
21 | from attrib_convert import AttribMapBuilder, Conversion
22 |
23 | logger = logging.getLogger(__name__)
24 |
25 | def load_resource (template_dir, name):
26 | instream=codecs.open(template_dir + name, 'r', 'utf-8')
27 | resource = instream.read()
28 | instream.close ()
29 | return resource
30 |
31 | class FmtTemplate:
32 | def __init__(self, data):
33 |
34 | self.attrib_map_builder = AttribMapBuilder ()
35 | for attrib in data['attributes'].keys():
36 | attrib_cfg = data['attributes'][attrib]
37 | evaluate = None
38 | if 'eval' in attrib_cfg:
39 | evaluate = attrib_cfg['eval']
40 | conversion = Conversion (attrib, attrib_cfg['default'], attrib_cfg['format'], attrib_cfg['type'], evaluate=evaluate)
41 | self.attrib_map_builder.set_conversion(conversion)
42 | self.attrib_map_builder.date_format = data['dateFormat']
43 |
44 | self.preamble = None
45 | self.postamble = None
46 | self.indent_start = data['indent']
47 | self.depth_start = data['depth']
48 | self.indent = data['indentString']
49 | self.nodes = {k:Template(v) for (k,v) in data['nodes'].items()}
50 | self.date_format = data['dateFormat']
51 | template_dir = os.environ['OFEXPORT_HOME'] + '/templates/'
52 | if 'preambleFile' in data:
53 | self.preamble = load_resource (template_dir, data['preambleFile'])
54 | elif 'preamble' in data:
55 | self.preamble = data['preamble']
56 | if 'postambleFile' in data:
57 | self.postamble = load_resource (template_dir, data['postambleFile'])
58 | elif 'postamble' in data:
59 | self.postamble = data['postamble']
60 |
61 | class Formatter(Visitor):
62 | def __init__ (self, out, template):
63 | self.template = template
64 | self.depth = self.template.indent_start
65 | self.traversal_depth = self.template.depth_start
66 | self.out = out
67 | def begin_folder (self, folder):
68 | self.update_attribs(folder, 'folder')
69 | line = format_item (self.template, 'FolderStart', folder.attribs['attrib_cache'])
70 | if line != None:
71 | print >>self.out, line
72 | self.depth+=1
73 | self.traversal_depth+=1
74 | def end_folder (self, folder):
75 | self.depth-=1
76 | self.traversal_depth-=1
77 | self.update_attribs(folder, 'folder')
78 | line = format_item (self.template, 'FolderEnd', folder.attribs['attrib_cache'])
79 | if line != None:
80 | print >>self.out, line
81 | def begin_project (self, project):
82 | self.update_attribs(project, 'task')
83 | line = format_item (self.template, 'ProjectStart', project.attribs['attrib_cache'])
84 | if line != None:
85 | print >>self.out, line
86 | self.handle_note (project)
87 | self.depth+=1
88 | self.traversal_depth+=1
89 | def end_project (self, project):
90 | self.depth-=1
91 | self.traversal_depth-=1
92 | self.update_attribs(project, 'task')
93 | line = format_item (self.template, 'ProjectEnd', project.attribs['attrib_cache'])
94 | if line != None:
95 | print >>self.out, line
96 | def begin_task (self, task):
97 | self.update_attribs(task, 'task')
98 | if self.is_empty (task) or self.project_mode == False:
99 | line = format_item (self.template, 'TaskStart', task.attribs['attrib_cache'])
100 | if line != None:
101 | print >>self.out, line
102 | self.handle_note (task)
103 | else:
104 | line = format_item (self.template, 'TaskGroupStart', task.attribs['attrib_cache'])
105 | if line != None:
106 | print >>self.out, line
107 | self.handle_note (task)
108 | self.depth+=1
109 | self.traversal_depth+=1
110 | def end_task (self, task):
111 | self.depth-=1
112 | self.traversal_depth-=1
113 | self.update_attribs(task, 'task')
114 | if self.is_empty (task) or self.project_mode == False:
115 | line = format_item (self.template, 'TaskEnd', task.attribs['attrib_cache'])
116 | if line != None:
117 | print >>self.out, line
118 | else:
119 | line = format_item (self.template, 'TaskGroupEnd', task.attribs['attrib_cache'])
120 | if line != None:
121 | print >>self.out, line
122 | def begin_context (self, context):
123 | self.update_attribs(context, 'context')
124 | line = format_item (self.template, 'ContextStart', context.attribs['attrib_cache'])
125 | if line != None:
126 | print >>self.out, line
127 | self.depth+=1
128 | self.traversal_depth+=1
129 | def end_context (self, context):
130 | self.depth-=1
131 | self.traversal_depth-=1
132 | self.update_attribs(context, 'context')
133 | line = format_item (self.template, 'ContextEnd', context.attribs['attrib_cache'])
134 | if line != None:
135 | print >>self.out, line
136 | def end_any (self, item):
137 | del item.attribs['attrib_cache']
138 | def is_empty (self, item):
139 | return len ([x for x in item.children if x.marked]) == 0
140 | def handle_note (self, item):
141 | if item.note != None and 'NoteLine' in self.template.nodes:
142 | for line in item.note.get_note_lines ():
143 | item.attribs['attrib_cache']['note_line'] = line
144 | print >>self.out, format_item (self.template, 'NoteLine', item.attribs['attrib_cache'])
145 | def update_attribs (self, item, link_type):
146 | if not 'attrib_cache' in item.attribs:
147 | attribs = self.template.attrib_map_builder.get_values (item)
148 | item.attribs['attrib_cache'] = attribs
149 | attribs['depth'] = str (self.traversal_depth)
150 | attribs['indent'] = self.template.indent * (self.depth)
151 | self.add_extra_template_attribs(item, attribs)
152 | def add_extra_template_attribs (self, item, attribs):
153 | pass
154 |
155 | def build_entry (line_template, attributes):
156 | return line_template.safe_substitute(attributes)
157 |
158 | def format_item (template, node_type, attributes):
159 | if node_type in template.nodes:
160 | return build_entry (template.nodes[node_type], attributes)
161 | return None
162 |
163 | def format_document (root, formatter, project_mode):
164 | if formatter.template.preamble != None:
165 | print >>formatter.out, formatter.template.preamble
166 | traverse_list (formatter, root.children, project_mode=project_mode)
167 | if formatter.template.postamble != None:
168 | print >>formatter.out, formatter.template.postamble
169 |
--------------------------------------------------------------------------------
/src/main/python/help.py:
--------------------------------------------------------------------------------
1 | # THIS FILE IS AUTOGENERATED
2 |
3 | def print_help ():
4 | print 'Version: 3.0.5 2014-02-23'
5 | print
6 | print 'Usage:'
7 | print
8 | print 'ofexport [options...] -o file_name'
9 | print
10 | print 'options:'
11 | print ' -h,-?,--help : print help'
12 | print ' -C : context mode (as opposed to project mode)'
13 | print ' -P : project mode - the default (as opposed to context mode)'
14 | print ' -I : include mode (as opposed to exclude mode)'
15 | print ' -E : exclude mode - the default (as opposed to include mode)'
16 | print ' -o file_name : the output file name, must end in a recognised suffix - see documentation'
17 | print ' -i file_name : read file_name instead of the OmniFocus database, must be in json format'
18 | print ' -T template_name : use the specified template instead of one derived from the output file extension'
19 | print ' --open : open the output file with the registered application (if one is installed)'
20 | print ' -v : verbose output'
21 | print ' -z : maximum diagnostics'
22 | print ' -V level : set the global log level (ERROR, INFO, DEBUG, TRACE)'
23 | print ' --log name=level : set a logger to a particular level'
24 | print ' --debug arg : set test options'
25 | print
26 | print 'filters:'
27 | print ' -a,--any expr : filter tasks, projects, contexts and folders against the expression'
28 | print ' -t,--task expr : filter any task against task against the expression'
29 | print ' -p,--project expr : filter any project against the expression'
30 | print ' -f,--folder expr : filter any folder against the expression'
31 | print ' -c,--context expr : filter any context type against the expression'
32 | print ' --tasks : filter out everything except tasks'
33 | print
34 | print ' See DOCUMENTATION.md for more information'
35 |
36 | SHORT_OPTS = 'h?CPIEo:i:T:vzV:a:t:p:f:c:'
37 | LONG_OPTS = ['help','open','log=','debug=','any=','task=','project=','folder=','context=','tasks']
38 |
--------------------------------------------------------------------------------
/src/main/python/ofexport.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | import os
18 | import codecs
19 | import getopt
20 | import sys
21 | import json
22 | from treemodel import traverse, Visitor, FOLDER, CONTEXT, PROJECT, TASK
23 | from omnifocus import build_model, find_database
24 | from datetime import date, datetime
25 | from plugin_json import read_json
26 | from help import print_help, SHORT_OPTS, LONG_OPTS
27 | from fmt_template import FmtTemplate
28 | from cmd_parser import make_filter
29 | import logging
30 | import cmd_parser
31 | from visitors import Tasks
32 |
33 | logging.basicConfig(format='%(asctime)-15s %(name)s %(levelname)s %(message)s', stream=sys.stdout)
34 | logger = logging.getLogger(__name__)
35 | logger.setLevel(level=logging.ERROR)
36 |
37 | LOGGER_NAMES = [
38 | __name__,
39 | 'cmd_parser',
40 | 'visitors',
41 | 'datematch',
42 | 'treemodel',
43 | 'omnifocus',
44 | 'fmt_template',
45 | 'plugin_ics']
46 |
47 | class SummaryVisitor (Visitor):
48 | def __init__ (self):
49 | self.counts = {}
50 | def end_any (self, item):
51 | if not 'counted' in item.attribs:
52 | item.attribs['counted'] = True
53 | if item.type in self.counts:
54 | self.counts[item.type] = self.counts[item.type] + 1
55 | else:
56 | self.counts[item.type] = 1
57 | def print_counts (self):
58 | # Subtract for the extra invisible roots that we've added.
59 | if CONTEXT in self.counts:
60 | self.counts[CONTEXT] = self.counts[CONTEXT]-1
61 | if FOLDER in self.counts:
62 | self.counts[FOLDER] = self.counts[FOLDER]-1
63 |
64 | logger.info ('Report Contents:')
65 | logger.info ('----------------')
66 | for k,v in [(k,self.counts[k]) for k in sorted(self.counts.keys())]:
67 | k = ' ' * (8 - len(k)) + k + 's:'
68 | logger.info (k + ' ' + str(v))
69 | logger.info ('----------------')
70 |
71 | def load_config (home_dir):
72 | logger.info ('loading config')
73 | instream=codecs.open(home_dir + '/ofexport.json', 'r', 'utf-8')
74 | config = json.loads(instream.read())
75 | instream.close ()
76 | return config
77 |
78 | def load_template (template_dir, name):
79 | logger.info ('loading template: %s', name)
80 | instream=codecs.open(template_dir + '/' + name + '.json', 'r', 'utf-8')
81 | template = FmtTemplate (json.loads(instream.read()))
82 | instream.close ()
83 | return template
84 |
85 | def fix_abbrieviated_expr (typ, arg):
86 | if arg.startswith ('=') or arg.startswith ('!='):
87 | if typ == 'any' or typ == 'all':
88 | result = 'name' + arg + ''
89 | else:
90 | result = '(type=' + typ + ') and (name' + arg + ')'
91 | elif arg in ['prune', 'flatten']:
92 | result = arg + ' ' + typ
93 | elif arg.startswith ('sort'):
94 | if arg == 'sort':
95 | result = arg + ' ' + typ + ' text'
96 | else:
97 | bits = arg.split ()
98 | assert len (bits) == 2, 'sort can have one field type argument'
99 | result = 'sort' + ' ' + typ + ' ' + bits[1]
100 | else:
101 | if typ == 'any' or typ == 'all':
102 | result = arg
103 | else:
104 | result = '(type=' + typ + ') and (' + arg + ')'
105 | logger.debug ("adapted argument: '%s'", result)
106 | return result
107 |
108 | def set_debug_opt (name, value):
109 | if name== 'now' :
110 | the_time = datetime.strptime (value, "%Y-%m-%d")
111 | cmd_parser.the_time = the_time
112 |
113 | if __name__ == "__main__":
114 | sys.stdout = codecs.getwriter('utf8')(sys.stdout)
115 |
116 | today = date.today ()
117 | time_fmt='%Y-%m-%d'
118 | opn=False
119 | project_mode=True
120 | file_name = None
121 | infile = None
122 | home_dir = os.environ['OFEXPORT_HOME']
123 | template_dir = home_dir + '/templates'
124 | include = True
125 |
126 | type_config_override_data = {}
127 |
128 | config = load_config (home_dir)
129 |
130 | opts, args = getopt.optlist, args = getopt.getopt(sys.argv[1:],SHORT_OPTS, LONG_OPTS)
131 |
132 | assert len (args) == 0, "unexpected arguments: " + str (args)
133 |
134 | for opt, arg in opts:
135 | if '--open' == opt:
136 | opn = True
137 | elif '-o' == opt:
138 | file_name = arg
139 | elif '-i' == opt:
140 | infile = arg
141 | elif '-T' == opt:
142 | type_config_override_data['template'] = arg
143 | elif '-v' == opt:
144 | for logname in LOGGER_NAMES:
145 | logging.getLogger(logname).setLevel (logging.INFO)
146 | elif '-V' == opt:
147 | level = arg
148 | for logname in LOGGER_NAMES:
149 | logging.getLogger(logname).setLevel (logging.__dict__[arg])
150 | elif '-z' == opt:
151 | for logname in LOGGER_NAMES:
152 | logging.getLogger(logname).setLevel (logging.DEBUG)
153 | elif '--log' == opt:
154 | bits = arg.split('=')
155 | assert len(bits) == 2
156 | name = bits[0]
157 | level = bits[1]
158 | if name=='ofexport':
159 | name = __name__
160 | logging.getLogger(name).setLevel (logging.__dict__[level])
161 | elif '--debug' == opt:
162 | bits = arg.split('=')
163 | assert len(bits) == 2
164 | name = bits[0]
165 | value = bits[1]
166 | set_debug_opt (name, value)
167 | elif opt in ('-?', '-h', '--help'):
168 | print_help ()
169 | sys.exit()
170 |
171 | if file_name == None:
172 | # This blank suffix is mapped to a plugin/template in the config
173 | fmt = ''
174 | else:
175 | assert file_name.find ('.') != -1, "filename has no suffix"
176 | dot = file_name.index ('.')
177 | fmt = file_name[dot+1:]
178 |
179 | if infile != None:
180 | root_project, root_context = read_json (infile)
181 | else:
182 | root_project, root_context = build_model (find_database (config['db_search_path']))
183 |
184 | subject = root_project
185 |
186 | for opt, arg in opts:
187 | logger.debug ("executing option %s : %s", opt, arg)
188 | visitor = None
189 | if opt in ('--project', '-p'):
190 | fixed_arg = fix_abbrieviated_expr(PROJECT, arg)
191 | visitor = make_filter (fixed_arg, include)
192 | elif opt in ('--task', '-t'):
193 | fixed_arg = fix_abbrieviated_expr(TASK, arg)
194 | visitor = make_filter (fixed_arg, include)
195 | elif opt in ('--context', '-c'):
196 | fixed_arg = fix_abbrieviated_expr(CONTEXT, arg)
197 | visitor = make_filter (fixed_arg, include)
198 | elif opt in ('--folder', '-f'):
199 | fixed_arg = fix_abbrieviated_expr(FOLDER, arg)
200 | visitor = make_filter (fixed_arg, include)
201 | elif opt in ('--any', '-a'):
202 | visitor = make_filter (fix_abbrieviated_expr('any', arg), include)
203 | elif opt in ('--tasks'):
204 | visitor = Tasks (root_project, root_context)
205 | elif '-C' == opt:
206 | logger.info ('context mode')
207 | project_mode = False
208 | subject = root_context
209 | elif '-P' == opt:
210 | logger.info ('project mode')
211 | project_mode = True
212 | subject = root_project
213 | elif '-I' == opt:
214 | logger.info ('include mode')
215 | include = True
216 | elif '-E' == opt:
217 | include = False
218 | logger.info ('exclude mode')
219 |
220 | logger.debug ("created filter %s", visitor)
221 | if visitor != None:
222 | logger.info ('running filter %s', visitor)
223 | traverse (visitor, subject, project_mode=project_mode)
224 |
225 | logger.info ('Generating: %s', file_name)
226 |
227 | if file_name != None:
228 | out=codecs.open(file_name, 'w', 'utf-8')
229 | else:
230 | out = sys.stdout
231 |
232 | generated = False
233 | file_types = config['file_types']
234 | for type_config in file_types.values():
235 | if not generated and fmt in type_config['suffixes']:
236 | plugin = 'plugin_' + type_config['plugin']
237 | m = __import__(plugin)
238 | type_config.update (type_config_override_data)
239 | m.generate(out, root_project, root_context, project_mode, template_dir, type_config)
240 | generated = True
241 |
242 | if not generated:
243 | raise Exception ('unknown format ' + fmt)
244 |
245 | if file_name != None:
246 | out.close()
247 | if opn:
248 | os.system("open '" + file_name + "'")
249 |
250 | visitor = SummaryVisitor ()
251 | traverse (visitor, root_project, project_mode=True)
252 | traverse (visitor, root_context, project_mode=False)
253 | visitor.print_counts()
254 |
--------------------------------------------------------------------------------
/src/main/python/plugin_html.py:
--------------------------------------------------------------------------------
1 | from fmt_template import Formatter, format_document
2 | from ofexport import load_template
3 |
4 | def escape (val):
5 | return val.replace('"','"').replace('&','&').replace('<','<').replace('>','>')
6 |
7 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
8 | subject = root_project if project_mode else root_context
9 | template = load_template (template_dir, type_config['template'])
10 |
11 | template.attrib_map_builder.type_fns['html.string'] = lambda x: escape (x)
12 | template.attrib_map_builder.type_fns['html.note'] = lambda x: ''.join([line+'
' for line in x.get_note_lines ()])
13 |
14 | visitor = Formatter (out, template)
15 | format_document (subject, visitor, project_mode)
--------------------------------------------------------------------------------
/src/main/python/plugin_ics.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from datetime import datetime, timedelta
18 | from fmt_template import Formatter, format_document
19 | import time
20 | import logging
21 | from ofexport import load_template
22 |
23 |
24 | logger = logging.getLogger(__name__)
25 |
26 | DATE_FORMAT_LONG = "%Y%m%dT%H%M00Z"
27 | DATE_FORMAT_SHORT = "%Y%m%d"
28 |
29 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
30 | subject = root_project if project_mode else root_context
31 | template = load_template (template_dir, type_config['template'])
32 | time_control_default = type_config["time_control_default"]
33 | logger.debug ("time_control_default: %s", time_control_default)
34 | visitor = PrintCalendarVisitor (out, template, time_control_default)
35 | format_document (subject, visitor, project_mode)
36 |
37 | class PrintCalendarVisitor(Formatter):
38 | def __init__ (self, out, template, time_control_default):
39 | Formatter.__init__(self, out, template)
40 | self.current_item = None
41 | self.time_control_default = time_control_default
42 |
43 | # Start and due dates are formated differently for all day tasks, need different types
44 | template.attrib_map_builder.type_fns['ics.date_due'] = lambda x: format_date(self.current_item, x, True)
45 | template.attrib_map_builder.type_fns['ics.date_to_start'] = lambda x: format_date(self.current_item, x, False)
46 | template.attrib_map_builder.type_fns['ics.note'] = lambda x: '\\r'.join(x.get_note_lines ())
47 |
48 | def begin_any (self, item):
49 | Formatter.begin_any(self, item)
50 | self.current_item = item
51 | def end_any (self, item):
52 | pass
53 | def begin_folder (self, folder):
54 | pass
55 | def end_folder (self, folder):
56 | pass
57 | def begin_project (self, project):
58 | if project.date_due != None or project.date_to_start != None:
59 | fix_dates(project)
60 | load_note_attribs (project, self.time_control_default)
61 | Formatter.begin_project(self, project)
62 | def end_project (self, project):
63 | if project.date_due != None or project.date_to_start != None:
64 | Formatter.end_project(self, project)
65 | def begin_task (self, task):
66 | if task.date_due != None or task.date_to_start != None:
67 | fix_dates(task)
68 | load_note_attribs (task, self.time_control_default)
69 | Formatter.begin_task(self, task)
70 | def end_task (self, task):
71 | if task.date_due != None or task.date_to_start != None:
72 | Formatter.end_task(self, task)
73 | def begin_context (self, context):
74 | pass
75 | def end_context (self, context):
76 | pass
77 | def add_extra_template_attribs (self, item, attribs):
78 | item.attribs['attrib_cache']['alarm'] = format_alarm (item)
79 |
80 | def fix_dates (item):
81 | if item.date_to_start == None and item.date_due == None:
82 | return
83 | if item.date_to_start == None and item.date_due != None:
84 | item.date_to_start = item.date_due
85 | elif item.date_to_start != None and item.date_due == None:
86 | item.date_due = item.date_to_start
87 |
88 | def load_note_attribs (item, time_control_default):
89 | logger.debug ("loading note attributes %s %s", item.id, item.name)
90 | found_directive = False
91 | if item.note != None:
92 | for line in item.note.get_note_lines ():
93 | found_directive = process_line_for_directives (item, line, found_directive)
94 | if not found_directive and len (time_control_default.strip()) > 0:
95 | found_directive = process_line_for_directives (item, time_control_default, found_directive)
96 |
97 | if found_directive:
98 | logger.debug ("attributes for %s are %s", item.id, item.attribs)
99 |
100 | if 'onstart' in item.attribs:
101 | item.date_due = item.date_to_start
102 | if 'ondue' in item.attribs:
103 | item.date_to_start = item.date_due
104 | try:
105 | if 'start' in item.attribs:
106 | bits = item.attribs['start'].split(':')
107 | the_date = item.date_to_start
108 | if len(bits) == 2:
109 | item.date_to_start = datetime (the_date.year, the_date.month, the_date.day, int(bits[0]), int(bits[1]), 0, 0, the_date.tzinfo)
110 | else:
111 | logger.error ("problem parsing cal directives, start malformed: %s %s", item.id, item.name)
112 | if 'due' in item.attribs:
113 | bits = item.attribs['due'].split(':')
114 | the_date = item.date_due
115 | if len(bits) == 2:
116 | item.date_due = datetime (the_date.year, the_date.month, the_date.day, int(bits[0]), int(bits[1]), 0, 0, the_date.tzinfo)
117 | else:
118 | logger.error ("problem parsing cal directives, due malformed: %s %s", item.id, item.name)
119 | except Exception as e:
120 | logger.error (e.message)
121 | logger.error ("problem parsing cal directives: %s %s", item.id, item.name)
122 | if item.date_to_start > item.date_due:
123 | logger.error ("problem parsing cal directives, the start date is after the due date: %s %s", item.id, item.name)
124 | item.date_to_start = item.date_due
125 | logger.debug ("final dates for %s are start:%s due:%s", item.id, item.date_to_start, item.date_due)
126 |
127 | def process_line_for_directives (item, line, found_directive):
128 | if line.strip().startswith('%of'):
129 | bits = line.split()
130 | if len(bits) >= 3 and bits[1] == 'cal':
131 | found_directive = True
132 | logger.debug ("found directive in %s %s", item.id, line)
133 | for flag in bits[2:]:
134 | bits2 = flag.split('=')
135 | if len(bits2) == 2:
136 | item.attribs[bits2[0]] = bits2[1]
137 | else:
138 | item.attribs[flag] = True
139 | return found_directive
140 |
141 | def format_date (item, the_date, is_due_date):
142 | if 'allday' in item.attribs:
143 | # Make all day - must have no hms in format
144 | #DTSTART;VALUE=DATE:20020923
145 | #DTEND;VALUE=DATE:20020924
146 | the_date = datetime (the_date.year, the_date.month, the_date.day, 0, 0, 0)
147 | if is_due_date:
148 | the_date = the_date + timedelta (days=1)
149 | # NO UTC CONVERSION - it happens on the day we asked for - no adjustment required
150 | result = the_date.strftime(DATE_FORMAT_SHORT)
151 | else:
152 | the_date = utc (the_date)
153 | result = the_date.strftime(DATE_FORMAT_LONG)
154 | typ = 'due' if is_due_date else 'start'
155 | logger.debug ("formatted date for %s is %s:%s", item.id, typ, result)
156 | return result
157 |
158 | def utc (the_date):
159 | epoch_second = time.mktime(the_date.timetuple())
160 | return datetime.utcfromtimestamp(epoch_second)
161 |
162 | def format_alarm (item):
163 | if "noalarm" in item.attribs:
164 | return ""
165 | return "BEGIN:VALARM\nACTION:DISPLAY\nDESCRIPTION:OmniFocus Reminder\nTRIGGER:-PT0M\nEND:VALARM\n"
--------------------------------------------------------------------------------
/src/main/python/plugin_json.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | import json
18 | import codecs
19 | from datetime import datetime
20 | from treemodel import Context, Project, Task, Folder, Note, CONTEXT, PROJECT, TASK, FOLDER
21 | from treemodel import traverse, Visitor
22 |
23 | TIME_FMT = "%Y-%m-%d %H:%M:%S"
24 |
25 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
26 | # json has intrinsic formatting - no template required
27 | root_project.marked = True
28 | root_context.marked = True
29 | visitor = ConvertStructureToJsonVisitor ()
30 | traverse (visitor, root_project, project_mode=True)
31 | visitor = ConvertStructureToJsonVisitor ()
32 | traverse (visitor, root_context, project_mode=False)
33 | print >> out, json.dumps([root_project.attribs['json_data'], root_context.attribs['json_data']], sort_keys=True, indent=2)
34 |
35 | def save_attrib (item, attrib, attribs, convert):
36 | if not attrib in item.__dict__:
37 | return
38 | value = item.__dict__[attrib]
39 | if value == None:
40 | return
41 | attribs[attrib] = convert (value)
42 |
43 | def load_attrib (item, attrib, attribs, convert):
44 | if not attrib in attribs:
45 | return
46 | value = attribs[attrib]
47 | item.__dict__[attrib] = convert (value)
48 |
49 | def get_note_lines (x):
50 | if x == None:
51 | return None
52 | return x.get_note_lines()
53 |
54 | class ConvertStructureToJsonVisitor(Visitor):
55 | def begin_any (self, item):
56 | if self.is_in_applicable_mode (item) and not 'json_data' in item.attribs:
57 | node_json_data = {}
58 | save_attrib (item, 'id', node_json_data, lambda x : x)
59 | save_attrib (item, 'link', node_json_data, lambda x : x)
60 | save_attrib (item, 'status', node_json_data, lambda x : x)
61 | save_attrib (item, 'name', node_json_data, lambda x : x)
62 | save_attrib (item, 'type', node_json_data, lambda x : x)
63 | save_attrib (item, 'date_completed', node_json_data, lambda x: x.strftime (TIME_FMT))
64 | save_attrib (item, 'date_to_start', node_json_data, lambda x: x.strftime (TIME_FMT))
65 | save_attrib (item, 'date_due', node_json_data, lambda x: x.strftime (TIME_FMT))
66 | save_attrib (item, 'flagged', node_json_data, lambda x : x)
67 | save_attrib (item, 'next', node_json_data, lambda x : x)
68 | save_attrib (item, 'note', node_json_data, lambda x : get_note_lines (x))
69 | save_attrib (item, 'order', node_json_data, lambda x : x)
70 | item.attribs['json_data'] = node_json_data
71 | def end_task (self, item):
72 | self.add_children(item)
73 | def end_project (self, item):
74 | self.add_children(item)
75 | def end_folder (self, item):
76 | self.add_children(item)
77 | def end_context (self, item):
78 | children_json_data = []
79 | for child in item.children:
80 | if child.marked and 'json_data' in child.attribs:
81 | child_json_data = child.attribs['json_data']
82 | if child.type == CONTEXT:
83 | children_json_data.append(child_json_data)
84 | else:
85 | # The name is just a debugging aid
86 | children_json_data.append({'ref' : child.id, 'name' : child.name })
87 | item.attribs['json_data']['children'] = children_json_data
88 | def is_in_applicable_mode (self, item):
89 | # Project ,ode items are written first,
90 | # Contexts second with refs to tasks/projects
91 | if self.project_mode:
92 | return item.type in (FOLDER, PROJECT, TASK)
93 | else:
94 | return item.type == CONTEXT
95 | def add_children (self, item):
96 | if self.is_in_applicable_mode (item):
97 | children_json_data = []
98 | for child in item.children:
99 | if child.marked:
100 | child_json_data = child.attribs['json_data']
101 | children_json_data.append(child_json_data)
102 | item.attribs['json_data']['children'] = children_json_data
103 |
104 | class JSONNote (Note):
105 | def __init__ (self, lines):
106 | self.lines = lines
107 | self.note = '\n'.join(lines)
108 | def get_note_lines (self):
109 | return self.lines
110 | def get_note (self):
111 | return self.note
112 |
113 | def load_from_json (json_data, item_db):
114 | if 'ref' in json_data:
115 | item = item_db[json_data['ref']]
116 | return item
117 |
118 | item_type = json_data['type']
119 | item_id = json_data['id']
120 | if item_type == FOLDER:
121 | item = Folder ()
122 | elif item_type == CONTEXT:
123 | item = Context ()
124 | elif item_type == TASK:
125 | item = Task ()
126 | elif item_type == PROJECT:
127 | item = Project ()
128 | load_attrib (item, 'id', json_data, lambda x: x)
129 | load_attrib (item, 'link', json_data, lambda x: x)
130 | load_attrib (item, 'status', json_data, lambda x: x)
131 | load_attrib (item, 'name', json_data, lambda x: x)
132 | load_attrib (item, 'date_completed', json_data, lambda x: datetime.strptime (x, TIME_FMT))
133 | load_attrib (item, 'date_to_start', json_data, lambda x: datetime.strptime (x, TIME_FMT))
134 | load_attrib (item, 'date_due', json_data, lambda x: datetime.strptime (x, TIME_FMT))
135 | load_attrib (item, 'flagged', json_data, lambda x: x)
136 | load_attrib (item, 'next', json_data, lambda x: x)
137 | load_attrib (item, 'note', json_data, lambda x: JSONNote (x))
138 | load_attrib (item, 'order', json_data, lambda x: x)
139 |
140 | for child_data in json_data['children']:
141 | child = load_from_json (child_data, item_db)
142 | item.add_child(child)
143 |
144 | item_db[item_id] = item
145 | return item
146 |
147 | def read_json (file_name):
148 | instream=codecs.open(file_name, 'r', 'utf-8')
149 | json_data = json.loads(instream.read())
150 | instream.close ()
151 |
152 | item_db = {}
153 | root_project = load_from_json (json_data[0], item_db)
154 | root_context = load_from_json (json_data[1], item_db)
155 |
156 | return root_project, root_context
157 |
158 |
--------------------------------------------------------------------------------
/src/main/python/plugin_markdown.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from fmt_template import Formatter, format_document
18 | from treemodel import PROJECT
19 | from ofexport import load_template
20 |
21 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
22 | subject = root_project if project_mode else root_context
23 | template = load_template (template_dir, type_config['template'])
24 | visitor = PrintMarkdownVisitor (out, template)
25 | format_document (subject, visitor, project_mode)
26 |
27 | class PrintMarkdownVisitor(Formatter):
28 | # All this nonsense is just to get the right number of
29 | # blank lines in the right places
30 | def __init__ (self, out, template):
31 | Formatter.__init__(self, out, template)
32 | self.header_depth = 0
33 | self.depth = 0
34 | self.out = out
35 | self.last_line_was_text = False
36 | def begin_folder (self, folder):
37 | is_output = 'FolderStart' in self.template.nodes
38 | if self.last_line_was_text and is_output:
39 | print >> self.out
40 | self.last_line_was_text = False
41 | Formatter.begin_folder(self, folder)
42 | if is_output:
43 | print >>self.out
44 | self.depth = 0
45 | self.header_depth+=1
46 | def begin_project (self, project):
47 | is_output = 'ProjectStart' in self.template.nodes
48 | if self.last_line_was_text and is_output:
49 | print >> self.out
50 | self.last_line_was_text = False
51 | Formatter.begin_project(self, project)
52 | if is_output:
53 | print >>self.out
54 | self.depth = 0
55 | self.header_depth+=1
56 | def begin_context (self, context):
57 | is_output = 'ContextStart' in self.template.nodes
58 | if self.last_line_was_text and is_output:
59 | print >> self.out
60 | self.last_line_was_text = False
61 | Formatter.begin_context(self, context)
62 | if is_output:
63 | print >>self.out
64 | self.depth = 0
65 | self.header_depth+=1
66 | def end_task (self,task):
67 | Formatter.end_task(self, task)
68 | self.last_line_was_text = True
69 | def end_context (self, context):
70 | self.header_depth-=1
71 | Formatter.end_context(self, context)
72 | def end_project (self, project):
73 | self.header_depth-=1
74 | Formatter.end_project(self, project)
75 | def end_folder (self, folder):
76 | self.header_depth-=1
77 | Formatter.end_folder(self, folder)
78 | def handle_note (self, item):
79 | if item.note != None and 'NoteLine' in self.template.nodes:
80 | if item.type == PROJECT:
81 | print >>self.out
82 | Formatter.handle_note (self, item)
83 | def add_extra_template_attribs (self, item, attribs):
84 | attribs['hashes'] = '#' * (self.header_depth+1) + ' '
85 |
--------------------------------------------------------------------------------
/src/main/python/plugin_opml.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from fmt_template import Formatter, format_document
18 | from ofexport import load_template
19 |
20 | def escape (val):
21 | return val.replace('"','"').replace('&','&').replace('<','<').replace('>','>')
22 |
23 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
24 | subject = root_project if project_mode else root_context
25 | template = load_template (template_dir, type_config['template'])
26 |
27 | template.attrib_map_builder.type_fns['opml.string'] = lambda x: escape (x)
28 | template.attrib_map_builder.type_fns['opml.note'] = lambda x: '
'.join([escape (line) for line in x.get_note_lines ()])
29 |
30 | visitor = Formatter (out, template)
31 | format_document (subject, visitor, project_mode)
--------------------------------------------------------------------------------
/src/main/python/plugin_taskpaper.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from string import replace
18 | from ofexport import load_template
19 | from fmt_template import Formatter, format_document
20 |
21 | def remove_trailing_colon (x):
22 | if x.endswith(':'):
23 | return x[:-1]
24 | return x
25 |
26 | def strip_brackets (x):
27 | return replace(replace(x, ')', ''), '(','')
28 |
29 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
30 | subject = root_project if project_mode else root_context
31 | template = load_template (template_dir, type_config['template'])
32 |
33 | template.attrib_map_builder.type_fns['taskpaper.tag'] = lambda x: strip_brackets(''.join (x.split ()))
34 | template.attrib_map_builder.type_fns['taskpaper.title'] = lambda x: remove_trailing_colon(x)
35 |
36 | visitor = Formatter (out, template)
37 | format_document (subject, visitor, project_mode)
--------------------------------------------------------------------------------
/src/main/python/plugin_text.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from fmt_template import Formatter, format_document
18 | from ofexport import load_template
19 |
20 | def generate (out, root_project, root_context, project_mode, template_dir, type_config):
21 | subject = root_project if project_mode else root_context
22 | template = load_template (template_dir, type_config['template'])
23 | visitor = Formatter (out, template)
24 | format_document (subject, visitor, project_mode)
--------------------------------------------------------------------------------
/src/main/python/typeof.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | class TypeOf(object):
18 | def __init__(self, name, thetype):
19 | self.thetype = thetype
20 | self.name = name
21 | def __get__(self, instance, owner):
22 | if self.name in instance.__dict__:
23 | return instance.__dict__[self.name]
24 | return None
25 | def __set__(self, instance, value):
26 | if value != None:
27 | assert isinstance (value, self.thetype), self.name + ': expected type ' + str(self.thetype) + ' got ' + str (value.__class__)
28 | instance.__dict__[self.name] = value
--------------------------------------------------------------------------------
/src/main/python/util.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | def strip_tabs_newlines (string):
18 | if string != None:
19 | words = string.split ()
20 | string = u' '.join(words)
21 | return string
--------------------------------------------------------------------------------
/src/main/python/visitors.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright 2013 Paul Sidnell
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | '''
16 |
17 | from treemodel import Visitor, Project, Context, TASK, PROJECT, FOLDER
18 | import logging
19 |
20 | logger = logging.getLogger(__name__)
21 |
22 | INCLUDED='INCLUDED'
23 | EXCLUDED='EXCLUDED'
24 | PATH_TO_INCLUDED='PATH_TO_INCLUDED'
25 |
26 | def set_attrib_to_root (path_to_root, name, value):
27 | for item in path_to_root:
28 | item.attribs[name] = value
29 |
30 | def mark_branch_not_marked (item, project_mode):
31 | if item.marked:
32 | item.marked = False
33 | if (item.type == TASK or item.type == PROJECT) and not project_mode:
34 | # We only got here because we recursed from a context
35 | # Tasks/Projects are not a tree in context mode, they're flat so we don't want
36 | # to un-mark all the children since they might be in a different context
37 | return
38 | for child in item.children:
39 | mark_branch_not_marked (child, project_mode)
40 |
41 | class BaseFilterVisitor(Visitor):
42 | def __init__(self, include=True):
43 | self.filter = None
44 | self.include = include
45 | self.traversal_path = []
46 | def begin_any (self, item):
47 | # Can't use the item parent since this only has meaning
48 | # in project mode - have to track our own traversal path
49 | parent = None
50 | if len(self.traversal_path) > 0:
51 | parent = self.traversal_path[-1]
52 | item.attribs[PATH_TO_INCLUDED] = False
53 | if parent != None:
54 | # Inherit these attribute
55 | item.attribs[INCLUDED] = parent.attribs[INCLUDED]
56 | item.attribs[EXCLUDED] = parent.attribs[EXCLUDED]
57 | assert parent.attribs[INCLUDED] != None, "missing attribute in " + parent.name
58 | assert parent.attribs[EXCLUDED] != None, "missing attribute in " + parent.name
59 | else:
60 | item.attribs[INCLUDED] = False
61 | item.attribs[EXCLUDED] = False
62 | self.traversal_path.append(item)
63 | def end_any (self, item):
64 | assert item.attribs[PATH_TO_INCLUDED] != None, "missing attribute in " + item.name
65 | assert item.attribs[INCLUDED] != None, "missing attribute in " + item.name
66 | assert item.attribs[EXCLUDED] != None, "missing attribute in " + item.name
67 | self.traversal_path.pop()
68 | if self.include and not (item.attribs[INCLUDED] or item.attribs[PATH_TO_INCLUDED]):
69 | mark_branch_not_marked (item, self.project_mode)
70 | # We've finished processing the node, tidy up
71 | # and avoid confusing the next filter.
72 | del (item.attribs[INCLUDED])
73 | del (item.attribs[EXCLUDED])
74 | del (item.attribs[PATH_TO_INCLUDED])
75 | def match_required (self, item):
76 | if item.attribs[INCLUDED] or item.attribs[EXCLUDED]:
77 | # The decision has already been made
78 | return False
79 | return True
80 | def set_item_matched (self, item, matched):
81 | # invoked from begin_XXX
82 | if self.include:
83 | if matched:
84 | # Then we want this node in the output and want to stop
85 | # this filter testing removing any parents or children of this node
86 | item.attribs[INCLUDED] = True
87 | set_attrib_to_root (self.traversal_path, PATH_TO_INCLUDED, True)
88 | else: # In exclude mode
89 | if matched:
90 | # This node is toast
91 | mark_branch_not_marked (item, self.project_mode)
92 | else:
93 | # We haven't excluded it so it stays
94 | pass
95 |
96 | def includes (include):
97 | if include:
98 | return 'include'
99 | else:
100 | return 'exclude'
101 |
102 | class Filter(BaseFilterVisitor):
103 | def __init__(self, types, match_fn, include, nice_string):
104 | BaseFilterVisitor.__init__(self, include)
105 | self.types = types
106 | self.match_fn = match_fn
107 | self.nice_string = nice_string
108 | def begin_any (self, item):
109 | BaseFilterVisitor.begin_any (self, item)
110 | if item.type in self.types and self.match_required(item):
111 | matched = self.match_fn(item)
112 | if matched:
113 | logger.debug ("matched id:%s %s %s", item.id, item.type, item.name)
114 | self.set_item_matched(item, matched);
115 | def __str__(self):
116 | return includes (self.include) + ' ' + str(self.types) + ' where ' + self.nice_string
117 |
118 |
119 | class Sort(Visitor):
120 | def __init__(self, types, get_key_fn, nice_string):
121 | Visitor.__init__(self)
122 | self.types = types
123 | self.get_key_fn = get_key_fn
124 | self.nice_string = nice_string
125 | def begin_any (self, item):
126 | if item.type in self.types:
127 | logger.debug ("sorting id:%s %s %s", item.id, item.type, item.name)
128 | item.children = self.sort_list(item.children)
129 | def sort_list (self, items):
130 | return sorted(items, cmp=self.compare)
131 | def compare (self, x, y):
132 | # Use the key we've been asked to use but
133 | # try other comparators to get at least a deterministic
134 | # ordering
135 | diff = self.cmp (self.get_key_fn (x), self.get_key_fn (y))
136 | if diff == 0:
137 | diff = self.cmp (x.order, y.order);
138 | if diff == 0:
139 | diff = self.cmp (x.id, y.id)
140 | return diff;
141 | def cmp (self, l, r):
142 | if l < r:
143 | return -1
144 | if l > r:
145 | return 1
146 | return 0
147 | def __str__(self):
148 | return 'Sort ' + str(self.types) + ' by ' + self.nice_string
149 |
150 | class Prune (Visitor):
151 | def __init__(self, types):
152 | Visitor.__init__(self)
153 | self.types = types
154 | def end_any (self, item):
155 | if item.type in self.types:
156 | logger.debug ("pruning candidate id:%s %s", item.id, item.name)
157 | self.prune_if_empty(item)
158 | def prune_if_empty (self, item):
159 | if item.marked:
160 | empty = len ([x for x in item.children if x.marked]) == 0
161 | if empty:
162 | logger.debug ("pruning id:%s %s", item.id, item.name)
163 | item.marked = False
164 | def __str__ (self):
165 | return 'Prune ' + str(self.types)
166 |
167 | class Flatten (Visitor):
168 | def __init__(self, types):
169 | Visitor.__init__(self)
170 | self.types = types
171 | def end_any (self, item):
172 | self.flatten (item)
173 | def flatten (self, item):
174 | logger.debug ("flattening candidate L1 id:%s %s %s", item.id, item.type, item.name)
175 | new_children = []
176 | for child in item.children:
177 | if child.type in self.types:
178 | logger.debug ("flattening candidate L2 id:%s %s %s", child.id, child.type, child.name)
179 | for grandchild in list(child.children):
180 | logger.debug ("flattening candidate L3 id:%s %s %s", grandchild.id, grandchild.type, grandchild.name)
181 | if grandchild.type == child.type or child.type == FOLDER:
182 | logger.debug ("flattening id:%s %s %s", grandchild.id, grandchild.type, grandchild.name)
183 | new_children.append(grandchild)
184 | child.children.remove (grandchild)
185 | new_children.append(child)
186 | item.children = []
187 | for child in new_children:
188 | item.add_child (child)
189 | def __str__ (self):
190 | return 'Flatten' + str(self.types)
191 |
192 | class Tasks (Visitor):
193 | def __init__(self, root_folder, root_context):
194 | Visitor.__init__(self)
195 | self.root_folder = root_folder
196 | self.root_context = root_context
197 | self.project = Project (name='Tasks')
198 | self.context = Context (name='Tasks')
199 | def end_project (self, item):
200 | for child in item.children:
201 | self.project.add_child(child)
202 | item.children = []
203 | def end_folder (self, item):
204 | if item == self.root_folder:
205 | self.root_folder.children = []
206 | self.root_folder.add_child(self.project)
207 | def end_context (self, item):
208 | if item != self.root_context:
209 | for child in item.children:
210 | if child.type == TASK:
211 | self.context.add_child(child)
212 | item.children = []
213 | else:
214 | self.root_context.children = []
215 | self.root_context.add_child(self.context)
216 | def __str__ (self):
217 | return 'Tasks'
--------------------------------------------------------------------------------
/src/main/python/word_cloud.py:
--------------------------------------------------------------------------------
1 | '''
2 | size-0{
3 | font-size: 11px;
4 | }
5 |
6 | size-1{
7 | font-size: 12px;
8 | }
9 | '''
10 |
11 | CSS_SIZES = range(0, 6) # 1,2...6 for use in your css-file size-1, size-2, etc.
12 |
13 | TAGS = {
14 | 'python' : 28059,
15 | 'html' : 19160,
16 | 'tag-cloud' : 40,
17 | }
18 |
19 | MAX = max(TAGS.values()) # Needed to calculate the steps for the font-size
20 |
21 | STEP = MAX / len(CSS_SIZES)
22 |
23 |
24 | print ''
25 | print '