todidnt 0.3.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,11 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
  ruby '1.9.3'
3
3
 
4
- gem 'chronic'
5
- gem 'launchy'
6
- gem 'tilt'
7
-
8
- group 'test' do
9
- gem 'minitest'
10
- gem 'mocha'
11
- end
4
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ todidnt (0.3.1)
5
+ chronic
6
+ launchy
7
+ slop
8
+ subprocess
9
+ tilt
10
+
1
11
  GEM
2
12
  remote: https://rubygems.org/
3
13
  specs:
4
- addressable (2.3.5)
14
+ addressable (2.3.6)
5
15
  chronic (0.10.2)
6
16
  launchy (2.4.2)
7
17
  addressable (~> 2.3)
@@ -9,14 +19,14 @@ GEM
9
19
  minitest (5.0.8)
10
20
  mocha (0.14.0)
11
21
  metaclass (~> 0.0.1)
12
- tilt (2.0.0)
22
+ slop (3.6.0)
23
+ subprocess (1.2.0)
24
+ tilt (2.0.1)
13
25
 
14
26
  PLATFORMS
15
27
  ruby
16
28
 
17
29
  DEPENDENCIES
18
- chronic
19
- launchy
20
30
  minitest
21
31
  mocha
22
- tilt
32
+ todidnt!
data/README.md CHANGED
@@ -3,8 +3,13 @@ Todidnt
3
3
 
4
4
  (:construction: This project is currently under construction! :construction:)
5
5
 
6
- Todidnt is a utility to help you track down TODOs and FIXMEs that you always
7
- said you'd do but haven't done yet.
6
+ Todidnt is a command line utility to help you track down TODOs and FIXMEs that
7
+ you always said you'd do but haven't actually done yet. It also generates pages
8
+ (rdoc-style) to show you things like pretty graphs of your repository TODOs over
9
+ time.
10
+
11
+ ![todidnt-rails-screenshot](https://cloud.githubusercontent.com/assets/286015/3520069/33fe0e7e-0727-11e4-9a88-6b70dbff236c.png)
12
+
8
13
 
9
14
  Usage
10
15
  ----
@@ -17,15 +22,7 @@ Then, run `todidnt` in any Git repository directory:
17
22
 
18
23
  $ todidnt
19
24
 
20
- TODO[1]
21
- ----
22
-
23
- - Filtering by author, label
24
- - TODO stats
25
-
26
25
  Credit
27
26
  ----
28
27
 
29
28
  Paul Battley (@threedaymonk) for initial idea and witty name.
30
-
31
- 1: Oh, the irony.
data/bin/todidnt CHANGED
@@ -2,25 +2,24 @@
2
2
 
3
3
  require 'optparse'
4
4
  require 'todidnt'
5
+ require 'slop'
5
6
 
6
- options = {:path => '.'}
7
- command = ARGV.shift
7
+ command = nil
8
+ opts = Slop.parse(ARGV, help: true) do
9
+ on 'p','path=', 'Git directory to run Todidnt in (default: current directory)', default: '.'
10
+ on 't', 'threshold=', 'Threshold of % of TODOs at which to count someone as an individual contributor rather than "Other" (default: 0.03, which means 3%).', as: Float, default: 0.03
8
11
 
9
- ARGV.options do |opts|
10
- opts.on('-p', '--path PATH', 'Git directory to run Todidnt in (default: current directory)') do |path|
11
- options[:path] = path
12
- end
13
-
14
- opts.on('-d', '--date STRING', 'Fuzzy string for overdue date comparison, e.g. last week or 7/2/2013 (default: today)') do |date|
15
- options[:date] = date
16
- end
17
-
18
- opts.on_tail('-h', '--help', 'Show this message') do
19
- puts opts
12
+ on 'version', 'Print the version.' do
13
+ puts Todidnt::VERSION
20
14
  exit
21
15
  end
22
16
 
23
- opts.parse!
17
+ commands = ['clear']
18
+ commands.each do |c|
19
+ command c do
20
+ run do command = c end
21
+ end
22
+ end
24
23
  end
25
24
 
26
- Todidnt::CLI.run(command, options)
25
+ Todidnt::CLI.run(command, opts)
data/lib/todidnt.rb CHANGED
@@ -5,6 +5,8 @@ require 'todidnt/todo_line'
5
5
  require 'todidnt/git_history'
6
6
  require 'todidnt/html_generator'
7
7
 
8
+ require 'todidnt/version'
9
+
8
10
  require 'chronic'
9
11
  require 'launchy'
10
12
 
@@ -23,9 +25,9 @@ module Todidnt
23
25
  end
24
26
  end
25
27
 
26
- def self.generate(options)
27
- GitRepo.new(options[:path]).run do |path|
28
- history = GitHistory.new
28
+ def self.generate(opts)
29
+ GitRepo.new(opts[:path]).run do |path|
30
+ history = GitHistory.new(opts)
29
31
  buckets, authors = history.timeline!
30
32
 
31
33
  lines = TodoLine.all(["TODO"])
@@ -47,7 +49,7 @@ module Todidnt
47
49
  end
48
50
  end
49
51
 
50
- def self.clear(options)
52
+ def self.clear(opts)
51
53
  puts "Deleting cache..."
52
54
  Cache.clear!
53
55
  puts "Done!"
@@ -3,10 +3,14 @@ module Todidnt
3
3
 
4
4
  attr_accessor :blames
5
5
 
6
- def initialize
6
+ def initialize(opts)
7
7
  @history = []
8
8
  @blames = {}
9
9
  @unmatched_deletions = []
10
+
11
+ # Contributor threshold (e.g. only show as a separate contributor
12
+ # if they've contributed to > N% of TODOs).
13
+ @threshold = opts[:threshold]
10
14
  end
11
15
 
12
16
  def timeline!
@@ -65,6 +69,8 @@ module Todidnt
65
69
  count = 0
66
70
 
67
71
  command.execute! do |line|
72
+ line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
73
+
68
74
  if (diff = /diff --git a\/(.*) b\/(.*)/.match(line))
69
75
  filename = diff[1]
70
76
  elsif (diff = /^\+(.*TODO.*)/.match(line))
@@ -152,16 +158,22 @@ module Todidnt
152
158
  # Figure out what the interval should be based on the total timespan.
153
159
  if timespan > 86400 * 365 * 10 # 10+ years
154
160
  interval = 86400 * 365 # years
161
+ timespan_label = 'years'
155
162
  elsif timespan > 86400 * 365 * 5 # 5-10 years
156
163
  interval = 86400 * (365 / 2) # 6 months
164
+ timespan_label = 'months'
157
165
  elsif timespan > 86400 * 365 # 2-5 years
158
166
  interval = 86400 * (365 / 4) # 3 months
167
+ timespan_label = 'months'
159
168
  elsif timespan > 86400 * 30 * 6 # 6 months-3 year
160
169
  interval = 86400 * 30 # months
161
- elsif timespan > 86400 * 1 # 1 month - 6 months
170
+ timespan_label = 'months'
171
+ elsif timespan > 86400 * 14 * 1 # 1/2 month - 6 months
162
172
  interval = 86400 * 7
163
- else # 0 - 2 months
173
+ timespan_label = 'days'
174
+ else # 0 - 1/2 months
164
175
  interval = 86400 # days
176
+ timespan_label = 'days'
165
177
  end
166
178
 
167
179
  original_interval_start = Time.new(min_commit_date.year, min_commit_date.month, min_commit_date.day).to_i
@@ -175,7 +187,7 @@ module Todidnt
175
187
 
176
188
  # Add the first bucket of 0
177
189
  buckets << {
178
- :timestamp => Time.at(interval_start).strftime('%D'),
190
+ :timestamp => format_date(Time.at(interval_start), timespan_label),
179
191
  :authors => {},
180
192
  :total => 0
181
193
  }
@@ -200,7 +212,7 @@ module Todidnt
200
212
  # in a new bucket, finish the current bucket.
201
213
  if i == (@history.length - 1) || @history[i + 1][:timestamp] >= interval_end
202
214
  buckets << {
203
- :timestamp => ([Time.at(interval_end), max_commit_date].min).strftime('%D'),
215
+ :timestamp => format_date(([Time.at(interval_end), max_commit_date].min), timespan_label),
204
216
  :authors => current_bucket_authors,
205
217
  :total => bucket_total
206
218
  }
@@ -221,7 +233,7 @@ module Todidnt
221
233
  bucket[:authors].each do |author, count|
222
234
  # Only include the author if they account for more than > 3% of
223
235
  # the TODOs in this bucket.
224
- if count > bucket[:total] * 0.03
236
+ if count > bucket[:total] * @threshold
225
237
  significant_authors[author] = count
226
238
  authors << author
227
239
  else
@@ -244,5 +256,17 @@ module Todidnt
244
256
  [buckets, authors]
245
257
  end
246
258
 
259
+ private
260
+
261
+ def format_date(date, timespan_label)
262
+ case timespan_label
263
+ when 'years'
264
+ date.strftime('%Y')
265
+ when 'months'
266
+ date.strftime('%-m/%y')
267
+ when 'days'
268
+ date.strftime('%-m/%-d')
269
+ end
270
+ end
247
271
  end
248
272
  end
@@ -0,0 +1,3 @@
1
+ module Todidnt
2
+ VERSION = '0.4.1'
3
+ end
@@ -29,7 +29,7 @@ body a:hover {
29
29
  header {
30
30
  background: #333;
31
31
  color: #fff;
32
- padding: 10px 30px;
32
+ padding: 10px 50px;
33
33
  }
34
34
 
35
35
  header h1 {
@@ -38,7 +38,7 @@ header h1 {
38
38
  }
39
39
 
40
40
  section.content {
41
- padding: 30px;
41
+ padding: 50px;
42
42
  }
43
43
 
44
44
  footer {
@@ -1,6 +1,32 @@
1
+ <style type='text/css'>
2
+ #legend {
3
+ width: 19%;
4
+ height: 500px;
5
+ overflow: auto;
6
+
7
+ margin-top: 20px;
8
+ }
9
+
10
+ #graph {
11
+ float: left;
12
+ overflow: auto;
13
+ width: 80%;
14
+ }
15
+
16
+ .x.axis line, .x.axis path, .y.axis line, .y.axis path {
17
+ fill: none;
18
+ stroke: #444;
19
+ }
20
+
21
+ </style>
22
+
23
+ <div id='graph'></div>
24
+ <div id='legend'></div>
25
+
1
26
  <script src='js/vendor/d3.min.js'></script>
2
27
  <script src='js/vendor/d3-tip.js'></script>
3
28
  <script type='text/javascript'>
4
29
  PRELOADED_DATA = <%= data.to_json %>;
5
30
  </script>
6
31
  <script src='js/todidnt-history.js'></script>
32
+
@@ -4,10 +4,10 @@ var margin = {
4
4
  top: 20,
5
5
  right: 20,
6
6
  bottom: 30,
7
- left: Math.round(window.innerWidth * 0.09)
7
+ left: 50
8
8
  };
9
9
 
10
- var width = window.innerWidth * 0.8, height = 500;
10
+ var width = window.innerWidth * 0.70, height = 500;
11
11
 
12
12
  // Graph scale setup
13
13
 
@@ -27,6 +27,17 @@ var getColor = function(name) {
27
27
  }
28
28
  };
29
29
 
30
+ var getGrayscaleColor = function(name) {
31
+ var color = getColor(name);
32
+
33
+ // Grayscale the color.
34
+ hsl = d3.hsl(color);
35
+ l = hsl.l;
36
+ l = Math.min(l + ((1 - l) * 0.85), 0.95);
37
+
38
+ return d3.hsl(hsl.h, 0, l);
39
+ }
40
+
30
41
  var xAxis = d3.svg.axis()
31
42
  .scale(x)
32
43
  .orient("bottom");
@@ -62,7 +73,7 @@ var data = PRELOADED_DATA['history'].map(function(d) {
62
73
 
63
74
  // Graph creation
64
75
 
65
- var svg = d3.select("section.content").append("svg")
76
+ var svg = d3.select("#graph").append("svg")
66
77
  .attr("width", '100%')
67
78
  .attr("height", height + margin.top + margin.bottom)
68
79
  .append("g")
@@ -73,21 +84,6 @@ svg.call(tip);
73
84
  x.domain(data.map(function(d) { return d.date; }));
74
85
  y.domain([0, d3.max(data, function(d) { return d.total; })]);
75
86
 
76
- svg.append("g")
77
- .attr("class", "x axis")
78
- .attr("transform", "translate(0," + height + ")")
79
- .call(xAxis);
80
-
81
- svg.append("g")
82
- .attr("class", "y axis")
83
- .call(yAxis)
84
- .append("text")
85
- .attr("transform", "rotate(-90)")
86
- .attr("y", 6)
87
- .attr("dy", ".71em")
88
- .style("text-anchor", "end")
89
- .text("TODOs");
90
-
91
87
  var dates = svg.selectAll(".dates")
92
88
  .data(data)
93
89
  .enter().append("g")
@@ -104,23 +100,70 @@ dates.selectAll("rect")
104
100
  .on("mouseover", tip.show)
105
101
  .on("mouseout", tip.hide);
106
102
 
103
+
104
+ // Axes
105
+
106
+ svg.append("g")
107
+ .attr("class", "x axis")
108
+ .attr("transform", "translate(0," + height + ")")
109
+ .call(xAxis);
110
+
111
+ svg.append("g")
112
+ .attr("class", "y axis")
113
+ .call(yAxis)
114
+ .append("text")
115
+ .attr("transform", "rotate(-90)")
116
+ .attr("y", 6)
117
+ .attr("dy", ".71em")
118
+ .style("text-anchor", "end")
119
+ .text("TODOs");
120
+
107
121
  // Legend
108
122
 
109
- var legend = svg.selectAll(".legend")
110
- .data(color.domain().slice().reverse())
111
- .enter().append("g")
112
- .attr("class", "legend")
113
- .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
123
+ var legend_svg = d3.select("#legend")
124
+ .append('svg')
125
+ .style("height", color.domain().length * 20);
114
126
 
115
- legend.append("rect")
116
- .attr("x", 30)
127
+ var legend = legend_svg.append('g')
128
+ .attr("class", "legend");
129
+
130
+ legend.selectAll('rect')
131
+ .data(color.domain().slice().reverse())
132
+ .enter().append("rect")
117
133
  .attr("width", 18)
118
134
  .attr("height", 18)
119
- .style("fill", getColor);
120
-
121
- legend.append("text")
122
- .attr("x", 55)
135
+ .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
136
+ .style("fill", getColor)
137
+ .style("cursor", "pointer")
138
+ .on('click', function(d, i) {
139
+ j = color.domain().length - i - 1; // reversed for some reason
140
+ dates.selectAll("rect").style("fill",
141
+ function(d2, i2) {
142
+ if (j != i2) {
143
+ return getGrayscaleColor(d2.name);
144
+ } else {
145
+ return getColor(d2.name);
146
+ }
147
+ }
148
+ )
149
+
150
+ legend.selectAll("rect").style("fill",
151
+ function(d2, i2) {
152
+ if (i != i2) {
153
+ return getGrayscaleColor(d2);
154
+ } else {
155
+ return getColor(d2);
156
+ }
157
+ }
158
+ )
159
+ })
160
+
161
+ legend.selectAll('text')
162
+ .data(color.domain().slice().reverse())
163
+ .enter().append("text")
164
+ .attr("x", 25)
123
165
  .attr("y", 9)
124
166
  .attr("dy", ".35em")
167
+ .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
125
168
  .style("text-anchor", "beginning")
126
169
  .text(function(d) { return d; });
data/todidnt.gemspec CHANGED
@@ -1,13 +1,22 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+ require 'todidnt/version'
3
+
1
4
  Gem::Specification.new do |s|
2
5
  s.name = 'todidnt'
3
- s.version = '0.3.1'
6
+ s.version = Todidnt::VERSION
4
7
  s.summary = 'Todidnt'
5
8
  s.description = "Todidnt finds and dates todos in your git repository."
6
9
  s.authors = ["Amber Feng"]
7
10
  s.email = 'amber.feng@gmail.com'
8
11
 
9
- s.add_development_dependency('minitest')
10
- s.add_development_dependency('mocha')
12
+ s.add_dependency('chronic', '0.10.2')
13
+ s.add_dependency('launchy', '2.4.2')
14
+ s.add_dependency('tilt', '2.0.1')
15
+ s.add_dependency('slop', '3.6.0')
16
+ s.add_dependency('subprocess', '1.0.0')
17
+
18
+ s.add_development_dependency('minitest', '5.4.0')
19
+ s.add_development_dependency('mocha', '1.1.0')
11
20
 
12
21
  s.files = `git ls-files`.split("\n")
13
22
  s.test_files = `git ls-files -- test/test_*.rb`.split("\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: todidnt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,40 +9,120 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-05 00:00:00.000000000 Z
12
+ date: 2014-08-02 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: chronic
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.10.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.10.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: launchy
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.4.2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.4.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: tilt
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 2.0.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: slop
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 3.6.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 3.6.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: subprocess
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - '='
84
+ - !ruby/object:Gem::Version
85
+ version: 1.0.0
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - '='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.0.0
14
94
  - !ruby/object:Gem::Dependency
15
95
  name: minitest
16
96
  requirement: !ruby/object:Gem::Requirement
17
97
  none: false
18
98
  requirements:
19
- - - ! '>='
99
+ - - '='
20
100
  - !ruby/object:Gem::Version
21
- version: '0'
101
+ version: 5.4.0
22
102
  type: :development
23
103
  prerelease: false
24
104
  version_requirements: !ruby/object:Gem::Requirement
25
105
  none: false
26
106
  requirements:
27
- - - ! '>='
107
+ - - '='
28
108
  - !ruby/object:Gem::Version
29
- version: '0'
109
+ version: 5.4.0
30
110
  - !ruby/object:Gem::Dependency
31
111
  name: mocha
32
112
  requirement: !ruby/object:Gem::Requirement
33
113
  none: false
34
114
  requirements:
35
- - - ! '>='
115
+ - - '='
36
116
  - !ruby/object:Gem::Version
37
- version: '0'
117
+ version: 1.1.0
38
118
  type: :development
39
119
  prerelease: false
40
120
  version_requirements: !ruby/object:Gem::Requirement
41
121
  none: false
42
122
  requirements:
43
- - - ! '>='
123
+ - - '='
44
124
  - !ruby/object:Gem::Version
45
- version: '0'
125
+ version: 1.1.0
46
126
  description: Todidnt finds and dates todos in your git repository.
47
127
  email: amber.feng@gmail.com
48
128
  executables:
@@ -63,6 +143,7 @@ files:
63
143
  - lib/todidnt/git_repo.rb
64
144
  - lib/todidnt/html_generator.rb
65
145
  - lib/todidnt/todo_line.rb
146
+ - lib/todidnt/version.rb
66
147
  - templates/all.erb
67
148
  - templates/css/style.css
68
149
  - templates/css/vendor/chosen.min.css