dcov 0.1.0 → 0.2.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/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