clockout 0.3.2 → 0.4

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 (5) hide show
  1. data/bin/clock +3 -2
  2. data/lib/clockout.rb +60 -211
  3. data/lib/printer.rb +141 -0
  4. data/lib/record.rb +54 -0
  5. metadata +21 -3
data/bin/clock CHANGED
@@ -177,10 +177,11 @@ else
177
177
  end
178
178
 
179
179
  clock = Clockout.new(path, user)
180
+ printer = Printer.new(clock)
180
181
 
181
182
  if (opts[:estimations])
182
- clock.print_estimations
183
+ printer.print_estimations
183
184
  else
184
- clock.print_chart(opts[:condensed])
185
+ printer.print_chart(opts[:condensed])
185
186
  end
186
187
  end
data/lib/clockout.rb CHANGED
@@ -1,131 +1,45 @@
1
1
  require 'grit'
2
- require 'time'
3
2
  require 'yaml'
4
3
 
4
+ require 'printer'
5
+ require 'record'
6
+
5
7
  COLS = 80
6
8
  DAY_FORMAT = '%B %e, %Y'
7
9
 
8
- class Commit
9
- attr_accessor :message, :minutes, :date, :diffs, :sha, :clocked_in, :clocked_out, :addition, :overriden, :author, :estimated
10
- def initialize(commit = nil)
11
- @addition = 0
12
- if commit
13
- @author = commit.author.email
14
- @date = commit.committed_date
15
- @message = commit.message.gsub("\n",' ')
16
- @sha = commit.id
17
- end
18
- end
19
- end
20
-
21
- class Clock
22
- attr_accessor :in, :out, :date, :author
23
- def initialize(type, date, auth)
24
- @in = (type == :in)
25
- @out = (type == :out)
26
- @date = date
27
- @author = auth
28
- end
29
- end
30
-
31
- def puts_error(str)
32
- puts "Error: ".red + str
33
- end
10
+ class Clockout
11
+ attr_accessor :blocks, :time_per_day
34
12
 
35
- def align(strings, cols = COLS, sep = " ")
36
- ret = ""
37
- size = 0
38
- strings.each do |string, method|
39
- ultimate = (string == strings.keys[-1])
40
- penultimate = (string == strings.keys[-2])
41
-
42
- out = string
43
- out += " " unless (ultimate || penultimate)
44
-
45
- if ultimate
46
- # Add seperator
47
- cols_left = cols - size - out.length
48
- ret += sep*cols_left if cols_left > 0
49
- elsif penultimate
50
- last = strings.keys.last.length
51
- max_len = cols - size - last - 1
52
- if string.length > max_len
53
- # Truncate
54
- out = string[0..max_len-5].strip + "... "
55
- end
13
+ def commits_to_records(grit_commits)
14
+ my_files = eval($opts[:my_files])
15
+ not_my_files = eval($opts[:not_my_files] || "")
16
+ grit_commits.map do |commit|
17
+ c = Commit.new(commit)
18
+ c.calculate_diffs(my_files, not_my_files)
19
+ c
56
20
  end
57
-
58
- # Apply color & print
59
- ret += method.to_proc.call(out)
60
-
61
- size += out.length
62
21
  end
63
22
 
64
- ret
65
- end
66
-
67
- class String
68
- def colorize(color)
69
- "\e[0;#{color};49m#{self}\e[0m"
70
- end
71
-
72
- def red() colorize(31) end
73
- def yellow() colorize(33) end
74
- def magenta() colorize(35) end
75
- def light_blue() colorize(94) end
76
- end
77
-
78
- class Numeric
79
- def as_time(type = nil, min_s = " min", hr_s = " hrs")
80
- type = (self < 60) ? :minutes : :hours if !type
81
- if type == :minutes
82
- "#{self.round(0)}#{min_s}"
83
- else
84
- "#{(self/60.0).round(2)}#{hr_s}"
23
+ def clocks_to_records(clocks, in_out)
24
+ clocks.map do |c|
25
+ Clock.new(in_out, c.first[0], c.first[1])
85
26
  end
86
27
  end
87
- end
88
28
 
89
- class Clockout
90
- def diffs(commit)
91
- plus, minus = 0, 0
92
-
93
- commit.stats.to_diffstat.each do |diff_stat|
94
- my_files = $opts[:my_files]
95
- not_my_files = $opts[:not_my_files]
96
- should_include = (diff_stat.filename =~ eval(my_files))
97
- should_ignore = not_my_files && (diff_stat.filename =~ eval(not_my_files))
98
- if should_include && !should_ignore
99
- plus += diff_stat.additions
100
- minus += diff_stat.deletions
29
+ def try_overriding_record(record)
30
+ overrides = $opts[:overrides]
31
+ overrides.each do |k, v|
32
+ if record.sha.start_with? k
33
+ record.minutes = v
34
+ record.overriden = true
35
+ return true
101
36
  end
102
- end
37
+ end if overrides
103
38
 
104
- # Weight deletions half as much, since they are typically
105
- # faster to do & also are 1:1 with additions when changing a line
106
- plus+minus/2
39
+ false
107
40
  end
108
41
 
109
- def prepare_data(commits_in, author)
110
- clockins = $opts[:in] || {}
111
- clockouts = $opts[:out] || {}
112
-
113
- # Convert clock-in/-outs into Clock objs & commits into Commit objs
114
- clocks = []
115
- clockins.each { |c| clocks << Clock.new(:in, c.first[0], c.first[1]) }
116
- clockouts.each { |c| clocks << Clock.new(:out, c.first[0], c.first[1]) }
117
- commits_in.map! do |commit|
118
- c = Commit.new(commit)
119
- c.diffs = diffs(commit)
120
- c
121
- end
122
-
123
- # Merge & sort everything by date
124
- data = (commits_in + clocks).sort { |a,b| a.date <=> b.date }
125
-
126
- # If author is specified, delete everything not by that author
127
- data.delete_if { |c| c.author != author } if author
128
-
42
+ def run(data)
129
43
  blocks = []
130
44
  total_diffs, total_mins = 0, 0
131
45
 
@@ -193,25 +107,18 @@ class Clockout
193
107
  end
194
108
  else
195
109
  # See if this commit was overriden in the config file
196
- overrides = $opts[:overrides]
197
- overrides.each do |k, v|
198
- if curr_c.sha.start_with? k
199
- curr_c.minutes = v
200
- curr_c.overriden = true
201
- break
202
- end
203
- end if overrides
204
-
205
- # If we're ignoring initial & it's initial, set minutes to 0
206
- if $opts[:ignore_initial] && !prev_c
207
- curr_c.minutes = 0
208
- elsif !curr_c.overriden && prev_c
209
- curr_c.clocked_in = true if prev_c.class == Clock && prev_c.in
210
- # If it added successfully into a block (or was clocked in), we can calculate based on last commit
211
- if add_commit.call(curr_c) || curr_c.clocked_in
212
- curr_c.minutes = (curr_c.date - prev_c.date)/60.0 # clock or commit, doesn't matter
110
+ if !try_overriding_record(curr_c)
111
+ # Otherwise, if we're ignoring initial & it's initial, set minutes to 0
112
+ if $opts[:ignore_initial] && !prev_c
113
+ curr_c.minutes = 0
114
+ else
115
+ curr_c.clocked_in = true if prev_c && prev_c.class == Clock && prev_c.in
116
+ # If it added successfully into a block (or was clocked in), we can calculate based on last commit
117
+ if add_commit.call(curr_c) || curr_c.clocked_in
118
+ curr_c.minutes = (curr_c.date - prev_c.date)/60.0 # clock or commit, doesn't matter
119
+ end
120
+ # Otherwise, we'll do an estimation later, once we have more data
213
121
  end
214
- # Otherwise, we'll do an estimation later, once we have more data
215
122
  end
216
123
 
217
124
  if curr_c.minutes
@@ -246,82 +153,21 @@ class Clockout
246
153
  blocks
247
154
  end
248
155
 
249
- def print_chart(condensed)
250
- cols = condensed ? 30 : COLS
251
- total_sum = 0
252
- current_day = nil
253
- @blocks.each do |block|
254
- date = block.first.date.strftime(DAY_FORMAT)
255
- if date != current_day
256
- puts if (!condensed && current_day)
257
-
258
- current_day = date
259
-
260
- sum = @time_per_day[date]
261
- total_sum += sum
262
-
263
- puts align({date => :magenta, sum.as_time(:hours) => :red}, cols, ".".magenta)
264
- end
265
-
266
- print_timeline(block) if (!condensed)
267
- end
268
-
269
- puts align({"-"*10 => :magenta}, cols)
270
- puts align({total_sum.as_time(:hours) => :red}, cols)
271
- end
272
-
273
- def print_timeline(block)
274
- # subtract from the time it took for first commit
275
- time = (block.first.date - block.first.minutes*60).strftime('%l:%M %p')+": "
276
- print time.yellow
277
-
278
- char_count = time.length
279
-
280
- block.each do |commit|
281
- c_mins = commit.minutes.as_time(nil, "m", "h")
282
- c_mins = "*#{c_mins}" if commit.clocked_in
283
- c_mins += "*" if commit.clocked_out
284
-
285
- seperator = " | "
286
-
287
- add = c_mins.length+seperator.length
288
- if char_count + add > COLS-5
289
- puts
290
- char_count = time.length # indent by the length of the time label on left
291
- print " "*char_count
292
- end
293
-
294
- char_count += add
295
-
296
- # Blue for clockin/out commits
297
- print c_mins+(commit.message ? seperator.red : seperator.light_blue)
298
- end
299
- puts
300
- end
301
-
302
- def print_estimations
303
- sum = 0
304
- estimations = []
305
- @blocks.each do |block|
306
- estimations << block.first if block.first.estimated
307
- end
156
+ def prepare_blocks(commits_in, author)
157
+ clockins = $opts[:in] || {}
158
+ clockouts = $opts[:out] || {}
308
159
 
309
- if estimations.empty?
310
- puts "No estimations made."
311
- else
312
- estimations.each do |c|
313
- date = c.date.strftime('%b %e')+":"
314
- sha = c.sha[0..7]
315
- time = c.minutes.as_time
160
+ # Convert clock-in/-outs into Clock objs & commits into Commit objs
161
+ clocks = clocks_to_records(clockins, :in) + clocks_to_records(clockouts, :out)
162
+ commits = commits_to_records(commits_in)
316
163
 
317
- puts align({date => :yellow, sha => :red, c.message => :to_s, time => :light_blue})
164
+ # Merge & sort everything by date
165
+ data = (commits + clocks).sort { |a,b| a.date <=> b.date }
318
166
 
319
- sum += c.minutes
320
- end
167
+ # If author is specified, delete everything not by that author
168
+ data.delete_if { |c| c.author != author } if author
321
169
 
322
- puts align({"-"*10 => :light_blue})
323
- puts align({sum.as_time(:hours) => :light_blue})
324
- end
170
+ @blocks = run(data)
325
171
  end
326
172
 
327
173
  def self.get_repo(path, original_path = nil)
@@ -372,22 +218,25 @@ class Clockout
372
218
  root_path
373
219
  end
374
220
 
375
- def initialize(path, author = nil)
376
- repo, root_path = Clockout.get_repo(path) || exit
221
+ def initialize(path = nil, author = nil)
222
+ @time_per_day = Hash.new(0)
377
223
 
378
224
  # Default options
379
225
  $opts = {time_cutoff:120, my_files:"/.*/", estimation_factor:1.0}
380
226
 
381
- # Parse config options
382
- clock_opts = Clockout.parse_clockfile(Clockout.clock_path(root_path))
227
+ if path
228
+ repo, root_path = Clockout.get_repo(path) || exit
383
229
 
384
- # Merge with config override options
385
- $opts.merge!(clock_opts) if clock_opts
230
+ # Parse config options
231
+ clock_opts = Clockout.parse_clockfile(Clockout.clock_path(root_path))
386
232
 
387
- commits = repo.commits('master', 500)
388
- commits.reverse!
233
+ # Merge with config override options
234
+ $opts.merge!(clock_opts) if clock_opts
389
235
 
390
- @time_per_day = Hash.new(0)
391
- @blocks = prepare_data(commits, author)
236
+ commits = repo.commits('master', 500)
237
+ commits.reverse!
238
+
239
+ prepare_blocks(commits, author)
240
+ end
392
241
  end
393
242
  end
data/lib/printer.rb ADDED
@@ -0,0 +1,141 @@
1
+ def puts_error(str)
2
+ puts "Error: ".red + str
3
+ end
4
+
5
+ class String
6
+ def colorize(color)
7
+ "\e[0;#{color};49m#{self}\e[0m"
8
+ end
9
+
10
+ def red() colorize(31) end
11
+ def yellow() colorize(33) end
12
+ def magenta() colorize(35) end
13
+ def light_blue() colorize(94) end
14
+ end
15
+
16
+ class Numeric
17
+ def as_time(type = nil, min_s = " min", hr_s = " hrs")
18
+ type = (self < 60) ? :minutes : :hours if !type
19
+ if type == :minutes
20
+ "#{self.round(0)}#{min_s}"
21
+ else
22
+ "#{(self/60.0).round(2)}#{hr_s}"
23
+ end
24
+ end
25
+ end
26
+
27
+ class Printer
28
+ def initialize(clockout)
29
+ @clockout = clockout
30
+ end
31
+
32
+ def align(strings, cols = COLS, sep = " ")
33
+ ret = ""
34
+ size = 0
35
+ strings.each do |string, method|
36
+ ultimate = (string == strings.keys[-1])
37
+ penultimate = (string == strings.keys[-2])
38
+
39
+ out = string
40
+ out += " " unless (ultimate || penultimate)
41
+
42
+ if ultimate
43
+ # Add seperator
44
+ cols_left = cols - size - out.length
45
+ ret += sep*cols_left if cols_left > 0
46
+ elsif penultimate
47
+ last = strings.keys.last.length
48
+ max_len = cols - size - last - 1
49
+ if string.length > max_len
50
+ # Truncate
51
+ out = string[0..max_len-5].strip + "... "
52
+ end
53
+ end
54
+
55
+ # Apply color & print
56
+ ret += method.to_proc.call(out)
57
+
58
+ size += out.length
59
+ end
60
+
61
+ ret
62
+ end
63
+
64
+ def print_chart(condensed)
65
+ cols = condensed ? 30 : COLS
66
+ total_sum = 0
67
+ current_day = nil
68
+ @clockout.blocks.each do |block|
69
+ date = block.first.date.strftime(DAY_FORMAT)
70
+ if date != current_day
71
+ puts if (!condensed && current_day)
72
+
73
+ current_day = date
74
+
75
+ sum = @clockout.time_per_day[date]
76
+ total_sum += sum
77
+
78
+ puts align({date => :magenta, sum.as_time(:hours) => :red}, cols, ".".magenta)
79
+ end
80
+
81
+ print_timeline(block) if (!condensed)
82
+ end
83
+
84
+ puts align({"-"*10 => :magenta}, cols)
85
+ puts align({total_sum.as_time(:hours) => :red}, cols)
86
+ end
87
+
88
+ def print_timeline(block)
89
+ # subtract from the time it took for first commit
90
+ time = (block.first.date - block.first.minutes*60).strftime('%l:%M %p')+": "
91
+ print time.yellow
92
+
93
+ char_count = time.length
94
+
95
+ block.each do |commit|
96
+ c_mins = commit.minutes.as_time(nil, "m", "h")
97
+ c_mins = "*#{c_mins}" if commit.clocked_in
98
+ c_mins += "*" if commit.clocked_out
99
+
100
+ seperator = " | "
101
+
102
+ add = c_mins.length+seperator.length
103
+ if char_count + add > COLS-5
104
+ puts
105
+ char_count = time.length # indent by the length of the time label on left
106
+ print " "*char_count
107
+ end
108
+
109
+ char_count += add
110
+
111
+ # Blue for clockin/out commits
112
+ print c_mins+(commit.message ? seperator.red : seperator.light_blue)
113
+ end
114
+ puts
115
+ end
116
+
117
+ def print_estimations
118
+ sum = 0
119
+ estimations = []
120
+ @clockout.blocks.each do |block|
121
+ estimations << block.first if block.first.estimated
122
+ end
123
+
124
+ if estimations.empty?
125
+ puts "No estimations made."
126
+ else
127
+ estimations.each do |c|
128
+ date = c.date.strftime('%b %e')+":"
129
+ sha = c.sha[0..7]
130
+ time = c.minutes.as_time
131
+
132
+ puts align({date => :yellow, sha => :red, c.message => :to_s, time => :light_blue})
133
+
134
+ sum += c.minutes
135
+ end
136
+
137
+ puts align({"-"*10 => :light_blue})
138
+ puts align({sum.as_time(:hours) => :light_blue})
139
+ end
140
+ end
141
+ end
data/lib/record.rb ADDED
@@ -0,0 +1,54 @@
1
+ class Record
2
+ attr_accessor :date, :author
3
+ end
4
+
5
+ class Commit < Record
6
+ # From Grit::Commit object
7
+ attr_accessor :message, :stats, :diffs, :sha
8
+ # Time calc
9
+ attr_accessor :minutes, :addition, :overriden, :estimated
10
+ # Whether it's been padded by a clock in/out
11
+ attr_accessor :clocked_in, :clocked_out
12
+
13
+ def initialize(commit = nil, date = nil)
14
+ @addition = 0
15
+ @date = date
16
+ if commit
17
+ @author = commit.author.email
18
+ @date = commit.committed_date
19
+ @message = commit.message.gsub("\n",' ')
20
+ @sha = commit.id
21
+ @stats = commit.stats
22
+ end
23
+ end
24
+
25
+ def calculate_diffs(my_files, not_my_files)
26
+ return @diffs if @diffs
27
+
28
+ plus, minus = 0, 0
29
+
30
+ @stats.to_diffstat.each do |diff_stat|
31
+ should_include = (diff_stat.filename =~ my_files)
32
+ should_ignore = not_my_files && (diff_stat.filename =~ not_my_files)
33
+ if should_include && !should_ignore
34
+ plus += diff_stat.additions
35
+ minus += diff_stat.deletions
36
+ end
37
+ end
38
+
39
+ # Weight deletions half as much, since they are typically
40
+ # faster to do & also are 1:1 with additions when changing a line
41
+ @diffs = plus+minus/2
42
+ end
43
+ end
44
+
45
+ class Clock < Record
46
+ # Whether its in or out
47
+ attr_accessor :in, :out
48
+ def initialize(type, date, auth)
49
+ @in = (type == :in)
50
+ @out = (type == :out)
51
+ @date = date
52
+ @author = auth
53
+ end
54
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clockout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: '0.4'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-22 00:00:00.000000000 Z
12
+ date: 2013-06-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: grit
@@ -27,7 +27,23 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
- description: An sort of extension to Git to support clocking hours worked on a project.
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Sort of an extension to Git to support clocking hours worked on a project.
31
47
  email:
32
48
  - danhassin@mac.com
33
49
  executables:
@@ -35,7 +51,9 @@ executables:
35
51
  extensions: []
36
52
  extra_rdoc_files: []
37
53
  files:
54
+ - lib/record.rb
38
55
  - lib/clockout.rb
56
+ - lib/printer.rb
39
57
  - !binary |-
40
58
  YmluL2Nsb2Nr
41
59
  homepage: http://rubygems.org/gems/clockout