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.
@@ -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