paradeiser 0.1.0 → 0.2.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +96 -35
  4. data/TODO.md +14 -15
  5. data/VISION.md +92 -84
  6. data/bin/{pom → par} +32 -2
  7. data/doc/Paradeiser::Break_status.svg +50 -0
  8. data/doc/Paradeiser::Pomodoro_status.svg +40 -22
  9. data/lib/paradeiser.rb +3 -3
  10. data/lib/paradeiser/controllers/breaks_controller.rb +19 -0
  11. data/lib/paradeiser/controllers/controller.rb +3 -3
  12. data/lib/paradeiser/controllers/paradeiser_controller.rb +13 -2
  13. data/lib/paradeiser/controllers/pomodori_controller.rb +36 -17
  14. data/lib/paradeiser/errors.rb +12 -6
  15. data/lib/paradeiser/executor.rb +2 -0
  16. data/lib/paradeiser/initializers/inflections.rb +4 -0
  17. data/lib/paradeiser/models/break.rb +40 -0
  18. data/lib/paradeiser/models/hook.rb +4 -4
  19. data/lib/paradeiser/models/interrupt.rb +18 -0
  20. data/lib/paradeiser/models/job.rb +1 -1
  21. data/lib/paradeiser/models/pomodoro.rb +30 -22
  22. data/lib/paradeiser/models/repository.rb +26 -13
  23. data/lib/paradeiser/models/scheduled.rb +25 -0
  24. data/lib/paradeiser/models/scheduler.rb +1 -1
  25. data/lib/paradeiser/models/status.rb +21 -0
  26. data/lib/paradeiser/{refinements.rb → refinements/numeric.rb} +4 -0
  27. data/lib/paradeiser/router.rb +12 -7
  28. data/lib/paradeiser/version.rb +1 -1
  29. data/lib/paradeiser/views/paradeiser/init.erb +1 -1
  30. data/lib/paradeiser/views/paradeiser/report.erb +5 -0
  31. data/lib/paradeiser/views/paradeiser/status.erb +13 -0
  32. data/paradeiser.gemspec +2 -0
  33. data/templates/linux/hooks/after-finish-break +10 -0
  34. data/templates/linux/hooks/after-finish-pomodoro +10 -0
  35. data/templates/linux/hooks/after-start-break +7 -0
  36. data/templates/linux/hooks/after-start-pomodoro +7 -0
  37. data/templates/mac/hooks/after-finish-break +10 -0
  38. data/templates/mac/hooks/after-finish-pomodoro +10 -0
  39. data/templates/mac/hooks/after-start-break +7 -0
  40. data/templates/mac/hooks/after-start-pomodoro +7 -0
  41. data/test/helper.rb +37 -4
  42. data/test/integration/{test_pom.rb → test_par.rb} +4 -4
  43. data/test/lib/{pomodoro_mock.rb → schedulable_mock.rb} +9 -1
  44. data/test/unit/test_break.rb +99 -0
  45. data/test/unit/test_break_controller.rb +56 -0
  46. data/test/unit/test_interrupt.rb +36 -0
  47. data/test/unit/test_paradeiser_controller_init.rb +92 -0
  48. data/test/unit/test_paradeiser_controller_report.rb +44 -0
  49. data/test/unit/test_paradeiser_controller_status.rb +70 -0
  50. data/test/unit/test_paradeiser_view.rb +66 -0
  51. data/test/unit/test_pomodori_controller.rb +87 -31
  52. data/test/unit/test_pomodori_view.rb +0 -50
  53. data/test/unit/test_pomodoro.rb +131 -9
  54. data/test/unit/test_pomodoro_hooks.rb +165 -17
  55. data/test/unit/test_repository.rb +38 -15
  56. data/test/unit/test_router.rb +4 -4
  57. data/test/unit/test_status.rb +26 -0
  58. metadata +70 -17
  59. data/lib/paradeiser/views/pomodori/report.erb +0 -5
  60. data/lib/paradeiser/views/pomodori/status.erb +0 -9
  61. data/templates/linux/hooks/after-finish +0 -10
  62. data/templates/linux/hooks/after-start +0 -7
  63. data/templates/mac/hooks/after-finish +0 -10
  64. data/templates/mac/hooks/after-start +0 -7
  65. data/test/unit/test_paradeiser_controller.rb +0 -88
@@ -0,0 +1,18 @@
1
+ module Paradeiser
2
+ class Interrupt
3
+ KNOWN_TYPES = [:internal, :external]
4
+ attr_reader :created_at, :type
5
+
6
+ def initialize(type = nil)
7
+ @type = type || :internal
8
+ raise InvalidTypeError.new(@type, KNOWN_TYPES) unless KNOWN_TYPES.include?(@type)
9
+ @created_at = Time.now
10
+ end
11
+ end
12
+
13
+ class ExternalInterrupt < Interrupt
14
+ def initialize
15
+ super(:external)
16
+ end
17
+ end
18
+ end
@@ -4,7 +4,7 @@ module Paradeiser
4
4
 
5
5
  attr_reader :id
6
6
 
7
- JOB_PATTERN = %r{^pom .+$}
7
+ JOB_PATTERN = %r|^#{BIN_PAR} .+$|
8
8
 
9
9
  def initialize(id)
10
10
  @id = id
@@ -1,32 +1,46 @@
1
1
  module Paradeiser
2
- class Pomodoro
3
- LENGTH_SECONDS = 25 * 60
2
+ class Pomodoro < Scheduled
3
+ attr_reader :interrupts, :interrupt_type
4
+ attr_accessor :canceled_at
4
5
 
5
- attr_accessor :id, :started_at, :finished_at
6
+ MINUTES_25 = 25
6
7
 
7
8
  state_machine :status, :initial => :idle do
8
9
  event :start do
9
10
  transition :idle => :active
10
11
  end
11
12
 
13
+ event :interrupt do
14
+ transition :active => :active
15
+ end
16
+
17
+ event :cancel do
18
+ transition :active => :canceled
19
+ end
20
+
12
21
  event :finish do
13
22
  transition :active => :finished
14
23
  end
15
24
 
16
- state :finished
17
- state :active
18
- state :idle
19
-
20
25
  after_transition :on => :start do |pom, transition|
21
26
  pom.started_at = Time.now
22
27
  Scheduler.clear # There must be no other jobs scheduled because of Rule #1
23
- Scheduler.add(:finish, LENGTH_SECONDS.minutes)
28
+ Scheduler.add(:"#{pom.name} finish", pom.length.minutes)
29
+ end
30
+
31
+ after_transition :on => :interrupt do |pom, transition|
32
+ pom.interrupts << Interrupt.new(pom.interrupt_type)
24
33
  end
25
34
 
26
35
  around_transition do |pom, transition, block|
27
- Hook.new(:before).execute(pom, transition)
36
+ Hook.new(:before).execute(pom, transition.event)
28
37
  block.call
29
- Hook.new(:after).execute(pom, transition)
38
+ Hook.new(:after).execute(pom, transition.event)
39
+ end
40
+
41
+ after_transition :on => :cancel do |pom, transition|
42
+ pom.canceled_at = Time.now
43
+ Scheduler.clear # There must be no other jobs scheduled because of Rule #1
30
44
  end
31
45
 
32
46
  after_transition :on => :finish do |pom, transition|
@@ -37,22 +51,16 @@ module Paradeiser
37
51
 
38
52
  def initialize
39
53
  super # required for state_machine
54
+ @interrupts = []
40
55
  end
41
56
 
42
- def new?
43
- @id.nil?
44
- end
45
-
46
- # from https://github.com/travis-ci/travis/blob/master/lib/travis/client/job.rb
47
- def duration
48
- start = started_at || Time.now
49
- finish = finished_at || Time.now
50
- (finish - start).to_i
57
+ def length
58
+ MINUTES_25 * 60
51
59
  end
52
60
 
53
- def remaining
54
- raise NoActivePomodoroError if !active?
55
- LENGTH_SECONDS - Time.now.to_i + started_at.to_i
61
+ def interrupt!(type = nil)
62
+ @interrupt_type = type
63
+ super
56
64
  end
57
65
  end
58
66
  end
@@ -16,18 +16,31 @@ module Paradeiser
16
16
  end
17
17
 
18
18
  def active
19
- all_active = find{|pom| pom.active?}
20
- raise SingletonError.new(all_active.first) if all_active.size > 1
21
- all_active.first
19
+ all_active = find{|pom| pom.active?}.sort{|a,b| a.started_at <=> b.started_at}
20
+
21
+ # Cannot recover from an internal inconsistency.
22
+ if all_active.size > 1
23
+ raise "The repository was corrupted. There are #{all_active.size} active objects, but only one is allowed to be active."
24
+ end
25
+
26
+ all_active.last
22
27
  end
23
28
 
24
29
  def active?
25
30
  !!active
26
31
  end
27
32
 
33
+ def last_finished
34
+ find{|p| p.finished?}.sort{|a,b| a.started_at <=> b.started_at}.last
35
+ end
36
+
37
+ def last_canceled
38
+ find{|p| p.canceled?}.sort{|a,b| a.started_at <=> b.started_at}.last
39
+ end
40
+
28
41
  def save(pom)
29
42
  raise IllegalStatusError if pom.idle?
30
- raise SingletonError.new(self.active) if self.active? && active.id != pom.id
43
+ raise SingletonError.new(pom.class, self.active, :save) if self.active? && active.id != pom.id
31
44
 
32
45
  pom.id = next_id if pom.new?
33
46
  backend.transaction do
@@ -35,23 +48,23 @@ module Paradeiser
35
48
  end
36
49
  end
37
50
 
51
+ def next_id
52
+ if all.empty?
53
+ 1
54
+ else
55
+ all.max{|a, b| a.id <=> b.id}.id + 1
56
+ end
57
+ end
58
+
38
59
  private
39
60
 
40
61
  def backend
41
62
  begin
42
- @backend ||= PStore.new(File.join(Paradeiser.pom_dir, 'repository.pstore'), true)
63
+ @backend ||= PStore.new(File.join(Paradeiser.par_dir, 'repository.pstore'), true)
43
64
  rescue PStore::Error => e
44
65
  raise NotInitializedError.new(e.message)
45
66
  end
46
67
  end
47
-
48
- def next_id
49
- if all.empty?
50
- 1
51
- else
52
- all.max{|a, b| a.id <=> b.id}.id + 1
53
- end
54
- end
55
68
  end
56
69
  end
57
70
  end
@@ -0,0 +1,25 @@
1
+ module Paradeiser
2
+ class Scheduled
3
+ attr_accessor :id, :started_at, :finished_at
4
+
5
+ def new?
6
+ @id.nil?
7
+ end
8
+
9
+ # from https://github.com/travis-ci/travis/blob/master/lib/travis/client/job.rb
10
+ def duration
11
+ start = started_at || Time.now
12
+ finish = finished_at || (respond_to?(:canceled_at) ? canceled_at : nil) || Time.now
13
+ (finish - start).to_i
14
+ end
15
+
16
+ def remaining
17
+ raise NotActiveError unless active?
18
+ length - Time.now.to_i + started_at.to_i
19
+ end
20
+
21
+ def name
22
+ self.class.name.split("::").last.downcase
23
+ end
24
+ end
25
+ end
@@ -17,7 +17,7 @@ module Paradeiser
17
17
  end
18
18
 
19
19
  def add(command, minutes)
20
- _, err = exec("echo pom #{command} | #{at} -q #{queue} now + #{minutes} minutes")
20
+ _, err = exec("echo #{BIN_PAR} #{command} | #{at} -q #{queue} now + #{minutes} minutes")
21
21
  id = parse_add(err.chomp)
22
22
  Job.new(id)
23
23
  end
@@ -0,0 +1,21 @@
1
+ module Paradeiser
2
+ module Status
3
+
4
+ MAP = {
5
+ 'pomodoro:active' => 0,
6
+ 'pomodoro:finished' => 1,
7
+ 'break:active' => 2,
8
+ 'break:finished' => 3,
9
+ }
10
+
11
+ def self.of(thing)
12
+ thing.nil? ? -1 : MAP[key(thing)]
13
+ end
14
+
15
+ private
16
+
17
+ def self.key(thing)
18
+ "#{thing.name}:#{thing.status}"
19
+ end
20
+ end
21
+ end
@@ -2,4 +2,8 @@ class Numeric
2
2
  def minutes
3
3
  self / 60
4
4
  end
5
+
6
+ def seconds
7
+ self
8
+ end
5
9
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
1
3
  module Paradeiser
2
4
  class Router
3
5
  attr_reader :status
@@ -8,19 +10,22 @@ module Paradeiser
8
10
 
9
11
  def dispatch(command)
10
12
  Proc.new do |args, options|
11
- method = command.name
13
+ parts = command.name.split
14
+ resource = parts.shift
15
+ controller_name = "#{resource.pluralize.capitalize}Controller".to_sym
12
16
 
13
- # TODO Dynamically find the controller that handles the method. :pomodoro is the default.
14
- if (:init == method.to_sym)
15
- controller_class = ParadeiserController
17
+ if Paradeiser.const_defined?(controller_name)
18
+ verb = parts.join
19
+ controller_class = Paradeiser.const_get(controller_name)
16
20
  else
17
- controller_class = PomodoriController
21
+ verb = resource
22
+ controller_class = ParadeiserController
18
23
  end
19
24
 
20
- controller = controller_class.new(method)
25
+ controller = controller_class.new(verb)
21
26
  controller.call(args, options)
22
27
 
23
- View.new(controller.model, method).render(controller.get_binding) if options.verbose || controller.has_output
28
+ View.new(controller.model, verb).render(controller.get_binding) if options.verbose || controller.has_output
24
29
 
25
30
  @status = controller.exitstatus
26
31
  end
@@ -1,3 +1,3 @@
1
1
  module Paradeiser
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1 +1 @@
1
- Suffessfully initialized <%= Paradeiser.pom_dir %>.
1
+ Suffessfully initialized <%= Paradeiser.par_dir %>.
@@ -0,0 +1,5 @@
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
5
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <% case @pom.status_name
2
+ when :active
3
+ %><%= @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
+ when :break
5
+ %><%= @pom.name.capitalize %> is active for another <%= @pom.remaining.minutes %> minutes.<%
6
+ when :finished
7
+ %>Nothing active. Last <%= @pom.name %> was finished at <%= @pom.finished_at.strftime('%R') %>.<% unless !@pom.respond_to?(:interrupts) || @pom.interrupts.empty? %> It had <%= @pom.interrupts.size %> interrupts.<% end %><%
8
+ when :canceled
9
+ %>Nothing active. Last <%= @pom.name %> was canceled at <%= @pom.canceled_at.strftime('%R') %>.<% unless !@pom.respond_to?(:interrupts) || @pom.interrupts.empty? %> It had <%= @pom.interrupts.size %> interrupts.<% end %><%
10
+ else
11
+ %>Current state is <%= @pom.status %>.<%
12
+ end
13
+ %>
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency 'commander'
22
22
  spec.add_runtime_dependency 'require_all'
23
23
  spec.add_runtime_dependency 'state_machine'
24
+ spec.add_runtime_dependency 'activesupport'
25
+ spec.add_runtime_dependency 'actionpack'
24
26
 
25
27
  # Moved to the Gemfile so that Travis CI can load the test group
26
28
  # spec.add_development_dependency 'rake'
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-finish-break hook for Paradeiser (called after a break ended). It
5
+ # displays a simple notification.
6
+ #
7
+ # $PAR_BREAK_ID - the ID of the break that just ended
8
+ # $PAR_BREAK_STARTED_AT - the time when the break was started
9
+ #
10
+ notify-send "Pomodoro" "The break $PAR_BREAK_ID (started at $PAR_BREAK_STARTED_AT) is over." -u critical > /dev/null
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-finish-pomodoro hook for Paradeiser (called after a pomodoro ended). It
5
+ # displays a simple notification.
6
+ #
7
+ # $PAR_POMODORO_ID - the ID of the pomodoro that just ended
8
+ # $PAR_POMODORO_STARTED_AT - the time when the pomodoro was started
9
+ #
10
+ notify-send "Pomodoro" "The pomodoro $PAR_POMODORO_ID (started at $PAR_POMODORO_STARTED_AT) is over." -u critical > /dev/null
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-start-break hook for Paradeiser (called after a break started). It
5
+ # displays a simple notification.
6
+ #
7
+ notify-send "Pomodoro" "A new break started." -u critical > /dev/null
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-start-pomodoro hook for Paradeiser (called after a pomodoro started). It
5
+ # displays a simple notification.
6
+ #
7
+ notify-send "Pomodoro" "A new pomodoro started." -u critical > /dev/null
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-finish-break hook for Paradeiser (called after a break ended). It
5
+ # displays a simple notification.
6
+ #
7
+ # $PAR_BREAK_ID - the ID of the break that just ended
8
+ # $PAR_BREAK_STARTED_AT - the time when the break was started
9
+ #
10
+ terminal-notifier-success -message "The break $PAR_BREAK_ID (started at $PAR_BREAK_STARTED_AT) is over." > /dev/null
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-finish-pomodoro hook for Paradeiser (called after a pomodoro ended). It
5
+ # displays a simple notification.
6
+ #
7
+ # $PAR_POMODORO_ID - the ID of the pomodoro that just ended
8
+ # $PAR_POMODORO_STARTED_AT - the time when the pomodoro was started
9
+ #
10
+ terminal-notifier-success -message "The pomodoro $PAR_POMODORO_ID (started at $PAR_POMODORO_STARTED_AT) is over." > /dev/null
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-start-break hook for Paradeiser (called after a break started). It
5
+ # displays a simple notification.
6
+ #
7
+ terminal-notifier-success -message "A new break started." > /dev/null
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # Sample after-start-pomodoro hook for Paradeiser (called after a pomodoro started). It
5
+ # displays a simple notification.
6
+ #
7
+ terminal-notifier-success -message "A new pomodoro started." > /dev/null
@@ -10,15 +10,48 @@ class MiniTest::Test
10
10
 
11
11
  protected
12
12
 
13
- def start!(pom = @pom)
13
+ def start!(thing = @pom || @break)
14
14
  Scheduler.stub(:add, nil) do
15
- pom.start!
15
+ thing.start!
16
16
  end
17
17
  end
18
18
 
19
- def finish!(pom = @pom)
19
+ def interrupt!(type = :internal, pom = @pom)
20
20
  Scheduler.stub(:clear, nil) do
21
- pom.finish!
21
+ pom.interrupt!(type)
22
+ end
23
+ end
24
+
25
+ def finish!(thing = @pom || @break)
26
+ Scheduler.stub(:clear, nil) do
27
+ thing.finish!
28
+ end
29
+ end
30
+
31
+ def cancel!(pom = @pom)
32
+ Scheduler.stub(:clear, nil) do
33
+ pom.cancel!
34
+ end
35
+ end
36
+ 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)
22
55
  end
23
56
  end
24
57
  end