├── .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 | ![Web Page](HazelRules.png) 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 | ![Web Page](HazelRulesScript.png) 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 | 2 | 3 | 4.0.0 4 | pom 5 | 6 | org.psidnell 7 | ofexport 8 | 3.0.5 9 | ofexport 10 | 11 | 12 | 13 | 14 | org.codehaus.mojo 15 | exec-maven-plugin 16 | 17 | 18 | 19 | bash 20 | 21 | -c 22 | rm -f src/main/python/*.pyc src/test/python/*.pyc 23 | 24 | 25 | python-clean 26 | clean 27 | 28 | exec 29 | 30 | 31 | 32 | 33 | bash 34 | 35 | build-scripts/generate-sources 36 | ${project.version} 37 | 38 | 39 | python-generate-sources 40 | generate-sources 41 | 42 | exec 43 | 44 | 45 | 46 | 47 | bash 48 | 49 | build-scripts/unit-test 50 | 51 | 52 | python-unit-test 53 | test 54 | 55 | exec 56 | 57 | 58 | 59 | 60 | bash 61 | 62 | build-scripts/integration-test 63 | 64 | 65 | python-integration-test 66 | integration-test 67 | 68 | exec 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=`cat VERSION` 4 | git tag -a "V$VERSION" -m "$VERSION built on `date`" 5 | git push 6 | -------------------------------------------------------------------------------- /src/main/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /src/main/python/attrib_convert.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 Template 18 | 19 | class Conversion: 20 | def __init__ (self, field, default, format_template, typ, evaluate = None): 21 | self.field = field 22 | self.evaluate = evaluate 23 | self.type = typ 24 | self.format_template = Template(format_template) 25 | self.default = default 26 | def value (self, node, type_fns): 27 | # Does the attrib have the attribute at all? 28 | # If not use the template default 29 | if not self.field in node.__dict__: 30 | return self.default 31 | 32 | value = node.__dict__[self.field] 33 | 34 | # If it's null use the demplate default 35 | if value == None: 36 | return self.default 37 | 38 | # Run optional custom code, for example loading 39 | # a sub attribute of the object 40 | if self.evaluate != None: 41 | value = eval (self.evaluate) 42 | 43 | # If that yielded null use the template default 44 | if value == None: 45 | return self.default 46 | 47 | # Run a converter assocuated with it's type 48 | # Possibly loaded from a plugin 49 | value = type_fns[self.type] (value) 50 | 51 | # If that yielded null use the template default 52 | if value == None: 53 | return self.default 54 | 55 | # Finally, having got a non-null string format it with the format associated with the field 56 | return self.format_template.safe_substitute (value=value) 57 | 58 | class AttribMapBuilder: 59 | def __init__ (self): 60 | 61 | self.type_fns = {} 62 | self.type_fns['string'] = lambda x: x 63 | self.type_fns['date'] = lambda x: x.strftime(self.date_format) 64 | self.type_fns['boolean'] = lambda x: str (x) 65 | self.type_fns['int'] = lambda x: int (x) 66 | self.type_fns['note'] = lambda x: x.get_note () 67 | 68 | self.attrib_conversions = {} 69 | self.set_conversion(Conversion("id", "", "$value", "string")) 70 | self.set_conversion(Conversion("type", "", "$value", "string")) 71 | self.set_conversion(Conversion("name", "", "$value", "string")) 72 | self.set_conversion(Conversion("link", "", "$value", "string")) 73 | self.set_conversion(Conversion("status", "", "$value", "string")) 74 | self.set_conversion(Conversion("flagged", "", "$value", "boolean")) 75 | self.set_conversion(Conversion("context", "", "$value", "string", "value.name")) 76 | self.set_conversion(Conversion("project", "", "$value", "string", "value.name")) 77 | self.set_conversion(Conversion("date_to_start", "", "$value", "date")) 78 | self.set_conversion(Conversion("date_due", "", "$value", "date")) 79 | self.set_conversion(Conversion("date_added", "", "$value", "date")) 80 | self.set_conversion(Conversion("estimated_minutes", "", "$value", "int")) 81 | self.set_conversion(Conversion("date_complete", "", "$value", "date")) 82 | self.set_conversion(Conversion("note", "", "$value", "note")) 83 | 84 | self.date_format = "%Y-%m-%d" 85 | def set_conversion (self, attrib): 86 | self.attrib_conversions[attrib.field] = attrib 87 | def get_values (self, x): 88 | result = {} 89 | for conversion in self.attrib_conversions.values(): 90 | key = conversion.field 91 | value = conversion.value (x, self.type_fns) 92 | result[key] = value 93 | return result 94 | -------------------------------------------------------------------------------- /src/main/python/cupboard/of_to_tags.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 traverse_list, Visitor 18 | from omnifocus import build_model, find_database 19 | import os 20 | import codecs 21 | 22 | ''' 23 | Experimental (and broken)!!!! 24 | 25 | A db directory must exist on your desktop first 26 | 27 | Creates a bazillion little html files each of which links/redirects back to an OmniFocus project/folder/context. 28 | I'm wondering if I can use openmeta on them to essentially get tags into OF via the back door. 29 | 30 | If I extract #tags from the item text then I can put that in the file and autotag can find it 31 | 32 | ''' 33 | class GenerateTagDBVisitor(Visitor): 34 | def begin_folder (self, folder): 35 | self.generate_entry ('folder', folder) 36 | def begin_project (self, project): 37 | self.generate_entry ('task', project) 38 | def begin_context (self, context): 39 | self.generate_entry ('context', context) 40 | def generate_entry (self, link_type, item): 41 | if 'persistentIdentifier' in item.ofattribs: 42 | ident = item.ofattribs['persistentIdentifier'] 43 | link = 'omnifocus:///' + link_type + '/' + ident 44 | base = item.name.replace('/','_').replace('\\','/').replace('\.', '_') 45 | 46 | file_name=os.environ['HOME'] + '/Desktop/db/' + base + ' ' + ident + '.html' 47 | out=codecs.open(file_name, 'w', 'utf-8') 48 | link = 'omnifocus:///' + link_type + '/' + ident 49 | print >>out, '' 50 | print >>out, '' 51 | print >>out, '' 52 | print >>out, '' 53 | print >>out, '' 54 | print >>out, '

' + 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 ' ' 26 | print ' OmniFocus' 27 | print ' ' 50 | print ' ' 51 | print ' ' 52 | 53 | 54 | 55 | for tag, count in TAGS.items(): 56 | css = count / STEP 57 | print '%s' % (css, tag) 58 | 59 | print ' ' 60 | print '' -------------------------------------------------------------------------------- /src/test/data/db-1-C.ft: -------------------------------------------------------------------------------- 1 | # No Context 2 | 3 | # Test Context 4 | 5 | - sub task with a colon: 6 | 7 | ## Sub Context 1 8 | 9 | - parent task F1 P1 1 10 | - task F2 P2 1 11 | - task F1 P2 1 12 | Note line 1. 13 | Note line 2. 14 | - task F2 P1 1 15 | 16 | ## Sub Context 2 17 | 18 | - task F1 P1 2 19 | - task F1 P2 2 20 | - task F2 P1 2 21 | - task F2 P2 2 22 | - I'm done! done:**2013-04-21** 23 | - task with no context 24 | -------------------------------------------------------------------------------- /src/test/data/db-1-C.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | OmniFocus 4 | 87 | 88 | 89 |

No Context

90 |

Test Context

91 |
  • sub task with a colon: Test Context F1 Project 1
  • 92 |

    Sub Context 1

    93 |
  • parent task F1 P1 1 Sub Context 1 F1 Project 1
  • 94 |
  • task F2 P2 1 Sub Context 1 F2 Project 2
  • 95 |
  • task F1 P2 1 Sub Context 1 F1 Project 2
  • 96 | Note line 1.
    97 | Note line 2.
    98 |
  • task F2 P1 1 FLAGGED Sub Context 1 F2 Project 1
  • 99 |

    Sub Context 2

    100 |
  • task F1 P1 2 Sub Context 2 F1 Project 1
  • 101 |
  • task F1 P2 2 Sub Context 2 F1 Project 2
  • 102 |
  • task F2 P1 2 Sub Context 2 F2 Project 1
  • 103 |
  • task F2 P2 2 Sub Context 2 F2 Project 2
  • 104 |
  • I'm done! 2013-04-21 Test Context F2 Project 1
  • 105 |
  • task with no context Test Context F2 Project 2
  • 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/test/data/db-1-C.md: -------------------------------------------------------------------------------- 1 | # No Context 2 | 3 | # Test Context 4 | 5 | - sub task with a colon: 6 | 7 | ## Sub Context 1 8 | 9 | - parent task F1 P1 1 10 | - task F2 P2 1 11 | - task F1 P2 1 12 | Note line 1. 13 | Note line 2. 14 | - task F2 P1 1 15 | 16 | ## Sub Context 2 17 | 18 | - task F1 P1 2 19 | - task F1 P2 2 20 | - task F2 P1 2 21 | - task F2 P2 2 22 | - I'm done! done:**2013-04-21** 23 | - task with no context 24 | -------------------------------------------------------------------------------- /src/test/data/db-1-C.opml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OmniFocus 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/data/db-1-C.taskpaper: -------------------------------------------------------------------------------- 1 | No Context: 2 | 3 | Test Context: 4 | omnifocus:///context/ptrX-c-R6_X 5 | - sub task with a colon @context(TestContext) @project(F1Project1) 6 | Sub Context 1: 7 | omnifocus:///context/aCWpXEa6Xgx 8 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 9 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 10 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 11 | Note line 1. 12 | Note line 2. 13 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 14 | Sub Context 2: 15 | omnifocus:///context/gOdcF2qsrrV 16 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 17 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 18 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 19 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 20 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 21 | - task with no context @context(TestContext) @project(F2Project2) 22 | -------------------------------------------------------------------------------- /src/test/data/db-1-c1.taskpaper: -------------------------------------------------------------------------------- 1 | Test Context: 2 | omnifocus:///context/ptrX-c-R6_X 3 | Sub Context 1: 4 | omnifocus:///context/aCWpXEa6Xgx 5 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 6 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 7 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 8 | Note line 1. 9 | Note line 2. 10 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-c2.taskpaper: -------------------------------------------------------------------------------- 1 | Test Context: 2 | omnifocus:///context/ptrX-c-R6_X 3 | Sub Context 2: 4 | omnifocus:///context/gOdcF2qsrrV 5 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 6 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 7 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 8 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 9 | -------------------------------------------------------------------------------- /src/test/data/db-1-c3.taskpaper: -------------------------------------------------------------------------------- 1 | Test Context: 2 | omnifocus:///context/ptrX-c-R6_X 3 | Sub Context 1: 4 | omnifocus:///context/aCWpXEa6Xgx 5 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 6 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 7 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 8 | Note line 1. 9 | Note line 2. 10 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-c4.taskpaper: -------------------------------------------------------------------------------- 1 | No Context: 2 | 3 | Test Context: 4 | omnifocus:///context/ptrX-c-R6_X 5 | - sub task with a colon @context(TestContext) @project(F1Project1) 6 | Sub Context 1: 7 | omnifocus:///context/aCWpXEa6Xgx 8 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 9 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 10 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 11 | Note line 1. 12 | Note line 2. 13 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 14 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 15 | - task with no context @context(TestContext) @project(F2Project2) 16 | -------------------------------------------------------------------------------- /src/test/data/db-1-f1.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 15 | Note line 1. 16 | Note line 2. 17 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 18 | -------------------------------------------------------------------------------- /src/test/data/db-1-f2.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 2: 4 | omnifocus:///folder/kyyjHNCEm8v 5 | F2 Project 1: @context(TestContext) 6 | omnifocus:///task/pLoziQtuwYD 7 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 8 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 9 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 10 | F2 Project 2: @done(2013-04-21) @context(TestContext) 11 | omnifocus:///task/fYuqelo7INX 12 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 13 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 14 | - task with no context @context(TestContext) @project(F2Project2) 15 | -------------------------------------------------------------------------------- /src/test/data/db-1-f3.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 2: 4 | omnifocus:///folder/kyyjHNCEm8v 5 | F2 Project 1: @context(TestContext) 6 | omnifocus:///task/pLoziQtuwYD 7 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 8 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 9 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 10 | F2 Project 2: @done(2013-04-21) @context(TestContext) 11 | omnifocus:///task/fYuqelo7INX 12 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 13 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 14 | - task with no context @context(TestContext) @project(F2Project2) 15 | -------------------------------------------------------------------------------- /src/test/data/db-1-f4.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 15 | Note line 1. 16 | Note line 2. 17 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 18 | -------------------------------------------------------------------------------- /src/test/data/db-1-flat.markdown: -------------------------------------------------------------------------------- 1 | - parent task F1 P1 1 2 | - sub task with a colon: 3 | - task F1 P1 2 4 | - task F1 P2 1 5 | - task F1 P2 2 6 | - task F2 P1 1 7 | - task F2 P1 2 8 | - I'm done! 9 | - task F2 P2 1 10 | - task F2 P2 2 11 | - task with no context 12 | -------------------------------------------------------------------------------- /src/test/data/db-1-html-lite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | OmniFocus 4 | 87 | 88 | 89 |

    Test Folder

    90 |

    Sub Folder 1

    91 |

    F1 Project 1

    97 |

    F1 Project 2

    101 |

    Sub Folder 2

    102 |

    F2 Project 1

    107 |

    F2 Project 2

    112 | 113 | 114 | -------------------------------------------------------------------------------- /src/test/data/db-1-p1.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | -------------------------------------------------------------------------------- /src/test/data/db-1-p2.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 2: @flagged @context(TestContext) 6 | omnifocus:///task/i0LhrVuFaPc 7 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 8 | Note line 1. 9 | Note line 2. 10 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-p3.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 2: @flagged @context(TestContext) 6 | omnifocus:///task/i0LhrVuFaPc 7 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 8 | Note line 1. 9 | Note line 2. 10 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-p4.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | Sub Folder 2: 13 | omnifocus:///folder/kyyjHNCEm8v 14 | F2 Project 1: @context(TestContext) 15 | omnifocus:///task/pLoziQtuwYD 16 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 17 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 18 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 19 | F2 Project 2: @done(2013-04-21) @context(TestContext) 20 | omnifocus:///task/fYuqelo7INX 21 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 22 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 23 | - task with no context @context(TestContext) @project(F2Project2) 24 | -------------------------------------------------------------------------------- /src/test/data/db-1-stdout.text: -------------------------------------------------------------------------------- 1 | Folder: Test Folder 2 | Folder: Sub Folder 1 3 | Project: F1 Project 1 status:active start:2013-04-22 due:2013-04-23 context:Test Context 4 | Note: Note line 1. 5 | Note: Note line 2. 6 | TaskGroup: parent task F1 P1 1 context:Sub Context 1 project:F1 Project 1 7 | Task: sub task with a colon: context:Test Context project:F1 Project 1 8 | Task: task F1 P1 2 context:Sub Context 2 project:F1 Project 1 9 | Project: F1 Project 2 flagged status:active context:Test Context 10 | Task: task F1 P2 1 context:Sub Context 1 project:F1 Project 2 11 | Note: Note line 1. 12 | Note: Note line 2. 13 | Task: task F1 P2 2 context:Sub Context 2 project:F1 Project 2 14 | Folder: Sub Folder 2 15 | Project: F2 Project 1 status:active context:Test Context 16 | Task: task F2 P1 1 flagged context:Sub Context 1 project:F2 Project 1 17 | Task: task F2 P1 2 context:Sub Context 2 project:F2 Project 1 18 | Task: I'm done! done:2013-04-21 context:Test Context project:F2 Project 1 19 | Project: F2 Project 2 status:active done:2013-04-21 context:Test Context 20 | Task: task F2 P2 1 context:Sub Context 1 project:F2 Project 2 21 | Task: task F2 P2 2 context:Sub Context 2 project:F2 Project 2 22 | Task: task with no context context:Test Context project:F2 Project 2 23 | -------------------------------------------------------------------------------- /src/test/data/db-1-t1.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-t2.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 10 | -------------------------------------------------------------------------------- /src/test/data/db-1-t3.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | -------------------------------------------------------------------------------- /src/test/data/db-1-t4.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | F1 Project 2: @flagged @context(TestContext) 12 | omnifocus:///task/i0LhrVuFaPc 13 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 14 | Note line 1. 15 | Note line 2. 16 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 17 | Sub Folder 2: 18 | omnifocus:///folder/kyyjHNCEm8v 19 | F2 Project 1: @context(TestContext) 20 | omnifocus:///task/pLoziQtuwYD 21 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 22 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 23 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 24 | F2 Project 2: @done(2013-04-21) @context(TestContext) 25 | omnifocus:///task/fYuqelo7INX 26 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 27 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 28 | - task with no context @context(TestContext) @project(F2Project2) 29 | -------------------------------------------------------------------------------- /src/test/data/db-1-taskpaper-lite.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | Sub Folder 1: 3 | F1 Project 1: 4 | parent task F1 P1 1: 5 | - sub task with a colon 6 | - task F1 P1 2 7 | F1 Project 2: 8 | - task F1 P2 1 9 | - task F1 P2 2 10 | Sub Folder 2: 11 | F2 Project 1: 12 | - task F2 P1 1 13 | - task F2 P1 2 14 | - I'm done! @2013-04-21-Sun 15 | F2 Project 2: 16 | - task F2 P2 1 17 | - task F2 P2 2 18 | - task with no context 19 | -------------------------------------------------------------------------------- /src/test/data/db-1-tasks1.taskpaper: -------------------------------------------------------------------------------- 1 | Tasks: 2 | 3 | parent task F1 P1 1: @context(SubContext1) @project(Tasks) 4 | - sub task with a colon @context(TestContext) @project(Tasks) 5 | - task F1 P1 2 @context(SubContext2) @project(Tasks) 6 | - task F1 P2 1 @context(SubContext1) @project(Tasks) 7 | Note line 1. 8 | Note line 2. 9 | - task F1 P2 2 @context(SubContext2) @project(Tasks) 10 | - task F2 P1 1 @flagged @context(SubContext1) @project(Tasks) 11 | - task F2 P1 2 @context(SubContext2) @project(Tasks) 12 | - I'm done! @done(2013-04-21) @context(TestContext) @project(Tasks) 13 | - task F2 P2 1 @context(SubContext1) @project(Tasks) 14 | - task F2 P2 2 @context(SubContext2) @project(Tasks) 15 | - task with no context @context(TestContext) @project(Tasks) 16 | -------------------------------------------------------------------------------- /src/test/data/db-1-tasks2.taskpaper: -------------------------------------------------------------------------------- 1 | Tasks: 2 | 3 | - parent task F1 P1 1 @context(Tasks) @project(F1Project1) 4 | - task F2 P2 1 @context(Tasks) @project(F2Project2) 5 | - task F1 P2 1 @context(Tasks) @project(F1Project2) 6 | Note line 1. 7 | Note line 2. 8 | - task F2 P1 1 @flagged @context(Tasks) @project(F2Project1) 9 | - task F1 P1 2 @context(Tasks) @project(F1Project1) 10 | - task F1 P2 2 @context(Tasks) @project(F1Project2) 11 | - task F2 P1 2 @context(Tasks) @project(F2Project1) 12 | - task F2 P2 2 @context(Tasks) @project(F2Project2) 13 | - sub task with a colon @context(Tasks) @project(F1Project1) 14 | - I'm done! @done(2013-04-21) @context(Tasks) @project(F2Project1) 15 | - task with no context @context(Tasks) @project(F2Project2) 16 | -------------------------------------------------------------------------------- /src/test/data/db-1.ft: -------------------------------------------------------------------------------- 1 | # Test Folder 2 | 3 | ## Sub Folder 1 4 | 5 | ### F1 Project 1 6 | 7 | Note line 1. 8 | Note line 2. 9 | 10 | - parent task F1 P1 1 11 | - sub task with a colon: 12 | - task F1 P1 2 13 | 14 | ### F1 Project 2 15 | 16 | - task F1 P2 1 17 | Note line 1. 18 | Note line 2. 19 | - task F1 P2 2 20 | 21 | ## Sub Folder 2 22 | 23 | ### F2 Project 1 24 | 25 | - task F2 P1 1 26 | - task F2 P1 2 27 | - I'm done! done:**2013-04-21** 28 | 29 | ### F2 Project 2 30 | 31 | - task F2 P2 1 32 | - task F2 P2 2 33 | - task with no context 34 | -------------------------------------------------------------------------------- /src/test/data/db-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | OmniFocus 4 | 87 | 88 | 89 |

    Test Folder

    90 |

    Sub Folder 1

    91 |

    F1 Project 1 2013-04-22 2013-04-23 Test Context

    99 |

    F1 Project 2 FLAGGED Test Context

    105 |

    Sub Folder 2

    106 |

    F2 Project 1 Test Context

    111 |

    F2 Project 2 2013-04-21 Test Context

    116 | 117 | 118 | -------------------------------------------------------------------------------- /src/test/data/db-1.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | X-WR-CALNAME:OmniFocus 4 | PRODID:-//ofexport//ofexport//EN 5 | METHOD:PUBLISH 6 | CALSCALE:GREGORIAN 7 | BEGIN:VEVENT 8 | DTSTART:20130421T230000Z 9 | DTEND:20130423T080000Z 10 | DTSTAMP:20130423T080000Z 11 | SUMMARY:F1 Project 1 12 | URL:omnifocus:///task/d39BskyduPI 13 | UID:499d8b42-aa62-11e2-97a6-a820662fff7a 14 | DESCRIPTION: 15 | BEGIN:VALARM 16 | ACTION:DISPLAY 17 | DESCRIPTION:OmniFocus Reminder 18 | TRIGGER:-PT0M 19 | END:VALARM 20 | END:VEVENT 21 | END:VCALENDAR 22 | -------------------------------------------------------------------------------- /src/test/data/db-1.md: -------------------------------------------------------------------------------- 1 | # Test Folder 2 | 3 | ## Sub Folder 1 4 | 5 | ### F1 Project 1 6 | 7 | Note line 1. 8 | Note line 2. 9 | 10 | - parent task F1 P1 1 11 | - sub task with a colon: 12 | - task F1 P1 2 13 | 14 | ### F1 Project 2 15 | 16 | - task F1 P2 1 17 | Note line 1. 18 | Note line 2. 19 | - task F1 P2 2 20 | 21 | ## Sub Folder 2 22 | 23 | ### F2 Project 1 24 | 25 | - task F2 P1 1 26 | - task F2 P1 2 27 | - I'm done! done:**2013-04-21** 28 | 29 | ### F2 Project 2 30 | 31 | - task F2 P2 1 32 | - task F2 P2 2 33 | - task with no context 34 | -------------------------------------------------------------------------------- /src/test/data/db-1.opml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OmniFocus 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/test/data/db-1.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | Note line 1. 8 | Note line 2. 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 15 | Note line 1. 16 | Note line 2. 17 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 18 | Sub Folder 2: 19 | omnifocus:///folder/kyyjHNCEm8v 20 | F2 Project 1: @context(TestContext) 21 | omnifocus:///task/pLoziQtuwYD 22 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 23 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 24 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 25 | F2 Project 2: @done(2013-04-21) @context(TestContext) 26 | omnifocus:///task/fYuqelo7INX 27 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 28 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 29 | - task with no context @context(TestContext) @project(F2Project2) 30 | -------------------------------------------------------------------------------- /src/test/data/db-1.text: -------------------------------------------------------------------------------- 1 | Folder: Test Folder 2 | Folder: Sub Folder 1 3 | Project: F1 Project 1 status:active start:2013-04-22 due:2013-04-23 context:Test Context 4 | Note: Note line 1. 5 | Note: Note line 2. 6 | TaskGroup: parent task F1 P1 1 context:Sub Context 1 project:F1 Project 1 7 | Task: sub task with a colon: context:Test Context project:F1 Project 1 8 | Task: task F1 P1 2 context:Sub Context 2 project:F1 Project 1 9 | Project: F1 Project 2 flagged status:active context:Test Context 10 | Task: task F1 P2 1 context:Sub Context 1 project:F1 Project 2 11 | Note: Note line 1. 12 | Note: Note line 2. 13 | Task: task F1 P2 2 context:Sub Context 2 project:F1 Project 2 14 | Folder: Sub Folder 2 15 | Project: F2 Project 1 status:active context:Test Context 16 | Task: task F2 P1 1 flagged context:Sub Context 1 project:F2 Project 1 17 | Task: task F2 P1 2 context:Sub Context 2 project:F2 Project 1 18 | Task: I'm done! done:2013-04-21 context:Test Context project:F2 Project 1 19 | Project: F2 Project 2 status:active done:2013-04-21 context:Test Context 20 | Task: task F2 P2 1 context:Sub Context 1 project:F2 Project 2 21 | Task: task F2 P2 2 context:Sub Context 2 project:F2 Project 2 22 | Task: task with no context context:Test Context project:F2 Project 2 23 | -------------------------------------------------------------------------------- /src/test/data/db-2-C.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 8 | - sub task with a colon @context(TestContext) @project(F1Project1) 9 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 10 | F1 Project 2: @flagged @context(TestContext) 11 | omnifocus:///task/i0LhrVuFaPc 12 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 13 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 14 | Sub Folder 2: 15 | omnifocus:///folder/kyyjHNCEm8v 16 | F2 Project 1: @context(TestContext) 17 | omnifocus:///task/pLoziQtuwYD 18 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 19 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 20 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 21 | empty project: @context(TestContext) 22 | omnifocus:///task/iXSU7vo3FuR 23 | F2 Project 2: @done(2013-04-21) @context(TestContext) 24 | omnifocus:///task/fYuqelo7INX 25 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 26 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 27 | - task with no context @context(TestContext) @project(F2Project2) 28 | -------------------------------------------------------------------------------- /src/test/data/db-3-Context-flatten.taskpaper: -------------------------------------------------------------------------------- 1 | Sub Context 1: 2 | omnifocus:///context/aCWpXEa6Xgx 3 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 4 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 5 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 6 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 7 | Sub Context 2: 8 | omnifocus:///context/gOdcF2qsrrV 9 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 10 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 11 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 12 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 13 | Sub Context 3: 14 | omnifocus:///context/d9VWvwNr7mh 15 | Empty Context: 16 | omnifocus:///context/n3UJuvpNzNa 17 | Test Context: 18 | omnifocus:///context/ptrX-c-R6_X 19 | - sub task with a colon @context(TestContext) @project(F1Project1) 20 | empty project: @context(TestContext) 21 | omnifocus:///task/iXSU7vo3FuR 22 | - task with no context @context(TestContext) @project(F2Project2) 23 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 24 | Miscellaneous: 25 | omnifocus:///context/ktJngRAdo-a 26 | -------------------------------------------------------------------------------- /src/test/data/db-3-Context-prune.taskpaper: -------------------------------------------------------------------------------- 1 | Miscellaneous: 2 | omnifocus:///context/ktJngRAdo-a 3 | Test Context: 4 | omnifocus:///context/ptrX-c-R6_X 5 | - sub task with a colon @context(TestContext) @project(F1Project1) 6 | Sub Context 1: 7 | omnifocus:///context/aCWpXEa6Xgx 8 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 9 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 10 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 11 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 12 | empty project: @context(TestContext) 13 | omnifocus:///task/iXSU7vo3FuR 14 | Sub Context 2: 15 | omnifocus:///context/gOdcF2qsrrV 16 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 17 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 18 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 19 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 20 | - task with no context @context(TestContext) @project(F2Project2) 21 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 22 | -------------------------------------------------------------------------------- /src/test/data/db-3-Folder-flatten.taskpaper: -------------------------------------------------------------------------------- 1 | Empty Folder: 2 | omnifocus:///folder/cmYwdTadWxD 3 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 4 | omnifocus:///task/d39BskyduPI 5 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 6 | - sub task with a colon @context(TestContext) @project(F1Project1) 7 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 8 | F1 Project 2: @flagged @context(TestContext) 9 | omnifocus:///task/i0LhrVuFaPc 10 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 11 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 12 | Sub Folder 1: 13 | omnifocus:///folder/msGNrO_ZBdL 14 | F2 Project 1: @context(TestContext) 15 | omnifocus:///task/pLoziQtuwYD 16 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 17 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 18 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 19 | empty project: @context(TestContext) 20 | omnifocus:///task/iXSU7vo3FuR 21 | F2 Project 2: @done(2013-04-21) @context(TestContext) 22 | omnifocus:///task/fYuqelo7INX 23 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 24 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 25 | - task with no context @context(TestContext) @project(F2Project2) 26 | Sub Folder 2: 27 | omnifocus:///folder/kyyjHNCEm8v 28 | Test Folder: 29 | omnifocus:///folder/bQoc15uOjLQ 30 | -------------------------------------------------------------------------------- /src/test/data/db-3-Folder-prune.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 8 | - sub task with a colon @context(TestContext) @project(F1Project1) 9 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 10 | F1 Project 2: @flagged @context(TestContext) 11 | omnifocus:///task/i0LhrVuFaPc 12 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 13 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 14 | Sub Folder 2: 15 | omnifocus:///folder/kyyjHNCEm8v 16 | F2 Project 1: @context(TestContext) 17 | omnifocus:///task/pLoziQtuwYD 18 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 19 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 20 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 21 | empty project: @context(TestContext) 22 | omnifocus:///task/iXSU7vo3FuR 23 | F2 Project 2: @done(2013-04-21) @context(TestContext) 24 | omnifocus:///task/fYuqelo7INX 25 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 26 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 27 | - task with no context @context(TestContext) @project(F2Project2) 28 | -------------------------------------------------------------------------------- /src/test/data/db-3-Project-flatten.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Empty Folder: 4 | omnifocus:///folder/cmYwdTadWxD 5 | Sub Folder 1: 6 | omnifocus:///folder/msGNrO_ZBdL 7 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 8 | - sub task with a colon @context(TestContext) 9 | omnifocus:///task/d39BskyduPI 10 | - parent task F1 P1 1 @context(SubContext1) 11 | 12 | - task F1 P1 2 @context(SubContext2) 13 | F1 Project 2: @flagged @context(TestContext) 14 | omnifocus:///task/i0LhrVuFaPc 15 | - task F1 P2 1 @context(SubContext1) 16 | - task F1 P2 2 @context(SubContext2) 17 | Sub Folder 2: 18 | omnifocus:///folder/kyyjHNCEm8v 19 | F2 Project 1: @context(TestContext) 20 | omnifocus:///task/pLoziQtuwYD 21 | - task F2 P1 1 @flagged @context(SubContext1) 22 | - task F2 P1 2 @context(SubContext2) 23 | - I'm done! @done(2013-04-21) @context(TestContext) 24 | empty project: @context(TestContext) 25 | omnifocus:///task/iXSU7vo3FuR 26 | F2 Project 2: @done(2013-04-21) @context(TestContext) 27 | omnifocus:///task/fYuqelo7INX 28 | - task F2 P2 1 @context(SubContext1) 29 | - task F2 P2 2 @context(SubContext2) 30 | - task with no context @context(TestContext) 31 | -------------------------------------------------------------------------------- /src/test/data/db-3-Project-prune.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Empty Folder: 4 | omnifocus:///folder/cmYwdTadWxD 5 | Sub Folder 1: 6 | omnifocus:///folder/msGNrO_ZBdL 7 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 8 | omnifocus:///task/d39BskyduPI 9 | parent task F1 P1 1: @context(SubContext1) @project(F1Project1) 10 | - sub task with a colon @context(TestContext) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 15 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 16 | Sub Folder 2: 17 | omnifocus:///folder/kyyjHNCEm8v 18 | F2 Project 1: @context(TestContext) 19 | omnifocus:///task/pLoziQtuwYD 20 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 21 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 22 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 23 | F2 Project 2: @done(2013-04-21) @context(TestContext) 24 | omnifocus:///task/fYuqelo7INX 25 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 26 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 27 | - task with no context @context(TestContext) @project(F2Project2) 28 | -------------------------------------------------------------------------------- /src/test/data/db-3-Project-sort.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Empty Folder: 4 | omnifocus:///folder/cmYwdTadWxD 5 | Sub Folder 1: 6 | omnifocus:///folder/msGNrO_ZBdL 7 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 8 | omnifocus:///task/d39BskyduPI 9 | parent task F1 P1 1: @context(SubContext1) 10 | - sub task with a colon @context(TestContext) 11 | - task F1 P1 2 @context(SubContext2) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) 15 | - task F1 P2 2 @context(SubContext2) 16 | Sub Folder 2: 17 | omnifocus:///folder/kyyjHNCEm8v 18 | F2 Project 1: @context(TestContext) 19 | omnifocus:///task/pLoziQtuwYD 20 | - I'm done! @done(2013-04-21) @context(TestContext) 21 | - task F2 P1 1 @flagged @context(SubContext1) 22 | - task F2 P1 2 @context(SubContext2) 23 | empty project: @context(TestContext) 24 | omnifocus:///task/iXSU7vo3FuR 25 | F2 Project 2: @done(2013-04-21) @context(TestContext) 26 | omnifocus:///task/fYuqelo7INX 27 | - task F2 P2 1 @context(SubContext1) 28 | - task F2 P2 2 @context(SubContext2) 29 | - task with no context @context(TestContext) 30 | -------------------------------------------------------------------------------- /src/test/data/db-3-Task-flatten.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Empty Folder: 4 | omnifocus:///folder/cmYwdTadWxD 5 | Sub Folder 1: 6 | omnifocus:///folder/msGNrO_ZBdL 7 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 8 | omnifocus:///task/d39BskyduPI 9 | - sub task with a colon @context(TestContext) @project(F1Project1) 10 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 11 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 12 | F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 15 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 16 | Sub Folder 2: 17 | omnifocus:///folder/kyyjHNCEm8v 18 | F2 Project 1: @context(TestContext) 19 | omnifocus:///task/pLoziQtuwYD 20 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 21 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 22 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 23 | empty project: @context(TestContext) 24 | omnifocus:///task/iXSU7vo3FuR 25 | F2 Project 2: @done(2013-04-21) @context(TestContext) 26 | omnifocus:///task/fYuqelo7INX 27 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 28 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 29 | - task with no context @context(TestContext) @project(F2Project2) 30 | -------------------------------------------------------------------------------- /src/test/data/db-3-any-flatten.taskpaper: -------------------------------------------------------------------------------- 1 | Empty Folder: 2 | omnifocus:///folder/cmYwdTadWxD 3 | F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 4 | omnifocus:///task/d39BskyduPI 5 | - sub task with a colon @context(TestContext) @project(F1Project1) 6 | - parent task F1 P1 1 @context(SubContext1) @project(F1Project1) 7 | - task F1 P1 2 @context(SubContext2) @project(F1Project1) 8 | F1 Project 2: @flagged @context(TestContext) 9 | omnifocus:///task/i0LhrVuFaPc 10 | - task F1 P2 1 @context(SubContext1) @project(F1Project2) 11 | - task F1 P2 2 @context(SubContext2) @project(F1Project2) 12 | Sub Folder 1: 13 | omnifocus:///folder/msGNrO_ZBdL 14 | F2 Project 1: @context(TestContext) 15 | omnifocus:///task/pLoziQtuwYD 16 | - task F2 P1 1 @flagged @context(SubContext1) @project(F2Project1) 17 | - task F2 P1 2 @context(SubContext2) @project(F2Project1) 18 | - I'm done! @done(2013-04-21) @context(TestContext) @project(F2Project1) 19 | empty project: @context(TestContext) 20 | omnifocus:///task/iXSU7vo3FuR 21 | F2 Project 2: @done(2013-04-21) @context(TestContext) 22 | omnifocus:///task/fYuqelo7INX 23 | - task F2 P2 1 @context(SubContext1) @project(F2Project2) 24 | - task F2 P2 2 @context(SubContext2) @project(F2Project2) 25 | - task with no context @context(TestContext) @project(F2Project2) 26 | Sub Folder 2: 27 | omnifocus:///folder/kyyjHNCEm8v 28 | Test Folder: 29 | omnifocus:///folder/bQoc15uOjLQ 30 | -------------------------------------------------------------------------------- /src/test/data/db-4-Context-sort.taskpaper: -------------------------------------------------------------------------------- 1 | Miscellaneous: 2 | omnifocus:///context/ktJngRAdo-a 3 | Test Context: 4 | omnifocus:///context/ptrX-c-R6_X 5 | 1-Sub Context 3: 6 | omnifocus:///context/d9VWvwNr7mh 7 | 1-empty project: @context(TestContext) 8 | omnifocus:///task/iXSU7vo3FuR 9 | - 1-sub task with a colon @context(TestContext) @project(2-F1Project1) 10 | 2-Empty Context: 11 | omnifocus:///context/n3UJuvpNzNa 12 | 3-Sub Context 2: 13 | omnifocus:///context/gOdcF2qsrrV 14 | - 1-task F1 P2 2 @context(3-SubContext2) @project(1-F1Project2) 15 | - 1-task F2 P1 2 @context(3-SubContext2) @project(2-F2Project1) 16 | - 3-task F1 P1 2 @context(3-SubContext2) @project(2-F1Project1) 17 | - task F2 P2 2 @context(3-SubContext2) @project(F2Project2) 18 | 4-Sub Context 1: 19 | omnifocus:///context/aCWpXEa6Xgx 20 | - 2-parent task F1 P1 1 @context(4-SubContext1) @project(2-F1Project1) 21 | - 2-task F1 P2 1 @context(4-SubContext1) @project(1-F1Project2) 22 | - 2-task F2 P1 1 @flagged @context(4-SubContext1) @project(2-F2Project1) 23 | - task F2 P2 1 @context(4-SubContext1) @project(F2Project2) 24 | - I'm done! @done(2013-04-21) @context(TestContext) @project(2-F2Project1) 25 | - task with no context @context(TestContext) @project(F2Project2) 26 | -------------------------------------------------------------------------------- /src/test/data/db-4-Folder-sort.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | 1-Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | 1-F1 Project 2: @flagged @context(TestContext) 6 | omnifocus:///task/i0LhrVuFaPc 7 | - 2-task F1 P2 1 @context(4-SubContext1) @project(1-F1Project2) 8 | - 1-task F1 P2 2 @context(3-SubContext2) @project(1-F1Project2) 9 | 2-F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 10 | omnifocus:///task/d39BskyduPI 11 | 2-parent task F1 P1 1: @context(4-SubContext1) @project(2-F1Project1) 12 | - 1-sub task with a colon @context(TestContext) @project(2-F1Project1) 13 | - 3-task F1 P1 2 @context(3-SubContext2) @project(2-F1Project1) 14 | 2-Empty Folder: 15 | omnifocus:///folder/cmYwdTadWxD 16 | 3-Sub Folder 2: 17 | omnifocus:///folder/kyyjHNCEm8v 18 | 1-empty project: @context(TestContext) 19 | omnifocus:///task/iXSU7vo3FuR 20 | 2-F2 Project 1: @context(TestContext) 21 | omnifocus:///task/pLoziQtuwYD 22 | - 2-task F2 P1 1 @flagged @context(4-SubContext1) @project(2-F2Project1) 23 | - 1-task F2 P1 2 @context(3-SubContext2) @project(2-F2Project1) 24 | - I'm done! @done(2013-04-21) @context(TestContext) @project(2-F2Project1) 25 | F2 Project 2: @done(2013-04-21) @context(TestContext) 26 | omnifocus:///task/fYuqelo7INX 27 | - task F2 P2 1 @context(4-SubContext1) @project(F2Project2) 28 | - task F2 P2 2 @context(3-SubContext2) @project(F2Project2) 29 | - task with no context @context(TestContext) @project(F2Project2) 30 | -------------------------------------------------------------------------------- /src/test/data/db-4-Project-sort.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | 2-Empty Folder: 4 | omnifocus:///folder/cmYwdTadWxD 5 | 1-Sub Folder 1: 6 | omnifocus:///folder/msGNrO_ZBdL 7 | 2-F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 8 | omnifocus:///task/d39BskyduPI 9 | 2-parent task F1 P1 1: @context(4-SubContext1) @project(2-F1Project1) 10 | - 1-sub task with a colon @context(TestContext) @project(2-F1Project1) 11 | - 3-task F1 P1 2 @context(3-SubContext2) @project(2-F1Project1) 12 | 1-F1 Project 2: @flagged @context(TestContext) 13 | omnifocus:///task/i0LhrVuFaPc 14 | - 1-task F1 P2 2 @context(3-SubContext2) @project(1-F1Project2) 15 | - 2-task F1 P2 1 @context(4-SubContext1) @project(1-F1Project2) 16 | 3-Sub Folder 2: 17 | omnifocus:///folder/kyyjHNCEm8v 18 | 2-F2 Project 1: @context(TestContext) 19 | omnifocus:///task/pLoziQtuwYD 20 | - 1-task F2 P1 2 @context(3-SubContext2) @project(2-F2Project1) 21 | - 2-task F2 P1 1 @flagged @context(4-SubContext1) @project(2-F2Project1) 22 | - I'm done! @done(2013-04-21) @context(TestContext) @project(2-F2Project1) 23 | 1-empty project: @context(TestContext) 24 | omnifocus:///task/iXSU7vo3FuR 25 | F2 Project 2: @done(2013-04-21) @context(TestContext) 26 | omnifocus:///task/fYuqelo7INX 27 | - task F2 P2 1 @context(4-SubContext1) @project(F2Project2) 28 | - task F2 P2 2 @context(3-SubContext2) @project(F2Project2) 29 | - task with no context @context(TestContext) @project(F2Project2) 30 | -------------------------------------------------------------------------------- /src/test/data/ex1-worms-last-week.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | 3-Sub Folder 2: 4 | omnifocus:///folder/kyyjHNCEm8v 5 | Worms: 6 | omnifocus:///folder/b-r3kiUoU2F 7 | Compost: @context(TestContext) 8 | omnifocus:///task/jMV4htWMbFb 9 | - last week @done(2013-04-27) @context(TestContext) @project(Compost) 10 | -------------------------------------------------------------------------------- /src/test/data/ex2-ham-today.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Ham: 4 | omnifocus:///folder/lcL-iBsxMtg 5 | Whatever: @context(TestContext) 6 | omnifocus:///task/obDRY3jsoxB 7 | - done today @flagged @done(2013-04-29) @context(TestContext) @project(Whatever) 8 | -------------------------------------------------------------------------------- /src/test/data/ex3-not-worms.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Ham: 4 | omnifocus:///folder/lcL-iBsxMtg 5 | Whatever: @context(TestContext) 6 | omnifocus:///task/obDRY3jsoxB 7 | - done today @flagged @done(2013-04-29) @context(TestContext) @project(Whatever) 8 | - vcvcvcvcvcvcv @context(TestContext) @project(Whatever) 9 | 1-Sub Folder 1: 10 | omnifocus:///folder/msGNrO_ZBdL 11 | 2-F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 12 | omnifocus:///task/d39BskyduPI 13 | 2-parent task F1 P1 1: @context(4-SubContext1) @project(2-F1Project1) 14 | - 1-sub task with a colon @context(TestContext) @project(2-F1Project1) 15 | - 3-task F1 P1 2 @due(2013-04-30) @context(3-SubContext2) @project(2-F1Project1) 16 | 1-F1 Project 2: @flagged @context(TestContext) 17 | omnifocus:///task/i0LhrVuFaPc 18 | - 2-task F1 P2 1 @context(4-SubContext1) @project(1-F1Project2) 19 | - 1-task F1 P2 2 @context(3-SubContext2) @project(1-F1Project2) 20 | 3-Sub Folder 2: 21 | omnifocus:///folder/kyyjHNCEm8v 22 | 2-F2 Project 1: @context(TestContext) 23 | omnifocus:///task/pLoziQtuwYD 24 | - 2-task F2 P1 1 @flagged @context(4-SubContext1) @project(2-F2Project1) 25 | - 1-task F2 P1 2 @context(3-SubContext2) @project(2-F2Project1) 26 | - I'm done! @done(2013-04-21) @context(TestContext) @project(2-F2Project1) 27 | F2 Project 2: @done(2013-04-21) @context(TestContext) 28 | omnifocus:///task/fYuqelo7INX 29 | - task F2 P2 1 @context(4-SubContext1) @project(F2Project2) 30 | - task F2 P2 2 @context(3-SubContext2) @project(F2Project2) 31 | - task with no context @context(TestContext) @project(F2Project2) 32 | -------------------------------------------------------------------------------- /src/test/data/ex4-worms-or-ham-today.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | Ham: 4 | omnifocus:///folder/lcL-iBsxMtg 5 | Whatever: @context(TestContext) 6 | omnifocus:///task/obDRY3jsoxB 7 | - done today @flagged @done(2013-04-29) @context(TestContext) @project(Whatever) 8 | 3-Sub Folder 2: 9 | omnifocus:///folder/kyyjHNCEm8v 10 | Worms: 11 | omnifocus:///folder/b-r3kiUoU2F 12 | Compost: @context(TestContext) 13 | omnifocus:///task/jMV4htWMbFb 14 | - today abc123 @done(2013-04-29) @context(TestContext) @project(Compost) 15 | -------------------------------------------------------------------------------- /src/test/data/ex5-work-this-week-flat.taskpaper: -------------------------------------------------------------------------------- 1 | Compost: @context(TestContext) 2 | omnifocus:///task/jMV4htWMbFb 3 | - last week @done(2013-04-27) @context(TestContext) @project(Compost) 4 | - xxxxx @context(TestContext) @project(Compost) 5 | - today abc123 @done(2013-04-29) @context(TestContext) @project(Compost) 6 | -------------------------------------------------------------------------------- /src/test/data/ex8-due-or-flagged.taskpaper: -------------------------------------------------------------------------------- 1 | Test Folder: 2 | omnifocus:///folder/bQoc15uOjLQ 3 | 1-Sub Folder 1: 4 | omnifocus:///folder/msGNrO_ZBdL 5 | 2-F1 Project 1: @start(2013-04-22) @due(2013-04-23) @context(TestContext) 6 | omnifocus:///task/d39BskyduPI 7 | - 3-task F1 P1 2 @due(2013-04-30) @context(3-SubContext2) @project(2-F1Project1) 8 | 3-Sub Folder 2: 9 | omnifocus:///folder/kyyjHNCEm8v 10 | 2-F2 Project 1: @context(TestContext) 11 | omnifocus:///task/pLoziQtuwYD 12 | - 2-task F2 P1 1 @flagged @context(4-SubContext1) @project(2-F2Project1) 13 | -------------------------------------------------------------------------------- /src/test/data/ex9.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | X-WR-CALNAME:OmniFocus 4 | PRODID:-//ofexport//ofexport//EN 5 | METHOD:PUBLISH 6 | CALSCALE:GREGORIAN 7 | BEGIN:VEVENT 8 | DTSTART:20130421T230000Z 9 | DTEND:20130423T080000Z 10 | DTSTAMP:20130423T080000Z 11 | SUMMARY:2-F1 Project 1 12 | URL:omnifocus:///task/d39BskyduPI 13 | UID:c9170651-b111-11e2-9198-a820662fff7a 14 | DESCRIPTION: 15 | BEGIN:VALARM 16 | ACTION:DISPLAY 17 | DESCRIPTION:OmniFocus Reminder 18 | TRIGGER:-PT0M 19 | END:VALARM 20 | END:VEVENT 21 | BEGIN:VEVENT 22 | DTSTART:20130430T080000Z 23 | DTEND:20130430T080000Z 24 | DTSTAMP:20130430T080000Z 25 | SUMMARY:3-task F1 P1 2 26 | URL:omnifocus:///task/iocL5D8sp9t 27 | UID:c92972a6-b111-11e2-b0ce-a820662fff7a 28 | DESCRIPTION: 29 | BEGIN:VALARM 30 | ACTION:DISPLAY 31 | DESCRIPTION:OmniFocus Reminder 32 | TRIGGER:-PT0M 33 | END:VALARM 34 | END:VEVENT 35 | END:VCALENDAR 36 | -------------------------------------------------------------------------------- /src/test/data/help.txt: -------------------------------------------------------------------------------- 1 | Version: XXX XXX 2 | 3 | Usage: 4 | 5 | ofexport [options...] -o file_name 6 | 7 | options: 8 | -h,-?,--help : print help 9 | -C : context mode (as opposed to project mode) 10 | -P : project mode - the default (as opposed to context mode) 11 | -I : include mode (as opposed to exclude mode) 12 | -E : exclude mode - the default (as opposed to include mode) 13 | -o file_name : the output file name, must end in a recognised suffix - see documentation 14 | -i file_name : read file_name instead of the OmniFocus database, must be in json format 15 | -T template_name : use the specified template instead of one derived from the output file extension 16 | --open : open the output file with the registered application (if one is installed) 17 | -v : verbose output 18 | -z : maximum diagnostics 19 | -V level : set the global log level (ERROR, INFO, DEBUG, TRACE) 20 | --log name=level : set a logger to a particular level 21 | --debug arg : set test options 22 | 23 | filters: 24 | -a,--any expr : filter tasks, projects, contexts and folders against the expression 25 | -t,--task expr : filter any task against task against the expression 26 | -p,--project expr : filter any project against the expression 27 | -f,--folder expr : filter any folder against the expression 28 | -c,--context expr : filter any context type against the expression 29 | --tasks : filter out everything except tasks 30 | 31 | See DOCUMENTATION.md for more information 32 | -------------------------------------------------------------------------------- /src/test/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /src/test/python/attrib_convert_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from treemodel import Task 4 | from attrib_convert import Conversion, AttribMapBuilder 5 | 6 | class Test_conversion_convert(unittest.TestCase): 7 | 8 | def test_convert_attribute (self): 9 | type_fns = {} 10 | type_fns['string'] = lambda x: x 11 | 12 | conversion = Conversion ("nosuchconversion", "default value", "($value)", "string") 13 | self.assertEquals("default value", conversion.value(Task (), type_fns)) 14 | 15 | conversion = Conversion ("name", "default value", "($value)", "string") 16 | self.assertEquals("default value", conversion.value(Task (), type_fns)) 17 | 18 | conversion = Conversion ("name", "default value", "($value)", "string") 19 | self.assertEquals("(123)", conversion.value(Task (name="123"), type_fns)) 20 | 21 | def test_build_attribute_map (self): 22 | builder = AttribMapBuilder () 23 | values = builder.get_values(Task (name="123")) 24 | self.assertEqual("123", values["name"]) 25 | -------------------------------------------------------------------------------- /src/test/python/ofexport_test.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 unittest 18 | 19 | from ofexport import fix_abbrieviated_expr 20 | 21 | class Test_fmt_datematch(unittest.TestCase): 22 | 23 | def test_fix_abbrieviated_expr (self): 24 | self.assertEquals ('(type=Project) and (name=x)', fix_abbrieviated_expr ('Project', '=x')) 25 | self.assertEquals ('(type=Project) and (name!=x)', fix_abbrieviated_expr ('Project', '!=x')) 26 | self.assertEquals ('name=x', fix_abbrieviated_expr ('any', '=x')) 27 | self.assertEquals ('name!=x', fix_abbrieviated_expr ('all', '!=x')) 28 | self.assertEquals ('prune Task', fix_abbrieviated_expr ('Task', 'prune')) 29 | self.assertEquals ('prune any', fix_abbrieviated_expr ('any', 'prune')) 30 | self.assertEquals ('prune all', fix_abbrieviated_expr ('all', 'prune')) 31 | self.assertEquals ('sort Folder text', fix_abbrieviated_expr ('Folder', 'sort')) 32 | self.assertEquals ('sort Folder due', fix_abbrieviated_expr ('Folder', 'sort due')) -------------------------------------------------------------------------------- /src/test/python/plugin_ics_test.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 unittest 18 | from plugin_ics import fix_dates, load_note_attribs, utc, format_date, format_alarm 19 | from treemodel import Task, Note 20 | from datetime import datetime 21 | import dateutil.parser 22 | 23 | class TestNote (Note): 24 | def __init__ (self, text): 25 | self.text = text 26 | self.lines = text.split('\n') 27 | def get_note (self): 28 | return self.text 29 | def get_note_lines (self): 30 | return self.lines 31 | 32 | class Test_fmt_datematch(unittest.TestCase): 33 | 34 | def test_fix_dates (self): 35 | tue = datetime.strptime('Apr 9 2013 11:33PM', '%b %d %Y %I:%M%p') 36 | wed = datetime.strptime('Apr 10 2013 11:33PM', '%b %d %Y %I:%M%p') 37 | 38 | task = Task () 39 | fix_dates (task) 40 | self.assertEquals(None, task.date_to_start) 41 | self.assertEquals(None, task.date_due) 42 | 43 | task = Task (date_to_start=tue) 44 | fix_dates (task) 45 | self.assertEquals(tue, task.date_to_start) 46 | self.assertEquals(tue, task.date_due) 47 | 48 | task = Task (date_due=tue) 49 | fix_dates (task) 50 | self.assertEquals(tue, task.date_to_start) 51 | self.assertEquals(tue, task.date_due) 52 | 53 | task = Task (date_to_start=tue, date_due=wed) 54 | fix_dates (task) 55 | self.assertEquals(tue, task.date_to_start) 56 | self.assertEquals(wed, task.date_due) 57 | 58 | def test_load_note_attribs (self): 59 | task = Task () 60 | n_attribs_before = len (task.attribs) 61 | load_note_attribs (task, "") 62 | self.assertEqual (n_attribs_before, len (task.attribs)) 63 | 64 | n_attribs_before = len (task.attribs) 65 | task = Task (note=TestNote("xyz")) 66 | self.assertEqual (n_attribs_before, len (task.attribs)) 67 | 68 | task = Task (note=TestNote("%of cal xxx")) 69 | load_note_attribs (task, "") 70 | self.assertEquals (True, task.attribs['xxx']) 71 | 72 | task = Task (note=TestNote("%of cal xxx\n%of cal yyy")) 73 | load_note_attribs (task, "") 74 | self.assertEquals (True, task.attribs['xxx']) 75 | self.assertEquals (True, task.attribs['yyy']) 76 | 77 | task = Task (note=TestNote("%of cal xxx yyy zzz ")) 78 | n_attribs_before = len (task.attribs) 79 | load_note_attribs (task, "") 80 | self.assertEquals (True, task.attribs['xxx']) 81 | self.assertEquals (True, task.attribs['yyy']) 82 | self.assertEquals (True, task.attribs['zzz']) 83 | self.assertEqual (n_attribs_before + 3, len (task.attribs)) 84 | 85 | def test_load_note_attribs_with_default (self): 86 | task = Task () 87 | n_attribs_before = len (task.attribs) 88 | load_note_attribs (task, "%of cal in_default") 89 | self.assertEquals (True, task.attribs['in_default']) 90 | self.assertEqual (n_attribs_before + 1, len (task.attribs)) 91 | 92 | task = Task (note=TestNote("%of cal in_note")) 93 | n_attribs_before = len (task.attribs) 94 | load_note_attribs (task, "%of cal in_default") 95 | self.assertEquals (True, task.attribs['in_note']) 96 | self.assertEqual (n_attribs_before + 1, len (task.attribs)) 97 | 98 | def test_load_note_start_attrib (self): 99 | the_date = dateutil.parser.parse('Wed, 27 Oct 2010 22:17:00 BST') 100 | task = Task (date_to_start=the_date, date_due=the_date, note=TestNote("%of cal start=18:12")) 101 | load_note_attribs (task, "") 102 | self.assertEqual ("2010.10.27 18:12", task.date_to_start.strftime ('%Y.%m.%d %H:%M')) 103 | self.assertEqual ("2010.10.27 22:17", task.date_due.strftime ('%Y.%m.%d %H:%M')) 104 | 105 | def test_load_note_due_attrib (self): 106 | the_date = dateutil.parser.parse('Wed, 27 Oct 2010 22:17:00 BST') 107 | task = Task (date_to_start=the_date, date_due=the_date, note=TestNote("%of cal due=23:12")) 108 | load_note_attribs (task, "") 109 | self.assertEqual ("2010.10.27 22:17", task.date_to_start.strftime ('%Y.%m.%d %H:%M')) 110 | self.assertEqual ("2010.10.27 23:12", task.date_due.strftime ('%Y.%m.%d %H:%M')) 111 | 112 | def test_utc (self): 113 | the_date = dateutil.parser.parse('Wed, 27 Oct 2010 22:17:00 BST') 114 | self.assertEquals ("2010.10.27 22:17 BST", the_date.strftime ('%Y.%m.%d %H:%M %Z')) 115 | self.assertEquals ("2010.10.27 21:17", utc(the_date).strftime ('%Y.%m.%d %H:%M')) 116 | 117 | the_date = dateutil.parser.parse('Wed, 27 Oct 2010 22:17:00 UTC') 118 | self.assertEquals ("2010.10.27 22:17 UTC", the_date.strftime ('%Y.%m.%d %H:%M %Z')) 119 | self.assertEquals ("2010.10.27 22:17", utc(the_date).strftime ('%Y.%m.%d %H:%M')) 120 | 121 | def test_format_date (self): 122 | wed = dateutil.parser.parse('Wed, 27 Oct 2010 22:17:00 BST') 123 | thu = dateutil.parser.parse('Thu, 28 Oct 2010 22:17:00 BST') 124 | 125 | task = Task (date_to_start=wed, date_due=thu) 126 | self.assertEquals ("20101027T211700Z", format_date (task, task.date_to_start, False)) 127 | self.assertEquals ("20101028T211700Z", format_date (task, task.date_due, True)) 128 | 129 | task = Task (date_to_start=wed, date_due=thu) 130 | task.attribs["allday"] = True 131 | self.assertEquals ("20101027", format_date (task, task.date_to_start, False)) 132 | self.assertEquals ("20101029", format_date (task, task.date_due, True)) 133 | 134 | wed1 = dateutil.parser.parse('Wed, 27 Oct 2010 00:00:00 BST') 135 | wed2 = dateutil.parser.parse('Wed, 27 Oct 2010 23:59:59 BST') 136 | task = Task (date_to_start=wed1, date_due=wed2) 137 | task.attribs["allday"] = True 138 | self.assertEquals ("20101027", format_date (task, task.date_to_start, False)) 139 | self.assertEquals ("20101028", format_date (task, task.date_due, True)) 140 | 141 | wed1 = dateutil.parser.parse('Wed, 27 Oct 2010 00:00:00 BST') 142 | wed2 = dateutil.parser.parse('Wed, 28 Oct 2010 00:00:00 BST') 143 | task = Task (date_to_start=wed1, date_due=wed2) 144 | task.attribs["allday"] = True 145 | self.assertEquals ("20101027", format_date (task, task.date_to_start, False)) 146 | self.assertEquals ("20101029", format_date (task, task.date_due, True)) 147 | 148 | def test_format_alarm (self): 149 | task = Task () 150 | self.assertEquals ("BEGIN:VALARM\nACTION:DISPLAY\nDESCRIPTION:OmniFocus Reminder\nTRIGGER:-PT0M\nEND:VALARM\n", format_alarm (task)) 151 | 152 | task.attribs['noalarm'] = True 153 | self.assertEquals ("", format_alarm (task)) 154 | 155 | -------------------------------------------------------------------------------- /src/test/python/test_helper.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 catch_exception (fn): 18 | try: 19 | fn() 20 | except Exception as e: 21 | return e.message 22 | assert False, "Exception expected, none raised" 23 | -------------------------------------------------------------------------------- /src/test/python/types_test.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 unittest 18 | from typeof import TypeOf 19 | 20 | class Parent(object): 21 | pass 22 | 23 | class Child (Parent): 24 | pass 25 | 26 | class DemoClass(object): 27 | string = TypeOf ("string", str) 28 | child = TypeOf ("child", Child) 29 | parent = TypeOf ("parent", Parent) 30 | 31 | class Test_typeof(unittest.TestCase): 32 | 33 | def test_field_access_when_not_set (self): 34 | demo = DemoClass () 35 | self.assertEqual (None, demo.string) 36 | 37 | def test_field_access_when_set (self): 38 | demo = DemoClass () 39 | demo.string = 'xxx' 40 | self.assertEqual ('xxx', demo.string) 41 | 42 | def test_set_to_null (self): 43 | demo = DemoClass () 44 | demo.string = None 45 | self.assertEqual (None, demo.string) 46 | 47 | def test_set_to_bad_type (self): 48 | demo = DemoClass () 49 | try: 50 | demo.string = 42 51 | self.fail('expected error') 52 | except AssertionError as e: 53 | self.assertEqual("string: expected type got ", e.message) 54 | 55 | def test_type_hierarchy_ok (self): 56 | demo = DemoClass () 57 | demo.child = Child () 58 | demo.parent = Parent () 59 | demo.parent = Child () 60 | 61 | def test_type_hierarchy_bad (self): 62 | demo = DemoClass () 63 | try: 64 | demo.child = Parent () 65 | self.fail('expected error') 66 | except AssertionError as e: 67 | self.assertEqual("child: expected type got ", e.message) 68 | -------------------------------------------------------------------------------- /src/test/python/util_test.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 unittest 18 | from util import strip_tabs_newlines 19 | 20 | class Test_util(unittest.TestCase): 21 | 22 | def test_strip_tabs_newlines (self): 23 | self.assertEquals ('aa bb cc dd ee', strip_tabs_newlines (' aa\nbb cc\ndd\tee')) -------------------------------------------------------------------------------- /templates/flat.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "flat", 3 | "#02": "A template that doesn't print Folder, Project, or Context titles or indent tasks", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " context:$value", 9 | "type": "string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " done:$value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " due:$value", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " start:$value", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " flagged", 30 | "type": "boolean" 31 | }, 32 | "id": { 33 | "default": "", 34 | "format": "$value", 35 | "type": "string" 36 | }, 37 | "link": { 38 | "default": "", 39 | "format": "$value", 40 | "type": "string" 41 | }, 42 | "name": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "note": { 48 | "default": "", 49 | "format": "", 50 | "type": "note" 51 | }, 52 | "project": { 53 | "default": "", 54 | "eval": "value.name", 55 | "format": " project:$value", 56 | "type": "string" 57 | }, 58 | "status": { 59 | "default": "", 60 | "format": " status:$value", 61 | "type": "string" 62 | }, 63 | "type": { 64 | "default": "", 65 | "format": "$value", 66 | "type": "string" 67 | } 68 | }, 69 | "dateFormat": "%Y-%m-%d", 70 | "depth": 0, 71 | "indent": 0, 72 | "indentString": "\t", 73 | "nodes": { 74 | "TaskGroupStart": "- $name$date_due", 75 | "TaskStart": "- $name$date_due" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /templates/help-template.txt: -------------------------------------------------------------------------------- 1 | 2 | Usage: 3 | 4 | ofexport [options...] -o file_name 5 | 6 | options: 7 | {{-h}},{{-?}},{{--help}} : print help 8 | {{-C}} : context mode (as opposed to project mode) 9 | {{-P}} : project mode - the default (as opposed to context mode) 10 | {{-I}} : include mode (as opposed to exclude mode) 11 | {{-E}} : exclude mode - the default (as opposed to include mode) 12 | {{-o:}} file_name : the output file name, must end in a recognised suffix - see documentation 13 | {{-i:}} file_name : read file_name instead of the OmniFocus database, must be in json format 14 | {{-T:}} template_name : use the specified template instead of one derived from the output file extension 15 | {{--open}} : open the output file with the registered application (if one is installed) 16 | {{-v}} : verbose output 17 | {{-z}} : maximum diagnostics 18 | {{-V:}} level : set the global log level (ERROR, INFO, DEBUG, TRACE) 19 | {{--log=}} name=level : set a logger to a particular level 20 | {{--debug=}} arg : set test options 21 | 22 | filters: 23 | {{-a:}},{{--any=}} expr : filter tasks, projects, contexts and folders against the expression 24 | {{-t:}},{{--task=}} expr : filter any task against task against the expression 25 | {{-p:}},{{--project=}} expr : filter any project against the expression 26 | {{-f:}},{{--folder=}} expr : filter any folder against the expression 27 | {{-c:}},{{--context=}} expr : filter any context type against the expression 28 | {{--tasks}} : filter out everything except tasks 29 | 30 | See DOCUMENTATION.md for more information 31 | -------------------------------------------------------------------------------- /templates/html-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "html-lite", 3 | "#02": "Based on the html template, doesn't include all the attributes in the output", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " $value", 9 | "type": "string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " $value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " $value", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " $value", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " FLAGGED", 30 | "type": "boolean" 31 | }, 32 | "id": { 33 | "default": "", 34 | "format": " $value", 35 | "type": "string" 36 | }, 37 | "link": { 38 | "default": "", 39 | "format": "$value", 40 | "type": "string" 41 | }, 42 | "name": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "note": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "note" 51 | }, 52 | "project": { 53 | "default": "", 54 | "eval": "value.name", 55 | "format": " $value", 56 | "type": "string" 57 | }, 58 | "type": { 59 | "default": "", 60 | "format": " $value", 61 | "type": "string" 62 | } 63 | }, 64 | "dateFormat": "%Y-%m-%d", 65 | "depth": 1, 66 | "indent": 2, 67 | "indentString": " ", 68 | "nodes": { 69 | "ContextStart": "$indent$name", 70 | "FolderStart": "$indent$name", 71 | "ProjectEnd": "$indent", 72 | "ProjectStart": "$indent$name
      ", 73 | "TaskGroupEnd": "$indent
    ", 74 | "TaskGroupStart": "$indent
  • $name$date_completed
    • ", 75 | "TaskStart": "$indent
    • $name$date_completed
    • " 76 | }, 77 | "postamble": " \n", 78 | "preambleFile": "html-start.txt" 79 | } 80 | -------------------------------------------------------------------------------- /templates/html-start.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | OmniFocus 4 | 87 | 88 | -------------------------------------------------------------------------------- /templates/html.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "html", 3 | "#02": "A template that produces html output", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " $value", 9 | "type": "html.string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " $value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " $value", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " $value", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " FLAGGED", 30 | "type": "boolean" 31 | }, 32 | "id": { 33 | "default": "", 34 | "format": " $value", 35 | "type": "html.string" 36 | }, 37 | "link": { 38 | "default": "", 39 | "format": "$value", 40 | "type": "string" 41 | }, 42 | "name": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "html.string" 46 | }, 47 | "note": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "html.note" 51 | }, 52 | "project": { 53 | "default": "", 54 | "eval": "value.name", 55 | "format": " $value", 56 | "type": "html.string" 57 | }, 58 | "type": { 59 | "default": "", 60 | "format": " $value", 61 | "type": "html.string" 62 | } 63 | }, 64 | "dateFormat": "%Y-%m-%d", 65 | "depth": 1, 66 | "indent": 2, 67 | "indentString": " ", 68 | "nodes": { 69 | "ContextStart": "$indent$name", 70 | "FolderStart": "$indent$name", 71 | "NoteLine": "$indent$note_line
      ", 72 | "ProjectEnd": "$indent
    ", 73 | "ProjectStart": "$indent$name$flagged$date_to_start$date_due$date_completed$context$project
      ", 74 | "TaskGroupEnd": "$indent
    ", 75 | "TaskGroupStart": "$indent
  • $name$flagged$date_to_start$date_due$date_completed$context$project
    • ", 76 | "TaskStart": "$indent
    • $name$flagged$date_to_start$date_due$date_completed$context$project
    • " 77 | }, 78 | "postamble": " \n", 79 | "preambleFile": "html-start.txt" 80 | } 81 | -------------------------------------------------------------------------------- /templates/ics.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "ics", 3 | "#02": "A template that produces calendar files that can be subscribed to with calendar apps/services", 4 | "attributes": { 5 | "date_due": { 6 | "default": "", 7 | "format": "$value", 8 | "type": "ics.date_due" 9 | }, 10 | "date_to_start": { 11 | "default": "", 12 | "format": "$value", 13 | "type": "ics.date_to_start" 14 | }, 15 | "id": { 16 | "default": "", 17 | "format": "$value", 18 | "type": "string" 19 | }, 20 | "link": { 21 | "default": "", 22 | "format": "$value", 23 | "type": "string" 24 | }, 25 | "name": { 26 | "default": "", 27 | "format": "$value", 28 | "type": "string" 29 | }, 30 | "note": { 31 | "default": "", 32 | "format": "", 33 | "type": "ics.note" 34 | } 35 | }, 36 | "dateFormat": "%Y%m%d", 37 | "depth": 0, 38 | "indent": 0, 39 | "indentString": " ", 40 | "nodes": { 41 | "ProjectStart": "BEGIN:VEVENT\nDTSTART:$date_to_start\nDTEND:$date_due\nDTSTAMP:$date_due\nSUMMARY:$name\nURL:$link\nUID:$id\nDESCRIPTION:\n${alarm}END:VEVENT", 42 | "TaskGroupStart": "BEGIN:VEVENT\nDTSTART:$date_to_start\nDTEND:$date_due\nDTSTAMP:$date_due\nSUMMARY:$name\nURL:$link\nUID:$id\nDESCRIPTION:\n${alarm}END:VEVENT", 43 | "TaskStart": "BEGIN:VEVENT\nDTSTART:$date_to_start\nDTEND:$date_due\nDTSTAMP:$date_due\nSUMMARY:$name\nURL:$link\nUID:$id\nDESCRIPTION:\n${alarm}END:VEVENT" 44 | }, 45 | "postamble": "END:VCALENDAR", 46 | "preamble": "BEGIN:VCALENDAR\nVERSION:2.0\nX-WR-CALNAME:OmniFocus\nPRODID:-//ofexport//ofexport//EN\nMETHOD:PUBLISH\nCALSCALE:GREGORIAN" 47 | } 48 | -------------------------------------------------------------------------------- /templates/markdown-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "markdown", 3 | "#02": "A template for markdown files", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " project:**$value**", 9 | "type": "string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " done:**$value**", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " due:**$value**", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " start:**$value**", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " flagged", 30 | "type": "boolean" 31 | }, 32 | "hashes": { 33 | "default": "", 34 | "format": "$value", 35 | "type": "string" 36 | }, 37 | "id": { 38 | "default": "", 39 | "format": " $value", 40 | "type": "string" 41 | }, 42 | "link": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "name": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "string" 51 | }, 52 | "note": { 53 | "default": "", 54 | "format": "$value", 55 | "type": "note" 56 | }, 57 | "project": { 58 | "default": "", 59 | "eval": "value.name", 60 | "format": " context:**$value**", 61 | "type": "string" 62 | }, 63 | "type": { 64 | "default": "", 65 | "format": " $value", 66 | "type": "string" 67 | } 68 | }, 69 | "dateFormat": "%Y-%m-%d", 70 | "depth": 0, 71 | "indent": 0, 72 | "indentString": " ", 73 | "nodes": { 74 | "ContextStart": "$hashes$name", 75 | "FolderStart": "$hashes$name", 76 | "ProjectStart": "$hashes$name", 77 | "TaskGroupStart": "$indent- $name$date_completed", 78 | "TaskStart": "$indent- $name$date_completed" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /templates/markdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "markdown", 3 | "#02": "A template for markdown files", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " project:**$value**", 9 | "type": "string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " done:**$value**", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " due:**$value**", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " start:**$value**", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " flagged", 30 | "type": "boolean" 31 | }, 32 | "hashes": { 33 | "default": "", 34 | "format": "$value", 35 | "type": "string" 36 | }, 37 | "id": { 38 | "default": "", 39 | "format": " $value", 40 | "type": "string" 41 | }, 42 | "link": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "name": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "string" 51 | }, 52 | "note": { 53 | "default": "", 54 | "format": "$value", 55 | "type": "note" 56 | }, 57 | "project": { 58 | "default": "", 59 | "eval": "value.name", 60 | "format": " context:**$value**", 61 | "type": "string" 62 | }, 63 | "type": { 64 | "default": "", 65 | "format": " $value", 66 | "type": "string" 67 | } 68 | }, 69 | "dateFormat": "%Y-%m-%d", 70 | "depth": 0, 71 | "indent": 0, 72 | "indentString": " ", 73 | "nodes": { 74 | "ContextStart": "$hashes$name", 75 | "FolderStart": "$hashes$name", 76 | "NoteLine": "$indent $note_line", 77 | "ProjectStart": "$hashes$name", 78 | "TaskGroupStart": "$indent- $name$date_completed", 79 | "TaskStart": "$indent- $name$date_completed" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /templates/opml.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "opml", 3 | "#02": "A template for opml files that can be read by OmniOutliner and several mindmap tools", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": "$value", 9 | "type": "opml.string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": "$value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": "$value", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": "$value", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "format": "$value", 29 | "type": "boolean" 30 | }, 31 | "id": { 32 | "default": "", 33 | "format": "$value", 34 | "type": "opml.string" 35 | }, 36 | "link": { 37 | "default": "", 38 | "format": "$value", 39 | "type": "opml.string" 40 | }, 41 | "name": { 42 | "default": "", 43 | "format": "$value", 44 | "type": "opml.string" 45 | }, 46 | "note": { 47 | "default": "", 48 | "format": "$value", 49 | "type": "opml.note" 50 | }, 51 | "project": { 52 | "default": "", 53 | "eval": "value.name", 54 | "format": "$value", 55 | "type": "opml.string" 56 | }, 57 | "type": { 58 | "default": "", 59 | "format": "$value", 60 | "type": "opml.string" 61 | } 62 | }, 63 | "dateFormat": "%Y-%m-%d", 64 | "depth": 0, 65 | "indent": 2, 66 | "indentString": " ", 67 | "nodes": { 68 | "ContextEnd": "$indent", 69 | "ContextStart": "$indent", 70 | "FolderEnd": "$indent", 71 | "FolderStart": "$indent", 72 | "ProjectEnd": "$indent", 73 | "ProjectStart": "$indent", 74 | "TaskEnd": "$indent", 75 | "TaskGroupEnd": "$indent", 76 | "TaskGroupStart": "$indent", 77 | "TaskStart": "$indent" 78 | }, 79 | "postamble": " \n", 80 | "preamble": "\n\n \n OmniFocus\n \n " 81 | } 82 | -------------------------------------------------------------------------------- /templates/taskpaper-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "taskpaper-lite", 3 | "#02": "Based on the taskpaper template but with less tags in the output and uses @yyyy-mm-dd instead of @done(yyyy-mm-dd)", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " @context($value)", 9 | "type": "taskpaper.tag" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " @$value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " @due($value)", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " @start($value)", 24 | "type": "date" 25 | }, 26 | "estimated_minutes": { 27 | "default": "", 28 | "format": " @mins($value)", 29 | "type": "int" 30 | }, 31 | "flagged": { 32 | "default": "", 33 | "eval": "True if value else None", 34 | "format": " @flagged", 35 | "type": "boolean" 36 | }, 37 | "id": { 38 | "default": "", 39 | "format": " @id($value)", 40 | "type": "taskpaper.tag" 41 | }, 42 | "link": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "name": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "taskpaper.title" 51 | }, 52 | "note": { 53 | "default": "", 54 | "format": "$value", 55 | "type": "note" 56 | }, 57 | "project": { 58 | "default": "", 59 | "eval": "value.name", 60 | "format": " @project($value)", 61 | "type": "taskpaper.tag" 62 | }, 63 | "type": { 64 | "default": "", 65 | "format": " @type($value)", 66 | "type": "taskpaper.tag" 67 | } 68 | }, 69 | "dateFormat": "%Y-%m-%d-%a", 70 | "depth": 0, 71 | "indent": 0, 72 | "indentString": "\t", 73 | "nodes": { 74 | "ContextStart": "$indent$name:", 75 | "FolderStart": "$indent$name:", 76 | "ProjectStart": "$indent$name:", 77 | "TaskGroupStart": "$indent$name:$date_completed", 78 | "TaskStart": "$indent- $name$date_completed$estimated_minutes" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /templates/taskpaper.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "taskpaper", 3 | "#02": "A for taskpaper documents", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " @context($value)", 9 | "type": "taskpaper.tag" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " @done($value)", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " @due($value)", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " @start($value)", 24 | "type": "date" 25 | }, 26 | "date_added": { 27 | "default": "", 28 | "format": " @added($value)", 29 | "type": "date" 30 | }, 31 | "estimated_minutes": { 32 | "default": "", 33 | "format": " @mins($value)", 34 | "type": "int" 35 | }, 36 | "flagged": { 37 | "default": "", 38 | "eval": "True if value else None", 39 | "format": " @flagged", 40 | "type": "boolean" 41 | }, 42 | "id": { 43 | "default": "", 44 | "format": " @id($value)", 45 | "type": "taskpaper.tag" 46 | }, 47 | "link": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "string" 51 | }, 52 | "name": { 53 | "default": "", 54 | "format": "$value", 55 | "type": "taskpaper.title" 56 | }, 57 | "note": { 58 | "default": "", 59 | "format": "$value", 60 | "type": "note" 61 | }, 62 | "project": { 63 | "default": "", 64 | "eval": "value.name", 65 | "format": " @project($value)", 66 | "type": "taskpaper.tag" 67 | }, 68 | "type": { 69 | "default": "", 70 | "format": " @type($value)", 71 | "type": "taskpaper.tag" 72 | } 73 | }, 74 | "dateFormat": "%Y-%m-%d", 75 | "depth": 0, 76 | "indent": 0, 77 | "indentString": "\t", 78 | "nodes": { 79 | "ContextStart": "$indent$name:\n$indent\t$link", 80 | "FolderStart": "$indent$name:\n$indent\t$link", 81 | "NoteLine": "$indent\t$note_line", 82 | "ProjectStart": "$indent$name:$flagged$date_to_start$date_due$date_added$date_completed$context\n$indent\t$link", 83 | "TaskGroupStart": "$indent$name:$flagged$date_to_start$date_due$date_added$date_completed$estimated_minutes$context$project", 84 | "TaskStart": "$indent- $name$flagged$date_to_start$date_due$date_added$date_completed$estimated_minutes$context$project" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /templates/text.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "text", 3 | "#02": "A template for text documents", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name", 8 | "format": " context:$value", 9 | "type": "string" 10 | }, 11 | "date_completed": { 12 | "default": "", 13 | "format": " done:$value", 14 | "type": "date" 15 | }, 16 | "date_due": { 17 | "default": "", 18 | "format": " due:$value", 19 | "type": "date" 20 | }, 21 | "date_to_start": { 22 | "default": "", 23 | "format": " start:$value", 24 | "type": "date" 25 | }, 26 | "flagged": { 27 | "default": "", 28 | "eval": "True if value else None", 29 | "format": " flagged", 30 | "type": "boolean" 31 | }, 32 | "id": { 33 | "default": "", 34 | "format": "$value", 35 | "type": "string" 36 | }, 37 | "link": { 38 | "default": "", 39 | "format": "$value", 40 | "type": "string" 41 | }, 42 | "name": { 43 | "default": "", 44 | "format": "$value", 45 | "type": "string" 46 | }, 47 | "note": { 48 | "default": "", 49 | "format": "", 50 | "type": "note" 51 | }, 52 | "project": { 53 | "default": "", 54 | "eval": "value.name", 55 | "format": " project:$value", 56 | "type": "string" 57 | }, 58 | "status": { 59 | "default": "", 60 | "format": " status:$value", 61 | "type": "string" 62 | }, 63 | "type": { 64 | "default": "", 65 | "format": "$value", 66 | "type": "string" 67 | } 68 | }, 69 | "dateFormat": "%Y-%m-%d", 70 | "depth": 0, 71 | "indent": 0, 72 | "indentString": " ", 73 | "nodes": { 74 | "ContextStart": "${indent}Context: $name$status", 75 | "FolderStart": "${indent}Folder: $name", 76 | "NoteLine": "$indent\tNote: $note_line", 77 | "ProjectStart": "${indent}Project: $name$flagged$status$date_to_start$date_due$date_completed$context$project", 78 | "TaskGroupStart": "${indent}TaskGroup: $name$flagged$date_to_start$date_due$date_completed$context$project", 79 | "TaskStart": "${indent}Task: $name$flagged$date_to_start$date_due$date_completed$context$project" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /templates/todotxt.json: -------------------------------------------------------------------------------- 1 | { 2 | "#01": "todotxt", 3 | "#02": "A template that exports to todo.txt syntax", 4 | "attributes": { 5 | "context": { 6 | "default": "", 7 | "eval": "value.name if value.name != 'No Context' else None", 8 | "format": " @$value", 9 | "type": "string" 10 | }, 11 | "date_added": { 12 | "default": "", 13 | "format": "$value ", 14 | "type": "date" 15 | }, 16 | "date_completed": { 17 | "default": "", 18 | "format": "x $value ", 19 | "type": "date" 20 | }, 21 | "date_due": { 22 | "default": "", 23 | "format": " due:$value", 24 | "type": "date" 25 | }, 26 | "date_to_start": { 27 | "default": "", 28 | "format": " t:$value", 29 | "type": "date" 30 | }, 31 | "flagged": { 32 | "default": "", 33 | "eval": "True if value else None", 34 | "format": " @flagged", 35 | "type": "string" 36 | }, 37 | "id": { 38 | "default": "", 39 | "format": "$value", 40 | "type": "string" 41 | }, 42 | "link": { 43 | "default": "", 44 | "format": " $value", 45 | "type": "string" 46 | }, 47 | "name": { 48 | "default": "", 49 | "format": "$value", 50 | "type": "string" 51 | }, 52 | "note": { 53 | "default": "", 54 | "format": "", 55 | "type": "note" 56 | }, 57 | "project": { 58 | "default": "", 59 | "eval": "value.name", 60 | "format": " +$value", 61 | "type": "string" 62 | }, 63 | "status": { 64 | "default": "", 65 | "format": " status:$value", 66 | "type": "string" 67 | }, 68 | "type": { 69 | "default": "", 70 | "format": " type:$value", 71 | "type": "string" 72 | } 73 | }, 74 | "dateFormat": "%Y-%m-%d", 75 | "depth": 0, 76 | "indent": 0, 77 | "indentString": "\t", 78 | "nodes": { 79 | "TaskGroupStart": "$date_completed$date_added$name$project$context$flagged$date_due$date_to_start", 80 | "TaskStart": "$date_completed$date_added$name$project$context$flagged$date_due$date_to_start" 81 | } 82 | } 83 | --------------------------------------------------------------------------------