timeless 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Timeless
2
- VERSION = "0.1.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -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
- spec.summary = %q{Timeless is a simple command line time tracker}
12
- spec.description = %q{Timeless comes with a pomodoro timer, with a standard stopwatch and a simple reporting command. Data is stored in csv and it can be easily imported into a spreadsheet.}
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.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
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.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
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', '~> 3.6.0', '>= 3.6.0'
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.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adolfo Villafiorita
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2015-03-18 00:00:00.000000000 Z
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: '1.7'
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: '1.7'
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: '10.0'
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: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: slop
42
+ name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 3.6.0
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: 3.6.0
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: 3.6.0
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: 3.6.0
82
+ version: '0'
61
83
  - !ruby/object:Gem::Dependency
62
- name: chronic
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 and a
76
- simple reporting command. Data is stored in csv and it can be easily imported into
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.textile
110
+ - README.md
89
111
  - Rakefile
90
- - bin/timeless
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.2.2
144
+ rubygems_version: 2.6.11
118
145
  signing_key:
119
146
  specification_version: 4
120
- summary: Timeless is a simple command line time tracker
147
+ summary: Timeless is a simple but effective command-line time tracker
121
148
  test_files: []
122
- has_rdoc:
@@ -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
@@ -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