thyme 0.0.14 → 0.0.15
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.
- checksums.yaml +4 -4
- data/README.md +54 -52
- data/bin/thyme +7 -5
- data/lib/thyme.rb +9 -204
- data/lib/thyme/config.rb +74 -0
- data/lib/thyme/console.rb +55 -0
- data/lib/thyme/error.rb +4 -0
- data/lib/thyme/format.rb +41 -0
- data/lib/thyme/hooks_plugin.rb +32 -0
- data/lib/thyme/timer.rb +120 -0
- data/lib/thyme/tmux.rb +29 -0
- data/lib/thyme/version.rb +3 -0
- metadata +18 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bd7f6b22e1eb3fb5b2cc353f25948207113f722
|
4
|
+
data.tar.gz: 9de5a45b3b793806536927db3e4464d157e5ab95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d1a24078f5c3d2ce251ef8c2b04010e32140a1206548dfec3d4771179ae2a7bf6c8eb890b1106a0db7c855929afb5e4c1057e4ea4e7fc7c6c9b67bb8fc89bb9
|
7
|
+
data.tar.gz: 7167e5a067fc49605767fd18a9d13b05a6d430b56a1b809f6c270894897d300ad216441939bfb66a42300354ad8cfed78e4e5bb5cd6bee5a2a99dadf3c7889ae
|
data/README.md
CHANGED
@@ -1,80 +1,76 @@
|
|
1
|
-
Description
|
2
|
-
===========
|
1
|
+
# Description
|
3
2
|
|
4
3
|
Thyme is a console pomodoro timer.
|
5
4
|
|
6
|
-
Installation
|
7
|
-
============
|
5
|
+
# Installation
|
8
6
|
|
9
7
|
$ gem install thyme
|
10
8
|
|
11
|
-
Usage
|
12
|
-
=====
|
9
|
+
# Usage
|
13
10
|
|
14
11
|
Start thyme with:
|
15
12
|
|
16
13
|
$ thyme
|
17
14
|
[= ] 24:59
|
18
15
|
|
19
|
-
You'll have 25 minutes by default.
|
16
|
+
You'll have 25 minutes by default. `Ctrl-C` to interrupt. You can also start
|
20
17
|
it in daemon mode, which is only useful if you've got tmux integration to notify
|
21
18
|
you of the timer:
|
22
19
|
|
23
20
|
$ thyme -d
|
24
21
|
|
25
|
-
|
26
|
-
for it to kill itself.
|
22
|
+
Some other useful commands:
|
27
23
|
|
28
|
-
|
29
|
-
|
24
|
+
$ thyme # run again to pause/unpause
|
25
|
+
$ thyme -s # stops daemon
|
26
|
+
$ thyme -d -r # repeats timer until you manually stop it
|
27
|
+
$ thyme -d -r 10 # repeats timer exactly 10 times
|
30
28
|
|
31
|
-
|
32
|
-
`~/.thymerc` file:
|
29
|
+
# Configure
|
33
30
|
|
34
|
-
|
35
|
-
|
36
|
-
set :
|
37
|
-
set :
|
38
|
-
set :
|
39
|
-
set :
|
31
|
+
Configurations live in the `~/.thymerc` file:
|
32
|
+
|
33
|
+
set :timer, 25*60 # 25 minute pomodoros
|
34
|
+
set :timer_break, 5*60 # 5 minute breaks
|
35
|
+
set :warning, 5*60 # show warning color in tmux at <5 minutes, 0 to disable
|
36
|
+
set :warning_color, 'red,bold' # warning color for tmux is red/bold
|
37
|
+
set :break_color, 'blue' # break color is blue
|
38
|
+
set :interval, 1 # refresh timer every 1 second
|
39
|
+
set :tmux, true # turn on tmux integration
|
40
40
|
set :tmux_theme, "#[fg=mycolor,bg=mycolor]#[fg=%s]%s#[fg=mycolor,bg=mycolor]"
|
41
41
|
|
42
|
+
# adds `-t --today` option, which opens a text file in vim
|
42
43
|
option :t, :today, 'open today sheet' do
|
43
44
|
`vim -O ~/.thyme-today.md ~/.thyme-records.md < \`tty\` > \`tty\``
|
44
45
|
end
|
45
46
|
|
47
|
+
# adds `-s --seconds num` option, which allows on the fly timer
|
46
48
|
option :s, 'seconds num', 'run with custom seconds' do |num|
|
47
|
-
|
49
|
+
set :timer, num.to_i
|
48
50
|
run
|
49
51
|
end
|
50
52
|
|
51
|
-
before
|
53
|
+
# execute hook before thyme program starts
|
54
|
+
before(:all) do
|
52
55
|
`mplayer ~/music/flight-of-the-bumble-bee.mp3 &`
|
53
56
|
end
|
54
57
|
|
55
|
-
|
56
|
-
|
58
|
+
# execute hook before each pomodoro
|
59
|
+
before do
|
60
|
+
`terminal-notifier -message "Let's get started!"`
|
57
61
|
end
|
58
62
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
* `:warning` seconds threshold before tmux timer turns red (use 0 to disable)
|
64
|
-
* `:warning_color` color of the tmux timer during the warning period
|
65
|
-
* `:interval` refresh rate of the progress bar and tmux status in seconds
|
66
|
-
* `:tmux` whether or not you want tmux integration on (false by default)
|
67
|
-
* `:tmux_theme` optionally lets you format the tmux status
|
68
|
-
|
69
|
-
The `option` method adds new options to the `thyme` command. In the above
|
70
|
-
example, we can now execute `thyme -b` or `thyme -t`. Use `thyme -h` to see
|
71
|
-
available options.
|
63
|
+
# execute hook after each pomodoro
|
64
|
+
after do |seconds_left|
|
65
|
+
`terminal-notifier -message "Thyme's Up!"` if seconds_left == 0
|
66
|
+
end
|
72
67
|
|
73
|
-
|
74
|
-
|
68
|
+
# execute hook after thyme program quits
|
69
|
+
after(:all) do
|
70
|
+
`mplayer ~/music/victory.mp3 &`
|
71
|
+
end
|
75
72
|
|
76
|
-
|
77
|
-
===========
|
73
|
+
# Tmux
|
78
74
|
|
79
75
|
For tmux integration, make sure to set the `:tmux` option in `~/.thymerc`:
|
80
76
|
|
@@ -85,25 +81,28 @@ Then in your `.tmux.conf` file:
|
|
85
81
|
set-option -g status-right '#(cat ~/.thyme-tmux)'
|
86
82
|
set-option -g status-interval 1
|
87
83
|
|
88
|
-
For vim integration, I like to execute `thyme -d` to toggle the timer.
|
84
|
+
For vim integration, I like to execute `thyme -d` to toggle the timer. This only
|
89
85
|
works if you have tmux integration setup for the countdown:
|
90
86
|
|
91
87
|
nmap <leader>t :!thyme -d<cr>
|
92
88
|
|
93
|
-
Plugins
|
94
|
-
=======
|
89
|
+
# Plugins
|
95
90
|
|
96
|
-
Thyme's functionality can also be extended with plugins.
|
97
|
-
|
91
|
+
Thyme's functionality can also be extended with plugins. They'll usually be installed
|
92
|
+
in `~/.thymerc` like this:
|
98
93
|
|
99
|
-
require
|
100
|
-
use ThymeGrowl, text:
|
94
|
+
require 'thyme_growl'
|
95
|
+
use ThymeGrowl, text: 'Go take a break!'
|
101
96
|
|
102
|
-
You can create your own plugins.
|
97
|
+
You can create your own plugins. They implement these methods:
|
103
98
|
|
104
99
|
class MyThymePlugin
|
105
100
|
def initialize(thyme, options={})
|
106
|
-
# `thyme` is an instance of Thyme (see lib/thyme.rb)
|
101
|
+
# `thyme` is an instance of Thyme::Config (see lib/thyme/config.rb)
|
102
|
+
end
|
103
|
+
|
104
|
+
def before_all
|
105
|
+
# code to run when thyme starts up
|
107
106
|
end
|
108
107
|
|
109
108
|
def before
|
@@ -117,12 +116,15 @@ You can create your own plugins. They implement these methods:
|
|
117
116
|
def after(seconds_left)
|
118
117
|
# code to run when timer stops
|
119
118
|
end
|
119
|
+
|
120
|
+
def after_all
|
121
|
+
# code to run when thyme program ends
|
122
|
+
end
|
120
123
|
end
|
121
124
|
|
122
|
-
The `before`, `tick`, and `
|
125
|
+
The `before_all`, `before`, `tick`, `after`, and `after_all` methods are all optional.
|
123
126
|
|
124
|
-
License
|
125
|
-
=======
|
127
|
+
# License
|
126
128
|
|
127
129
|
Copyright Hugh Bien - http://hughbien.com.
|
128
130
|
Released under BSD License, see LICENSE.md for more info.
|
data/bin/thyme
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'optparse'
|
3
|
-
|
3
|
+
require_relative '../lib/thyme'
|
4
4
|
|
5
5
|
$0 = 'thyme'
|
6
6
|
ARGV.options do |o|
|
7
|
-
thyme = Thyme.new
|
7
|
+
thyme = Thyme::Console.new
|
8
8
|
o.set_summary_indent(' ')
|
9
|
-
o.banner =
|
9
|
+
o.banner = "Usage: #{File.basename($0)} [OPTION]"
|
10
10
|
o.define_head "Timer for Pomodoro Technique"
|
11
11
|
o.on('-b', '--break', 'run break timer') { thyme.break! }
|
12
12
|
o.on('-d', '--daemon', 'run in background') { thyme.daemonize! }
|
13
13
|
o.on('-h', '--help', 'show this help message') { puts o; exit }
|
14
|
-
thyme.
|
14
|
+
o.on('-r', '--repeat [COUNT]', 'repeat timer') { |count| thyme.repeat!(count) }
|
15
|
+
o.on('-s', '--stop', 'stops running timer') { thyme.stop; exit }
|
16
|
+
thyme.load(o)
|
15
17
|
o.parse!
|
16
|
-
thyme.run
|
18
|
+
thyme.run
|
17
19
|
end
|
data/lib/thyme.rb
CHANGED
@@ -1,205 +1,10 @@
|
|
1
|
-
require 'ruby-progressbar'
|
2
1
|
require 'date'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@break = false
|
13
|
-
@interval = 1
|
14
|
-
@timer = 25 * 60
|
15
|
-
@timer_break = 5 * 60
|
16
|
-
@tmux = false
|
17
|
-
@tmux_theme = "#[default]#[fg=%s]%s#[default]"
|
18
|
-
@warning = 5 * 60
|
19
|
-
@warning_color = "red,bold"
|
20
|
-
@plugins = []
|
21
|
-
end
|
22
|
-
|
23
|
-
def use(plugin_class, *args, &block)
|
24
|
-
@plugins << plugin_class.new(self, *args, &block)
|
25
|
-
end
|
26
|
-
|
27
|
-
def run(force=false)
|
28
|
-
if force
|
29
|
-
running? ? stop_timer : start_timer
|
30
|
-
else
|
31
|
-
@run = true
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def break!
|
36
|
-
@break = true
|
37
|
-
end
|
38
|
-
|
39
|
-
def daemonize!
|
40
|
-
@daemon = true
|
41
|
-
Process.daemon
|
42
|
-
end
|
43
|
-
|
44
|
-
def daemon?
|
45
|
-
!!@daemon
|
46
|
-
end
|
47
|
-
|
48
|
-
def set(opt, val)
|
49
|
-
raise ThymeError.new("Invalid option: #{opt}") if !OPTIONS.include?(opt.to_sym)
|
50
|
-
self.instance_variable_set("@#{opt}", val)
|
51
|
-
end
|
52
|
-
|
53
|
-
def before(&block)
|
54
|
-
hooks_plugin.add(:before, &block)
|
55
|
-
end
|
56
|
-
|
57
|
-
def after(&block)
|
58
|
-
hooks_plugin.add(:after, &block)
|
59
|
-
end
|
60
|
-
|
61
|
-
def tick(&block)
|
62
|
-
hooks_plugin.add(:tick, &block)
|
63
|
-
end
|
64
|
-
|
65
|
-
def option(optparse, short, long, desc, &block)
|
66
|
-
optparse.on("-#{short}", "--#{long}", desc) do |*args|
|
67
|
-
self.instance_exec(*args, &block)
|
68
|
-
exit if !@run
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def load_config(optparse)
|
73
|
-
return if !File.exists?(CONFIG_FILE)
|
74
|
-
app = self
|
75
|
-
environment = Class.new do
|
76
|
-
define_method(:set) { |opt,val| app.set(opt,val) }
|
77
|
-
define_method(:use) { |plugin,*args,&b| app.use(plugin,*args,&b) }
|
78
|
-
define_method(:before) { |&block| app.before(&block) }
|
79
|
-
define_method(:after) { |&block| app.after(&block) }
|
80
|
-
define_method(:tick) { |&block| app.tick(&block) }
|
81
|
-
define_method(:option) { |sh,lo,desc,&b| app.option(optparse,sh,lo,desc,&b) }
|
82
|
-
end.new
|
83
|
-
environment.instance_eval(File.read(CONFIG_FILE), CONFIG_FILE)
|
84
|
-
end
|
85
|
-
|
86
|
-
def running?
|
87
|
-
File.exists?(PID_FILE)
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
def start_timer
|
93
|
-
File.open(PID_FILE, "w") { |f| f.print(Process.pid) }
|
94
|
-
seconds_start = @break ? @timer_break : @timer
|
95
|
-
seconds_left = seconds_start + 1
|
96
|
-
start_time = DateTime.now
|
97
|
-
min_length = (seconds_left / 60).floor.to_s.length
|
98
|
-
tmux_file = File.open(TMUX_FILE, "w") if @tmux
|
99
|
-
started = false
|
100
|
-
bar = ENV['THYME_TEST'].nil? && !daemon? ?
|
101
|
-
ProgressBar.create(
|
102
|
-
title: format(seconds_left-1, min_length),
|
103
|
-
total: seconds_start,
|
104
|
-
length: 50,
|
105
|
-
format: '[%B] %t') : nil
|
106
|
-
while seconds_left > 0
|
107
|
-
seconds_passed = seconds_since(start_time)
|
108
|
-
seconds_left = [seconds_start - seconds_passed, 0].max
|
109
|
-
title = format(seconds_left, min_length)
|
110
|
-
fg = color(seconds_left)
|
111
|
-
if bar
|
112
|
-
bar.title = title
|
113
|
-
bar.progress = seconds_passed
|
114
|
-
end
|
115
|
-
if @tmux
|
116
|
-
tmux_file.truncate(0)
|
117
|
-
tmux_file.rewind
|
118
|
-
tmux_file.write(@tmux_theme % [fg, title])
|
119
|
-
tmux_file.flush
|
120
|
-
end
|
121
|
-
unless started
|
122
|
-
started = true
|
123
|
-
send_to_plugin :before
|
124
|
-
end
|
125
|
-
send_to_plugin :tick, seconds_left
|
126
|
-
sleep(@interval)
|
127
|
-
end
|
128
|
-
rescue SignalException => e
|
129
|
-
puts ""
|
130
|
-
ensure
|
131
|
-
tmux_file.close if tmux_file
|
132
|
-
File.delete(TMUX_FILE) if File.exists?(TMUX_FILE)
|
133
|
-
File.delete(PID_FILE) if File.exists?(PID_FILE)
|
134
|
-
seconds_left = [seconds_start - seconds_since(start_time), 0].max
|
135
|
-
send_to_plugin :after, seconds_left
|
136
|
-
end
|
137
|
-
|
138
|
-
def stop_timer
|
139
|
-
pid = File.read(PID_FILE).to_i
|
140
|
-
Process.kill('TERM', pid) if pid > 1
|
141
|
-
rescue Errno::ESRCH # process is already dead, cleanup files and restart
|
142
|
-
File.delete(TMUX_FILE) if File.exists?(TMUX_FILE)
|
143
|
-
File.delete(PID_FILE) if File.exists?(PID_FILE)
|
144
|
-
end
|
145
|
-
|
146
|
-
def send_to_plugin(message, *args)
|
147
|
-
@plugins.each do |plugin|
|
148
|
-
begin
|
149
|
-
plugin.public_send(message, *args) if plugin.respond_to?(message)
|
150
|
-
rescue
|
151
|
-
$stderr.puts "Exception raised from #{plugin.class}:", $!, $@
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def hooks_plugin
|
157
|
-
@hooks_plugin ||= begin
|
158
|
-
use ThymeHooksPlugin
|
159
|
-
@plugins.last
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def seconds_since(time)
|
164
|
-
((DateTime.now - time) * 24 * 60 * 60).to_i
|
165
|
-
end
|
166
|
-
|
167
|
-
def format(seconds, min_length)
|
168
|
-
min = (seconds / 60).floor
|
169
|
-
lead = ' ' * (min_length - min.to_s.length)
|
170
|
-
sec = (seconds % 60).floor
|
171
|
-
sec = "0#{sec}" if sec.to_s.length == 1
|
172
|
-
@interval < 60 ?
|
173
|
-
"#{lead}#{min}:#{sec}" :
|
174
|
-
"#{lead}#{min}m"
|
175
|
-
end
|
176
|
-
|
177
|
-
def color(seconds)
|
178
|
-
!@break && seconds < @warning ? @warning_color : 'default'
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
class ThymeError < StandardError; end;
|
183
|
-
|
184
|
-
class ThymeHooksPlugin
|
185
|
-
def initialize(app)
|
186
|
-
@app = app
|
187
|
-
@hooks = {before: [], tick: [], after: []}
|
188
|
-
end
|
189
|
-
|
190
|
-
def add(type, &block)
|
191
|
-
@hooks[type] << block
|
192
|
-
end
|
193
|
-
|
194
|
-
def before
|
195
|
-
@hooks[:before].each { |b| @app.instance_exec(&b) }
|
196
|
-
end
|
197
|
-
|
198
|
-
def tick(seconds_left)
|
199
|
-
@hooks[:tick].each { |t| @app.instance_exec(seconds_left, &t) }
|
200
|
-
end
|
201
|
-
|
202
|
-
def after(seconds_left)
|
203
|
-
@hooks[:after].each { |a| @app.instance_exec(seconds_left, &a) }
|
204
|
-
end
|
205
|
-
end
|
2
|
+
require 'ruby-progressbar'
|
3
|
+
require_relative 'thyme/config'
|
4
|
+
require_relative 'thyme/console'
|
5
|
+
require_relative 'thyme/error'
|
6
|
+
require_relative 'thyme/format'
|
7
|
+
require_relative 'thyme/hooks_plugin'
|
8
|
+
require_relative 'thyme/timer'
|
9
|
+
require_relative 'thyme/tmux'
|
10
|
+
require_relative 'thyme/version'
|
data/lib/thyme/config.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Thyme
|
2
|
+
class Config
|
3
|
+
CONFIG_FILE = "#{ENV['HOME']}/.thymerc"
|
4
|
+
PID_FILE = "#{ENV['HOME']}/.thyme-pid"
|
5
|
+
TMUX_FILE = "#{ENV['HOME']}/.thyme-tmux"
|
6
|
+
OPTIONS = [:break_color, :interval, :timer, :timer_break, :tmux, :tmux_theme, :warning, :warning_color]
|
7
|
+
OPTIONS.each { |opt| attr_reader(opt) }
|
8
|
+
attr_accessor :break, :daemon, :repeat, :repeat_index
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
# options set via config file
|
12
|
+
@break_color = 'default'
|
13
|
+
@interval = 1
|
14
|
+
@timer = 25 * 60
|
15
|
+
@timer_break = 5 * 60
|
16
|
+
@tmux = false
|
17
|
+
@tmux_theme = "#[default]#[fg=%s]%s#[default]"
|
18
|
+
@warning = 5 * 60
|
19
|
+
@warning_color = 'red,bold'
|
20
|
+
|
21
|
+
# plugins set via config file
|
22
|
+
@plugins = []
|
23
|
+
@hooks_plugin = use(Thyme::HooksPlugin)
|
24
|
+
|
25
|
+
# settings via command line
|
26
|
+
@break = false
|
27
|
+
@daemon = false
|
28
|
+
@repeat = 1
|
29
|
+
@repeat_index = 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(opt, val)
|
33
|
+
raise Thyme::Error.new("Invalid option: #{opt}") if !OPTIONS.include?(opt.to_sym)
|
34
|
+
self.instance_variable_set("@#{opt}", val)
|
35
|
+
end
|
36
|
+
|
37
|
+
def use(plugin_class, *args, &block)
|
38
|
+
plugin = plugin_class.new(self, *args, &block)
|
39
|
+
@plugins << plugin
|
40
|
+
plugin
|
41
|
+
end
|
42
|
+
|
43
|
+
def before(kind = :each, &block)
|
44
|
+
type = kind == :all ? :before_all : :before
|
45
|
+
@hooks_plugin.add(type, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def after(kind = :each, &block)
|
49
|
+
type = kind == :all ? :after_all : :after
|
50
|
+
@hooks_plugin.add(type, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def tick(&block)
|
54
|
+
@hooks_plugin.add(:tick, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def option(optparse, short, long, desc, &block)
|
58
|
+
optparse.on("-#{short}", "--#{long}", desc) do |*args|
|
59
|
+
self.instance_exec(*args, &block)
|
60
|
+
exit if !@run
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_to_plugin(message, *args)
|
65
|
+
@plugins.each do |plugin|
|
66
|
+
begin
|
67
|
+
plugin.public_send(message, *args) if plugin.respond_to?(message)
|
68
|
+
rescue
|
69
|
+
$stderr.puts "Exception raised from #{plugin.class}:", $!, $@
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Thyme
|
2
|
+
class Console
|
3
|
+
attr_accessor :config
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@config = Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def break!
|
10
|
+
@config.break = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def daemonize!
|
14
|
+
@config.daemon = true
|
15
|
+
Process.daemon if !ENV['THYME_TEST']
|
16
|
+
end
|
17
|
+
|
18
|
+
def repeat!(count = 0)
|
19
|
+
@config.repeat = count.to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
timer.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
timer.run
|
28
|
+
end
|
29
|
+
|
30
|
+
def load(optparse, &block)
|
31
|
+
return if block.nil? && !File.exists?(Config::CONFIG_FILE)
|
32
|
+
config = @config
|
33
|
+
environment = Class.new do
|
34
|
+
define_method(:set) { |opt,val| config.set(opt,val) }
|
35
|
+
define_method(:use) { |plugin,*args,&b| config.use(plugin,*args,&b) }
|
36
|
+
define_method(:before) { |*args,&block| config.before(*args,&block) }
|
37
|
+
define_method(:after) { |*args,&block| config.after(*args,&block) }
|
38
|
+
define_method(:tick) { |&block| config.tick(&block) }
|
39
|
+
define_method(:option) { |sh,lo,desc,&b| config.option(optparse,sh,lo,desc,&b) }
|
40
|
+
end.new
|
41
|
+
|
42
|
+
if block # for test environment
|
43
|
+
environment.instance_eval(&block)
|
44
|
+
else
|
45
|
+
environment.instance_eval(File.read(Config::CONFIG_FILE), Config::CONFIG_FILE)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def timer
|
52
|
+
Timer.new(@config)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/thyme/error.rb
ADDED
data/lib/thyme/format.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Thyme
|
2
|
+
class Format
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def seconds_since(time)
|
8
|
+
((DateTime.now - time) * 24 * 60 * 60).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def time_left(seconds, min_length)
|
12
|
+
min = (seconds / 60).floor
|
13
|
+
lead = ' ' * [0, min_length - min.to_s.length].max
|
14
|
+
sec = (seconds % 60).floor
|
15
|
+
sec = "0#{sec}" if sec.to_s.length == 1
|
16
|
+
@config.interval < 60 ?
|
17
|
+
"#{lead}#{min}:#{sec} #{repeat_subtitle}".sub(/\s*$/, '') :
|
18
|
+
"#{lead}#{min}m #{repeat_subtitle}".sub(/\s*$/, '')
|
19
|
+
end
|
20
|
+
|
21
|
+
def repeat_subtitle
|
22
|
+
if @config.repeat == 1
|
23
|
+
''
|
24
|
+
elsif @config.repeat == 0
|
25
|
+
"(#{@config.repeat_index})"
|
26
|
+
else
|
27
|
+
"(#{@config.repeat_index}/#{@config.repeat})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tmux_color(seconds)
|
32
|
+
if @config.break
|
33
|
+
@config.break_color
|
34
|
+
elsif seconds < @config.warning
|
35
|
+
@config.warning_color
|
36
|
+
else
|
37
|
+
'default'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Thyme
|
2
|
+
class HooksPlugin
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
@hooks = {before_all: [], before: [], tick: [], after: [], after_all: []}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(type, &block)
|
9
|
+
@hooks[type] << block
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_all
|
13
|
+
@hooks[:before_all].each { |b| @config.instance_exec(&b) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def before
|
17
|
+
@hooks[:before].each { |b| @config.instance_exec(&b) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def tick(seconds_left)
|
21
|
+
@hooks[:tick].each { |t| @config.instance_exec(seconds_left, &t) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def after(seconds_left)
|
25
|
+
@hooks[:after].each { |a| @config.instance_exec(seconds_left, &a) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def after_all
|
29
|
+
@hooks[:after_all].each { |a| @config.instance_exec(&a) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/thyme/timer.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Thyme
|
2
|
+
class Timer
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
@format = Format.new(config)
|
6
|
+
@tmux = Tmux.new(config)
|
7
|
+
end
|
8
|
+
|
9
|
+
def stop
|
10
|
+
send_signal('TERM')
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
# pause/unpause timer if it's already running
|
15
|
+
send_signal('USR1') and return if File.exists?(Config::PID_FILE)
|
16
|
+
|
17
|
+
begin
|
18
|
+
File.open(Config::PID_FILE, "w") { |f| f.print(Process.pid) }
|
19
|
+
@tmux.open
|
20
|
+
if @config.repeat == 1
|
21
|
+
run_single
|
22
|
+
else
|
23
|
+
while @config.repeat_index <= @config.repeat || @config.repeat == 0
|
24
|
+
@config.break = false
|
25
|
+
run_single
|
26
|
+
if @config.repeat_index < @config.repeat || @config.repeat == 0
|
27
|
+
@config.break = true
|
28
|
+
run_single
|
29
|
+
end
|
30
|
+
@config.repeat_index += 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue Thyme::StopTimer
|
34
|
+
# stop signal received
|
35
|
+
ensure
|
36
|
+
@tmux.close
|
37
|
+
File.delete(Config::PID_FILE) if File.exists?(Config::PID_FILE)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def run_single
|
44
|
+
seconds_total = @config.break ? @config.timer_break : @config.timer
|
45
|
+
seconds_left = seconds_total + 1
|
46
|
+
start_time = DateTime.now
|
47
|
+
paused_time = nil
|
48
|
+
min_length = (seconds_left / 60).floor.to_s.length
|
49
|
+
started = false
|
50
|
+
@bar ||= ENV['THYME_TEST'].nil? && !@config.daemon ?
|
51
|
+
ProgressBar.create(
|
52
|
+
title: @format.time_left(seconds_left-1, min_length),
|
53
|
+
total: seconds_total,
|
54
|
+
length: 50,
|
55
|
+
format: '[%B] %t') : nil
|
56
|
+
@bar.reset if @bar
|
57
|
+
while seconds_left > 0
|
58
|
+
begin
|
59
|
+
if paused_time
|
60
|
+
sleep(@config.interval)
|
61
|
+
next
|
62
|
+
end
|
63
|
+
seconds_passed = @format.seconds_since(start_time)
|
64
|
+
seconds_left = [seconds_total - seconds_passed, 0].max
|
65
|
+
title = @format.time_left(seconds_left, min_length)
|
66
|
+
if @bar
|
67
|
+
@bar.title = title
|
68
|
+
if seconds_left == 0 && !last?
|
69
|
+
@bar.progress = seconds_passed - 0.01 # prevent bar from finishing
|
70
|
+
else
|
71
|
+
@bar.progress = seconds_passed
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@tmux.tick(@format.tmux_color(seconds_left), title)
|
75
|
+
unless started
|
76
|
+
started = true
|
77
|
+
@config.send_to_plugin(:before_all) if first?
|
78
|
+
@config.send_to_plugin(:before)
|
79
|
+
end
|
80
|
+
@config.send_to_plugin(:tick, seconds_left)
|
81
|
+
sleep(@config.interval)
|
82
|
+
rescue SignalException => e
|
83
|
+
if e.signm == 'SIGUSR1' && paused_time.nil?
|
84
|
+
paused_time = DateTime.now
|
85
|
+
elsif e.signm == 'SIGUSR1'
|
86
|
+
delta = DateTime.now - paused_time
|
87
|
+
start_time += delta
|
88
|
+
paused_time = nil
|
89
|
+
else
|
90
|
+
puts ""
|
91
|
+
@interrupted = true
|
92
|
+
raise Thyme::StopTimer
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
ensure
|
97
|
+
seconds_left = [seconds_total - @format.seconds_since(start_time), 0].max
|
98
|
+
@config.send_to_plugin(:after, seconds_left)
|
99
|
+
@config.send_to_plugin(:after_all) if @interrupted || last?
|
100
|
+
end
|
101
|
+
|
102
|
+
def first?
|
103
|
+
@config.repeat == 1 || (!@config.break && @config.repeat_index == 1)
|
104
|
+
end
|
105
|
+
|
106
|
+
def last?
|
107
|
+
@config.repeat == @config.repeat_index
|
108
|
+
end
|
109
|
+
|
110
|
+
def send_signal(signal)
|
111
|
+
pid = File.read(Config::PID_FILE).to_i
|
112
|
+
Process.kill(signal, pid) if pid > 1
|
113
|
+
rescue Errno::ESRCH, Errno::ENOENT # process is already dead, cleanup files
|
114
|
+
File.delete(Config::TMUX_FILE) if File.exists?(Config::TMUX_FILE)
|
115
|
+
File.delete(Config::PID_FILE) if File.exists?(Config::PID_FILE)
|
116
|
+
ensure
|
117
|
+
true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/thyme/tmux.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Thyme
|
2
|
+
class Tmux
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def open
|
8
|
+
return if !@config.tmux
|
9
|
+
@tmux_file = File.open(Config::TMUX_FILE, "w")
|
10
|
+
@tmux_file.truncate(0)
|
11
|
+
@tmux_file.rewind
|
12
|
+
@tmux_file.write('')
|
13
|
+
@tmux_file.flush
|
14
|
+
end
|
15
|
+
|
16
|
+
def tick(color, title)
|
17
|
+
return if !@tmux_file
|
18
|
+
@tmux_file.truncate(0)
|
19
|
+
@tmux_file.rewind
|
20
|
+
@tmux_file.write(@config.tmux_theme % [color, title])
|
21
|
+
@tmux_file.flush
|
22
|
+
end
|
23
|
+
|
24
|
+
def close
|
25
|
+
@tmux_file.close if @tmux_file
|
26
|
+
File.delete(Config::TMUX_FILE) if File.exists?(Config::TMUX_FILE)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thyme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hugh Bien
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-progressbar
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '1.0'
|
27
27
|
description: Extensible and configurable timer for Pomodoro Technique.
|
28
28
|
email:
|
29
29
|
- hugh@hughbien.com
|
@@ -36,8 +36,17 @@ files:
|
|
36
36
|
- README.md
|
37
37
|
- bin/thyme
|
38
38
|
- lib/thyme.rb
|
39
|
-
|
40
|
-
|
39
|
+
- lib/thyme/config.rb
|
40
|
+
- lib/thyme/console.rb
|
41
|
+
- lib/thyme/error.rb
|
42
|
+
- lib/thyme/format.rb
|
43
|
+
- lib/thyme/hooks_plugin.rb
|
44
|
+
- lib/thyme/timer.rb
|
45
|
+
- lib/thyme/tmux.rb
|
46
|
+
- lib/thyme/version.rb
|
47
|
+
homepage: http://hughbien.com/thyme/
|
48
|
+
licenses:
|
49
|
+
- BSD
|
41
50
|
metadata: {}
|
42
51
|
post_install_message:
|
43
52
|
rdoc_options: []
|
@@ -55,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
64
|
version: 1.3.6
|
56
65
|
requirements: []
|
57
66
|
rubyforge_project:
|
58
|
-
rubygems_version: 2.
|
67
|
+
rubygems_version: 2.4.5
|
59
68
|
signing_key:
|
60
69
|
specification_version: 4
|
61
70
|
summary: Timer for Pomodoro Technique
|