thyme 0.0.10 → 0.0.16
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 +7 -0
- data/README.md +85 -42
- data/bin/thyme +7 -5
- data/lib/thyme.rb +9 -119
- data/lib/thyme/config.rb +78 -0
- data/lib/thyme/console.rb +58 -0
- data/lib/thyme/error.rb +4 -0
- data/lib/thyme/format.rb +43 -0
- data/lib/thyme/hooks_plugin.rb +33 -0
- data/lib/thyme/timer.rb +124 -0
- data/lib/thyme/tmux.rb +31 -0
- data/lib/thyme/version.rb +3 -0
- metadata +22 -18
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5abf29ab5c23ad3c627e59ba2359fe9edadd4e29d3a9867a8bd5544bc3f4b9a1
|
|
4
|
+
data.tar.gz: 843d993b305a5e72067ba9e426f0b840fdc3f5f352c092fb3dff46087558c809
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8258374971ba89526629690016690128b84cd3ba7a77adfaead03cdac0f463db4df4dbf02462938194f64483a9bd2f16fdd0a96c73a3f4a659745ffa401c4c52
|
|
7
|
+
data.tar.gz: 9bf433d64f8268e896a385e49777bb25d279f2b98af52bfc24c5598710dc1635a2161a54de9012aad992f762c9148b56dd45b816d699dc34e3322c7dad7d0796
|
data/README.md
CHANGED
|
@@ -1,74 +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
|
-
minutes 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
|
-
set :interval, 1
|
|
36
|
-
set :tmux, true
|
|
37
|
-
set :tmux_theme, "#[fg=mycolor,bg=mycolor]#[fg=%s]%s#[fg=mycolor,bg=mycolor]"
|
|
31
|
+
Configurations live in the `~/.thymerc` file:
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
set :tmux_theme, "#[fg=mycolor,bg=mycolor]#[fg=%s]%s#[fg=mycolor,bg=mycolor]"
|
|
43
41
|
|
|
42
|
+
# adds `-t --today` option, which opens a text file in vim
|
|
44
43
|
option :t, :today, 'open today sheet' do
|
|
45
44
|
`vim -O ~/.thyme-today.md ~/.thyme-records.md < \`tty\` > \`tty\``
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
# adds `-s --seconds num` option, which allows on the fly timer
|
|
48
|
+
option :s, 'seconds num', 'run with custom seconds' do |num|
|
|
49
|
+
set :timer, num.to_i
|
|
50
|
+
@run = true
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
# execute hook before thyme program starts
|
|
54
|
+
before(:all) do
|
|
55
|
+
`mplayer ~/music/flight-of-the-bumble-bee.mp3 &`
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* `:tmux` is whether or not you want tmux integration on (off by default)
|
|
61
|
-
* `:tmux_theme` optionally lets you format the tmux status
|
|
58
|
+
# execute hook before each pomodoro
|
|
59
|
+
before do
|
|
60
|
+
`terminal-notifier -message "Let's get started!"`
|
|
61
|
+
end
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
# execute hook after each pomodoro
|
|
64
|
+
after do |seconds_left|
|
|
65
|
+
`terminal-notifier -message "Thyme's Up!"` if seconds_left == 0
|
|
66
|
+
end
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
# execute hook after thyme program quits
|
|
69
|
+
after(:all) do
|
|
70
|
+
`mplayer ~/music/victory.mp3 &`
|
|
71
|
+
end
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
===========
|
|
73
|
+
# Tmux
|
|
72
74
|
|
|
73
75
|
For tmux integration, make sure to set the `:tmux` option in `~/.thymerc`:
|
|
74
76
|
|
|
@@ -79,14 +81,55 @@ Then in your `.tmux.conf` file:
|
|
|
79
81
|
set-option -g status-right '#(cat ~/.thyme-tmux)'
|
|
80
82
|
set-option -g status-interval 1
|
|
81
83
|
|
|
82
|
-
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
|
|
83
85
|
works if you have tmux integration setup for the countdown:
|
|
84
86
|
|
|
85
87
|
nmap <leader>t :!thyme -d<cr>
|
|
86
|
-
nmap <leader>T :!thyme -s<cr>
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
# Plugins
|
|
90
|
+
|
|
91
|
+
Thyme's functionality can also be extended with plugins. They'll usually be installed
|
|
92
|
+
in `~/.thymerc` like this:
|
|
93
|
+
|
|
94
|
+
require 'thyme_growl'
|
|
95
|
+
use ThymeGrowl, text: 'Go take a break!'
|
|
96
|
+
|
|
97
|
+
You can create your own plugins. They implement these methods:
|
|
98
|
+
|
|
99
|
+
class MyThymePlugin
|
|
100
|
+
def initialize(thyme, options={})
|
|
101
|
+
# `thyme` is an instance of Thyme::Config (see lib/thyme/config.rb)
|
|
102
|
+
|
|
103
|
+
# adds `-t --today` option, which opens a text file in vim
|
|
104
|
+
thyme.option :t, :today, 'open today sheet' do
|
|
105
|
+
`vim -O ~/.thyme-today.md ~/.thyme-records.md < \`tty\` > \`tty\``
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def before_all
|
|
110
|
+
# code to run when thyme starts up
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def before
|
|
114
|
+
# code to run when timer starts
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def tick(seconds_left)
|
|
118
|
+
# code to run each tick
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def after(seconds_left)
|
|
122
|
+
# code to run when timer stops
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def after_all
|
|
126
|
+
# code to run when thyme program ends
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
The `before_all`, `before`, `tick`, `after`, and `after_all` methods are all optional.
|
|
131
|
+
|
|
132
|
+
# License
|
|
90
133
|
|
|
91
134
|
Copyright Hugh Bien - http://hughbien.com.
|
|
92
135
|
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
|
|
8
|
-
thyme.load_config(o)
|
|
7
|
+
thyme = Thyme::Console.new
|
|
9
8
|
o.set_summary_indent(' ')
|
|
10
|
-
o.banner =
|
|
9
|
+
o.banner = "Usage: #{File.basename($0)} [OPTION]"
|
|
11
10
|
o.define_head "Timer for Pomodoro Technique"
|
|
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
|
-
o.on('-
|
|
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
18
|
thyme.run
|
|
17
19
|
end
|
data/lib/thyme.rb
CHANGED
|
@@ -1,120 +1,10 @@
|
|
|
1
|
-
require 'ruby-progressbar'
|
|
2
1
|
require 'date'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@timer = 25 * 60
|
|
13
|
-
@tmux = false
|
|
14
|
-
@interval = 1
|
|
15
|
-
@tmux_theme = "#[default]#[fg=%s]%s#[default]"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def run
|
|
19
|
-
before_hook = @before
|
|
20
|
-
seconds_start = @timer
|
|
21
|
-
seconds_left = seconds_start + 1
|
|
22
|
-
start_time = DateTime.now
|
|
23
|
-
min_length = (seconds_left / 60).floor.to_s.length
|
|
24
|
-
tmux_file = File.open(TMUX_FILE, "w")
|
|
25
|
-
bar = ProgressBar.create(
|
|
26
|
-
title: format(seconds_left-1, min_length),
|
|
27
|
-
total: seconds_start,
|
|
28
|
-
length: 50,
|
|
29
|
-
format: '[%B] %t')
|
|
30
|
-
while seconds_left > 0
|
|
31
|
-
seconds_passed = seconds_since(start_time)
|
|
32
|
-
seconds_left = [seconds_start - seconds_passed, 0].max
|
|
33
|
-
title = format(seconds_left, min_length)
|
|
34
|
-
fg = color(seconds_left)
|
|
35
|
-
bar.title = title
|
|
36
|
-
bar.progress = seconds_passed
|
|
37
|
-
if @tmux
|
|
38
|
-
tmux_file.truncate(0)
|
|
39
|
-
tmux_file.rewind
|
|
40
|
-
tmux_file.write(@tmux_theme % [fg, title])
|
|
41
|
-
tmux_file.flush
|
|
42
|
-
end
|
|
43
|
-
if before_hook
|
|
44
|
-
self.instance_exec(&before_hook)
|
|
45
|
-
before_hook = nil
|
|
46
|
-
end
|
|
47
|
-
sleep(@interval)
|
|
48
|
-
end
|
|
49
|
-
rescue SignalException => e
|
|
50
|
-
puts ""
|
|
51
|
-
ensure
|
|
52
|
-
tmux_file.close
|
|
53
|
-
File.delete(TMUX_FILE) if File.exists?(TMUX_FILE)
|
|
54
|
-
File.delete(PID_FILE) if File.exists?(PID_FILE)
|
|
55
|
-
seconds_left = [seconds_start - seconds_since(start_time), 0].max
|
|
56
|
-
self.instance_exec(seconds_left, &@after) if @after
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def stop
|
|
60
|
-
return if !File.exists?(PID_FILE)
|
|
61
|
-
pid = File.read(PID_FILE).to_i
|
|
62
|
-
File.delete(PID_FILE)
|
|
63
|
-
Process.kill('TERM', pid) if pid > 1
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def daemonize!
|
|
67
|
-
Process.daemon
|
|
68
|
-
File.open(PID_FILE, "w") { |f| f.print(Process.pid) }
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def set(opt, val)
|
|
72
|
-
raise ThymeError.new("Invalid option: #{opt}") if !OPTIONS.include?(opt.to_sym)
|
|
73
|
-
self.instance_variable_set("@#{opt}", val)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def before(&block)
|
|
77
|
-
@before = block
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def after(&block)
|
|
81
|
-
@after = block
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def option(optparse, short, long, desc, &block)
|
|
85
|
-
optparse.on("-#{short}", "--#{long}", desc) { self.instance_exec(&block); exit }
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def load_config(optparse)
|
|
89
|
-
return if !File.exists?(CONFIG_FILE)
|
|
90
|
-
app = self
|
|
91
|
-
Object.class_eval do
|
|
92
|
-
define_method(:set) { |opt,val| app.set(opt,val) }
|
|
93
|
-
define_method(:before) { |&block| app.before(&block) }
|
|
94
|
-
define_method(:after) { |&block| app.after(&block) }
|
|
95
|
-
define_method(:option) { |sh,lo,desc,&b| app.option(optparse,sh,lo,desc,&b) }
|
|
96
|
-
end
|
|
97
|
-
load(CONFIG_FILE, true)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
def seconds_since(time)
|
|
102
|
-
((DateTime.now - time) * 24 * 60 * 60).to_i
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def format(seconds, min_length)
|
|
106
|
-
min = (seconds / 60).floor
|
|
107
|
-
lead = ' ' * (min_length - min.to_s.length)
|
|
108
|
-
sec = (seconds % 60).floor
|
|
109
|
-
sec = "0#{sec}" if sec.to_s.length == 1
|
|
110
|
-
@interval < 60 ?
|
|
111
|
-
"#{lead}#{min}:#{sec}" :
|
|
112
|
-
"#{lead}#{min}m"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def color(seconds)
|
|
116
|
-
seconds < (5*60) ? 'red,bold' : 'default'
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
class ThymeError < StandardError; 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,78 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# Configure state for the application. This can be done via the thymerc file or CLI flags.
|
|
3
|
+
# Public methods in this file are exposed to the thymerc file.
|
|
4
|
+
class Config
|
|
5
|
+
CONFIG_FILE = "#{ENV['HOME']}/.thymerc"
|
|
6
|
+
PID_FILE = "#{ENV['HOME']}/.thyme-pid"
|
|
7
|
+
TMUX_FILE = "#{ENV['HOME']}/.thyme-tmux"
|
|
8
|
+
OPTIONS = [:default_color, :break_color, :interval, :timer, :timer_break, :tmux, :tmux_theme, :warning, :warning_color]
|
|
9
|
+
OPTIONS.each { |opt| attr_reader(opt) }
|
|
10
|
+
attr_accessor :break, :daemon, :repeat, :repeat_index, :optparse
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
# options set via config file
|
|
14
|
+
@break_color = 'default'
|
|
15
|
+
@default_color = 'default'
|
|
16
|
+
@interval = 1
|
|
17
|
+
@timer = 25 * 60
|
|
18
|
+
@timer_break = 5 * 60
|
|
19
|
+
@tmux = false
|
|
20
|
+
@tmux_theme = "#[default]#[fg=%s]%s#[default]"
|
|
21
|
+
@warning = 5 * 60
|
|
22
|
+
@warning_color = 'red,bold'
|
|
23
|
+
|
|
24
|
+
# plugins set via config file
|
|
25
|
+
@plugins = []
|
|
26
|
+
@hooks_plugin = use(Thyme::HooksPlugin)
|
|
27
|
+
|
|
28
|
+
# settings via command line
|
|
29
|
+
@break = false
|
|
30
|
+
@daemon = false
|
|
31
|
+
@repeat = 1
|
|
32
|
+
@repeat_index = 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set(opt, val)
|
|
36
|
+
raise Thyme::Error.new("Invalid option: #{opt}") if !OPTIONS.include?(opt.to_sym)
|
|
37
|
+
self.instance_variable_set("@#{opt}", val)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def use(plugin_class, *args, &block)
|
|
41
|
+
plugin = plugin_class.new(self, *args, &block)
|
|
42
|
+
@plugins << plugin
|
|
43
|
+
plugin
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def before(kind = :each, &block)
|
|
47
|
+
type = kind == :all ? :before_all : :before
|
|
48
|
+
@hooks_plugin.add(type, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def after(kind = :each, &block)
|
|
52
|
+
type = kind == :all ? :after_all : :after
|
|
53
|
+
@hooks_plugin.add(type, &block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def tick(&block)
|
|
57
|
+
@hooks_plugin.add(:tick, &block)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def option(short, long, desc, &block)
|
|
61
|
+
return if !@optparse
|
|
62
|
+
@optparse.on("-#{short}", "--#{long}", desc) do |*args|
|
|
63
|
+
self.instance_exec(*args, &block)
|
|
64
|
+
exit if !@run
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def send_to_plugin(message, *args)
|
|
69
|
+
@plugins.each do |plugin|
|
|
70
|
+
begin
|
|
71
|
+
plugin.public_send(message, *args) if plugin.respond_to?(message)
|
|
72
|
+
rescue
|
|
73
|
+
$stderr.puts "Exception raised from #{plugin.class}:", $!, $@
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# Exposes application to the CLI in bin/thyme
|
|
3
|
+
class Console
|
|
4
|
+
attr_accessor :config
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@config = Config.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def break!
|
|
11
|
+
@config.break = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def daemonize!
|
|
15
|
+
@config.daemon = true
|
|
16
|
+
Process.daemon if !ENV['THYME_TEST']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def repeat!(count = 0)
|
|
20
|
+
@config.repeat = count.to_i
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stop
|
|
24
|
+
timer.stop
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
timer.run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Loads the thymerc configuration file. Requires optparse b/c users can extend CLI via thymerc
|
|
32
|
+
def load(optparse, &block)
|
|
33
|
+
return if block.nil? && !File.exists?(Config::CONFIG_FILE)
|
|
34
|
+
config = @config
|
|
35
|
+
config.optparse = optparse
|
|
36
|
+
environment = Class.new do
|
|
37
|
+
define_method(:set) { |opt,val| config.set(opt,val) }
|
|
38
|
+
define_method(:use) { |plugin,*args,&b| config.use(plugin,*args,&b) }
|
|
39
|
+
define_method(:before) { |*args,&block| config.before(*args,&block) }
|
|
40
|
+
define_method(:after) { |*args,&block| config.after(*args,&block) }
|
|
41
|
+
define_method(:tick) { |&block| config.tick(&block) }
|
|
42
|
+
define_method(:option) { |sh,lo,desc,&b| config.option(sh,lo,desc,&b) }
|
|
43
|
+
end.new
|
|
44
|
+
|
|
45
|
+
if block # for test environment
|
|
46
|
+
environment.instance_eval(&block)
|
|
47
|
+
else
|
|
48
|
+
environment.instance_eval(File.read(Config::CONFIG_FILE), Config::CONFIG_FILE)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def timer
|
|
55
|
+
Timer.new(@config)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/thyme/error.rb
ADDED
data/lib/thyme/format.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# Methods used to nicely format output to the user
|
|
3
|
+
class Format
|
|
4
|
+
def initialize(config)
|
|
5
|
+
@config = config
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def seconds_since(time)
|
|
9
|
+
((DateTime.now - time) * 24 * 60 * 60).to_i
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Displays time depending on configured interval eg. 15m OR 15:20
|
|
13
|
+
def time_left(seconds, min_length)
|
|
14
|
+
min = (seconds / 60).floor
|
|
15
|
+
lead = ' ' * [0, min_length - min.to_s.length].max
|
|
16
|
+
sec = (seconds % 60).floor
|
|
17
|
+
sec = "0#{sec}" if sec.to_s.length == 1
|
|
18
|
+
@config.interval < 60 ?
|
|
19
|
+
"#{lead}#{min}:#{sec} #{repeat_subtitle}".sub(/\s*$/, '') :
|
|
20
|
+
"#{lead}#{min}m #{repeat_subtitle}".sub(/\s*$/, '')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def repeat_subtitle
|
|
24
|
+
if @config.repeat == 1
|
|
25
|
+
''
|
|
26
|
+
elsif @config.repeat == 0
|
|
27
|
+
"(#{@config.repeat_index})"
|
|
28
|
+
else
|
|
29
|
+
"(#{@config.repeat_index}/#{@config.repeat})"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def tmux_color(seconds)
|
|
34
|
+
if @config.break
|
|
35
|
+
@config.break_color
|
|
36
|
+
elsif seconds < @config.warning
|
|
37
|
+
@config.warning_color
|
|
38
|
+
else
|
|
39
|
+
@config.default_color
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# Generic plugin for users to run Ruby code in Thyme event callbacks in thymerc
|
|
3
|
+
class HooksPlugin
|
|
4
|
+
def initialize(config)
|
|
5
|
+
@config = config
|
|
6
|
+
@hooks = {before_all: [], before: [], tick: [], after: [], after_all: []}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def add(type, &block)
|
|
10
|
+
@hooks[type] << block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def before_all
|
|
14
|
+
@hooks[:before_all].each { |b| @config.instance_exec(&b) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def before
|
|
18
|
+
@hooks[:before].each { |b| @config.instance_exec(&b) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def tick(seconds_left)
|
|
22
|
+
@hooks[:tick].each { |t| @config.instance_exec(seconds_left, &t) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def after(seconds_left)
|
|
26
|
+
@hooks[:after].each { |a| @config.instance_exec(seconds_left, &a) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def after_all
|
|
30
|
+
@hooks[:after_all].each { |a| @config.instance_exec(&a) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/thyme/timer.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# The actual timer logic where you can pause, unpause, or stop one or more timers
|
|
3
|
+
class Timer
|
|
4
|
+
def initialize(config)
|
|
5
|
+
@config = config
|
|
6
|
+
@format = Format.new(config)
|
|
7
|
+
@tmux = Tmux.new(config)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def stop
|
|
11
|
+
send_signal('TERM')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
# pause/unpause timer if it's already running
|
|
16
|
+
send_signal('USR1') and return if File.exists?(Config::PID_FILE)
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
File.open(Config::PID_FILE, "w") { |f| f.print(Process.pid) }
|
|
20
|
+
@tmux.open
|
|
21
|
+
if @config.repeat == 1
|
|
22
|
+
run_single
|
|
23
|
+
else
|
|
24
|
+
while @config.repeat_index <= @config.repeat || @config.repeat == 0
|
|
25
|
+
@config.break = false
|
|
26
|
+
run_single
|
|
27
|
+
if @config.repeat_index < @config.repeat || @config.repeat == 0
|
|
28
|
+
@config.break = true
|
|
29
|
+
run_single
|
|
30
|
+
end
|
|
31
|
+
@config.repeat_index += 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
rescue Thyme::StopTimer
|
|
35
|
+
# stop signal received
|
|
36
|
+
ensure
|
|
37
|
+
@tmux.close
|
|
38
|
+
File.delete(Config::PID_FILE) if File.exists?(Config::PID_FILE)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# TODO: refactor this method, too large!
|
|
45
|
+
def run_single
|
|
46
|
+
seconds_total = @config.break ? @config.timer_break : @config.timer
|
|
47
|
+
seconds_left = seconds_total + 1
|
|
48
|
+
start_time = DateTime.now
|
|
49
|
+
paused_time = nil
|
|
50
|
+
min_length = (seconds_left / 60).floor.to_s.length
|
|
51
|
+
started = false
|
|
52
|
+
@bar ||= ENV['THYME_TEST'].nil? && !@config.daemon ?
|
|
53
|
+
ProgressBar.create(
|
|
54
|
+
title: @format.time_left(seconds_left-1, min_length),
|
|
55
|
+
total: seconds_total,
|
|
56
|
+
length: 50,
|
|
57
|
+
format: '[%B] %t') : nil
|
|
58
|
+
@bar.reset if @bar
|
|
59
|
+
while seconds_left > 0
|
|
60
|
+
begin
|
|
61
|
+
if paused_time
|
|
62
|
+
sleep(@config.interval)
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
seconds_passed = @format.seconds_since(start_time)
|
|
66
|
+
seconds_left = [seconds_total - seconds_passed, 0].max
|
|
67
|
+
title = @format.time_left(seconds_left, min_length)
|
|
68
|
+
if @bar
|
|
69
|
+
@bar.title = title
|
|
70
|
+
if seconds_left == 0 && !last?
|
|
71
|
+
@bar.progress = seconds_passed - 0.01 # prevent bar from finishing
|
|
72
|
+
else
|
|
73
|
+
@bar.progress = seconds_passed
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
@tmux.tick(@format.tmux_color(seconds_left), title)
|
|
77
|
+
unless started
|
|
78
|
+
started = true
|
|
79
|
+
@config.send_to_plugin(:before_all) if first?
|
|
80
|
+
@config.send_to_plugin(:before)
|
|
81
|
+
end
|
|
82
|
+
@config.send_to_plugin(:tick, seconds_left)
|
|
83
|
+
sleep(@config.interval)
|
|
84
|
+
rescue SignalException => e
|
|
85
|
+
if e.signm == 'SIGUSR1' && paused_time.nil?
|
|
86
|
+
paused_time = DateTime.now
|
|
87
|
+
elsif e.signm == 'SIGUSR1'
|
|
88
|
+
delta = DateTime.now - paused_time
|
|
89
|
+
start_time += delta
|
|
90
|
+
paused_time = nil
|
|
91
|
+
else
|
|
92
|
+
puts ""
|
|
93
|
+
@interrupted = true
|
|
94
|
+
raise Thyme::StopTimer
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
ensure
|
|
99
|
+
seconds_left = [seconds_total - @format.seconds_since(start_time), 0].max
|
|
100
|
+
@config.send_to_plugin(:after, seconds_left)
|
|
101
|
+
@config.send_to_plugin(:after_all) if @interrupted || last?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def first?
|
|
105
|
+
@config.repeat == 1 || (!@config.break && @config.repeat_index == 1)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def last?
|
|
109
|
+
@config.repeat == @config.repeat_index
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Since timers can be daemonized, we'll use Unix signals to trigger events such as
|
|
113
|
+
# pause/unpause in a separate process.
|
|
114
|
+
def send_signal(signal)
|
|
115
|
+
pid = File.read(Config::PID_FILE).to_i
|
|
116
|
+
Process.kill(signal, pid) if pid > 1
|
|
117
|
+
rescue Errno::ESRCH, Errno::ENOENT # process is already dead, cleanup files
|
|
118
|
+
File.delete(Config::TMUX_FILE) if File.exists?(Config::TMUX_FILE)
|
|
119
|
+
File.delete(Config::PID_FILE) if File.exists?(Config::PID_FILE)
|
|
120
|
+
ensure
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/thyme/tmux.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Thyme
|
|
2
|
+
# Provides tmux integration. Thyme outputs the timer to TMUX_FILE. Tmux reads this file and
|
|
3
|
+
# outputs to its bar.
|
|
4
|
+
class Tmux
|
|
5
|
+
def initialize(config)
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def open
|
|
10
|
+
return if !@config.tmux
|
|
11
|
+
@tmux_file = File.open(Config::TMUX_FILE, "w")
|
|
12
|
+
@tmux_file.truncate(0)
|
|
13
|
+
@tmux_file.rewind
|
|
14
|
+
@tmux_file.write('')
|
|
15
|
+
@tmux_file.flush
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tick(color, title)
|
|
19
|
+
return if !@tmux_file
|
|
20
|
+
@tmux_file.truncate(0)
|
|
21
|
+
@tmux_file.rewind
|
|
22
|
+
@tmux_file.write(@config.tmux_theme % [color, title])
|
|
23
|
+
@tmux_file.flush
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def close
|
|
27
|
+
@tmux_file.close if @tmux_file
|
|
28
|
+
File.delete(Config::TMUX_FILE) if File.exists?(Config::TMUX_FILE)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
metadata
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: thyme
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.0.16
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Hugh Bien
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2020-10-19 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: ruby-progressbar
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
none: false
|
|
18
16
|
requirements:
|
|
19
|
-
- -
|
|
17
|
+
- - "~>"
|
|
20
18
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: '0'
|
|
19
|
+
version: '1.0'
|
|
22
20
|
type: :runtime
|
|
23
21
|
prerelease: false
|
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
-
none: false
|
|
26
23
|
requirements:
|
|
27
|
-
- -
|
|
24
|
+
- - "~>"
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '0'
|
|
26
|
+
version: '1.0'
|
|
30
27
|
description: Extensible and configurable timer for Pomodoro Technique.
|
|
31
28
|
email:
|
|
32
29
|
- hugh@hughbien.com
|
|
@@ -39,28 +36,35 @@ files:
|
|
|
39
36
|
- README.md
|
|
40
37
|
- bin/thyme
|
|
41
38
|
- lib/thyme.rb
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
50
|
+
metadata: {}
|
|
44
51
|
post_install_message:
|
|
45
52
|
rdoc_options: []
|
|
46
53
|
require_paths:
|
|
47
54
|
- lib
|
|
48
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
-
none: false
|
|
50
56
|
requirements:
|
|
51
|
-
- -
|
|
57
|
+
- - ">="
|
|
52
58
|
- !ruby/object:Gem::Version
|
|
53
59
|
version: '0'
|
|
54
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
-
none: false
|
|
56
61
|
requirements:
|
|
57
|
-
- -
|
|
62
|
+
- - ">="
|
|
58
63
|
- !ruby/object:Gem::Version
|
|
59
64
|
version: 1.3.6
|
|
60
65
|
requirements: []
|
|
61
|
-
|
|
62
|
-
rubygems_version: 1.8.23
|
|
66
|
+
rubygems_version: 3.1.2
|
|
63
67
|
signing_key:
|
|
64
|
-
specification_version:
|
|
68
|
+
specification_version: 4
|
|
65
69
|
summary: Timer for Pomodoro Technique
|
|
66
70
|
test_files: []
|