timr 0.1.0.pre.dev.5 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4157f4d824d2cadedc511f421b488656ac6a9640
4
- data.tar.gz: f859883d4ee1a270a61653e4e6c063bacd5f9969
3
+ metadata.gz: 169f2bc1266172e38449b616494751f1675e3cf7
4
+ data.tar.gz: b33d71090571dbcd6aa1e21a62a39f2b6fd8c484
5
5
  SHA512:
6
- metadata.gz: 461b4bccfd4fd85530a1d570eb8feac64c4f21e47cb2846e18688bbfe8a053e75934f7444f9fa30bb1cc5983b8f089615e316cecfc2f3cf0114717eda371c5cd
7
- data.tar.gz: ab112bd2d0e900913dac25283681c2eddf21ae381e78b2299b7d42836baab4e8cd7f4ab6ab2a94685ff22eb9878d4c73116f4c3297622e4a970981ee99cad133
6
+ metadata.gz: 9425deec3cc7505b9672cbef8eb5a977521ef5374e28c22ba54f7accaa98aae794152e3495486c28dc0df3e04de4da61222a3a70771a4e614be2308586ffc4b8
7
+ data.tar.gz: 72769b4c7417eecfac58f7a00c0e1e7f19d5b3eb024cf818431a24306bf6d0fc772667fdb544da2633c91651eff95e380980d65ff8a12e38e6f1d6cc3e9a6088
data/.editorconfig CHANGED
@@ -8,3 +8,7 @@ end_of_line = lf
8
8
  charset = utf-8
9
9
  trim_trailing_whitespace = false
10
10
  insert_final_newline = true
11
+
12
+ [*.yml]
13
+ indent_style = space
14
+ indent_size = 4
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.2
5
+ sudo: required
6
+ before_install:
7
+ - gem update --system
8
+ - gem --version
9
+ - gem install bundler -v '~>1.12'
10
+ - bundler --version
11
+ install:
12
+ - make
13
+ - gem build timr.gemspec
14
+ - gem install timr-*.gem
15
+ - gem list -l timr
16
+ script:
17
+ - make test
18
+ - cd
19
+ - which -a timr
20
+ - timr --version
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- timr (0.1.0.pre.dev.5)
4
+ timr (0.1.0)
5
5
  curses (~> 1.0)
6
6
  thefox-ext (~> 1.4)
7
7
  uuid (~> 2.3)
@@ -26,4 +26,4 @@ DEPENDENCIES
26
26
  timr!
27
27
 
28
28
  BUNDLED WITH
29
- 1.11.2
29
+ 1.12.3
data/Makefile CHANGED
@@ -6,3 +6,7 @@ include Makefile.common
6
6
 
7
7
  dev:
8
8
  RUBYOPT=-rbundler/setup ruby ./bin/timr
9
+
10
+ .PHONY: test
11
+ test:
12
+ RUBYOPT=-w TZ=Europe/Vienna $(BUNDLER) exec ./tests/ts_all.rb
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Timr
2
2
 
3
- A Time Tracking Tool for the Command-line.
3
+ Time Tracking for Hackers.
4
+
5
+ ## Project Links
6
+
7
+ - [Timr Gem](https://rubygems.org/gems/timr)
8
+ - [Travis CI Repository](https://travis-ci.org/TheFox/timr)
4
9
 
5
10
  ## License
6
11
 
data/bin/timr CHANGED
@@ -12,10 +12,14 @@ opts = OptionParser.new do |o|
12
12
  o.banner = 'Usage: timr [options]'
13
13
  o.separator('')
14
14
 
15
- o.on_tail('-d', '--dir <path>', 'Path to a timr directory.') do |path|
15
+ o.on('-d', '--dir <path>', 'Path to a timr directory.') do |path|
16
16
  @options[:dir] = path
17
17
  end
18
18
 
19
+ o.on('-p', '--project <name>', 'Project Name: use ~/.timr/NAME as path.') do |name|
20
+ @options[:dir] = File.expand_path(name, "#{Dir.home}/.timr/projects")
21
+ end
22
+
19
23
  o.on_tail('-v', '--version', 'Show version.') do
20
24
  puts "#{TheFox::Timr::NAME} #{TheFox::Timr::VERSION} (#{TheFox::Timr::DATE})"
21
25
  puts "#{TheFox::Timr::HOMEPAGE}"
data/lib/timr/stack.rb CHANGED
@@ -17,20 +17,11 @@ module TheFox
17
17
  !@task.nil?
18
18
  end
19
19
 
20
- def create(name, description)
21
- @task = Task.new
22
- @task.name = name
23
- @task.description = description
24
-
25
- @task
26
- end
27
-
28
20
  def length
29
21
  @tasks.length
30
22
  end
31
23
 
32
24
  def tasks_texts
33
- show_star = length > 1
34
25
  @tasks.map{ |task|
35
26
  status = task.status
36
27
  status = '*' if task == @task
@@ -51,14 +42,19 @@ module TheFox
51
42
  end
52
43
 
53
44
  def pop_all(new_task = nil)
54
- @tasks.each do |task|
55
- task.stop
56
- end
57
- @tasks = []
58
- @task = nil
59
-
60
- if !new_task.nil?
61
- push(new_task)
45
+ if @task != new_task
46
+ @tasks.each do |task|
47
+ task.stop
48
+ end
49
+ @tasks = []
50
+ @task = nil
51
+
52
+ if !new_task.nil?
53
+ push(new_task)
54
+ end
55
+ true
56
+ else
57
+ false
62
58
  end
63
59
  end
64
60
 
@@ -69,6 +65,9 @@ module TheFox
69
65
  @task = task
70
66
  @task.start
71
67
  @tasks << @task
68
+ true
69
+ else
70
+ false
72
71
  end
73
72
  end
74
73
 
data/lib/timr/task.rb CHANGED
@@ -19,8 +19,8 @@ module TheFox
19
19
  'id' => UUID.new.generate,
20
20
  'name' => nil,
21
21
  'description' => nil,
22
- 'created' => Time.now.strftime(TIME_FORMAT),
23
- 'modified' => Time.now.strftime(TIME_FORMAT),
22
+ 'created' => Time.now.utc.strftime(TIME_FORMAT),
23
+ 'modified' => Time.now.utc.strftime(TIME_FORMAT),
24
24
  }
25
25
  @track = nil
26
26
  @timeline = []
@@ -42,12 +42,14 @@ module TheFox
42
42
  end
43
43
 
44
44
  def save_to_file(basepath)
45
- if @changed
46
- path = File.expand_path("task_#{@meta['id']}.yml", basepath)
47
-
45
+ path = File.expand_path("task_#{@meta['id']}.yml", basepath)
46
+
47
+ if @changed || !@track.nil?
48
48
  timeline_c = @timeline
49
49
  .map{ |track|
50
- track.to_h
50
+ h = track.to_h
51
+ h['e'] = Time.now.utc.strftime(TIME_FORMAT) if !h.has_key?('e') || h['e'].nil?
52
+ h
51
53
  }
52
54
 
53
55
  store = YAML::Store.new(path)
@@ -57,6 +59,8 @@ module TheFox
57
59
  end
58
60
  @changed = false
59
61
  end
62
+
63
+ path
60
64
  end
61
65
 
62
66
  def running?
@@ -66,15 +70,15 @@ module TheFox
66
70
  def status
67
71
  case @status
68
72
  when :running
69
- '>'
73
+ ?>
70
74
  when :stop
71
- '|'
75
+ ?|
72
76
  end
73
77
  end
74
78
 
75
79
  def changed
76
80
  @changed = true
77
- @meta['modified'] = Time.now.strftime(TIME_FORMAT)
81
+ @meta['modified'] = Time.now.utc.strftime(TIME_FORMAT)
78
82
  end
79
83
 
80
84
  def id
@@ -86,22 +90,34 @@ module TheFox
86
90
  end
87
91
 
88
92
  def name=(name)
89
- changed
93
+ @changed = true
90
94
  @meta['name'] = name
91
95
  end
92
96
 
97
+ def description
98
+ @meta['description']
99
+ end
100
+
93
101
  def description=(description)
94
- changed
102
+ @changed = true
95
103
  @meta['description'] = description == '' ? nil : description
96
104
  end
97
105
 
106
+ def track
107
+ @track
108
+ end
109
+
110
+ def has_track?
111
+ !@track.nil?
112
+ end
113
+
98
114
  def timeline
99
115
  @timeline
100
116
  end
101
117
 
102
118
  def start
103
119
  if !running?
104
- changed
120
+ @changed = true
105
121
  @track = Track.new(self)
106
122
  @timeline << @track
107
123
  end
@@ -110,8 +126,8 @@ module TheFox
110
126
 
111
127
  def stop
112
128
  if running? && !@track.nil?
113
- changed
114
- @track.end = Time.now
129
+ @changed = true
130
+ @track.end_time = Time.now
115
131
  @track = nil
116
132
  @timeline_diff_total = nil
117
133
  end
@@ -134,13 +150,13 @@ module TheFox
134
150
  name
135
151
  end
136
152
 
137
- def run_time_track
153
+ def run_time_track(end_time = Time.now)
138
154
  hours = 0
139
155
  minutes = 0
140
156
  seconds = 0
141
157
 
142
158
  if !@track.nil?
143
- diff = (Time.now - @track.begin).to_i.abs
159
+ diff = (end_time - @track.begin_time).to_i.abs
144
160
  hours = diff / 3600
145
161
 
146
162
  diff -= hours * 3600
@@ -153,7 +169,7 @@ module TheFox
153
169
  [hours, minutes, seconds]
154
170
  end
155
171
 
156
- def run_time_total
172
+ def run_time_total(end_time = Time.now)
157
173
  # Cache all other tracks.
158
174
  if @timeline_diff_total.nil?
159
175
  @timeline_diff_total = @timeline
@@ -168,7 +184,7 @@ module TheFox
168
184
 
169
185
  track_diff = 0
170
186
  if !@track.nil?
171
- track_diff = (Time.now - @track.begin).to_i.abs
187
+ track_diff = (end_time - @track.begin_time).to_i.abs
172
188
  end
173
189
 
174
190
  diff = @timeline_diff_total.to_i + track_diff
data/lib/timr/timr.rb CHANGED
@@ -21,6 +21,7 @@ module TheFox
21
21
 
22
22
  @stack = Stack.new
23
23
  @tasks = {}
24
+ @last_write = nil
24
25
 
25
26
  init_dirs
26
27
  tasks_load
@@ -61,12 +62,19 @@ module TheFox
61
62
  end
62
63
  end
63
64
 
64
- def tasks_save
65
+ def tasks_save(print_status = false)
66
+ @last_write = Time.now
67
+ if print_status
68
+ ui_status_text("Store files ... #{Time.now.strftime('%T')}")
69
+ end
65
70
  Dir.chdir(@data_dir_path) do
66
71
  @tasks.each do |task_id, task|
67
72
  task.save_to_file('.')
68
73
  end
69
74
  end
75
+ if print_status
76
+ ui_status_text("Files stored. #{Time.now.strftime('%T')}")
77
+ end
70
78
  end
71
79
 
72
80
  def ui_init_curses
@@ -81,7 +89,7 @@ module TheFox
81
89
 
82
90
  Curses.init_pair(Curses::COLOR_BLUE, Curses::COLOR_WHITE, Curses::COLOR_BLUE)
83
91
  Curses.init_pair(Curses::COLOR_RED, Curses::COLOR_WHITE, Curses::COLOR_RED)
84
- Curses.init_pair(Curses::COLOR_YELLOW, Curses::COLOR_BLACK, Curses::COLOR_YELLOW)
92
+ Curses.init_pair(Curses::COLOR_GREEN, Curses::COLOR_BLACK, Curses::COLOR_GREEN)
85
93
  end
86
94
 
87
95
  def ui_title_line
@@ -154,7 +162,7 @@ module TheFox
154
162
  def ui_status_line(init = false)
155
163
  line_nr = Curses.lines - 2
156
164
 
157
- Curses.attron(Curses.color_pair(Curses::COLOR_YELLOW) | Curses::A_NORMAL) do
165
+ Curses.attron(Curses.color_pair(Curses::COLOR_GREEN) | Curses::A_NORMAL) do
158
166
  if init
159
167
  Curses.setpos(line_nr, 0)
160
168
  Curses.clrtoeol
@@ -164,16 +172,20 @@ module TheFox
164
172
  Curses.setpos(line_nr, COL)
165
173
  if @stack.has_task?
166
174
  status = @stack.task.status
175
+ track_begin_time_s = '--:--'
176
+ if @stack.task.has_track?
177
+ track_begin_time_s = @stack.task.track.begin_time.strftime('%R')
178
+ end
167
179
  run_time_track = '%4d:%02d:%02d' % @stack.task.run_time_track
168
180
  run_time_total = '%4d:%02d:%02d' % @stack.task.run_time_total
169
181
 
170
- Curses.addstr("#{status} #{run_time_track} #{run_time_total}")
182
+ Curses.addstr("#{status} #{track_begin_time_s} #{run_time_track} #{run_time_total}")
171
183
  else
172
- Curses.addstr("#{TASK_NO_TASK_LOADED_C} ----:--:-- ----:--:--")
184
+ Curses.addstr("#{TASK_NO_TASK_LOADED_C} --:-- ----:--:-- ----:--:--")
173
185
  end
174
186
 
175
187
  if Curses.cols > MIN_COLS
176
- time_format = '%F %R %Z'
188
+ time_format = '%F %R'
177
189
  if Curses.cols <= 30
178
190
  time_format = '%R'
179
191
  elsif Curses.cols <= 40
@@ -183,7 +195,7 @@ module TheFox
183
195
  elsif Curses.cols <= 60
184
196
  time_format = '%F %R'
185
197
  elsif Curses.cols > 80
186
- time_format = '%F %T %Z'
198
+ time_format = '%F %T'
187
199
  end
188
200
  time_str = Time.now.strftime(time_format)
189
201
  Curses.setpos(line_nr, Curses.cols - time_str.length - 1)
@@ -209,11 +221,8 @@ module TheFox
209
221
  if !@window.nil?
210
222
  line_nr = 1
211
223
  @window.content_refresh
212
- current_line = @window.current_line
213
224
  max_line_len = Curses.cols - 2
214
225
  @window.page.each do |line_object|
215
- is_cursor = line_nr == @window.cursor
216
-
217
226
  line_text = ''
218
227
  if line_object.is_a?(Task) || line_object.is_a?(Track)
219
228
  line_text = line_object.to_list_s
@@ -229,7 +238,7 @@ module TheFox
229
238
  rest = Curses.cols - line_text.length - COL
230
239
 
231
240
  if @window.has_cursor?
232
- if is_cursor
241
+ if line_nr == @window.cursor
233
242
  Curses.setpos(line_nr, 0)
234
243
  Curses.attron(Curses.color_pair(Curses::COLOR_BLUE) | Curses::A_BOLD) do
235
244
  Curses.addstr(' ' * COL + line_text + ' ' * rest)
@@ -243,7 +252,6 @@ module TheFox
243
252
  Curses.addstr(line_text)
244
253
  end
245
254
 
246
-
247
255
  line_nr += 1
248
256
  end
249
257
  end
@@ -306,9 +314,10 @@ module TheFox
306
314
  end
307
315
  end
308
316
 
317
+ update_content_length
309
318
  window_content_changed
310
319
  ui_stack_lines_refresh
311
- ui_window_refresh if !push
320
+ ui_window_refresh
312
321
  end
313
322
 
314
323
  def task_apply_replace_stack(task)
@@ -323,11 +332,34 @@ module TheFox
323
332
  task_apply(task, true)
324
333
  end
325
334
 
335
+ def task_apply_pop
336
+ if @stack.pop
337
+ update_content_length
338
+ #window_content_changed
339
+ ui_refresh
340
+ end
341
+ end
342
+
343
+ # Update only Windows which shows the data. For example,
344
+ # if a task is created all Windows needs to know this,
345
+ # and only Windows which are using the tasks.
346
+ # Not only the length of the rows can change, but also
347
+ # the actual an change.
326
348
  def window_content_changed
327
349
  @window_tasks.content_changed
328
350
  @window_timeline.content_changed
329
351
  end
330
352
 
353
+ def write_all_data
354
+ if @last_write.nil?
355
+ @last_write = Time.now
356
+ else
357
+ if Time.now - @last_write >= 300
358
+ tasks_save(true)
359
+ end
360
+ end
361
+ end
362
+
331
363
  def run
332
364
  ui_init_curses
333
365
  update_content_length
@@ -369,7 +401,7 @@ module TheFox
369
401
  ui_window_refresh
370
402
  when Curses::Key::RESIZE
371
403
  update_content_length
372
- ui_status_text("Resizing: #{Curses.lines}x#{Curses.cols}")
404
+ ui_status_text("Window size: #{Curses.cols}x#{Curses.lines}")
373
405
 
374
406
  # Refreshing the complete screen while resizing
375
407
  # can make everything slower. So for fast resizing
@@ -385,9 +417,7 @@ module TheFox
385
417
  task = object.task
386
418
  end
387
419
 
388
- if task.nil?
389
- ui_status_text("Unrecognized object: #{object.class}")
390
- else
420
+ if !task.nil? # && @stack.task != task
391
421
  task_apply_replace_stack(task)
392
422
  end
393
423
  when 'b', 'p'
@@ -413,22 +443,22 @@ module TheFox
413
443
  if task_name.nil?
414
444
  ui_status_text('Aborted.')
415
445
  else
416
- task_description = ui_status_input('Description: ')
417
-
418
- task = @stack.create(task_name, task_description)
446
+ task = Task.new
447
+ task.name = task_name
419
448
  task_apply_replace_stack(task)
420
449
 
421
- ui_status_text("Task '#{task_name}' created: #{task.id}")
450
+ ui_status_text("Task '#{task_name}' created.")
422
451
  end
423
452
  when 'x'
424
453
  @stack.task.stop if @stack.has_task?
454
+ window_content_changed
455
+ ui_refresh
425
456
  when 'c'
426
457
  @stack.task.toggle if @stack.has_task?
458
+ window_content_changed
459
+ ui_refresh
427
460
  when 'v'
428
- if @stack.pop
429
- window_content_changed
430
- ui_refresh
431
- end
461
+ task_apply_pop
432
462
  when 'f'
433
463
  task_apply_stack_pop_all
434
464
  when 'h', '?'
@@ -440,12 +470,13 @@ module TheFox
440
470
  when '2'
441
471
  ui_window_show(@window_tasks)
442
472
  when 'w'
443
- tasks_save
473
+ tasks_save(true)
444
474
  when 'q'
445
475
  break
446
476
  when nil
447
477
  # Do some work.
448
478
  ui_status_line
479
+ write_all_data
449
480
  else
450
481
  ui_status_text_error("Invalid key '#{key_pressed}' (#{Curses.keyname(key_pressed)})")
451
482
  end
data/lib/timr/track.rb CHANGED
@@ -7,44 +7,57 @@ module TheFox
7
7
 
8
8
  class Track
9
9
 
10
- def initialize(task, tbegin = Time.now, tend = nil)
10
+ def initialize(task = nil, begin_time = Time.now, end_time = nil)
11
11
  @task = task
12
- @tbegin = tbegin
13
- @tend = tend
12
+ @begin_time = begin_time
13
+ @end_time = end_time
14
14
  end
15
15
 
16
- def begin=(tb)
17
- @tbegin = tb
16
+ def task
17
+ @task
18
18
  end
19
19
 
20
- def begin
21
- @tbegin
20
+ def begin_time=(begin_time)
21
+ @begin_time = nil
22
+ @begin_time = begin_time.utc if !begin_time.nil?
22
23
  end
23
24
 
24
- def end=(te)
25
- @tend = te
25
+ def begin_time
26
+ if !@begin_time.nil?
27
+ @begin_time.localtime
28
+ else
29
+ nil
30
+ end
26
31
  end
27
32
 
28
- def end
29
- @tend
33
+ def end_time=(end_time)
34
+ @end_time = nil
35
+ @end_time = end_time.utc if !end_time.nil?
30
36
  end
31
37
 
32
- def diff
33
- if !@tbegin.nil? && !@tend.nil?
34
- (@tend - @tbegin).abs
38
+ def end_time
39
+ if !@end_time.nil?
40
+ @end_time.localtime
35
41
  else
36
- 0
42
+ nil
37
43
  end
38
44
  end
39
45
 
40
- def task
41
- @task
46
+ def diff
47
+ if !@begin_time.nil? && !@end_time.nil?
48
+ (@end_time - @begin_time).abs.to_i
49
+ else
50
+ 0
51
+ end
42
52
  end
43
53
 
44
54
  def to_h
45
- h = {}
46
- h['b'] = @tbegin.strftime(TIME_FORMAT) if !@tbegin.nil?
47
- h['e'] = @tend.strftime(TIME_FORMAT) if !@tend.nil?
55
+ h = {
56
+ 'b' => nil,
57
+ 'e' => nil,
58
+ }
59
+ h['b'] = @begin_time.utc.strftime(TIME_FORMAT) if !@begin_time.nil?
60
+ h['e'] = @end_time.utc.strftime(TIME_FORMAT) if !@end_time.nil?
48
61
  h
49
62
  end
50
63
 
@@ -53,28 +66,33 @@ module TheFox
53
66
  end
54
67
 
55
68
  def to_list_s
56
- tend_date = nil
57
- tend_time_s = ''
58
- if !@tend.nil?
59
- tend_time_s = !@tend.nil? ? @tend.strftime('%R') : 'xx:xx'
60
- tend_date = @tend.to_date
69
+ end_date = nil
70
+ end_time_s = 'xx:xx'
71
+ if !@end_time.nil?
72
+ end_date = @end_time.localtime.to_date
73
+ end_time_s = @end_time.localtime.strftime('%R')
74
+ end
75
+
76
+ begin_date_s = ''
77
+ begin_date = @begin_time.localtime.to_date
78
+ end_date_s = ''
79
+ if (begin_date != end_date && !end_date.nil?) || !begin_date.today?
80
+ begin_date_s = @begin_time.localtime.strftime('%F')
81
+ end_date_s = @end_time.localtime.strftime('%F') if !@end_time.nil?
61
82
  end
62
83
 
63
- tbegin_date_s = ''
64
- tbegin_date = @tbegin.to_date
65
- tend_date_s = ''
66
- if (tbegin_date != tend_date && !tend_date.nil?) || !tbegin_date.today?
67
- tbegin_date_s = @tbegin.strftime('%F')
68
- tend_date_s = @tend.strftime('%F') if !@tend.nil?
84
+ task_name = ''
85
+ if !@task.nil?
86
+ task_name = @task.to_list_s
69
87
  end
70
88
 
71
- '%10s %5s - %5s %10s %s' % [tbegin_date_s, @tbegin.strftime('%R'), tend_time_s, tend_date_s, @task.to_list_s]
89
+ '%10s %5s - %5s %10s %s' % [begin_date_s, @begin_time.localtime.strftime('%R'), end_time_s, end_date_s, task_name]
72
90
  end
73
91
 
74
- def self.from_h(task, h)
75
- t = Track.new(task)
76
- t.begin = Time.parse(h['b'])
77
- t.end = Time.parse(h['e'])
92
+ def self.from_h(task = nil, h)
93
+ t = Track.new(task, nil)
94
+ t.begin_time = Time.parse(h['b']) if h.has_key?('b')
95
+ t.end_time = Time.parse(h['e']) if h.has_key?('e')
78
96
  t
79
97
  end
80
98
 
data/lib/timr/version.rb CHANGED
@@ -2,8 +2,8 @@
2
2
  module TheFox
3
3
  module Timr
4
4
  NAME = 'Timr'
5
- VERSION = '0.1.0-dev.5'
6
- DATE = '2016-05-10'
5
+ VERSION = '0.1.0'
6
+ DATE = '2016-05-15'
7
7
  HOMEPAGE = 'https://github.com/TheFox/timr'
8
8
 
9
9
  COL = 1