tomatoharvest 0.0.1 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +0 -1
- data/README.md +2 -0
- data/TODO +0 -2
- data/lib/tomatoharvest/cli.rb +16 -6
- data/lib/tomatoharvest/config.rb +29 -12
- data/lib/tomatoharvest/list.rb +25 -36
- data/lib/tomatoharvest/list_loader.rb +45 -0
- data/lib/tomatoharvest/time_entry.rb +1 -0
- data/lib/tomatoharvest/timer.rb +14 -10
- data/lib/tomatoharvest/tmux.rb +1 -1
- data/lib/tomatoharvest/version.rb +1 -1
- data/lib/tomatoharvest.rb +1 -0
- data/spec/helper.rb +24 -40
- data/spec/lib/tomatoharvest/cli_spec.rb +23 -4
- data/spec/lib/tomatoharvest/config_spec.rb +29 -9
- data/spec/lib/tomatoharvest/list_loader_spec.rb +69 -0
- data/spec/lib/tomatoharvest/list_spec.rb +28 -10
- data/spec/lib/tomatoharvest/time_entry_spec.rb +57 -38
- data/spec/lib/tomatoharvest/timer_spec.rb +13 -9
- data/spec/support/file_helpers.rb +18 -0
- data/spec/support/harvest_helpers.rb +25 -0
- data/tomatoharvest.gemspec +7 -7
- metadata +39 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f52afcccd4a11b6c0c6cda0692079bebcf3a96f
|
4
|
+
data.tar.gz: 32a18edb8874add209ba439cb094906c603d5d88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8945effcbc9b58c7eb8669447893a22d1bbad71eb19799ce268332ec1c9da69e2a93a9a4f50570846b0c8856d1a867c3a4f805b6aa5646a82429ca0756b93ec2
|
7
|
+
data.tar.gz: e5c24caf89932bb39c25fd0e912dce5bad88a123ee0363f522b979fe0677f24f0f115aa8533067455fc9043f5ebe95186aab0ee339f5222e180126adf2c8e2a5
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
data/TODO
CHANGED
data/lib/tomatoharvest/cli.rb
CHANGED
@@ -6,30 +6,34 @@ module TomatoHarvest
|
|
6
6
|
|
7
7
|
desc "add", "add a task"
|
8
8
|
def add(name)
|
9
|
+
list = ListLoader.from_file
|
9
10
|
task = Task.new(name)
|
10
|
-
|
11
|
+
list.add(task)
|
12
|
+
list.save!
|
11
13
|
say "#{task.name} added with id #{task.id}"
|
12
14
|
end
|
13
15
|
|
14
16
|
desc "list", "list all tasks"
|
15
17
|
def list
|
16
|
-
list
|
18
|
+
list = ListLoader.from_file
|
19
|
+
table = list.map do |task|
|
17
20
|
[task.id, task.name]
|
18
21
|
end
|
19
|
-
|
22
|
+
table.unshift(['id', 'name'])
|
20
23
|
|
21
24
|
shell = Thor::Base.shell.new
|
22
|
-
shell.print_table(
|
25
|
+
shell.print_table(table)
|
23
26
|
end
|
24
27
|
|
25
28
|
desc "start", "start a task"
|
26
29
|
def start(id, minutes = DEFAULT_MINUTES)
|
27
|
-
|
30
|
+
list = ListLoader.from_file
|
31
|
+
task = list.find(id)
|
28
32
|
config = Config.load.merge("name" => task.name)
|
29
33
|
entry = TimeEntry.build_and_test(config)
|
30
34
|
|
31
35
|
say "Timer started for #{task.name}"
|
32
|
-
Timer.start(task.id, minutes: minutes, time_entry: entry)
|
36
|
+
Timer.start(list, task.id, minutes: minutes, time_entry: entry)
|
33
37
|
end
|
34
38
|
|
35
39
|
desc "stop", "stop current timer"
|
@@ -41,6 +45,12 @@ module TomatoHarvest
|
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
48
|
+
desc "remove", "remove a task"
|
49
|
+
def remove(id)
|
50
|
+
list = ListLoader.from_file
|
51
|
+
task = list.remove(id)
|
52
|
+
say "#{id} removed"
|
53
|
+
end
|
44
54
|
|
45
55
|
end
|
46
56
|
end
|
data/lib/tomatoharvest/config.rb
CHANGED
@@ -1,21 +1,38 @@
|
|
1
1
|
module TomatoHarvest
|
2
2
|
class Config
|
3
|
-
|
4
|
-
LOCAL_CONFIG_PATH = File.join(Dir.pwd, '.tomaconfig')
|
3
|
+
DIR_NAME = '.toma'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
HOME_DIR = ENV['HOME']
|
6
|
+
GLOBAL_DIR = File.join(HOME_DIR, DIR_NAME)
|
7
|
+
LOCAL_DIR = File.join(Dir.pwd, DIR_NAME)
|
8
|
+
|
9
|
+
def self.load
|
10
|
+
old_config = merge_config(old_config_path)
|
11
|
+
|
12
|
+
global_path = config_path(GLOBAL_DIR)
|
13
|
+
global_config = merge_config(global_path, old_config)
|
14
|
+
|
15
|
+
local_path = config_path(LOCAL_DIR)
|
16
|
+
merge_config(local_path, global_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.merge_config(path, base = {})
|
20
|
+
mergable =
|
21
|
+
if File.exists?(path)
|
22
|
+
YAML.load_file(path)
|
23
|
+
else
|
24
|
+
{}
|
10
25
|
end
|
11
|
-
end
|
12
26
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
27
|
+
base.merge(mergable)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.config_path(directory)
|
31
|
+
File.join(directory, 'config.yaml')
|
32
|
+
end
|
17
33
|
|
18
|
-
|
34
|
+
def self.old_config_path
|
35
|
+
File.join(HOME_DIR, '.tomaconfig')
|
19
36
|
end
|
20
37
|
end
|
21
38
|
end
|
data/lib/tomatoharvest/list.rb
CHANGED
@@ -1,48 +1,47 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'forwardable'
|
2
3
|
|
3
4
|
module TomatoHarvest
|
4
5
|
class List
|
5
|
-
|
6
|
+
extend ::Forwardable
|
6
7
|
|
7
8
|
attr_reader :items
|
8
9
|
|
9
|
-
|
10
|
+
def_delegators :items, :count, :map
|
10
11
|
|
11
|
-
def self.
|
12
|
-
new.
|
13
|
-
list.add(item)
|
14
|
-
list.save
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.all
|
19
|
-
new.all
|
12
|
+
def self.init_and_load(*args)
|
13
|
+
new(*args).load!
|
20
14
|
end
|
21
15
|
|
22
|
-
def
|
23
|
-
|
16
|
+
def initialize(path, items = nil)
|
17
|
+
@path = path
|
18
|
+
@items = items || []
|
24
19
|
end
|
25
20
|
|
26
|
-
def
|
27
|
-
if File.exists?(
|
28
|
-
@items =
|
29
|
-
else
|
30
|
-
@items = []
|
21
|
+
def load!
|
22
|
+
if File.exists?(@path) && items = YAML.load_file(@path)
|
23
|
+
@items = items
|
31
24
|
end
|
25
|
+
|
26
|
+
self
|
32
27
|
end
|
33
28
|
|
34
29
|
def find(id)
|
35
|
-
|
36
|
-
all.find do |item|
|
30
|
+
@items.find do |item|
|
37
31
|
item.id == id.to_i
|
38
32
|
end
|
39
33
|
end
|
40
34
|
|
41
|
-
def save
|
42
|
-
|
43
|
-
|
35
|
+
def save!
|
36
|
+
dir = File.dirname(@path)
|
37
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
38
|
+
|
39
|
+
yaml = YAML.dump(@items)
|
40
|
+
File.open(@path, "w+") do |f|
|
44
41
|
f.write(yaml)
|
45
42
|
end
|
43
|
+
|
44
|
+
self
|
46
45
|
end
|
47
46
|
|
48
47
|
def add(item)
|
@@ -56,21 +55,11 @@ module TomatoHarvest
|
|
56
55
|
@items << item
|
57
56
|
end
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
string = ""
|
63
|
-
|
64
|
-
# better way to do this?
|
65
|
-
File.open(PATH, "r") do |f|
|
66
|
-
while line = f.gets
|
67
|
-
string += line
|
68
|
-
end
|
58
|
+
def remove(id)
|
59
|
+
@items.delete_if do |item|
|
60
|
+
item.id == id.to_i
|
69
61
|
end
|
70
|
-
|
71
|
-
YAML::load(string)
|
72
62
|
end
|
73
63
|
|
74
64
|
end
|
75
65
|
end
|
76
|
-
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module TomatoHarvest
|
2
|
+
|
3
|
+
class ListLoader
|
4
|
+
|
5
|
+
FILENAME = 'list.yaml'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def from_file
|
10
|
+
local_path = list_path(TomatoHarvest::Config::LOCAL_DIR)
|
11
|
+
|
12
|
+
if File.exists? local_path
|
13
|
+
List.init_and_load(local_path)
|
14
|
+
else
|
15
|
+
load_old_list || List.init_and_load(global_path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_old_list
|
20
|
+
old_path = File.join(TomatoHarvest::Config::HOME_DIR, '.toma')
|
21
|
+
|
22
|
+
if exists_as_file(old_path)
|
23
|
+
old_list = List.init_and_load(old_path)
|
24
|
+
File.delete old_path
|
25
|
+
List.new(global_path, old_list.items).save!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def exists_as_file(path)
|
30
|
+
File.exists?(path) && !File.directory?(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def global_path
|
34
|
+
list_path(TomatoHarvest::Config::GLOBAL_DIR)
|
35
|
+
end
|
36
|
+
|
37
|
+
def list_path(dir)
|
38
|
+
File.join(dir, FILENAME)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/tomatoharvest/timer.rb
CHANGED
@@ -2,32 +2,32 @@ require 'daemons'
|
|
2
2
|
|
3
3
|
module TomatoHarvest
|
4
4
|
class Timer
|
5
|
-
|
6
|
-
PID_NAME = '
|
5
|
+
SLEEP_LENGTH = 1
|
6
|
+
PID_NAME = 'pid'
|
7
7
|
|
8
|
-
def self.start(
|
9
|
-
new(
|
8
|
+
def self.start(*args)
|
9
|
+
new(*args).start
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.stop
|
13
|
-
if monitor = Daemons::Monitor.find(
|
13
|
+
if monitor = Daemons::Monitor.find(pid_dir, PID_NAME)
|
14
14
|
monitor.stop
|
15
15
|
true
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def initialize(task_id, options = {})
|
19
|
+
def initialize(list, task_id, options = {})
|
20
20
|
@minutes = options[:minutes]
|
21
21
|
@time_entry = options[:time_entry]
|
22
22
|
@notifier = Notifier.new
|
23
|
-
@list =
|
23
|
+
@list = list
|
24
24
|
@task = @list.find(task_id)
|
25
25
|
@timer = 0
|
26
26
|
@tmux = Tmux.new
|
27
27
|
end
|
28
28
|
|
29
29
|
def start
|
30
|
-
if Daemons.daemonize(app_name: PID_NAME, dir:
|
30
|
+
if Daemons.daemonize(app_name: PID_NAME, dir: self.class.pid_dir, dir_mode: :normal)
|
31
31
|
at_exit { save_and_log }
|
32
32
|
run_timer
|
33
33
|
else
|
@@ -36,13 +36,17 @@ module TomatoHarvest
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def self.pid_dir
|
40
|
+
TomatoHarvest::Config::GLOBAL_DIR
|
41
|
+
end
|
42
|
+
|
39
43
|
private
|
40
44
|
|
41
45
|
def run_timer
|
42
46
|
@notifier.notify "Pomodoro started for #{@minutes} minutes", :subtitle => @task.name
|
43
47
|
|
44
48
|
(@minutes * 60).times do |i|
|
45
|
-
sleep
|
49
|
+
sleep SLEEP_LENGTH
|
46
50
|
@timer += 1
|
47
51
|
@tmux.update(@timer)
|
48
52
|
end
|
@@ -50,7 +54,7 @@ module TomatoHarvest
|
|
50
54
|
|
51
55
|
def save_and_log
|
52
56
|
@task.log_pomodoro(@timer)
|
53
|
-
@list.save
|
57
|
+
@list.save!
|
54
58
|
@time_entry.log(@timer) if @time_entry
|
55
59
|
@notifier.notify "Pomodoro finished", :subtitle => "Pomodoro finished!"
|
56
60
|
@tmux.update(0)
|
data/lib/tomatoharvest/tmux.rb
CHANGED
data/lib/tomatoharvest.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -5,47 +5,29 @@ require 'tomatoharvest'
|
|
5
5
|
require 'webmock/rspec'
|
6
6
|
require 'minitest/unit'
|
7
7
|
|
8
|
+
require 'support/file_helpers'
|
9
|
+
require 'support/harvest_helpers'
|
10
|
+
|
8
11
|
WebMock.disable_net_connect!(allow_localhost: true)
|
9
12
|
|
10
13
|
RSpec.configure do |c|
|
11
14
|
c.include MiniTest::Assertions
|
15
|
+
c.include FileHelpers
|
16
|
+
c.include HarvestHelpers
|
12
17
|
|
13
18
|
#
|
14
19
|
# Speed up the timer
|
15
20
|
#
|
16
|
-
c.before :
|
17
|
-
|
18
|
-
|
19
|
-
super(time/100000)
|
20
|
-
end
|
21
|
-
end
|
21
|
+
c.before :each do
|
22
|
+
const = 'TomatoHarvest::Timer::SLEEP_LENGTH'
|
23
|
+
stub_const(const, 1/100000)
|
22
24
|
end
|
23
25
|
|
24
26
|
#
|
25
27
|
# Stub HTTP requests
|
26
28
|
#
|
27
29
|
c.before do
|
28
|
-
|
29
|
-
projects: [ {
|
30
|
-
name: 'Pomdoro',
|
31
|
-
id: 1,
|
32
|
-
tasks: [
|
33
|
-
{
|
34
|
-
name: 'Ruby Development',
|
35
|
-
id: 1
|
36
|
-
}
|
37
|
-
]
|
38
|
-
} ],
|
39
|
-
day_entries: []
|
40
|
-
}
|
41
|
-
|
42
|
-
stub_request(:get, /https:\/\/user:password@domain.harvestapp.com\/daily\/.*/).
|
43
|
-
with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json; charset=utf-8', 'User-Agent'=>'Harvestable/2.0.0'}).
|
44
|
-
to_return(:status => 200, :body => body.to_json, :headers => {})
|
45
|
-
|
46
|
-
stub_request(:post, "https://user:password@domain.harvestapp.com/daily/add").
|
47
|
-
with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json; charset=utf-8', 'User-Agent'=>'Harvestable/2.0.0'}).
|
48
|
-
to_return(:status => 200, :body => "", :headers => {})
|
30
|
+
stub_harvest
|
49
31
|
end
|
50
32
|
|
51
33
|
#
|
@@ -53,29 +35,31 @@ RSpec.configure do |c|
|
|
53
35
|
# Dont notify the terminal
|
54
36
|
#
|
55
37
|
c.before do
|
56
|
-
Daemons.
|
57
|
-
TerminalNotifier.
|
58
|
-
TomatoHarvest::Tmux.
|
38
|
+
allow(Daemons).to receive(:daemonize) { false }
|
39
|
+
allow(TerminalNotifier).to receive(:notify) { true }
|
40
|
+
allow_any_instance_of(TomatoHarvest::Tmux).to receive(:update) { true }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Stub Home dir
|
44
|
+
c.before(:each) do
|
45
|
+
stub_const('TomatoHarvest::Config::HOME_DIR', 'spec/')
|
59
46
|
end
|
60
47
|
|
61
48
|
#
|
62
|
-
# Cleanup .toma
|
49
|
+
# Cleanup .toma/
|
63
50
|
#
|
64
51
|
|
65
52
|
[
|
66
|
-
["TomatoHarvest::Config::
|
67
|
-
["TomatoHarvest::Config::
|
68
|
-
|
69
|
-
].each do |tuple|
|
70
|
-
path = tuple[1]
|
71
|
-
|
53
|
+
["TomatoHarvest::Config::GLOBAL_DIR", File.expand_path('spec/.toma/')],
|
54
|
+
["TomatoHarvest::Config::LOCAL_DIR", File.expand_path('.toma/')]
|
55
|
+
].each do |const, path|
|
72
56
|
c.before :each do
|
73
|
-
stub_const(
|
74
|
-
|
57
|
+
stub_const(const, path)
|
58
|
+
FileUtils.rm_rf(path) if File.directory?(path)
|
75
59
|
end
|
76
60
|
|
77
61
|
c.after :each do
|
78
|
-
|
62
|
+
FileUtils.rm_rf(path) if File.directory?(path)
|
79
63
|
end
|
80
64
|
end
|
81
65
|
end
|
@@ -22,6 +22,18 @@ describe TomatoHarvest::CLI do
|
|
22
22
|
|
23
23
|
end
|
24
24
|
|
25
|
+
describe 'remove' do
|
26
|
+
before do
|
27
|
+
TomatoHarvest::CLI.start ['add', 'foo']
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'removes task from the list' do
|
31
|
+
out = capture_io { TomatoHarvest::CLI.start ['remove', 1] }.join ''
|
32
|
+
expect(out).to match(/1 removed/)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
25
37
|
describe 'start' do
|
26
38
|
before do
|
27
39
|
TomatoHarvest::CLI.start ['add', 'foo']
|
@@ -48,10 +60,8 @@ describe TomatoHarvest::CLI do
|
|
48
60
|
password: 'password'
|
49
61
|
}
|
50
62
|
|
51
|
-
path = TomatoHarvest::Config::
|
52
|
-
|
53
|
-
YAML::dump(options, file)
|
54
|
-
end
|
63
|
+
path = TomatoHarvest::Config.config_path(TomatoHarvest::Config::GLOBAL_DIR)
|
64
|
+
create_yaml_file(path, options)
|
55
65
|
end
|
56
66
|
|
57
67
|
it 'starts the timer with specified length' do
|
@@ -63,4 +73,13 @@ describe TomatoHarvest::CLI do
|
|
63
73
|
|
64
74
|
end
|
65
75
|
|
76
|
+
describe 'stop' do
|
77
|
+
|
78
|
+
it 'warns when timer isnt running' do
|
79
|
+
out = capture_io { TomatoHarvest::CLI.start ['stop'] }.join ''
|
80
|
+
expect(out).to match(/Timer not running/)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
66
85
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'fileutils'
|
1
2
|
require 'helper'
|
2
3
|
|
3
4
|
describe TomatoHarvest::Config do
|
4
5
|
|
5
6
|
describe '.load' do
|
7
|
+
|
6
8
|
let(:global_options) do
|
7
9
|
{
|
8
10
|
project: 'Project',
|
@@ -11,9 +13,8 @@ describe TomatoHarvest::Config do
|
|
11
13
|
end
|
12
14
|
|
13
15
|
before do
|
14
|
-
|
15
|
-
|
16
|
-
end
|
16
|
+
path = TomatoHarvest::Config.config_path(TomatoHarvest::Config::GLOBAL_DIR)
|
17
|
+
create_yaml_file(path, global_options)
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'loads from the yaml config file' do
|
@@ -26,15 +27,34 @@ describe TomatoHarvest::Config do
|
|
26
27
|
options = {
|
27
28
|
type: 'JS Development',
|
28
29
|
}
|
29
|
-
local_config = File.join(Dir.pwd, '.tomaconfig')
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
path = TomatoHarvest::Config.config_path(TomatoHarvest::Config::LOCAL_DIR)
|
32
|
+
create_yaml_file(path, options)
|
33
|
+
|
34
|
+
expected = global_options.merge(options)
|
35
|
+
|
36
|
+
expect(TomatoHarvest::Config.load).to eql(expected)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when there is an old config file' do
|
42
|
+
|
43
|
+
let(:old_config) do
|
44
|
+
{
|
45
|
+
domain: 'fake.domain.name'
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'loads it' do
|
50
|
+
old_config_path = File.join(TomatoHarvest::Config::HOME_DIR, '.tomaconfig')
|
51
|
+
expanded_path = File.expand_path(old_config_path)
|
52
|
+
create_yaml_file(expanded_path, old_config)
|
34
53
|
|
35
|
-
|
54
|
+
expected = old_config.merge(global_options)
|
55
|
+
expect(TomatoHarvest::Config.load).to eql(expected)
|
36
56
|
|
37
|
-
|
57
|
+
File.delete(old_config_path)
|
38
58
|
end
|
39
59
|
|
40
60
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe TomatoHarvest::ListLoader do
|
4
|
+
|
5
|
+
describe '.from_file' do
|
6
|
+
|
7
|
+
let(:filename) { TomatoHarvest::ListLoader::FILENAME }
|
8
|
+
|
9
|
+
it 'returns a list' do
|
10
|
+
list = described_class.from_file
|
11
|
+
expect(list).to be_an_instance_of(TomatoHarvest::List)
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when there is an old list' do
|
15
|
+
|
16
|
+
let(:items) { ['item'] }
|
17
|
+
let(:path) { File.join(TomatoHarvest::Config::HOME_DIR, '.toma') }
|
18
|
+
|
19
|
+
before do
|
20
|
+
create_yaml_file(path, items)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'moves it to the global location' do
|
24
|
+
list = described_class.from_file
|
25
|
+
expect(list.count).to eql(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'persists the list' do
|
29
|
+
described_class.from_file
|
30
|
+
list = described_class.from_file
|
31
|
+
expect(list.count).to eql(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when there is a global list' do
|
37
|
+
|
38
|
+
let(:items) { ['item'] }
|
39
|
+
|
40
|
+
before do
|
41
|
+
path = File.join(TomatoHarvest::Config::GLOBAL_DIR, filename)
|
42
|
+
create_yaml_file(path, items)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns the global list' do
|
46
|
+
list = described_class.from_file
|
47
|
+
expect(list.items).to eql(items)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when there is a local list' do
|
51
|
+
let(:local_items) { ['local_item'] }
|
52
|
+
|
53
|
+
before do
|
54
|
+
path = File.join(TomatoHarvest::Config::LOCAL_DIR, filename)
|
55
|
+
create_yaml_file(path, local_items)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns the local list' do
|
59
|
+
list = described_class.from_file
|
60
|
+
expect(list.items).to eql(local_items)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -2,26 +2,35 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe TomatoHarvest::List do
|
4
4
|
|
5
|
+
let(:path) { TomatoHarvest::ListLoader.global_path }
|
6
|
+
let(:list) { TomatoHarvest::List.new(path) }
|
7
|
+
|
5
8
|
def add_task(name)
|
6
9
|
task = TomatoHarvest::Task.new(name)
|
7
|
-
|
10
|
+
list.add(task)
|
8
11
|
end
|
9
12
|
|
10
|
-
describe '
|
13
|
+
describe 'load!' do
|
11
14
|
|
12
|
-
it '
|
13
|
-
|
14
|
-
|
15
|
+
it 'doesnt load if file is empty' do
|
16
|
+
create_file(path, "")
|
17
|
+
list.load!
|
18
|
+
expect(list.items).to eql([])
|
15
19
|
end
|
16
20
|
|
17
21
|
end
|
18
22
|
|
19
|
-
describe '.
|
23
|
+
describe '.add' do
|
24
|
+
|
25
|
+
it 'adds to the list' do
|
26
|
+
add_task('foo')
|
27
|
+
expect(list.items.first).to be_an_instance_of(TomatoHarvest::Task)
|
28
|
+
end
|
20
29
|
|
21
30
|
it 'should have two items' do
|
22
31
|
add_task('foo')
|
23
32
|
add_task('bar')
|
24
|
-
expect(
|
33
|
+
expect(list.count).to eql(2)
|
25
34
|
end
|
26
35
|
|
27
36
|
end
|
@@ -31,8 +40,8 @@ describe TomatoHarvest::List do
|
|
31
40
|
it 'returns the task with the corresponding id' do
|
32
41
|
add_task('foo')
|
33
42
|
add_task('bar')
|
34
|
-
expect(
|
35
|
-
expect(
|
43
|
+
expect(list.find(1).name).to eql('foo')
|
44
|
+
expect(list.find(2).name).to eql('bar')
|
36
45
|
end
|
37
46
|
|
38
47
|
end
|
@@ -41,11 +50,20 @@ describe TomatoHarvest::List do
|
|
41
50
|
|
42
51
|
it 'adds the task to the items array' do
|
43
52
|
task = TomatoHarvest::Task.new('foo')
|
44
|
-
list = described_class.new
|
45
53
|
list.add(task)
|
46
54
|
expect(list.items.first.id).to eql(1)
|
47
55
|
end
|
48
56
|
|
49
57
|
end
|
50
58
|
|
59
|
+
describe '.remove' do
|
60
|
+
|
61
|
+
it 'removes the task from the item array' do
|
62
|
+
add_task('foo')
|
63
|
+
list.remove(1)
|
64
|
+
expect(list.count).to eql(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
51
69
|
end
|
@@ -6,59 +6,64 @@ describe TomatoHarvest::TimeEntry do
|
|
6
6
|
|
7
7
|
let(:entry) do
|
8
8
|
described_class.new.tap do |entry|
|
9
|
-
entry.
|
9
|
+
allow(entry).to receive(:project) { double }
|
10
|
+
allow(entry).to receive(:task) { double }
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
it "raises an error if project can't be found" do
|
14
|
-
entry.
|
15
|
+
allow(entry).to receive(:project) { nil }
|
15
16
|
expect{ entry.test }.to raise_error("Couldn't find project")
|
16
17
|
end
|
17
18
|
|
18
19
|
it "raises an error if task can't be found" do
|
19
|
-
entry.
|
20
|
+
allow(entry).to receive(:task) { nil }
|
20
21
|
expect{ entry.test }.to raise_error("Couldn't find task type")
|
21
22
|
end
|
22
23
|
|
23
24
|
end
|
24
25
|
|
25
26
|
describe '#log' do
|
27
|
+
let(:options) do
|
28
|
+
{
|
29
|
+
'domain' => 'domain',
|
30
|
+
'username' => 'user',
|
31
|
+
'password' => 'password',
|
32
|
+
'project' => 'Pomodoro',
|
33
|
+
'task' => 'Ruby Development',
|
34
|
+
'name' => 'Template Refactoring'
|
35
|
+
}
|
36
|
+
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
id: 1
|
48
|
-
}
|
49
|
-
]
|
50
|
-
} ],
|
51
|
-
|
52
|
-
day_entries: [ {
|
53
|
-
notes: 'Template Refactoring',
|
54
|
-
project_id: 1,
|
55
|
-
task_id: 1,
|
56
|
-
hours: 1
|
57
|
-
} ]
|
58
|
-
}
|
38
|
+
let(:entries) { [] }
|
39
|
+
|
40
|
+
before do
|
41
|
+
body = {
|
42
|
+
projects: [ {
|
43
|
+
name: 'Pomodoro',
|
44
|
+
id: 1,
|
45
|
+
tasks: [
|
46
|
+
{
|
47
|
+
name: 'Ruby Development',
|
48
|
+
id: 1
|
49
|
+
}
|
50
|
+
]
|
51
|
+
} ],
|
52
|
+
day_entries: entries
|
53
|
+
}
|
54
|
+
|
55
|
+
stub_request(:get, /https:\/\/user:password@domain.harvestapp.com\/daily\/.*/).
|
56
|
+
to_return(:status => 200, :body => body.to_json, :headers => {})
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
59
|
+
context 'task is already logged today' do
|
60
|
+
let(:entries) do
|
61
|
+
[ {
|
62
|
+
notes: 'Template Refactoring',
|
63
|
+
project_id: 1,
|
64
|
+
task_id: 1,
|
65
|
+
hours: 1
|
66
|
+
} ]
|
62
67
|
end
|
63
68
|
|
64
69
|
it 'updates exisiting entry' do
|
@@ -75,7 +80,21 @@ describe TomatoHarvest::TimeEntry do
|
|
75
80
|
entry = TomatoHarvest::TimeEntry.new(options)
|
76
81
|
entry.log(60 * 30)
|
77
82
|
|
78
|
-
stub.
|
83
|
+
expect(stub).to have_been_requested
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'seconds are rounded to 0.00 hours' do
|
89
|
+
|
90
|
+
it 'should not log' do
|
91
|
+
update_url = "https://user:password@domain.harvestapp.com/daily/add"
|
92
|
+
stub = stub_request(:post, update_url)
|
93
|
+
|
94
|
+
entry = TomatoHarvest::TimeEntry.new(options)
|
95
|
+
entry.log(0)
|
96
|
+
|
97
|
+
expect(stub).not_to have_been_requested
|
79
98
|
end
|
80
99
|
|
81
100
|
end
|
@@ -6,10 +6,14 @@ describe TomatoHarvest::Timer do
|
|
6
6
|
|
7
7
|
let(:task) { TomatoHarvest::Task.new('foo') }
|
8
8
|
|
9
|
+
let(:list) do
|
10
|
+
path = TomatoHarvest::ListLoader.global_path
|
11
|
+
TomatoHarvest::List.new(path)
|
12
|
+
end
|
13
|
+
|
9
14
|
before do
|
10
|
-
list = TomatoHarvest::List.new
|
11
15
|
list.add(task)
|
12
|
-
list.save
|
16
|
+
list.save!
|
13
17
|
end
|
14
18
|
|
15
19
|
def stub_notifier(minutes)
|
@@ -23,23 +27,23 @@ describe TomatoHarvest::Timer do
|
|
23
27
|
end
|
24
28
|
|
25
29
|
it 'can run for a custom length' do
|
26
|
-
TomatoHarvest::Timer.start(task.id, minutes: 15)
|
30
|
+
TomatoHarvest::Timer.start(list, task.id, minutes: 15)
|
27
31
|
|
28
|
-
reloaded_task =
|
32
|
+
reloaded_task = list.find(task.id)
|
29
33
|
expect(reloaded_task.logged_minutes).to eql(15.0)
|
30
34
|
end
|
31
35
|
|
32
36
|
it 'can be run twice' do
|
33
|
-
TomatoHarvest::Timer.start(task.id, minutes: 20)
|
34
|
-
TomatoHarvest::Timer.start(task.id, minutes: 20)
|
35
|
-
reloaded_task =
|
37
|
+
TomatoHarvest::Timer.start(list, task.id, minutes: 20)
|
38
|
+
TomatoHarvest::Timer.start(list, task.id, minutes: 20)
|
39
|
+
reloaded_task = list.find(task.id)
|
36
40
|
expect(reloaded_task.logged_minutes).to eql(40.0)
|
37
41
|
end
|
38
42
|
|
39
43
|
it 'logs a time entry if passed in' do
|
40
44
|
entry = double
|
41
|
-
entry.
|
42
|
-
TomatoHarvest::Timer.start(task.id, time_entry: entry, minutes: 25)
|
45
|
+
expect(entry).to receive(:log)
|
46
|
+
TomatoHarvest::Timer.start(list, task.id, time_entry: entry, minutes: 25)
|
43
47
|
end
|
44
48
|
|
45
49
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FileHelpers
|
2
|
+
|
3
|
+
# Creates a YAML config file at the specified path.
|
4
|
+
def create_yaml_file(path, options)
|
5
|
+
create_file(path, YAML::dump(options))
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_file(path, contents)
|
9
|
+
dir = File.dirname(path)
|
10
|
+
|
11
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
12
|
+
|
13
|
+
File.open(path, 'w') do |file|
|
14
|
+
file.write(contents)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module HarvestHelpers
|
2
|
+
def stub_harvest
|
3
|
+
body = {
|
4
|
+
projects: [ {
|
5
|
+
name: 'Pomdoro',
|
6
|
+
id: 1,
|
7
|
+
tasks: [
|
8
|
+
{
|
9
|
+
name: 'Ruby Development',
|
10
|
+
id: 1
|
11
|
+
}
|
12
|
+
]
|
13
|
+
} ],
|
14
|
+
day_entries: []
|
15
|
+
}
|
16
|
+
|
17
|
+
stub_request(:get, /https:\/\/user:password@domain.harvestapp.com\/daily\/.*/).
|
18
|
+
with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json; charset=utf-8', 'User-Agent'=>'Harvestable/2.0.0'}).
|
19
|
+
to_return(:status => 200, :body => body.to_json, :headers => {})
|
20
|
+
|
21
|
+
stub_request(:post, "https://user:password@domain.harvestapp.com/daily/add").
|
22
|
+
with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json; charset=utf-8', 'User-Agent'=>'Harvestable/2.0.0'}).
|
23
|
+
to_return(:status => 200, :body => "", :headers => {})
|
24
|
+
end
|
25
|
+
end
|
data/tomatoharvest.gemspec
CHANGED
@@ -19,12 +19,12 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
-
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rspec"
|
24
|
-
spec.add_development_dependency "webmock"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
spec.add_development_dependency "webmock", "~> 1.18"
|
25
25
|
|
26
|
-
spec.add_dependency
|
27
|
-
spec.add_dependency
|
28
|
-
spec.add_dependency
|
29
|
-
spec.add_dependency
|
26
|
+
spec.add_dependency 'thor', '~> 0.19'
|
27
|
+
spec.add_dependency 'harvested', '~> 2.0'
|
28
|
+
spec.add_dependency 'daemons', '~> 1.1'
|
29
|
+
spec.add_dependency 'terminal-notifier', '~> 1.4' if TomatoHarvest::OS.mac?
|
30
30
|
end
|
metadata
CHANGED
@@ -1,125 +1,125 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tomatoharvest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Reh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.6'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '10.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '10.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
47
|
+
version: '3.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
54
|
+
version: '3.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: webmock
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.18'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.18'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: thor
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0.19'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.19'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: harvested
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
89
|
+
version: '2.0'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
96
|
+
version: '2.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: daemons
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '1.1'
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
110
|
+
version: '1.1'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: terminal-notifier
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - ~>
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '1.4'
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - ~>
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.4'
|
125
125
|
description: Command line pomodoro timer that logs to Harvest.
|
@@ -130,7 +130,7 @@ executables:
|
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
|
-
- .gitignore
|
133
|
+
- ".gitignore"
|
134
134
|
- Gemfile
|
135
135
|
- LICENSE.txt
|
136
136
|
- README.md
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/tomatoharvest/cli.rb
|
142
142
|
- lib/tomatoharvest/config.rb
|
143
143
|
- lib/tomatoharvest/list.rb
|
144
|
+
- lib/tomatoharvest/list_loader.rb
|
144
145
|
- lib/tomatoharvest/notifier.rb
|
145
146
|
- lib/tomatoharvest/notifier/notification_center.rb
|
146
147
|
- lib/tomatoharvest/os.rb
|
@@ -153,9 +154,12 @@ files:
|
|
153
154
|
- spec/helper.rb
|
154
155
|
- spec/lib/tomatoharvest/cli_spec.rb
|
155
156
|
- spec/lib/tomatoharvest/config_spec.rb
|
157
|
+
- spec/lib/tomatoharvest/list_loader_spec.rb
|
156
158
|
- spec/lib/tomatoharvest/list_spec.rb
|
157
159
|
- spec/lib/tomatoharvest/time_entry_spec.rb
|
158
160
|
- spec/lib/tomatoharvest/timer_spec.rb
|
161
|
+
- spec/support/file_helpers.rb
|
162
|
+
- spec/support/harvest_helpers.rb
|
159
163
|
- tomatoharvest.gemspec
|
160
164
|
homepage: http://github.com/samuelreh/tomatoharvest/
|
161
165
|
licenses:
|
@@ -167,17 +171,17 @@ require_paths:
|
|
167
171
|
- lib
|
168
172
|
required_ruby_version: !ruby/object:Gem::Requirement
|
169
173
|
requirements:
|
170
|
-
- -
|
174
|
+
- - ">="
|
171
175
|
- !ruby/object:Gem::Version
|
172
176
|
version: '0'
|
173
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
178
|
requirements:
|
175
|
-
- -
|
179
|
+
- - ">="
|
176
180
|
- !ruby/object:Gem::Version
|
177
181
|
version: '0'
|
178
182
|
requirements: []
|
179
183
|
rubyforge_project:
|
180
|
-
rubygems_version: 2.
|
184
|
+
rubygems_version: 2.2.2
|
181
185
|
signing_key:
|
182
186
|
specification_version: 4
|
183
187
|
summary: Log your pomodoros to Harvest
|
@@ -185,7 +189,10 @@ test_files:
|
|
185
189
|
- spec/helper.rb
|
186
190
|
- spec/lib/tomatoharvest/cli_spec.rb
|
187
191
|
- spec/lib/tomatoharvest/config_spec.rb
|
192
|
+
- spec/lib/tomatoharvest/list_loader_spec.rb
|
188
193
|
- spec/lib/tomatoharvest/list_spec.rb
|
189
194
|
- spec/lib/tomatoharvest/time_entry_spec.rb
|
190
195
|
- spec/lib/tomatoharvest/timer_spec.rb
|
196
|
+
- spec/support/file_helpers.rb
|
197
|
+
- spec/support/harvest_helpers.rb
|
191
198
|
has_rdoc:
|