├── Support ├── tests │ ├── GTD.gtdlog │ ├── test_example.gtd │ ├── example.gtd │ ├── test_utils.rb │ └── test_gtd.rb ├── stylesheet.css ├── css │ ├── arrow.png │ ├── arrow_down.png │ └── sidebar.css ├── js │ ├── sorttable.js │ └── sidebar.js ├── bin │ ├── gtdalt_ical_synchronization.scpt │ ├── mark_completed.rb │ ├── get_lists.rb │ ├── fill_remind_file.rb │ ├── KinklessToGTDConverter.rb │ ├── process_inbox.rb │ └── gtdalt_ical_synchronization.applescript ├── nibs │ └── datePicker.nib │ │ ├── keyedobjects.nib │ │ ├── info.nib │ │ └── classes.nib ├── lib │ ├── GTDiCalendar.rb │ ├── GTDInfoRoutines.rb │ ├── GTDUtils.rb │ └── GTD.rb ├── INBOX.txt └── README.txt ├── Snippets ├── Note With Link.tmSnippet └── new project.tmSnippet ├── Preferences ├── Folding.tmPreferences ├── Preferences.tmPreferences └── Symbol list.tmPreferences ├── Templates └── Sample.tmTemplate │ ├── info.plist │ └── sample.gtd ├── Commands ├── Convert From Kinkless.tmCommand ├── Fill Remind File.tmCommand ├── Open Links in Browser.tmCommand ├── Process Inbox.tmCommand ├── Show Help.tmCommand ├── Action With Same Context.tmCommand ├── clean completed tasks.tmCommand ├── Synchronize with iCal.tmCommand ├── toggle completion.tmCommand ├── Mark Project as Complete.tmCommand ├── New.tmCommand ├── Note for Action (tooltip).tmCommand ├── Surround in Project.tmCommand ├── Locate Note's Action.tmCommand ├── choose context.tmCommand ├── Go to Project.tmCommand ├── actions for context (Text).tmCommand ├── Project Statistics.tmCommand ├── Find Project.tmCommand ├── Add Note.tmCommand ├── Set Date.tmCommand ├── All Actions.tmCommand └── Review.tmCommand ├── DragCommands └── LinkToDraggedFile.tmDragCommand ├── README.mdown ├── Macros └── New (on next line).tmMacro ├── info.plist └── Syntaxes └── GTDalt.tmLanguage /Support/tests/GTD.gtdlog: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Support/stylesheet.css: -------------------------------------------------------------------------------- 1 | table { 2 | color: red; 3 | background-color: yellow; 4 | } -------------------------------------------------------------------------------- /Support/css/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textmate/gtdalt.tmbundle/master/Support/css/arrow.png -------------------------------------------------------------------------------- /Support/js/sorttable.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textmate/gtdalt.tmbundle/master/Support/js/sorttable.js -------------------------------------------------------------------------------- /Support/css/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textmate/gtdalt.tmbundle/master/Support/css/arrow_down.png -------------------------------------------------------------------------------- /Support/bin/gtdalt_ical_synchronization.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textmate/gtdalt.tmbundle/master/Support/bin/gtdalt_ical_synchronization.scpt -------------------------------------------------------------------------------- /Support/nibs/datePicker.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textmate/gtdalt.tmbundle/master/Support/nibs/datePicker.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Support/tests/test_example.gtd: -------------------------------------------------------------------------------- 1 | project World domination 2 | #completed:[2006-06-20] @office Assemble email address of world leaders 3 | @errand Create giant laser beam [1] due:[2006-06-04] 4 | @email-task Threaten to destroy Barbados 5 | project A subproject 6 | @testing An action 7 | end 8 | @work Take over world 9 | end 10 | @email Hello there 11 | [1] A note here 12 | -------------------------------------------------------------------------------- /Support/nibs/datePicker.nib/info.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBDocumentLocation 6 | 69 14 356 240 0 0 1024 746 7 | IBFramework Version 8 | 439.0 9 | IBOpenObjects 10 | 11 | 5 12 | 13 | IBSystem Version 14 | 8L127 15 | 16 | 17 | -------------------------------------------------------------------------------- /Support/nibs/datePicker.nib/classes.nib: -------------------------------------------------------------------------------- 1 | { 2 | IBClasses = ( 3 | {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 4 | {CLASS = NSDatePicker; LANGUAGE = ObjC; SUPERCLASS = NSControl; }, 5 | {ACTIONS = {performButtonClick = id; }; CLASS = NSObject; LANGUAGE = ObjC; }, 6 | { 7 | ACTIONS = {performButtonClick = id; }; 8 | CLASS = ObjectWithButton; 9 | LANGUAGE = ObjC; 10 | SUPERCLASS = NSObject; 11 | } 12 | ); 13 | IBVersion = 1; 14 | } -------------------------------------------------------------------------------- /Snippets/Note With Link.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:note comment }<${2:http://$3}>$0 7 | keyEquivalent 8 | ^n 9 | name 10 | New Link 11 | scope 12 | text.gtdalt meta.note 13 | uuid 14 | 74B7D57C-F538-41BE-A228-1F077EC8F3B4 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/new project.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | project ${1:project_name} 7 | $0 8 | end 9 | keyEquivalent 10 | ! 11 | name 12 | New 13 | scope 14 | text.gtdalt - meta.action - meta.line.project 15 | uuid 16 | 98227572-90C0-43C5-A18B-EA2591674553 17 | 18 | 19 | -------------------------------------------------------------------------------- /Preferences/Folding.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Folding 7 | scope 8 | text.gtdalt 9 | settings 10 | 11 | foldingStartMarker 12 | ^\s*project 13 | foldingStopMarker 14 | ^\s*end\s*$ 15 | 16 | uuid 17 | 87C3BBE9-D08A-4FA7-806F-D0964CE285BD 18 | 19 | 20 | -------------------------------------------------------------------------------- /Preferences/Preferences.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Preferences 7 | scope 8 | text.gtdalt 9 | settings 10 | 11 | decreaseIndentPattern 12 | ^\s*end\s*$ 13 | increaseIndentPattern 14 | ^\s*project\s+.*$ 15 | 16 | uuid 17 | B8601C35-4EBE-4672-8A09-919A6730DC37 18 | 19 | 20 | -------------------------------------------------------------------------------- /Preferences/Symbol list.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Symbol list 7 | scope 8 | meta.line.project.begin.gtdalt 9 | settings 10 | 11 | showInSymbolList 12 | 1 13 | symbolTransformation 14 | s/project//; 15 | 16 | uuid 17 | E6CF82DF-159E-4B88-B903-4B3BBCDD3233 18 | 19 | 20 | -------------------------------------------------------------------------------- /Support/bin/mark_completed.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 2 | # 3 | require 'Date' 4 | name, file, line = ARGV 5 | f = File.open(file) 6 | data = f.readlines 7 | f.close 8 | line = line.to_i 9 | raise "Did not find a matching action with name #{name} in file: #{file} and line: #{line}." unless data[line-1].index(name) 10 | l = data[line-1] 11 | # This code will actually un-complete a completed action 12 | if l =~ /^(#completed:)(\[\d{4}-\d{2}-\d{2}\])(.*\n?)/ then 13 | data[line-1] = $3 14 | elsif l =~ /^\s*@/ 15 | data[line-1] = "#completed:[#{Date.today}]#{l}" 16 | else 17 | raise "Not an action line." 18 | end 19 | f = File.open(file, 'w') 20 | f.write data 21 | f.close 22 | -------------------------------------------------------------------------------- /Templates/Sample.tmTemplate/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | command 6 | if [[ ! -f "$TM_NEW_FILE" ]]; then 7 | TM_YEAR=`date +%Y` \ 8 | TM_DATE=`date +%Y-%m-%d` \ 9 | perl -pe 's/\$\{([^}]*)\}/$ENV{$1}/g' \ 10 | < sample.gtd > "$TM_NEW_FILE" 11 | fi 12 | extension 13 | gtd 14 | name 15 | Sample 16 | uuid 17 | D876C2A8-1E6F-4793-B990-8478DF7552DD 18 | 19 | 20 | -------------------------------------------------------------------------------- /Commands/Convert From Kinkless.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | "$TM_BUNDLE_SUPPORT/bin/KinklessToGTDConverter.rb" 9 | 10 | fallbackInput 11 | line 12 | input 13 | document 14 | name 15 | Convert From Kinkless 16 | output 17 | openAsNewDocument 18 | uuid 19 | 40ABCA1E-A154-47C7-8EBC-D22DC078D295 20 | 21 | 22 | -------------------------------------------------------------------------------- /Support/tests/example.gtd: -------------------------------------------------------------------------------- 1 | project World domination 2 | #completed:[2006-06-20] @office Assemble email address of world leaders 3 | @errand Create giant laser beam 4 | @email-task Threaten to destroy Barbados 5 | @work Take over world 6 | end 7 | @email Hello there 8 | project testing project 9 | project Some subproject 10 | @homework first action due:[2006-07-20] 11 | @email another action due:[2006-06-30] 12 | @email a third action 13 | project a further subproject 14 | 15 | end 16 | end 17 | 18 | #completed:[2006-06-25] @home Hurray [1] due:[2006-07-04] 19 | project another one 20 | @home second action [2] due:[2006-06-04] 21 | @errand a new one [3] 22 | @email another third action 23 | end 24 | end 25 | [1] A note here 26 | [2] Another note 27 | [3] A third note -------------------------------------------------------------------------------- /Commands/Fill Remind File.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | "$TM_BUNDLE_SUPPORT/bin/fill_remind_file.rb" 9 | echo "Your remind files are now filled!" 10 | fallbackInput 11 | document 12 | input 13 | none 14 | keyEquivalent 15 | ^~@+ 16 | name 17 | Fill Remind Files 18 | output 19 | showAsTooltip 20 | scope 21 | text.gtdalt 22 | uuid 23 | 7A8587A6-743D-4449-A0E2-BFD9A97893BC 24 | 25 | 26 | -------------------------------------------------------------------------------- /Commands/Open Links in Browser.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | links = STDIN.read.scan(/<([^>]*)>/) 10 | links.each do |link| 11 | `open "#{link}"` 12 | end 13 | fallbackInput 14 | line 15 | input 16 | selection 17 | keyEquivalent 18 | ^l 19 | name 20 | Open Links in Line 21 | output 22 | discard 23 | scope 24 | text.gtdalt 25 | uuid 26 | 65E842B0-D1A6-40E7-91C0-3215930AC0EA 27 | 28 | 29 | -------------------------------------------------------------------------------- /Commands/Process Inbox.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #### NOT VERY MUCH TESTED #### 9 | #### USE WITH CARE #### 10 | "$TM_BUNDLE_SUPPORT/bin/process_inbox.rb" 11 | 12 | fallbackInput 13 | word 14 | input 15 | none 16 | keyEquivalent 17 | ^~@= 18 | name 19 | Process Actions From Inbox File 20 | output 21 | openAsNewDocument 22 | scope 23 | text.gtdalt 24 | uuid 25 | FCAAFD28-1765-4618-851D-20AA52499F77 26 | 27 | 28 | -------------------------------------------------------------------------------- /Commands/Show Help.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | bundleUUID 8 | 30FA6EB3-2180-46F8-B2DD-EDA6B443CBB6 9 | command 10 | . "$TM_SUPPORT_PATH/lib/webpreview.sh" 11 | html_header "GTDalt Help" "Getting Things Done (alternative)" 12 | Markdown.pl "$TM_BUNDLE_SUPPORT/README.txt" 13 | html_footer 14 | fallbackInput 15 | word 16 | input 17 | none 18 | name 19 | Help 20 | output 21 | showAsHTML 22 | scope 23 | text.gtdalt 24 | uuid 25 | 8E083C6C-9C1A-4AD9-AA39-3BCABF925B73 26 | 27 | 28 | -------------------------------------------------------------------------------- /Commands/Action With Same Context.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | line = ENV['TM_CURRENT_LINE'] 10 | require ENV['TM_SUPPORT_PATH'] + "/lib/escape.rb" 11 | match = line.match('(\s*@\S+)') 12 | print "#{e_sn line}\n#{e_sn match[1]} $0" 13 | 14 | fallbackInput 15 | line 16 | input 17 | selection 18 | keyEquivalent 19 | $ 20 | name 21 | New With Same Context 22 | output 23 | insertAsSnippet 24 | scope 25 | text.gtdalt meta.action 26 | uuid 27 | 65FAC309-42DC-4C48-AC0D-BC7AA5864C71 28 | 29 | 30 | -------------------------------------------------------------------------------- /DragCommands/LinkToDraggedFile.tmDragCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_SUPPORT_PATH'] + '/lib/escape' 10 | p = File.expand_path(ENV['TM_DROPPED_FILE']) 11 | print "<file:\/\/#{e_url p}>" 12 | draggedFileExtensions 13 | 14 | * 15 | txt 16 | gtd 17 | markdown 18 | pdf 19 | 20 | input 21 | selection 22 | name 23 | Link to Dragged File 24 | output 25 | insertAsSnippet 26 | scope 27 | text.gtdalt 28 | uuid 29 | FFECDABA-E216-4182-B0CB-7031D279EEDE 30 | 31 | 32 | -------------------------------------------------------------------------------- /Commands/clean completed tasks.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT']+"/lib/GTD.rb" 10 | include GTD 11 | file = ENV['TM_FILEPATH'] 12 | obj = GTDFile.new(file) 13 | obj.cleanup_projects 14 | log = File.open(File.join(File.dirname(file),"GTD.gtdlog"),"a") 15 | log.write MyLogger.dump + "\n" 16 | log.close 17 | puts obj.dump_object 18 | input 19 | none 20 | keyEquivalent 21 | * 22 | name 23 | clean completed tasks 24 | output 25 | replaceDocument 26 | scope 27 | text.gtdalt 28 | uuid 29 | 7118880F-C5BA-4359-87A1-480FB61E416B 30 | 31 | 32 | -------------------------------------------------------------------------------- /Commands/Synchronize with iCal.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_SUPPORT_PATH']+"/lib/progress.rb" 10 | TextMate.call_with_progress(:title=> "Synchronizing with iCal" , :message =>"This may take a while...") { 11 | #`osascript "$TM_BUNDLE_SUPPORT/bin/gtdalt_ical_synchronization.applescript"` 12 | 13 | `osascript "$TM_BUNDLE_SUPPORT/bin/gtdalt_ical_synchronization.scpt"` 14 | } 15 | input 16 | none 17 | keyEquivalent 18 | ^~@i 19 | name 20 | Synchronize with iCal 21 | output 22 | discard 23 | scope 24 | text.gtdalt 25 | uuid 26 | EB99AA83-E73B-42F6-88E1-6F8AB29F9BFD 27 | 28 | 29 | -------------------------------------------------------------------------------- /Commands/toggle completion.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require 'date' 10 | lines = STDIN.readlines 11 | for line in lines 12 | if line =~ /^(#completed:)(\[\d{4}-\d{2}-\d{2}\])(.*)/ then 13 | print $3 14 | elsif line =~ /^\s*@/ 15 | print "#completed:[#{Date.today}]#{line}" 16 | else 17 | print line 18 | end 19 | end 20 | 21 | fallbackInput 22 | line 23 | input 24 | selection 25 | keyEquivalent 26 | @/ 27 | name 28 | Mark as Complete 29 | output 30 | replaceSelectedText 31 | scope 32 | text.gtdalt 33 | uuid 34 | A9C04B17-6A04-4D4D-81ED-2F4EFCD61FA3 35 | 36 | 37 | -------------------------------------------------------------------------------- /Commands/Mark Project as Complete.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTD.rb' 10 | include GTD 11 | file = ENV['TM_FILEPATH'] 12 | obj = GTDFile.new(file) 13 | obj.cleanup_projects 14 | log = File.open(File.join(File.dirname(file),"GTD.gtdlog"),"a") 15 | log.write MyLogger.dump + "\n" 16 | log.close 17 | puts obj.dump_object 18 | fallbackInput 19 | document 20 | input 21 | none 22 | keyEquivalent 23 | ~@/ 24 | name 25 | Archive Completed 26 | output 27 | replaceDocument 28 | scope 29 | text.gtdalt 30 | uuid 31 | 2803EE1A-6878-47C7-BB7F-3C1B69315209 32 | 33 | 34 | -------------------------------------------------------------------------------- /Commands/New.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTDUtils.rb' 10 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb' 11 | contextList = GTDContexts.contexts 12 | choice = TextMate::UI.menu(contextList) 13 | if choice then 14 | print "@#{contextList[choice]} " 15 | else 16 | require ENV['TM_SUPPORT_PATH'] + '/lib/exit_codes.rb' 17 | TextMate.exit_discard 18 | end 19 | 20 | input 21 | none 22 | keyEquivalent 23 | @ 24 | name 25 | New 26 | output 27 | afterSelectedText 28 | scope 29 | text.gtdalt - meta.action - meta.line.project - meta.note 30 | uuid 31 | BBA04431-8960-4017-A684-7FBACB0F7FFF 32 | 33 | 34 | -------------------------------------------------------------------------------- /Commands/Note for Action (tooltip).tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | lines = STDIN.readlines 10 | row = ENV['TM_LINE_NUMBER'].to_i 11 | currentLine = lines[row-1] 12 | found = currentLine[/\[\d+\]/] 13 | if found then 14 | note = lines.find{ |line| line[/^\[\d+\]/] == found } 15 | note.slice!(/^\[\d+\]\s+/) 16 | print note 17 | else 18 | print "This action does not have a note." 19 | end 20 | fallbackInput 21 | document 22 | input 23 | document 24 | keyEquivalent 25 | ^? 26 | name 27 | Show Note as Tool Tip 28 | output 29 | showAsTooltip 30 | scope 31 | text.gtdalt meta.action 32 | uuid 33 | 3BF349AD-3452-4CA4-AAA7-8836565E3A5F 34 | 35 | 36 | -------------------------------------------------------------------------------- /Commands/Surround in Project.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | lines = STDIN.read.gsub(/\$/,"\\$").split("\n") 10 | currentTab = lines[0].scan(/^\s*/)[0] 11 | unless ENV['TM_SOFT_TABS'] == "YES" then 12 | extraTab = "\t" 13 | else 14 | extraTab = (" " * ENV['TM_TAB_SIZE'].to_i) 15 | end 16 | print lines.map{|i| extraTab + i}.unshift(currentTab + "project $0").push(currentTab + "end").join("\n") + "\n" 17 | fallbackInput 18 | line 19 | input 20 | selection 21 | keyEquivalent 22 | ^W 23 | name 24 | With Selection 25 | output 26 | insertAsSnippet 27 | scope 28 | text.gtdalt 29 | uuid 30 | 253F6CF9-0E14-48C8-8468-005018E4E301 31 | 32 | 33 | -------------------------------------------------------------------------------- /Commands/Locate Note's Action.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | text = STDIN.read 10 | lines = text.split("\n") 11 | row = ENV['TM_LINE_NUMBER'].to_i 12 | m = lines[row-1].scan(/^\[\d+\]/)[0] 13 | txt = text.slice(0..text.index(m)) 14 | lines = txt.split("\n") 15 | line = lines.length 16 | last_line = lines.last 17 | column = last_line.scan(/^\s*@[^\s]+\s+/)[0].length + 1 18 | `open "txmt://open?line=#{line}&column=#{column}"` 19 | 20 | fallbackInput 21 | document 22 | input 23 | document 24 | keyEquivalent 25 | ^{ 26 | name 27 | Go to Action 28 | output 29 | discard 30 | scope 31 | text.gtdalt meta.note 32 | uuid 33 | 14CE2968-B4F4-4AB5-8A26-5F0EC156E1D0 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can install this bundle in TextMate by opening the preferences and going to the bundles tab. After installation it will be automatically updated for you. 4 | 5 | # General 6 | 7 | * [Bundle Styleguide](http://kb.textmate.org/bundle_styleguide) — _before you make changes_ 8 | * [Commit Styleguide](http://kb.textmate.org/commit_styleguide) — _before you send a pull request_ 9 | * [Writing Bug Reports](http://kb.textmate.org/writing_bug_reports) — _before you report an issue_ 10 | 11 | # License 12 | 13 | If not otherwise specified (see below), files in this repository fall under the following license: 14 | 15 | Permission to copy, use, modify, sell and distribute this 16 | software is granted. This software is provided "as is" without 17 | express or implied warranty, and with no claim as to its 18 | suitability for any purpose. 19 | 20 | An exception is made for files in readable text which contain their own license information, or files where an accompanying file exists (in the same directory) with a “-license” suffix added to the base-name name of the original file, and an extension of txt, html, or similar. For example “tidy” is accompanied by “tidy-license.txt”. -------------------------------------------------------------------------------- /Commands/choose context.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTDUtils.rb' 10 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb' 11 | line = STDIN.read 12 | contextList = GTDContexts.contexts 13 | choice = TextMate::UI.menu(contextList) 14 | if choice then 15 | print line.sub(/@(\S+)($|\s+)/) {|text,space| "@" + e_sn(contextList[choice]) + ((space.nil? or space == "") ? " " : space)+"$0"} 16 | else 17 | print e_sn(line) 18 | end 19 | 20 | fallbackInput 21 | line 22 | input 23 | selection 24 | keyEquivalent 25 | @ 26 | name 27 | Change Context 28 | output 29 | insertAsSnippet 30 | scope 31 | text.gtdalt meta.action 32 | uuid 33 | 2B282247-5930-462F-8D6E-54ADF228EC49 34 | 35 | 36 | -------------------------------------------------------------------------------- /Support/tests/test_utils.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | $:.unshift "../lib" 3 | ENV['TM_GTD_CONTEXT'] = "" 4 | ENV['TM_GTD_CONTEXTS'] = "hello there" 5 | require "GTDUtils.rb" 6 | 7 | class TestUtils < Test::Unit::TestCase 8 | def setup 9 | @a = [6,2,5,3,4] 10 | end 11 | def test_array_next 12 | assert_equal(6, @a.next(4)) 13 | assert_equal(2, @a.next(6)) 14 | assert_equal(4, @a.next(3)) 15 | end 16 | def test_array_previous 17 | assert_equal(6, @a.previous(2)) 18 | assert_equal(4, @a.previous(6)) 19 | end 20 | def test_contexts 21 | puts GTDContexts.get_env_contexts 22 | assert_equal(2, GTDContexts.get_env_contexts.length) 23 | assert_equal(2, GTDContexts.contexts.length) 24 | GTDContexts.contexts=["hi","you", "there"] 25 | assert_equal(["hi","there", "you"], GTDContexts.contexts) 26 | GTDContexts.contexts += ["you", "there", "foo"] 27 | assert_equal(["foo", "hi","there", "you"], GTDContexts.contexts) 28 | GTDContexts.contexts << "bar" 29 | assert_equal(5, GTDContexts.contexts.length) 30 | GTDContexts.contexts = nil 31 | assert_equal(2, GTDContexts.contexts.length) 32 | ENV['TM_GTD_CONTEXTS'] = nil 33 | GTDContexts.contexts = nil 34 | assert_equal(6, GTDContexts.contexts.length) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /Support/bin/get_lists.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 2 | # ENV['TM_GTD_CONTEXT'] = "email home office online writing errand reading someday programming" 3 | # ENV['TM_GTD_DIRECTORY'] = "/Users/haris/Documents/MyGTD" 4 | # ENV['TM_BUNDLE_SUPPORT'] = '/Users/haris/Library/Application Support/TextMate/Bundles/GTDAlt.tmbundle/Support' 5 | require File.dirname(__FILE__) + '/../lib/GTD.rb' 6 | include GTD 7 | GTD.process_directory 8 | all_actions = GTD.actions 9 | contexts = GTDContexts.contexts 10 | contxts = [] 11 | def esc(str) 12 | str.gsub('"','\\"') 13 | end 14 | for context in contexts 15 | # puts context 16 | ar = "{context:\"#{context}\",actions:{" 17 | actions = all_actions.find_all{ |a| a.context == context} 18 | acts = [] 19 | for act in actions 20 | it = "{action:\"#{esc(act.name)}\"" 21 | unless act.due.nil? then 22 | it << ",#{act.due_type || 'due'}date:\"#{act.due}\"" 23 | end 24 | unless act.note.nil? or act.note == "" then 25 | it << ",nte:\"#{esc(act.note)}\"" 26 | end 27 | it << ",completed:\"#{act.completed? ? 'yes' : 'no'}\"" 28 | it << ",link:\"#{act.txmt}\",file:\"#{act.file}\",line:\"#{act.line}\"" 29 | it << "}" 30 | acts << it 31 | end 32 | ar << acts.join(",") << "}}" 33 | contxts << ar 34 | end 35 | puts "{" + contxts.join(",") + "}" -------------------------------------------------------------------------------- /Commands/Go to Project.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTD.rb' 10 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb' 11 | include GTD 12 | files = GTD.process_directory 13 | projects = files.map{|i| i.projects}.flatten 14 | projectNames = projects.map{|i| i.name+" : "+i.root.name} 15 | require 'pp' 16 | choice = TextMate::UI.menu(projectNames) 17 | if choice then 18 | projectName = projectNames[choice] 19 | project = projects.find{|i| i.name+" : "+i.root.name == projectName} 20 | puts project.txmt 21 | `open "#{project.txmt}"` 22 | end 23 | fallbackInput 24 | word 25 | input 26 | none 27 | keyEquivalent 28 | ^G 29 | name 30 | Go to Project 31 | output 32 | discard 33 | scope 34 | text.gtdalt 35 | uuid 36 | A54CF0C9-ECBF-4A20-B345-3EA167ABD6A0 37 | 38 | 39 | -------------------------------------------------------------------------------- /Support/lib/GTDiCalendar.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__),"GTD.rb") 2 | require 'date' 3 | begin 4 | require 'rubygems' 5 | require_gem 'icalendar', ">= 0.96" 6 | rescue LoadError 7 | require 'icalendar' 8 | end 9 | class CalendarWriter 10 | include Icalendar 11 | def initialize 12 | @cal = Calendar.new 13 | # @items = [] 14 | end 15 | def add_action(action) 16 | case action.due_type 17 | when "at" 18 | ev = Event.new 19 | ev.timestamp = DateTime.now 20 | ev.start = Date.parse(action.due) 21 | else 22 | ev = Todo.new 23 | ev.due = Date.parse(action.due) if action.due 24 | end 25 | ev.summary = action.name 26 | @cal.add ev 27 | # ev.comment = "From project: #{action.parent.name} in file #{action.root.name}.\n#{action.note || ""}." 28 | end 29 | def +(itemsArray) 30 | for item in itemsArray do 31 | case item 32 | when Component 33 | puts "here" 34 | @cal.add item 35 | when GTD::Action 36 | add_action item 37 | end 38 | end 39 | self 40 | end 41 | def dump 42 | @cal.to_ical 43 | end 44 | end 45 | module GTD 46 | def GTD.calendar_for_context(context) 47 | acts = GTD.actions_for_context(context) 48 | cw = CalendarWriter.new 49 | cw += acts 50 | puts cw.dump 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /Commands/actions for context (Text).tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | $:<< ENV['TM_BUNDLE_SUPPORT'] << ENV['TM_SUPPORT_PATH'] 10 | require "lib/GTD.rb" 11 | require "lib/ui" 12 | include GTD 13 | word = STDIN.read 14 | case word 15 | when /^@?(\w+)$/ 16 | w = $1 17 | when /\s+@(\w+)\s*.*/ 18 | w = $1 19 | end 20 | if GTD.get_contexts.include?(word) then 21 | context = word 22 | else 23 | context = TextMate::UI.request_item(:title => 'Listing actions for context', :prompt => 'Choose the new context', :items => GTD.contexts) 24 | exit unless context 25 | end 26 | actions = GTD.actions_for_context(context).map{|r| [r[0],r[1].to_s,r[2]]} 27 | puts prettify(actions) 28 | 29 | fallbackInput 30 | word 31 | input 32 | selection 33 | name 34 | Actions for Context (Text) 35 | output 36 | openAsNewDocument 37 | scope 38 | text.gtdalt 39 | uuid 40 | C78ECFD8-DABE-4E2C-B628-0DDF2A3A3B21 41 | 42 | 43 | -------------------------------------------------------------------------------- /Support/lib/GTDInfoRoutines.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__),"GTD.rb") 2 | def html_actions_for_context(context) 3 | actions = GTD.actions_for_context(context) 4 | pr = Printer.new 5 | b = < 7 | 8 | 9 | 10 | 11 | HTML 12 | e = < 14 | 15 | HTML 16 | pr.raw b 17 | pr.table do 18 | pr.title("Actions for context: #{context}") 19 | pr.headers(["Action name","Project","Due_by","Completed"]) 20 | actions.each do |a| 21 | proj = a.parent.link 22 | due = case a.due 23 | when "",nil 24 | "" 25 | when DateLate 26 | "#{a.due}" 27 | else 28 | a.due 29 | end 30 | pr.row([a.link,proj,due, a.mark_completed_link]) 31 | end 32 | pr.raw e 33 | return pr.to_html 34 | #.to_html 35 | end 36 | # ar << "
" 37 | # ar << "

Actions for context: #{context}

" 38 | end 39 | 40 | def prettify(array) 41 | # pp array 42 | return "" if array.nil? || array.empty? 43 | ar = [] 44 | columns = array[0].zip(*array[1..-1]) 45 | # pp columns 46 | maxs = columns.map{|c| c.map{|i| i.length}.max} 47 | maxs[2] = 10 48 | # puts maxs 49 | pattern = "| "+maxs.map{|m| "%-#{m}s"}.join(" | ")+" |" 50 | slider = "+-" + maxs.map{|m| "-" * m}.join("-+-")+"-+" 51 | # print pattern 52 | ar << slider 53 | ar << sprintf(pattern,"Action","Project","Due by") 54 | ar << slider 55 | array.each {|i| ar << sprintf(pattern,*i)} 56 | ar << slider 57 | return ar.join("\n") 58 | end 59 | 60 | -------------------------------------------------------------------------------- /Commands/Project Statistics.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | $:<< ENV['TM_BUNDLE_SUPPORT'] << ENV['TM_SUPPORT_PATH'] 10 | require "lib/GTD.rb" 11 | require "lib/ui" 12 | include GTD 13 | GTD.process_directory 14 | projects = GTD.projects.find_all{|proj| !proj.completed?} 15 | pr = Printer.new 16 | b = <<HTML 17 | <html> 18 | <head> 19 | <link rel="stylesheet" href="file://#{ENV['TM_SUPPORT_PATH']}/css/default.css" type="text/css" media="screen" title="no title" charset="utf-8" /> 20 | </head> 21 | <body> 22 | HTML 23 | e = <<HTML 24 | </body> 25 | </html> 26 | HTML 27 | pr.raw b 28 | pr.table do 29 | pr.title "List of all incomplete Projects" 30 | pr.headers([ "Project", "Items", "Next Action"]) 31 | projects.each do |project| 32 | pr.row([project.link, 33 | project.flatten.reject{|i| i.completed? }.length-1, 34 | project.next_action.link]) 35 | end 36 | end 37 | pr.raw e 38 | puts pr.to_html 39 | fallbackInput 40 | line 41 | input 42 | none 43 | keyEquivalent 44 | ^~@p 45 | name 46 | Project Statistics 47 | output 48 | showAsHTML 49 | scope 50 | text.gtdalt 51 | uuid 52 | BF7C70D8-A0D9-44A3-9CB4-8D99372E68B6 53 | 54 | 55 | -------------------------------------------------------------------------------- /Macros/New (on next line).tmMacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | commands 6 | 7 | 8 | command 9 | moveToEndOfParagraph: 10 | 11 | 12 | command 13 | insertNewline: 14 | 15 | 16 | argument 17 | 18 | beforeRunningCommand 19 | nop 20 | command 21 | #!/usr/bin/env ruby18 22 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTDUtils.rb' 23 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb' 24 | contextList = GTDContexts.contexts 25 | choice = TextMate::UI.menu(contextList) 26 | if choice then 27 | print "@#{contextList[choice]} " 28 | else 29 | require ENV['TM_SUPPORT_PATH'] + '/lib/exit_codes.rb' 30 | TextMate.exit_discard 31 | end 32 | 33 | input 34 | none 35 | keyEquivalent 36 | @ 37 | name 38 | New 39 | output 40 | afterSelectedText 41 | scope 42 | text.gtdalt - meta.action - meta.line.project - meta.note 43 | uuid 44 | BBA04431-8960-4017-A684-7FBACB0F7FFF 45 | 46 | command 47 | executeCommandWithOptions: 48 | 49 | 50 | keyEquivalent 51 |  52 | name 53 | New (on next line) 54 | scope 55 | text.gtdalt 56 | uuid 57 | 4EF0270A-A1D7-4E98-8123-6E6F1305B7E2 58 | 59 | 60 | -------------------------------------------------------------------------------- /Commands/Find Project.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTD.rb' 10 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui.rb' 11 | include GTD 12 | files = GTD.process_directory 13 | projects = files.map{|i| i.projects}.flatten 14 | search_string = TextMate::UI.request_string(:title => "Finding projects", :prompt => "Abbreviated words to seach for:") 15 | exit unless search_string && !(search_string =~ /^\s*$/) 16 | search_terms = search_string.downcase.split(" ") 17 | for term in search_terms do 18 | projects = projects.find_all { |proj| proj.name.downcase.index(term) != nil } 19 | end 20 | case projects.length 21 | when 0 22 | project = nil 23 | when 1 24 | project = projects[0] 25 | else 26 | project = TextMate::UI.request_item(:title => "Finding projects",:prompt => "Too many possible matches found! Please select one:",:items => projects.map{|i| i.name+" : "+i.root.name} ) 27 | project = projects.find{|proj| proj.name+" : "+proj.root.name == project} if project 28 | end 29 | if project then 30 | # project.root.update! 31 | `open "#{project.txmt}"` 32 | end 33 | 34 | fallbackInput 35 | word 36 | input 37 | none 38 | keyEquivalent 39 | ^F 40 | name 41 | Find 42 | output 43 | discard 44 | scope 45 | text.gtdalt 46 | uuid 47 | 255E029C-7B95-49F2-B7AC-0FAB4F7FBA58 48 | 49 | 50 | -------------------------------------------------------------------------------- /Commands/Add Note.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_SUPPORT_PATH'] + "/lib/exit_codes.rb" 10 | require ENV['TM_SUPPORT_PATH'] + "/lib/escape.rb" 11 | text = STDIN.read 12 | lines = text.split("\n") 13 | row = ENV['TM_LINE_NUMBER'].to_i 14 | col = ENV['TM_LINE_INDEX'].to_i 15 | currentLine = lines[row-1] 16 | found = currentLine.scan(/\[\d+\]/)[0] 17 | if found then 18 | # No need to change the file at all in this case. Just locate the proper 19 | # line and use the txmt scheme to take us there. 20 | txt = text.slice(0..text.rindex(found)+found.length) 21 | lines = txt.split("\n") 22 | line = lines.length 23 | column = lines.last.length + 1 24 | `open "txmt://open?line=#{line}&column=#{column}"` 25 | TextMate.exit_discard 26 | # print text.insert(text.rindex(found)+found.length + 1,"$0") 27 | else 28 | m = text.scan(/\[(\d+)\]/).map{|i| i[0].to_i}.max || 0 29 | before = [] 30 | (row-1).times do before << lines.shift end 31 | # cl = lines.shift 32 | if lines[0] =~ /(?:due|at|from):/ then 33 | before << lines[0].slice!(/^.*(?=\s(?:due|at|from):)/) 34 | else 35 | before << lines.shift 36 | lines.unshift("") 37 | end 38 | print e_sn(before.join("\n") + " [#{m+1}]" + lines.join("\n")) + "\n[#{m+1}] $0" 39 | end 40 | fallbackInput 41 | document 42 | input 43 | selection 44 | keyEquivalent 45 | ^{ 46 | name 47 | Go to Note 48 | output 49 | insertAsSnippet 50 | scope 51 | text.gtdalt - meta.note 52 | uuid 53 | BF20459A-D83B-4821-A68D-06F3D885309C 54 | 55 | 56 | -------------------------------------------------------------------------------- /Support/bin/fill_remind_file.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 2 | require File.join(ENV['TM_BUNDLE_SUPPORT'],'lib','GTD.rb') 3 | include GTD 4 | filename = File.join(ENV['TM_GTD_DIRECTORY'], "gtdalt.reminders") 5 | GTD.process_directory 6 | reminderLines = [] 7 | actions = GTD.actions.reject{|i| i.completed?} 8 | for action in actions do 9 | if action.due.nil? then 10 | dateObj = nil 11 | else 12 | dateObj = Date.parse(action.due) 13 | end 14 | if action.note =~ /(REM\s+[^%]*)%/ then 15 | reminderLines << $1 + "MSG #{action.note}.%" 16 | else 17 | if action.note =~ /rep:(\S+)/ then 18 | instructions = $1.split(",") 19 | for instruction in instructions do 20 | instruction = instruction.downcase 21 | case instruction 22 | when /^week$/ 23 | string = if dateObj.nil? then "Monday" else dateObj.strftime('%A') end 24 | when /^day$/ 25 | string = if dateObj.nil? then "" else dateObj.strftime('%B %d %Y *1') end 26 | when /^month$/ 27 | string = if dateObj.nil? then "1" else dateObj.strftime('%d %Y') end 28 | when /^\d+$/ 29 | string = if dateObj.nil? then instruction else dateObj.strftime('%B %d %Y *#instruction') end 30 | when /^mon|tue|wed|thu|fri|sat|sun|monday|tuesday|wednesday|thursday|friday|saturday|sunday$/ 31 | string = instruction 32 | end 33 | if ENV['TM_GTD_REMINDER'] then 34 | reminderLines << "REM #{string} +#{ENV['TM_GTD_REMINDER']} MSG #{action.name.gsub(/%/,"")} %b.%" 35 | else 36 | reminderLines << "REM #{string} MSG #{action.name.gsub(/%/,"")}.%" 37 | end 38 | end 39 | elsif !dateObj.nil? then 40 | case action.due_type 41 | when /at/,/from/ 42 | reminderLines << "REM #{dateObj.strftime('%B %d %Y')}"+ 43 | (ENV['TM_GTD_REMINDER'].nil? ? " +1" : " +#{ENV['TM_GTD_REMINDER']}") + " MSG #{action.name.gsub(/%/,"")} %b.%" 44 | when /due/ 45 | reminderLines << "REM #{dateObj.strftime('%B %d %Y')}"+ 46 | (ENV['TM_GTD_REMINDER'].nil? ? " +1" : " +#{ENV['TM_GTD_REMINDER']}") + " MSG #{action.name.gsub(/%/,"")} DUE:%b.%" 47 | end 48 | end 49 | end 50 | end 51 | reminderData = reminderLines.join("\n") 52 | File.open(filename, "w") do |f| 53 | f.puts reminderData 54 | end -------------------------------------------------------------------------------- /Support/bin/KinklessToGTDConverter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 2 | # 3 | # Kinkless to GTD converter. 4 | # 5 | # Instructions: First, save your Kinkless oo3 file as an OMPL document from within 6 | # OmniOutlinerPro. Then, open it in TextMate and select the command: 7 | # GTDAlt -> Convert From Kinkless. 8 | # A new document will be created, save it with extension gtd. 9 | # 10 | require 'pp' 11 | require 'rexml/document' 12 | require 'Date' 13 | require 'cgi' 14 | include REXML 15 | file = STDIN.read 16 | 17 | class MyBuilder 18 | def initialize 19 | @objects = Array.new 20 | end 21 | def add(hash) 22 | @objects << hash 23 | end 24 | 25 | def build 26 | indent = 0 27 | indent_inc = 2 28 | s = [] 29 | endA = [] 30 | index = 0 31 | @objects.each do |o| 32 | case o[:type] 33 | when :project 34 | t = " " * indent 35 | s << t + "project #{o[:text]}" 36 | indent += indent_inc 37 | when :action 38 | t = " " * indent 39 | t << "@#{o[:context]} #{o[:text]}" 40 | if o[:note] != "" then 41 | index += 1 42 | t << " [#{index}]" 43 | endA << "[#{index}] #{o[:note]}" 44 | end 45 | if o[:due_date] != "" then 46 | the_date = "" 47 | begin 48 | the_date = Date.parse(o[:due_date],true) 49 | rescue 50 | print "had problems with date:" + o[:due_date] + "\n" 51 | end 52 | t << " due:[#{the_date}]" 53 | end 54 | s << t 55 | when :end 56 | indent -= indent_inc 57 | t = " " * indent 58 | s << t + "end" 59 | end 60 | end 61 | return (s + endA).join("\n") 62 | end 63 | end 64 | 65 | doc = Document.new(file) 66 | $b = MyBuilder.new 67 | def process_project(project) 68 | $b.add(:type => :project, :text => CGI.unescapeHTML(project.attribute(:text).to_s)) 69 | subitems = project.children.each do |e| 70 | unless e.raw then 71 | if e.attribute(:Context) then 72 | $b.add(:type => :action, :text => CGI.unescapeHTML(e.attribute(:text).to_s), :context => e.attribute(:Context), :due_date => e.attribute(:DueDate).to_s, :note => CGI.unescapeHTML(e.attribute(:_note).to_s)) 73 | else 74 | process_project(e) 75 | end 76 | end 77 | end 78 | $b.add(:type => :end) 79 | 80 | end 81 | projects = doc.elements.each("//outline[@text='Projects']/outline") do |project| 82 | process_project(project) 83 | # pp project.methods.sort 84 | end 85 | puts $b.build -------------------------------------------------------------------------------- /Templates/Sample.tmTemplate/sample.gtd: -------------------------------------------------------------------------------- 1 | project This is the project title 2 | @home Here is an action. Each action has a context. This one's context is home. 3 | @email You can change an action's context by pressing ctrl-option-C. 4 | @reading Do this now to change this action's context to: programming. 5 | @email You can set your own, space-separated, list of contexts in the variable TM_GTD_CONTEXTS. 6 | @online The next action has a note attached to it. 7 | @errand Press ctrl-{ to be taken to the corresponding note. [1] 8 | @call You can also see a note as a tooltip. For that, press ctrl-?. 9 | project This is a subproject 10 | @call You can create a new action by going to a new line and pressing @. 11 | @call Try it on the next line. 12 | 13 | @home You can also use Enter for starting an action in a new line. This works with the caret anywhere on this line. 14 | @home You can start a note with the same context as the current line via shift-Enter. 15 | end 16 | @email The following is a completed action: 17 | #completed:[2006-11-04] @call This action is marked as completed. You can toggle the completion status via cmd-/. 18 | @home You can clean all completed actions from the current file via opt-cmd-/. 19 | @home Any projects with no actions left in them will also be removed as completed. 20 | @home A gtdalt.log file keeps track of completed items. 21 | end 22 | project !This is a permanent project because of the exclamation point. 23 | @office This project will not be removed when all the actions are completed. 24 | @office You can set due dates for items. due:[2006-10-11] 25 | @office To do that, press #. 26 | @writing You can also set 'from' and 'at' dates: from:[2006-11-05] 27 | @online Notes can contain links to websites [2] 28 | @online Emails [3] 29 | @reading Or any file in your hard drive. [4] 30 | @reading You can create links to files by dragging them. First go to the note. [5] 31 | @reading At this point, you have to copy a url first before creating a link. [6] 32 | end 33 | @call Actions can also appear outside of projects. 34 | @call To create a new project, press Return to go to a new line, then press !. Type the project's name, then press tab. 35 | [1] This is the note numbered 1. Notes must be on a single line. Use external files for longer notes. Press ctrl-{ to go back to the corresponding action. 36 | [2] Press ctrl-L to go to: 37 | [3] Press ctrl-L to start an email: 38 | [4] You can link to other files as well: 39 | [5] Now, drag a file and drop it on this line. 40 | [6] Press ctrl-N to create a new link here. In fact, create two links. Then you can open them together via ctrl-L. -------------------------------------------------------------------------------- /Commands/Set Date.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby18 9 | dialog = ENV['TM_SUPPORT_PATH'] + '/bin/tm_dialog' 10 | require ENV['TM_SUPPORT_PATH'] + '/lib/osx/plist' 11 | require ENV['TM_SUPPORT_PATH'] + '/lib/escape' 12 | require ENV['TM_SUPPORT_PATH'] + '/lib/exit_codes' 13 | require 'date' 14 | line = STDIN.read 15 | if line =~ / (at|due|from):\[(\d{4})-(\d{2})-(\d{2})\]$/ 16 | absoluteYear, absoluteMonth, absoluteDay = $2.to_i, $3.to_i, $4.to_i 17 | weekdayDay = 0 18 | date = Date.new(absoluteYear,absoluteMonth,absoluteDay) 19 | else 20 | date = Date.today 21 | absoluteYear, absoluteMonth, absoluteDay = date.year, date.month, date.day 22 | weekdayDay = date.wday 23 | end 24 | tod = Date.today 25 | def to_cocoa_date(date) 26 | ((date - Date.new(2001, 1, 1)+1)* 3600 * 24).to_i 27 | end 28 | def from_cocoa_date(number) 29 | Date.new(2001,1,1)+number.to_i/(3600 * 24) - 1 30 | end 31 | def myformat(date) 32 | date.strftime("%A, %m/%d/%Y") 33 | end 34 | plist = { 35 | 'currentDeadline' => "Current value: " + myformat(date), 36 | 'daysFromToday' => "Current value: " + (date - tod).to_s, 37 | 'newDeadlineDate' => to_cocoa_date(date), 38 | 'newDaysFromToday' => (date - tod).to_i, 39 | 'today' => "Today is " + myformat(tod) 40 | }.to_plist 41 | str = `#{e_sh dialog} datePicker -mp #{e_sh plist}` 42 | res=OSX::PropertyList.load(str) 43 | if res['returnButton'].to_s != "OK" then 44 | newDate = nil 45 | else 46 | newDate = res['newDeadlineDate'].to_s 47 | if newDate =~ /^[\d\+\-*]+$/ 48 | newDate = (tod + eval(res['newDaysFromToday'].to_s).to_i) 49 | else 50 | newDate =~ /^\w{3} (\w{3}) (\d{2}) \d{2}:\d{2}:\d{2} \w{3} (\d{4})/ 51 | newDate = Date.new($3.to_i,Date::ABBR_MONTHNAMES.index($1),$2.to_i)-1 52 | end 53 | end 54 | #puts res['newDeadlineDate']/(3600*24) 55 | #pp from_cocoa_date(res['newDeadlineDate']).to_s 56 | if newDate.nil? 57 | TextMate.exit_discard 58 | else 59 | print e_sn(line).gsub(/ (at|due|from):\[\d{4}-\d{2}-\d{2}\]$/,"") + " #{if $1 && $1 != "" then $1 else "\$0due" end}:[#{newDate}]" 60 | end 61 | 62 | fallbackInput 63 | line 64 | input 65 | selection 66 | keyEquivalent 67 | # 68 | name 69 | Set Date 70 | output 71 | insertAsSnippet 72 | scope 73 | text.gtdalt meta.action 74 | uuid 75 | 7FBD47E6-F919-45C0-956C-3996106E0C49 76 | 77 | 78 | -------------------------------------------------------------------------------- /Support/INBOX.txt: -------------------------------------------------------------------------------- 1 | The GTDALT bundle now contains a script that processes an “Inbox file” for actions, and distributes them to appropriate places. The Inbox file consists of lines of the form: 2 | 3 | @context action name >project name 4 | @context another action 5 | 6 | Here `context` and `project name` need not be matching the exact context of project name, but they each need to be *full* substrings of a *unique* item. In the absence of a project name, the action is added as a stand-alone action in a file with extension `temp.gtd`. If the context does not match any substring of an existing context, then a new context is created. 7 | 8 | The script attempts to find a unique place for each such line, and if it does so then it removes the line from the inbox and adds it at the appropriate place. At the end, it presents you with a log of the actions that took place. 9 | 10 | The purpose of this script is to be used in conjunction with a program like [Quicksilver](http://quicksilver.blacktree.com/). You use Quicksilver to append such lines to the inbox as described [here](http://www.43folders.com/2004/09/04/quicksilver-append-to-a-text-file-from-anywhere/). Then when you are ready to review things, you run the script. 11 | 12 | **Note:** I have taken some care to guarantee that no data loss results as a consequence of running this script. But please, take care to double-check the first couple of runs of the program, to make sure items go to the appropriate locations. 13 | 14 | The script, in order to replace the old “.gtd” files, takes the following steps, stopping if at any point it encounters a system error: 15 | 16 | 1. Writes the entire contents of the desired new file to files with the same name and extension “.gtd~~” 17 | 2. Writes the new inbox file adding “~~” to the extension. 18 | 3. Renames the old “.gtd” files so that they have extension “.gtd~”. 19 | 4. Renames the old inbox file adding “~” to the extension. 20 | 5. Renames the new “.gtd~~” files so that they have extension “.gtd”. 21 | 6. Renames the new inbox file so that it doesn't have the “~~”. 22 | 23 | So if the script fails at some point, you know where to look, and at the end of the day you end up with backups as “.gtd~”. 24 | 25 | In order to use the script, you need to do some work first, namely to set two environment variables: 26 | 27 | 1. A variable named `TM_GTD_DIRECTORY` needs to point to an *existing* directory containing your default GTD files. You can have multiple directories containing GTD files, and work with each individual set (they are completely independent), but the script needs to know which of these directories you want it to work with. 28 | 2. A variable named `TM_GTD_INBOX` needs to point to an *existing* “inbox file”, just a plain text file with lines as described above. (The script will just ignore malformed lines). I suggest calling it `inbox.txt` and placing somewhere in your `Documents`, but you can use any name you want. 29 | 30 | Now you should be ready to go! -------------------------------------------------------------------------------- /Commands/All Actions.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | saveActiveFile 7 | command 8 | #!/usr/bin/env ruby18 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/GTD.rb' 10 | include GTD 11 | objects = GTD.process_directory 12 | acts = objects.map{|o| o.actions}.flatten 13 | acts.sort! do |a,b| 14 | da, db = *[a.due, b.due].map {|i| i || ""} # convert nils to empty strings 15 | # puts a,b, da, db 16 | if a.context == b.context then 17 | if da == db then 18 | a.parent.name <=> b.parent.name 19 | else 20 | da <=> db 21 | end 22 | else 23 | a.context <=> b.context 24 | end 25 | end 26 | pr = Printer.new 27 | b = <<HTML 28 | <html> 29 | <head> 30 | <link rel="stylesheet" href="file://#{ENV['TM_SUPPORT_PATH']}/css/default.css" type="text/css" media="screen" title="no title" charset="utf-8" /> 31 | <link rel="stylesheet" href="file://#{ENV['TM_BUNDLE_SUPPORT']}/css/sidebar.css" type="text/css" media="screen" title="no title" charset="utf-8" /> 32 | <script src="file://#{ENV['TM_BUNDLE_SUPPORT']}/js/sidebar.js" type="text/javascript" charset="utf-8"></script> 33 | <script src="file://#{ENV['TM_BUNDLE_SUPPORT']}/js/sorttable.js" type="text/javascript" charset="utf-8"></script> 34 | <style type="text/css"> 35 | .nobr {white-space: nowrap} 36 | </style> 37 | </head> 38 | <body> 39 | <body class=""> 40 | <ul id="toggles"> 41 | <p>Next Actions Only <input type="checkbox" name="next-show" value="" id="next-show-button"></p> 42 | <li class="collapse" tablecolumn="0">Contexts</li> 43 | <li class="collapse" tablecolumn="2">Projects</li> 44 | <li class="collapse" tablecolumn="3">Files</li> 45 | </ul> 46 | HTML 47 | e = <<HTML 48 | </body> 49 | </html> 50 | HTML 51 | pr.raw b 52 | pr.table do 53 | pr.title "List of all Actions" 54 | pr.headers([ "Context", "Action", "Project", "File" , "Due by","Completed"]) 55 | acts.each do |a| 56 | proj = a.parent.link 57 | due = case a.due 58 | when "",nil 59 | "" 60 | when DateLate 61 | "<span style=\"color:red\">#{a.due}</span>" 62 | else 63 | a.due 64 | end 65 | note_part = (a.note != "") ? " title=\"#{a.note}\"" : "" 66 | text = "<a href=\"#{a.txmt}\"#{note_part}>#{a.name}</a>" 67 | if a.is_next_action? 68 | pr.row_next([a.context, text, proj, a.root.link , "<span class=\"nobr\">#{due}</span>", a.mark_completed_link]) 69 | else 70 | pr.row([a.context, text, proj, a.root.link , "<span class=\"nobr\">#{due}</span>", a.mark_completed_link]) 71 | end 72 | end 73 | end 74 | pr.raw e 75 | puts pr.to_html 76 | fallbackInput 77 | word 78 | input 79 | none 80 | keyEquivalent 81 | ^~@0 82 | name 83 | Current Actions 84 | output 85 | showAsHTML 86 | scope 87 | text.gtdalt 88 | uuid 89 | F4884C32-9046-48E8-AB57-1784AF8C264C 90 | 91 | 92 | -------------------------------------------------------------------------------- /Support/bin/process_inbox.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 2 | $:<Some files need to be created first!" + `#{e_sh File.join(ENV['TM_SUPPORT_PATH'],'bin',"MarkDown.pl")} #{e_sh File.join(ENV['TM_BUNDLE_SUPPORT'],"/INBOX.txt")} `) 17 | end 18 | dir = File.expand_path(dir) 19 | inboxfile = File.expand_path(inboxfile) 20 | tempInboxFilename = File.join(dir, "temp.gtd") 21 | `touch "#{tempInboxFilename}"` unless File.exist?(tempInboxFilename) 22 | objects = GTD.process_directory(dir) 23 | inbox_object = objects.find{|o| o.file == tempInboxFilename} 24 | objects << (inbox_object = GTDFile.new(tempInboxFilename)) unless inbox_object 25 | projects = GTD.projects 26 | contexts = GTDContexts.contexts 27 | f = File.open(inboxfile, "r") 28 | lines = f.readlines.map{|i| i.chomp} 29 | f.close 30 | 31 | $log = [] 32 | $unsorted = [] 33 | for text in lines do 34 | begin 35 | m = text.match(/^\s*@(\S+)\s+([^>]+)(?:>\s*(.*))?$/) 36 | raise(NormalException, text.match(/^\s*$/) ? "" : "Malformed Input Line: #{text}") unless m 37 | context, action, project = m[1..3] 38 | newContext = contexts.find_all{|c| c.downcase.index(context.downcase) != nil} 39 | case newContext.length 40 | when 0 41 | contexts = GTDContexts.contexts << context 42 | $log << "Created new context: #{context}." 43 | newContext = context 44 | when 1 45 | newContext = newContext[0] 46 | else 47 | raise NormalException, "Too many contexts matching: #{context}." 48 | end 49 | targetProj = inbox_object 50 | unless project == nil or project == "" then 51 | proj = projects.find_all{|pro| pro.name.downcase.index(project.downcase) != nil} 52 | case proj.length 53 | when 0 54 | choice = TextMate::UI.request_item(:title => "No matching project", :prompt => "Did not find any project matching: #{project}. Please select a project from the list", :items => projects.map{|pro| pro.name} ) 55 | if choice then 56 | targetProj = projects.find{|pro| pro.name == choice} 57 | else 58 | raise NormalException, "Did not find any project matching: #{project}." 59 | end 60 | when 1 61 | targetProj = proj[0] 62 | else 63 | choice = TextMate::UI.request_item(:title => "Too many matching projects", :prompt => "Found too many projects matching: #{project}. Please select a project from the list", :items => proj.map{|pro| pro.name} ) 64 | if choice then 65 | targetProj = projects.find{|pro| pro.name == choice} 66 | else 67 | raise NormalException, "Too many projects matching: #{project}." 68 | end 69 | end 70 | end 71 | act = Action.new(:name => action,:context => newContext) 72 | targetProj.add_item(act) 73 | $log << "Added action #{act.name} with context: #{newContext} to project: #{targetProj.name}." 74 | rescue NormalException => e 75 | $unsorted << text 76 | $log << e.to_s 77 | next 78 | rescue Exception => e 79 | raise e 80 | end 81 | end 82 | # Create files we want 83 | data = objects.map{|o| [o.file.to_s,o.dump_object]} << [inboxfile,$unsorted.join("\n")] 84 | GTD.safe_write_with_backup(data) 85 | # Return feedback 86 | puts $log.join("\n") 87 | puts "LINES THAT COULD NOT BE PROCESSED:" 88 | puts $unsorted.join("\n") -------------------------------------------------------------------------------- /Support/tests/test_gtd.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | $:.unshift "../lib" 3 | ENV['TM_GTD_CONTEXT'] = "hello there" 4 | ENV['TM_GTD_CONTEXTS'] = "hello there" 5 | require "GTD.rb" 6 | include GTD 7 | class TestGTD < Test::Unit::TestCase 8 | def setup 9 | GTDContexts.contexts = nil 10 | end 11 | def test_action 12 | assert_equal(["hello","there"], GTDContexts.contexts) 13 | @a = Action.new(:name => "the name", :context => "thecontext", :parent => "project", :file => "2005-03-05") 14 | assert_not_nil(@a) 15 | assert_equal("the name", @a.name) 16 | assert_equal("thecontext", @a.context) 17 | assert_equal("project", @a.parent) 18 | @b = Action.new(:name => "another action",:context => "emailing",:file => "file2", :line => 15) 19 | assert_equal("another action", @b.name) 20 | assert_equal("emailing", @b.context) 21 | assert_equal(nil, @b.project) 22 | assert_equal(["file2",15], [@b.file,@b.line]) 23 | assert_equal(nil,@b.due) 24 | assert_equal(["hello", "there"],GTDContexts.contexts) 25 | end 26 | def test_GTD_parse 27 | assert_equal(["hello","there"],GTDContexts.contexts) 28 | File.open("test_example.gtd") do |f| 29 | @data = f.read 30 | end 31 | instructions = GTD::parse(@data) 32 | assert_not_nil(instructions) 33 | assert_equal(11, instructions.length) 34 | assert_equal([:project,:completed,:action,:action,:project,:action,:end,:action,:end,:action,:note],instructions.map{|i| i[0]}[0..10]) 35 | assert_equal([:project,"World domination",nil,nil], instructions[0]) 36 | assert_equal([:action,"Create giant laser beam [1]","errand","due:[2006-06-04]"], instructions[2]) 37 | assert_equal([:end,nil,nil,nil], instructions[6]) 38 | assert_equal([:action,"Hello there","email",nil], instructions[9]) 39 | end 40 | def test_GTDFile_initialize 41 | @object = GTDFile.new("test_example.gtd") 42 | assert_not_nil(@object) 43 | assert_equal(2, @object.projects.length) 44 | assert_equal(["World domination","A subproject"], @object.projects.map{|i| i.name}) 45 | assert_equal(5, @object.actions.length) 46 | assert_equal(["Create giant laser beam","Threaten to destroy Barbados","An action","Take over world","Hello there"], @object.actions.map{|i| i.name}) 47 | assert_equal(["email","email-task","errand","hello","testing","there","work"], GTDContexts.contexts) 48 | assert_equal(7, GTDContexts.contexts.length) 49 | end 50 | def test_projects 51 | test_GTDFile_initialize 52 | @p1, @p2 = @object.projects 53 | assert_equal("World domination", @p1.name) 54 | assert_equal("A subproject", @p2.name) 55 | assert_equal(@p1,@p2.parent) 56 | assert_equal(5, @p1.subitems.length) 57 | @p1.subitems.each do |s| 58 | assert_equal(@p1, s.parent) 59 | end 60 | assert_equal(1,@p2.subitems.length) 61 | end 62 | def test_flatten 63 | test_GTDFile_initialize 64 | flat = @object.flatten 65 | assert_equal(9, flat.length) 66 | flat.each { |item| assert_equal(@object, item.root) } 67 | end 68 | def test_actions 69 | test_projects 70 | @a1 = @p1.subitems[1] 71 | assert_equal("Create giant laser beam", @a1.name) 72 | assert_equal("2006-06-04", @a1.due) 73 | assert_equal("A note here ", @a1.note) 74 | assert_equal("due", @a1.due_type) 75 | end 76 | def test_update 77 | test_GTDFile_initialize 78 | l = @object.update! 79 | assert_equal(11,l) 80 | assert_equal([0,1,2,3,4,5,6,8,10], @object.flatten.map {|i| i.line}) 81 | assert_not_nil(@object.file) 82 | @object.flatten.each do |e| 83 | assert_equal(@object.file,e.file) 84 | end 85 | end 86 | def test_dump_object 87 | test_GTDFile_initialize 88 | File.open("test_example.gtd") do |f| 89 | assert_equal(f.read.chomp, @object.dump_object) 90 | end 91 | end 92 | def test_GTD_singleton_calls 93 | contexts = GTDContexts.contexts 94 | GTD.process_directory 95 | @na = GTD.next_actions 96 | end 97 | end -------------------------------------------------------------------------------- /Support/css/sidebar.css: -------------------------------------------------------------------------------- 1 | /* Sortable tables */ 2 | table.sortable a.sortheader { 3 | /* background-color:#eee;*/ 4 | color:#666666; 5 | /* font-weight: bold;*/ 6 | text-decoration: none; 7 | display: block; 8 | } 9 | table.sortable span.sortarrow { 10 | color: #666666; 11 | text-decoration: none; 12 | } 13 | table.sortable a.sortheader:hover, table.sortable a.sortheader:hover span.sortarrow { 14 | color: #384450; 15 | } 16 | /* Sidebar */ 17 | * { 18 | margin: 0; 19 | padding: 0; 20 | } 21 | ul { padding-right: 5px; } 22 | li { list-style: none; } 23 | ul#toggles { 24 | float:left; 25 | margin-top: 10px; 26 | width: 180px; 27 | } 28 | ul#toggles>li>span { 29 | padding-left: 3px; 30 | font-size: 85%; 31 | } 32 | ul#toggles>li>span a { 33 | margin: 0 1px; 34 | } 35 | ul#toggles>li { 36 | padding-left: 14px; 37 | background: url(arrow_down.png) no-repeat 2px 4px; 38 | padding-bottom: 5px; 39 | } 40 | ul#toggles>li.collapse { 41 | background: url(arrow.png) no-repeat 2px 4px; 42 | } 43 | .collapse * { display: none; } 44 | .next-show tr.not-next { display: none; } 45 | tr.next { background-color: #F6F6F6; } 46 | 47 | .con-0-hide .con-0, .con-1-hide .con-1, .con-2-hide .con-2, .con-3-hide .con-3, .con-4-hide .con-4, .con-5-hide .con-5, .con-6-hide .con-6, .con-7-hide .con-7, .con-8-hide .con-8, .con-9-hide .con-9, .con-10-hide .con-10, .con-11-hide .con-11, .con-12-hide .con-12, .con-13-hide .con-13, .con-14-hide .con-14, .con-15-hide .con-15, .con-16-hide .con-16, .con-17-hide .con-17, .con-18-hide .con-18, .con-19-hide .con-19, .con-20-hide .con-20, .con-21-hide .con-21, .con-22-hide .con-22, .con-23-hide .con-23, .con-24-hide .con-24, .con-25-hide .con-25, .con-26-hide .con-26, .con-27-hide .con-27, .con-28-hide .con-28, .con-29-hide .con-29, .pro-0-hide .pro-0, .pro-1-hide .pro-1, .pro-2-hide .pro-2, .pro-3-hide .pro-3, .pro-4-hide .pro-4, .pro-5-hide .pro-5, .pro-6-hide .pro-6, .pro-7-hide .pro-7, .pro-8-hide .pro-8, .pro-9-hide .pro-9, .pro-10-hide .pro-10, .pro-11-hide .pro-11, .pro-12-hide .pro-12, .pro-13-hide .pro-13, .pro-14-hide .pro-14, .pro-15-hide .pro-15, .pro-16-hide .pro-16, .pro-17-hide .pro-17, .pro-18-hide .pro-18, .pro-19-hide .pro-19, .pro-20-hide .pro-20, .pro-21-hide .pro-21, .pro-22-hide .pro-22, .pro-23-hide .pro-23, .pro-24-hide .pro-24, .pro-25-hide .pro-25, .pro-26-hide .pro-26, .pro-27-hide .pro-27, .pro-28-hide .pro-28, .pro-29-hide .pro-29, .pro-30-hide .pro-30, .pro-31-hide .pro-31, .pro-32-hide .pro-32, .pro-33-hide .pro-33, .pro-34-hide .pro-34, .pro-35-hide .pro-35, .pro-36-hide .pro-36, .pro-37-hide .pro-37, .pro-38-hide .pro-38, .pro-39-hide .pro-39, .pro-40-hide .pro-40, .pro-41-hide .pro-41, .pro-42-hide .pro-42, .pro-43-hide .pro-43, .pro-44-hide .pro-44, .pro-45-hide .pro-45, .pro-46-hide .pro-46, .pro-47-hide .pro-47, .pro-48-hide .pro-48, .pro-49-hide .pro-49, .pro-50-hide .pro-50, .pro-51-hide .pro-51, .pro-52-hide .pro-52, .pro-53-hide .pro-53, .pro-54-hide .pro-54, .pro-55-hide .pro-55, .pro-56-hide .pro-56, .pro-57-hide .pro-57, .pro-58-hide .pro-58, .pro-59-hide .pro-59, .pro-60-hide .pro-60, .pro-61-hide .pro-61, .pro-62-hide .pro-62, .pro-63-hide .pro-63, .pro-64-hide .pro-64, .pro-65-hide .pro-65, .pro-66-hide .pro-66, .pro-67-hide .pro-67, .pro-68-hide .pro-68, .pro-69-hide .pro-69, .pro-70-hide .pro-70, .pro-71-hide .pro-71, .pro-72-hide .pro-72, .pro-73-hide .pro-73, .pro-74-hide .pro-74, .pro-75-hide .pro-75, .pro-76-hide .pro-76, .pro-77-hide .pro-77, .pro-78-hide .pro-78, .pro-79-hide .pro-79, .pro-80-hide .pro-80, .pro-81-hide .pro-81, .pro-82-hide .pro-82, .pro-83-hide .pro-83, .pro-84-hide .pro-84, .pro-85-hide .pro-85, .pro-86-hide .pro-86, .pro-87-hide .pro-87, .pro-88-hide .pro-88, .pro-89-hide .pro-89, .pro-90-hide .pro-90, .pro-91-hide .pro-91, .pro-92-hide .pro-92, .pro-93-hide .pro-93, .pro-94-hide .pro-94, .pro-95-hide .pro-95, .pro-96-hide .pro-96, .pro-97-hide .pro-97, .pro-98-hide .pro-98, .pro-99-hide .pro-99, .pro-100-hide .pro-100, .pro-101-hide .pro-101, .pro-102-hide .pro-102, .pro-103-hide .pro-103, .pro-104-hide .pro-104, .pro-105-hide .pro-105, .pro-106-hide .pro-106, .pro-107-hide .pro-107, .pro-108-hide .pro-108, .pro-109-hide .pro-109, .pro-110-hide .pro-110, .pro-111-hide .pro-111, .pro-112-hide .pro-112, .pro-113-hide .pro-113, .pro-114-hide .pro-114, .pro-115-hide .pro-115, .pro-116-hide .pro-116, .pro-117-hide .pro-117, .pro-118-hide .pro-118, .pro-119-hide .pro-119, .pro-120-hide .pro-120, .pro-121-hide .pro-121, .pro-122-hide .pro-122, .pro-123-hide .pro-123, .pro-124-hide .pro-124, .pro-125-hide .pro-125, .pro-126-hide .pro-126, .pro-127-hide .pro-127, .pro-128-hide .pro-128, .pro-129-hide .pro-129, .fil-0-hide .fil-0, .fil-1-hide .fil-1, .fil-2-hide .fil-2, .fil-3-hide .fil-3, .fil-4-hide .fil-4, .fil-5-hide .fil-5, .fil-6-hide .fil-6, .fil-7-hide .fil-7, .fil-8-hide .fil-8, .fil-9-hide .fil-9, .fil-10-hide .fil-10, .fil-11-hide .fil-11, .fil-12-hide .fil-12, .fil-13-hide .fil-13, .fil-14-hide .fil-14, .fil-15-hide .fil-15, .fil-16-hide .fil-16, .fil-17-hide .fil-17, .fil-18-hide .fil-18, .fil-19-hide .fil-19, .fil-20-hide .fil-20, .fil-21-hide .fil-21, .fil-22-hide .fil-22, .fil-23-hide .fil-23, .fil-24-hide .fil-24, .fil-25-hide .fil-25, .fil-26-hide .fil-26, .fil-27-hide .fil-27, .fil-28-hide .fil-28, .fil-29-hide .fil-29 { 48 | display:none; 49 | } -------------------------------------------------------------------------------- /Commands/Review.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | bundleUUID 8 | 30FA6EB3-2180-46F8-B2DD-EDA6B443CBB6 9 | command 10 | #!/usr/bin/env ruby18 11 | require 'date' 12 | $: << ENV['TM_BUNDLE_SUPPORT'] 13 | require "lib/GTD.rb" 14 | include GTD 15 | 16 | objects = GTD.process_directory 17 | gtdacts = objects.map{|o| o.actions}.flatten 18 | gtdprojects = objects.map{|o| o.projects}.flatten 19 | 20 | class LogProject 21 | def initialize(date,name) 22 | @date = date 23 | @name = name 24 | end 25 | def getprojects 26 | return @name 27 | end 28 | end 29 | 30 | class LogAction 31 | def initialize(date,project,context,name) 32 | @date = date 33 | @project = project 34 | @context = context 35 | @name = name 36 | end 37 | def getcontexts 38 | return @context 39 | end 40 | def getprojects 41 | return @project 42 | end 43 | end 44 | 45 | acts = Array.new 46 | projects = Array.new 47 | 48 | today = Date.today 49 | review = today - 7 50 | 51 | logfile = File.join(GTD.get_gtd_directory,"GTD.gtdlog") 52 | if File.exists?(logfile) then 53 | log = File.open(logfile) 54 | else 55 | puts "Log file #{logfile} not found. Review requires this file. You should run the Clean Current File command to generate a log file." 56 | exit 57 | end 58 | 59 | log.readlines.each { |line| 60 | if line =~ /^\/(.*)\/(.*)(\/@(\S+))?/ then 61 | logdate = Date.strptime($1, '%Y-%m-%d') 62 | if logdate >= review then 63 | project = $2 64 | if line =~ /^\/(.*)\/(.*)\/@(\S+)\s(.*)$/ 65 | logact = LogAction.new($1.to_s,$2.to_s,$3.to_s,$4.to_s) 66 | acts << logact 67 | else 68 | # logproj = LogProject.new(logdate.to_s,project) 69 | projects << LogProject.new(logdate.to_s,project) 70 | end 71 | end 72 | end 73 | } 74 | 75 | pr = Printer.new 76 | b = <<HTML 77 | <html> 78 | <head> 79 | <link rel="stylesheet" href="file://#{ENV['TM_SUPPORT_PATH']}/css/default.css" type="text/css" media="screen" title="no title" charset="utf-8" /> 80 | <style type="text/css"> 81 | .nobr {white-space: nowrap} 82 | </style> 83 | </head> 84 | <body> 85 | <h1>GTD Review (#{review} - #{Date.today})</h1> 86 | HTML 87 | e = <<HTML 88 | </body> 89 | </html> 90 | HTML 91 | pr.raw b 92 | 93 | 94 | pComplete = Array.new 95 | projects.each {|p| pComplete << p.getprojects } 96 | if not pComplete.empty? then 97 | pComplete = pComplete.uniq.sort 98 | pr.raw "<h2>Projects completed</h2><ul>" 99 | pComplete.each {|p| 100 | pr.raw "<li>#{p}" 101 | } 102 | pr.raw "</ul>" 103 | end 104 | 105 | if not acts.empty? then 106 | pUsed = Array.new 107 | acts.each {|p| pUsed << p.getprojects } 108 | pUsed = pUsed.uniq.sort 109 | diffarray = Array.new 110 | gtdprojects.each{|p| diffarray << p.name } 111 | projectsDiff = diffarray - pUsed 112 | projectsDiff = projectsDiff.uniq.sort 113 | if not projectsDiff.empty? then 114 | pr.raw "<h2>Projects not acted on</h2>" 115 | pr.table do 116 | pr.headers([ "Project", "Next Action", "Context", "Due by", "Completed"]) 117 | projectsDiff.each{|p| 118 | project = gtdprojects.find{|i| i.name == p} 119 | if project.next_action then 120 | if project.next_action.note then 121 | text = "#{project.next_action.link} / #{project.next_action.note}" 122 | else 123 | text = project.next_action.link 124 | end 125 | due = case project.next_action.due 126 | when "",nil 127 | "" 128 | when DateLate 129 | "<span style=\"color:red\">#{project.next_action.due}</span>" 130 | else 131 | project.next_action.due 132 | end 133 | pr.row([project.link, text, "<span class=\"nobr\">#{project.next_action.context}</span>", "<span class=\"nobr\">#{due}</span>", project.next_action.mark_completed_link]) 134 | end 135 | } 136 | end # pr.table 137 | end 138 | else # if acts is empty 139 | pr.raw "<h2>No logged activity to report</h2>" 140 | end 141 | 142 | pr.raw "<h2>Dated Actions</h2>" 143 | pr.table do 144 | pr.headers([ "Context", "Action", "Project", "Due by", "Completed"]) 145 | gtdacts.delete_if {|a| a.due == nil } 146 | gtdacts.sort{|a,b| a.due <=> b.due}.each do |a| 147 | proj = if a.project != nil then a.project.link else "none" end 148 | if a.due != nil then 149 | due = case a.due 150 | when "",nil 151 | "" 152 | when DateLate 153 | "<span style=\"color:red\">#{a.due}</span>" 154 | else 155 | a.due 156 | end 157 | text = "<a href=\"#{a.txmt}\">#{a.name}</a>" 158 | pr.row([a.context, text, proj , "<span class=\"nobr\">#{due}</span>", a.mark_completed_link]) 159 | end 160 | end 161 | end 162 | 163 | pr.raw e 164 | puts pr.to_html 165 | input 166 | selection 167 | keyEquivalent 168 | ^~@- 169 | name 170 | Past Activity 171 | output 172 | showAsHTML 173 | scope 174 | text.gtdalt 175 | uuid 176 | BB5DCAEF-2D31-47BC-B4D5-C221F026B2F3 177 | 178 | 179 | -------------------------------------------------------------------------------- /Support/js/sidebar.js: -------------------------------------------------------------------------------- 1 | function toggle_sublist(e) { 2 | if (this == e.target) { 3 | if (this.className == "collapse") 4 | this.className = ""; 5 | else 6 | this.className = "collapse"; 7 | } 8 | return true; 9 | } 10 | function toggle_next_actions() { 11 | var table = document.getElementsByTagName('tbody')[0]; 12 | if (this.checked) { 13 | table.className = "next-show"; 14 | } else { 15 | table.className = ""; 16 | } 17 | return true; 18 | } 19 | function toggle_this() { 20 | var body_tag = document.getElementsByTagName('body')[0]; 21 | var the_name = this.name; 22 | var i = body_tag.className.indexOf(the_name); 23 | var c_name = body_tag.className; 24 | if (i == -1) { 25 | body_tag.className = the_name + " " + c_name; 26 | } else { 27 | body_tag.className = c_name.substring(0,i) + c_name.substring(i+the_name.length,c_name.length); 28 | } 29 | return true; 30 | } 31 | function get_toggles() { 32 | var list = document.getElementById('toggles').childNodes; 33 | var to_return = new Array; 34 | for (i=0;i'; 72 | for (var i=0;i'+ titles[i] + '' 74 | str += string_substitute(link_str,"TEMPLATE",type_name); 75 | } 76 | str += ""; 77 | return str; 78 | } 79 | function find_contexts () { 80 | return find_values(0) 81 | } 82 | 83 | function find_values(index){ 84 | var tags = document.getElementsByTagName('tr'); 85 | var contexts = new Array; 86 | for (i=0;i 2 | 3 | 4 | 5 | contactEmailRot13 6 | pfxvnqnf@tznvy.pbz 7 | contactName 8 | Charilaos Skiadas 9 | description 10 | Support for a variant of <a href="http://skiadas.dcostanet.net/afterthought/2006/06/25/details-on-the-gtdalt-bundle/">Getting Things Done</a>. 11 | mainMenu 12 | 13 | excludedItems 14 | 15 | C78ECFD8-DABE-4E2C-B628-0DDF2A3A3B21 16 | 7118880F-C5BA-4359-87A1-480FB61E416B 17 | 087C6A41-F2A3-4CEF-B991-93BCF84F6B63 18 | 19 | items 20 | 21 | 864AF155-E241-499E-B619-9871BB28DC70 22 | EA55C412-913F-45AD-979D-0A90666BB862 23 | 3D6AC681-C5FA-4141-A100-AC8FFBA85A29 24 | ------------------------------------ 25 | 3F759882-3B39-4C69-8078-F5190DE75B7F 26 | D41A7357-2D2A-4716-8621-363134EDA81E 27 | ------------------------------------ 28 | 8E083C6C-9C1A-4AD9-AA39-3BCABF925B73 29 | 0E310136-E497-47BF-82F8-26818C0829AA 30 | 31 | submenus 32 | 33 | 3D6AC681-C5FA-4141-A100-AC8FFBA85A29 34 | 35 | items 36 | 37 | 14CE2968-B4F4-4AB5-8A26-5F0EC156E1D0 38 | 74B7D57C-F538-41BE-A228-1F077EC8F3B4 39 | 65E842B0-D1A6-40E7-91C0-3215930AC0EA 40 | 41 | name 42 | Note 43 | 44 | 3F759882-3B39-4C69-8078-F5190DE75B7F 45 | 46 | items 47 | 48 | BB5DCAEF-2D31-47BC-B4D5-C221F026B2F3 49 | BF7C70D8-A0D9-44A3-9CB4-8D99372E68B6 50 | F4884C32-9046-48E8-AB57-1784AF8C264C 51 | 52 | name 53 | Review 54 | 55 | 864AF155-E241-499E-B619-9871BB28DC70 56 | 57 | items 58 | 59 | 98227572-90C0-43C5-A18B-EA2591674553 60 | 253F6CF9-0E14-48C8-8468-005018E4E301 61 | ------------------------------------ 62 | A54CF0C9-ECBF-4A20-B345-3EA167ABD6A0 63 | 255E029C-7B95-49F2-B7AC-0FAB4F7FBA58 64 | 65 | name 66 | Project 67 | 68 | D41A7357-2D2A-4716-8621-363134EDA81E 69 | 70 | items 71 | 72 | FCAAFD28-1765-4618-851D-20AA52499F77 73 | 7A8587A6-743D-4449-A0E2-BFD9A97893BC 74 | EB99AA83-E73B-42F6-88E1-6F8AB29F9BFD 75 | ------------------------------------ 76 | 40ABCA1E-A154-47C7-8EBC-D22DC078D295 77 | 78 | name 79 | Synchronization 80 | 81 | EA55C412-913F-45AD-979D-0A90666BB862 82 | 83 | items 84 | 85 | BBA04431-8960-4017-A684-7FBACB0F7FFF 86 | 4EF0270A-A1D7-4E98-8123-6E6F1305B7E2 87 | 65FAC309-42DC-4C48-AC0D-BC7AA5864C71 88 | ------------------------------------ 89 | 2B282247-5930-462F-8D6E-54ADF228EC49 90 | 7FBD47E6-F919-45C0-956C-3996106E0C49 91 | 424CDCC9-54C3-4D85-9A9D-89D0175C51D4 92 | ------------------------------------ 93 | BF20459A-D83B-4821-A68D-06F3D885309C 94 | 3BF349AD-3452-4CA4-AAA7-8836565E3A5F 95 | ------------------------------------ 96 | A9C04B17-6A04-4D4D-81ED-2F4EFCD61FA3 97 | 2803EE1A-6878-47C7-BB7F-3C1B69315209 98 | 99 | name 100 | Action 101 | 102 | 103 | 104 | name 105 | GTDAlt 106 | ordering 107 | 108 | 7A8587A6-743D-4449-A0E2-BFD9A97893BC 109 | 087C6A41-F2A3-4CEF-B991-93BCF84F6B63 110 | 40ABCA1E-A154-47C7-8EBC-D22DC078D295 111 | 8E083C6C-9C1A-4AD9-AA39-3BCABF925B73 112 | FCAAFD28-1765-4618-851D-20AA52499F77 113 | 7118880F-C5BA-4359-87A1-480FB61E416B 114 | A54CF0C9-ECBF-4A20-B345-3EA167ABD6A0 115 | 255E029C-7B95-49F2-B7AC-0FAB4F7FBA58 116 | 253F6CF9-0E14-48C8-8468-005018E4E301 117 | 65E842B0-D1A6-40E7-91C0-3215930AC0EA 118 | FFECDABA-E216-4182-B0CB-7031D279EEDE 119 | 98227572-90C0-43C5-A18B-EA2591674553 120 | BBA04431-8960-4017-A684-7FBACB0F7FFF 121 | 74B7D57C-F538-41BE-A228-1F077EC8F3B4 122 | 4EF0270A-A1D7-4E98-8123-6E6F1305B7E2 123 | 65FAC309-42DC-4C48-AC0D-BC7AA5864C71 124 | 2B282247-5930-462F-8D6E-54ADF228EC49 125 | F4884C32-9046-48E8-AB57-1784AF8C264C 126 | BF7C70D8-A0D9-44A3-9CB4-8D99372E68B6 127 | C78ECFD8-DABE-4E2C-B628-0DDF2A3A3B21 128 | BF20459A-D83B-4821-A68D-06F3D885309C 129 | 3BF349AD-3452-4CA4-AAA7-8836565E3A5F 130 | 14CE2968-B4F4-4AB5-8A26-5F0EC156E1D0 131 | A9C04B17-6A04-4D4D-81ED-2F4EFCD61FA3 132 | 2803EE1A-6878-47C7-BB7F-3C1B69315209 133 | 7FBD47E6-F919-45C0-956C-3996106E0C49 134 | BB5DCAEF-2D31-47BC-B4D5-C221F026B2F3 135 | C36472BD-A8CD-4613-A595-CEFB052E6181 136 | B8601C35-4EBE-4672-8A09-919A6730DC37 137 | E6CF82DF-159E-4B88-B903-4B3BBCDD3233 138 | D876C2A8-1E6F-4793-B990-8478DF7552DD 139 | EB99AA83-E73B-42F6-88E1-6F8AB29F9BFD 140 | 0E310136-E497-47BF-82F8-26818C0829AA 141 | 142 | uuid 143 | 30FA6EB3-2180-46F8-B2DD-EDA6B443CBB6 144 | 145 | 146 | -------------------------------------------------------------------------------- /Support/lib/GTDUtils.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | require 'fileutils.rb' 3 | # Useful methods that don't need to scan any files in order to work 4 | class GTDContexts 5 | class << self 6 | def contexts 7 | @@contexts ||= self.get_env_contexts 8 | @@contexts.uniq! 9 | @@contexts.sort! 10 | @@contexts 11 | end 12 | def contexts=(newContexts) 13 | @@contexts = newContexts 14 | end 15 | def get_env_contexts 16 | contexts = ("#{ENV['TM_GTD_CONTEXT']} #{ENV['TM_GTD_CONTEXTS']}").chomp.split(" ").compact.sort 17 | contexts = ["email", "office", "online", "home", "call", "waiting"] if contexts.empty? 18 | return contexts.uniq.sort 19 | end 20 | end 21 | end 22 | # COMMENT TO BE REMOVED ONCE THINGS ARE CHECKED TO WORK 23 | # module GTDLight 24 | # def GTDLight.get_env_contexts 25 | # contexts = ((ENV['TM_GTD_CONTEXT'] || "") + " " + (ENV['TM_GTD_CONTEXTS'] || "")).chomp.split(" ").compact.sort 26 | # contexts = ["email", "office", "online", "home", "call", "waiting"] if contexts.empty? 27 | # return contexts.uniq.sort 28 | # end 29 | # end 30 | class Array 31 | def next(item) 32 | i = (self.index(item) || -1) + 1 33 | return self[i % length] 34 | end 35 | def previous(item) 36 | i = (self.index(item) || length) -1 37 | return self[i % length] 38 | end 39 | end 40 | # The test date === DateLate returns true if +date+ is earlier than Date.today 41 | class DateLate 42 | def self.===(date) 43 | date = Date.parse(date) unless Date === date 44 | return date < Date.today 45 | end 46 | end 47 | # A simple mixin that creates +uri+'s, +txmt+ uri schemes and +link+'s out of any object 48 | # that responds to +file+, +line+ and +name+. 49 | module Linkable 50 | def uri 51 | "file://#{e_url self.file.to_s}" 52 | end 53 | def txmt 54 | "txmt://open?url=#{self.uri}&line=#{self.line}" 55 | end 56 | def link(attributes = {}) 57 | s = "#{self.name}" 62 | end 63 | def mark_completed_link(attributes={}) 64 | s = "Mark!" 72 | end 73 | end 74 | 75 | # Very light logger class. 76 | class MyLogger 77 | # Adds a string to the logs. 78 | def self.log(string) 79 | @@logs << string 80 | end 81 | # Dumps a string with all the logs. 82 | def self.dump 83 | @@logs.sort.join("\n") 84 | end 85 | # Clears the logs. 86 | def self.clear 87 | @@logs = [] 88 | end 89 | self.clear 90 | end 91 | # A very-poor-man's Builder. Produces tables. Example of use: 92 | # 93 | # pr = Printer.new 94 | # pr.table do 95 | # pr.title("Actions for context: #{context}") 96 | # pr.headers(["Action name","Project","Due_by"]) 97 | # actions.each do |a| 98 | # proj = a.parent.link 99 | # due = case a.due 100 | # when "",nil 101 | # "" 102 | # when DateLate 103 | # "#{a.due}" 104 | # else 105 | # a.due 106 | # end 107 | # pr.row([a.link,proj,due]) 108 | # end 109 | # return pr.to_html 110 | class Printer 111 | # An array of items of the form +[:type,items]+, +items+ an array of strings. 112 | attr_accessor :items 113 | def initialize 114 | @items = [] 115 | end 116 | # Adds caption to the table. 117 | def title(title) 118 | @items << [:title,title] 119 | end 120 | # Creates a table, with contents generated by yielding the block. 121 | def table 122 | @items << [:table_begin,nil] 123 | yield 124 | @items << [:table_end,nil] 125 | end 126 | # Adds headers to the table. 127 | def headers(*headers) 128 | @items << [:headers,*headers] 129 | end 130 | # Adds a row of items. 131 | def row(*row_items) 132 | @items << [:row,*row_items] 133 | end 134 | def row_next(*row_items) 135 | @items << [:row_next,*row_items] 136 | end 137 | # Adds raw HTML code. 138 | def raw(code) 139 | @items << [:raw, code] 140 | end 141 | # Generate an HTML string out of the data that has been passed. 142 | def to_html 143 | s = [] 144 | @items.each do |type,item| 145 | case type 146 | when :title 147 | s << "#{item}" 148 | when :table_begin 149 | s << '' 150 | when :table_end 151 | s << "
" 152 | when :row 153 | s << "" 154 | s << item.map{|i| "#{i}"}.join("\n") 155 | s << "" 156 | when :row_next 157 | s << "" 158 | s << item.map{|i| "#{i}"}.join("\n") 159 | s << "" 160 | when :headers 161 | s << "" 162 | s << item.map{|i| "#{i}"}.join("\n") 163 | s << "" 164 | when :raw 165 | s << item 166 | end 167 | end 168 | return s.join("\n")+"\n" 169 | end 170 | end 171 | 172 | module GTD 173 | # Safe write of the pairs in +filepairs+, of the form [filename,format]. 174 | def GTD.safe_write_with_backup(filepairs) 175 | # First read each filepair and compare with string to replace it. 176 | # If the saem, ignore. 177 | changed_filepairs = [] 178 | for filename,string in filepairs do 179 | File.open(filename,'r') do |f| 180 | changed_filepairs << [filename,string] unless f.read.to_s == string 181 | end 182 | end 183 | begin 184 | for filename,string in changed_filepairs do 185 | newFile = filename + "~~" 186 | raise "Could not create different name" if newFile == filename 187 | File.open(newFile, 'w') do |f| 188 | f.puts string 189 | end 190 | end 191 | rescue Exception => e 192 | $stderr.puts "There was a problem saving the files: #{e}.\nExiting... Some extra files may have been created." 193 | raise e 194 | end 195 | begin 196 | for filename,string in changed_filepairs do 197 | FileUtils.mv(filename,filename+"~") 198 | end 199 | for filename,string in changed_filepairs do 200 | FileUtils.mv(filename+"~~",filename) 201 | end 202 | rescue Exception => e 203 | $stderr.puts "There was a problem moving the files: #{e}.\nExiting... Files with extension \".gtd~~\" contain the newest data that could not be moved." 204 | raise e 205 | end 206 | end 207 | end 208 | class NilClass 209 | def link 210 | return "" 211 | end 212 | end 213 | # URL escape a string but preserve slashes (idea being we have a file system path that we want to use with file://) 214 | # Borrowed from TextMate's library. 215 | def e_url(str) 216 | str.gsub(/([^a-zA-Z0-9\/_.-]+)/n) do 217 | '%' + $1.unpack('H2' * $1.size).join('%').upcase 218 | end 219 | end 220 | # Also borrowed from TextMate's library. 221 | # escape text to make it useable in a shell script as one “word” (string) 222 | # Do not escape slashes. 223 | def e_sh(str) 224 | str.to_s.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF])/, '\\') 225 | end 226 | # first escape for use in the shell, then escape for use in a JS string 227 | def e_js_sh(str) 228 | (e_sh str).gsub("\\", "\\\\\\\\") 229 | end 230 | -------------------------------------------------------------------------------- /Support/bin/gtdalt_ical_synchronization.applescript: -------------------------------------------------------------------------------- 1 | -- property parent : application "iCal" 2 | global the_contexts, the_calendars, the_todo_pairs, the_gtd_data 3 | set_the_contexts() 4 | set_the_calendars() 5 | -- display_calendar_names() 6 | set_the_gtd_data() 7 | -- return 8 | get_the_todo_pairs() 9 | process_existing_todos() 10 | -- display_gtd_data() 11 | -- return result 12 | set_the_gtd_data() -- Need to reread things to not have completed items show up again. 13 | get_the_todo_pairs() -- Need to reread things to not have completed items show up again. 14 | update_gtd_entries() 15 | -- return result 16 | return Logger 17 | property Logger : {} 18 | -- HANDLERS 19 | on update_gtd_entries() 20 | repeat with a_context in the_gtd_data 21 | repeat with an_action in actions of a_context 22 | if completed of an_action as boolean is false then 23 | set candidate to find_gtd_in_todos(contents of an_action) 24 | if candidate is "not found" then 25 | create_new_entry from an_action into (context of a_context) 26 | else 27 | update_new_entry for candidate from an_action 28 | end if 29 | end if 30 | end repeat 31 | end repeat 32 | end update_gtd_entries 33 | on create_new_entry from the_action_rec into the_context 34 | set end of Logger to "Creating new entry:" & action of the_action_rec & " in context:" & the_context 35 | tell application "iCal" 36 | set the_cal to get first calendar where name is ("GTDALT" & the_context) 37 | set the_todo to make todo at the end of the_cal 38 | set summary of the_todo to action of the_action_rec 39 | set temp_date to "not found" 40 | try 41 | set temp_date to (duedate of the_action_rec) 42 | end try 43 | if temp_date is not "not found" then 44 | set due date of the_todo to (my get_date(temp_date)) 45 | end if 46 | try 47 | set description of the_todo to (nte of the_action_rec) 48 | end try 49 | set stamp date of the_todo to current date 50 | set url of the_todo to (link of the_action_rec) 51 | end tell 52 | end create_new_entry 53 | on update_new_entry for todo_rec from the_action_rec 54 | -- set end of Logger to "Updating entry:" & (f_todo of todo_rec) & "with entry:" -- & (action of the_action_rec) 55 | tell application "iCal" 56 | set the_todo to f_todo of todo_rec 57 | set temp_date to "not found" 58 | try 59 | set temp_date to (duedate of the_action_rec) 60 | end try 61 | if temp_date is not "not found" then 62 | set due date of the_todo to (my get_date(temp_date)) 63 | end if 64 | try 65 | set description of the_todo to (nte of the_action_rec) 66 | end try 67 | set stamp date of the_todo to current date 68 | set url of the_todo to (link of the_action_rec) 69 | end tell 70 | end update_new_entry 71 | on find_gtd_in_todos(gtd_action) 72 | global the_contexts, the_calendars, the_todo_pairs, the_gtd_data 73 | local the_name 74 | set the_name to action of gtd_action 75 | tell application "iCal" 76 | repeat with todo_rec in the_todo_pairs 77 | if (summary of f_todo of todo_rec) = the_name then 78 | return todo_rec 79 | end if 80 | end repeat 81 | return "not found" 82 | end tell 83 | end find_gtd_in_todos 84 | on find_todo_in_gtd(todo_item) 85 | global the_contexts, the_calendars, the_todo_pairs, the_gtd_data 86 | local the_name 87 | tell application "iCal" 88 | set the_name to summary of todo_item 89 | repeat with context in the_gtd_data 90 | repeat with candidate in actions of context 91 | if contents of action of candidate is equal to the_name then 92 | return candidate 93 | end if 94 | end repeat 95 | end repeat 96 | return "not found" 97 | end tell 98 | end find_todo_in_gtd 99 | on process_existing_todos() 100 | global the_contexts, the_calendars, the_todo_pairs, the_gtd_data 101 | local todo_pair, suspect 102 | set suspect_list to {} 103 | -- tell application "iCal" 104 | -- display dialog "processing todos" 105 | -- end tell 106 | set the_command to do shell script "echo -n $TM_BUNDLE_SUPPORT" 107 | -- tell application "iCal" 108 | -- display dialog "path: " & the_command 109 | -- end tell 110 | repeat with todo_pair in the_todo_pairs 111 | set suspect to find_todo_in_gtd(contents of f_todo of todo_pair) 112 | if suspect is not "not found" then 113 | tell application "iCal" 114 | set the_date to completion date of f_todo of todo_pair 115 | set temp_test to false 116 | try 117 | the_date -- The try block will fail right here if there was no completion date 118 | set temp_test to true 119 | end try 120 | if temp_test is true then 121 | set todo_to_delete to f_todo of todo_pair 122 | -- display dialog "Will delete" & summary of todo_to_delete 123 | set the_string to "\"" & the_command & "/bin/mark_completed.rb\" \"" & (action of suspect) & "\" \"" & (file of suspect) & "\" " & (line of suspect) 124 | -- display dialog the_string 125 | set end of suspect_list to do shell script the_string 126 | -- return todo_to_delete 127 | delete todo_to_delete 128 | -- set end of suspect_list to todo_to_delete 129 | -- end try 130 | end if 131 | end tell 132 | else -- No suspect found, this means the todo is redundant. 133 | tell application "iCal" 134 | -- display dialog "About to delete something!" 135 | delete f_todo of todo_pair 136 | end tell 137 | end if 138 | end repeat 139 | return suspect_list 140 | end process_existing_todos 141 | on set_the_gtd_data() 142 | global the_gtd_data 143 | -- tell application "iCal" 144 | -- display dialog "reading data" 145 | -- end tell 146 | set test_string to (do shell script "\"$TM_BUNDLE_SUPPORT/bin/get_lists.rb\"" without altering line endings) 147 | -- set test_string to (do shell script "\"/Users/haris/Library/Application Support/TextMate/Bundles/GTDAlt.tmbundle/Support/bin/get_lists.rb\"" without altering line endings) 148 | -- tell application "iCal" 149 | -- display dialog test_string 150 | -- end tell 151 | set the_gtd_data to run script test_string 152 | -- tell application "iCal" 153 | -- display dialog "just read data" 154 | -- end tell 155 | end set_the_gtd_data 156 | on set_the_calendars() -- Reads calendars from iCal 157 | global the_contexts 158 | global the_calendars 159 | local temp_var, temp_name 160 | set the_calendars to {} 161 | tell application "iCal" 162 | repeat with C in the_contexts 163 | set temp_var to {} 164 | set temp_name to "GTDALT" & C 165 | set temp_var to get (calendars whose name is temp_name) 166 | if temp_var is {} then 167 | -- Need to create a new calendar 168 | set temp_var to make new calendar 169 | set name of temp_var to temp_name 170 | set temp_var to {temp_var} 171 | end if 172 | set end of the_calendars to item 1 of temp_var 173 | end repeat 174 | end tell 175 | end set_the_calendars 176 | 177 | on get_the_todo_pairs() -- Returns a list of records of a calendar and a todo contained in it. 178 | global the_todo_pairs, the_calendars 179 | local tod, cal 180 | set the_todo_pairs to {} 181 | tell application "iCal" 182 | repeat with cal in the_calendars 183 | set the_cal to contents of cal 184 | repeat with tod in todos of the_cal 185 | set the_tod to contents of tod 186 | set end of the_todo_pairs to {f_calendar:cal, f_todo:the_tod} 187 | end repeat 188 | end repeat 189 | end tell 190 | end get_the_todo_pairs 191 | 192 | on set_the_contexts() -- Returns a list of the contexts from the variable $TM_GTD_CONTEXT 193 | -- local cal_names 194 | global the_contexts 195 | set the text item delimiters to " " 196 | set the_contexts to (get text items of (do shell script "echo -n $TM_GTD_CONTEXT" without altering line endings)) 197 | end set_the_contexts 198 | 199 | on get_date(a_date) 200 | local d, elements 201 | set the text item delimiters to "-" 202 | set elements to text items of a_date 203 | set d to date (item 3 of elements) 204 | set month of d to (item 2 of elements) 205 | set year of d to (item 1 of elements) 206 | return d 207 | end get_date 208 | 209 | -- DEBUGGING HANDLERS 210 | on display_calendar_names() 211 | global the_calendars 212 | tell application "iCal" 213 | set the_list to "" 214 | repeat with the_cal in the_calendars 215 | set the_list to the_list & name of the_cal & " " 216 | end repeat 217 | display dialog the_list 218 | end tell 219 | end 220 | on display_gtd_data() 221 | global the_gtd_data 222 | set the_string to "" 223 | repeat with the_rec in the_gtd_data 224 | set the_string to the_string & "\nContext:" & context of the_rec 225 | repeat with the_act in (actions of the_rec) 226 | set the_string to the_string & "\n" & action of the_act 227 | end repeat 228 | end repeat 229 | tell app "iCal" 230 | display dialog the_string 231 | end tell 232 | end display_gtd_data -------------------------------------------------------------------------------- /Syntaxes/GTDalt.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | gtd 8 | gtdlog 9 | 10 | keyEquivalent 11 | ^~G 12 | name 13 | GTDalt 14 | patterns 15 | 16 | 17 | begin 18 | ^\s*(project)\s+(.*)(\n) 19 | beginCaptures 20 | 21 | 0 22 | 23 | name 24 | meta.line.project.begin.gtdalt 25 | 26 | 1 27 | 28 | name 29 | keyword.control.project.begin.gtdalt 30 | 31 | 2 32 | 33 | name 34 | entity.name.section.project.title.gtdalt 35 | 36 | 3 37 | 38 | name 39 | meta.project.newline.gtdalt 40 | 41 | 42 | end 43 | ^\s*(end)\s* 44 | endCaptures 45 | 46 | 0 47 | 48 | name 49 | meta.line.project.end.gtdalt 50 | 51 | 1 52 | 53 | name 54 | keyword.control.project.end.gtdalt 55 | 56 | 57 | name 58 | meta.project.begin.gtdalt 59 | patterns 60 | 61 | 62 | include 63 | $self 64 | 65 | 66 | 67 | 68 | captures 69 | 70 | 1 71 | 72 | name 73 | storage.type.context.action.gtdalt 74 | 75 | 2 76 | 77 | name 78 | punctuation.definition.context.action.gtdalt 79 | 80 | 81 | match 82 | ^\s*((@)\S++\n) 83 | name 84 | meta.action.only-context.gtdalt 85 | 86 | 87 | begin 88 | ^\s*((@)\S++\s) 89 | beginCaptures 90 | 91 | 1 92 | 93 | name 94 | storage.type.context.action.gtdalt 95 | 96 | 2 97 | 98 | name 99 | punctuation.definition.context.action.gtdalt 100 | 101 | 102 | end 103 | \n|$ 104 | name 105 | meta.action.gtdalt 106 | patterns 107 | 108 | 109 | include 110 | #note 111 | 112 | 113 | include 114 | #date 115 | 116 | 117 | include 118 | #title 119 | 120 | 121 | 122 | 123 | captures 124 | 125 | 0 126 | 127 | name 128 | comment.line.number-sign.action.completed.gtdalt 129 | 130 | 2 131 | 132 | name 133 | punctuation.definition.completed.gtdalt 134 | 135 | 3 136 | 137 | name 138 | punctuation.definition.completed.gtdalt 139 | 140 | 4 141 | 142 | name 143 | string.quoted.other.timestamp.action.completed.gtdalt 144 | 145 | 5 146 | 147 | name 148 | punctuation.definition.date.gtdalt 149 | 150 | 6 151 | 152 | name 153 | punctuation.definition.date.gtdalt 154 | 155 | 156 | match 157 | ^((#)completed(:))((\[)\d{4}-\d{2}-\d{2}(\]))\s*(.*) 158 | name 159 | meta.action.completed.gtdalt 160 | 161 | 162 | begin 163 | ^((\[)\d+(\])) 164 | beginCaptures 165 | 166 | 1 167 | 168 | name 169 | support.other.note.gtdalt 170 | 171 | 2 172 | 173 | name 174 | punctuation.definition.note.note.gtdalt 175 | 176 | 3 177 | 178 | name 179 | punctuation.definition.note.note.gtdalt 180 | 181 | 182 | end 183 | \n|$ 184 | name 185 | meta.note.gtdalt 186 | patterns 187 | 188 | 189 | include 190 | #link 191 | 192 | 193 | 194 | 195 | captures 196 | 197 | 1 198 | 199 | name 200 | punctuation.separator.archived.gtdalt 201 | 202 | 2 203 | 204 | name 205 | string.quoted.other.timestamp.action.archived.gtdalt 206 | 207 | 3 208 | 209 | name 210 | punctuation.separator.archived.gtdalt 211 | 212 | 4 213 | 214 | name 215 | support.other.project.action.archived.gtdalt 216 | 217 | 5 218 | 219 | name 220 | punctuation.separator.archived.gtdalt 221 | 222 | 6 223 | 224 | name 225 | storage.type.context.action.archived.gtdalt 226 | 227 | 7 228 | 229 | name 230 | comment.line.slash.action.archived.gtdalt 231 | 232 | 233 | match 234 | ^(\/)(\d{4}-\d{2}-\d{2})(\/)([^\/]+)(\/)(@\S+)\s++(.*)$ 235 | name 236 | meta.action.archived.gtdalt 237 | 238 | 239 | captures 240 | 241 | 1 242 | 243 | name 244 | punctuation.separator.archived.gtdalt 245 | 246 | 2 247 | 248 | name 249 | string.quoted.other.timestamp.project.archived.gtdalt 250 | 251 | 3 252 | 253 | name 254 | punctuation.separator.archived.gtdalt 255 | 256 | 4 257 | 258 | name 259 | support.other.project.archived.gtdalt 260 | 261 | 262 | match 263 | ^(\/)(\d{4}-\d{2}-\d{2})(\/)([^\/]+)$ 264 | name 265 | meta.project.archived.gtdalt 266 | 267 | 268 | begin 269 | ^(#)\s 270 | captures 271 | 272 | 1 273 | 274 | name 275 | punctuation.definition.comment.gtdalt 276 | 277 | 278 | end 279 | \n 280 | name 281 | comment.line.number-sign.generic.gtdalt 282 | 283 | 284 | repository 285 | 286 | date 287 | 288 | captures 289 | 290 | 1 291 | 292 | name 293 | keyword.operator.due.gtdalt 294 | 295 | 2 296 | 297 | name 298 | punctuation.separator.key-value.due.gtdalt 299 | 300 | 3 301 | 302 | name 303 | string.quoted.other.timestamp.due.gtdalt 304 | 305 | 4 306 | 307 | name 308 | punctuation.definition.due.gtdalt 309 | 310 | 5 311 | 312 | name 313 | punctuation.definition.due.gtdalt 314 | 315 | 316 | match 317 | ((?:due|at|from)(:))((\[)\d{4}-\d{2}-\d{2}(\])) 318 | 319 | link 320 | 321 | captures 322 | 323 | 1 324 | 325 | name 326 | punctuation.definition.link.gtdalt 327 | 328 | 2 329 | 330 | name 331 | markup.underline.link.gtdalt 332 | 333 | 3 334 | 335 | name 336 | punctuation.definition.link.gtdalt 337 | 338 | 339 | match 340 | (<)([^>]*)(>) 341 | 342 | note 343 | 344 | captures 345 | 346 | 1 347 | 348 | name 349 | punctuation.definition.note.gtdalt 350 | 351 | 2 352 | 353 | name 354 | punctuation.definition.note.gtdalt 355 | 356 | 357 | match 358 | (\[)\d+(\]) 359 | name 360 | support.other.note.gtdalt 361 | 362 | title 363 | 364 | match 365 | \S+(?:\s+\S+)*?(?=\s*(?:\[\d+\]|(?:due|at|from):|$)) 366 | name 367 | constant.other.title.gtdalt 368 | 369 | 370 | scopeName 371 | text.gtdalt 372 | uuid 373 | C36472BD-A8CD-4613-A595-CEFB052E6181 374 | 375 | 376 | -------------------------------------------------------------------------------- /Support/README.txt: -------------------------------------------------------------------------------- 1 | This is a general help file for the GTDAlt bundle, one of the *two* bundles for implementing [GTD](http://en.wikipedia.org/wiki/Gtd) in [TextMate](http://www.macromates.com). 2 | 3 | # Why two bundles? 4 | 5 | You might have noticed that there is a GTD bundle and a GTDAlt bundle. Why two bundles? Because different people implement GTD differently, and because variety never hurt anyone :). The two bundles were created by two different people, and they have very different workflows. So try them both and choose the one you prefer! 6 | 7 | --- 8 | You can also access this file from within the TextMate bundle. This file consists of three parts. The first part is an introduction to the bundle, describing how to use it. The second describes its internals, describing how to extend the bundle. The third is an example of creating a new command. 9 | 10 | # Using the GTDAlt bundle 11 | 12 | First of all, let's describe the GTD format. Here is a typical file: 13 | 14 | project World domination 15 | #completed:[2006-06-20] @office Assemble email address of world leaders 16 | @errand Create giant laser beam 17 | @email Threaten to destroy Barbados 18 | @work Take over world 19 | end 20 | @email Hello there 21 | project testing project 22 | project Some subproject 23 | @homework first action due:[2006-07-20] 24 | @email another action 25 | @email a third action 26 | end 27 | 28 | @home Hurray [1] due:[2006-07-04] 29 | @home second action [2] due:[2006-06-04] 30 | @errand a new one [3] 31 | @email a third action 32 | end 33 | [1] A note here 34 | [2] Another note 35 | [3] A third note 36 | 37 | As you can see it has a very simple minimalistic style. Projects start with a `project` line, and end with an `end` line, and they can be nested. Actions start with an at-sign, which is then followed by the context. Contexts can contain any non-space character. Then comes the name of the action, which is followed by two optional parts. The first one is a bracketed number, and signifies the presence of a note for the action. Notes are all at the end, and can contain links in brackets. The second optional part is a due date, in the format `due:[yyyy-mm-dd]`. 38 | 39 | --- 40 | 41 | **Updates:** Some things have changed a bit: 42 | 43 | 1. First, GTDAlt now allows `from:` and `at:` instead of `due:`, but for the time being doesn't yet do anything with them. 44 | 2. Entering a new context has changed slightly. First, it is imperative that you set up the variable `TM_GTD_CONTEXT` to hold a space-separated list of all the context you want to be using. Then, when you try to create a new command the context is selected, with a current value. Type the first few characters of the desired context you want to switch to, and press tab, and the context will be automatically completed. 45 | 3. When you want a new action with the same context as the previous action, press `shift-enter` instead of plain `enter`. 46 | 4. A lot of shortcuts have changed. Look in the bundle for details. 47 | 48 | --- 49 | Now let's describe the workflow for working with a gtd file: 50 | 51 | 1. You create a new project via a snippet with shortcut the exclamation point `!` (`shift-1`). 52 | 2. You create a new action by typing `shift-2` (`@`). This sets a default context (email) which can then be changed as described in the next steps. At this stage you only enter the name of the action. 53 | 3. There are three ways to change the context. You could of course just select it and type in. You can also press `ctrl-C` anywhere in the line, which changes the context to the next context in alphabetical order. Also `ctrl-opt-C` offers a pop-up of all contexts to pick from. These contexts are assembled by scanning all gtd files in the given directory, or by simply looking at the environment variable `TM_GTD_CONTEXT`, space-separated. 54 | 4. Pressing `ctrl-{` (`ctrl-shift=[`) adds a note. If a note is present, it takes you to the note for editing. The same keypress takes you back to the action. 55 | 5. Pressing `ctrl-?` (`ctrl-shift-/`) shows the note of the action in the current line as a tooltip. 56 | 6. Pressing `#` (`shift-3`) adds a due date to the current line's action. A popup shows up, asking you to enter a date. You can either enter a date in a standard format, or you can type something like “today”, “in 2 months“ or “next tuesday”. 57 | 7. Once a due date has be set, you can either completely change the date by the exact same keypress, which will allow you to enter a new date, or you can use one of a series of commands for adjusting the date: 58 | + `shift-,` (`<`) reduces the date by one day. 59 | + `shift-.` (`>`) increases the date by one day. 60 | + `ctrl-,` reduces the date by one week. 61 | + `ctrl-.` increases the date by one week. 62 | + `ctrl-shift-,` (`ctrl-<`) reduces the date by one month. 63 | + `ctrl-shift-.` (`ctrl->`) increases the date by one month. 64 | 8. You can mark a line as completed by pressing `cmd-/`. This marks it as the second line in the example above. Pressing it again unmarks it. These two also work with a whole selection of lines, even a selection crossing among various projects. 65 | 9. `opt-cmd-/` cleans up the current project, moving information to the file `GTD.gtdlog` in the current directory. This takes care of all completed actions, as well as completed tasks. 66 | 10. `ctrl-L` opens all links in the current line to the current browser. 67 | 11. `ctrl-shift-W` surrounds the current selection in a project. 68 | 12. Finally, pressing `%` (`shift-5`) asks you for a context, and then creates an HTML page with a list of all actions for that context, along with their due dates if any, and the projects they belong to. Each action and project is a link to its file location. Design improvement suggestions for this page are strongly encouraged. 69 | 13. `ctrl-opt-P` allows you to move to another project, by showing you a pop-up of all projects from all gtd files. Of course you could use the "Go To Symbol" pop-up (`cmd-shift-T`), but this only shows the projects in the current file. 70 | 71 | Finally, the commands in the bundle work by default with the current directory. You can ask them to work with another special directory by setting the variable `TM_GTD_DIRECTORY`. 72 | 73 | # The internals of the GTDAlt bundle 74 | 75 | First of all, notice that you can browse the RDoc documentation for the bundle under `Support/bin/doc/index.html`. Each command should start with the three lines: 76 | 77 | #!/usr/bin/env ruby 78 | require ENV['TM_BUNDLE_PATH']+"/lib/GTD.rb" 79 | include GTD 80 | 81 | This sets up the ground-work for working with the GTD module. There are a number of classes in the GTD module, let's look at them one at a time: 82 | 83 | ## GTDFile 84 | 85 | This is the basic class. It has a number of useful class methods, as well as instance methods. A new instance method is created with a command like: 86 | 87 | obj = GTDFile.new(filename) 88 | 89 | where `filename` is a path to a gtd file. For instance to load the current file you would use: 90 | 91 | obj = GTDFile.new(ENV['TM_FILEPATH']) 92 | 93 | This processes the current file and returns an object (instance of the GTDFile class). You can query this object for data on the gtd file. For instance: 94 | 95 | + `obj.projects` returns an array of projects (Project objects). 96 | + `obj.actions` returns an array of actions (Action objects). 97 | + `obj.notes` returns an array of notes (Note objects). 98 | + `obj.completed_actions` returns an array of completed actions (Action objects marked as completed). 99 | 100 | You can recover the text corresponding to file by using `obj.dump_object`. This can for instance be used to reproduce the text after the various completed actions have been removed. The call `obj.cleanup_projects` removes completed actions and projects (a project is completed when all its actions and subprojects are completed). It adds logging information to the class `MyLogger`, which you can recover using `MyLogger.dump`. Check the “Clean-up current file” command to see how this is used. Finally, `obj.actions_for_context(context)` returns all actions with a given context. All this does is: 101 | 102 | return self.actions.find_all {|a| a.context == context} 103 | 104 | There are a couple of class methods that allow work with an entire directory. 105 | 106 | + `self.process_instructions` processes all files in the current directory (or the one pointed to by `TM_GTD_DIRECTORY` or the one specified as an extra argument). It returns an array of objects, one for each gtd file in the directory. 107 | + `self.get_contexts` returns an array of all contexts discovered so far. 108 | + `self.add_contexts(contexts)` adds the contexts passed to it to the context list. 109 | + `self.actions_for_context(context)` returns all actions with given context from all files. 110 | + `self.gtd_files_in_directory` returns all files from the same directory as in `process_instructions`. 111 | 112 | ## The Linkable mix-in 113 | 114 | `Linkable` is a mix-in module that all of the various item classes, like Project and Action, have. It expects that class that includes it to respond to the instance methods `file`, `line` and optionally `name`, and it adds three methods: 115 | 116 | + `uri` returns the file uri scheme of the string returned by `file`. It expects the full path to the file. 117 | + `txmt` returns a txmt uri scheme of the file described by the string `file` and the line number `line`. 118 | + `link` returns html code for an anchor link with the link described by `txmt` and link name described by `name`. 119 | 120 | ## The Project, Action etc classes 121 | 122 | These are the classes representing the various items in the file. They have some common instance methods, namely the `file`, `line`, `name` methods, which return the expected things. 123 | 124 | Project has a couple of extra obvious instance methods, namely: `subitems` returning the array of subitems, `parent` returning the parent project, or nil if there isn't one, and `end_line` returning the line number where the project's “end” was located. Finally, `completed?` returns whether the Project is completed or not. 125 | 126 | Action has some more interesting methods, namely: 127 | 128 | + `due` returns the string of the due date. 129 | + `project` returns the project the action belongs to, or nil if there isn't one. 130 | + `note` returns the note text, or an empty string if there isn't one. 131 | + `completed` and `completed?` return whether the action is completed. 132 | + `context` returns the string of the context of the current string. 133 | 134 | There are two more item classes, Comment and EndMarker, doing some obvious things. 135 | 136 | ## The Printer class 137 | 138 | There is one final class of interest, the Printer class. It is used to generate tabular output out of data provide. It works similarly to the amazing Builder class, it's like a very-poor-man's Builder. First create an instance: 139 | 140 | pr = Printer.new 141 | 142 | Then you need to add data to it. Here's an example, straight from the “Actions for context” command: 143 | 144 | pr.table do 145 | pr.title("Actions for context: #{context}") 146 | pr.headers(["Action name","Project","Due_by"]) 147 | actions.each do |a| 148 | proj = if a.project != nil then a.project.link else "none" end 149 | due = case a.due 150 | when "",nil 151 | "" 152 | when DateLate 153 | "#{a.due}" 154 | else 155 | a.due 156 | end 157 | pr.row([a.link,proj,due]) 158 | end 159 | return pr.to_html 160 | 161 | Note the class `DateLate`. It simply responds to: `DateLate === date`, where date is either a Date object or a date expressed as a string. It returns `true` if the date is earlier than `Date.today`. The last command, `pr.to_html`, is the one producing html output. `Printer` instances also respond to the method `raw`, which allows you to just add raw html code (e.g. style) directly. 162 | 163 | # An Example 164 | 165 | Let's create a command that generates an html file with all actions, sorted by their due date breaking ties alphabetically, along with links to the corresponding files, and tooltips showing the notes for those actions that have notes. First, we need to process the directory: 166 | 167 | #!/usr/bin/env ruby 168 | require ENV['TM_BUNDLE_PATH']+"/lib/GTD.rb" 169 | include GTD 170 | objects = GTDFile.process_directory 171 | 172 | Then we extract all actions: 173 | 174 | acts = objects.map{|o| o.actions}.flatten 175 | 176 | Then we sort them: 177 | 178 | acts.sort! do |a,b| 179 | da, db = *[a.due, b.due].map {|i| i || ""} # convert nils to empty strings 180 | if da == db then 181 | a.name <=> b.name 182 | else 183 | da <=> db 184 | end 185 | end 186 | 187 | Now we create the printer object: 188 | 189 | pr = Printer.new 190 | pr.table do 191 | pr.title "List of all Actions" 192 | pr.headers(["Action", "Context", "Due by"]) 193 | acts.each do |a| 194 | due = case a.due 195 | when "",nil 196 | "" 197 | when DateLate 198 | "#{a.due}" 199 | else 200 | a.due 201 | end 202 | note_part = (a.note != "") ? " title=\"#{a.note}\"" : "" 203 | text = "#{a.name}" 204 | pr.row([text, a.context, due]) 205 | end 206 | end 207 | 208 | Finally, we print the resulting object: 209 | 210 | puts pr.to_html 211 | 212 | This it it, we just created a new command! You'll find it in the bundle, under the key-equivalent `ctrl-%` (`ctrl-shift-5`). 213 | 214 | 215 | That's it for now. Enjoy! 216 | 217 | Later -------------------------------------------------------------------------------- /Support/lib/GTD.rb: -------------------------------------------------------------------------------- 1 | # 2 | # GTD.rb library for "getting things done". 3 | # 4 | # Author:: Charilaos Skiadas 5 | # Version:: 0.1 6 | require File.join(File.dirname(__FILE__),"GTDUtils.rb") 7 | require 'pp' 8 | require 'pathname' 9 | # require 'dir' 10 | # Module GTD. Used as a name-space for all other classes and methods. 11 | module GTD 12 | #-- 13 | # include GTDLight 14 | PROJECT_BEGIN_REGEXP = /^\s*(project)\s*(.*)$/ 15 | PROJECT_END_REGEXP = /^\s*(end)\s*$/ 16 | ACTION_REGEXP = /^\s*@(\S+)\s+((?:[^\[]+)(?:\s*\[\d+\])?)\s*((?:due|from|at):\[(\d{4}-\d{2}-\d{2})\])?\s*$/ 17 | NOTE_REGEXP = /^\[(\d+)\]\s+(.*)$/ 18 | COMPLETED_REGEXP = /^#completed:\[(\d{4}-\d{2}-\d{2})\]\s*@(\S+)\s+(([^\[]+)(?:\s*\[\d+\])?)(?:\s+(?:(?:due|from|at):\[\d{4}-\d{2}-\d{2}\]))?\s*$/ 19 | COMMENT_REGEXP = /^(\s*#.*)$/ 20 | #++ 21 | def GTD::parse(data) 22 | instructions = [] 23 | d = data.split("\n") 24 | until d.empty? 25 | l = d.shift.chomp 26 | case l 27 | when PROJECT_BEGIN_REGEXP 28 | instructions << [:project,$2,nil,nil] 29 | when PROJECT_END_REGEXP 30 | instructions << [:end,nil,nil,nil] 31 | when ACTION_REGEXP 32 | instructions << [:action,$2,$1,$3] 33 | when COMPLETED_REGEXP 34 | instructions << [:completed,$3,$2,$1] 35 | when NOTE_REGEXP 36 | instructions << [:note,$2,$1,nil] 37 | when COMMENT_REGEXP 38 | instructions << [:comment,$1,nil,nil] 39 | when /^(\s*)$/ 40 | instructions << [:comment,$1,nil,nil] 41 | # else raise "Parse error on line: #{l}.\n This is not a line I recognize." 42 | end 43 | end 44 | return instructions 45 | end 46 | 47 | class << self 48 | @@objects = Array.new 49 | @@files = Array.new 50 | def objects 51 | @@objects 52 | end 53 | def clear_objects 54 | # pp "here" 55 | @@objects = [] 56 | end 57 | def add_object(object) 58 | @@objects << object 59 | end 60 | # Returns an array of all gtd files in given the directory, or in ENV['TM_GTD_DIRECTORY'] if 61 | # that is nil, or in the default directory otherwise. 62 | def get_gtd_directory(directory = nil) 63 | if directory then 64 | directory 65 | elsif (ENV['TM_GTD_DIRECTORY'] || "").to_s != "" 66 | ENV['TM_GTD_DIRECTORY'].to_s 67 | else 68 | Pathname.new(`pwd`.chomp) 69 | end 70 | end 71 | def gtd_files_in_directory(directory = nil) 72 | path = get_gtd_directory(directory) 73 | return Dir::glob(File.join(File.expand_path(path),"*.gtd")) 74 | end 75 | # Reads all files in given directory and processes them. Returns an array of 76 | # GTDFile objects, one for each file. 77 | def process_directory(directory = nil) 78 | files = gtd_files_in_directory(directory) 79 | for filename in files do 80 | @@objects << GTDFile.new(filename) 81 | end 82 | @@objects 83 | end 84 | def all_lines 85 | @actions + @projects + @other_lines + @completed_actions 86 | end 87 | # Returns the actions for a particular context from among all objects. 88 | def actions_for_context(context) 89 | return @@objects.map{|i| i.flatten.find_all{|action| Action === action && action.context == context}}.flatten 90 | end 91 | # Returns all next actions from all projects 92 | def next_actions 93 | return @@objects.map { |i| i.next_actions}.flatten.compact 94 | end 95 | def actions 96 | return @@objects.map{|i| i.flatten.find_all{|a| Action === a}}.flatten 97 | end 98 | def projects 99 | return @@objects.map { |i| i.projects }.flatten 100 | end 101 | 102 | end 103 | 104 | # Module for updating container objects (Project, GTDFile) 105 | # that have underlying file structure. 106 | module Container 107 | attr_accessor :subitems, :name, :file, :line 108 | include Linkable 109 | def update!(update_info = nil) 110 | if update_info then 111 | self.file = update_info[:file] 112 | self.line = update_info[:line] 113 | else 114 | update_info = {:file => self.file, :line => self.line} 115 | end 116 | if self.line == nil 117 | self.subitems.each { |item| item.update!(update_info) } 118 | return nil 119 | else 120 | cur_line = self.line + 1 121 | for item in self.subitems do 122 | cur_line = item.update!(update_info.update(:line => cur_line)) 123 | end 124 | return cur_line + (GTDFile === self ? 0 : 1) 125 | end 126 | end 127 | def flatten 128 | self.subitems.map{|i| i.flatten}.flatten 129 | end 130 | def <<(object) 131 | self.subitems << object 132 | end 133 | def next_action 134 | self.subitems.find{|i| Action === i} 135 | end 136 | def next_actions 137 | self.flatten.map{|i| i.next_action}.compact 138 | end 139 | def projects 140 | self.flatten.find_all{|i| Project === i} 141 | end 142 | def actions 143 | self.flatten.find_all{|i| Action === i && !i.completed?} 144 | end 145 | def completed_actions 146 | self.flatten.find_all{|i| Action === i && i.completed?} 147 | end 148 | def flatten 149 | self.subitems.map{|i| i.flatten}.flatten.unshift(self) 150 | end 151 | def add_item(item) 152 | self.subitems << item 153 | item.parent = self 154 | end 155 | def remove_item(item) 156 | self.subitems.delete item 157 | end 158 | def push(item) 159 | add_item(item) 160 | end 161 | def pop 162 | self.subitems.pop 163 | end 164 | def unshift(item) 165 | self.subitems.unshift(item) 166 | item.parent = self 167 | end 168 | def shift 169 | self.subitems.shift 170 | end 171 | # Returns the string representing the object. 172 | def dump_object(indent = "", indent_inc = nil, notes = []) 173 | unless ENV['TM_SOFT_TABS'] == "YES" then 174 | indent_inc = "\t" 175 | else 176 | indent_inc = (" " * ENV['TM_TAB_SIZE'].to_i) 177 | end unless indent_inc 178 | s = [] 179 | case self 180 | when Project 181 | s << indent + "project #{self.name}" 182 | for obj in subitems do 183 | s << obj.dump_object(indent + indent_inc,indent_inc,notes) 184 | end 185 | s << indent + "end" 186 | when Comment 187 | s << self.name 188 | when GTDFile 189 | for obj in subitems do 190 | s << obj.dump_object(indent,indent_inc,notes) 191 | end 192 | notes.each_with_index { |note, index| s<<"[#{index+1}] #{note}" } 193 | end 194 | return s.join("\n") 195 | end 196 | end 197 | 198 | # A class managing +gtd+ files. 199 | class GTDFile 200 | include Container 201 | attr_reader :notes, :other_lines, :subitems, :file, :line 202 | # Loads the data in filename and creates an object representing all projects/actions. 203 | # It can be provided by the data string instead. 204 | def initialize(filename) 205 | @subitems = Array.new 206 | @current_project = self 207 | @line = 0 208 | @notes = Hash.new 209 | if filename =~/gtd\z/ # Case where we were fed a filename 210 | @current_file = filename 211 | @file = @current_file 212 | if File.exist?(filename) then 213 | f = File.open(filename, "r") 214 | instructions = GTD::parse(f.read) 215 | process_instructions(instructions) 216 | f.close 217 | else 218 | File.open(filename, "a"){|f|} 219 | end 220 | else # Case where we were fed a string of data 221 | @file = nil 222 | @current_file = nil 223 | instructions = GTD::parse(filename) 224 | process_instructions(instructions) 225 | end 226 | end 227 | def name 228 | self.file.split("/").last 229 | end 230 | def root 231 | self 232 | end 233 | def process_instructions(instructions) 234 | until instructions.empty? 235 | code, name, context, due = instructions.shift 236 | case code 237 | when :project 238 | proj = Project.new(:name => name,:parent => @current_project) 239 | @current_project << proj 240 | @current_project = proj 241 | when :end 242 | raise "'end' without corresponding 'project'. Line:#{@current_line} of file #{@current_file}." if @current_project == self 243 | @current_project = @current_project.parent 244 | when :action 245 | name =~ /^\s*(\S.*?\S)\s*(?:\[(\d+)\])?$/ 246 | thename, noteid = $1, $2 247 | if due =~ /^(due|at|from):\[(\d{4}\-\d{2}\-\d{2})\]/ then 248 | due_type, date = $1, $2 249 | else 250 | due_type, date = nil, nil 251 | end 252 | act = Action.new(:name => thename, 253 | :context => context, 254 | :parent => @current_project, 255 | :due => date, :due_type => due_type) 256 | if noteid != "" then 257 | act.note = noteid 258 | @notes[noteid] = act 259 | end 260 | GTDContexts.contexts << context 261 | @current_project << act 262 | when :note 263 | act = @notes[context] 264 | act.note = name unless act==nil 265 | when :completed 266 | name =~ /^\s*(\S.*?\S)\s*(?:\[(\d+)\])?$/ 267 | thename, noteid = $1, $2 268 | act = Action.new(:name => thename, :context => context,:parent => @current_project,:due => due, :completed => true) 269 | if noteid != "" then 270 | act.note = noteid 271 | @notes[noteid] = act 272 | end 273 | @current_project << act 274 | else 275 | cmt = Comment.new(:name => name, :parent => @current_project) 276 | @current_project << cmt 277 | end 278 | end 279 | raise "'project' without 'end'." if @current_project != self 280 | update! 281 | end 282 | # Removes completed projects and actions and returns an array of references to them. 283 | # It also deletes all actions that have been marked as completed. You can call the 284 | # MyLogger class, and MyLogger.dump it. 285 | def cleanup_projects 286 | c_actions = self.completed_actions 287 | until c_actions.empty? do 288 | a = c_actions.shift 289 | note = (a.note.nil? || ENV['TM_ARCHIVE_NOTES'].nil?) ? "" : "\n Note: #{a.note}" 290 | MyLogger.log "/#{a.due}/#{a.parent.name}/@#{a.context} #{a.name}#{note}" 291 | a.parent.remove_item(a) 292 | end 293 | 294 | completed_projects = self.projects.find_all {|i| i.completed?} 295 | completed_projects.each do |project| 296 | # pp project 297 | MyLogger.log "/#{Date.today}/#{project.name}" 298 | project.parent.remove_item(project) 299 | project.root.update! 300 | end 301 | end 302 | end 303 | # Thin class to incorporate the idea of an *item* in a file/project etc. 304 | # For a project, this is the line it starts at. For anything else, it is the 305 | # line it is at. 306 | class GTDItem 307 | attr_accessor :parent, :file, :line, :name, :completed 308 | def initialize(hash) 309 | @parent = hash[:parent] 310 | @name = hash[:name] 311 | @completed = true if @completed == nil 312 | self.update!(hash) 313 | end 314 | def update!(update_info) 315 | @file = update_info[:file] 316 | @line = update_info[:line] 317 | if @line then 318 | return @line + 1 319 | else 320 | return @line 321 | end 322 | end 323 | def completed? 324 | completed 325 | end 326 | def root 327 | parent.root 328 | end 329 | def flatten 330 | [self] 331 | end 332 | def next_action 333 | return nil 334 | end 335 | def dump_object(indent = "",inc_indent = " ",notes = []) 336 | return indent + self.name 337 | end 338 | end 339 | 340 | class Project < GTDItem 341 | include Container 342 | # Contains both subprojects and independent actions 343 | attr_accessor :subitems 344 | # attr_accessor :end_line # this doesn't seem necessary on hindsight 345 | def initialize(hash) 346 | @subitems = [] 347 | super 348 | end 349 | def to_s 350 | name 351 | end 352 | def completed? 353 | for i in @subitems do 354 | return false unless i.completed? 355 | end 356 | return false if name =~ /^!/ 357 | return true 358 | end 359 | def next_action 360 | return @subitems.find { |e| Action === e && !e.completed? } 361 | end 362 | # Updates its subitems. Returns its end line plus 1. 363 | end 364 | 365 | class Action < GTDItem 366 | include Linkable 367 | # If the action is completed, then its completion date. Otherwise its due date or +nil+. 368 | attr_accessor :due 369 | # The type of the date. Should be "due", "at" or "from". 370 | attr_accessor :due_type 371 | # The text in the note, not the actual Note object. Empty if no note. 372 | attr_accessor :note 373 | attr_accessor :context, :completed 374 | def initialize(hash) 375 | @due = hash[:due] 376 | @due_type = hash[:due_type] 377 | @context = hash[:context] 378 | @note = hash[:note] 379 | @completed = hash[:completed] || false 380 | super 381 | end 382 | def to_s 383 | name + "from project: " + project.to_s + " @" + context 384 | end 385 | def project 386 | parent 387 | end 388 | def completed? 389 | completed 390 | end 391 | # Whether the action is the next action or not. It should either have no parent, 392 | # or it should be its parent's first action subitem. 393 | def is_next_action? 394 | self == self.project.next_action 395 | end 396 | def dump_object(indent = "",inc_indent = " ", notes = []) 397 | if self.completed? then 398 | return "\#completed:[#{self.due}]" + indent + "@#{self.context} #{self.name}" 399 | else 400 | notes << self.note if self.note 401 | return indent + "@#{self.context} #{self.name}" + 402 | (self.note ? " [#{notes.length}]" : "") + 403 | (self.due ? " #{due_type}:[#{self.due}]" : "") 404 | end 405 | end 406 | end 407 | class Comment < GTDItem 408 | end 409 | # class EndMarker < GTDItem 410 | # end 411 | end 412 | --------------------------------------------------------------------------------