detom 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +38 -0
- data/README.markdown +7 -0
- data/README.rdoc +4 -0
- data/Rakefile +14 -0
- data/bin/detom +100 -0
- data/detom.gemspec +22 -0
- data/detom.rdoc +5 -0
- data/lib/detom.rb +8 -0
- data/lib/detom/commands/clients.rb +19 -0
- data/lib/detom/commands/record.rb +35 -0
- data/lib/detom/version.rb +3 -0
- data/lib/detom/yaml_file_store.rb +66 -0
- data/spec/detom/commands/clients_spec.rb +67 -0
- data/spec/detom/commands/record_spec.rb +166 -0
- data/spec/spec_helper.rb +100 -0
- data/tmp/record_test.json +10 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27690f426f4574891be0599cf5316fd3f5c52af44f439e2a3a4081af034ae18a
|
4
|
+
data.tar.gz: 16f3a7bc52f572e97d77676b0aa0c4843308fbcda9aeedf923dd90124ab8d75a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e760682f0d9b8ff4ec60c5d5aadf1ac9d4d9b5cc1f41942aa4411438f6206f0bd1d20a1b274850cc60c3b1220cb0ed95d08626c4ed39d9e8d9316e074356913
|
7
|
+
data.tar.gz: 54641b2f23fd77bf4647d7fe7251e2f9cd8b906a618e8548c36756539cf26d6f0fa7527a4a98463c863f2617685e396d8a1983d3f1f2527c9ef5663c98c98613
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
detom (0.0.1)
|
5
|
+
gli (= 2.19.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.4.4)
|
11
|
+
gli (2.19.0)
|
12
|
+
rake (13.0.1)
|
13
|
+
rdoc (6.2.1)
|
14
|
+
rspec (3.9.0)
|
15
|
+
rspec-core (~> 3.9.0)
|
16
|
+
rspec-expectations (~> 3.9.0)
|
17
|
+
rspec-mocks (~> 3.9.0)
|
18
|
+
rspec-core (3.9.2)
|
19
|
+
rspec-support (~> 3.9.3)
|
20
|
+
rspec-expectations (3.9.2)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.9.0)
|
23
|
+
rspec-mocks (3.9.1)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.9.0)
|
26
|
+
rspec-support (3.9.3)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
detom!
|
33
|
+
rake
|
34
|
+
rdoc
|
35
|
+
rspec
|
36
|
+
|
37
|
+
BUNDLED WITH
|
38
|
+
2.1.4
|
data/README.markdown
ADDED
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
Rake::RDocTask.new do |rd|
|
6
|
+
rd.main = "README.rdoc"
|
7
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
8
|
+
rd.title = 'Your application title'
|
9
|
+
end
|
10
|
+
|
11
|
+
spec = eval(File.read('detom.gemspec'))
|
12
|
+
|
13
|
+
Gem::PackageTask.new(spec) do |pkg|
|
14
|
+
end
|
data/bin/detom
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gli'
|
3
|
+
begin # XXX: Remove this begin/rescue before distributing your app
|
4
|
+
require 'detom'
|
5
|
+
rescue LoadError => e
|
6
|
+
STDERR.puts e.message
|
7
|
+
STDERR.puts e.backtrace
|
8
|
+
STDERR.puts "In development, you need to use `bundle exec bin/detom` to run your app"
|
9
|
+
STDERR.puts "At install-time, RubyGems will make sure lib, etc. are in the load path"
|
10
|
+
STDERR.puts "Feel free to remove this message from bin/detom now"
|
11
|
+
exit 64
|
12
|
+
end
|
13
|
+
|
14
|
+
class App
|
15
|
+
extend GLI::App
|
16
|
+
|
17
|
+
program_desc "Minimalistic project and client time tracking on the command line"
|
18
|
+
|
19
|
+
version Detom::VERSION
|
20
|
+
|
21
|
+
subcommand_option_handling :normal
|
22
|
+
arguments :strict
|
23
|
+
|
24
|
+
desc "List all clients currently being tracked"
|
25
|
+
command :clients do |c|
|
26
|
+
c.action do |global_options,options,args|
|
27
|
+
begin
|
28
|
+
Commands::Clients.new.call
|
29
|
+
rescue StandardError => e
|
30
|
+
STDERR.puts e.message
|
31
|
+
STDERR.puts e.backtrace
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Record some time spent on a client. <client> and <time> are required. <client> is the name of the client and <time> is an amount of time in minutes, e.g. 39m, or hours, e.g. 3h"
|
37
|
+
arg_name "<client> <time>"
|
38
|
+
command :record do |c|
|
39
|
+
c.flag [:d, :"day-month"]
|
40
|
+
c.action do |global_options, options, args|
|
41
|
+
help_now! "You must provide record with <client> and <time> as arguments" if args.length < 2
|
42
|
+
|
43
|
+
begin
|
44
|
+
store = YamlFileStore.new
|
45
|
+
Commands::Record.new(store).call args[0], args[1], options[:d]
|
46
|
+
rescue StandardError => e
|
47
|
+
STDERR.puts e.message
|
48
|
+
STDERR.puts e.backtrace
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Archive tracking for a client or a project. Literally copies the client file into ~/.detom/archive/client-<today>.json"
|
54
|
+
arg_name "client"
|
55
|
+
command :archive do |c|
|
56
|
+
c.action do |global_options, options, args|
|
57
|
+
exit_now! "archive has not yet been implemented"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Use to mark the start / stop time on a client. This can then be used to record an amount of time against that client."
|
62
|
+
arg_name "client"
|
63
|
+
switch [:e, :end],
|
64
|
+
desc: "Registers the mark as the end of a period."
|
65
|
+
|
66
|
+
switch [:l, :list],
|
67
|
+
desc: "Displays all stored marks for the client."
|
68
|
+
|
69
|
+
switch [:C, :clear],
|
70
|
+
desc: "Clears all stored marks for the client."
|
71
|
+
|
72
|
+
command :mark do |c|
|
73
|
+
c.action do |global_options, options, args|
|
74
|
+
exit_now! "mark has not yet been implemented"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
pre do |global,command,options,args|
|
79
|
+
# Pre logic here
|
80
|
+
# Return true to proceed; false to abort and not call the
|
81
|
+
# chosen command
|
82
|
+
# Use skips_pre before a command to skip this block
|
83
|
+
# on that command only
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
post do |global,command,options,args|
|
88
|
+
# Post logic here
|
89
|
+
# Use skips_post before a command to skip this
|
90
|
+
# block on that command only
|
91
|
+
end
|
92
|
+
|
93
|
+
on_error do |exception|
|
94
|
+
# Error logic here
|
95
|
+
# return false to skip default error handling
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
exit App.run(ARGV)
|
data/detom.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Ensure we require the local version and not one we might have installed already
|
2
|
+
require File.join([File.dirname(__FILE__),'lib','detom','version.rb'])
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'detom'
|
5
|
+
s.version = Detom::VERSION
|
6
|
+
s.author = 'NJ Pearman'
|
7
|
+
s.email = 'n.pearman@gmail.com'
|
8
|
+
s.homepage = 'https://github.com/njpearman/detom'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'A minimal command line time tracker for individuals'
|
11
|
+
s.files = `git ls-files`.split("
|
12
|
+
")
|
13
|
+
s.require_paths << 'lib'
|
14
|
+
s.extra_rdoc_files = ['README.rdoc','detom.rdoc']
|
15
|
+
s.rdoc_options << '--title' << 'detom' << '--main' << 'README.rdoc' << '-ri'
|
16
|
+
s.bindir = 'bin'
|
17
|
+
s.executables << 'detom'
|
18
|
+
s.add_development_dependency('rake')
|
19
|
+
s.add_development_dependency('rdoc')
|
20
|
+
s.add_development_dependency('rspec')
|
21
|
+
s.add_runtime_dependency('gli','2.19.0')
|
22
|
+
end
|
data/detom.rdoc
ADDED
data/lib/detom.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Commands
|
2
|
+
class Clients
|
3
|
+
def initialize(store=YamlFileStore.new)
|
4
|
+
@store = store
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
@store.each do |client, tracked_time|
|
9
|
+
if tracked_time.nil?
|
10
|
+
puts client
|
11
|
+
next
|
12
|
+
end
|
13
|
+
|
14
|
+
total_time = tracked_time.map {|key, value| value.reduce(&:+) }.reduce &:+
|
15
|
+
puts "#{client} #{total_time}m"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Commands
|
2
|
+
class Record
|
3
|
+
def initialize(store)
|
4
|
+
@store = store
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(client_name, time_to_log, day_month = nil)
|
8
|
+
if day_month
|
9
|
+
raise "Day/month is an unrecognised format. Use `%d-%m` format" unless day_month =~ /\d\d-\d\d/
|
10
|
+
|
11
|
+
day = day_month.split("-").first
|
12
|
+
month = day_month.split("-").last
|
13
|
+
|
14
|
+
# parse
|
15
|
+
day_month = [Time.now.year.to_s, month, day].join "-"
|
16
|
+
else
|
17
|
+
day_month = Time.now.strftime("%Y-%m-%d")
|
18
|
+
end
|
19
|
+
|
20
|
+
@store[client_name] = {} if @store[client_name].nil?
|
21
|
+
|
22
|
+
client = @store[client_name]
|
23
|
+
|
24
|
+
if client[day_month]
|
25
|
+
client[day_month] << time_to_log.to_i
|
26
|
+
else
|
27
|
+
client[day_month] = [ time_to_log.to_i ]
|
28
|
+
end
|
29
|
+
|
30
|
+
@store.save!
|
31
|
+
puts "Logged #{time_to_log} for #{client_name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
class YamlFileStore
|
4
|
+
DEFAULT_APP_DIRECTORY = File.join Dir.home, ".detom"
|
5
|
+
|
6
|
+
def initialize(filepath=DEFAULT_APP_DIRECTORY)
|
7
|
+
@store = {}
|
8
|
+
@filepath = filepath
|
9
|
+
prepare!
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
enumerator = @store.sort.enum_for
|
14
|
+
|
15
|
+
loop do
|
16
|
+
case current = enumerator.next
|
17
|
+
when current.kind_of?(Array) then yield **current
|
18
|
+
else yield current
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
@filepath
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
@store[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(key, value)
|
34
|
+
@store[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def save!
|
38
|
+
Dir.chdir(@filepath) do
|
39
|
+
@store.each do |key, value|
|
40
|
+
File.open(File.join(@filepath, key), "w") {|f| YAML.dump(@store[key], f) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def prepare!
|
46
|
+
if root_directory_exists?
|
47
|
+
# load files
|
48
|
+
Dir.chdir(@filepath) do
|
49
|
+
Dir["*"].each do |filename|
|
50
|
+
@store[filename] = YAML.load File.read(File.join(@filepath, filename))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
create_root_directory
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def root_directory_exists?
|
60
|
+
Dir.exist? @filepath
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_root_directory
|
64
|
+
Dir.mkdir @filepath
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "detom/commands/clients"
|
2
|
+
require "detom/yaml_file_store"
|
3
|
+
|
4
|
+
RSpec.describe Commands::Clients do
|
5
|
+
describe "#call" do
|
6
|
+
context "for app directory setup" do
|
7
|
+
subject { described_class.new.call }
|
8
|
+
|
9
|
+
let(:dir) { class_double("Dir").as_stubbed_const }
|
10
|
+
let!(:expected_directory) { File.join(Dir.home, ".detom") }
|
11
|
+
|
12
|
+
def stub_app_directory
|
13
|
+
dir = class_double("Dir").as_stubbed_const
|
14
|
+
|
15
|
+
expect(dir).to receive(:exist?).with(expected_directory).and_return true
|
16
|
+
expect(dir).to_not receive(:mkdir).with expected_directory
|
17
|
+
expect(dir).to receive(:chdir).with(YamlFileStore::DEFAULT_APP_DIRECTORY)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "creates the app directory in ~/.detom if it does not already exist" do
|
21
|
+
expect(dir).to receive(:exist?).with(expected_directory).and_return false
|
22
|
+
expect(dir).to receive(:mkdir).times.with expected_directory
|
23
|
+
|
24
|
+
expect { subject }.to output("").to_stdout
|
25
|
+
end
|
26
|
+
|
27
|
+
it "does not create app directory if ~/.detom/ already exists" do
|
28
|
+
stub_app_directory
|
29
|
+
expect { subject }.to output("").to_stdout
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with a stubbed DEFAULT_APP_DIRECTORY" do
|
34
|
+
class StubStore < YamlFileStore
|
35
|
+
def initialize(clients)
|
36
|
+
@store = clients
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
subject { -> { described_class.new(StubStore.new(clients)).call } }
|
41
|
+
|
42
|
+
context "with one empty client file" do
|
43
|
+
let(:clients) { %w(foo_client) }
|
44
|
+
|
45
|
+
it { is_expected.to output("foo_client\n").to_stdout }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with one client file tracking time" do
|
49
|
+
let(:clients) { {"foo_client" => { "2020-07-29" => [30, 63] } } }
|
50
|
+
|
51
|
+
it { is_expected.to output("foo_client 93m\n").to_stdout }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with more than one empty client file" do
|
55
|
+
let(:clients) { %w(foo_client rii_client suu_client) }
|
56
|
+
|
57
|
+
it { is_expected.to output("foo_client\nrii_client\nsuu_client\n").to_stdout }
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with unordered empty client file" do
|
61
|
+
let(:clients) { %w(rii_client foo_client baa_client suu_client) }
|
62
|
+
|
63
|
+
it { is_expected.to output("baa_client\nfoo_client\nrii_client\nsuu_client\n").to_stdout }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require "detom/commands/record"
|
2
|
+
require "detom/yaml_file_store"
|
3
|
+
|
4
|
+
describe Commands::Record do
|
5
|
+
subject { described_class.new(store) }
|
6
|
+
let(:store) { YamlFileStore.new(test_filepath) }
|
7
|
+
|
8
|
+
describe "#call" do
|
9
|
+
let(:test_filepath) { File.join(File.dirname(__FILE__), "..", "..", "..", "tmp", "record_test") }
|
10
|
+
let(:today) { Time.now.strftime("%Y-%m-%d") }
|
11
|
+
|
12
|
+
before { Dir.mkdir test_filepath unless Dir.exists? test_filepath }
|
13
|
+
after { FileUtils.rm_rf test_filepath if Dir.exists? test_filepath }
|
14
|
+
|
15
|
+
context "when recording time today" do
|
16
|
+
context "once for one client" do
|
17
|
+
it "stores the time spent on the client" do
|
18
|
+
subject.call("foo_client", "6m")
|
19
|
+
expect(store["foo_client"]).to eq({ today => [6] })
|
20
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
21
|
+
---
|
22
|
+
'#{today}':
|
23
|
+
- 6
|
24
|
+
JSON
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "twice for one client" do
|
29
|
+
it "stores the time spent on the client" do
|
30
|
+
subject.call("foo_client", "6m")
|
31
|
+
subject.call("foo_client", "39m")
|
32
|
+
expect(store["foo_client"]).to eq({ today => [6, 39] })
|
33
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
34
|
+
---
|
35
|
+
'#{today}':
|
36
|
+
- 6
|
37
|
+
- 39
|
38
|
+
JSON
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "three times for one client" do
|
43
|
+
it "stores the time spent on the client" do
|
44
|
+
subject.call("foo_client", "6m")
|
45
|
+
subject.call("foo_client", "39m")
|
46
|
+
expect { subject.call("foo_client", "92m") }.to output("Logged 92m for foo_client\n").to_stdout
|
47
|
+
expect(store["foo_client"]).to eq({ today => [6, 39, 92] })
|
48
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
49
|
+
---
|
50
|
+
'#{today}':
|
51
|
+
- 6
|
52
|
+
- 39
|
53
|
+
- 92
|
54
|
+
JSON
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "once each for two clients" do
|
59
|
+
it "stores the time spent on the clients" do
|
60
|
+
record_command = described_class.new(store)
|
61
|
+
record_command.call("foo_client", "6m")
|
62
|
+
record_command.call("raa_client", "39m")
|
63
|
+
expect(store["foo_client"]).to eq({ today => [6] })
|
64
|
+
expect(store["raa_client"]).to eq({ today => [39] })
|
65
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
66
|
+
---
|
67
|
+
'#{today}':
|
68
|
+
- 6
|
69
|
+
JSON
|
70
|
+
expect(File.read(File.join(test_filepath, "raa_client"))).to eq <<-JSON
|
71
|
+
---
|
72
|
+
'#{today}':
|
73
|
+
- 39
|
74
|
+
JSON
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "once each for three clients" do
|
79
|
+
it "stores the time spent on the clients" do
|
80
|
+
subject.call("foo_client", "6m")
|
81
|
+
subject.call("raa_client", "39m")
|
82
|
+
subject.call("gii_client", "72m")
|
83
|
+
expect(store["foo_client"]).to eq({ today => [6] })
|
84
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
85
|
+
---
|
86
|
+
'#{today}':
|
87
|
+
- 6
|
88
|
+
JSON
|
89
|
+
expect(store["raa_client"]).to eq({ today => [39] })
|
90
|
+
expect(File.read(File.join(test_filepath, "raa_client"))).to eq <<-JSON
|
91
|
+
---
|
92
|
+
'#{today}':
|
93
|
+
- 39
|
94
|
+
JSON
|
95
|
+
expect(store["gii_client"]).to eq({ today => [72] })
|
96
|
+
expect(File.read(File.join(test_filepath, "gii_client"))).to eq <<-JSON
|
97
|
+
---
|
98
|
+
'#{today}':
|
99
|
+
- 72
|
100
|
+
JSON
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when recording time for a day in the past" do
|
106
|
+
let(:five_days_ago) { Time.now - (5 * 24 * 60 * 60) }
|
107
|
+
let(:expected_formatted_date) { five_days_ago.strftime("%Y-%m-%d") }
|
108
|
+
|
109
|
+
context "once for one client" do
|
110
|
+
it "stores the time spent on the client" do
|
111
|
+
subject.call("foo_client", "6m", five_days_ago.strftime("%d-%m"))
|
112
|
+
expect(store["foo_client"]).to eq({ expected_formatted_date => [6] })
|
113
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
114
|
+
---
|
115
|
+
'#{expected_formatted_date}':
|
116
|
+
- 6
|
117
|
+
JSON
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when recording time for different days" do
|
123
|
+
let(:five_days_ago) { Time.now - (5 * 24 * 60 * 60) }
|
124
|
+
let(:ten_days_ago) { Time.now - (10 * 24 * 60 * 60) }
|
125
|
+
let(:expected_formatted_date_1) { five_days_ago.strftime("%Y-%m-%d") }
|
126
|
+
let(:expected_formatted_date_2) { ten_days_ago.strftime("%Y-%m-%d") }
|
127
|
+
|
128
|
+
context "once for one client" do
|
129
|
+
it "stores the time spent on the client" do
|
130
|
+
subject.call("foo_client", "6m", five_days_ago.strftime("%d-%m"))
|
131
|
+
subject.call("foo_client", "45m", ten_days_ago.strftime("%d-%m"))
|
132
|
+
expect(store["foo_client"]).to eq({ expected_formatted_date_1 => [6], expected_formatted_date_2 => [45] })
|
133
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
134
|
+
---
|
135
|
+
'#{expected_formatted_date_1}':
|
136
|
+
- 6
|
137
|
+
'#{expected_formatted_date_2}':
|
138
|
+
- 45
|
139
|
+
JSON
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "when recording additional time for one client" do
|
145
|
+
let(:five_days_ago) { Time.now - (5 * 24 * 60 * 60) }
|
146
|
+
let(:ten_days_ago) { Time.now - (10 * 24 * 60 * 60) }
|
147
|
+
let(:expected_formatted_date_1) { five_days_ago.strftime("%Y-%m-%d") }
|
148
|
+
let(:expected_formatted_date_2) { ten_days_ago.strftime("%Y-%m-%d") }
|
149
|
+
|
150
|
+
context "once for one client" do
|
151
|
+
it "stores the time spent on the client" do
|
152
|
+
File.open(File.join(test_filepath, "foo_client"), "w") {|f| f.write YAML.dump(expected_formatted_date_1 => [6]) }
|
153
|
+
subject.call("foo_client", "45m", ten_days_ago.strftime("%d-%m"))
|
154
|
+
expect(store["foo_client"]).to eq({ expected_formatted_date_1 => [6], expected_formatted_date_2 => [45] })
|
155
|
+
expect(File.read(File.join(test_filepath, "foo_client"))).to eq <<-JSON
|
156
|
+
---
|
157
|
+
'#{expected_formatted_date_1}':
|
158
|
+
- 6
|
159
|
+
'#{expected_formatted_date_2}':
|
160
|
+
- 45
|
161
|
+
JSON
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# rspec-expectations config goes here. You can use an alternate
|
18
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
19
|
+
# assertions if you prefer.
|
20
|
+
config.expect_with :rspec do |expectations|
|
21
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
22
|
+
# and `failure_message` of custom matchers include text for helper methods
|
23
|
+
# defined using `chain`, e.g.:
|
24
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
25
|
+
# # => "be bigger than 2 and smaller than 4"
|
26
|
+
# ...rather than:
|
27
|
+
# # => "be bigger than 2"
|
28
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
32
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
41
|
+
# have no way to turn it off -- the option exists only for backwards
|
42
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
43
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
44
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
45
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
46
|
+
|
47
|
+
# The settings below are suggested to provide a good initial experience
|
48
|
+
# with RSpec, but feel free to customize to your heart's content.
|
49
|
+
=begin
|
50
|
+
# This allows you to limit a spec run to individual examples or groups
|
51
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
52
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
53
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
54
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
55
|
+
config.filter_run_when_matching :focus
|
56
|
+
|
57
|
+
# Allows RSpec to persist some state between runs in order to support
|
58
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
59
|
+
# you configure your source control system to ignore this file.
|
60
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
61
|
+
|
62
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
63
|
+
# recommended. For more details, see:
|
64
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
65
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
66
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
67
|
+
config.disable_monkey_patching!
|
68
|
+
|
69
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
70
|
+
# be too noisy due to issues in dependencies.
|
71
|
+
config.warnings = true
|
72
|
+
|
73
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
74
|
+
# file, and it's useful to allow more verbose output when running an
|
75
|
+
# individual spec file.
|
76
|
+
if config.files_to_run.one?
|
77
|
+
# Use the documentation formatter for detailed output,
|
78
|
+
# unless a formatter has already been configured
|
79
|
+
# (e.g. via a command-line flag).
|
80
|
+
config.default_formatter = "doc"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Print the 10 slowest examples and example groups at the
|
84
|
+
# end of the spec run, to help surface which specs are running
|
85
|
+
# particularly slow.
|
86
|
+
config.profile_examples = 10
|
87
|
+
|
88
|
+
# Run specs in random order to surface order dependencies. If you find an
|
89
|
+
# order dependency and want to debug it, you can fix the order by providing
|
90
|
+
# the seed, which is printed after each run.
|
91
|
+
# --seed 1234
|
92
|
+
config.order = :random
|
93
|
+
|
94
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
95
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
96
|
+
# test failures related to randomization by passing the same `--seed` value
|
97
|
+
# as the one that triggered the failure.
|
98
|
+
Kernel.srand config.seed
|
99
|
+
=end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: detom
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- NJ Pearman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: gli
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.19.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.19.0
|
69
|
+
description:
|
70
|
+
email: n.pearman@gmail.com
|
71
|
+
executables:
|
72
|
+
- detom
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files:
|
75
|
+
- README.rdoc
|
76
|
+
- detom.rdoc
|
77
|
+
files:
|
78
|
+
- ".rspec"
|
79
|
+
- Gemfile
|
80
|
+
- Gemfile.lock
|
81
|
+
- README.markdown
|
82
|
+
- README.rdoc
|
83
|
+
- Rakefile
|
84
|
+
- bin/detom
|
85
|
+
- detom.gemspec
|
86
|
+
- detom.rdoc
|
87
|
+
- lib/detom.rb
|
88
|
+
- lib/detom/commands/clients.rb
|
89
|
+
- lib/detom/commands/record.rb
|
90
|
+
- lib/detom/version.rb
|
91
|
+
- lib/detom/yaml_file_store.rb
|
92
|
+
- spec/detom/commands/clients_spec.rb
|
93
|
+
- spec/detom/commands/record_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
- tmp/record_test.json
|
96
|
+
homepage: https://github.com/njpearman/detom
|
97
|
+
licenses: []
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options:
|
101
|
+
- "--title"
|
102
|
+
- detom
|
103
|
+
- "--main"
|
104
|
+
- README.rdoc
|
105
|
+
- "-ri"
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubygems_version: 3.0.3
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: A minimal command line time tracker for individuals
|
124
|
+
test_files: []
|