timeless 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a45d84d4290e39bba5b6fe3a99fd101cefd7f97c
4
+ data.tar.gz: 761ab4cf1594beb69534198f556a11f936806801
5
+ SHA512:
6
+ metadata.gz: 5b97c16e7cc2fc1b1b919c93518a11c7ae6a115e11ff1138251bf26bd699da7d18b11f59b0a222c4ca242105d596528b52e1bb6149abd4f1df8f4c37adeffd3f
7
+ data.tar.gz: 72160f36426d2659db29cb1cfc57deb81948e1a24d483639197f881c0068c65b7b06455fc684d018793e091d7d85b0bd5e22d3c96d9b485d116d175affe9fddd
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *~
16
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in timeless.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Adolfo Villafiorita
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,99 @@
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,386 @@
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
@@ -0,0 +1,7 @@
1
+ require "timeless/version"
2
+
3
+ module Timeless
4
+ require "timeless/pomodoro"
5
+ require "timeless/stopwatch"
6
+ require "timeless/storage"
7
+ end
@@ -0,0 +1,40 @@
1
+ module Timeless
2
+ module Pomodoro
3
+ require 'terminal-notifier'
4
+ require 'date'
5
+
6
+ TITLE = 'Pomodoro'
7
+ MESSAGE = 'Finish'
8
+ SOUND = 'Glass'
9
+
10
+ WORKING = 25
11
+ WORKING_LONG = 50
12
+
13
+ BREAK = 5
14
+ BREAK_LONG = 15
15
+
16
+ INTERVAL = 0.3
17
+
18
+ def self.run_pomodoro_timer total_mins, notes=nil
19
+ start = Time.now
20
+ total_secs = total_mins * 60
21
+ s = 0
22
+ _s = nil
23
+ while true
24
+ s = (Time.now - start).to_i
25
+ r = total_secs - s
26
+ printf("\r%i:%02i", r / 60, r % 60) if s != _s
27
+ break if s >= total_secs
28
+ _s = s
29
+ sleep INTERVAL
30
+ end
31
+ print "\n"
32
+
33
+ TerminalNotifier.notify(MESSAGE, title: TITLE, sound: SOUND)
34
+ system "say 'Pomodoro complete. Take a break, now'"
35
+
36
+ [start, Time.now, notes]
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,62 @@
1
+ #
2
+ # implement a stopwatch, which can be started and stopped
3
+ #
4
+ module Timeless
5
+ module Stopwatch
6
+ require 'date'
7
+
8
+ def self.start start=nil, notes=nil
9
+ store_start (start ? start : Time.now), (notes ? notes : "")
10
+ end
11
+
12
+ def self.stop start=nil, stop=nil, notes=nil
13
+ if not start
14
+ start, _ = get_start
15
+ end
16
+
17
+ if notes == nil or notes == ""
18
+ _, notes = get_start
19
+ end
20
+
21
+ if not stop
22
+ stop = Time.now
23
+ end
24
+
25
+ forget # forget started clock, if any
26
+
27
+ [start, stop, notes]
28
+ end
29
+
30
+ #
31
+ # Storing and retrieving start time and notes
32
+ #
33
+ # file containing the start time
34
+
35
+ START_FILENAME = File.expand_path("~/.timeless-tmp.csv")
36
+
37
+ def self.clocking?
38
+ File.exists?(START_FILENAME)
39
+ end
40
+
41
+ def self.get_start
42
+ if clocking?
43
+ array = CSV.read(START_FILENAME, "r")
44
+ [array[0][0], array[0][1]]
45
+ else
46
+ [nil, nil]
47
+ end
48
+ end
49
+
50
+ def self.store_start start, notes
51
+ CSV.open(START_FILENAME, "w") do |csv|
52
+ csv << [start, notes]
53
+ end
54
+ end
55
+
56
+ # forget the start clock
57
+ def self.forget
58
+ File.delete(START_FILENAME) if File.exists?(START_FILENAME)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ module Timeless
2
+ require 'csv'
3
+
4
+ module Storage
5
+ TIMELESS_FILE=File.expand_path("~/.timeless.csv")
6
+
7
+ def self.store start, stop, notes
8
+ CSV.open(TIMELESS_FILE, "a") do |csv|
9
+ csv << [start, stop, notes]
10
+ end
11
+ end
12
+
13
+ def self.last
14
+ CSV.read(TIMELESS_FILE).last
15
+ end
16
+
17
+ def self.get from_date=nil, to_date=nil, filter=nil
18
+ entries = CSV.read(TIMELESS_FILE)
19
+ entries.select do |x|
20
+ (from_date ? Time.parse(x[0]) >= from_date : true) and
21
+ (to_date ? Time.parse(x[1]) <= to_date : true) and
22
+ (filter ? x[2].include?(filter) : true)
23
+ end
24
+ end
25
+
26
+ # get all unique keys of a given type in file
27
+ def self.get_key key
28
+ entries = CSV.read(TIMELESS_FILE)
29
+ entries.map { |x| extract_kpv key, x[2] }.uniq.sort
30
+ end
31
+
32
+ def self.export
33
+ entries = CSV.read(TIMELESS_FILE)
34
+
35
+ CSV { |csvout| csvout << ["Start Date", "Start Time", "End Date", "End Time", "Duration (s)", "Project", "Notes"] }
36
+ CSV do |csvout|
37
+ entries.each do |entry|
38
+ start = Time.parse(entry[0])
39
+ stop = Time.parse(entry[1])
40
+
41
+ start_date = start.strftime("%Y-%m-%d")
42
+ start_time = start.strftime("%H:%M:%S")
43
+
44
+ stop_date = stop.strftime("%Y-%m-%d")
45
+ stop_time = stop.strftime("%H:%M:%S")
46
+
47
+ duration = stop - start
48
+
49
+ # extract project and client, if present
50
+ project = extract_kpv "p", entry[2]
51
+ client = extract_kpv "c", entry[2]
52
+
53
+ notes = entry[2]
54
+
55
+ csvout << [start_date, start_time, stop_date, stop_time, duration, project, client, notes]
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # return the value of a key-pair value in string or "" if not present
63
+ def self.extract_kpv key, string
64
+ match = string.match(/#{key}:([^ ]+)/)
65
+ match ? match[1] : ""
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,3 @@
1
+ module Timeless
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'timeless/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "timeless"
8
+ spec.version = Timeless::VERSION
9
+ spec.authors = ["Adolfo Villafiorita"]
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.}
13
+ spec.homepage = "http://github.com/avillafiorita/timeless"
14
+ spec.license = "MIT"
15
+
16
+ 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.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_runtime_dependency 'slop', '~> 3.6.0', '>= 3.6.0'
25
+ spec.add_runtime_dependency 'chronic'
26
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timeless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adolfo Villafiorita
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: slop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.6.0
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.6.0
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: 3.6.0
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 3.6.0
61
+ - !ruby/object:Gem::Dependency
62
+ name: chronic
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ 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.
78
+ email:
79
+ - adolfo.villafiorita@me.com
80
+ executables:
81
+ - timeless
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - ".gitignore"
86
+ - Gemfile
87
+ - LICENSE.txt
88
+ - README.textile
89
+ - Rakefile
90
+ - bin/timeless
91
+ - lib/timeless.rb
92
+ - lib/timeless/pomodoro.rb
93
+ - lib/timeless/stopwatch.rb
94
+ - lib/timeless/storage.rb
95
+ - lib/timeless/version.rb
96
+ - timeless.gemspec
97
+ homepage: http://github.com/avillafiorita/timeless
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.2.2
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Timeless is a simple command line time tracker
121
+ test_files: []
122
+ has_rdoc: