otask 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/bin/otask +2 -241
  4. data/lib/otask.rb +359 -2
  5. data/lib/version.rb +3 -0
  6. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 72e2f8c4d11c95a93aa27235eb107d48f8311238
4
- data.tar.gz: 3b98b4d521835a01861347136ffc6d4f74ae82b7
3
+ metadata.gz: fcddd03631acb9fa09856952c64f9f21551967fb
4
+ data.tar.gz: 5160e3408d228bbd720077989047235f43adba1c
5
5
  SHA512:
6
- metadata.gz: 6077b721ef8255456a790852a3f1392bc73f53958f17fc7c58ad00a53aa41ae561917aa3e5216f6f37c83a8b21faaf0950e49de53ed577b526cbd2b207fc9b8c
7
- data.tar.gz: 71889ff718473bec68d0b8e8d57e6bf59d50f5d78e233402527fdcf63bb14f76c8f3ea72bfce325e544ffb59d179fb11cce6e219b543eee5ea2690b3ff0e8bbc
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
- class Otask
2
- VERSION = '0.2.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
@@ -0,0 +1,3 @@
1
+ class OTask
2
+ VERSION = '0.2.3'
3
+ end
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.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-04-14 00:00:00.000000000 Z
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