otask 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/bin/otask +2 -241
- data/lib/otask.rb +359 -2
- data/lib/version.rb +3 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcddd03631acb9fa09856952c64f9f21551967fb
|
4
|
+
data.tar.gz: 5160e3408d228bbd720077989047235f43adba1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82f6959605968cbe7bc2751184b7f598cde23f31b5701dcb70facc82eccd7a9f80b3b024b6d182d8c46017a3e2823f5c61723ccbd9f746e1389eacdec81cc754
|
7
|
+
data.tar.gz: fcd38123ced5d14de8a2e060b8552f2633745074368109e3e1bcf1a81278111bbb54e0c44396e91ee38671549185137419f953bce8fa1771a675c010849c52c3
|
data/README.md
CHANGED
@@ -8,6 +8,8 @@ I don't know how long appscript is going to work for us, and it's now a dead pro
|
|
8
8
|
|
9
9
|
$ [sudo] gem install otask
|
10
10
|
|
11
|
+
**Note:** If you use Xcode 5.1 and have trouble installing the gem, see [this post for a fix.](http://kaspermunck.github.io/2014/03/fixing-clang-error/)
|
12
|
+
|
11
13
|
### Documentation
|
12
14
|
|
13
15
|
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.
|
data/bin/otask
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
# -q, --quiet Output as little as possible, overrides verbose
|
14
14
|
# -V, --verbose Verbose output
|
15
15
|
# -g, --growl Use Growl for feedback
|
16
|
+
# -l, --list [TYPE] List all projects or contexts
|
16
17
|
#
|
17
18
|
# == Author
|
18
19
|
# Brett Terpstra
|
@@ -21,247 +22,7 @@
|
|
21
22
|
# Copyright (c) 2014 Brett Terpstra. Licensed under the MIT License:
|
22
23
|
# http://www.opensource.org/licenses/mit-license.php
|
23
24
|
|
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
|
25
|
+
require 'otask'
|
265
26
|
|
266
27
|
app = OTask.new(ARGV, STDIN)
|
267
28
|
app.run
|
data/lib/otask.rb
CHANGED
@@ -1,3 +1,360 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'date'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'appscript';include Appscript
|
6
|
+
require 'amatch';include Amatch
|
7
|
+
require 'chronic'
|
8
|
+
require 'rdoc'
|
9
|
+
require 'version.rb'
|
10
|
+
|
11
|
+
class OTask
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
def initialize(arguments, stdin)
|
15
|
+
@arguments = arguments
|
16
|
+
@stdin = stdin
|
17
|
+
|
18
|
+
# Set defaults
|
19
|
+
@options = OpenStruct.new
|
20
|
+
@options.verbose = false
|
21
|
+
@options.quiet = false
|
22
|
+
@options.list = false
|
23
|
+
@options.completion = false
|
24
|
+
|
25
|
+
of = app('OmniFocus')
|
26
|
+
@dd = of.default_document
|
27
|
+
# TO DO - add additional defaults
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse options, check arguments, then process the command
|
31
|
+
def run
|
32
|
+
if parsed_options?
|
33
|
+
usage_and_exit unless arguments_valid?
|
34
|
+
end
|
35
|
+
|
36
|
+
if parsed_options? && arguments_valid?
|
37
|
+
if @options.verbose
|
38
|
+
puts "Start at #{DateTime.now}\n\n"
|
39
|
+
puts "--Options--------------------"
|
40
|
+
output_options if @options.verbose # [Optional]
|
41
|
+
puts "--Task-Properties------------"
|
42
|
+
end
|
43
|
+
process_arguments
|
44
|
+
process_command
|
45
|
+
|
46
|
+
puts "\nFinished at #{DateTime.now}" if @options.verbose
|
47
|
+
else
|
48
|
+
usage_and_exit
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def parsed_options?
|
56
|
+
|
57
|
+
# Specify options
|
58
|
+
opts = OptionParser.new
|
59
|
+
opts.on( '-v', '--version', 'Display version number and exit' ) do
|
60
|
+
output_version
|
61
|
+
Process.exit 0
|
62
|
+
end
|
63
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
64
|
+
puts opts
|
65
|
+
puts "\nNotes are entered in parenthesis: This is my title (This is the note)"
|
66
|
+
puts "Tags are added with hashmarks #like #this. They can appear anywhere in the title string, but will be removed from the output."
|
67
|
+
puts "Note: natural-language date parsing will work better if you install the Chronic Ruby gem (`sudo gem install chronic`)." unless TodoUtils.new.use_chronic
|
68
|
+
exit(0)
|
69
|
+
end
|
70
|
+
opts.on('-V', '--verbose', 'Verbose logging') {
|
71
|
+
@options.verbose = true unless @options.completion
|
72
|
+
}
|
73
|
+
opts.on('-q', '--quiet', 'Run silently') { @options.quiet = true }
|
74
|
+
opts.on('-g', '--growl', 'Use Growl notifications') { @options.growl = true }
|
75
|
+
opts.on('-l', '--list TYPE[:filter_pattern]', 'List projects or contexts') do |arg|
|
76
|
+
type, filter = arg.split(/:/)
|
77
|
+
filter = false if filter.nil?
|
78
|
+
@options.list = [type, filter]
|
79
|
+
end
|
80
|
+
opts.on('-c', '--completion', 'Output project/contect list space separated') do |arg|
|
81
|
+
@options.verbose = false
|
82
|
+
@options.completion = true
|
83
|
+
end
|
84
|
+
# TO DO - add additional options
|
85
|
+
|
86
|
+
opts.parse!(@arguments) rescue return false
|
87
|
+
|
88
|
+
process_options
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
# Performs post-parse processing on options
|
93
|
+
def process_options
|
94
|
+
@options.verbose = false if @options.quiet
|
95
|
+
if @options.growl
|
96
|
+
procs = app("System Events").processes.bundle_identifier.get(:result_type => :list)
|
97
|
+
|
98
|
+
if procs.include? "com.Growl.GrowlHelperApp"
|
99
|
+
app("Growl").register(:as_application => "OTask", :all_notifications => ['Alert'], :default_notifications => ['Alert'], :icon_of_application => "OmniFocus")
|
100
|
+
else
|
101
|
+
@options.growl = false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def output_options
|
107
|
+
@options.marshal_dump.each do |name, val|
|
108
|
+
puts " #{name}: #{val}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_project_names(opt={})
|
113
|
+
kind = opt[:type] || :project
|
114
|
+
|
115
|
+
els = []
|
116
|
+
objects = opt[:type] == :context ? @dd.flattened_contexts : @dd.flattened_projects
|
117
|
+
|
118
|
+
objects.get.each{|p|
|
119
|
+
begin
|
120
|
+
if opt[:type] == :context || !p.status.respond_to?("get")
|
121
|
+
els.push(p.name.get)
|
122
|
+
else
|
123
|
+
els.push(p.name.get) if p.status.get == :active
|
124
|
+
end
|
125
|
+
rescue => e
|
126
|
+
p e
|
127
|
+
end
|
128
|
+
}
|
129
|
+
els
|
130
|
+
end
|
131
|
+
|
132
|
+
def list_type(type, filter)
|
133
|
+
|
134
|
+
|
135
|
+
output = []
|
136
|
+
filtered = filter ? ", filtered by '#{filter}'" : ""
|
137
|
+
case type.downcase
|
138
|
+
when /^p/i
|
139
|
+
puts "List projects#{filtered}:" if @options.verbose
|
140
|
+
output = get_project_names({:type => :project })
|
141
|
+
when /^c/i
|
142
|
+
puts "List contexts#{filtered}:" if @options.verbose
|
143
|
+
output = get_project_names({:type => :context })
|
144
|
+
else
|
145
|
+
puts "Unrecognized list option: #{type}"
|
146
|
+
return false
|
147
|
+
end
|
148
|
+
|
149
|
+
if filter
|
150
|
+
patt = filter.split("").join('.*?')
|
151
|
+
output.delete_if { |el| el !~ /#{patt}/i }
|
152
|
+
end
|
153
|
+
|
154
|
+
if @options.completion
|
155
|
+
print output.map {|item| item.strip.gsub(/\s+/,'')}.join(' ')
|
156
|
+
else
|
157
|
+
puts output
|
158
|
+
end
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
|
162
|
+
# True if required arguments were provided
|
163
|
+
def arguments_valid?
|
164
|
+
return true if @options.list
|
165
|
+
# TO DO - implement your real logic here
|
166
|
+
@arguments.length > 0 ? true : false
|
167
|
+
end
|
168
|
+
|
169
|
+
# Setup the arguments
|
170
|
+
def process_arguments
|
171
|
+
input = @stdin.stat.size > 0 ? @stdin.read : nil
|
172
|
+
titlestring = @arguments.join(' ')
|
173
|
+
|
174
|
+
# check for trailing ! (flagged task)
|
175
|
+
@options.flagged = titlestring.match(/ !\Z/).nil? ? false : true
|
176
|
+
titlestring.sub!(/ !\Z/,'') if @options.flagged
|
177
|
+
|
178
|
+
# check for #project in the string
|
179
|
+
projmatch = titlestring.match(/ ?#([a-z0-9]+)/i)
|
180
|
+
@options.project = projmatch.nil? ? nil : projmatch[1]
|
181
|
+
titlestring.sub!(/ ?##{projmatch[1]}/,'') unless projmatch.nil?
|
182
|
+
|
183
|
+
# check for @contexts
|
184
|
+
contextmatch = titlestring.match(/ @([^ ]+)/)
|
185
|
+
@options.context = contextmatch.nil? ? false : contextmatch[1]
|
186
|
+
titlestring.sub!(/ @[^ ]+/,'') unless contextmatch.nil?
|
187
|
+
|
188
|
+
# start/due date
|
189
|
+
startmatch = titlestring.match(/ s(?:tart)?\(([^\)]+)\)/)
|
190
|
+
@options.start = startmatch.nil? ? false : startmatch[1]
|
191
|
+
titlestring.sub!(/ s(tart)?\(([^\)]+)\)/,'') unless startmatch.nil?
|
192
|
+
duematch = titlestring.match(/ d(?:ue)?\(([^\)]+)\)/)
|
193
|
+
@options.due = duematch.nil? ? false : duematch[1]
|
194
|
+
titlestring.sub!(/ d(ue)?\(([^\)]+)\)/,'') unless duematch.nil?
|
195
|
+
|
196
|
+
# creation date
|
197
|
+
creationmatch = titlestring.match(/ c(?:reate)?\(([^\)]+)\)/)
|
198
|
+
@options.creation = creationmatch.nil? ? false : creationmatch[1]
|
199
|
+
titlestring.sub!(/ c(reate)?\(([^\)]+)\)/,'') unless creationmatch.nil?
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
@options.notes = ''
|
204
|
+
if titlestring =~ /\(([^\)]+)\)/
|
205
|
+
@options.notes = $1
|
206
|
+
titlestring.gsub!(/\(([^\)]+)\)/,'')
|
207
|
+
end
|
208
|
+
if @options.notes == '' && !input.nil? # check for piped input on STDIN
|
209
|
+
@options.notes = input
|
210
|
+
elsif @options.notes && input
|
211
|
+
@options.notes = @options.notes + "\n\n" + input
|
212
|
+
end
|
213
|
+
|
214
|
+
# See if there's a bang at the end of the task name
|
215
|
+
unless @options.flagged
|
216
|
+
@options.flagged = titlestring.match(/!\Z/).nil? ? false : true
|
217
|
+
titlestring.sub!(/!\Z/,'') if @options.flagged
|
218
|
+
end
|
219
|
+
|
220
|
+
@options.name = titlestring
|
221
|
+
end
|
222
|
+
|
223
|
+
def usage_and_exit
|
224
|
+
puts 'Usage: otask [options] "Task string"'
|
225
|
+
puts
|
226
|
+
puts "For help use: otask -h"
|
227
|
+
|
228
|
+
exit(0)
|
229
|
+
end
|
230
|
+
|
231
|
+
def output_version
|
232
|
+
puts "oTask version #{OTask::VERSION}"
|
233
|
+
end
|
234
|
+
|
235
|
+
def add_task
|
236
|
+
|
237
|
+
if @props['project']
|
238
|
+
proj_name = @props["project"]
|
239
|
+
proj = @dd.flattened_projects[proj_name]
|
240
|
+
end
|
241
|
+
if @props['context']
|
242
|
+
ctx_name = @props["context"]
|
243
|
+
ctx = @dd.flattened_contexts[ctx_name]
|
244
|
+
end
|
245
|
+
|
246
|
+
tprops = @props.inject({}) do |h, (k, v)|
|
247
|
+
h[:"#{k}"] = v
|
248
|
+
h
|
249
|
+
end
|
250
|
+
|
251
|
+
tprops.delete(:project)
|
252
|
+
tprops[:context] = ctx if @props['context']
|
253
|
+
tprops[:flagged] = @props['flagged']
|
254
|
+
|
255
|
+
t = @dd.make(:new => :inbox_task, :with_properties => tprops)
|
256
|
+
t.assigned_container.set(proj) if @props['project']
|
257
|
+
|
258
|
+
return true
|
259
|
+
end
|
260
|
+
|
261
|
+
def best_match(items,fragment)
|
262
|
+
if @options.verbose
|
263
|
+
print "Expanding #{fragment}"
|
264
|
+
end
|
265
|
+
|
266
|
+
good_match = false
|
267
|
+
patt = fragment.split('').join('.*?')
|
268
|
+
# puts "Regex: #{patt}" if @options.verbose
|
269
|
+
items.each {|item|
|
270
|
+
if item =~ /#{patt}/i
|
271
|
+
good_match = item
|
272
|
+
break;
|
273
|
+
end
|
274
|
+
}
|
275
|
+
if good_match
|
276
|
+
puts "=> #{good_match} (regex)" if @options.verbose
|
277
|
+
else
|
278
|
+
highscore = {'score'=>0,'name'=>nil}
|
279
|
+
fragment.downcase!
|
280
|
+
|
281
|
+
items.reverse.each {|item|
|
282
|
+
if fragment && !item.nil?
|
283
|
+
score = (Jaro.new(item.downcase).match(fragment) * 10).to_i
|
284
|
+
# puts "#{item} (#{score})" if @options.verbose
|
285
|
+
if score > highscore['score']
|
286
|
+
highscore = {'score'=>score,'name'=>item}
|
287
|
+
end
|
288
|
+
end
|
289
|
+
}
|
290
|
+
if highscore['name'].nil?
|
291
|
+
good_match = highscore['name']
|
292
|
+
puts "=> #{good_match} (Jaro: #{highscore['score']})" if @options.verbose
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
puts "=> NO_MATCH" if @options.verbose && !good_match
|
297
|
+
good_match
|
298
|
+
end
|
299
|
+
|
300
|
+
def parse_date(datestring)
|
301
|
+
days = 0
|
302
|
+
if datestring =~ /^\+(\d+)$/
|
303
|
+
days = (60 * 60 * 24 * $1.to_i)
|
304
|
+
newdate = Time.now + days
|
305
|
+
else
|
306
|
+
newdate = Chronic.parse(datestring, {:context => :future, :ambiguous_time_range => 8})
|
307
|
+
end
|
308
|
+
# parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
|
309
|
+
# return parsed =~ /1969/ ? false : parsed
|
310
|
+
return newdate
|
311
|
+
end
|
312
|
+
|
313
|
+
def growl(title, message)
|
314
|
+
app("Growl").notify(:title => title, :description => message, :application_name => "OTask", :with_name => "Alert") if @options.growl
|
315
|
+
end
|
316
|
+
|
317
|
+
def process_command
|
318
|
+
if @options.list
|
319
|
+
list_type(@options.list[0], @options.list[1])
|
320
|
+
Process.exit 0
|
321
|
+
end
|
322
|
+
|
323
|
+
of = app('OmniFocus')
|
324
|
+
dd = of.default_document
|
325
|
+
|
326
|
+
@props = {}
|
327
|
+
@props['name'] = @options.name
|
328
|
+
if @options.project
|
329
|
+
projs = get_project_names({:type => :project})
|
330
|
+
name = best_match(projs,@options.project)
|
331
|
+
@props['project'] = name if name
|
332
|
+
end
|
333
|
+
if @options.context
|
334
|
+
ctxs = get_project_names({:type => :context})
|
335
|
+
name = best_match(ctxs,@options.context)
|
336
|
+
@props['context'] = name if name
|
337
|
+
end
|
338
|
+
@props['start_date'] = parse_date(@options.start) if @options.start
|
339
|
+
@props['due_date'] = parse_date(@options.due) if @options.due
|
340
|
+
@props['creation_date'] = parse_date(@options.creation) if @options.creation
|
341
|
+
@props['note'] = @options.notes unless @options.notes == ''
|
342
|
+
@props['flagged'] = @options.flagged
|
343
|
+
add_task
|
344
|
+
unless @options.quiet
|
345
|
+
o = (@props['flagged'] ? "Flagged task \"" : "Task \"") + @props['name'] + "\" added to "
|
346
|
+
o += @props['project'].nil? ? "Inbox" : @props['project']
|
347
|
+
o += ", context "+@props['context']+"." unless @props['context'].nil?
|
348
|
+
|
349
|
+
if @options.growl
|
350
|
+
growl("Task created",o)
|
351
|
+
else
|
352
|
+
puts o
|
353
|
+
end
|
354
|
+
end
|
355
|
+
@props.each do |name, val|
|
356
|
+
puts " #{name}: #{val}"
|
357
|
+
end if @options.verbose
|
358
|
+
end
|
3
359
|
end
|
360
|
+
|
data/lib/version.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: otask
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -130,6 +130,7 @@ files:
|
|
130
130
|
- README.md
|
131
131
|
- bin/otask
|
132
132
|
- lib/otask.rb
|
133
|
+
- lib/version.rb
|
133
134
|
homepage: http://brettterpstra.com/
|
134
135
|
licenses:
|
135
136
|
- MIT
|