paradeiser 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -0
  4. data/Gemfile +6 -0
  5. data/Guardfile +17 -0
  6. data/README.md +76 -187
  7. data/Rakefile +12 -1
  8. data/TODO.md +62 -0
  9. data/VISION.md +474 -0
  10. data/bin/pom +60 -0
  11. data/doc/Paradeiser::Pomodoro_status.svg +50 -0
  12. data/lib/paradeiser.rb +25 -2
  13. data/lib/paradeiser/controllers/controller.rb +30 -0
  14. data/lib/paradeiser/controllers/paradeiser_controller.rb +10 -0
  15. data/lib/paradeiser/controllers/pomodori_controller.rb +38 -0
  16. data/lib/paradeiser/errors.rb +31 -0
  17. data/lib/paradeiser/executor.rb +15 -0
  18. data/lib/paradeiser/models/hook.rb +26 -0
  19. data/lib/paradeiser/models/job.rb +25 -0
  20. data/lib/paradeiser/models/pomodoro.rb +58 -0
  21. data/lib/paradeiser/models/repository.rb +57 -0
  22. data/lib/paradeiser/models/scheduler.rb +43 -0
  23. data/lib/paradeiser/refinements.rb +5 -0
  24. data/lib/paradeiser/router.rb +29 -0
  25. data/lib/paradeiser/version.rb +1 -1
  26. data/lib/paradeiser/view.rb +21 -0
  27. data/lib/paradeiser/views/paradeiser/init.erb +1 -0
  28. data/lib/paradeiser/views/pomodori/finish.erb +1 -0
  29. data/lib/paradeiser/views/pomodori/report.erb +5 -0
  30. data/lib/paradeiser/views/pomodori/start.erb +1 -0
  31. data/lib/paradeiser/views/pomodori/status.erb +9 -0
  32. data/paradeiser.gemspec +21 -4
  33. data/templates/linux/hooks/after-finish +10 -0
  34. data/templates/linux/hooks/after-start +7 -0
  35. data/templates/mac/hooks/after-finish +10 -0
  36. data/templates/mac/hooks/after-start +7 -0
  37. data/test/helper.rb +24 -0
  38. data/test/integration/test_pom.rb +17 -0
  39. data/test/lib/assertions.rb +10 -0
  40. data/test/lib/at_mock.rb +6 -0
  41. data/test/lib/options_mock.rb +7 -0
  42. data/test/lib/pomodoro_mock.rb +11 -0
  43. data/test/lib/process_status_mock.rb +2 -0
  44. data/test/lib/pstore_mock.rb +16 -0
  45. data/test/templates/hooks/pre-finish +1 -0
  46. data/test/unit/test_paradeiser_controller.rb +88 -0
  47. data/test/unit/test_pomodori_controller.rb +103 -0
  48. data/test/unit/test_pomodori_view.rb +78 -0
  49. data/test/unit/test_pomodoro.rb +100 -0
  50. data/test/unit/test_pomodoro_hooks.rb +83 -0
  51. data/test/unit/test_repository.rb +127 -0
  52. data/test/unit/test_router.rb +36 -0
  53. data/test/unit/test_scheduler.rb +44 -0
  54. metadata +244 -13
@@ -0,0 +1,474 @@
1
+ # Paradeiser
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/paradeiser.png)](http://badge.fury.io/rb/paradeiser)
4
+ [![Build Status](https://secure.travis-ci.org/nerab/paradeiser.png?branch=master)](http://travis-ci.org/nerab/paradeiser)
5
+
6
+ _This project is developed with the [readme-driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html) method. This file contains the vision, whereas the [README](README.md) reflects the functionality that is actually implemented._
7
+
8
+ Paradeiser is a command-line tool for the [Pomodoro Technique](http://www.pomodorotechnique.com/). It keeps track of the current pomodoro and assists the user in managing active and past pomodori:
9
+
10
+ * Records finished and cancelled pomodori as well as internal and external interruptions and other events
11
+ * Keeps track of the timer for the active pomodoro and the break
12
+ * Provides out-of-the-box reports that show details about finished and cancelled pomodori
13
+ * Shows information about breaks and interruptions
14
+
15
+ Paradeiser itself is not concerned with the actual management of tasks. There are plenty of tools for that; e.g. [TaskWarrior](http://taskwarrior.org/).
16
+
17
+ ## Concepts
18
+
19
+ ### Rule #1
20
+
21
+ There must never be more than one pomodoro [xor](http://en.wikipedia.org/wiki/Xor) break at any given time.
22
+
23
+ This is scoped to a single user account (not just the `$POM_DIR` directory, but also the `at` queue).
24
+
25
+ ## Installation
26
+
27
+ $ gem install paradeiser
28
+
29
+ ## Usage
30
+
31
+ ### Start a new pomodoro
32
+
33
+ $ pom start
34
+
35
+ If a break is still active, it will be stopped before the new pomodoro is started. Because of Rule #1, calling start while a pomodoro is active will print an error message.
36
+
37
+ ### Finish the pomodoro
38
+
39
+ $ pom finish
40
+ $ pom finish This one went very well.
41
+
42
+ If a pomodoro is active, it will be marked as successful after stopping it, regardless of whether the 25 minutes are over or not. Remaining arguments, if present, will be added to the pomodoro as annotation.
43
+
44
+ If there is no active pomodoro, an error message will be printed.
45
+
46
+ ### Record an interruption of the current pomodoro
47
+
48
+ $ pom interrupt --external Phone call from boss
49
+ $ pom interrupt --internal "Couldn't stay away from Twitter"
50
+
51
+ Remaining arguments, if present, will be added to the interrupt as annotation. If no pomodoro is active, the command will throw an error.
52
+
53
+ ### Start a break
54
+
55
+ $ pom break [start] [--short | --long]
56
+
57
+ If there is an active pomodoro, an error message will be printed. The `start` command is optional and may be omitted (it's only there for symmetry with `pom break end`, see the section about `at`).
58
+
59
+ By default the break will be five minutes long. After four pomodori within a day, the break will be 30 minutes long. This can be overridden with `--short` or `--long`, with an optional argument value that determines the lenght of the break in minutes (e.g. `pom break --short=10`).
60
+
61
+ While there is a command to stop a break (see the section about `at`), it isn't really necessary to call it from a user's perspective. Either a new pomodoro is started, which will implicitely stop the break, or the break ends naturally because it is over. We do not track break time.
62
+
63
+ ### Annotate a pomodoro
64
+
65
+ $ pom annotate This was intense, but I am happy about the work I finished.
66
+
67
+ The annotation will be added to the active or, if none is active, to the most recently finished or cancelled pomodoro. If no text is given, the annotation text is read from STDIN.
68
+
69
+ Breaks cannot have annotations.
70
+
71
+ ### Cancel the pomodoro
72
+
73
+ $ pom cancel Just couldn't concentrate anymore.
74
+
75
+ It will be marked as unsuccessful (remember, a pomodoro is indivisible). If no pomodoro is active, the command will throw an error. If a break is active, the command will do nothing except printing a warning. Remaining arguments, if present, will be added to the pomodoro as annotation.
76
+
77
+ ### Log a pomodoro
78
+
79
+ $ pom log
80
+
81
+ Add a successfully finished pomodoro that was never recorded as being started (maybe the user forgot to call `pom start`). It will appear in the reports and will count towards efficiency calculations.
82
+
83
+ The current time will be used for the finish timestamp, and the start time will be calculated from the finish time backwards.
84
+
85
+ ### Initialize Paradeiser
86
+
87
+ * Initialize the default directory that is used to store the Paradeiser configuration and data:
88
+
89
+ $ pom init
90
+
91
+ Creates the `$POM_DIR` directory and the sample hooks in `$POM_DIR/hooks`. The data store will not be created on `pom init`, but when the first write operation happens (e.g. `pom start`, but not `pom report`).
92
+
93
+ * Initialize an abritrary directory
94
+
95
+ $ pom init /tmp
96
+
97
+ This command initializes `/tmp` as `$POM_DIR`. It also sets the config variable `dir` in `~/.pomrc`:
98
+
99
+ $ pom config
100
+
101
+ If the `at` command is not available or not enabled, `pom init` will issue a warning. The program will continue because it is still useful for recording, although it will not be able to enqueue itself in order to execute the (time-based) `before-finish` and `before-break` hooks.
102
+
103
+ ### Troubleshooting
104
+
105
+ `pom doctor` performs a number of checks that ensure that Paradeiser can run with best results.
106
+
107
+ It checks if
108
+
109
+ * `at` is there and enabled
110
+ * ...
111
+
112
+ `pom doctor` also provides a hint on how to correct that situation.
113
+
114
+ ### Location
115
+ Recording the location of a pomodoro allows Paradeiser to compare the average count of successful and cancelled pomodori and the number of interruptions by location, so that a report can tell in which environment we get the most work done.
116
+
117
+ * Show the current location
118
+
119
+ $ pom location
120
+ Home Office
121
+
122
+ $ pom location --verbose
123
+ Home Office (macbook@01:23:45:67:89:0A)
124
+
125
+ * List all locations
126
+
127
+ $ pom locations
128
+ Home Office
129
+ Starbucks
130
+
131
+ $ pom locations --verbose
132
+ Home Office (macbook@01:23:45:67:89:0A)
133
+ Starbucks (macbook@45:01:89:0A:67:23)
134
+
135
+ * Show the label of a location identifier
136
+
137
+ $ pom location macbook@01:23:45:67:89:0A
138
+ Home Office
139
+
140
+ * Show the identifier of a location
141
+
142
+ $ pom location "Home Office"
143
+ macbook@01:23:45:67:89:0A
144
+
145
+ * Label a location
146
+
147
+ $ pom location macbook@01:23:45:67:89:0A "Your Label"
148
+
149
+ Paradeiser will automatically figure out the current location from the hostname and the MAC address of the default gateway (see below for details). This can be overridden by setting `$POM_LOCATION` or with a command line option:
150
+
151
+ $ pom location --location="On the road"
152
+ On the road
153
+
154
+ $ POM_LOCATION="On the road" pom location
155
+ On the road
156
+
157
+ Both the `--location` option and the environment variable can be passed to almost all commands.
158
+
159
+ ## Timer with `at`
160
+
161
+ A central aspect of the Pomodoro Technique is the timer function:
162
+
163
+ * The remaining time of the active pomodoro or break must be displayed to the user.
164
+ * When the pomodoro or break is over, the user also needs to get a notification.
165
+
166
+ The `at` command is used for this. We just tell it to call
167
+
168
+ pom finish
169
+
170
+ when the pomodoro is over. A similar command exists as
171
+
172
+ pom break finish
173
+
174
+ which is called by `at` when the break is over.
175
+
176
+ When a pomodoro is started, Paradeiser enqueues itself to `at` like this:
177
+
178
+ echo pom finish | at now + 25 minutes
179
+
180
+ When `at` calls Paradeiser with this command, the pomodoro / break will be over and Paradeiser can do all the internal processing related to stopping the pomodoro / break (incl. calling the appropriate hooks, see below).
181
+
182
+ Paradeiser uses a dedicated at queue named 'p' to organize its jobs and to prevent accidentially overwriting other scheduled tasks.
183
+
184
+ ## Status
185
+
186
+ Paradeiser can print the current status to STDOUT with the `pom status` command. The current state is provided as process exit status (which is also useful when the output is suppressed).
187
+
188
+ * Given an active pomodoro:
189
+
190
+ $ pom status
191
+ Pomodoro #2 is active (started 11:03, 14 minutes remaining).
192
+
193
+ $ pom status > /dev/null
194
+ $ echo $?
195
+ 0
196
+
197
+ * Given no active pomodoro and the last one (not earlier as today) was finished:
198
+
199
+ $ pom status
200
+ No active pomodoro. Last one was finished at 16:58.
201
+
202
+ $ pom status > /dev/null
203
+ $ echo $?
204
+ 1
205
+
206
+ * Given no active pomodoro and the last one (not earlier as today) was cancelled:
207
+
208
+ $ pom status
209
+ No pomodoro active. Last pomodoro was cancelled at 17:07.
210
+
211
+ $ pom status > /dev/null
212
+ $ echo $?
213
+ 2
214
+
215
+ * Given a break (implies no active pomodoro):
216
+
217
+ $ pom status
218
+ Taking a 5 minute break until 2013-07-16 17.07 (4 minutes remaining).
219
+
220
+ $ pom status > /dev/null
221
+ $ echo $?
222
+ 3
223
+
224
+ * Short status (03:39 remaining in the active pomodoro or break):
225
+
226
+ $ pom status --short
227
+ 03:39
228
+
229
+ * Given a break and a custom status format (resembles the format switch analog to `date +%Y-%m-%dT%H:%M:%S`):
230
+
231
+ $ pom status --format %C-%M:%S
232
+ B03:39
233
+
234
+ * Output in JSON format
235
+
236
+ $ pom status --format JSON
237
+ {
238
+ "status": {
239
+ "state": "break",
240
+ "length": 600,
241
+ "remaining": 363,
242
+ "start": 1373988295,
243
+ "end": 1373988595
244
+ }
245
+ }
246
+
247
+ ## Reports
248
+
249
+ $ pom report
250
+ Daily Pomodoro Report for 2013-07-16
251
+
252
+ 3 pomodori finished
253
+ 1 pomodoro cancelled
254
+ 1 internal interruptions
255
+ 2 external interruptions
256
+ 4 breaks (3 short, 1 long; 45 minutes in total)
257
+
258
+ Most efficient location: Home Office
259
+ Least efficient location: Coffeshop
260
+
261
+ By default, the command groups by `--day`. Alternative options are `--week`, `--month` or `--year`. Without a value, the argument assumes the current day / week / month / year. The first day of the period can be specified as argument, e.g. `pom report --day=2013-07-18`. The period is parsed with [Chronic](http://chronic.rubyforge.org/), which also enables symbolic values like `pom report --month="last month"`.
262
+
263
+ The report can also be grouped by location:
264
+
265
+ $ pom report location
266
+ Pomodoro Location Report
267
+
268
+ Home Office: 38 finished, 12 cancelled, 21 interrupts
269
+ Starbucks: 12 finished, 2 cancelled, 45 interrupts
270
+ On the road: 14 finished, 0 cancelled, 12 interrupts
271
+
272
+ The following locations do not have a label. Assign it with
273
+
274
+ $ pom location macbook@01:23:45:67:89:0A "Your Label"
275
+
276
+ Detailed report for a single location:
277
+
278
+ $ pom report location "Home Office"
279
+ Pomodoro Location Report for Home Office
280
+
281
+ 58 pomodori finished
282
+ 12 pomodoro cancelled
283
+ 8 internal interruptions
284
+ 13 external interruptions
285
+ 18 breaks (13 short, 5 long; 4 hours in total)
286
+
287
+ Efficiency: 0.8 (10% over median)
288
+
289
+ Further grouping is also possible, e.g. by year:
290
+
291
+ $ pom report location --year=2012
292
+ Pomodoro Location Report for 2012
293
+
294
+ 233 pomodori finished
295
+ 41 pomodoro cancelled
296
+ 33 internal interruptions
297
+ 123 external interruptions
298
+ 98 breaks (63 short, 35 long; 45 hours in total)
299
+
300
+ Most efficient month: September (0.75)
301
+ Least efficient month: July (0.58)
302
+
303
+ ### Efficiency
304
+ The efficiency is calculated from the number of successful vs. cancelled pomodori, together with the number interruptions. Breaks are not counted towards efficiency.
305
+
306
+ Efficiency can be reported by day, week, month, year, or location:
307
+
308
+ $ pom report efficiency
309
+ Efficiency Report for 2013-07-16
310
+
311
+ TODO
312
+
313
+ ### Analytics
314
+
315
+ * Influence of interrupts to efficiency
316
+ - 80% of the internal interruptions happen within 5 minutes after the start
317
+ - In the last 3 months, the ratio between (internal / external) interrupts and cancelled pomodori improved by 10% from 0.6 to 0.5.
318
+
319
+ ### Timesheet
320
+
321
+ $ pom timesheet
322
+ TODO Ordered list of pomodori and breaks, each with annotations (like a time sheet)
323
+
324
+ The same options as for regular reports apply. The timesheet report also details the efficiency of each location.
325
+
326
+ ### Exporting a Report
327
+
328
+ $ pom report --weekly --format JSON # weekly report in JSON format
329
+ {
330
+ "TODO": "Specify"
331
+ }
332
+
333
+ ## Output Policy
334
+ Paradeiser follows the [Rule of Silence](http://www.faqs.org/docs/artu/ch01s06.html#id2878450). If all goes well, a command will not print any output to `STDOUT` unless `--verbose` is given. `status`, `report` and `timesheet` are exempted from this rule, as their primary purpose is to print to STDOUT.
335
+
336
+ ## Hooks
337
+ Instead of handling tasks itself, Paradeiser integrates with external tools via hooks. Every event will attempt to find and execute an appropriate script in `$POM_DIR/hooks/`. Sufficient information will be made available via environment variables.
338
+
339
+ `before-` hooks will be called before the action is executed internally. If a `before-`hook exits non-zero, paradeiser will abort the action and exit non-zero itself; indicating in a message to STDERR which hook caused the abort.
340
+
341
+ `after-` hooks will be called after the action was executed internally. The exit status of a `post`-hook will be passed through paradeiser, but it will not affect the execution of the action anymore.
342
+
343
+ ### Available Hooks
344
+
345
+ * `before-start` is called after the `start` command was received, but before internal processing for the `start` action begins
346
+ * `after-start` is called after all interal processing for the `start` action ended
347
+ * `before-finish` is called after the timer of the current pomodoro fired (the pomodoro is over), but before internal processing for the `finish` action begins
348
+ * `after-finish` is called after all interal processing for the `finish` action ended
349
+ * `before-interrupt` is called after the `interrupt` command was received, but before internal action processing begins
350
+ * `after-interrupt` is called after all interal processing for the `interrupt` action ended
351
+ * `before-cancel` is called after the `cancel` command was received, but before internal action processing begins
352
+ * `after-cancel` is called after all interal processing for the `cancel` action ended
353
+ * `before-break` is called after the `break` command was received, but before internal processing for the `break` action begins
354
+ * `after-break` is called after the timer of the current break fired (the break is over), but after internal processing for the `break` action ended
355
+
356
+ Examples for the use of hooks are:
357
+
358
+ * Displaying a desktop notification on `after-finish`
359
+ * tmux status bar integration like [pomo](https://github.com/visionmedia/pomo) by writing the status to `~/.pomo_stat` from the `after-` hooks.
360
+ * Displaying a desktop notification
361
+
362
+ `$POM_TITLE` is one of the environment variables set by Paradeiser that provides the context for hooks. See below for the full list of available environment variables.
363
+
364
+ ### Edit a hook
365
+
366
+ $ pom edit after-stop
367
+
368
+ Launches `$VISUAL` (or, if empty, `$EDITOR`) with the given hook.
369
+
370
+ ## Environment Variables
371
+
372
+ Variable | Used in | Description
373
+ --- | --- | ---
374
+ `$POM_DIR` | Everywhere | Directory where the data store and the hooks are stored. Defaults to `~/.paradeiser/`.
375
+ `$POM_ID` | Hooks | Identifier of the pomodoro
376
+ `$POM_STARTED_AT` | Hooks | Timestamp of when the pomodoro was started
377
+ `$POM_TITLE` | Hooks | Title of the pomodoro
378
+ `$POM_LOCATION` | Location Commands | Location of the pomodoro
379
+
380
+ ## Configuration File
381
+
382
+ The configuration is stored in a config file. It is user-editable file, but editing the configuration is also exposed with `pom config`.
383
+
384
+ $ pom config dir
385
+ /home/nerab/.paradeiser
386
+
387
+ # Note how setting $POM_DIR affects pom config
388
+ $ POM_DIR=/tmp pom config dir
389
+ /tmp
390
+
391
+ $ pom config dir /var/tmp
392
+ $ pom config dir
393
+ /var/tmp
394
+
395
+ `pom config` shows the _active_ config when called, i.e. it takes `$POM_DIR` into account.
396
+
397
+ Available config variables:
398
+
399
+ Variable | Description
400
+ --- | ---
401
+ `POM_DIR` | Directory where the data store and the hooks are stored. Defaults to `~/.paradeiser/`. Overridden by `$POM_DIR`.
402
+ `AT_QUEUE` | Name of the `at` queue to use. Defaults to `p`.
403
+
404
+ ## Taskwarrior Integration
405
+
406
+ This is deployed to `~/.paradeiser/hooks/after-finish` by default.
407
+
408
+ # exit unless $(task)
409
+
410
+ # find all active
411
+ active = $(task active) # doesn't work yet; find the right column with 'task columns' and the info in http://taskwarrior.org/projects/taskwarrior/wiki/Feature_custom_reports
412
+
413
+ # TODO tag all active tasks so that we can re-start them
414
+
415
+ # stop all active
416
+ task $(task active) stop
417
+
418
+ ## Similar Projects
419
+
420
+ These and many more exist, why another tool?
421
+
422
+ * https://github.com/visionmedia/pomo
423
+ * https://github.com/stefanoverna/redpomo
424
+
425
+ They have a lot of what I wanted, but pomo focuses very much on the tasks themselves and less on the pomodori.
426
+
427
+ ## Implementation Notes
428
+
429
+ ### State Machine
430
+ Paradeiser uses a [state machine](https://github.com/pluginaweek/state_machine) to model a pomodoro. Internal event handlers do the actual work; among them is the task of calling the external hooks.
431
+
432
+ ![State Transition Diagram](https://rawgithub.com/nerab/paradeiser/master/doc/Paradeiser::Pomodoro_status.svg)
433
+
434
+ The graph was created using the rake task that comes with `state_machine`:
435
+
436
+ rake state_machine:draw CLASS=Paradeiser::Pomodoro TARGET=doc FORMAT=svg HUMAN_NAMES=true ORIENTATION=landscape
437
+
438
+ ### I18N
439
+ Paradeiser uses [I18N](https://github.com/svenfuchs/i18n) to translate messages and localize time and date formats.
440
+
441
+ ## API
442
+ The actual storage backend is *not a public API* and may change at any given time. External tools should use the Ruby API instead, or rely on the JSON export / import.
443
+
444
+ ## Sync
445
+ In todays world of distributed devices, synching data is a problem almost every app needs to solve. Paradeiser is no exception - it is very easy to come up with use cases that involve many computers. Maybe the user has different devices (mobile and desktop), maybe the user works in different environments, and wants to record all pomodori into a single system.
446
+
447
+ There are many potential solutions to this problem:
448
+
449
+ 1. A centralized server could host a data store (relational database, NoSQL store, etc.)
450
+ 1. A shared directory could host a single file and access could be coordinated using lock files
451
+ 1. Commercial solutions like the [Dropbox Sync API](https://www.dropbox.com/developers/sync)
452
+ 1. Custom solutions like [toystore](https://github.com/jnunemaker/toystore) with a [git adapter](https://github.com/bkeepers/adapter-git)
453
+
454
+ All of these are too much overhead right now, so the decision was made to keep Paradeiser simple and not implement any sync features. This may me revisited in a later version of the app.
455
+
456
+ ## Location
457
+ In order to record the current location at the start of the pomodoro, Paradeiser will record the hostname and the MAC address of the default gateway:
458
+
459
+ * OSX
460
+
461
+ arp 0.0.0.0 | head -1 | awk {'print $4'}
462
+
463
+ * Linux:
464
+
465
+ GATEWAY=$(netstat -rn | grep "^0.0.0.0" | cut -c17-31); ping -c1 $GATEWAY >/dev/null; arp -n $GATEWAY | tail -n1 | cut -c34-50
466
+
467
+ The location is then used to assign a label to one or more hostname@MAC strings, which will be used in a report.
468
+
469
+ ## Issues
470
+
471
+ 1. `at` is disabled on MacOS by default. You will need to [turn it on](https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man8/atrun.8.html) in order to make Paradeiser work correctly.
472
+
473
+ ## What about the app's name?
474
+ In Austrian German, "Paradeiser" means tomato, of which the Italian translation is pomodoro.