paradeiser 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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