timeless 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: