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
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tim Uruski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Tamarillo
|
2
|
+
|
3
|
+
A command line [pomodoro/tomato](http://www.pomodorotechnique.com/) timer.
|
4
|
+
|
5
|
+
Currently this will just keep track of whether you are in the middle of
|
6
|
+
a tomato. If you are it will display how much time remains. If you
|
7
|
+
complete to the tomato without being interrupted, it will log it as
|
8
|
+
completed for the day. You can also interrupt a tomato, which will
|
9
|
+
freeze it for later analysis.
|
10
|
+
|
11
|
+
It also makes it easy to includes the current tomato status in your
|
12
|
+
prompt. Tomatoes are stored in `~/.tamarillo` by default.
|
13
|
+
|
14
|
+
[](http://travis-ci.org/timuruski/tamarillo)
|
15
|
+
|
16
|
+
|
17
|
+
## Why Tamarillo?
|
18
|
+
|
19
|
+
The tamarillo is a cousin to the tomato, which is also related to
|
20
|
+
eggplants, potatoes and the deadly nightshade. When tomatoes were
|
21
|
+
first introduced to Europe, they were not popular because people
|
22
|
+
associated them with the deadly poisons of their cousins.
|
23
|
+
|
24
|
+
Also I wasn't clever enough to come up with Tomatillo at the time. In
|
25
|
+
any case, tamarillos are delicious if you can find them. I recommend
|
26
|
+
stewing them and then serving over vanilla ice cream; home made if you
|
27
|
+
can.
|
28
|
+
|
29
|
+
|
30
|
+
## Examples
|
31
|
+
|
32
|
+
These examples are just thought experiments, this interface has not been
|
33
|
+
implemented yet.
|
34
|
+
|
35
|
+
### Starting and stopping a tomato
|
36
|
+
|
37
|
+
```
|
38
|
+
$ tam start
|
39
|
+
> tamarillo started
|
40
|
+
|
41
|
+
$ tam stop
|
42
|
+
> tomato stopped around ~17m
|
43
|
+
|
44
|
+
$ tam pause
|
45
|
+
> tomato paused around ~16m
|
46
|
+
|
47
|
+
$ tam interrupt
|
48
|
+
> tomato interrupted around ~14m
|
49
|
+
```
|
50
|
+
|
51
|
+
### Status of current tomato
|
52
|
+
|
53
|
+
```
|
54
|
+
$ tam status
|
55
|
+
> ~19m # rough time only, don't sweat the seconds
|
56
|
+
|
57
|
+
$ tam
|
58
|
+
> ~19m
|
59
|
+
|
60
|
+
$ tam status --full
|
61
|
+
> active 19:21
|
62
|
+
```
|
63
|
+
|
64
|
+
### Configuration
|
65
|
+
|
66
|
+
```
|
67
|
+
$ tam config --duration=25
|
68
|
+
> tamarillo duration is 25 minutes
|
69
|
+
|
70
|
+
$ tam config --alert=growl
|
71
|
+
> tamarillo will use Growl for notifications
|
72
|
+
|
73
|
+
$ tam config --daemon ~/.tamarillo/pid
|
74
|
+
> tamarillo will monitor the current tomato from here
|
75
|
+
```
|
76
|
+
|
77
|
+
|
78
|
+
## Future ideas
|
79
|
+
|
80
|
+
* task management, tomatoes are assigned to a task
|
81
|
+
* daemon process for monitoring the current tomato
|
82
|
+
* notification helper app for various environments
|
83
|
+
* instaweb view of history
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
# require 'cucumber'
|
6
|
+
require 'cucumber/rake/task'
|
7
|
+
|
8
|
+
desc "Interactive pomodoro console"
|
9
|
+
task :console do
|
10
|
+
exec("irb -Ilib -r'bundler/setup' -r'tamarillo'")
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
14
|
+
t.rspec_opts = '--color'
|
15
|
+
end
|
16
|
+
|
17
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
18
|
+
tag_opts = ' --tags ~@pending'
|
19
|
+
tag_opts = " --tags #{ENV['TAGS']}" if ENV['TAGS']
|
20
|
+
t.cucumber_opts = "features --format progress -x -s#{tag_opts}"
|
21
|
+
t.fork = false
|
22
|
+
end
|
23
|
+
task :default => [:spec, :features]
|
data/bin/tam
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Feature: configuration
|
2
|
+
|
3
|
+
Scenario: Outputting the current config
|
4
|
+
Given the default configuration
|
5
|
+
When I run `tam config`
|
6
|
+
Then the exit status should be 0
|
7
|
+
Then the output should contain:
|
8
|
+
"""
|
9
|
+
duration: 25
|
10
|
+
"""
|
11
|
+
|
12
|
+
Scenario: Setting the tomato duration
|
13
|
+
Given the default configuration
|
14
|
+
When I run `tam config duration=15`
|
15
|
+
And I run `tam config`
|
16
|
+
Then the exit status should be 0
|
17
|
+
Then the output should contain:
|
18
|
+
"""
|
19
|
+
duration: 15
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: Setting invalid duration
|
23
|
+
Given the default configuration
|
24
|
+
When I run `tam config duration=invalid_input`
|
25
|
+
And I run `tam config`
|
26
|
+
Then the exit status should be 0
|
27
|
+
Then the output should contain:
|
28
|
+
"""
|
29
|
+
duration: 25
|
30
|
+
"""
|
31
|
+
|
32
|
+
Scenario: Changing the tomato duration
|
33
|
+
Given there is no active tomato
|
34
|
+
When I run `tam config duration=5`
|
35
|
+
And I run `tam start`
|
36
|
+
And I run `tam`
|
37
|
+
Then the output should contain:
|
38
|
+
"""
|
39
|
+
About 5 minutes
|
40
|
+
"""
|
41
|
+
And the output should not contain:
|
42
|
+
"""
|
43
|
+
About 25 minutes
|
44
|
+
"""
|
@@ -0,0 +1,50 @@
|
|
1
|
+
After do
|
2
|
+
# Kill any forked monitor processes.
|
3
|
+
storage = Tamarillo::Storage.new(tamarillo_path)
|
4
|
+
if monitor_pid = storage.read_monitor
|
5
|
+
Process.kill('QUIT', monitor_pid)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def tamarillo_path
|
10
|
+
Pathname.new("#{current_dir}/tamarillo")
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear_tomatoes
|
14
|
+
# Not happy with how specific to implementation this is.
|
15
|
+
# Giving the Storage system a way to remove tomatoes might be good.
|
16
|
+
in_current_dir do
|
17
|
+
year = Time.new.year
|
18
|
+
FileUtils.remove_dir("tamarillo/#{year}", :force)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Given /^the default configuration$/ do
|
23
|
+
Tamarillo::Storage.new(tamarillo_path)
|
24
|
+
Tamarillo::Config.new.write(tamarillo_path.join('config.yml'))
|
25
|
+
end
|
26
|
+
|
27
|
+
Given /^there is no active tomato$/ do
|
28
|
+
clear_tomatoes
|
29
|
+
end
|
30
|
+
|
31
|
+
Given /^there is an active tomato$/ do
|
32
|
+
clock = Tamarillo::Clock.now
|
33
|
+
tomato = Tamarillo::Tomato.new(25 * 60, clock)
|
34
|
+
storage = Tamarillo::Storage.new(tamarillo_path)
|
35
|
+
storage.write_tomato(tomato)
|
36
|
+
end
|
37
|
+
|
38
|
+
Given /^there is a completed tomato$/ do
|
39
|
+
clear_tomatoes
|
40
|
+
time = Time.now - (25 * 60)
|
41
|
+
clock = Tamarillo::Clock.new(time)
|
42
|
+
tomato = Tamarillo::Tomato.new(25 * 60, clock)
|
43
|
+
storage = Tamarillo::Storage.new(tamarillo_path)
|
44
|
+
storage.write_tomato(tomato)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
Then /^the output should be empty$/ do
|
49
|
+
assert_exact_output('', all_output)
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Feature: tamarillo
|
2
|
+
|
3
|
+
Scenario: First usage
|
4
|
+
Given there is no active tomato
|
5
|
+
When I run `tam`
|
6
|
+
Then the exit status should be 0
|
7
|
+
Then the output should be empty
|
8
|
+
|
9
|
+
Scenario: Starting a tomato
|
10
|
+
Given the default configuration
|
11
|
+
And there is no active tomato
|
12
|
+
When I run `tam start`
|
13
|
+
Then the output should match /About \d+ minutes/
|
14
|
+
And the exit status should be 0
|
15
|
+
|
16
|
+
Scenario: Tomato status
|
17
|
+
Given there is an active tomato
|
18
|
+
When I run `tam`
|
19
|
+
Then the output should match /About \d+ minutes/
|
20
|
+
|
21
|
+
Scenario: Interrupting a tomato
|
22
|
+
Given there is an active tomato
|
23
|
+
When I run `tam interrupt`
|
24
|
+
And I run `tam`
|
25
|
+
Then the output should be empty
|
26
|
+
|
27
|
+
Scenario: A tomato is completed
|
28
|
+
Given there is a completed tomato
|
29
|
+
When I run `tam`
|
30
|
+
Then the output should be empty
|
31
|
+
|
32
|
+
Scenario: Invalid command
|
33
|
+
When I run `tam blah`
|
34
|
+
Then the exit status should be 1
|
35
|
+
And the output should contain:
|
36
|
+
"""
|
37
|
+
Invalid command 'blah'
|
38
|
+
"""
|
39
|
+
|
40
|
+
Scenario: Tomato status for prompt
|
41
|
+
Given there is an active tomato
|
42
|
+
When I run `tam status --prompt`
|
43
|
+
Then the output should match:
|
44
|
+
"""
|
45
|
+
\d\d:\d\d \d{4} \d{4}
|
46
|
+
"""
|
47
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
# Internal: Keeps track of time elapsed.
|
4
|
+
#
|
5
|
+
# The Clock model reprensents the amount of time elapsed since
|
6
|
+
# the time a Pomodoro/Tomato began, typically measured in
|
7
|
+
# 15-25 minute blocks.
|
8
|
+
#
|
9
|
+
module Tamarillo
|
10
|
+
class Clock
|
11
|
+
attr_reader :start_time, :start_date
|
12
|
+
|
13
|
+
# Public: Initialize a new clock.
|
14
|
+
#
|
15
|
+
# start_time - A Time instance when the clock was started.
|
16
|
+
def initialize(start_time)
|
17
|
+
@start_time = start_time
|
18
|
+
@start_date = start_time.to_date
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Returns a clock starting at the current time.
|
22
|
+
def self.now
|
23
|
+
new(Time.now)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Calculate time elapsed.
|
27
|
+
#
|
28
|
+
# Returns the number of seconds since the clock started.
|
29
|
+
def elapsed
|
30
|
+
Time.now.to_i - @start_time.to_i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'tamarillo'
|
2
|
+
|
3
|
+
module Tamarillo
|
4
|
+
class Command
|
5
|
+
DEFAULT_PATH = '~/.tamarillo'
|
6
|
+
DEFAULT_COMMAND = 'status'
|
7
|
+
|
8
|
+
VALID_COMMANDS = %w[status config start stop interrupt]
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
config = Tamarillo::Config.load(config_path)
|
12
|
+
storage = Storage.new(tamarillo_path, config)
|
13
|
+
@controller = Controller.new(config, storage)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(*args)
|
17
|
+
command = parse_command_name!(args)
|
18
|
+
send(command.to_sym, *args.drop(1))
|
19
|
+
end
|
20
|
+
|
21
|
+
def status(*args)
|
22
|
+
format = Formats::HUMAN
|
23
|
+
format = Formats::PROMPT if args.include?('--prompt')
|
24
|
+
|
25
|
+
status = @controller.status(format)
|
26
|
+
puts status unless status.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(*args)
|
30
|
+
tomato = @controller.start_new_tomato
|
31
|
+
status unless tomato.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def interrupt(*args)
|
35
|
+
@controller.interrupt_current_tomato
|
36
|
+
end
|
37
|
+
alias :stop :interrupt
|
38
|
+
|
39
|
+
def config(*args)
|
40
|
+
params = Hash[args.map { |pair| pair.split('=', 2) }]
|
41
|
+
@controller.update_config(params)
|
42
|
+
puts @controller.config.to_yaml
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def parse_command_name!(args)
|
48
|
+
name = args.first || DEFAULT_COMMAND
|
49
|
+
unless VALID_COMMANDS.include?(name)
|
50
|
+
puts "Invalid command '#{name}'"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
name
|
55
|
+
end
|
56
|
+
|
57
|
+
def tamarillo_path
|
58
|
+
path = ENV['TAMARILLO_PATH'] || DEFAULT_PATH
|
59
|
+
Pathname.new(File.expand_path(path))
|
60
|
+
end
|
61
|
+
|
62
|
+
def config_path
|
63
|
+
tamarillo_path.join('config.yml')
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'tamarillo/notification'
|
3
|
+
|
4
|
+
module Tamarillo
|
5
|
+
# Internal: This configures the Tamarillo CLI, which uses values from
|
6
|
+
# this config when generating and interacting with Tomatoes. Currently
|
7
|
+
# it just holds the duration of each tomato, but it will later
|
8
|
+
# configure things like daemon process location and storage interface.
|
9
|
+
class Config
|
10
|
+
DEFAULT_DURATION_IN_MINUTES = 25
|
11
|
+
|
12
|
+
# Public: Gets/Sets the duration of each Tomato in minutes.
|
13
|
+
attr_accessor :duration_in_minutes
|
14
|
+
|
15
|
+
# Public: Initializes a new config.
|
16
|
+
#
|
17
|
+
# options - The hash used to configure Tamarillo.
|
18
|
+
# :duration_in_minutes - The duration in minutes.
|
19
|
+
def initialize(options = {})
|
20
|
+
self.duration_in_minutes = options[:duration_in_minutes]
|
21
|
+
self.notifier = options[:notifier]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Returns the duration of each tomato in minutes.
|
25
|
+
def duration_in_minutes
|
26
|
+
@duration_in_minutes
|
27
|
+
end
|
28
|
+
alias :duration :duration_in_minutes
|
29
|
+
|
30
|
+
# Public: Sets the duration of each tomato in minutes.
|
31
|
+
#
|
32
|
+
# Uses the default value if the value is invalid.
|
33
|
+
def duration_in_minutes=(value)
|
34
|
+
value = value.to_i
|
35
|
+
@duration_in_minutes = (value > 0) ? value : DEFAULT_DURATION_IN_MINUTES
|
36
|
+
end
|
37
|
+
alias :duration= :duration_in_minutes=
|
38
|
+
|
39
|
+
# Public: Returns the duration of each tomato in seconds.
|
40
|
+
def duration_in_seconds
|
41
|
+
@duration_in_minutes.to_i * 60
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Returns the notifier type.
|
45
|
+
def notifier
|
46
|
+
@notifier
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Sets the notifier type.
|
50
|
+
def notifier=(value)
|
51
|
+
new_value = Notification.valid!(value)
|
52
|
+
new_value ||= @notifier
|
53
|
+
new_value ||= Notification.default
|
54
|
+
|
55
|
+
@notifier = new_value
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Initializes a config from a YAML file.
|
59
|
+
#
|
60
|
+
# This tries to read config data from YAML. If the YAML is missing
|
61
|
+
# values or is invalid, defaults are used. If the file doesn't
|
62
|
+
# exist, then a default configuration is created.
|
63
|
+
#
|
64
|
+
# path - String or Pathname to the YAML file.
|
65
|
+
#
|
66
|
+
# Returns: A config instance.
|
67
|
+
def self.load(path)
|
68
|
+
options = {}
|
69
|
+
|
70
|
+
if File.exist?(path)
|
71
|
+
yaml = YAML.load( File.read(path.to_s) ) || {}
|
72
|
+
options[:duration_in_minutes] = yaml['duration']
|
73
|
+
options[:notifier] = yaml['notifier']
|
74
|
+
end
|
75
|
+
|
76
|
+
new(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: Write a config out to a file.
|
80
|
+
#
|
81
|
+
# path - a String or Pathname to the destination file.
|
82
|
+
def write(path)
|
83
|
+
File.open(path, 'w') { |f| f << to_yaml }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: Returns config in YAML format.
|
87
|
+
def to_yaml
|
88
|
+
options = {
|
89
|
+
'duration' => duration_in_minutes,
|
90
|
+
'notifier' => notifier.to_s }
|
91
|
+
YAML.dump(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'tamarillo'
|
2
|
+
|
3
|
+
module Tamarillo
|
4
|
+
# Formats for tomatoes
|
5
|
+
module Formats
|
6
|
+
HUMAN = :human.freeze
|
7
|
+
PROMPT = :prompt.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: This is intended to provide an environment for tomatoes to
|
11
|
+
# work from. It integrates the storage, config and monitor into a
|
12
|
+
# single coherant object.
|
13
|
+
class Controller
|
14
|
+
# Initializes a new controller.
|
15
|
+
def initialize(config, storage)
|
16
|
+
@config = config
|
17
|
+
@storage = storage
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Formats and returns the status of the current tomato.
|
21
|
+
# Returns nil if no tomato is found.
|
22
|
+
def status(format)
|
23
|
+
tomato = @storage.latest
|
24
|
+
return unless tomato && tomato.active?
|
25
|
+
|
26
|
+
case format
|
27
|
+
when Formats::HUMAN then format_approx_time(tomato.remaining)
|
28
|
+
when Formats::PROMPT then format_prompt(tomato)
|
29
|
+
else raise "Invalid format"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Starts a new tomato if one is not already running.
|
34
|
+
def start_new_tomato
|
35
|
+
tomato = @storage.latest
|
36
|
+
return if tomato && tomato.active?
|
37
|
+
|
38
|
+
tomato = Tomato.new(@config.duration_in_seconds, Clock.now)
|
39
|
+
@storage.write_tomato(tomato)
|
40
|
+
start_monitor(tomato)
|
41
|
+
|
42
|
+
tomato
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Interrupts the current tomato if one is running.
|
46
|
+
def interrupt_current_tomato
|
47
|
+
stop_monitor
|
48
|
+
|
49
|
+
tomato = @storage.latest
|
50
|
+
return if tomato.nil?
|
51
|
+
|
52
|
+
tomato.interrupt!
|
53
|
+
@storage.write_tomato(tomato)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Returns the current config.
|
57
|
+
def config
|
58
|
+
@config
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: Updates the current config.
|
62
|
+
def update_config(options = {})
|
63
|
+
valid_config_options(options).each do |key,value|
|
64
|
+
@config.send("#{key}=".to_sym, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
@storage.write_config
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def valid_config_options(options)
|
73
|
+
valid_keys = %w[duration notifier]
|
74
|
+
options.select { |k,_| valid_keys.include?(k.to_s) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_approx_time(time)
|
78
|
+
minutes = (time / 60).round
|
79
|
+
'About %d minutes' % minutes
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_time(time)
|
83
|
+
minutes = (time / 60).floor
|
84
|
+
seconds = time % 60
|
85
|
+
"%02d:%02d" % [minutes, seconds]
|
86
|
+
end
|
87
|
+
|
88
|
+
def format_prompt(tomato)
|
89
|
+
[format_time(tomato.remaining), tomato.remaining, tomato.duration].join(' ')
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_monitor(tomato)
|
93
|
+
notifier = Notification.for(@config.notifier)
|
94
|
+
monitor = Monitor.new(tomato, notifier)
|
95
|
+
monitor.start
|
96
|
+
@storage.write_monitor(monitor)
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop_monitor
|
100
|
+
if monitor_pid = @storage.read_monitor
|
101
|
+
begin
|
102
|
+
Process.kill('QUIT', monitor_pid)
|
103
|
+
rescue Errno::ESRCH
|
104
|
+
@storage.clear_monitor
|
105
|
+
rescue Errno::EPERM
|
106
|
+
warn "No privilege to kill monitor process."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Tamarillo
|
2
|
+
class Monitor
|
3
|
+
# The time between checks.
|
4
|
+
SLEEP_TIME = 0.3
|
5
|
+
attr_reader :pid
|
6
|
+
|
7
|
+
# Public: Initializes a new monitor.
|
8
|
+
def initialize(tomato, notifier)
|
9
|
+
@tomato = tomato
|
10
|
+
@notifier = notifier
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Starts watching a tomato for completion.
|
14
|
+
def start
|
15
|
+
@pid = fork do
|
16
|
+
until @tomato.completed?
|
17
|
+
sleep SLEEP_TIME
|
18
|
+
end
|
19
|
+
|
20
|
+
@notifier.call
|
21
|
+
end
|
22
|
+
|
23
|
+
Process.detach(@pid)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Tamarillo
|
2
|
+
module Notification
|
3
|
+
class Bell
|
4
|
+
CHIME_COUNT = 3
|
5
|
+
CHIME_COMMAND = 'tput bel; sleep 0.2'
|
6
|
+
|
7
|
+
# Public: executes the notification.
|
8
|
+
def call
|
9
|
+
cmd = (1..CHIME_COUNT).map { CHIME_COMMAND }.join(';')
|
10
|
+
system(cmd)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|