paradeiser 0.2.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Guardfile +1 -0
  4. data/README.md +98 -6
  5. data/TODO.md +22 -6
  6. data/VISION.md +171 -26
  7. data/bin/par +20 -4
  8. data/lib/paradeiser/controllers/breaks_controller.rb +0 -1
  9. data/lib/paradeiser/controllers/controller.rb +15 -1
  10. data/lib/paradeiser/controllers/paradeiser_controller.rb +23 -1
  11. data/lib/paradeiser/controllers/pomodori_controller.rb +23 -1
  12. data/lib/paradeiser/errors.rb +6 -0
  13. data/lib/paradeiser/executor.rb +1 -1
  14. data/lib/paradeiser/initializers/inflections.rb +3 -0
  15. data/lib/paradeiser/models/break.rb +1 -0
  16. data/lib/paradeiser/models/pomodoro.rb +13 -1
  17. data/lib/paradeiser/models/repository.rb +11 -1
  18. data/lib/paradeiser/models/scheduled.rb +7 -1
  19. data/lib/paradeiser/refinements/pluralize.rb +10 -0
  20. data/lib/paradeiser/router.rb +0 -2
  21. data/lib/paradeiser/version.rb +1 -1
  22. data/lib/paradeiser/view.rb +1 -1
  23. data/lib/paradeiser/views/breaks/finish.erb +1 -0
  24. data/lib/paradeiser/views/breaks/start.erb +1 -0
  25. data/lib/paradeiser/views/paradeiser/init.erb +1 -1
  26. data/lib/paradeiser/views/paradeiser/report.erb +10 -4
  27. data/lib/paradeiser/views/paradeiser/status.erb +2 -1
  28. data/lib/paradeiser/views/pomodori/annotate.erb +1 -0
  29. data/lib/paradeiser/views/pomodori/cancel.erb +1 -0
  30. data/lib/paradeiser/views/pomodori/interrupt.erb +1 -0
  31. data/lib/paradeiser/views/pomodori/log.erb +1 -0
  32. data/lib/paradeiser/views/pomodori/start.erb +1 -1
  33. data/paradeiser.gemspec +2 -0
  34. data/test/bin/notify-send +1 -0
  35. data/test/helper.rb +7 -24
  36. data/test/integration/test_annotate.rb +19 -0
  37. data/test/integration/test_finish.rb +9 -0
  38. data/test/integration/test_interrupt.rb +9 -0
  39. data/test/integration/test_log.rb +12 -0
  40. data/test/integration/test_no_args.rb +7 -0
  41. data/test/integration/test_start.rb +7 -0
  42. data/test/integration/test_status.rb +10 -0
  43. data/test/integration/test_unknown.rb +7 -0
  44. data/test/lib/at_mock.rb +1 -1
  45. data/test/lib/controller_test.rb +25 -0
  46. data/test/lib/integration_test.rb +45 -0
  47. data/test/lib/paradeiser_controller_test.rb +7 -0
  48. data/test/lib/view_test.rb +12 -0
  49. data/test/unit/test_break.rb +7 -44
  50. data/test/unit/test_break_view.rb +22 -0
  51. data/test/unit/test_breaks_controller.rb +66 -0
  52. data/test/unit/test_paradeiser_controller_export.rb +107 -0
  53. data/test/unit/test_paradeiser_controller_report.rb +73 -26
  54. data/test/unit/test_paradeiser_controller_status.rb +42 -24
  55. data/test/unit/test_paradeiser_view_init.rb +7 -0
  56. data/test/unit/test_paradeiser_view_report.rb +132 -0
  57. data/test/unit/test_paradeiser_view_status.rb +17 -0
  58. data/test/unit/test_pomodori_controller.rb +241 -33
  59. data/test/unit/test_pomodori_view.rb +26 -13
  60. data/test/unit/test_pomodoro.rb +23 -81
  61. data/test/unit/test_pomodoro_hooks.rb +12 -25
  62. data/test/unit/test_repository.rb +5 -21
  63. data/test/unit/test_scheduler.rb +1 -1
  64. metadata +61 -8
  65. data/test/integration/test_par.rb +0 -17
  66. data/test/unit/test_break_controller.rb +0 -56
  67. data/test/unit/test_paradeiser_view.rb +0 -66
data/bin/par CHANGED
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler'
4
- Bundler.require
5
-
6
3
  require 'commander/import'
7
4
  require 'paradeiser'
8
5
  include Paradeiser
@@ -52,6 +49,20 @@ begin
52
49
  end
53
50
  alias_command :interrupt, :'pomodoro interrupt'
54
51
 
52
+ command :'pomodoro annotate' do |c|
53
+ c.syntax = "#{program(:name)} #{c.name}"
54
+ c.summary = 'Annotates the active or most recent pomodoro'
55
+ c.action router.dispatch(c)
56
+ end
57
+ alias_command :annotate, :'pomodoro annotate'
58
+
59
+ command :'pomodoro log' do |c|
60
+ c.syntax = "#{program(:name)} #{c.name}"
61
+ c.summary = 'Logs an already finished pomodoro'
62
+ c.action router.dispatch(c)
63
+ end
64
+ alias_command :log, :'pomodoro log'
65
+
55
66
  command :'break start' do |c|
56
67
  c.syntax = "#{program(:name)} #{c.name}"
57
68
  c.summary = 'Start a break'
@@ -71,10 +82,15 @@ begin
71
82
  c.action router.dispatch(c)
72
83
  end
73
84
 
85
+ command :export do |c|
86
+ c.syntax = "#{program(:name)} #{c.name}"
87
+ c.summary = 'Export all pomodori as JSON'
88
+ c.action router.dispatch(c)
89
+ end
90
+
74
91
  command :status do |c|
75
92
  c.syntax = "#{program(:name)} #{c.name}"
76
93
  c.summary = 'Show status of active pomodoro or break'
77
- c.option '--quiet', 'Be quiet - no output is printed. The exit code reflects the status.'
78
94
  c.action router.dispatch(c)
79
95
  end
80
96
  rescue
@@ -4,7 +4,6 @@ module Paradeiser
4
4
  raise SingletonError.new(Break, Repository.active, :start) if Repository.active?
5
5
 
6
6
  @break = Break.new
7
- @break.start!
8
7
  Repository.save(@break)
9
8
  end
10
9
 
@@ -4,7 +4,7 @@ module Paradeiser
4
4
 
5
5
  def initialize(method)
6
6
  @method = method
7
- @exitstatus = -1
7
+ @exitstatus = 0
8
8
  @has_output = false
9
9
  end
10
10
 
@@ -12,6 +12,7 @@ module Paradeiser
12
12
  @args = args
13
13
  @options = options
14
14
  send(@method)
15
+ render
15
16
  end
16
17
 
17
18
  def model
@@ -22,6 +23,19 @@ module Paradeiser
22
23
  return binding
23
24
  end
24
25
 
26
+ def render(options = {})
27
+ return if @already_rendered # render only once
28
+ return unless (@options && @options.verbose) || has_output
29
+
30
+ if options.has_key?(:text)
31
+ puts options[:text]
32
+ else
33
+ puts View.new(model, @method).render(binding)
34
+ end
35
+
36
+ @already_rendered = true
37
+ end
38
+
25
39
  protected
26
40
 
27
41
  attr_writer :exitstatus, :has_output
@@ -1,4 +1,8 @@
1
1
  require 'fileutils'
2
+ #require 'action_view/helpers/text_helper'
3
+ require 'active_support/core_ext/enumerable'
4
+ require 'active_model'
5
+ require 'active_model/serializers/json'
2
6
 
3
7
  module Paradeiser
4
8
  class ParadeiserController < Controller
@@ -8,7 +12,19 @@ module Paradeiser
8
12
  end
9
13
 
10
14
  def report
11
- @pom = Repository.all
15
+ pomodori = Repository.all_pomodori
16
+
17
+ @finished = pomodori.select{|p| p.finished?}.size
18
+ @canceled = pomodori.select{|p| p.canceled?}.size
19
+ @external_interrupts = pomodori.map{|p| p.interrupts}.flatten.select{|i| :external == i.type}.size
20
+ @internal_interrupts = pomodori.map{|p| p.interrupts}.flatten.select{|i| :internal == i.type}.size
21
+
22
+ breaks = Repository.all_breaks
23
+ @breaks = breaks.size
24
+ @break_minutes = breaks.sum{|b| b.duration}.to_i.minutes
25
+
26
+ @annotations = pomodori.collect{|p| p.annotations}.flatten
27
+
12
28
  self.has_output = true
13
29
  end
14
30
 
@@ -16,6 +32,12 @@ module Paradeiser
16
32
  @pom = Repository.active || Repository.all.last
17
33
  self.exitstatus = Status.of(@pom).to_i
18
34
  self.has_output = true
35
+ render(:text => 'There are no pomodori or breaks.') unless @pom
36
+ end
37
+
38
+ def export
39
+ self.has_output = true
40
+ render(:text => Repository.all.to_json)
19
41
  end
20
42
  end
21
43
  end
@@ -7,7 +7,6 @@ module Paradeiser
7
7
  raise SingletonError.new(Pomodoro, Repository.active, :start) if Repository.active?
8
8
 
9
9
  @pom = Pomodoro.new
10
- @pom.start!
11
10
  Repository.save(@pom)
12
11
  end
13
12
 
@@ -15,6 +14,7 @@ module Paradeiser
15
14
  @pom = Repository.active
16
15
  raise NotActiveError unless @pom
17
16
  raise SingletonError.new(Pomodoro, @pom, :finish) if Repository.active? && !@pom.kind_of?(Pomodoro)
17
+ @pom.annotate(@args.join(' ')) if @args.any?
18
18
  @pom.cancel!
19
19
  Repository.save(@pom)
20
20
  end
@@ -23,6 +23,7 @@ module Paradeiser
23
23
  @pom = Repository.active
24
24
  raise NotActiveError unless @pom
25
25
  raise SingletonError.new(Pomodoro, @pom, :finish) if Repository.active? && !@pom.kind_of?(Pomodoro)
26
+ @pom.annotate(@args.join(' ')) if @args.any?
26
27
  @pom.finish!
27
28
  Repository.save(@pom)
28
29
  end
@@ -32,15 +33,36 @@ module Paradeiser
32
33
  raise NotActiveError unless @pom
33
34
  raise SingletonError.new(Pomodoro, @pom, :interrupt) if Repository.active? && !@pom.kind_of?(Pomodoro)
34
35
 
36
+ @pom.annotate(@args.join(' ')) if @args.any?
37
+
35
38
  if @options.external
39
+ @interrupt_type = 'externally'
36
40
  @pom.interrupt!(:external)
37
41
  else
42
+ @interrupt_type = 'internally'
38
43
  @pom.interrupt!
39
44
  end
40
45
 
41
46
  Repository.save(@pom)
42
47
  end
43
48
 
49
+ def annotate
50
+ raise MissingAnnotationError unless @args && @args.any?
51
+ @pom = Repository.all.select{|p| p.kind_of?(Pomodoro)}.sort{|a,b| a.started_at <=> b.started_at}.last
52
+ @pom.annotate(@args.join(' '))
53
+ Repository.save(@pom)
54
+ end
55
+
56
+ def log
57
+ @pom = Pomodoro.new
58
+ @pom.id = Repository.next_id
59
+ @pom.annotate(@args.join(' ')) if @args.any?
60
+ @pom.status = :finished
61
+ @pom.finished_at = Time.now
62
+ @pom.started_at = @pom.finished_at - Pomodoro::MINUTES_25 * 60
63
+ Repository.save(@pom)
64
+ end
65
+
44
66
  private
45
67
 
46
68
  def end_break
@@ -34,4 +34,10 @@ module Paradeiser
34
34
  super("'#{type}' is not a valid type. Valid are only #{choices}.")
35
35
  end
36
36
  end
37
+
38
+ class MissingAnnotationError < StandardError
39
+ def initialize
40
+ super('The mandatory text is missing for the annotation')
41
+ end
42
+ end
37
43
  end
@@ -12,6 +12,6 @@ module Executor
12
12
  end
13
13
 
14
14
  def queue
15
- 'p'
15
+ ENV['PAR_AT_QUEUE'] || 'p'
16
16
  end
17
17
  end
@@ -1,4 +1,7 @@
1
1
  ActiveSupport::Inflector.inflections do |inflect|
2
+ inflect.irregular 'paradeiser', 'paradeiser'
2
3
  inflect.irregular 'pomodoro', 'pomodori'
4
+ inflect.irregular 'interrupt', 'interrupts'
3
5
  inflect.irregular 'break', 'breaks'
6
+ inflect.irregular 'minute', 'minutes'
4
7
  end
@@ -31,6 +31,7 @@ module Paradeiser
31
31
  def initialize(length = 300.seconds)
32
32
  super() # required for state_machine
33
33
  @length = length
34
+ start!
34
35
  end
35
36
 
36
37
  def length
@@ -1,6 +1,6 @@
1
1
  module Paradeiser
2
2
  class Pomodoro < Scheduled
3
- attr_reader :interrupts, :interrupt_type
3
+ attr_reader :interrupts, :interrupt_type, :annotations
4
4
  attr_accessor :canceled_at
5
5
 
6
6
  MINUTES_25 = 25
@@ -52,6 +52,8 @@ module Paradeiser
52
52
  def initialize
53
53
  super # required for state_machine
54
54
  @interrupts = []
55
+ @annotations = []
56
+ start!
55
57
  end
56
58
 
57
59
  def length
@@ -62,5 +64,15 @@ module Paradeiser
62
64
  @interrupt_type = type
63
65
  super
64
66
  end
67
+
68
+ def duration
69
+ start = started_at || Time.now
70
+ finish = finished_at || canceled_at || Time.now
71
+ (finish - start).to_i
72
+ end
73
+
74
+ def annotate(text)
75
+ @annotations << text
76
+ end
65
77
  end
66
78
  end
@@ -7,6 +7,14 @@ module Paradeiser
7
7
  end
8
8
  end
9
9
 
10
+ def all_pomodori
11
+ all.select{|p| p.kind_of?(Pomodoro)}
12
+ end
13
+
14
+ def all_breaks
15
+ all.select{|b| b.kind_of?(Break)}
16
+ end
17
+
10
18
  def any?(&blk)
11
19
  all.any?(&blk)
12
20
  end
@@ -40,7 +48,9 @@ module Paradeiser
40
48
 
41
49
  def save(pom)
42
50
  raise IllegalStatusError if pom.idle?
43
- raise SingletonError.new(pom.class, self.active, :save) if self.active? && active.id != pom.id
51
+
52
+ # Do not allow saving of a new active pomodoro while another pomodoro or break is active
53
+ raise SingletonError.new(pom.class, self.active, :save) if self.active? && pom.new?
44
54
 
45
55
  pom.id = next_id if pom.new?
46
56
  backend.transaction do
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/enumerable'
2
+
1
3
  module Paradeiser
2
4
  class Scheduled
3
5
  attr_accessor :id, :started_at, :finished_at
@@ -9,7 +11,7 @@ module Paradeiser
9
11
  # from https://github.com/travis-ci/travis/blob/master/lib/travis/client/job.rb
10
12
  def duration
11
13
  start = started_at || Time.now
12
- finish = finished_at || (respond_to?(:canceled_at) ? canceled_at : nil) || Time.now
14
+ finish = finished_at || Time.now
13
15
  (finish - start).to_i
14
16
  end
15
17
 
@@ -21,5 +23,9 @@ module Paradeiser
21
23
  def name
22
24
  self.class.name.split("::").last.downcase
23
25
  end
26
+
27
+ def as_json(*options)
28
+ {:type => name.titlecase, :length => length}.merge(super(*options))
29
+ end
24
30
  end
25
31
  end
@@ -0,0 +1,10 @@
1
+ # stand-in for 'action_view/helpers/text_helper'
2
+ def pluralize(count, singular, plural = nil)
3
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
4
+ singular
5
+ else
6
+ plural || singular.pluralize
7
+ end
8
+
9
+ "#{count || 0} #{word}"
10
+ end
@@ -25,8 +25,6 @@ module Paradeiser
25
25
  controller = controller_class.new(verb)
26
26
  controller.call(args, options)
27
27
 
28
- View.new(controller.model, verb).render(controller.get_binding) if options.verbose || controller.has_output
29
-
30
28
  @status = controller.exitstatus
31
29
  end
32
30
  end
@@ -1,3 +1,3 @@
1
1
  module Paradeiser
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -5,7 +5,7 @@ module Paradeiser
5
5
  end
6
6
 
7
7
  def render(controller_binding)
8
- puts(template.result(controller_binding))
8
+ template.result(controller_binding)
9
9
  end
10
10
 
11
11
  private
@@ -0,0 +1 @@
1
+ Finished break #<%= @break.id %> after <%= @break.duration.minutes %> minutes.
@@ -0,0 +1 @@
1
+ Started a new break (<%= @break.length.minutes %> minutes).
@@ -1 +1 @@
1
- Suffessfully initialized <%= Paradeiser.par_dir %>.
1
+ Successfully initialized <%= Paradeiser.par_dir %>.
@@ -1,5 +1,11 @@
1
- ID | Name | Status | Started | Ended | Interrupts
2
- <% @pom.each do |pom| %>
3
- <% interrupts_by_type = pom.interrupts.group_by{|i| i.type } %>
4
- <%= pom.id %> | <%= pom.name %> | <%= pom.status %> | <%= pom.started_at.strftime('%R') %> | <%= pom.finished_at.strftime('%R') if pom.finished? %><%= pom.canceled_at.strftime('%R') if pom.canceled? %> | <%= interrupts[:internal].size rescue 0 %> I, <%= interrupts_by_type[:external].size rescue 0 %> E
1
+ # Pomodoro Report
2
+ - <%= pluralize(@finished, 'pomodoro') %> finished
3
+ - <%= pluralize(@canceled, 'pomodoro') %> canceled
4
+ - <%= pluralize(@internal_interrupts, 'internal interrupt') %>
5
+ - <%= pluralize(@external_interrupts, 'external interrupt') %>
6
+ - <%= pluralize(@breaks, 'break') %> (<%= pluralize(@break_minutes, 'minute') %> in total)
7
+ <% if @annotations.any? %>
8
+
9
+ ## Annotations<% @annotations.each do |annotation| %>
10
+ * <%= annotation %><% end %>
5
11
  <% end %>
@@ -1,4 +1,5 @@
1
- <% case @pom.status_name
1
+ <%
2
+ case @pom.status_name
2
3
  when :active
3
4
  %><%= @pom.name.capitalize %> #<%= @pom.id %> is active for another <%= @pom.remaining.minutes %> minutes (started at <%= @pom.started_at.strftime('%R') %>).<% unless !@pom.respond_to?(:interrupts) || @pom.interrupts.empty? %> <%= @pom.interrupts.size %> interrupts on record.<% end %><%
4
5
  when :break
@@ -0,0 +1 @@
1
+ Successfully annotated pomodoro #<%= @pom.id %>.
@@ -0,0 +1 @@
1
+ Canceled pomodoro #<%= @pom.id %> after <%= @pom.duration.minutes %> minutes.
@@ -0,0 +1 @@
1
+ Marked pomodoro #<%= @pom.id %> as <%= @interrupt_type %> interrupted.
@@ -0,0 +1 @@
1
+ Successfully logged pomodoro #<%= @pom.id %>.
@@ -1 +1 @@
1
- Starting pomodoro #<%= @pom.id %>.
1
+ Started pomodoro #<%= @pom.id %>.
@@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency 'state_machine'
24
24
  spec.add_runtime_dependency 'activesupport'
25
25
  spec.add_runtime_dependency 'actionpack'
26
+ # spec.add_runtime_dependency 'actionview'
27
+ spec.add_runtime_dependency 'activemodel'
26
28
 
27
29
  # Moved to the Gemfile so that Travis CI can load the test group
28
30
  # spec.add_development_dependency 'rake'
@@ -0,0 +1 @@
1
+ true
@@ -10,9 +10,13 @@ class MiniTest::Test
10
10
 
11
11
  protected
12
12
 
13
- def start!(thing = @pom || @break)
14
- Scheduler.stub(:add, nil) do
15
- thing.start!
13
+ def produce(clazz)
14
+ @started = srand
15
+
16
+ Time.stub :now, Time.at(@started) do
17
+ Scheduler.stub(:add, nil) do
18
+ clazz.new
19
+ end
16
20
  end
17
21
  end
18
22
 
@@ -34,24 +38,3 @@ protected
34
38
  end
35
39
  end
36
40
  end
37
-
38
- class ControllerTest < MiniTest::Test
39
-
40
- protected
41
-
42
- def invoke(method, *attributes)
43
- controller = ParadeiserController.new(method)
44
-
45
- Repository.stub :backend, @backend do
46
- Scheduler.stub(:add, nil) do
47
- Scheduler.stub(:clear, nil) do
48
- controller.call(nil, nil)
49
- end
50
- end
51
- end
52
-
53
- attributes.map do |attribute|
54
- controller.get_binding.eval(attribute)
55
- end
56
- end
57
- end