timeless 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,411 @@
1
+ require 'slop'
2
+
3
+ module Timeless
4
+ module CommandSyntax
5
+ APP_NAME="timeless"
6
+
7
+ # return a hash with all the commands and their options
8
+ def self.commands
9
+ h = Hash.new
10
+ self.methods.each do |method|
11
+ if method.to_s.include?("_opts") then
12
+ h = h.merge(eval(method.to_s))
13
+ end
14
+ end
15
+ return h
16
+ end
17
+
18
+ private
19
+
20
+ def self.version_opts
21
+ opts = Slop::Options.new
22
+ opts.banner = "version -- print version information"
23
+ help = <<EOS
24
+ NAME
25
+ #{opts.banner}
26
+
27
+ SYNOPSYS
28
+ #{opts.to_s}
29
+
30
+ DESCRIPTION
31
+ return version information
32
+
33
+ EXAMPLES
34
+ #{APP_NAME} version
35
+ #{APP_NAME} version #{VERSION}
36
+ EOS
37
+ return { :version => [opts, :version, help] }
38
+ end
39
+
40
+ def self.console_opts
41
+ opts = Slop::Options.new
42
+ opts.banner = "console [options] -- Enter the console"
43
+ help = <<EOS
44
+ NAME
45
+ #{opts.banner}
46
+
47
+ SYNOPSYS
48
+ #{opts.to_s}
49
+
50
+ DESCRIPTION
51
+ Invoke a console, from which you can more easily run
52
+ #{APP_NAME} commands.
53
+
54
+ EXAMPLES
55
+ #{APP_NAME} console
56
+ #{APP_NAME}:000> command
57
+ ....
58
+ #{APP_NAME}:001> another_command
59
+ ...
60
+ #{APP_NAME}:002>
61
+ EOS
62
+ return { :console => [opts, :console, help] }
63
+ end
64
+
65
+ def self.man_opts
66
+ opts = Slop::Options.new
67
+ opts.banner = "man -- print a manual page"
68
+ help = <<EOS
69
+ NAME
70
+ #{opts.banner}
71
+
72
+ SYNOPSYS
73
+ #{opts.to_s}
74
+
75
+ DESCRIPTION
76
+ Print the README file of this gem
77
+
78
+ EXAMPLES
79
+ #{APP_NAME} man
80
+ EOS
81
+ return { :man => [opts, :man, help] }
82
+ end
83
+
84
+ def self.help_opts
85
+ opts = Slop::Options.new
86
+ opts.banner = "help [command] -- print usage string"
87
+ help = <<EOS
88
+ NAME
89
+ #{opts.banner}
90
+
91
+ SYNOPSYS
92
+ #{opts.to_s}
93
+
94
+ DESCRIPTION
95
+ Print help about a command
96
+
97
+ EXAMPLES
98
+ #{APP_NAME} help
99
+ #{APP_NAME} help process
100
+ EOS
101
+ return { :help => [opts, :help, help] }
102
+ end
103
+
104
+
105
+ #
106
+ # APP SPECIFIC COMMANDS START HERE
107
+ #
108
+
109
+ def self.pom_opts
110
+ opts = Slop::Options.new
111
+ opts.banner = "pom [options] [notes] -- run a pomodoro timer"
112
+
113
+ opts.boolean "--long", "Use a long timer (50 minutes)"
114
+ opts.integer "--duration", "Duration of the pomodoro timer, in minutes"
115
+
116
+ help = <<EOS
117
+ NAME
118
+ #{opts.banner}
119
+
120
+ SYNOPSYS
121
+ #{opts.to_s}
122
+
123
+ DESCRIPTION
124
+ Generate a sidecar file for the track(s) passed as input.
125
+
126
+ Once created a sidecar file usually does not need to be regenerated. You can
127
+ use the `--force` option to cause the sidecar file to be rewritten.
128
+
129
+ EXAMPLES
130
+ timeless pom
131
+ timeless pom --long
132
+ EOS
133
+ return { pom: [opts, :pom, help] }
134
+ end
135
+
136
+ def self.start_opts
137
+ opts = Slop::Options.new
138
+ opts.banner = "start [--at chronic expression] [notes] -- start the timer"
139
+
140
+ opts.string "--at", "Manually set the start time"
141
+
142
+ help = <<EOS
143
+ NAME
144
+ #{opts.banner}
145
+
146
+ SYNOPSYS
147
+ #{opts.to_s}
148
+
149
+ DESCRIPTION
150
+ Start the timer: invoke this command when you want to record the actual
151
+ start of a given activity.
152
+
153
+ Use --at, if the start time is not "now". If you specify notes, they
154
+ will be used when you stop the activity.
155
+
156
+ Notes are written in free text, but you can use the following tags to mark certain
157
+ strings:
158
+
159
+ p: to specify a project
160
+ c: to specify a client
161
+ a: to specify the activity
162
+
163
+ So, for instance, you can write something like: "working on p:next_big_thing trying
164
+ to a:test function asked by c:john_smith"
165
+
166
+ Tags are used when reporting and exporting.
167
+
168
+ EXAMPLES
169
+
170
+ timeless start p:mobile_app c:john_smith a:testing
171
+ timeless start --at 'thirty minutes ago'
172
+ EOS
173
+ return { start: [opts, :start, help] }
174
+ end
175
+
176
+ def self.forget_opts
177
+ opts = Slop::Options.new
178
+ opts.banner = "forget -- forget the last start command"
179
+
180
+ help = <<EOS
181
+ NAME
182
+ #{opts.banner}
183
+
184
+ SYNOPSYS
185
+ #{opts.to_s}
186
+
187
+ DESCRIPTION
188
+ Forget the active timer (if any).
189
+
190
+ Timeless protects your active timer (that is, the timer you start with "timeless start"
191
+ command) and will complain if you try to overwrite it (e.g., by using another start
192
+ command or if you specity the start with 'clock' or 'stop').
193
+
194
+ EXAMPLES
195
+ timeless forget
196
+ EOS
197
+ return { forget: [opts, :forget, help] }
198
+ end
199
+
200
+ def self.stop_opts
201
+ opts = Slop::Options.new
202
+ opts.banner = "stop [--start 'chronic expression'] [--stop|end|at 'chronic expression'] [--last] -- stop the active timer"
203
+
204
+ opts.string "--start", "Start time, using chronic expression"
205
+
206
+ opts.string "--stop", "Stop time, using chronic expression"
207
+ opts.string "--at", "A synonym of stop"
208
+ opts.string "--end", "A synonym of stop"
209
+
210
+ opts.boolean "--last", "Reuse notes of last clock"
211
+
212
+ help = <<EOS
213
+ NAME
214
+ #{opts.banner}
215
+
216
+ SYNOPSYS
217
+ #{opts.to_s}
218
+
219
+ DESCRIPTION
220
+ Stop clocking, using notes, if specified. Require a start time if there is no active timer.
221
+
222
+ EXAMPLES
223
+ timeless stop
224
+ timeless stop working on p:project
225
+ timeless stop --at 'one hour ago' working on p:project
226
+ timeless stop --start 'three hours ago' forgot to start the timer when working on p:project
227
+ EOS
228
+ return { stop: [opts, :stop, help] }
229
+ end
230
+
231
+ def self.clock_opts
232
+ opts = Slop::Options.new
233
+ opts.banner = "clock [--start 'chronic expression'] [--stop|end 'chronic expression'] [--last] -- stop the active timer"
234
+
235
+ opts.string "--start", "Start time, using chronic expression (use last stop time if not specified)"
236
+ opts.string "--stop", "Stop time, using chronic expression (use now, if not specified)"
237
+ opts.string "--end", "A synonym of stop"
238
+
239
+ opts.boolean "--last", "Reuse notes of last clock"
240
+
241
+ help = <<EOS
242
+ NAME
243
+ #{opts.banner}
244
+
245
+ SYNOPSYS
246
+ #{opts.to_s}
247
+
248
+ DESCRIPTION
249
+ Clock an entry. Use this command when you want to catchup with activities you have not clocked.
250
+
251
+ With no options, it will add an entry ending now and starting from the end of the last clocked
252
+ entry. You can change the start usin the --start option and the stop time with the --stop option.
253
+
254
+ EXAMPLES
255
+ timeless clock
256
+ timeless clock --start 'five hours ago' --stop 'three hours ago' quick break
257
+ timeless clock --end 'one hour ago' working on p:project
258
+ EOS
259
+ return { clock: [opts, :clock, help] }
260
+ end
261
+
262
+
263
+ def self.current_opts
264
+ opts = Slop::Options.new
265
+ opts.banner = "current -- show time elapsed since last start command"
266
+ help = <<EOS
267
+ NAME
268
+ #{opts.banner}
269
+
270
+ SYNOPSYS
271
+ #{opts.to_s}
272
+
273
+ DESCRIPTION
274
+ Show time elapsed since last start command.
275
+
276
+ EXAMPLES
277
+ timeless current
278
+ EOS
279
+ return { current: [opts, :current, help] }
280
+ end
281
+
282
+ def self.last_opts
283
+ opts = Slop::Options.new
284
+ opts.banner = "last -- show last clocked entry"
285
+ help = <<EOS
286
+ NAME
287
+ #{opts.banner}
288
+
289
+ SYNOPSYS
290
+ #{opts.to_s}
291
+
292
+ DESCRIPTION
293
+ Show last clocked entry.
294
+
295
+ EXAMPLES
296
+ timeless last
297
+ EOS
298
+ return { last: [opts, :last, help] }
299
+ end
300
+
301
+ def self.list_opts
302
+ opts = Slop::Options.new
303
+ opts.banner = "list key -- list all values assigned to a given tag (e.g., list all projects)"
304
+ help = <<EOS
305
+ NAME
306
+ #{opts.banner}
307
+
308
+ SYNOPSYS
309
+ #{opts.to_s}
310
+
311
+ DESCRIPTION
312
+ List all values recorded for a given tag (e.g., list all projects).
313
+
314
+ EXAMPLES
315
+ timeless list p
316
+ EOS
317
+ return { list: [opts, :list, help] }
318
+ end
319
+
320
+ def self.gaps_opts
321
+ opts = Slop::Options.new
322
+ opts.banner = "gaps -- report all gaps in you timesheets"
323
+
324
+ opts.string "--from", "Start date for listing gaps"
325
+ opts.string "--to", "End date for listing gaps"
326
+ opts.integer "--interval", "Minimum interval to report (in minutes)"
327
+
328
+
329
+ help = <<EOS
330
+ NAME
331
+ #{opts.banner}
332
+
333
+ SYNOPSYS
334
+ #{opts.to_s}
335
+
336
+ DESCRIPTION
337
+ List all gaps in the timesheets (periods you have not clocked).
338
+
339
+ Use --from and --to to limit your search.
340
+
341
+ Use --interval to report only gaps longer than a given interval.
342
+
343
+ EXAMPLES
344
+ timeless gaps --interval 10
345
+ EOS
346
+ return { gaps: [opts, :gaps, help] }
347
+ end
348
+
349
+ def self.statement_opts
350
+ opts = Slop::Options.new
351
+ opts.banner = "statement -- produce a list of clocked activities"
352
+
353
+ opts.string "--from", "Start date for listing gaps"
354
+ opts.string "--to", "End date for listing gaps"
355
+ opts.string "--filter", "Consider only entries whose notes contain this string"
356
+
357
+
358
+ help = <<EOS
359
+ NAME
360
+ #{opts.banner}
361
+
362
+ SYNOPSYS
363
+ #{opts.to_s}
364
+
365
+ DESCRIPTION
366
+ Produce a statement of clocked activities.
367
+
368
+ Use --from and --to to restrict to a given time period.
369
+
370
+ Use --filter to restrict entries whose notes match the filter criteria.
371
+ You can use --filter, for instance, to extract all entries related to
372
+ a project "project_1", using "p:project_1"
373
+
374
+ EXAMPLES
375
+ timeless statement --filter p:project_1
376
+ EOS
377
+ return { statement: [opts, :statement, help] }
378
+ end
379
+
380
+ def self.balance_opts
381
+ opts = Slop::Options.new
382
+ opts.banner = "balance -- produce a report of clocked activities"
383
+
384
+ opts.string "--from", "Start date for listing gaps"
385
+ opts.string "--to", "End date for listing gaps"
386
+ opts.string "--filter", "Consider only entries whose notes contain this string"
387
+
388
+
389
+ help = <<EOS
390
+ NAME
391
+ #{opts.banner}
392
+
393
+ SYNOPSYS
394
+ #{opts.to_s}
395
+
396
+ DESCRIPTION
397
+ Produce a report organized by keys of clocked activities.
398
+
399
+ Use --from and --to to restrict to a given time period.
400
+
401
+ Use --filter to restrict entries whose notes match the filter criteria.
402
+ You can use --filter, for instance, to extract all entries related to
403
+ a project "project_1", using "p:project_1"
404
+
405
+ EXAMPLES
406
+ timeless balance --filter p:project_1
407
+ EOS
408
+ return { balance: [opts, :balance, help] }
409
+ end
410
+ end
411
+ end
@@ -1,12 +1,8 @@
1
1
  module Timeless
2
2
  module Pomodoro
3
- require 'terminal-notifier'
3
+ require 'notiffany'
4
4
  require 'date'
5
5
 
6
- TITLE = 'Pomodoro'
7
- MESSAGE = 'Finish'
8
- SOUND = 'Glass'
9
-
10
6
  WORKING = 25
11
7
  WORKING_LONG = 50
12
8
 
@@ -29,9 +25,10 @@ module Timeless
29
25
  sleep INTERVAL
30
26
  end
31
27
  print "\n"
32
-
33
- TerminalNotifier.notify(MESSAGE, title: TITLE, sound: SOUND)
34
- system "say 'Pomodoro complete. Take a break, now'"
28
+
29
+ notifier = Notiffany.connect(title: "Pomodoro Complete!")
30
+ notifier.notify("You clocked: #{total_mins} minute#{"s" if total_mins != 1}.\nYou deserve a break, now", image: :success)
31
+ notifier.disconnect # some plugins like TMux and TerminalTitle rely on this
35
32
 
36
33
  [start, Time.now, notes]
37
34
  end
@@ -11,7 +11,7 @@ module Timeless
11
11
  end
12
12
 
13
13
  def self.last
14
- CSV.read(TIMELESS_FILE).last
14
+ CSV.read(TIMELESS_FILE).sort { |x, y| x[1] <=> y[1] }.last
15
15
  end
16
16
 
17
17
  def self.get from_date=nil, to_date=nil, filter=nil
@@ -29,10 +29,33 @@ module Timeless
29
29
  entries.map { |x| extract_kpv key, x[2] }.uniq.sort
30
30
  end
31
31
 
32
+ def self.balance from, to, filter
33
+ entries = Timeless::Storage.get(from, to, filter)
34
+
35
+ hash = {"p" => {}, "c" => {}, "a" => {}}
36
+
37
+ entries.each do |entry|
38
+ start = Time.parse(entry[0])
39
+ stop = Time.parse(entry[1])
40
+ duration = (stop - start) / 60
41
+
42
+ project = extract_kpv "p", entry[2]
43
+ client = extract_kpv "c", entry[2]
44
+ activity = extract_kpv "a", entry[2]
45
+
46
+ hash["p"][project] = (hash["p"][project] || 0) + duration if project != ""
47
+ hash["c"][client] = (hash["c"][client] || 0) + duration if client != ""
48
+ hash["a"][activity] = (hash["a"][activity] || 0) + duration if activity != ""
49
+
50
+ end
51
+ hash
52
+ end
53
+
54
+
32
55
  def self.export
33
56
  entries = CSV.read(TIMELESS_FILE)
34
57
 
35
- CSV { |csvout| csvout << ["Start Date", "Start Time", "End Date", "End Time", "Duration (s)", "Project", "Notes"] }
58
+ CSV { |csvout| csvout << ["Start Date", "Start Time", "End Date", "End Time", "Duration (s)", "Project", "Client", "Activity", "Notes"] }
36
59
  CSV do |csvout|
37
60
  entries.each do |entry|
38
61
  start = Time.parse(entry[0])
@@ -49,10 +72,11 @@ module Timeless
49
72
  # extract project and client, if present
50
73
  project = extract_kpv "p", entry[2]
51
74
  client = extract_kpv "c", entry[2]
75
+ activity = extract_kpv "a", entry[2]
52
76
 
53
77
  notes = entry[2]
54
78
 
55
- csvout << [start_date, start_time, stop_date, stop_time, duration, project, client, notes]
79
+ csvout << [start_date, start_time, stop_date, stop_time, duration, project, client, activity, notes]
56
80
  end
57
81
  end
58
82
  end