todidnt 0.3.1 → 0.4.1

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.
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