timeless 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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