timr 0.1.0.pre.dev.5 → 0.1.0

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