timeless 0.1.0 → 0.4.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/Gemfile +3 -1
- data/LICENSE.txt +17 -18
- data/README.md +152 -0
- data/Rakefile +1 -1
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/elisp/forms.el +22 -0
- data/exe/timeless +5 -0
- data/lib/timeless.rb +6 -5
- data/lib/timeless/cli/command_semantics.rb +352 -0
- data/lib/timeless/cli/command_syntax.rb +411 -0
- data/lib/timeless/pomodoro.rb +5 -8
- data/lib/timeless/storage.rb +27 -3
- data/lib/timeless/version.rb +1 -1
- data/timeless.gemspec +13 -7
- metadata +52 -26
- data/README.textile +0 -99
- data/bin/timeless +0 -386
data/lib/timeless/version.rb
CHANGED
data/timeless.gemspec
CHANGED
@@ -8,19 +8,25 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Timeless::VERSION
|
9
9
|
spec.authors = ["Adolfo Villafiorita"]
|
10
10
|
spec.email = ["adolfo.villafiorita@me.com"]
|
11
|
-
|
12
|
-
spec.
|
11
|
+
|
12
|
+
spec.summary = %q{Timeless is a simple but effective command-line time tracker}
|
13
|
+
spec.description = %q{Timeless comes with a pomodoro timer, with a standard stopwatch (start, top), semantic tags in notes (projects, clients, activites), and reporting and exporting command. Data is stored in csv and it can be easily imported into a spreadsheet.}
|
14
|
+
|
13
15
|
spec.homepage = "http://github.com/avillafiorita/timeless"
|
14
16
|
spec.license = "MIT"
|
15
17
|
|
16
18
|
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.
|
18
|
-
spec.
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
21
|
spec.require_paths = ["lib"]
|
20
22
|
|
21
|
-
spec.
|
22
|
-
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "minitest"
|
23
28
|
|
24
|
-
spec.add_runtime_dependency 'slop', '~>
|
29
|
+
spec.add_runtime_dependency 'slop', '~> 4.5.0'
|
25
30
|
spec.add_runtime_dependency 'chronic'
|
31
|
+
spec.add_runtime_dependency 'notiffany'
|
26
32
|
end
|
metadata
CHANGED
@@ -1,65 +1,87 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timeless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adolfo Villafiorita
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-09-13 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
|
-
version: '
|
19
|
+
version: '0'
|
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
|
-
version: '
|
26
|
+
version: '0'
|
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: '0'
|
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: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
48
52
|
- - ">="
|
49
53
|
- !ruby/object:Gem::Version
|
50
|
-
version:
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: slop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 4.5.0
|
51
62
|
type: :runtime
|
52
63
|
prerelease: false
|
53
64
|
version_requirements: !ruby/object:Gem::Requirement
|
54
65
|
requirements:
|
55
66
|
- - "~>"
|
56
67
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
68
|
+
version: 4.5.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: chronic
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
58
80
|
- - ">="
|
59
81
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
82
|
+
version: '0'
|
61
83
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
84
|
+
name: notiffany
|
63
85
|
requirement: !ruby/object:Gem::Requirement
|
64
86
|
requirements:
|
65
87
|
- - ">="
|
@@ -72,9 +94,9 @@ dependencies:
|
|
72
94
|
- - ">="
|
73
95
|
- !ruby/object:Gem::Version
|
74
96
|
version: '0'
|
75
|
-
description: Timeless comes with a pomodoro timer, with a standard stopwatch
|
76
|
-
|
77
|
-
a spreadsheet.
|
97
|
+
description: Timeless comes with a pomodoro timer, with a standard stopwatch (start,
|
98
|
+
top), semantic tags in notes (projects, clients, activites), and reporting and exporting
|
99
|
+
command. Data is stored in csv and it can be easily imported into a spreadsheet.
|
78
100
|
email:
|
79
101
|
- adolfo.villafiorita@me.com
|
80
102
|
executables:
|
@@ -85,10 +107,15 @@ files:
|
|
85
107
|
- ".gitignore"
|
86
108
|
- Gemfile
|
87
109
|
- LICENSE.txt
|
88
|
-
- README.
|
110
|
+
- README.md
|
89
111
|
- Rakefile
|
90
|
-
- bin/
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- elisp/forms.el
|
115
|
+
- exe/timeless
|
91
116
|
- lib/timeless.rb
|
117
|
+
- lib/timeless/cli/command_semantics.rb
|
118
|
+
- lib/timeless/cli/command_syntax.rb
|
92
119
|
- lib/timeless/pomodoro.rb
|
93
120
|
- lib/timeless/stopwatch.rb
|
94
121
|
- lib/timeless/storage.rb
|
@@ -114,9 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
141
|
version: '0'
|
115
142
|
requirements: []
|
116
143
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
144
|
+
rubygems_version: 2.6.11
|
118
145
|
signing_key:
|
119
146
|
specification_version: 4
|
120
|
-
summary: Timeless is a simple command
|
147
|
+
summary: Timeless is a simple but effective command-line time tracker
|
121
148
|
test_files: []
|
122
|
-
has_rdoc:
|
data/README.textile
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
h1. Timeless
|
2
|
-
|
3
|
-
Timeless is a simple command line time tracker.
|
4
|
-
|
5
|
-
h2. Installation
|
6
|
-
|
7
|
-
Add this line to your application's Gemfile:
|
8
|
-
|
9
|
-
bc.. ruby
|
10
|
-
gem 'timeless'
|
11
|
-
|
12
|
-
p. And then execute:
|
13
|
-
|
14
|
-
bc. $ bundle
|
15
|
-
|
16
|
-
Or install it yourself as:
|
17
|
-
|
18
|
-
bc. $ gem install timeless
|
19
|
-
|
20
|
-
h2. Usage
|
21
|
-
|
22
|
-
timeless is a simple command line time tracker. Entries store the following information:
|
23
|
-
|
24
|
-
# start time
|
25
|
-
# end time
|
26
|
-
# notes
|
27
|
-
|
28
|
-
timeless allows one to
|
29
|
-
|
30
|
-
* run a pomodoro timer
|
31
|
-
* clock time using a "stopwatch" (with start and stop commands)
|
32
|
-
* manually entering data
|
33
|
-
* print a text report, possibly filtered by date ranges of notes' content
|
34
|
-
* export to csv
|
35
|
-
|
36
|
-
Timeless has also some basic support for key-pair values in the notes form. They can be used to assign a special meaning to some strings and improve filtering and exporting. Two special keys are @p@ for projects and @c@ for clients. These two keys are exported in dedicated columns when exporting data to csv.
|
37
|
-
|
38
|
-
Some non nominal conditions are also handled (e.g., starting a clock twice, trying to stop when no clock was started).
|
39
|
-
|
40
|
-
h2. Examples
|
41
|
-
|
42
|
-
Get information about command syntax:
|
43
|
-
|
44
|
-
bc. timeless -h
|
45
|
-
|
46
|
-
Run a pomodoro timer:
|
47
|
-
|
48
|
-
bc.. timeless pom p:project meeting with c:john
|
49
|
-
timeless pom --duration 60 p:prj2
|
50
|
-
timeless pom --long # default to 50 minutes
|
51
|
-
|
52
|
-
p. Start clocking using a stopwatch:
|
53
|
-
|
54
|
-
bc.. timeless start requirements doc
|
55
|
-
timeless start --at 'thirty minutes ago' fixing bug 182 for c:tim
|
56
|
-
timeless start --force --at 'five minutes ago' requirements document for p:prj1
|
57
|
-
|
58
|
-
Stop clocking:
|
59
|
-
|
60
|
-
bc.. timeless stop
|
61
|
-
timeless stop forgot notes on start
|
62
|
-
timeless stop --last # reuse the notes of last entry
|
63
|
-
timeless stop --at 'five minutes ago'
|
64
|
-
timeless stop --start '1 hour ago' --at 'now' clocked a full entry
|
65
|
-
|
66
|
-
p. Enter a full entry:
|
67
|
-
|
68
|
-
bc.. timeless clock activity on p:project # from end of last entry to now
|
69
|
-
timeless clock --start 'three hours ago' --stop 'five minutes ago'
|
70
|
-
|
71
|
-
p. What was i doing?
|
72
|
-
|
73
|
-
bc.. timeless forget # forget pending clock
|
74
|
-
timeless last # print last entry
|
75
|
-
timeless current # print current entry
|
76
|
-
|
77
|
-
p. Reporting and exporting
|
78
|
-
|
79
|
-
bc.. timeless report
|
80
|
-
timeless report --from yesterday --filter p:project
|
81
|
-
timeless export
|
82
|
-
|
83
|
-
Notice that even though data is stored in csv, the export command exports data in a format which is more easily parsed by a Spreadsheet such as LibreOffice.
|
84
|
-
|
85
|
-
h2. If something goes wrong
|
86
|
-
|
87
|
-
Data is stored in the CSV file @~/.timeless.csv@. You can edit the file to manually fix entries if you make some mistake.
|
88
|
-
|
89
|
-
h2. License
|
90
|
-
|
91
|
-
Licensed under the terms of the MIT License.
|
92
|
-
|
93
|
-
h2. Contributing
|
94
|
-
|
95
|
-
# Fork it ( https://github.com/[my-github-username]/timeless/fork )
|
96
|
-
# Create your feature branch (`git checkout -b my-new-feature`)
|
97
|
-
# Commit your changes (`git commit -am 'Add some feature'`)
|
98
|
-
# Push to the branch (`git push origin my-new-feature`)
|
99
|
-
# Create a new Pull Request
|
data/bin/timeless
DELETED
@@ -1,386 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'slop'
|
4
|
-
require 'date'
|
5
|
-
require 'chronic'
|
6
|
-
|
7
|
-
require "timeless"
|
8
|
-
|
9
|
-
version = Timeless::VERSION
|
10
|
-
app_name = "timeless"
|
11
|
-
app_one_liner = "A command line time tracker"
|
12
|
-
|
13
|
-
man = <<EOS
|
14
|
-
NAME
|
15
|
-
#{app_name} -- #{app_one_liner}
|
16
|
-
|
17
|
-
SYNOPSYS
|
18
|
-
#{app_name} [-h|-v]
|
19
|
-
#{app_name} command [options] [args]
|
20
|
-
|
21
|
-
DESCRIPTION
|
22
|
-
timeless is a simple command line time tracker
|
23
|
-
|
24
|
-
It allows one to:
|
25
|
-
|
26
|
-
* run a pomodoro timer of standard or custom duration
|
27
|
-
* clock time using a \"stopwatch\" (with start and stop commands)
|
28
|
-
* manually entering data
|
29
|
-
* print a text report, possibly filtered by date ranges or strings
|
30
|
-
* export to csv
|
31
|
-
|
32
|
-
Some non nominal conditions are handled (e.g. starting twice, trying to stop
|
33
|
-
when no clock was started).
|
34
|
-
|
35
|
-
Timeless assigns a special meaning to some keypairs in notes. In particular,
|
36
|
-
you can use:
|
37
|
-
|
38
|
-
p:PROJECT
|
39
|
-
c:NAME
|
40
|
-
|
41
|
-
to specify the project and the client a specific entry refers to. The information
|
42
|
-
can be used for filtering and when exporting data.
|
43
|
-
|
44
|
-
EXAMPLES
|
45
|
-
|
46
|
-
timeless help
|
47
|
-
timeless man
|
48
|
-
|
49
|
-
timeless pom
|
50
|
-
|
51
|
-
timeless start doing something for c:a on p:b
|
52
|
-
timeless stop --at '5 minutes ago'
|
53
|
-
|
54
|
-
timeless report
|
55
|
-
timeless export
|
56
|
-
|
57
|
-
VERSION
|
58
|
-
This is version #{version}
|
59
|
-
|
60
|
-
LICENSE
|
61
|
-
MIT
|
62
|
-
|
63
|
-
SEE ALSO
|
64
|
-
#{app_name} -h
|
65
|
-
https://github.com/avillafiorita/#{app_name}
|
66
|
-
EOS
|
67
|
-
|
68
|
-
def last_entry
|
69
|
-
start, stop, notes = Timeless::Storage.last
|
70
|
-
|
71
|
-
interval = Time.parse(stop) - Time.parse(start)
|
72
|
-
seconds = interval % 60
|
73
|
-
minutes = (interval / 60) % 60
|
74
|
-
hours = interval / 3600
|
75
|
-
|
76
|
-
sprintf "Timeless: you clocked %02d:%02d:%02d on %s\nYou stopped clocking at: %s", hours, minutes, seconds, notes, stop
|
77
|
-
end
|
78
|
-
|
79
|
-
#
|
80
|
-
# code shared by stop and clock (which accept slightly different options,
|
81
|
-
# but behave in the same way
|
82
|
-
#
|
83
|
-
def manage_stop start, stop, notes, reuse_last
|
84
|
-
# syntax check:
|
85
|
-
# - --start is illegal if there is a running clock
|
86
|
-
# - --last is illegal if there are notes
|
87
|
-
#
|
88
|
-
# however:
|
89
|
-
# - --last and notes prevail over notes stored when starting the clock
|
90
|
-
if Timeless::Stopwatch.clocking? and start
|
91
|
-
puts "Timeless error: you specified --start with a running clock. Use 'timeless forget' or drop --start"
|
92
|
-
end
|
93
|
-
if reuse_last and notes != "" then
|
94
|
-
puts "Timeless error: you specified both --last and notes. Choose one or the other"
|
95
|
-
end
|
96
|
-
|
97
|
-
if reuse_last then
|
98
|
-
_, _, notes = Timeless::Storage.last
|
99
|
-
end
|
100
|
-
|
101
|
-
if Timeless::Stopwatch.clocking?
|
102
|
-
start, stop, notes = Timeless::Stopwatch.stop(start, stop, notes) # merge passed with data in running clock
|
103
|
-
Timeless::Storage.store(start, stop, notes)
|
104
|
-
|
105
|
-
puts "Clock stopped at #{stop}."
|
106
|
-
puts last_entry
|
107
|
-
else
|
108
|
-
start = start ? start : Timeless::Storage.last[1]
|
109
|
-
stop = stop ? stop : Time.now
|
110
|
-
Timeless::Storage.store(start, stop, notes)
|
111
|
-
|
112
|
-
puts "Clock stopped at #{stop}."
|
113
|
-
puts last_entry
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
#
|
118
|
-
# Main App Starts Here!
|
119
|
-
#
|
120
|
-
if ARGV == [] then
|
121
|
-
puts "type 'timeless -h' for help."
|
122
|
-
exit
|
123
|
-
end
|
124
|
-
|
125
|
-
#begin
|
126
|
-
opts = Slop.parse :help => true, :strict => true do
|
127
|
-
|
128
|
-
banner "#{app_name} [-h|-v]\n#{app_name} command [options] [args]"
|
129
|
-
|
130
|
-
##############################################################################
|
131
|
-
on "-v", "--version", 'Print version information' do
|
132
|
-
puts "#{app_name} version #{version}"
|
133
|
-
end
|
134
|
-
|
135
|
-
##############################################################################
|
136
|
-
command :man do
|
137
|
-
banner "#{app_name} man"
|
138
|
-
description "Print usage instruction for #{app_name}"
|
139
|
-
|
140
|
-
run do |_, _|
|
141
|
-
puts man
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
##############################################################################
|
146
|
-
command :pom do
|
147
|
-
banner "#{app_name} pom [options] [notes]"
|
148
|
-
description "Start a pomodoro timer."
|
149
|
-
|
150
|
-
on :long, "Use a long timer (50 minutes)"
|
151
|
-
on :duration=, "Duration of the pomodoro timer, in minutes", as: Integer
|
152
|
-
|
153
|
-
run do |opts, args|
|
154
|
-
duration = opts.to_hash[:duration] ||
|
155
|
-
(opts.to_hash[:long] ? Timeless::Pomodoro::WORKING_LONG : Timeless::Pomodoro::WORKING)
|
156
|
-
|
157
|
-
start, stop, notes = Timeless::Pomodoro.run_pomodoro_timer(duration, args.join(" "))
|
158
|
-
Timeless::Storage.store(start, stop, notes)
|
159
|
-
puts last_entry
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
##############################################################################
|
164
|
-
command :start do
|
165
|
-
banner "#{app_name} start [--at chronic expression] [notes]"
|
166
|
-
description "Start clocking."
|
167
|
-
|
168
|
-
on :at=, "Actual start time, using chronic expression"
|
169
|
-
on :force, "Forget existing running timer"
|
170
|
-
|
171
|
-
run do |opts, args|
|
172
|
-
start = Chronic.parse(opts.to_hash[:at]) # nil if no option specified
|
173
|
-
notes = args.join(" ") # empty string is no args specified
|
174
|
-
force = opts.to_hash[:force]
|
175
|
-
|
176
|
-
if Timeless::Stopwatch.clocking? and not force
|
177
|
-
start, notes = Timeless::Stopwatch.get_start # for information purposes only
|
178
|
-
puts "There is a clock started at #{start} (notes: \"#{notes}\"). Use --force to override."
|
179
|
-
else
|
180
|
-
Timeless::Stopwatch.start(start, notes)
|
181
|
-
puts "Clock started at #{start ? start : Time.now}."
|
182
|
-
puts "You may want to specify notes for the entry when you stop clocking." if notes == ""
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
##############################################################################
|
188
|
-
command :forget do
|
189
|
-
banner "#{app_name} forget"
|
190
|
-
description "Forget the timer you started, if any"
|
191
|
-
|
192
|
-
run do |opts, args|
|
193
|
-
Timeless::Stopwatch.forget
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
##############################################################################
|
198
|
-
command :stop do
|
199
|
-
banner "#{app_name} stop [--at chronic expression] [--start chronic expression] [--last|notes]"
|
200
|
-
description "Stop clocking, using notes, if specified. Require a start time if there is no pending clock."
|
201
|
-
|
202
|
-
on :at=, "Stop time, using chronic expression"
|
203
|
-
on :start=, "Start time, using chronic expression. Required if there is no timer started."
|
204
|
-
on :last, "Reuse notes of last clocked entry"
|
205
|
-
|
206
|
-
|
207
|
-
run do |opts, args|
|
208
|
-
start = Chronic.parse(opts.to_hash[:start]) # nil if no option specified
|
209
|
-
stop = Chronic.parse(opts.to_hash[:at]) # nil if no option specified
|
210
|
-
notes = args.join(" ") # empty string if no args specified
|
211
|
-
|
212
|
-
manage_stop start, stop, notes, opts[:last]
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
##############################################################################
|
217
|
-
command :clock do
|
218
|
-
banner "#{app_name} clock [--start chronic expression] [--stop|--end chronic expression] [--last|notes]"
|
219
|
-
description "Clock an entry specifying start and end time. Leave any running clock."
|
220
|
-
|
221
|
-
on :start=, "Start time, using chronic expression (start from previous entry if not specified)."
|
222
|
-
on :stop=, "End time, using chronic expression (stop now, if not specified)."
|
223
|
-
on :end=, "End time, an alias for stop."
|
224
|
-
on :last, "Reuse notes of last entry"
|
225
|
-
|
226
|
-
run do |opts, args|
|
227
|
-
start = Chronic.parse(opts.to_hash[:start]) # nil if no option specified
|
228
|
-
stop_opt = Chronic.parse(opts.to_hash[:stop]) # nil if no option specified
|
229
|
-
end_opt = Chronic.parse(opts.to_hash[:end]) # nil if no option specified
|
230
|
-
stop = stop_opt ? stop_opt : end_opt # stop_opt if --stop, end_opt if --end, nil otherwise
|
231
|
-
notes = args.join(" ") # empty string if no args specified
|
232
|
-
|
233
|
-
if stop_opt and end_opt then
|
234
|
-
puts "Timeless error: specify end time with either --end or --stop (not both)"
|
235
|
-
exit
|
236
|
-
end
|
237
|
-
|
238
|
-
manage_stop start, stop, notes, opts[:last]
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
|
243
|
-
##############################################################################
|
244
|
-
command :current do
|
245
|
-
banner "#{app_name} current"
|
246
|
-
description "Show time elapsed since last start command."
|
247
|
-
|
248
|
-
run do |opts, args|
|
249
|
-
if Timeless::Stopwatch.clocking? then
|
250
|
-
start, notes = Timeless::Stopwatch.get_start
|
251
|
-
puts "Timeless: you have been clocking #{"%.0d" % ((Time.now- Time.parse(start)) / 60)} minutes"
|
252
|
-
puts "on: #{notes}" if notes
|
253
|
-
else
|
254
|
-
puts "There is no clock started."
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
end
|
259
|
-
|
260
|
-
##############################################################################
|
261
|
-
command :last do
|
262
|
-
banner "#{app_name} last"
|
263
|
-
description "Show last entry."
|
264
|
-
|
265
|
-
run do |_, _|
|
266
|
-
puts last_entry
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
##############################################################################
|
271
|
-
command :list do
|
272
|
-
banner "#{app_name} list key"
|
273
|
-
description "List all values assigned to a given key (e.g., list all projects)"
|
274
|
-
|
275
|
-
run do |_, args|
|
276
|
-
values = Timeless::Storage.get_key args[0]
|
277
|
-
puts "Timeless: list of keys #{args[0]} found in timesheets:"
|
278
|
-
values.each do |value|
|
279
|
-
puts value
|
280
|
-
end
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
##############################################################################
|
285
|
-
command :export do
|
286
|
-
banner "#{app_name} export"
|
287
|
-
description "Export to csv"
|
288
|
-
|
289
|
-
run do |_, _|
|
290
|
-
Timeless::Storage.export
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
|
295
|
-
##############################################################################
|
296
|
-
command :gaps do
|
297
|
-
banner "#{app_name} gaps"
|
298
|
-
description "List all gaps in the timesheets (periods you have not clocked)"
|
299
|
-
|
300
|
-
on :from=, "From date"
|
301
|
-
on :to=, "To date"
|
302
|
-
on :interval=, "Minimum interval to report (in minutes)", :as => Integer
|
303
|
-
|
304
|
-
run do |opts, _|
|
305
|
-
from = opts.to_hash[:from] ? Chronic.parse(opts.to_hash[:from]) : nil
|
306
|
-
to = opts.to_hash[:to] ? Chronic.parse(opts.to_hash[:to]) : nil
|
307
|
-
interval = opts.to_hash[:interval] || 1
|
308
|
-
|
309
|
-
entries = Timeless::Storage.get(from, to)
|
310
|
-
previous_stop = from || Time.parse(entries[0][0])
|
311
|
-
day = ""
|
312
|
-
|
313
|
-
printf "%-18s %-5s - %-5s %-6s %-s\n", "Day", "Start", "End", "Gap", "Command to fix"
|
314
|
-
entries.sort { |x,y| Time.parse(x[0]) <=> Time.parse(y[0]) }.each do |entry|
|
315
|
-
current_start = Time.parse(entry[0])
|
316
|
-
|
317
|
-
current_day = current_start.strftime("%a %b %d, %Y")
|
318
|
-
|
319
|
-
if (current_start.day == previous_stop.day and current_start - previous_stop > interval * 60) then
|
320
|
-
|
321
|
-
if current_day != day then
|
322
|
-
day = current_day
|
323
|
-
else
|
324
|
-
current_day = "" # avoid printing the day if same as before
|
325
|
-
end
|
326
|
-
|
327
|
-
duration = (current_start - previous_stop) / 60
|
328
|
-
printf "%-18s %5s - %5s %02i:%02i %s\n",
|
329
|
-
current_day,
|
330
|
-
previous_stop.strftime("%H:%M"),
|
331
|
-
current_start.strftime("%H:%M"),
|
332
|
-
duration / 60, duration % 60,
|
333
|
-
"timeless clock --start '#{previous_stop}' --end '#{current_start}'"
|
334
|
-
|
335
|
-
end
|
336
|
-
previous_stop = Time.parse(entry[1])
|
337
|
-
end
|
338
|
-
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
##############################################################################
|
343
|
-
command :report do
|
344
|
-
banner "#{app_name} report"
|
345
|
-
description "Display a report of entries."
|
346
|
-
|
347
|
-
on :from=, "From date"
|
348
|
-
on :to=, "To date"
|
349
|
-
on :filter=, "With notes containing string"
|
350
|
-
|
351
|
-
run do |opts, args|
|
352
|
-
from = opts.to_hash[:from] ? Chronic.parse(opts.to_hash[:from]) : nil
|
353
|
-
to = opts.to_hash[:to] ? Chronic.parse(opts.to_hash[:to]) : nil
|
354
|
-
|
355
|
-
entries = Timeless::Storage.get(from, to, opts.to_hash[:filter])
|
356
|
-
# it could become a function of a reporting module
|
357
|
-
printf "%-18s %-5s - %-5s %-8s %-s\n", "Day", "Start", "End", "Duration", "Notes"
|
358
|
-
total = 0
|
359
|
-
day = ""
|
360
|
-
entries.sort { |x,y| Time.parse(x[0]) <=> Time.parse(y[0]) }.each do |entry|
|
361
|
-
current_day = Time.parse(entry[0]).strftime("%a %b %d, %Y")
|
362
|
-
if current_day != day then
|
363
|
-
day = current_day
|
364
|
-
else
|
365
|
-
current_day = "" # avoid printing the day if same as before
|
366
|
-
end
|
367
|
-
|
368
|
-
duration = (Time.parse(entry[1]) - Time.parse(entry[0])) / 60
|
369
|
-
total = total + duration
|
370
|
-
|
371
|
-
printf "%-18s %5s - %5s %02i:%02i %s\n",
|
372
|
-
current_day,
|
373
|
-
Time.parse(entry[0]).strftime("%H:%M"),
|
374
|
-
Time.parse(entry[1]).strftime("%H:%M"),
|
375
|
-
duration / 60, duration % 60,
|
376
|
-
entry[2]
|
377
|
-
end
|
378
|
-
puts "----------------------------------------------------------------------"
|
379
|
-
printf "Total %02i:%02i\n", total / 60, total % 60
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
383
|
-
# rescue
|
384
|
-
# puts "error: unknown option"
|
385
|
-
# puts "type '#{app_name} -h' for help"
|
386
|
-
# end
|