clockout 0.3.2 → 0.4

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