timr 0.3.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.ackrc +9 -0
  3. data/.editorconfig +1 -0
  4. data/.env.example +7 -0
  5. data/.github/CONTRIBUTING.md +32 -0
  6. data/.github/ISSUE_TEMPLATE.md +13 -0
  7. data/.gitignore +8 -2
  8. data/.rdoc_options +21 -0
  9. data/.travis.yml +10 -7
  10. data/Gemfile +8 -0
  11. data/README.md +216 -3
  12. data/bin/.gitignore +2 -0
  13. data/bin/README.md +17 -0
  14. data/bin/build.sh +14 -0
  15. data/bin/build_api.sh +14 -0
  16. data/bin/build_coverage.sh +23 -0
  17. data/bin/build_info.sh +27 -0
  18. data/bin/build_man.sh +41 -0
  19. data/bin/clean.sh +14 -0
  20. data/bin/dev_setup.sh +19 -0
  21. data/bin/install.sh +49 -0
  22. data/bin/publish +38 -0
  23. data/bin/release.sh +35 -0
  24. data/bin/test.sh +19 -0
  25. data/bin/timr +20 -40
  26. data/bin/timr_bash_completion.sh +337 -0
  27. data/bin/uninstall.sh +24 -0
  28. data/lib/timr.rb +36 -8
  29. data/lib/timr/command/basic_command.rb +170 -0
  30. data/lib/timr/command/continue_command.rb +86 -0
  31. data/lib/timr/command/help_command.rb +137 -0
  32. data/lib/timr/command/log_command.rb +297 -0
  33. data/lib/timr/command/pause_command.rb +89 -0
  34. data/lib/timr/command/pop_command.rb +176 -0
  35. data/lib/timr/command/push_command.rb +141 -0
  36. data/lib/timr/command/report_command.rb +689 -0
  37. data/lib/timr/command/start_command.rb +172 -0
  38. data/lib/timr/command/status_command.rb +198 -0
  39. data/lib/timr/command/stop_command.rb +127 -0
  40. data/lib/timr/command/task_command.rb +318 -0
  41. data/lib/timr/command/track_command.rb +381 -0
  42. data/lib/timr/command/version_command.rb +18 -0
  43. data/lib/timr/duration.rb +159 -0
  44. data/lib/timr/exception/timr_error.rb +113 -0
  45. data/lib/timr/ext/time.rb +12 -0
  46. data/lib/timr/helper/datetime_helper.rb +128 -0
  47. data/lib/timr/helper/terminal_helper.rb +58 -0
  48. data/lib/timr/helper/translation_helper.rb +45 -0
  49. data/lib/timr/model/basic_model.rb +287 -0
  50. data/lib/timr/model/config.rb +48 -0
  51. data/lib/timr/model/foreign_id_db.rb +84 -0
  52. data/lib/timr/model/stack.rb +161 -0
  53. data/lib/timr/model/task.rb +1039 -0
  54. data/lib/timr/model/track.rb +589 -0
  55. data/lib/timr/progressbar.rb +41 -0
  56. data/lib/timr/simple_opt_parser.rb +230 -0
  57. data/lib/timr/status.rb +70 -0
  58. data/lib/timr/table.rb +88 -0
  59. data/lib/timr/timr.rb +500 -558
  60. data/lib/timr/version.rb +4 -15
  61. data/man/.gitignore +2 -0
  62. data/man/_footer +3 -0
  63. data/man/timr-continue.1 +48 -0
  64. data/man/timr-continue.1.ronn +39 -0
  65. data/man/timr-ftime.7 +77 -0
  66. data/man/timr-ftime.7.ronn +57 -0
  67. data/man/timr-log.1 +109 -0
  68. data/man/timr-log.1.ronn +87 -0
  69. data/man/timr-pause.1 +56 -0
  70. data/man/timr-pause.1.ronn +45 -0
  71. data/man/timr-pop.1 +66 -0
  72. data/man/timr-pop.1.ronn +53 -0
  73. data/man/timr-push.1 +25 -0
  74. data/man/timr-push.1.ronn +20 -0
  75. data/man/timr-report.1 +228 -0
  76. data/man/timr-report.1.ronn +193 -0
  77. data/man/timr-start.1 +100 -0
  78. data/man/timr-start.1.ronn +82 -0
  79. data/man/timr-status.1 +53 -0
  80. data/man/timr-status.1.ronn +42 -0
  81. data/man/timr-stop.1 +75 -0
  82. data/man/timr-stop.1.ronn +60 -0
  83. data/man/timr-task.1 +147 -0
  84. data/man/timr-task.1.ronn +115 -0
  85. data/man/timr-track.1 +109 -0
  86. data/man/timr-track.1.ronn +89 -0
  87. data/man/timr.1 +119 -0
  88. data/man/timr.1.ronn +68 -0
  89. data/timr.gemspec +18 -3
  90. data/timr.sublime-project +20 -1
  91. metadata +142 -23
  92. data/Makefile +0 -12
  93. data/Makefile.common +0 -56
  94. data/lib/timr/stack.rb +0 -81
  95. data/lib/timr/task.rb +0 -258
  96. data/lib/timr/track.rb +0 -167
  97. data/lib/timr/window.rb +0 -259
  98. data/lib/timr/window_help.rb +0 -41
  99. data/lib/timr/window_tasks.rb +0 -30
  100. data/lib/timr/window_test.rb +0 -20
  101. data/lib/timr/window_timeline.rb +0 -33
  102. data/tests/tc_stack.rb +0 -121
  103. data/tests/tc_task.rb +0 -190
  104. data/tests/tc_track.rb +0 -144
  105. data/tests/tc_window.rb +0 -428
  106. data/tests/ts_all.rb +0 -6
@@ -0,0 +1,141 @@
1
+
2
+ module TheFox
3
+ module Timr
4
+ module Command
5
+
6
+ # Push a new [Track](rdoc-ref:TheFox::Timr::Model::Track) to the [Stack](rdoc-ref:TheFox::Timr::Model::Stack).
7
+ #
8
+ # Man page: [timr-push(1)](../../../../man/timr-push.1.html)
9
+ class PushCommand < BasicCommand
10
+
11
+ include TheFox::Timr::Helper
12
+ include TheFox::Timr::Error
13
+
14
+ # Path to man page.
15
+ MAN_PATH = 'man/timr-push.1'
16
+
17
+ def initialize(argv = Array.new)
18
+ super()
19
+
20
+ @help_opt = false
21
+
22
+ @foreign_id_opt = nil
23
+ @name_opt = nil
24
+ @description_opt = nil
25
+ @estimation_opt = nil
26
+
27
+ @hourly_rate_opt = nil
28
+ @has_flat_rate_opt = nil
29
+
30
+ @date_opt = nil
31
+ @time_opt = nil
32
+ @message_opt = nil
33
+ @edit_opt = false
34
+
35
+ @task_id_opt = nil
36
+ @track_id_opt = nil
37
+ @id_opts = Array.new
38
+
39
+ loop_c = 0 # Limit the loop.
40
+ while loop_c < 1024 && argv.length > 0
41
+ loop_c += 1
42
+ arg = argv.shift
43
+
44
+ case arg
45
+ when '-h', '--help'
46
+ @help_opt = true
47
+
48
+ when '--id'
49
+ @foreign_id_opt = argv.shift.strip
50
+ when '-n', '--name'
51
+ @name_opt = argv.shift
52
+ when '--desc', '--description'
53
+ @description_opt = argv.shift
54
+ when '-e', '--est', '--estimation'
55
+ @estimation_opt = argv.shift
56
+
57
+ when '-r', '--hourly-rate'
58
+ @hourly_rate_opt = argv.shift
59
+ when '--fr', '--flat', '--flat-rate'
60
+ @has_flat_rate_opt = true
61
+
62
+ when '-d', '--date'
63
+ @date_opt = argv.shift
64
+ when '-t', '--time'
65
+ @time_opt = argv.shift
66
+ when '-m', '--message'
67
+ @message_opt = argv.shift
68
+ when '--edit'
69
+ @edit_opt = true
70
+
71
+ else
72
+ if arg[0] == '-'
73
+ raise PushCommandError, "Unknown argument '#{arg}'. See 'timr push --help'."
74
+ else
75
+ if @id_opts.length < 2
76
+ @id_opts << arg
77
+ else
78
+ raise PushCommandError, "Unknown argument '#{arg}'. See 'timr push --help'."
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ check_foreign_id(@foreign_id_opt)
85
+
86
+ if @id_opts.length
87
+ @task_id_opt, @track_id_opt = @id_opts
88
+ end
89
+ end
90
+
91
+ # See BasicCommand#run.
92
+ def run
93
+ if @help_opt
94
+ help
95
+ return
96
+ end
97
+
98
+ @timr = Timr.new(@cwd)
99
+
100
+ run_edit
101
+
102
+ options = {
103
+ :foreign_id => @foreign_id_opt,
104
+ :name => @name_opt,
105
+ :description => @description_opt,
106
+ :estimation => @estimation_opt,
107
+
108
+ :hourly_rate => @hourly_rate_opt,
109
+ :has_flat_rate => @has_flat_rate_opt,
110
+
111
+ :date => @date_opt,
112
+ :time => @time_opt,
113
+ :message => @message_opt,
114
+
115
+ :task_id => @task_id_opt,
116
+ :track_id => @track_id_opt,
117
+ }
118
+
119
+ track = @timr.push(options)
120
+ unless track
121
+ raise TrackError, 'Could not start a new Track.'
122
+ end
123
+
124
+ puts '--- PUSHED ---'
125
+ puts track.to_compact_str
126
+ puts @timr.stack
127
+ end
128
+
129
+ private
130
+
131
+ def help
132
+ start_command = StartCommand.new(['--help'])
133
+ start_command.run
134
+ start_command.shutdown
135
+ end
136
+
137
+ end # class PushCommand
138
+
139
+ end # module Command
140
+ end # module Timr
141
+ end # module TheFox
@@ -0,0 +1,689 @@
1
+
2
+ require 'csv'
3
+
4
+ module TheFox
5
+ module Timr
6
+ module Command
7
+
8
+ # This Command is very similar to LogCommand. By default it prints all [Tasks](rdoc-ref:TheFox::Timr::Model::Task) of the current month.
9
+ #
10
+ # Man page: [timr-report(1)](../../../../man/timr-report.1.html)
11
+ class ReportCommand < BasicCommand
12
+
13
+ include TheFox::Timr::Helper
14
+ include TheFox::Timr::Error
15
+
16
+ # Path to man page.
17
+ MAN_PATH = 'man/timr-report.1'
18
+
19
+ def initialize(argv = Array.new)
20
+ super()
21
+ # puts "argv '#{argv}'"
22
+
23
+ @help_opt = false
24
+ @tasks_opt = false
25
+ @tracks_opt = false
26
+ @from_opt = nil
27
+ @to_opt = nil
28
+ # @billed_opt = false
29
+ # @unbilled_opt = false
30
+ @csv_opt = nil
31
+ @force_opt = false
32
+
33
+ loop_c = 0 # Limit the loop.
34
+ while loop_c < 1024 && argv.length > 0
35
+ loop_c += 1
36
+ arg = argv.shift
37
+
38
+ case arg
39
+ when '-h', '--help'
40
+ @help_opt = true
41
+
42
+ when '-d', '--day'
43
+ @from_opt, @to_opt = DateTimeHelper.parse_day_argv(argv)
44
+ when '-m', '--month'
45
+ @from_opt, @to_opt = DateTimeHelper.parse_month_argv(argv)
46
+ when '-y', '--year'
47
+ @from_opt, @to_opt = DateTimeHelper.parse_year_argv(argv)
48
+
49
+ when '-a', '--all'
50
+ @from_opt = Time.parse('1970-01-01 00:00:00')
51
+ @to_opt = Time.parse('2099-12-31 23:59:59')
52
+
53
+ when '--tasks'
54
+ @tasks_opt = true
55
+ when '-t', '--tracks'
56
+ @tracks_opt = true
57
+
58
+ # when '--billed'
59
+ # @billed_opt = true
60
+ # when '--unbilled'
61
+ # @unbilled_opt = true
62
+
63
+ when '--csv'
64
+ @csv_opt = argv.shift
65
+ if !@csv_opt
66
+ raise ReportCommandError, 'Invalid value for --csv option.'
67
+ end
68
+ when '-f', '--force' # -f inofficial, maybe used for --file?
69
+ @force_opt = true
70
+
71
+ else
72
+ raise ReportCommandError, "Unknown argument '#{arg}'. See 'timr report --help'."
73
+ end
74
+ end
75
+
76
+ today = Date.today
77
+ unless @from_opt
78
+ @from_opt = Time.new(today.year, today.month, 1, 0, 0, 0)
79
+ end
80
+ unless @to_opt
81
+ month_end = Date.new(today.year, today.month, -1)
82
+ @to_opt = Time.new(today.year, today.month, month_end.day, 23, 59, 59)
83
+ end
84
+
85
+ # @billed_resolved_opt = nil
86
+ # if @billed_opt || @unbilled_opt
87
+ # if @billed_opt
88
+ # @billed_resolved_opt = true
89
+ # elsif @unbilled_opt
90
+ # @billed_resolved_opt = false
91
+ # end
92
+ # end
93
+
94
+ @filter_options = {
95
+ :format => '%y-%m-%d %H:%M',
96
+ :from => @from_opt,
97
+ :to => @to_opt,
98
+ # :billed => @billed_resolved_opt,
99
+ }
100
+ @csv_filter_options = {
101
+ :format => '%F %T %z',
102
+ :from => @from_opt,
103
+ :to => @to_opt,
104
+ }
105
+
106
+ if @csv_opt
107
+ if @csv_opt == '-'
108
+ # OK
109
+ else
110
+ @csv_opt = Pathname.new(@csv_opt).expand_path
111
+ end
112
+ end
113
+ end
114
+
115
+ # See BasicCommand#run.
116
+ def run
117
+ if @help_opt
118
+ help
119
+ return
120
+ end
121
+
122
+ @timr = Timr.new(@cwd)
123
+
124
+ if @csv_opt
125
+ if @tasks_opt
126
+ export_tasks_csv
127
+ elsif @tracks_opt
128
+ export_tracks_csv
129
+ else
130
+ export_tasks_csv
131
+ end
132
+ else
133
+ if @tasks_opt
134
+ print_task_table
135
+ elsif @tracks_opt
136
+ print_track_table
137
+ else
138
+ print_task_table
139
+ end
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def print_task_table
146
+ puts "From #{@from_opt.strftime('%F %T %z')}"
147
+ puts " To #{@to_opt.strftime('%F %T %z')}"
148
+ puts
149
+
150
+ table_options = {
151
+ :headings => [
152
+ {:format => '%3s', :label => '###'},
153
+ {:format => '%-14s', :label => 'START', :padding_left => ' ', :padding_right => ' '},
154
+ {:format => '%-14s', :label => 'END', :padding_left => ' ', :padding_right => ' '},
155
+ {:format => '%7s', :label => 'DUR', :padding_left => ' ', :padding_right => ' '},
156
+ {:format => '%7s', :label => 'UNB', :padding_left => ' ', :padding_right => ' '},
157
+ #{:format => '%3s', :label => 'TRC'},
158
+ {:format => '%-6s', :label => 'TASK', :padding_right => ' '},
159
+ ],
160
+ }
161
+ table = Table.new(table_options)
162
+
163
+ totals = {
164
+ :duration => Duration.new,
165
+ :unbilled_duration => Duration.new,
166
+ :task_c => 0,
167
+ :tracks_c => 0,
168
+
169
+ :begin_datetime => nil,
170
+ :end_datetime => nil,
171
+ }
172
+
173
+ table_has_rows = false
174
+
175
+ filtered_tasks.each do |task|
176
+ table_has_rows = true
177
+ totals[:task_c] += 1
178
+
179
+ tracks = task.tracks
180
+
181
+ # Task Tracks Count
182
+ tracks_c = tracks.count
183
+
184
+ # Global Tracks Count
185
+ totals[:tracks_c] += tracks_c
186
+
187
+ # Task Duration
188
+ duration = task.duration(@filter_options)
189
+ unbilled_duration = task.unbilled_duration(@filter_options)
190
+
191
+ # Global Duration Sum
192
+ totals[:duration] += duration
193
+ totals[:unbilled_duration] += unbilled_duration
194
+
195
+ # Task Begin DateTime
196
+ bdt = task.begin_datetime(@filter_options)
197
+
198
+ # Task End DateTime
199
+ edt = task.end_datetime(@filter_options)
200
+
201
+ # Determine First Begin DateTime of the table.
202
+ if bdt && (!totals[:begin_datetime] || bdt < totals[:begin_datetime])
203
+ totals[:begin_datetime] = bdt
204
+ end
205
+
206
+ # Determine Last End DateTime of the table.
207
+ if edt && (!totals[:end_datetime] || edt > totals[:end_datetime])
208
+ totals[:end_datetime] = edt
209
+ end
210
+
211
+ table << [
212
+ totals[:task_c],
213
+ task.begin_datetime_s(@filter_options),
214
+ task.end_datetime_s(@filter_options),
215
+ duration.to_human,
216
+ unbilled_duration.to_human,
217
+ # tracks_c,
218
+ '%s %s' % [task.short_id, task.foreign_id ? task.foreign_id : task.name(15)]
219
+ ]
220
+ end
221
+
222
+ table << []
223
+
224
+ totals[:begin_datetime_s] = totals[:begin_datetime] ? totals[:begin_datetime].localtime.strftime(@filter_options[:format]) : '---'
225
+
226
+ totals[:end_datetime_s] = totals[:end_datetime] ? totals[:end_datetime].localtime.strftime(@filter_options[:format]) : '---'
227
+
228
+ # Add totals to the bottom.
229
+ table << [
230
+ nil, # task_c
231
+ totals[:begin_datetime_s],
232
+ totals[:end_datetime_s],
233
+ totals[:duration].to_human, # duration
234
+ totals[:unbilled_duration].to_human, # duration
235
+ # totals[:tracks_c],
236
+ 'TOTAL', # task
237
+ ]
238
+
239
+ if table_has_rows
240
+ puts table
241
+ else
242
+ puts 'No tasks found.'
243
+ end
244
+ end
245
+
246
+ def print_track_table
247
+ puts "From #{@from_opt.strftime('%F %T %z')}"
248
+ puts " To #{@to_opt.strftime('%F %T %z')}"
249
+ puts
250
+
251
+ table_options = {
252
+ :headings => [
253
+ {:format => '%3s', :label => '###'},
254
+ {:format => '%-14s', :label => 'START', :padding_left => ' ', :padding_right => ' '},
255
+ {:format => '%-14s', :label => 'END', :padding_left => ' ', :padding_right => ' '},
256
+ {:format => '%7s', :label => 'DUR', :padding_left => ' ', :padding_right => ' '},
257
+ {:format => '%-6s', :label => 'TASK', :padding_right => ' '},
258
+ {:format => '%-6s', :label => 'TRACK', :padding_right => ' '},
259
+ ],
260
+ }
261
+ table = Table.new(table_options)
262
+
263
+ totals = {
264
+ :duration => Duration.new,
265
+ :tracks_c => 0,
266
+
267
+ :begin_datetime => nil,
268
+ :end_datetime => nil,
269
+ }
270
+
271
+ table_has_rows = false
272
+ @timr.tracks(@filter_options).each do |track_id, track|
273
+ table_has_rows = true
274
+ totals[:tracks_c] += 1
275
+
276
+ # Track Duration
277
+ duration = track.duration(@filter_options)
278
+
279
+ # Global Duration Sum
280
+ totals[:duration] += duration
281
+
282
+ # Track Begin DateTime
283
+ bdt = track.begin_datetime(@filter_options)
284
+
285
+ # Track End DateTime
286
+ edt = track.end_datetime(@filter_options)
287
+
288
+ # Determine First Begin DateTime of the table.
289
+ if bdt && (!totals[:begin_datetime] || bdt < totals[:begin_datetime])
290
+ totals[:begin_datetime] = bdt
291
+ end
292
+
293
+ # Determine Last End DateTime of the table.
294
+ if edt && (!totals[:end_datetime] || edt > totals[:end_datetime])
295
+ totals[:end_datetime] = edt
296
+ end
297
+
298
+ # Get Task from Track.
299
+ task = track.task
300
+
301
+ table << [
302
+ totals[:tracks_c],
303
+ track.begin_datetime_s(@filter_options),
304
+ track.end_datetime_s(@filter_options),
305
+ duration.to_human,
306
+ '%s' % [task.foreign_id ? task.foreign_id : task.short_id],
307
+ '%s %s' % [track.short_id, track.title(15)],
308
+ ]
309
+ end
310
+
311
+ table << []
312
+
313
+ totals[:begin_datetime_s] = totals[:begin_datetime] ? totals[:begin_datetime].localtime.strftime(@filter_options[:format]) : '---'
314
+
315
+ totals[:end_datetime_s] = totals[:end_datetime] ? totals[:end_datetime].localtime.strftime(@filter_options[:format]) : '---'
316
+
317
+ # Add totals to the bottom.
318
+ table << [
319
+ nil, # task_c
320
+ totals[:begin_datetime_s],
321
+ totals[:end_datetime_s],
322
+ totals[:duration].to_human, # duration
323
+ 'TOTAL', # task
324
+ nil, # track
325
+ ]
326
+
327
+ if table_has_rows
328
+ puts table
329
+ else
330
+ puts 'No tracks found.'
331
+ end
332
+ end
333
+
334
+ def export_tasks_csv
335
+ if @csv_opt == '-'
336
+ csv_file_handle = STDOUT
337
+ else
338
+ if @csv_opt.exist? && !@force_opt
339
+ raise ReportCommandError, "File '#{@csv_opt}' already exist. Use --force to overwrite it."
340
+ end
341
+ #csv_file_handle = @csv_opt.to_s
342
+ csv_file_handle = File.open(@csv_opt, 'wb')
343
+ end
344
+
345
+ totals = {
346
+ :row_c => 0,
347
+
348
+ :duration => Duration.new,
349
+ :estimation => Duration.new,
350
+ :remaining_time => Duration.new,
351
+ :billed_duration => Duration.new,
352
+ :unbilled_duration => Duration.new,
353
+
354
+ :tracks_c => 0,
355
+ :billed_tracks_c => 0,
356
+ :unbilled_tracks_c => 0,
357
+
358
+ :begin_datetime => nil,
359
+ :end_datetime => nil,
360
+ }
361
+
362
+ csv_options = {
363
+ :headers => [
364
+ 'ROW_NO',
365
+
366
+ 'TASK_ID',
367
+ 'TASK_FOREIGN_ID',
368
+ 'TASK_NAME',
369
+
370
+ 'TASK_BEGIN_DATETIME',
371
+ 'TASK_END_DATETIME',
372
+
373
+ 'TASK_DURATION_HUMAN',
374
+ 'TASK_DURATION_SECONDS',
375
+
376
+ 'TASK_ESTIMATION_HUMAN',
377
+ 'TASK_ESTIMATION_SECONDS',
378
+ 'TASK_REMAINING_TIME_HUMAN',
379
+ 'TASK_REMAINING_TIME_SECONDS',
380
+
381
+ 'TASK_BILLED_DURATION_HUMAN',
382
+ 'TASK_BILLED_DURATION_SECONDS',
383
+ 'TASK_UNBILLED_DURATION_HUMAN',
384
+ 'TASK_UNBILLED_DURATION_SECONDS',
385
+
386
+ 'TASK_TRACK_COUNT',
387
+ 'TASK_BILLED_TRACK_COUNT',
388
+ 'TASK_UNBILLED_TRACK_COUNT',
389
+
390
+ # 'TASK_SHORTEST_TRACK_DURATION_HUMAN', # @TODO shortest longest track duration
391
+ # 'TASK_SHORTEST_TRACK_DURATION_SECONDS',
392
+ # 'TASK_LONGEST_TRACK_DURATION_HUMAN',
393
+ # 'TASK_LONGEST_TRACK_DURATION_SECONDS',
394
+ ],
395
+ :write_headers => true,
396
+ :skip_blanks => true,
397
+ #:force_quotes => true,
398
+ }
399
+ csv = CSV.new(csv_file_handle, csv_options)
400
+ filtered_tasks.each do |task|
401
+ totals[:row_c] += 1
402
+
403
+ tracks = task.tracks
404
+
405
+ # Task Tracks Count
406
+ tracks_c = tracks.count
407
+ billed_tracks_c = task.tracks({:billed => true}).count
408
+ unbilled_tracks_c = task.tracks({:billed => false}).count
409
+
410
+ # Global Tracks Count
411
+ totals[:tracks_c] += tracks_c
412
+ totals[:billed_tracks_c] += billed_tracks_c
413
+ totals[:unbilled_tracks_c] += unbilled_tracks_c
414
+
415
+ # Task Duration
416
+ duration = task.duration(@csv_filter_options)
417
+ estimation = task.estimation
418
+ remaining_time = task.remaining_time
419
+ billed_duration = task.billed_duration(@csv_filter_options)
420
+ unbilled_duration = task.unbilled_duration(@csv_filter_options)
421
+
422
+ # Global Duration Sum
423
+ if duration
424
+ totals[:duration] += duration
425
+ end
426
+ if estimation
427
+ totals[:estimation] += estimation
428
+ end
429
+ if remaining_time
430
+ totals[:remaining_time] += remaining_time
431
+ end
432
+ if billed_duration
433
+ totals[:billed_duration] += billed_duration
434
+ end
435
+ if unbilled_duration
436
+ totals[:unbilled_duration] += unbilled_duration
437
+ end
438
+
439
+ # Task Begin DateTime
440
+ bdt = task.begin_datetime(@csv_filter_options)
441
+
442
+ # Task End DateTime
443
+ edt = task.end_datetime(@csv_filter_options)
444
+
445
+ # Determine First Begin DateTime of the table.
446
+ if bdt && (!totals[:begin_datetime] || bdt < totals[:begin_datetime])
447
+ totals[:begin_datetime] = bdt
448
+ end
449
+
450
+ # Determine Last End DateTime of the table.
451
+ if edt && (!totals[:end_datetime] || edt > totals[:end_datetime])
452
+ totals[:end_datetime] = edt
453
+ end
454
+
455
+ csv << [
456
+ totals[:row_c],
457
+
458
+ task.id,
459
+ task.foreign_id,
460
+ task.name,
461
+
462
+ task.begin_datetime_s(@csv_filter_options),
463
+ task.end_datetime_s(@csv_filter_options),
464
+
465
+ duration ? duration.to_human : '---',
466
+ duration ? duration.to_i : 0,
467
+ estimation ? estimation.to_human : '---',
468
+ estimation ? estimation.to_i : 0,
469
+ remaining_time ? remaining_time.to_human : '---',
470
+ remaining_time ? remaining_time.to_i : 0,
471
+ billed_duration ? billed_duration.to_human : '---',
472
+ billed_duration ? billed_duration.to_i : 0,
473
+ unbilled_duration ? unbilled_duration.to_human : '---',
474
+ unbilled_duration ? unbilled_duration.to_i : 0,
475
+
476
+ tracks_c,
477
+ billed_tracks_c,
478
+ unbilled_tracks_c,
479
+ ]
480
+ end
481
+
482
+ totals[:begin_datetime_s] = totals[:begin_datetime] ? totals[:begin_datetime].localtime.strftime(@csv_filter_options[:format]) : '---'
483
+
484
+ totals[:end_datetime_s] = totals[:end_datetime] ? totals[:end_datetime].localtime.strftime(@csv_filter_options[:format]) : '---'
485
+
486
+ totals[:row_c] += 1
487
+ csv << [
488
+ totals[:row_c],
489
+
490
+ 'TOTAL',
491
+ nil,
492
+ nil,
493
+
494
+ totals[:begin_datetime_s],
495
+ totals[:end_datetime_s],
496
+
497
+ totals[:duration] ? totals[:duration].to_human : '---',
498
+ totals[:duration] ? totals[:duration].to_i : 0,
499
+ totals[:estimation] ? totals[:estimation].to_human : '---',
500
+ totals[:estimation] ? totals[:estimation].to_i : 0,
501
+ totals[:remaining_time] ? totals[:remaining_time].to_human : '---',
502
+ totals[:remaining_time] ? totals[:remaining_time].to_i : 0,
503
+ totals[:billed_duration] ? totals[:billed_duration].to_human : '---',
504
+ totals[:billed_duration] ? totals[:billed_duration].to_i : 0,
505
+ totals[:unbilled_duration] ? totals[:unbilled_duration].to_human : '---',
506
+ totals[:unbilled_duration] ? totals[:unbilled_duration].to_i : 0,
507
+
508
+ totals[:tracks_c],
509
+ totals[:billed_tracks_c],
510
+ totals[:unbilled_tracks_c],
511
+ ]
512
+
513
+ csv.close
514
+ end
515
+
516
+ def export_tracks_csv
517
+ if @csv_opt == '-'
518
+ csv_file_handle = STDOUT
519
+ else
520
+ if @csv_opt.exist? && !@force_opt
521
+ raise ReportCommandError, "File '#{@csv_opt}' already exist. Use --force to overwrite it."
522
+ end
523
+ #csv_file_handle = @csv_opt.to_s
524
+ csv_file_handle = File.open(@csv_opt, 'wb')
525
+ end
526
+
527
+ totals = {
528
+ :duration => Duration.new,
529
+ :billed_duration => Duration.new,
530
+ :unbilled_duration => Duration.new,
531
+ :row_c => 0,
532
+
533
+ :begin_datetime => nil,
534
+ :end_datetime => nil,
535
+ }
536
+
537
+ csv_options = {
538
+ :headers => [
539
+ 'ROW_NO',
540
+
541
+ 'TASK_ID',
542
+ 'TASK_FOREIGN_ID',
543
+ 'TASK_NAME',
544
+
545
+ 'TRACK_ID',
546
+ 'TRACK_TITLE',
547
+
548
+ 'TRACK_BEGIN_DATETIME',
549
+ 'TRACK_END_DATETIME',
550
+
551
+ 'TRACK_DURATION_HUMAN',
552
+ 'TRACK_DURATION_SECONDS',
553
+ 'TRACK_BILLED_DURATION_HUMAN',
554
+ 'TRACK_BILLED_DURATION_SECONDS',
555
+ 'TRACK_UNBILLED_DURATION_HUMAN',
556
+ 'TRACK_UNBILLED_DURATION_SECONDS',
557
+ 'TRACK_IS_BILLED',
558
+ ],
559
+ :write_headers => true,
560
+ :skip_blanks => true,
561
+ #:force_quotes => true,
562
+ }
563
+ csv = CSV.new(csv_file_handle, csv_options)
564
+ @timr.tracks(@csv_filter_options).each do |track_id, track|
565
+ totals[:row_c] += 1
566
+
567
+ # Track Duration
568
+ duration = track.duration(@csv_filter_options)
569
+ billed_duration = track.billed_duration(@csv_filter_options)
570
+ unbilled_duration = track.unbilled_duration(@csv_filter_options)
571
+
572
+ # Global Duration Sum
573
+ if duration
574
+ totals[:duration] += duration
575
+ end
576
+ if billed_duration
577
+ totals[:billed_duration] += billed_duration
578
+ end
579
+ if unbilled_duration
580
+ totals[:unbilled_duration] += unbilled_duration
581
+ end
582
+
583
+ # Get Task from Track.
584
+ task = track.task
585
+
586
+ # Track Begin DateTime
587
+ bdt = track.begin_datetime(@csv_filter_options)
588
+
589
+ # Track End DateTime
590
+ edt = track.end_datetime(@csv_filter_options)
591
+
592
+ # Determine First Begin DateTime of the table.
593
+ if bdt && (!totals[:begin_datetime] || bdt < totals[:begin_datetime])
594
+ totals[:begin_datetime] = bdt
595
+ end
596
+
597
+ # Determine Last End DateTime of the table.
598
+ if edt && (!totals[:end_datetime] || edt > totals[:end_datetime])
599
+ totals[:end_datetime] = edt
600
+ end
601
+
602
+ csv << [
603
+ totals[:row_c],
604
+
605
+ task.id,
606
+ task.foreign_id,
607
+ task.name,
608
+
609
+ track.id,
610
+ track.title,
611
+ track.begin_datetime_s(@csv_filter_options),
612
+ track.end_datetime_s(@csv_filter_options),
613
+
614
+ duration ? duration.to_human : '---',
615
+ duration ? duration.to_i : 0,
616
+ billed_duration ? billed_duration.to_human : '---',
617
+ billed_duration ? billed_duration.to_i : 0,
618
+ unbilled_duration ? unbilled_duration.to_human : '---',
619
+ unbilled_duration ? unbilled_duration.to_i : 0,
620
+
621
+ track.is_billed.to_i,
622
+ ]
623
+ end
624
+
625
+ totals[:begin_datetime_s] = totals[:begin_datetime] ? totals[:begin_datetime].localtime.strftime(@csv_filter_options[:format]) : '---'
626
+
627
+ totals[:end_datetime_s] = totals[:end_datetime] ? totals[:end_datetime].localtime.strftime(@csv_filter_options[:format]) : '---'
628
+
629
+ totals[:row_c] += 1
630
+ csv << [
631
+ totals[:row_c],
632
+ 'TOTAL',
633
+
634
+ nil,
635
+ nil,
636
+ nil,
637
+
638
+ nil,
639
+
640
+ totals[:begin_datetime_s],
641
+ totals[:end_datetime_s],
642
+
643
+ totals[:duration] ? totals[:duration].to_human : '---',
644
+ totals[:duration] ? totals[:duration].to_i : 0,
645
+ totals[:billed_duration] ? totals[:billed_duration].to_human : '---',
646
+ totals[:billed_duration] ? totals[:billed_duration].to_i : 0,
647
+ totals[:unbilled_duration] ? totals[:unbilled_duration].to_human : '---',
648
+ totals[:unbilled_duration] ? totals[:unbilled_duration].to_i : 0,
649
+ ]
650
+
651
+ csv.close
652
+ end
653
+
654
+ def filtered_tasks
655
+ # Get Tracks.
656
+ tracks = @timr.tracks(@filter_options)
657
+
658
+ # Convert Tracks to Tasks. Convert the Array to a Set removes all duplicates.
659
+ tracks.map{ |track_id, track| track.task }.to_set
660
+ end
661
+
662
+ def help
663
+ puts 'usage: timr report '
664
+ puts ' or: timr report [-h|--help]'
665
+ puts
666
+ puts 'Options'
667
+ puts ' -d, --day <date> A single day from 00:00 to 23:59.'
668
+ puts ' -m, --month <[YYYY-]MM> A single month from 01 to 31.'
669
+ puts ' -y, --year [<YYYY>] A single year from 01-01 to 12-31.'
670
+ puts ' -a, --all All.'
671
+ puts ' --tasks Export Tasks (default)'
672
+ puts ' --tracks Export Tracks'
673
+ # puts ' --billed Filter only Tasks/Tracks which are billed.'
674
+ # puts ' --unbilled Filter only Tasks/Tracks which are not billed.'
675
+ puts " --csv <path> Export as CSV file. Use '--csv -' to use STDOUT."
676
+ puts " --force Force overwrite file."
677
+ puts
678
+ puts 'For column descriptions see man page:'
679
+ puts "'timr help report'"
680
+ puts
681
+ HelpCommand.print_datetime_help
682
+ puts
683
+ end
684
+
685
+ end # class TrackCommand
686
+
687
+ end # module Command
688
+ end # module Timr
689
+ end # module TheFox