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.
Files changed (43) hide show
  1. data/.gitignore +19 -0
  2. data/.travis.yml +6 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +22 -0
  5. data/README.md +83 -0
  6. data/Rakefile +23 -0
  7. data/bin/tam +4 -0
  8. data/features/config.feature +44 -0
  9. data/features/step_definitions/tamarillo_steps.rb +50 -0
  10. data/features/support/env.rb +5 -0
  11. data/features/tamarillo.feature +47 -0
  12. data/lib/tamarillo/clock.rb +33 -0
  13. data/lib/tamarillo/command.rb +68 -0
  14. data/lib/tamarillo/config.rb +95 -0
  15. data/lib/tamarillo/controller.rb +113 -0
  16. data/lib/tamarillo/monitor.rb +27 -0
  17. data/lib/tamarillo/notification/bell.rb +15 -0
  18. data/lib/tamarillo/notification/growl.rb +13 -0
  19. data/lib/tamarillo/notification/none.rb +13 -0
  20. data/lib/tamarillo/notification/speech.rb +13 -0
  21. data/lib/tamarillo/notification/touch.rb +16 -0
  22. data/lib/tamarillo/notification.rb +36 -0
  23. data/lib/tamarillo/storage.rb +118 -0
  24. data/lib/tamarillo/tomato.rb +96 -0
  25. data/lib/tamarillo/tomato_file.rb +60 -0
  26. data/lib/tamarillo/version.rb +3 -0
  27. data/lib/tamarillo.rb +7 -0
  28. data/spec/lib/tamarillo/clock_spec.rb +39 -0
  29. data/spec/lib/tamarillo/command_spec.rb +17 -0
  30. data/spec/lib/tamarillo/config_spec.rb +133 -0
  31. data/spec/lib/tamarillo/controller_spec.rb +137 -0
  32. data/spec/lib/tamarillo/monitor_spec.rb +27 -0
  33. data/spec/lib/tamarillo/notification/bell_spec.rb +7 -0
  34. data/spec/lib/tamarillo/notification/growl_spec.rb +7 -0
  35. data/spec/lib/tamarillo/notification/speech_spec.rb +7 -0
  36. data/spec/lib/tamarillo/notification_spec.rb +22 -0
  37. data/spec/lib/tamarillo/storage_spec.rb +171 -0
  38. data/spec/lib/tamarillo/tomato_file_spec.rb +41 -0
  39. data/spec/lib/tamarillo/tomato_spec.rb +116 -0
  40. data/spec/support/invalid-config.yml +1 -0
  41. data/spec/support/sample-config.yml +3 -0
  42. data/tamarillo.gemspec +26 -0
  43. metadata +178 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rbenv-version
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ vendor/bundle
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ # uncomment this line if your project needs to run something other than `rake`:
6
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
+ [![Build Status](https://secure.travis-ci.org/timuruski/tamarillo.png)](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,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tamarillo/command'
4
+ Tamarillo::Command.new.execute(*ARGV)
@@ -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,5 @@
1
+ require 'bundler/setup'
2
+ require 'aruba/cucumber'
3
+ require 'tamarillo'
4
+
5
+ ENV['TAMARILLO_PATH'] = 'tamarillo'
@@ -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
@@ -0,0 +1,13 @@
1
+ module Tamarillo
2
+ module Notification
3
+ class Growl
4
+ GROWL_COMMAND = %Q{/usr/bin/env growlnotify --message 'Tomato complete.' --sticky}
5
+
6
+ # Public: executes the notification.
7
+ def call
8
+ system(GROWL_COMMAND)
9
+ end
10
+
11
+ end
12
+ end
13
+ end