paradeiser 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/README.md +96 -35
- data/TODO.md +14 -15
- data/VISION.md +92 -84
- data/bin/{pom → par} +32 -2
- data/doc/Paradeiser::Break_status.svg +50 -0
- data/doc/Paradeiser::Pomodoro_status.svg +40 -22
- data/lib/paradeiser.rb +3 -3
- data/lib/paradeiser/controllers/breaks_controller.rb +19 -0
- data/lib/paradeiser/controllers/controller.rb +3 -3
- data/lib/paradeiser/controllers/paradeiser_controller.rb +13 -2
- data/lib/paradeiser/controllers/pomodori_controller.rb +36 -17
- data/lib/paradeiser/errors.rb +12 -6
- data/lib/paradeiser/executor.rb +2 -0
- data/lib/paradeiser/initializers/inflections.rb +4 -0
- data/lib/paradeiser/models/break.rb +40 -0
- data/lib/paradeiser/models/hook.rb +4 -4
- data/lib/paradeiser/models/interrupt.rb +18 -0
- data/lib/paradeiser/models/job.rb +1 -1
- data/lib/paradeiser/models/pomodoro.rb +30 -22
- data/lib/paradeiser/models/repository.rb +26 -13
- data/lib/paradeiser/models/scheduled.rb +25 -0
- data/lib/paradeiser/models/scheduler.rb +1 -1
- data/lib/paradeiser/models/status.rb +21 -0
- data/lib/paradeiser/{refinements.rb → refinements/numeric.rb} +4 -0
- data/lib/paradeiser/router.rb +12 -7
- data/lib/paradeiser/version.rb +1 -1
- data/lib/paradeiser/views/paradeiser/init.erb +1 -1
- data/lib/paradeiser/views/paradeiser/report.erb +5 -0
- data/lib/paradeiser/views/paradeiser/status.erb +13 -0
- data/paradeiser.gemspec +2 -0
- data/templates/linux/hooks/after-finish-break +10 -0
- data/templates/linux/hooks/after-finish-pomodoro +10 -0
- data/templates/linux/hooks/after-start-break +7 -0
- data/templates/linux/hooks/after-start-pomodoro +7 -0
- data/templates/mac/hooks/after-finish-break +10 -0
- data/templates/mac/hooks/after-finish-pomodoro +10 -0
- data/templates/mac/hooks/after-start-break +7 -0
- data/templates/mac/hooks/after-start-pomodoro +7 -0
- data/test/helper.rb +37 -4
- data/test/integration/{test_pom.rb → test_par.rb} +4 -4
- data/test/lib/{pomodoro_mock.rb → schedulable_mock.rb} +9 -1
- data/test/unit/test_break.rb +99 -0
- data/test/unit/test_break_controller.rb +56 -0
- data/test/unit/test_interrupt.rb +36 -0
- data/test/unit/test_paradeiser_controller_init.rb +92 -0
- data/test/unit/test_paradeiser_controller_report.rb +44 -0
- data/test/unit/test_paradeiser_controller_status.rb +70 -0
- data/test/unit/test_paradeiser_view.rb +66 -0
- data/test/unit/test_pomodori_controller.rb +87 -31
- data/test/unit/test_pomodori_view.rb +0 -50
- data/test/unit/test_pomodoro.rb +131 -9
- data/test/unit/test_pomodoro_hooks.rb +165 -17
- data/test/unit/test_repository.rb +38 -15
- data/test/unit/test_router.rb +4 -4
- data/test/unit/test_status.rb +26 -0
- metadata +70 -17
- data/lib/paradeiser/views/pomodori/report.erb +0 -5
- data/lib/paradeiser/views/pomodori/status.erb +0 -9
- data/templates/linux/hooks/after-finish +0 -10
- data/templates/linux/hooks/after-start +0 -7
- data/templates/mac/hooks/after-finish +0 -10
- data/templates/mac/hooks/after-start +0 -7
- 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
|
@@ -1,32 +1,46 @@
|
|
1
1
|
module Paradeiser
|
2
|
-
class Pomodoro
|
3
|
-
|
2
|
+
class Pomodoro < Scheduled
|
3
|
+
attr_reader :interrupts, :interrupt_type
|
4
|
+
attr_accessor :canceled_at
|
4
5
|
|
5
|
-
|
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,
|
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
|
43
|
-
|
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
|
54
|
-
|
55
|
-
|
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
|
-
|
21
|
-
|
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.
|
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
|
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
|
data/lib/paradeiser/router.rb
CHANGED
@@ -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
|
-
|
13
|
+
parts = command.name.split
|
14
|
+
resource = parts.shift
|
15
|
+
controller_name = "#{resource.pluralize.capitalize}Controller".to_sym
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
controller_class =
|
17
|
+
if Paradeiser.const_defined?(controller_name)
|
18
|
+
verb = parts.join
|
19
|
+
controller_class = Paradeiser.const_get(controller_name)
|
16
20
|
else
|
17
|
-
|
21
|
+
verb = resource
|
22
|
+
controller_class = ParadeiserController
|
18
23
|
end
|
19
24
|
|
20
|
-
controller = controller_class.new(
|
25
|
+
controller = controller_class.new(verb)
|
21
26
|
controller.call(args, options)
|
22
27
|
|
23
|
-
View.new(controller.model,
|
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
|
data/lib/paradeiser/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Suffessfully initialized <%= Paradeiser.
|
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
|
+
%>
|
data/paradeiser.gemspec
CHANGED
@@ -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,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
|
data/test/helper.rb
CHANGED
@@ -10,15 +10,48 @@ class MiniTest::Test
|
|
10
10
|
|
11
11
|
protected
|
12
12
|
|
13
|
-
def start!(
|
13
|
+
def start!(thing = @pom || @break)
|
14
14
|
Scheduler.stub(:add, nil) do
|
15
|
-
|
15
|
+
thing.start!
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def interrupt!(type = :internal, pom = @pom)
|
20
20
|
Scheduler.stub(:clear, nil) do
|
21
|
-
pom.
|
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
|