otask 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +83 -0
  3. data/bin/otask +267 -0
  4. data/lib/otask.rb +3 -0
  5. metadata +165 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 72e2f8c4d11c95a93aa27235eb107d48f8311238
4
+ data.tar.gz: 3b98b4d521835a01861347136ffc6d4f74ae82b7
5
+ SHA512:
6
+ metadata.gz: 6077b721ef8255456a790852a3f1392bc73f53958f17fc7c58ad00a53aa41ae561917aa3e5216f6f37c83a8b21faaf0950e49de53ed577b526cbd2b207fc9b8c
7
+ data.tar.gz: 71889ff718473bec68d0b8e8d57e6bf59d50f5d78e233402527fdcf63bb14f76c8f3ea72bfce325e544ffb59d179fb11cce6e219b543eee5ea2690b3ff0e8bbc
@@ -0,0 +1,83 @@
1
+ [appscript]: http://appscript.sourceforge.net/
2
+
3
+ This is a CLI for OmniFocus. I had an AppleScript/Ruby monstrosity that actually worked with TaskPaper, The Hit List, Things and OmniFocus, but that one got out of hand. I took the good parts of it, concentrated on OmniFocus and converted it to [appscript][]. The result is OTask.
4
+
5
+ I don't know how long appscript is going to work for us, and it's now a dead project. I make no promises. This version of oTask has been tested on Ruby 2.0 (Mavericks) and with OmniFocus and OmniFocus 2.
6
+
7
+ ### Installation
8
+
9
+ $ [sudo] gem install otask
10
+
11
+ ### Documentation
12
+
13
+ OTask uses a custom syntax to allow entry of the various elements of an action in one line of text. The following formats can be used anywhere in the line, with the exception of the flag (!) which must be the last character on the line, preceded by a space.
14
+
15
+ * @context (fragment, no spaces)
16
+ * \#project (fragment, no spaces)
17
+ * due(due date) (can be shortened as d(date))
18
+ * create(creation date) (can be shortened as c(date))
19
+ * (notes)
20
+ * ! (sets task as flagged)
21
+
22
+ Contexts and project specifiers should not include spaces. The algorithm that is used will find the best match for the string you give it, so you only need to include enough of it to distinguish it from other contexts or projects. For example, if I were going to put an action directly into my Markdown QuickTags folder, I could just use "#mdqt" and it will find it. "@corr" will get me the "correspondence" context.
23
+
24
+ Dates are entered in natural language format. You can type "tomorrow," "in 3 days," "next tuesday," etc. You can also use "+3" to set a date 3 days from the current day, "+7" for a week, and so on.
25
+
26
+ ### Command line options
27
+
28
+ -h, --help Displays help message
29
+ -q, --quiet Output as little as possible, overrides verbose
30
+ -V, --verbose Verbose output
31
+ -g, --growl Use Growl for feedback
32
+
33
+ ### Example usage
34
+
35
+ $ otask "Write a letter to mom"
36
+
37
+ This will put a task into your inbox with the name "Write a letter to mom." Nothing else will be set, it will wait there for you to pick it up.
38
+
39
+ $ otask -g "Pick up the kids from school @err #single due(today 3pm) !"
40
+
41
+ This creates a new task in a project called Single Tasks, with a context of "errands", a due date of 3pm on the current day, and flags the task.
42
+
43
+ The task will go to your inbox by default, and--if provided--project and context will be set. Your settings for automatic cleanup will determine what happens after that. Task elements not specified are left unset.
44
+
45
+ The `-g` parameter gives us our feedback via Growl, which is handy if you're calling it from a background script or application launcher like Quicksilver or LaunchBar.
46
+
47
+ $ otask "Brainstorm for the morning meeting (Bill had some ideas, it might be worth checking in with him this afternoon) d(tomorrow 8am) #hipstartup @think"
48
+
49
+ This will create a task with a note. Everything in parenthesis is removed from the task name and placed into the notes of the action, sans parenthesis. Note that the due date prefix can be shortened to just "d".
50
+
51
+ OTask looks for notes in parenthesis, but it can also receive piped input from other applications as a note for the task. If you wanted to include text from a file, the output of a command or the plain-text contents of your clipboard, you can just pipe the output into the command, specifying the rest of the options as usual.
52
+
53
+ $ pbpaste | otask "Notes from the morning meeting @ref"
54
+
55
+ That would take the current contents of your clipboard and make them the attached note on the "Notes from the morning meeting" task (with the context "reference").
56
+
57
+ ### Calling from LaunchBar (et al.)
58
+
59
+ You can do this with any app that can run a script with input, or call it from automated scripts if you could think of a reason to. Below is the AppleScript for a LaunchBar action. Create a new script in AppleScript Editor and paste the code in. Edit the path in the last function to point to wherever you put the otask script. Save the AppleScript as OTask.scpt in `~/Library/Application Support/LaunchBar/Actions`.
60
+
61
+ You'll find the Action in LaunchBar after it indexes. Type 'ota' (or as much as you need to get it to come up) and then press space bar. Use the syntax shown above to write out your action and its elements, but leave out the 'otask' part and any parameters. Hit return and Growl (you have it [installed, right?](http://growl.info)) will tell you what's up.
62
+
63
+ on handle_string(actionString)
64
+ if (length of actionString is not 0) then
65
+ my runRubyScript(actionString)
66
+ end if
67
+ open location "x-launchbar:hide"
68
+ end handle_string
69
+
70
+ on runRubyScript(action)
71
+ do shell script "/usr/bin/otask -g \"" & action & "\""
72
+ end runRubyScript
73
+
74
+
75
+ ### Author
76
+
77
+ Brett Terpstra
78
+
79
+ ### Copyright
80
+
81
+ Copyright (c) 2011 Brett Terpstra. Licensed under the MIT License:
82
+
83
+ <http://www.opensource.org/licenses/mit-license.php>
@@ -0,0 +1,267 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # == Synopsis
4
+ # OTask provides CLI functionality for adding tasks to OmniFocus.
5
+ #
6
+ # == Usage
7
+ # otask [options] "Task string"
8
+ #
9
+ # For help use: otask -h
10
+ #
11
+ # == Options
12
+ # -h, --help Displays help message
13
+ # -q, --quiet Output as little as possible, overrides verbose
14
+ # -V, --verbose Verbose output
15
+ # -g, --growl Use Growl for feedback
16
+ #
17
+ # == Author
18
+ # Brett Terpstra
19
+ #
20
+ # == Copyright
21
+ # Copyright (c) 2014 Brett Terpstra. Licensed under the MIT License:
22
+ # http://www.opensource.org/licenses/mit-license.php
23
+
24
+
25
+ require 'optparse'
26
+ require 'ostruct'
27
+ require 'date'
28
+ require 'appscript';include Appscript
29
+ require 'amatch';include Amatch
30
+ require 'chronic'
31
+ require 'rdoc'
32
+
33
+ class OTask
34
+ attr_reader :options
35
+
36
+ def initialize(arguments, stdin)
37
+ @arguments = arguments
38
+ @stdin = stdin
39
+
40
+ # Set defaults
41
+ @options = OpenStruct.new
42
+ @options.verbose = false
43
+ @options.quiet = false
44
+
45
+ # TO DO - add additional defaults
46
+ end
47
+
48
+ # Parse options, check arguments, then process the command
49
+ def run
50
+ if parsed_options? && !arguments_valid?
51
+ usage_and_exit
52
+ end
53
+
54
+ if parsed_options? && arguments_valid?
55
+ if @options.verbose
56
+ puts "Start at #{DateTime.now}\n\n"
57
+ puts "--Options--------------------"
58
+ output_options if @options.verbose # [Optional]
59
+ puts "--Task-Properties------------"
60
+ end
61
+ process_arguments
62
+ process_command
63
+
64
+ puts "\nFinished at #{DateTime.now}" if @options.verbose
65
+ end
66
+ end
67
+
68
+ protected
69
+
70
+ def parsed_options?
71
+
72
+ # Specify options
73
+ opts = OptionParser.new
74
+ opts.on( '-h', '--help', 'Display this screen' ) do
75
+ puts opts
76
+ puts "\nNotes are entered in parenthesis: This is my title (This is the note)"
77
+ exit(0)
78
+ end
79
+ opts.on('-V', '--verbose', 'Verbose logging') { @options.verbose = true }
80
+ opts.on('-q', '--quiet', 'Run silently') { @options.quiet = true }
81
+ opts.on('-g', '--growl', 'Use Growl notifications') { @options.growl = true }
82
+ # TO DO - add additional options
83
+
84
+ opts.parse!(@arguments) rescue return false
85
+
86
+ process_options
87
+ true
88
+ end
89
+
90
+ # Performs post-parse processing on options
91
+ def process_options
92
+ @options.verbose = false if @options.quiet
93
+ if @options.growl
94
+ procs = app("System Events").processes.bundle_identifier.get(:result_type => :list)
95
+
96
+ if procs.include? "com.Growl.GrowlHelperApp"
97
+ app("Growl").register(:as_application => "OTask", :all_notifications => ['Alert'], :default_notifications => ['Alert'], :icon_of_application => "OmniFocus")
98
+ else
99
+ @options.growl = false
100
+ end
101
+ end
102
+ end
103
+
104
+ def output_options
105
+ @options.marshal_dump.each do |name, val|
106
+ puts " #{name}: #{val}"
107
+ end
108
+ end
109
+
110
+ # True if required arguments were provided
111
+ def arguments_valid?
112
+ # TO DO - implement your real logic here
113
+ @arguments.length == 1 ? true : false
114
+ end
115
+
116
+ # Setup the arguments
117
+ def process_arguments
118
+ input = @stdin.stat.size > 0 ? @stdin.read : nil
119
+ titlestring = @arguments.join(' ')
120
+
121
+ # check for trailing ! (flagged task)
122
+ @options.flagged = titlestring.match(/ !\Z/).nil? ? false : true
123
+ titlestring.sub!(/ !\Z/,'') if @options.flagged
124
+
125
+ # check for #project in the string
126
+ projmatch = titlestring.match(/ ?#([a-z0-9]+)/i)
127
+ @options.project = projmatch.nil? ? nil : projmatch[1]
128
+ titlestring.sub!(/ ?##{projmatch[1]}/,'') unless projmatch.nil?
129
+
130
+ # check for @contexts
131
+ contextmatch = titlestring.match(/ @([^ ]+)/)
132
+ @options.context = contextmatch.nil? ? false : contextmatch[1]
133
+ titlestring.sub!(/ @[^ ]+/,'') unless contextmatch.nil?
134
+
135
+ # start/due date
136
+ # startmatch = titlestring.match(/ s(?:tart)?\(([^\)]+)\)/)
137
+ # @options.start = startmatch.nil? ? false : startmatch[1]
138
+ # titlestring.sub!(/ s(tart)?\(([^\)]+)\)/,'') unless startmatch.nil?
139
+ duematch = titlestring.match(/ d(?:ue)?\(([^\)]+)\)/)
140
+ @options.due = duematch.nil? ? false : duematch[1]
141
+ titlestring.sub!(/ d(ue)?\(([^\)]+)\)/,'') unless duematch.nil?
142
+
143
+ # creation date
144
+ creationmatch = titlestring.match(/ c(?:reate)?\(([^\)]+)\)/)
145
+ @options.creation = creationmatch.nil? ? false : creationmatch[1]
146
+ titlestring.sub!(/ c(reate)?\(([^\)]+)\)/,'') unless creationmatch.nil?
147
+
148
+
149
+
150
+ @options.notes = ''
151
+ if titlestring =~ / \(([^\)]+)\)/
152
+ @options.notes = $1
153
+ titlestring.sub!(/ \(([^\)]+)\)/,'')
154
+ end
155
+ if @options.notes == '' && !input.nil? # check for piped input on STDIN
156
+ @options.notes = input
157
+ elsif @options.notes && input
158
+ @options.notes = @options.notes + "\n\n" + input
159
+ end
160
+ @options.name = titlestring
161
+ end
162
+
163
+ def usage_and_exit
164
+ puts 'Usage: otask [options] "Task string [#proj @context (note) start() due()]"'
165
+ puts
166
+ puts "For help use: otask -h"
167
+
168
+ exit(0)
169
+ end
170
+
171
+ def output_version
172
+ puts "#{File.basename(__FILE__)} version #{VERSION}"
173
+ end
174
+
175
+ def add_task(dd, props)
176
+
177
+ if props['project']
178
+ proj_name = props["project"]
179
+ proj = dd.flattened_tasks[proj_name]
180
+ end
181
+ if props['context']
182
+ ctx_name = props["context"]
183
+ ctx = dd.flattened_contexts[ctx_name]
184
+ end
185
+
186
+ tprops = props.inject({}) do |h, (k, v)|
187
+ h[:"#{k}"] = v
188
+ h
189
+ end
190
+
191
+ tprops.delete(:project)
192
+ tprops[:context] = ctx if props['context']
193
+
194
+ t = dd.make(:new => :inbox_task, :with_properties => tprops)
195
+ t.assigned_container.set(proj) if props['project']
196
+
197
+ return true
198
+ end
199
+
200
+ def best_match(items,fragment)
201
+
202
+ highscore = {'score'=>0,'name'=>nil}
203
+ items.each {|item|
204
+ if fragment && !item.nil?
205
+ score = (Jaro.new(item).match(fragment) * 10).to_i
206
+ highscore = score > highscore['score'] ? {'score'=>score,'name'=>item} : highscore
207
+ end
208
+ }
209
+ return highscore['name']
210
+
211
+ end
212
+
213
+ def parse_date(datestring)
214
+ days = 0
215
+ if datestring =~ /^\+(\d+)$/
216
+ days = (60 * 60 * 24 * $1.to_i)
217
+ newdate = Time.now + days
218
+ else
219
+ newdate = Chronic.parse(datestring, {:context => :future, :ambiguous_time_range => 8})
220
+ end
221
+ # parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
222
+ # return parsed =~ /1969/ ? false : parsed
223
+ return newdate
224
+ end
225
+
226
+ def growl(title, message)
227
+ app("Growl").notify(:title => title, :description => message, :application_name => "OTask", :with_name => "Alert") if @options.growl
228
+ end
229
+
230
+ def process_command
231
+ of = app('OmniFocus')
232
+ dd = of.default_document
233
+
234
+ @props = {}
235
+ @props['name'] = @options.name
236
+ if @options.project
237
+ projs = dd.flattened_projects.name.get
238
+ @props['project'] = best_match(projs,@options.project)
239
+ end
240
+ if @options.context
241
+ ctxs = dd.flattened_contexts.name.get
242
+ @props['context'] = best_match(ctxs,@options.context)
243
+ end
244
+ # @props['start_date'] = parse_date(@options.start) if @options.start
245
+ @props['due_date'] = parse_date(@options.due) if @options.due
246
+ @props['creation_date'] = parse_date(@options.creation) if @options.creation
247
+ @props['note'] = @options.notes unless @options.notes == ''
248
+ @props['flagged'] = @options.flagged
249
+ add_task(dd, @props)
250
+ unless @options.quiet
251
+ o = "Task added to "
252
+ o += @props['project'].nil? ? "Inbox" : @props['project']
253
+ o += ", context "+@props['context']+"." unless @props['context'].nil?
254
+ if @options.growl
255
+ growl("Task created",o)
256
+ else
257
+ puts o
258
+ end
259
+ end
260
+ @props.each do |name, val|
261
+ puts " #{name}: #{val}"
262
+ end if @options.verbose
263
+ end
264
+ end
265
+
266
+ app = OTask.new(ARGV, STDIN)
267
+ app.run
@@ -0,0 +1,3 @@
1
+ class Otask
2
+ VERSION = '0.2.2'
3
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: otask
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Brett Terpstra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rdoc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.1'
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: 4.1.1
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '4.1'
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 4.1.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: aruba
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: chronic
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: '0.10'
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 0.10.2
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.10'
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: 0.10.2
81
+ - !ruby/object:Gem::Dependency
82
+ name: rb-appscript
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '0.6'
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: 0.6.1
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ~>
96
+ - !ruby/object:Gem::Version
97
+ version: '0.6'
98
+ - - '>='
99
+ - !ruby/object:Gem::Version
100
+ version: 0.6.1
101
+ - !ruby/object:Gem::Dependency
102
+ name: amatch
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ version: '0.3'
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.0
111
+ type: :runtime
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '0.3'
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: 0.3.0
121
+ description: A CLI for OmniFocus task entry with natural language syntax and fuzzy
122
+ project/context matching.
123
+ email: me@brettterpstra.com
124
+ executables:
125
+ - otask
126
+ extensions: []
127
+ extra_rdoc_files:
128
+ - README.md
129
+ files:
130
+ - README.md
131
+ - bin/otask
132
+ - lib/otask.rb
133
+ homepage: http://brettterpstra.com/
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options:
139
+ - --title
140
+ - doing
141
+ - --main
142
+ - README.md
143
+ - --markup
144
+ - markdown
145
+ - -ri
146
+ require_paths:
147
+ - lib
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.2.2
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: A command line tool for creating OmniFocus tasks (Mac)
165
+ test_files: []