dcov 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -12,4 +12,15 @@
12
12
 
13
13
  == 0.1.0
14
14
 
15
- No serious enhancements other than a pretty major bug fix and a new version number. :)
15
+ No serious enhancements other than a pretty major bug fix and a new version number. :)
16
+
17
+ == 0.2.0
18
+
19
+ * 2 major enhancements
20
+ * Added new analyzer system
21
+ * Added quality analysis
22
+
23
+ == 0.2.1
24
+ * 2 minor changes
25
+ * Removed Ruport dependency; too much baggage!
26
+ * Tweaked a few things
data/bin/dcov CHANGED
@@ -3,12 +3,6 @@
3
3
  # Created by Jeremy McAnally on 2007-5-28.
4
4
  # Copyright (c) 2007. All rights reserved.
5
5
 
6
- begin
7
- require 'rubygems'
8
- rescue LoadError
9
- # no rubygems to load, so we fail silently
10
- end
11
-
12
6
  require 'optparse'
13
7
  require File.dirname(__FILE__) + '/../lib/dcov'
14
8
 
@@ -45,9 +39,5 @@ BANNER
45
39
  end
46
40
  end
47
41
 
48
- if (ARGV != [])
49
- OPTIONS[:files] = ARGV
50
- @coverage = Dcov::Analyzer.new(OPTIONS)
51
- else
52
- raise "No files to analyze!"
53
- end
42
+ OPTIONS[:files] = ARGV
43
+ @coverage = Dcov::Analyzer.new(OPTIONS)
data/lib/dcov/analyzer.rb CHANGED
@@ -1,34 +1,86 @@
1
1
  require 'rdoc/rdoc'
2
- require 'dcov/analyzed_token.rb'
3
- require 'dcov/stats'
2
+ require File.dirname(__FILE__) + '/analyzed_token.rb'
3
+ require File.dirname(__FILE__) + '/stats'
4
4
 
5
5
  # Makes +stats+ accessible to us so we can inject our own.
6
- # We also add a +classifier+ attribtue to the code object classes.
6
+ # We also add a +classifier+ attribute to the code object classes.
7
7
  module RDoc
8
8
  class RDoc
9
9
  attr_accessor :stats
10
10
  end
11
11
 
12
+ module DcovHelpers
13
+ attr_accessor :reporting_data
14
+
15
+ def initialize(*args)
16
+ super(*args)
17
+ @reporting_data = {}
18
+ initialize_reporting_data
19
+ end
20
+
21
+ def full_name
22
+ hierarchy = self.name
23
+ inspect = self
24
+ while (hier_piece = inspect.parent)
25
+ hierarchy = hier_piece.name + "::" + hierarchy unless hier_piece.name == 'TopLevel'
26
+ inspect = hier_piece
27
+ end
28
+
29
+ hierarchy
30
+ end
31
+ end
32
+
12
33
  class AnyMethod
34
+ include DcovHelpers
35
+
13
36
  def classifier
14
37
  :method
15
38
  end
39
+
40
+ def initialize_reporting_data
41
+ @reporting_data[:parameters_without_coverage] = []
42
+ @reporting_data[:default_values_without_coverage] = []
43
+ end
16
44
  end
17
45
 
18
46
  class NormalClass
47
+ include DcovHelpers
48
+
19
49
  def classifier
20
50
  :class
21
51
  end
52
+
53
+ def initialize_reporting_data
54
+ end
22
55
  end
23
56
 
24
57
  class NormalModule
58
+ include DcovHelpers
59
+
25
60
  def classifier
26
61
  :module
27
62
  end
63
+
64
+ def initialize_reporting_data
65
+ end
28
66
  end
29
67
  end
30
68
 
31
69
  module Dcov
70
+ class AnalysisContext
71
+ attr_accessor :token, :comment, :classifier, :stats
72
+
73
+ def initialize(token, comment, classifier, stats)
74
+ @token = token
75
+ @comment = comment || ''
76
+ @classifier = classifier
77
+ @stats = stats
78
+ end
79
+
80
+ def must(description)
81
+ yield description
82
+ end
83
+ end
32
84
 
33
85
  # Mocked options object to feed to RDoc
34
86
  class RDocOptionsMock
@@ -44,7 +96,7 @@ module Dcov
44
96
  end
45
97
 
46
98
  # Main class
47
- class Analyzer
99
+ class Analyzer
48
100
  attr_accessor :classes, :methods, :modules, :hierarchy, :stats
49
101
 
50
102
  # Grab the arguments from the DCov init script or
@@ -52,6 +104,7 @@ module Dcov
52
104
  # parsing.
53
105
  def initialize(options)
54
106
  @options = options
107
+ raise "No files to analyze!" if @options[:files] == [] || @options[:files] == nil
55
108
 
56
109
  # Setup the analyzed tokens array so we can keep track of which methods we've already
57
110
  # taken a look at...
@@ -109,7 +162,7 @@ module Dcov
109
162
  def process_token(token)
110
163
  analyzed_token = AnalyzedToken.new(token.name, token.parent)
111
164
  unless @analyzed_tokens.include?(analyzed_token)
112
- expose_to_analyzers(token)
165
+ token = expose_to_analyzers(token)
113
166
  @analyzed_tokens << analyzed_token
114
167
 
115
168
  [:method_list, :classes, :modules].each do |meth, type|
@@ -125,7 +178,9 @@ module Dcov
125
178
  def generate
126
179
  print "Generating report..."
127
180
  require File.dirname(__FILE__) + "/generators/#{@options[:output_format]}/generator.rb"
128
- report = @stats.as(@options[:output_format].to_sym)
181
+
182
+ generator = Dcov::Generator.new @stats.renderable_data
183
+ report = generator.to_s
129
184
  print "done.\n"
130
185
 
131
186
  print "Writing report..."
@@ -143,10 +198,7 @@ module Dcov
143
198
  def find_analyzers
144
199
  Dir::entries(File.dirname(__FILE__) + "/analyzers").each do |analyzer|
145
200
  next unless /(\w+)_analyzer.rb$/ =~ analyzer
146
- type = $1
147
-
148
- require File.dirname(__FILE__) + "/analyzers/#{analyzer}"
149
- @analyzers << "#{type.capitalize}Analyzer".intern
201
+ @analyzers << File.dirname(__FILE__) + "/analyzers/#{analyzer}"
150
202
  end
151
203
  end
152
204
 
@@ -155,8 +207,82 @@ module Dcov
155
207
  def expose_to_analyzers(token)
156
208
  @analyzers.each do |analyzer|
157
209
  classifier = token.classifier
210
+ eval(File.read(analyzer))
158
211
 
159
- Dcov::Analyzers.const_get(analyzer).analyze(token, classifier, @stats)
212
+ token = analyze_token(token, classifier, @stats)
213
+ @method_analysis, @module_analysis, @class_analysis, @all_analysis = nil
214
+ end
215
+
216
+ token
217
+ end
218
+
219
+ def documentation_for_methods(&block)
220
+ @method_analysis = block
221
+ end
222
+
223
+ def documentation_for_classes(&block)
224
+ @class_analysis = block
225
+ end
226
+
227
+ def documentation_for_modules(&block)
228
+ @module_analysis = block
229
+ end
230
+
231
+ def documentation_for_all(&block)
232
+ @all_analysis = block
233
+ end
234
+
235
+ def analyze_token(token, classifier, stats)
236
+ context = AnalysisContext.new(token, (token.comment || ''), classifier, stats)
237
+
238
+ @all_analysis.call(context) if @all_analysis
239
+
240
+ case classifier
241
+ when :method
242
+ @method_analysis.call(context) if @method_analysis
243
+ when :class
244
+ @class_analysis.call(context) if @class_analysis
245
+ when :module
246
+ @module_analysis.call(context) if @module_analysis
247
+ end
248
+
249
+ context.token
250
+ end
251
+
252
+ def param_names_for(token)
253
+ if token.params
254
+ params = token.params.dup
255
+ params.gsub!(/\(/, '')
256
+ params.gsub!(/\)/, '')
257
+
258
+ if params.include?(",")
259
+ params = params.split(",")
260
+ else
261
+ params = [params]
262
+ end
263
+
264
+ processed_params = []
265
+
266
+ params.each do |param|
267
+ param_value = nil
268
+
269
+ # We have a default value...
270
+ if param.include?('=')
271
+ param_pieces = param.scan(/(.*)=(.*)/)[0]
272
+ param = param_pieces[0].strip
273
+ param_value = param_pieces[1].strip
274
+ end
275
+
276
+ processed_params << [param, param_value]
277
+ end
278
+
279
+ if processed_params == [["", nil]]
280
+ []
281
+ else
282
+ processed_params
283
+ end
284
+ else
285
+ []
160
286
  end
161
287
  end
162
288
  end
@@ -1,9 +1,7 @@
1
- module Dcov
2
- module Analyzers
3
- class CoverageAnalyzer
4
- def self.analyze(token, classifier, stats)
5
- (token.comment.nil? || token.comment == '') ? stats.coverage[classifier][:not_covered] << token : stats.coverage[classifier][:covered] << token
6
- end
7
- end
1
+ documentation_for_all do |the_documentation|
2
+ the_documentation.must " have coverage." do
3
+ the_documentation.stats.coverage[:tokens][the_documentation.token] = []
4
+ (the_documentation.token.comment.nil? || the_documentation.token.comment == '') ? the_documentation.stats.coverage[the_documentation.classifier][:not_covered] << the_documentation.token : the_documentation.stats.coverage[the_documentation.classifier][:covered] << the_documentation.token
5
+ (the_documentation.token.comment.nil? || the_documentation.token.comment == '') ? the_documentation.stats.coverage[:tokens][the_documentation.token] << :not_covered : the_documentation.stats.coverage[:tokens][the_documentation.token] << :covered
8
6
  end
9
- end
7
+ end
@@ -1,38 +1,69 @@
1
+
1
2
  module Dcov
2
3
  # Generates HTML output
3
- class Generator < Ruport::Formatter::HTML
4
- renders :html, :for => StatsRenderer
4
+ class Generator # < Ruport::Formatter::HTML
5
+ # renders :html, :for => StatsRenderer
6
+
7
+ include Dcov::StatsRenderer::Helpers
8
+
9
+ attr_reader :data
10
+ def initialize(data)
11
+ @data = data
12
+ end
13
+
14
+ def to_s
15
+ build_stats_header + build_stats_body + build_stats_footer
16
+ end
5
17
 
6
18
  def build_stats_header
7
- output << "<html><head><title>dcov results</title>"+
8
- "</head><body>\n<h1>dcov results</h1>\n\n"
19
+ # Little CSS, a little HTML...
20
+ output = ""
21
+ output << """<html><head><title>dcov results</title>
22
+ <style>
23
+ BODY { font-family: Helvetica, Arial, sans-serif; font-size: 10pt; background: #333; color: white; margin: 16px; }
24
+ H1 { text-shadow: 3pt 3pt 5pt black;}
25
+ .quality_problem { font-size: 8pt; color: #999; }
26
+ LI { margin: 8px; }
27
+ </style>
28
+ </head><body>\n<h1>dcov results</h1>\n\n"""
9
29
  end
10
30
 
11
31
  def build_stats_body
32
+ output = ""
12
33
  output << "<p>\n"
13
34
  output << "Class coverage: <b>#{class_coverage}%</b><br>\n"
14
35
  output << "Module coverage: <b>#{module_coverage}%</b><br>\n"
15
36
  output << "Method coverage: <b>#{method_coverage}%</b><br>\n"
16
37
  output << "</p>\n\n"
17
- output << "<ul>\n"
38
+ output << "<ol>\n"
18
39
 
19
40
  data[:structured].each do |key,value|
20
41
  output << ((value[0].comment.nil? || value[0].comment == '') ?
21
- "\t<li><font color='#f00;'>#{key}</font>\n\t\t<ul>\n" :
22
- "\t<li> #{key}\n\t\t<ul>")
42
+ "\t<li><font color='#f00;'><tt>#{key.is_a?(String) ? key : key.full_name }</tt></font>\n\t\t<ol>\n" :
43
+ "\t<li><tt>#{key}</tt>\n\t\t<ol>") unless value[0].is_a?(Dcov::TopLevel)
23
44
 
24
45
  value[1].each do |itm|
25
46
  output << ((itm.comment.nil? || itm.comment == '') ?
26
- "\t\t\t<li><font color='#f00;'>#{itm.name}</font></li>\n" :
27
- "\t\t\t<li> #{itm.name}</li>\n")
47
+ "\t\t\t<li><font color='#f00;'><tt>#{itm.name}</tt></font>\n" :
48
+ "\t\t\t<li><tt>#{itm.name}</tt>\n")
49
+
50
+ # Quality information
51
+ output << "#{"<br /><span class='quality_problem'>parameters without documentation: <tt>" + itm.reporting_data[:parameters_without_coverage].join(", ") + "</tt></span>" if itm.reporting_data[:parameters_without_coverage].length > 0}"
52
+ output << "#{"<br /><span class='quality_problem'>default values without documentation: <tt>" + itm.reporting_data[:default_values_without_coverage].join(", ") + "</tt></span>" if itm.reporting_data[:default_values_without_coverage].length > 0}"
53
+ output << "#{"<br /><span class='quality_problem'>options are not documented</span>" if itm.reporting_data[:no_options_documentation]}"
54
+ output << "#{"<br /><span class='quality_problem'>there are no examples</span>" if itm.reporting_data[:no_examples]}"
55
+
56
+ output << "</li>\n"
28
57
  end
29
58
 
30
- output << "\t\t</ul>\n\t</li>\n\n"
59
+ output << "\t\t</ol>\n\t</li>\n\n" unless value[0].is_a?(Dcov::TopLevel)
31
60
  end
61
+ output
32
62
  end
33
63
 
34
64
  def build_stats_footer
35
- output << "\n\n</body></html>"
65
+ output = ""
66
+ output << "</ol>\n\n</body></html>"
36
67
  end
37
68
 
38
69
  end
data/lib/dcov/stats.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'rubygems'
2
- require 'ruport'
2
+ # require 'ruport'
3
3
 
4
4
  module Dcov
5
- class StatsRenderer < Ruport::Renderer
5
+ class StatsRenderer # < Ruport::Renderer
6
6
 
7
- stage :stats_header, :stats_body, :stats_footer
7
+ # stage :stats_header, :stats_body, :stats_footer
8
8
 
9
9
  module Helpers
10
10
 
@@ -33,8 +33,8 @@ module Dcov
33
33
 
34
34
  attr_accessor :num_files, :num_classes, :num_modules, :num_methods, :coverage
35
35
 
36
- include Ruport::Renderer::Hooks
37
- renders_with Dcov::StatsRenderer
36
+ # include Ruport::Renderer::Hooks
37
+ # renders_with Dcov::StatsRenderer
38
38
 
39
39
  def initialize
40
40
  @num_files = @num_classes = @num_modules = @num_methods = 0
@@ -45,23 +45,52 @@ module Dcov
45
45
  [:class, :method, :module].each do |type|
46
46
  @coverage[type] = { :covered => [], :not_covered => [] }
47
47
  end
48
+
49
+ @coverage[:tokens] = {}
48
50
  end
49
51
 
50
52
  # Print out the coverage rating
51
53
  def print
52
- puts "\n\nFiles: #{@num_files}"
54
+ # TODO: add a flag for textmate, the ugliest format ever:
55
+ # txmt://open?url=file:///path/to/file.rb&line=86&column=3
56
+
57
+ puts "Files: #{@num_files}"
53
58
  puts "Total Classes: #{@num_classes}"
54
59
  puts "Total Modules: #{@num_modules}"
55
60
  puts "Total Methods: #{@num_methods}"
56
- puts "\n\n\n"
57
- puts "Class coverage: #{coverage_rating(:class)}%\n"
58
- puts "\tNot covered: #{@coverage[:class][:not_covered].map{|itm| itm.name}.join(', ')}\n\n"
59
-
60
- puts "Method coverage: #{coverage_rating(:method)}%\n"
61
- puts "\tNot covered: #{@coverage[:method][:not_covered].map{|itm| itm.parent.name + "#" + itm.name}.join(', ')}\n\n"
62
61
 
62
+ puts
63
63
  puts "Module coverage: #{coverage_rating(:module)}%"
64
- puts "\tNot covered: #{@coverage[:module][:not_covered].map{|itm| itm.name}.join(', ')}\n\n"
64
+ puts " Not covered:"
65
+ @coverage[:module][:not_covered].sort_by {|o| o.name}.each do |itm|
66
+ location = itm.in_files.first.file_absolute_name || "no known location"
67
+ puts " #{itm.name}:"
68
+ puts " #{location}"
69
+ end
70
+
71
+ puts
72
+
73
+ puts
74
+ puts "Class coverage: #{coverage_rating(:class)}%"
75
+ puts " Not covered:"
76
+ @coverage[:class][:not_covered].sort_by {|o| o.name}.each do |itm|
77
+ location = itm.in_files.first.file_absolute_name
78
+ puts " #{itm.name}"
79
+ puts " #{location}"
80
+ end
81
+
82
+ puts
83
+
84
+ puts
85
+ puts "Method coverage: #{coverage_rating(:method)}%\n"
86
+ puts " Not covered:"
87
+ @coverage[:method][:not_covered].sort_by {|o| [o.parent.name, o.name]}.each do |itm|
88
+ location = itm.token_stream.first.text.sub(/^# File /, '').sub(/, line (\d+)$/, ':\1')
89
+ puts " #{itm.parent.name}##{itm.name}:"
90
+ puts " #{location}"
91
+ end
92
+
93
+ puts
65
94
  end
66
95
 
67
96
  # Get the coverage rating (e.g., 34%) for the tokens of the type specified
@@ -86,13 +115,13 @@ module Dcov
86
115
 
87
116
  # Create a properly nested structure
88
117
  data[:structured] = {}
89
- classes.each {|cls| data[:structured][cls.name] = [cls, []]}
90
- modules.each {|mod| data[:structured][mod.name] = [mod, []]}
118
+ classes.each {|cls| data[:structured][cls.full_name] = [cls, []]}
119
+ modules.each {|mod| data[:structured][mod.full_name] = [mod, []]}
91
120
  data[:structured]['[Toplevel]'] = [TopLevel.new, []]
92
121
 
93
122
  methods.each do |method|
94
- if (data[:structured].has_key?(method.parent.name))
95
- data[:structured][method.parent.name][1] << method
123
+ if (data[:structured].has_key?(method.parent.full_name))
124
+ data[:structured][method.parent.full_name][1] << method
96
125
  else
97
126
  data[:structured]['[Toplevel]'][1] << method
98
127
  end
data/lib/dcov/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Dcov #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
5
- TINY = 0
4
+ MINOR = 2
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/lib/dcov.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Dcov
2
2
  end
3
3
 
4
- require 'dcov/version.rb'
5
- require 'dcov/analyzer.rb'
4
+ require File.dirname(__FILE__) + '/dcov/version.rb'
5
+ require File.dirname(__FILE__) + '/dcov/analyzer.rb'
data/spec/dcov_spec.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require File.dirname(__FILE__) + '/spec_helper.rb'
4
4
  require File.dirname(__FILE__) + '/../lib/dcov.rb'
5
5
 
6
- describe "A new Dcov::Coverage object" do
6
+ describe "The Dcov analyzer" do
7
7
  before(:each) do
8
8
  @myopts = OptionsMock.new
9
9
  end
@@ -64,7 +64,7 @@ describe "A new Dcov::Coverage object" do
64
64
  end
65
65
 
66
66
  it "should give an exception if no files are given." do
67
- @myopts[:files] = ''
67
+ @myopts[:files] = []
68
68
  lambda { Dcov::Analyzer.new(@myopts) }.should raise_error(RuntimeError)
69
69
 
70
70
  @myopts[:files] = nil
@@ -94,6 +94,35 @@ describe "A new Dcov::Coverage object" do
94
94
  coverage_is_zero.stats.coverage_rating(:method).should == 0
95
95
  end
96
96
 
97
+ it "should process the tokens into a proper structure after quality analysis." do
98
+ @analyzer.stats.renderable_data[:structured].should_not == nil
99
+ end
100
+
101
+ it "should attach reporting data to each token." do
102
+ @analyzer.stats.renderable_data[:structured][@analyzer.stats.renderable_data[:structured].keys[0]][0].reporting_data.should == {}
103
+ end
104
+
105
+ it "should attach extra reporting data to each method." do
106
+ @analyzer.stats.renderable_data[:structured]['Document::Element'][1][0].reporting_data.should_not == {}
107
+ end
108
+
109
+ it "should catch tokens without examples." do
110
+ @analyzer.stats.renderable_data[:structured]['Document::Element'][1][2].reporting_data.keys.include?(:no_examples).should == true
111
+ end
112
+
113
+ it "should catch methods that have undocumented default values." do
114
+ @analyzer.stats.renderable_data[:structured]['Document::Element'][1][1].reporting_data.keys.include?(:default_values_without_coverage).should == true
115
+ end
116
+
117
+ it "should catch methods that have undocumented parameters." do
118
+ @analyzer.stats.renderable_data[:structured]['Document::Element'][1][1].reporting_data.keys.include?(:parameters_without_coverage).should == true
119
+ end
120
+
121
+ it "should catch methods that have undocumented options hashes." do
122
+ @analyzer.stats.renderable_data[:structured]['Document::Element'][1][1].reporting_data.keys.include?(:no_options_documentation).should == true
123
+ end
124
+
125
+ # Let's kill off the HTML!
97
126
  after(:all) do
98
127
  `chmod 744 coverage.html; rm coverage.html`
99
128
  end
data/spec/mock_code.rb CHANGED
@@ -19,11 +19,14 @@ module Document
19
19
  def initialize
20
20
  end
21
21
 
22
- def parse
22
+ def parse(document = "Current Document", options = {})
23
23
  end
24
24
 
25
25
  # The main method
26
- def elementize
26
+ # ==== Examples
27
+ # elementize
28
+ # # => Hello!
29
+ def elementize(the_item)
27
30
  end
28
31
  end
29
32
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0
2
+ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: dcov
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2007-07-07 00:00:00 -04:00
6
+ version: 0.2.1
7
+ date: 2007-09-16 00:00:00 -04:00
8
8
  summary: A documentation analysis tool for Ruby.
9
9
  require_paths:
10
10
  - lib