tamarillo 0.1.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.
- data/.gitignore +19 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +83 -0
- data/Rakefile +23 -0
- data/bin/tam +4 -0
- data/features/config.feature +44 -0
- data/features/step_definitions/tamarillo_steps.rb +50 -0
- data/features/support/env.rb +5 -0
- data/features/tamarillo.feature +47 -0
- data/lib/tamarillo/clock.rb +33 -0
- data/lib/tamarillo/command.rb +68 -0
- data/lib/tamarillo/config.rb +95 -0
- data/lib/tamarillo/controller.rb +113 -0
- data/lib/tamarillo/monitor.rb +27 -0
- data/lib/tamarillo/notification/bell.rb +15 -0
- data/lib/tamarillo/notification/growl.rb +13 -0
- data/lib/tamarillo/notification/none.rb +13 -0
- data/lib/tamarillo/notification/speech.rb +13 -0
- data/lib/tamarillo/notification/touch.rb +16 -0
- data/lib/tamarillo/notification.rb +36 -0
- data/lib/tamarillo/storage.rb +118 -0
- data/lib/tamarillo/tomato.rb +96 -0
- data/lib/tamarillo/tomato_file.rb +60 -0
- data/lib/tamarillo/version.rb +3 -0
- data/lib/tamarillo.rb +7 -0
- data/spec/lib/tamarillo/clock_spec.rb +39 -0
- data/spec/lib/tamarillo/command_spec.rb +17 -0
- data/spec/lib/tamarillo/config_spec.rb +133 -0
- data/spec/lib/tamarillo/controller_spec.rb +137 -0
- data/spec/lib/tamarillo/monitor_spec.rb +27 -0
- data/spec/lib/tamarillo/notification/bell_spec.rb +7 -0
- data/spec/lib/tamarillo/notification/growl_spec.rb +7 -0
- data/spec/lib/tamarillo/notification/speech_spec.rb +7 -0
- data/spec/lib/tamarillo/notification_spec.rb +22 -0
- data/spec/lib/tamarillo/storage_spec.rb +171 -0
- data/spec/lib/tamarillo/tomato_file_spec.rb +41 -0
- data/spec/lib/tamarillo/tomato_spec.rb +116 -0
- data/spec/support/invalid-config.yml +1 -0
- data/spec/support/sample-config.yml +3 -0
- data/tamarillo.gemspec +26 -0
- metadata +178 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tamarillo
|
2
|
+
module Notification
|
3
|
+
class Touch
|
4
|
+
# Public: initializes a new notifier.
|
5
|
+
def initialize(path)
|
6
|
+
@path = File.expand_path(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: executes the notification.
|
10
|
+
def call
|
11
|
+
system("touch #{@path}")
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'tamarillo/notification/bell'
|
2
|
+
require 'tamarillo/notification/growl'
|
3
|
+
require 'tamarillo/notification/none'
|
4
|
+
require 'tamarillo/notification/speech'
|
5
|
+
require 'tamarillo/notification/touch'
|
6
|
+
|
7
|
+
module Tamarillo
|
8
|
+
module Notification
|
9
|
+
BELL = :bell.freeze
|
10
|
+
GROWL = :growl.freeze
|
11
|
+
NONE = :none.freeze
|
12
|
+
SPEECH = :speech.freeze
|
13
|
+
|
14
|
+
VALID = [BELL, GROWL, NONE, SPEECH]
|
15
|
+
|
16
|
+
# Public: Returns a valid notification type.
|
17
|
+
def self.valid!(value)
|
18
|
+
value = value.to_s.downcase.to_sym
|
19
|
+
VALID.include?(value) ? value : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.default
|
23
|
+
BELL
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Resolves a notification class.
|
27
|
+
def self.for(type)
|
28
|
+
case type
|
29
|
+
when BELL then Bell.new
|
30
|
+
when GROWL then Growl.new
|
31
|
+
when SPEECH then Speech.new
|
32
|
+
else None.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'tamarillo/clock'
|
2
|
+
require 'tamarillo/config'
|
3
|
+
require 'tamarillo/tomato'
|
4
|
+
require 'tamarillo/tomato_file'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
# Public: Stores tomatoes using the filesystem.
|
9
|
+
#
|
10
|
+
# This model represents a directory of Tomato files and configuration.
|
11
|
+
# It can read and write them, and find the latest one for the current
|
12
|
+
# day.
|
13
|
+
module Tamarillo
|
14
|
+
class Storage
|
15
|
+
# Returns: the String path to the storage directory.
|
16
|
+
attr_reader :path
|
17
|
+
# Returns: the Config for this storage.
|
18
|
+
# Used to set the duration when reading tomatoes in.
|
19
|
+
attr_reader :config
|
20
|
+
|
21
|
+
# Public: Initialize a new storage object.
|
22
|
+
def initialize(path, config = nil)
|
23
|
+
@config = config || Tamarillo::Config.new
|
24
|
+
@path = Pathname.new(path)
|
25
|
+
FileUtils.mkdir_p(@path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Write the config to the filesystem.
|
29
|
+
#
|
30
|
+
# Returns the Pathname to the config that was written.
|
31
|
+
def write_config
|
32
|
+
File.open(config_path, 'w') { |f| f << @config.to_yaml }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Write a tomato to the filesystem.
|
36
|
+
#
|
37
|
+
# tomato - A Tomato instance.
|
38
|
+
#
|
39
|
+
# Returns the Pathname to the tomato that was written.
|
40
|
+
def write_tomato(tomato)
|
41
|
+
tomato_file = TomatoFile.new(tomato)
|
42
|
+
tomato_path = @path.join(tomato_file.path)
|
43
|
+
FileUtils.mkdir_p(File.dirname(tomato_path))
|
44
|
+
File.open(tomato_path, 'w') { |f| f << tomato_file.content }
|
45
|
+
|
46
|
+
tomato_path
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Writes a monitor pid.
|
50
|
+
#
|
51
|
+
# monitor - The monitor to write.
|
52
|
+
def write_monitor(monitor)
|
53
|
+
File.open(monitor_path, 'w') { |f| f << monitor.pid }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Read a tomato from the filesystem.
|
57
|
+
#
|
58
|
+
# path - A String path to a tomato file.
|
59
|
+
#
|
60
|
+
# Returns a Tomato instance if found, nil if not found.
|
61
|
+
def read_tomato(path)
|
62
|
+
return unless File.exist?(path)
|
63
|
+
|
64
|
+
data = File.readlines(path)
|
65
|
+
start_time = Time.iso8601(data[0]).localtime
|
66
|
+
state = data[2]
|
67
|
+
|
68
|
+
clock = Clock.new(start_time)
|
69
|
+
duration = config.duration_in_seconds
|
70
|
+
tomato = Tomato.new(duration, clock)
|
71
|
+
tomato.interrupt! if state == 'interrupted'
|
72
|
+
|
73
|
+
tomato
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Returns the pid of monitor.
|
77
|
+
def read_monitor
|
78
|
+
if File.exists?(monitor_path)
|
79
|
+
File.read(monitor_path).to_i
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Removes monitor pid-file.
|
84
|
+
def clear_monitor
|
85
|
+
File.delete(monitor_path) if File.exists?(monitor_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Returns a Tomato instance if one exists.
|
89
|
+
def latest
|
90
|
+
return unless File.directory?(tomato_dir)
|
91
|
+
# p Dir.glob(tomato_dir.join('*'))
|
92
|
+
|
93
|
+
# XXX tomato_dir.to_s because FakeFS chokes on Pathname.
|
94
|
+
if latest_name = Dir.glob(tomato_dir.join('*')).sort.last
|
95
|
+
read_tomato(latest_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Private: Returns a Pathname to the config.
|
102
|
+
def config_path
|
103
|
+
@path.join('config.yml')
|
104
|
+
end
|
105
|
+
|
106
|
+
# Private: Returns a Pathmame to the monitor pid.
|
107
|
+
def monitor_path
|
108
|
+
@path.join('monitor.pid')
|
109
|
+
end
|
110
|
+
|
111
|
+
# Private: Returns a Pathname to the current day.
|
112
|
+
def tomato_dir
|
113
|
+
base_path = File.dirname(TomatoFile.path(Time.now))
|
114
|
+
@path.join(base_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Tamarillo
|
4
|
+
# Public: A unit of work.
|
5
|
+
#
|
6
|
+
# A Tomato is a 'pomodoro', it keeps track of the amount of time you
|
7
|
+
# have focused on a single task. It can be interrupted or completed.
|
8
|
+
class Tomato
|
9
|
+
# Internal: A set of valid states a Tomato can be in.
|
10
|
+
module States
|
11
|
+
ACTIVE = :active.freeze
|
12
|
+
COMPLETED = :completed.freeze
|
13
|
+
INTERRUPTED = :interrupted.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Gets/Sets the length of the tomato in seconds.
|
17
|
+
attr_accessor :duration
|
18
|
+
|
19
|
+
# Public: Initializes a new Tomato.
|
20
|
+
#
|
21
|
+
# duration - The length of the Tomato in seconds.
|
22
|
+
# clock - A Clock instance to keep track of elapsed time.
|
23
|
+
def initialize(duration, clock)
|
24
|
+
@duration = duration
|
25
|
+
@clock = clock
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Returns the starting Time of the Tomato.
|
29
|
+
def started_at
|
30
|
+
@clock.start_time
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Returns the Date the Tomato was started on.
|
34
|
+
def date
|
35
|
+
@clock.start_date
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Returns true if two Tomatoes share a start Time.
|
39
|
+
def eql?(other)
|
40
|
+
other.started_at == started_at ||
|
41
|
+
super(other)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Returns the number of seconds until completion.
|
45
|
+
def remaining
|
46
|
+
return 0 if @interrupted
|
47
|
+
|
48
|
+
d = @duration - @clock.elapsed
|
49
|
+
d > 0 ? d : 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Returns the number of seconds elapsed since start.
|
53
|
+
def elapsed
|
54
|
+
@clock.elapsed
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Marks the tomato as interrupted.
|
58
|
+
#
|
59
|
+
# Returns the Tomato.
|
60
|
+
def interrupt!
|
61
|
+
@interrupted = true
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: Returns true if the Tomato has not been completed or
|
66
|
+
# interrupted.
|
67
|
+
def active?
|
68
|
+
States::ACTIVE == state
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Returns true if the elapsed Time matches the duration.
|
72
|
+
def completed?
|
73
|
+
States::COMPLETED == state
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Returns true if the Tomato has been interrupted.
|
77
|
+
def interrupted?
|
78
|
+
States::INTERRUPTED == state
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Returns which state the Tomato is in.
|
82
|
+
#
|
83
|
+
# I'd rather keep this internal, but the storage system needs to
|
84
|
+
# know what state the tamato was in when it was written.
|
85
|
+
def state
|
86
|
+
if @interrupted
|
87
|
+
States::INTERRUPTED
|
88
|
+
elsif remaining == 0
|
89
|
+
States::COMPLETED
|
90
|
+
else
|
91
|
+
States::ACTIVE
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Tamarillo
|
4
|
+
# Internal: Represents a tomato in the filesystem.
|
5
|
+
class TomatoFile
|
6
|
+
FILENAME_FORMAT = '%Y%m%d%H%M%S'
|
7
|
+
PATH_FORMAT = '%Y/%m%d'
|
8
|
+
|
9
|
+
# Public: Initializes a new tomato file.
|
10
|
+
#
|
11
|
+
# tomato - A Tomato instance to serialize.
|
12
|
+
def initialize(tomato)
|
13
|
+
@tomato = tomato
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public; Returns the filename of the Tomato.
|
17
|
+
def name
|
18
|
+
@tomato.started_at.strftime(FILENAME_FORMAT)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Returns a String path to the Tomato.
|
22
|
+
def path
|
23
|
+
self.class.path(@tomato.started_at)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Generate a path from a time.
|
27
|
+
#
|
28
|
+
# time - A Tomato's start time Date.
|
29
|
+
#
|
30
|
+
# Returns A String path generated from a Date.
|
31
|
+
def self.path(time)
|
32
|
+
dir = time.strftime(PATH_FORMAT)
|
33
|
+
name = time.strftime(FILENAME_FORMAT)
|
34
|
+
|
35
|
+
"#{dir}/#{name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Returns the serialized form of a Tomato.
|
39
|
+
def content
|
40
|
+
[time,task,state].join("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Private: Returns the start time in ISO-8601 format.
|
46
|
+
def time
|
47
|
+
@tomato.started_at.iso8601
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns: The name of the task being worked on during the Tomato.
|
51
|
+
def task
|
52
|
+
"Some task I'm working on"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the state of the Tomato.
|
56
|
+
def state
|
57
|
+
@tomato.state.to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/tamarillo.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../../../lib/tamarillo/clock'
|
2
|
+
require 'timecop'
|
3
|
+
|
4
|
+
include Tamarillo
|
5
|
+
|
6
|
+
describe Clock do
|
7
|
+
after do
|
8
|
+
Timecop.return
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has a start_time" do
|
12
|
+
now = Time.new(2012,4,1,6,0,0)
|
13
|
+
clock = Clock.new(now)
|
14
|
+
clock.start_time.should == now
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a start_date" do
|
18
|
+
now = Time.new(2012,1,1,6,0,0)
|
19
|
+
today = Date.new(2012,1,1)
|
20
|
+
clock = Clock.new(now)
|
21
|
+
clock.start_date.should == today
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has an elapsed value" do
|
25
|
+
now = Time.new(2012,1,1,6,0,0)
|
26
|
+
clock = Clock.new(now)
|
27
|
+
Timecop.freeze(2012,1,1,6,0,30)
|
28
|
+
|
29
|
+
clock.elapsed.should == 30
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#now" do
|
33
|
+
it "creates an instance starting at the current time" do
|
34
|
+
Timecop.freeze(2012,1,1,6,0,30)
|
35
|
+
clock = Clock.now
|
36
|
+
clock.start_time.should == Time.now
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../../../lib/tamarillo/command'
|
2
|
+
|
3
|
+
describe Tamarillo::Command do
|
4
|
+
before do
|
5
|
+
@stdout = $stdout
|
6
|
+
$stdout = StringIO.new
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
$stdout = @stdout
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has an execute command" do
|
14
|
+
expect { subject.execute }
|
15
|
+
.to_not raise_error
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'active_support/core_ext/numeric/time'
|
2
|
+
require_relative '../../../lib/tamarillo/config'
|
3
|
+
|
4
|
+
describe Tamarillo::Config do
|
5
|
+
let(:default_duration) { Tamarillo::Config::DEFAULT_DURATION_IN_MINUTES }
|
6
|
+
|
7
|
+
describe "empty config" do
|
8
|
+
subject { Tamarillo::Config.new }
|
9
|
+
its(:duration_in_minutes) { should == default_duration }
|
10
|
+
its(:duration_in_seconds) { should == default_duration * 60 }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "duration_in_minutes" do
|
14
|
+
it "can be read" do
|
15
|
+
config = Tamarillo::Config.new(duration_in_minutes: 15)
|
16
|
+
config.duration_in_minutes.should == 15
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can be assigned" do
|
20
|
+
subject.duration_in_minutes = 10
|
21
|
+
subject.duration_in_minutes.should == 10
|
22
|
+
end
|
23
|
+
|
24
|
+
it "assigns the default when value is nil" do
|
25
|
+
subject.duration_in_minutes = nil
|
26
|
+
subject.duration_in_minutes.should == default_duration
|
27
|
+
end
|
28
|
+
|
29
|
+
it "affects the duration in seconds" do
|
30
|
+
subject.duration_in_minutes = 30
|
31
|
+
subject.duration_in_seconds.should == 30 * 60
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "duration" do
|
36
|
+
it "aliases duration_in_minutes" do
|
37
|
+
expect { subject.duration = 10 }
|
38
|
+
.to change { subject.duration_in_minutes }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "duration_in_seconds" do
|
43
|
+
it "converts the duration in minutes into seconds" do
|
44
|
+
config = Tamarillo::Config.new(duration_in_minutes: 15)
|
45
|
+
config.duration_in_seconds.should == 15 * 60
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "notifier" do
|
50
|
+
it "can be read" do
|
51
|
+
config = Tamarillo::Config.new(notifier: Tamarillo::Notification::SPEECH)
|
52
|
+
config.notifier.should == Tamarillo::Notification::SPEECH
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can be assigned" do
|
56
|
+
config = Tamarillo::Config.new
|
57
|
+
config.notifier = 'Growl'
|
58
|
+
config.notifier.should == Tamarillo::Notification::GROWL
|
59
|
+
end
|
60
|
+
|
61
|
+
it "falls back to the existing value if a new value is invalid" do
|
62
|
+
config = Tamarillo::Config.new(notifier: Tamarillo::Notification::SPEECH)
|
63
|
+
config.notifier = 'bogus'
|
64
|
+
config.notifier.should == Tamarillo::Notification::SPEECH
|
65
|
+
end
|
66
|
+
|
67
|
+
it "has a default value" do
|
68
|
+
config = Tamarillo::Config.new
|
69
|
+
config.notifier.should == Tamarillo::Notification::BELL
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "read from YAML" do
|
74
|
+
let(:sample_config_path) { Pathname.new('spec/support/sample-config.yml') }
|
75
|
+
let(:invalid_config_path) { Pathname.new('spec/support/invalid-config.yml') }
|
76
|
+
|
77
|
+
describe "duration value" do
|
78
|
+
it "can read the duration from YAML" do
|
79
|
+
config = Tamarillo::Config.load(sample_config_path)
|
80
|
+
config.duration_in_seconds.should == 30 * 60
|
81
|
+
end
|
82
|
+
|
83
|
+
it "uses the default duration if the YAML is malformed" do
|
84
|
+
config = Tamarillo::Config.load(invalid_config_path)
|
85
|
+
config.duration_in_seconds.should == default_duration * 60
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "notifier value" do
|
90
|
+
it "can read the notifier from YAML" do
|
91
|
+
config = Tamarillo::Config.load(sample_config_path)
|
92
|
+
config.notifier.should == Tamarillo::Notification::GROWL
|
93
|
+
end
|
94
|
+
|
95
|
+
it "uses the default value if the YAML is malformed" do
|
96
|
+
config = Tamarillo::Config.load(invalid_config_path)
|
97
|
+
config.notifier.should == Tamarillo::Notification::BELL
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "creates a default config when path is missing" do
|
102
|
+
config_path = Pathname.new('some/invalid/path')
|
103
|
+
config = Tamarillo::Config.load(config_path)
|
104
|
+
config.duration_in_seconds.should == default_duration * 60
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "write to YAML" do
|
109
|
+
before do
|
110
|
+
FileUtils.mkdir('tmp') unless File.directory?('tmp')
|
111
|
+
end
|
112
|
+
|
113
|
+
it "can be written to YAML" do
|
114
|
+
config = Tamarillo::Config.new
|
115
|
+
config.duration_in_minutes = 5
|
116
|
+
config.write('tmp/write-test.yml')
|
117
|
+
|
118
|
+
File.read('tmp/write-test.yml').should include('duration: 5')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "dumping YAML" do
|
123
|
+
it "outputs a YAML format" do
|
124
|
+
config = Tamarillo::Config.new
|
125
|
+
yaml = config.to_yaml
|
126
|
+
yaml.should == <<EOS
|
127
|
+
---
|
128
|
+
duration: 25
|
129
|
+
notifier: bell
|
130
|
+
EOS
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative '../../../lib/tamarillo/controller'
|
2
|
+
|
3
|
+
describe Tamarillo::Controller do
|
4
|
+
let(:config) { double('config') }
|
5
|
+
let(:storage) { double('storage') }
|
6
|
+
|
7
|
+
subject { Tamarillo::Controller.new(config, storage) }
|
8
|
+
|
9
|
+
describe "#status" do
|
10
|
+
let(:tomato) do
|
11
|
+
stub(:remaining => 1500, :duration => 1500, :active? => true)
|
12
|
+
end
|
13
|
+
let(:storage) { stub(:latest => tomato) }
|
14
|
+
|
15
|
+
it "can return a humanized format" do
|
16
|
+
subject.status(Tamarillo::Formats::HUMAN).should == 'About 25 minutes'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can return a machine optimized format" do
|
20
|
+
subject.status(Tamarillo::Formats::PROMPT).should == '25:00 1500 1500'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "complains if the format is invalid" do
|
24
|
+
expect { subject.status('invalid') }
|
25
|
+
.should raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns nil if no active tomato" do
|
29
|
+
tomato.stub(:active?) { false }
|
30
|
+
subject.status(Tamarillo::Formats::HUMAN).should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#start_new_tomato" do
|
35
|
+
before do
|
36
|
+
config.stub(:duration_in_seconds => 1500)
|
37
|
+
config.stub(:notifier => nil)
|
38
|
+
storage.stub(:write_monitor)
|
39
|
+
# Prevent monitor forking while testing.
|
40
|
+
Tamarillo::Monitor.any_instance.stub(:start)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "stores a new tomato" do
|
44
|
+
storage.stub(:latest => nil)
|
45
|
+
storage.should_receive(:write_tomato)
|
46
|
+
|
47
|
+
subject.start_new_tomato
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does nothing if a tomato is already in progress." do
|
51
|
+
tomato = stub(:active? => true)
|
52
|
+
storage.stub(:latest => tomato)
|
53
|
+
storage.should_not_receive(:write_tomato)
|
54
|
+
|
55
|
+
subject.start_new_tomato
|
56
|
+
end
|
57
|
+
|
58
|
+
it "uses the configured duration" do
|
59
|
+
storage.stub(:latest => nil)
|
60
|
+
storage.stub(:write_tomato)
|
61
|
+
Tamarillo::Tomato.should_receive(:new).with(1500, anything)
|
62
|
+
|
63
|
+
subject.start_new_tomato
|
64
|
+
end
|
65
|
+
|
66
|
+
it "uses a current clock" do
|
67
|
+
storage.stub(:latest => nil)
|
68
|
+
storage.stub(:write_tomato)
|
69
|
+
Tamarillo::Clock.should_receive(:now)
|
70
|
+
|
71
|
+
subject.start_new_tomato
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns the started tomato" do
|
75
|
+
storage.stub(:latest => nil)
|
76
|
+
storage.stub(:write_tomato)
|
77
|
+
subject.start_new_tomato.should be_a(Tamarillo::Tomato)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#interrupt_current_tomato" do
|
82
|
+
before do
|
83
|
+
storage.stub(:read_monitor)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "interrupts the current tomato" do
|
87
|
+
tomato = double('tomato')
|
88
|
+
tomato.should_receive(:interrupt!)
|
89
|
+
storage.stub(:write_tomato)
|
90
|
+
storage.should_receive(:latest).and_return(tomato)
|
91
|
+
|
92
|
+
subject.interrupt_current_tomato
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesn't raise if no tomato is present" do
|
96
|
+
storage.should_receive(:latest).and_return(nil)
|
97
|
+
expect { subject.interrupt_current_tomato }
|
98
|
+
.should_not raise_error(NoMethodError)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "writes the tomato" do
|
102
|
+
tomato = double('tomato')
|
103
|
+
tomato.stub(:interrupt!)
|
104
|
+
storage.stub(:latest => tomato)
|
105
|
+
storage.should_receive(:write_tomato).with(tomato)
|
106
|
+
|
107
|
+
subject.interrupt_current_tomato
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#config" do
|
112
|
+
it "returns the passed in config" do
|
113
|
+
subject.config.should == config
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#update_config" do
|
118
|
+
before do
|
119
|
+
storage.stub(:write_config)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "can update the duration" do
|
123
|
+
config.should_receive(:duration=).with(10)
|
124
|
+
subject.update_config(:duration => 10)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "ignores bogus arguments" do
|
128
|
+
config.should_not_receive(:foo=).with('bar')
|
129
|
+
subject.update_config(:foo => 'bar')
|
130
|
+
end
|
131
|
+
|
132
|
+
it "writes the config" do
|
133
|
+
storage.should_receive(:write_config)
|
134
|
+
subject.update_config
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|