rcov 0.5.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+
2
+ == Code coverage analysis automation with Rake
3
+
4
+ Since 0.4.0, <tt>rcov</tt> features a <tt>Rcov::RcovTask</tt> task for rake
5
+ which can be used to automate test coverage analysis. Basic usage is as
6
+ follows:
7
+
8
+ require 'rcov/rcovtask'
9
+ Rcov::RcovTask.new do |t|
10
+ t.test_files = FileList['test/test*.rb']
11
+ # t.verbose = true # uncomment to see the executed command
12
+ end
13
+
14
+ This will create by default a task named <tt>rcov</tt>, and also a task to
15
+ remove the output directory where the XHTML report is generated.
16
+ The latter will be named <tt>clobber_rcob</tt>, and will be added to the main
17
+ <tt>clobber</tt> target.
18
+
19
+ === Passing command line options to <tt>rcov</tt>
20
+
21
+ You can provide a description, change the name of the generated tasks (the
22
+ one used to generate the report(s) and the clobber_ one) and pass options to
23
+ <tt>rcov</tt>:
24
+
25
+ desc "Analyze code coverage of the unit tests."
26
+ Rcov::RcovTask.new(:coverage) do |t|
27
+ t.test_files = FileList['test/test*.rb']
28
+ t.verbose = true
29
+ ## get a text report on stdout when rake is run:
30
+ t.rcov_opts << "--text-report"
31
+ ## only report files under 80% coverage
32
+ t.rcov_opts << "--threshold 80"
33
+ end
34
+
35
+ That will generate a <tt>coverage</tt> task and the associated
36
+ <tt>clobber_coverage</tt> task to remove the directory the report is dumped
37
+ to ("<tt>coverage</tt>" by default).
38
+
39
+ You can specify a different destination directory, which comes handy if you
40
+ have several <tt>RcovTask</tt>s; the <tt>clobber_*</tt> will take care of
41
+ removing that directory:
42
+
43
+ desc "Analyze code coverage for the FileStatistics class."
44
+ Rcov::RcovTask.new(:rcov_sourcefile) do |t|
45
+ t.test_files = FileList['test/test_FileStatistics.rb']
46
+ t.verbose = true
47
+ t.rcov_opts << "--test-unit-only"
48
+ t.output_dir = "coverage.sourcefile"
49
+ end
50
+
51
+ Rcov::RcovTask.new(:rcov_ccanalyzer) do |t|
52
+ t.test_files = FileList['test/test_CodeCoverageAnalyzer.rb']
53
+ t.verbose = true
54
+ t.rcov_opts << "--test-unit-only"
55
+ t.output_dir = "coverage.ccanalyzer"
56
+ end
57
+
58
+ === Options passed through the <tt>rake</tt> command line
59
+
60
+ You can override the options defined in the RcovTask by passing the new
61
+ options at the time you invoke rake.
62
+ The documentation for the Rcov::RcovTask explains how this can be done.
@@ -0,0 +1,68 @@
1
+
2
+ == Code coverage analysis automation with Rant
3
+
4
+ Since 0.5.0, <tt>rcov</tt> features a <tt>Rcov</tt> generator for eant
5
+ which can be used to automate test coverage analysis. Basic usage is as
6
+ follows:
7
+
8
+ require 'rcov/rant'
9
+
10
+ desc "Create a cross-referenced code coverage report."
11
+ gen Rcov do |g|
12
+ g.test_files = sys['test/test*.rb']
13
+ end
14
+
15
+ This will create by default a task named <tt>rcov</tt>.
16
+
17
+ === Passing command line options to <tt>rcov</tt>
18
+
19
+ You can provide a description, change the name of the generated tasks (the
20
+ one used to generate the report(s) and the clobber_ one) and pass options to
21
+ <tt>rcov</tt>:
22
+
23
+ desc "Create cross-referenced code coverage report."
24
+ gen Rcov, :coverage do |g|
25
+ g.test_files = sys['test/test*.rb']
26
+ g.rcov_opts << "--threshold 80" << "--callsites"
27
+ end
28
+
29
+ That will generate a <tt>coverage</tt> task.
30
+
31
+ You can specify a different destination directory, which comes handy if you
32
+ have several rcov tasks:
33
+
34
+ desc "Analyze code coverage for the FileStatistics class."
35
+ gen Rcov, :rcov_sourcefile do |g|
36
+ g.libs << "ext/rcovrt"
37
+ g.test_files = sys['test/test_FileStatistics.rb']
38
+ g.rcov_opts << "--test-unit-only"
39
+ g.output_dir = "coverage.sourcefile"
40
+ end
41
+
42
+ desc "Analyze code coverage for CodeCoverageAnalyzer."
43
+ gen Rcov, :rcov_ccanalyzer do |g|
44
+ g.libs << "ext/rcovrt"
45
+ g.test_files = sys['test/test_CodeCoverageAnalyzer.rb']
46
+ g.rcov_opts << "--test-unit-only"
47
+ g.output_dir = "coverage.ccanalyzer"
48
+ end
49
+
50
+ === Options specified passed to the generator
51
+
52
+ The +Rcov+ generator recognizes the following options:
53
+ +libs+:: directories to be added to the <tt>$LOAD_PATH</tt>
54
+ +rcov_opts+:: array of options to be passed to rcov
55
+ +test_files+:: files to execute
56
+ +test_dirs+:: directories where to look for test files automatically
57
+ +pattern+:: pattern for automatic discovery of unit tests to be executed
58
+ +output_dir+:: directory where to leave the generated reports
59
+
60
+ +test_files+ overrides the combination of +test_dirs+ and +pattern+.
61
+
62
+
63
+ === Options passed through the <tt>rake</tt> command line
64
+
65
+ You can override the options defined in the Rcov tasks by specifying them
66
+ using environment variables at the time rant is executed.
67
+ RCOVPATH=/my/modified/rcov rant rcov # use the specified rcov executable
68
+ RCOVOPTS="--no-callsites -x foo" rant rcov # pass those options to rcov
@@ -0,0 +1,161 @@
1
+ # This Rakefile serves as an example of how to use Rcov::RcovTask.
2
+ # Take a look at the RDoc documentation (or README.rake) for further
3
+ # information.
4
+
5
+ $:.unshift "lib" if File.directory? "lib"
6
+ require 'rcov/rcovtask'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+
10
+ # Use the specified rcov executable instead of the one in $PATH
11
+ # (this way we get a sort of informal functional test).
12
+ # This could also be specified from the command like, e.g.
13
+ # rake rcov RCOVPATH=/path/to/myrcov
14
+ ENV["RCOVPATH"] = "bin/rcov"
15
+
16
+ # The following task is largely equivalent to:
17
+ # Rcov::RcovTask.new
18
+ # (really!)
19
+ desc "Create a cross-referenced code coverage report."
20
+ Rcov::RcovTask.new do |t|
21
+ t.libs << "ext/rcovrt"
22
+ t.test_files = FileList['test/test*.rb']
23
+ t.rcov_opts << "--callsites" # comment to disable cross-references
24
+ t.verbose = true
25
+ end
26
+
27
+ desc "Analyze code coverage for the FileStatistics class."
28
+ Rcov::RcovTask.new(:rcov_sourcefile) do |t|
29
+ t.libs << "ext/rcovrt"
30
+ t.test_files = FileList['test/test_FileStatistics.rb']
31
+ t.verbose = true
32
+ t.rcov_opts << "--test-unit-only"
33
+ t.output_dir = "coverage.sourcefile"
34
+ end
35
+
36
+ Rcov::RcovTask.new(:rcov_ccanalyzer) do |t|
37
+ t.libs << "ext/rcovrt"
38
+ t.test_files = FileList['test/test_CodeCoverageAnalyzer.rb']
39
+ t.verbose = true
40
+ t.rcov_opts << "--test-unit-only"
41
+ t.output_dir = "coverage.ccanalyzer"
42
+ end
43
+
44
+ desc "Run the unit tests with rcovrt."
45
+ Rake::TestTask.new(:test_rcovrt => ["ext/rcovrt/rcovrt.so"]) do |t|
46
+ t.libs << "ext/rcovrt"
47
+ t.test_files = FileList['test/test*.rb']
48
+ t.verbose = true
49
+ end
50
+
51
+ file "ext/rcovrt/rcovrt.so" => "ext/rcovrt/rcov.c" do
52
+ ruby "setup.rb config"
53
+ ruby "setup.rb setup"
54
+ end
55
+
56
+ desc "Run the unit tests in pure-Ruby mode."
57
+ Rake::TestTask.new(:test_pure_ruby) do |t|
58
+ t.libs << "ext/rcovrt"
59
+ t.test_files = FileList['test/turn_off_rcovrt.rb', 'test/test*.rb']
60
+ t.verbose = true
61
+ end
62
+
63
+ desc "Run the unit tests, both rcovrt and pure-Ruby modes"
64
+ task :test => [:test_rcovrt, :test_pure_ruby]
65
+
66
+ task :default => :test
67
+
68
+ desc "Generate rdoc documentation for the rcov library"
69
+ Rake::RDocTask.new("rdoc") { |rdoc|
70
+ rdoc.rdoc_dir = 'doc'
71
+ rdoc.title = "rcov"
72
+ rdoc.options << "--line-numbers" << "--inline-source"
73
+ rdoc.rdoc_files.include('README.API')
74
+ rdoc.rdoc_files.include('README.rake')
75
+ rdoc.rdoc_files.include('README.rant')
76
+ rdoc.rdoc_files.include('lib/**/*.rb')
77
+ }
78
+
79
+
80
+ # {{{ Package tasks
81
+
82
+ require 'rcov/version'
83
+
84
+ PKG_REVISION = ".1"
85
+ PKG_FILES = FileList[
86
+ "bin/rcov",
87
+ "lib/**/*.rb",
88
+ "ext/rcovrt/extconf.rb",
89
+ "ext/rcovrt/rcov.c",
90
+ "LEGAL", "LICENSE", "Rakefile", "Rantfile", "README.*", "THANKS", "test/*.rb",
91
+ "mingw-rbconfig.rb",
92
+ "setup.rb", "BLURB", "CHANGES"
93
+ ]
94
+
95
+ require 'rake/gempackagetask'
96
+ Spec = Gem::Specification.new do |s|
97
+ s.name = "rcov"
98
+ s.version = Rcov::VERSION + PKG_REVISION
99
+ s.summary = "Code coverage analysis tool for Ruby"
100
+ s.description = <<EOF
101
+ rcov is a code coverage tool for Ruby. It is commonly used for viewing overall
102
+ test unit coverage of target code. It features fast execution (20-300 times
103
+ faster than previous tools), multiple analysis modes, XHTML and several kinds
104
+ of text reports, easy automation with Rake via a RcovTask, fairly accurate
105
+ coverage information through code linkage inference using simple heuristics,
106
+ colorblind-friendliness...
107
+ EOF
108
+ s.files = PKG_FILES.to_a
109
+ s.require_path = 'lib'
110
+ s.extensions << "ext/rcovrt/extconf.rb"
111
+ s.author = "Mauricio Fernandez"
112
+ s.email = "mfp@acm.org"
113
+ s.homepage = "http://eigenclass.org/hiki.rb?rcov"
114
+ s.bindir = "bin" # Use these for applications.
115
+ s.executables = ["rcov"]
116
+ s.has_rdoc = true
117
+ s.extra_rdoc_files = %w[README.API README.rake]
118
+ s.rdoc_options << "--main" << "README.API" << "--title" << 'rcov code coverage tool'
119
+ s.test_files = Dir["test/test_*.rb"]
120
+ end
121
+
122
+ task :gem => [:test]
123
+ Rake::GemPackageTask.new(Spec) do |p|
124
+ p.need_tar = true
125
+ end
126
+
127
+ # {{{ Cross-compilation and building of a binary RubyGems package for mswin32
128
+
129
+ require 'rake/clean'
130
+
131
+ WIN32_PKG_DIR = "rcov-" + Rcov::VERSION + PKG_REVISION
132
+
133
+ file "#{WIN32_PKG_DIR}" => [:package] do
134
+ sh "tar zxf pkg/#{WIN32_PKG_DIR}.tgz"
135
+ end
136
+
137
+ desc "Cross-compile the rcovrt.so extension for win32"
138
+ file "rcovrt_win32" => ["#{WIN32_PKG_DIR}"] do
139
+ cp "mingw-rbconfig.rb", "#{WIN32_PKG_DIR}/ext/rcovrt/rbconfig.rb"
140
+ sh "cd #{WIN32_PKG_DIR}/ext/rcovrt/ && ruby -I. extconf.rb && make"
141
+ mv "#{WIN32_PKG_DIR}/ext/rcovrt/rcovrt.so", "#{WIN32_PKG_DIR}/lib"
142
+ end
143
+
144
+ Win32Spec = Spec.clone
145
+ Win32Spec.platform = Gem::Platform::WIN32
146
+ Win32Spec.extensions = []
147
+ Win32Spec.files += ["lib/rcovrt.so"]
148
+
149
+ desc "Build the binary RubyGems package for win32"
150
+ task :rubygems_win32 => ["rcovrt_win32"] do
151
+ Dir.chdir("#{WIN32_PKG_DIR}") do
152
+ Gem::Builder.new(Win32Spec).build
153
+ verbose(true) {
154
+ mv Dir["*.gem"].first, "../pkg/rcov-#{Rcov::VERSION + PKG_REVISION}-mswin32.gem"
155
+ }
156
+ end
157
+ end
158
+
159
+ CLEAN.include "#{WIN32_PKG_DIR}"
160
+
161
+ # vim: set sw=2 ft=ruby:
@@ -0,0 +1,75 @@
1
+ # This Rantfile serves as an example of how to use the Rcov generator.
2
+ # Take a look at the RDoc documentation (or README.rant) for further
3
+ # information.
4
+
5
+ $:.unshift "lib" if File.directory? "lib"
6
+
7
+ import %w(rubytest rubydoc autoclean)
8
+ require 'rcov/rant'
9
+
10
+ task :default => :test
11
+
12
+ # Use the specified rcov executable instead of the one in $PATH
13
+ # (this way we get a sort of informal functional test).
14
+ # This could also be specified from the command like, e.g.
15
+ # rake rcov RCOVPATH=/path/to/myrcov
16
+ ENV["RCOVPATH"] = "bin/rcov"
17
+
18
+ desc "Create a cross-referenced code coverage report."
19
+ gen Rcov do |g|
20
+ g.libs << "ext/rcovrt"
21
+ g.test_files = sys['test/test*.rb']
22
+ g.rcov_opts << "--callsites" # comment to disable cross-references
23
+ end
24
+
25
+ desc "Analyze code coverage for the FileStatistics class."
26
+ gen Rcov, :rcov_sourcefile do |g|
27
+ g.libs << "ext/rcovrt"
28
+ g.test_files = sys['test/test_FileStatistics.rb']
29
+ g.rcov_opts << "--test-unit-only"
30
+ g.output_dir = "coverage.sourcefile"
31
+ end
32
+
33
+ desc "Analyze code coverage for CodeCoverageAnalyzer."
34
+ gen Rcov, :rcov_ccanalyzer do |g|
35
+ g.libs << "ext/rcovrt"
36
+ g.test_files = sys['test/test_CodeCoverageAnalyzer.rb']
37
+ g.rcov_opts << "--test-unit-only"
38
+ g.output_dir = "coverage.ccanalyzer"
39
+ end
40
+
41
+ desc "Run the unit tests, both rcovrt and pure-Ruby modes"
42
+ task :test => [:test_rcovrt, :test_pure_ruby]
43
+
44
+ desc "Run the unit tests with rcovrt."
45
+ gen RubyTest, :test_rcovrt => %w[ext/rcovrt/rcovrt.so] do |g|
46
+ g.libs << "ext/rcovrt"
47
+ g.test_files = sys['test/test*.rb']
48
+ g.verbose = true
49
+ end
50
+
51
+ file "ext/rcovrt/rcovrt.so" => "ext/rcovrt/rcov.c" do
52
+ sys "ruby setup.rb config"
53
+ sys "ruby setup.rb setup"
54
+ end
55
+
56
+ desc "Run the unit tests in pure-Ruby mode."
57
+ gen RubyTest, :test_pure_ruby do |g|
58
+ g.libs << "ext/rcovrt"
59
+ g.test_files = sys['test/turn_off_rcovrt.rb', 'test/test*.rb']
60
+ g.verbose = true
61
+ end
62
+
63
+ desc "Generate documentation."
64
+ gen RubyDoc, :rdoc do |g|
65
+ g.verbose = true
66
+ g.dir = "doc"
67
+ g.files = sys["README.API", "README.rake", "README.rant", "lib/**/*.rb"]
68
+ g.opts = %w(--line-numbers --inline-source --title rcov --main README.API)
69
+ end
70
+
71
+ desc "Remove autogenerated files."
72
+ gen AutoClean, :clean
73
+ var[:clean].include %w(InstalledFiles .config coverage coverage.* )
74
+
75
+ # vim: set sw=2 ft=ruby:
data/THANKS ADDED
@@ -0,0 +1,29 @@
1
+
2
+ Tom Dolbilin:
3
+ * identified and fixed backslash problem on win32 for generated filenames
4
+
5
+ Andrew Kreiling:
6
+ * made the index XHTML compliant
7
+ * consolidate multiple references to the same underlying .rb file
8
+
9
+ Robert Feldt:
10
+ * pointed me to dynamic uses of the tracing hooks, provided the inspiration
11
+ for RCOV__.run_hooked
12
+ * helped to refine the color scheme
13
+
14
+ Andre Nathan:
15
+ * identified a bug in the heuristics: missing propagation for lines
16
+ with only }, ), ]
17
+
18
+ David Roberts:
19
+ * reported confusing behavior when all files are ignored because they match
20
+ a regexp in the reject list
21
+ * tested the RubyGems package for win32
22
+
23
+ John-Mason Shackelford:
24
+ * reported an important bug in the pure-Ruby tracer module, which broke it
25
+ altogether in 0.4.0
26
+ * suggested a change in the CSS to make XHTML reports more readable under IE
27
+
28
+ Dave Burt:
29
+ * reported an issue with text reports under cmd.exe (should use < 80 cols)
@@ -0,0 +1,1839 @@
1
+ #! /home/batsman/usr/bin/ruby
2
+ # rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
3
+ #
4
+ # rcov originally based on
5
+ # module COVERAGE__ originally (c) NAKAMURA Hiroshi
6
+ # module PrettyCoverage originally (c) Simon Strandgaard
7
+ #
8
+ # rewritten & extended by Mauricio Fern�ndez <mfp@acm.org>
9
+ #
10
+ # See LEGAL and LICENSE for additional licensing information.
11
+ #
12
+
13
+ require 'cgi'
14
+ require 'rbconfig'
15
+ require 'optparse'
16
+ require 'ostruct'
17
+ # load xx-0.1.0-1
18
+ eval File.read(File.expand_path(__FILE__)).gsub(/.*^__END__$/m,"")
19
+
20
+ # extend XX
21
+ module XX
22
+ module XMLish
23
+ include Markup
24
+
25
+ def xmlish_ *a, &b
26
+ xx_which(XMLish){ xx_with_doc_in_effect(*a, &b)}
27
+ end
28
+ end
29
+ end
30
+
31
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
32
+ require 'rcov/version'
33
+
34
+ module Rcov
35
+
36
+ class Formatter
37
+ ignore_files = [/\A#{Regexp.escape(Config::CONFIG["libdir"])}/, /\btc_[^.]*.rb/,
38
+ /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
39
+ DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
40
+ :output_threshold => 101, :dont_ignore => [],
41
+ :callsite_analyzer => nil}
42
+ def initialize(opts = {})
43
+ options = DEFAULT_OPTS.clone.update(opts)
44
+ @files = {}
45
+ @ignore_files = options[:ignore]
46
+ @dont_ignore_files = options[:dont_ignore]
47
+ @sort_criterium = case options[:sort]
48
+ when :loc : lambda{|fname, finfo| finfo.num_code_lines}
49
+ when :coverage : lambda{|fname, finfo| finfo.code_coverage}
50
+ else lambda{|fname, finfo| fname}
51
+ end
52
+ @sort_reverse = options[:sort_reverse]
53
+ @output_threshold = options[:output_threshold]
54
+ @callsite_analyzer = options[:callsite_analyzer]
55
+ @callsite_index = nil
56
+ end
57
+
58
+ def add_file(filename, lines, coverage, counts)
59
+ old_filename = filename
60
+ filename = normalize_filename(filename)
61
+ SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
62
+ if @ignore_files.any?{|x| x === filename} &&
63
+ !@dont_ignore_files.any?{|x| x === filename}
64
+ return nil
65
+ end
66
+ if @files[filename]
67
+ @files[filename].merge(lines, coverage, counts)
68
+ else
69
+ @files[filename] = FileStatistics.new filename, lines, counts
70
+ end
71
+ end
72
+
73
+ def normalize_filename(filename)
74
+ File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
75
+ end
76
+
77
+ def each_file_pair_sorted(&b)
78
+ return sorted_file_pairs unless block_given?
79
+ sorted_file_pairs.each(&b)
80
+ end
81
+
82
+ def sorted_file_pairs
83
+ pairs = @files.sort_by do |fname, finfo|
84
+ @sort_criterium.call(fname, finfo)
85
+ end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
86
+ @sort_reverse ? pairs.reverse : pairs
87
+ end
88
+
89
+ def total_coverage
90
+ lines = 0
91
+ total = 0.0
92
+ @files.each do |k,f|
93
+ total += f.num_lines * f.total_coverage
94
+ lines += f.num_lines
95
+ end
96
+ return 0 if lines == 0
97
+ total / lines
98
+ end
99
+
100
+ def code_coverage
101
+ lines = 0
102
+ total = 0.0
103
+ @files.each do |k,f|
104
+ total += f.num_code_lines * f.code_coverage
105
+ lines += f.num_code_lines
106
+ end
107
+ return 0 if lines == 0
108
+ total / lines
109
+ end
110
+
111
+ def num_code_lines
112
+ lines = 0
113
+ @files.each{|k, f| lines += f.num_code_lines }
114
+ lines
115
+ end
116
+
117
+ def num_lines
118
+ lines = 0
119
+ @files.each{|k, f| lines += f.num_lines }
120
+ lines
121
+ end
122
+
123
+ private
124
+ def cross_references_for(filename, lineno)
125
+ return nil unless @callsite_analyzer
126
+ @callsite_index ||= build_callsite_index
127
+ @callsite_index[normalize_filename(filename)][lineno]
128
+ end
129
+
130
+ def build_callsite_index
131
+ index = Hash.new{|h,k| h[k] = {}}
132
+ @callsite_analyzer.analyzed_classes.each do |classname|
133
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
134
+ defsite = @callsite_analyzer.defsite(classname, methname)
135
+ index[normalize_filename(defsite.file)][defsite.line] =
136
+ @callsite_analyzer.callsites(classname, methname)
137
+ end
138
+ end
139
+ index
140
+ end
141
+ end
142
+
143
+ class TextSummary < Formatter
144
+ def execute
145
+ puts summary
146
+ end
147
+
148
+ def summary
149
+ "%.1f%% %d file(s) %d Lines %d LOC" % [code_coverage * 100,
150
+ @files.size, num_lines, num_code_lines]
151
+ end
152
+ end
153
+
154
+ class TextReport < TextSummary
155
+ def execute
156
+ print_lines
157
+ print_header
158
+ print_lines
159
+ each_file_pair_sorted do |fname, finfo|
160
+ name = fname.size < 52 ? fname : "..." + fname[-48..-1]
161
+ print_info(name, finfo.num_lines, finfo.num_code_lines,
162
+ finfo.code_coverage)
163
+ end
164
+ print_lines
165
+ print_info("Total", num_lines, num_code_lines, code_coverage)
166
+ print_lines
167
+ puts summary
168
+ end
169
+
170
+ def print_info(name, lines, loc, coverage)
171
+ puts "|%-51s | %5d | %5d | %5.1f%% |" % [name, lines, loc, 100 * coverage]
172
+ end
173
+
174
+ def print_lines
175
+ puts "+----------------------------------------------------+-------+-------+--------+"
176
+ end
177
+
178
+ def print_header
179
+ puts "| File | Lines | LOC | COV |"
180
+ end
181
+ end
182
+
183
+ class TextCoverage < Formatter
184
+ DEFAULT_OPTS = {:textmode => :coverage}
185
+ def initialize(opts = {})
186
+ options = DEFAULT_OPTS.clone.update(opts)
187
+ @textmode = options[:textmode]
188
+ @color = options[:color]
189
+ super(options)
190
+ end
191
+
192
+ def execute
193
+ each_file_pair_sorted do |filename, fileinfo|
194
+ puts "=" * 80
195
+ puts filename
196
+ puts "=" * 80
197
+ SCRIPT_LINES__[filename].each_with_index do |line, i|
198
+ case @textmode
199
+ when :counts
200
+ puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
201
+ when :coverage
202
+ if @color
203
+ prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
204
+ puts "#{prefix}%s\e[37;40m" % line.chomp
205
+ else
206
+ prefix = fileinfo.coverage[i] ? " " : "## "
207
+ puts "#{prefix}#{line}"
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+
216
+ class HTMLCoverage < Formatter
217
+ include XX::XHTML
218
+ include XX::XMLish
219
+ require 'fileutils'
220
+ JAVASCRIPT_PROLOG = <<-EOS
221
+
222
+ // <![CDATA[
223
+ function toggleCode( id ) {
224
+ if ( document.getElementById )
225
+ elem = document.getElementById( id );
226
+ else if ( document.all )
227
+ elem = eval( "document.all." + id );
228
+ else
229
+ return false;
230
+
231
+ elemStyle = elem.style;
232
+
233
+ if ( elemStyle.display != "block" ) {
234
+ elemStyle.display = "block"
235
+ } else {
236
+ elemStyle.display = "none"
237
+ }
238
+
239
+ return true;
240
+ }
241
+
242
+ // Make cross-references hidden by default
243
+ document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
244
+
245
+ // ]]>
246
+ EOS
247
+
248
+ CSS_PROLOG = <<-EOS
249
+ span.marked0 {
250
+ background-color: rgb(185, 210, 200);
251
+ display: block;
252
+ }
253
+ span.marked1 {
254
+ background-color: rgb(190, 215, 205);
255
+ display: block;
256
+ }
257
+ span.inferred0 {
258
+ background-color: rgb(175, 200, 200);
259
+ display: block;
260
+ }
261
+ span.inferred1 {
262
+ background-color: rgb(180, 205, 205);
263
+ display: block;
264
+ }
265
+ span.uncovered0 {
266
+ background-color: rgb(225, 110, 110);
267
+ display: block;
268
+ }
269
+ span.uncovered1 {
270
+ background-color: rgb(235, 120, 120);
271
+ display: block;
272
+ }
273
+ span.overview {
274
+ border-bottom: 8px solid black;
275
+ }
276
+ div.overview {
277
+ border-bottom: 8px solid black;
278
+ }
279
+ body {
280
+ font-family: verdana, arial, helvetica;
281
+ }
282
+
283
+ div.footer {
284
+ font-size: 68%;
285
+ margin-top: 1.5em;
286
+ }
287
+
288
+ h1, h2, h3, h4, h5, h6 {
289
+ margin-bottom: 0.5em;
290
+ }
291
+
292
+ h5 {
293
+ margin-top: 0.5em;
294
+ }
295
+
296
+ .hidden {
297
+ display: none;
298
+ }
299
+
300
+ div.separator {
301
+ height: 10px;
302
+ }
303
+ /* Commented out for better readability, esp. on IE */
304
+ /*
305
+ table tr td, table tr th {
306
+ font-size: 68%;
307
+ }
308
+
309
+ td.value table tr td {
310
+ font-size: 11px;
311
+ }
312
+ */
313
+
314
+ table.percent_graph {
315
+ height: 12px;
316
+ border: #808080 1px solid;
317
+ empty-cells: show;
318
+ }
319
+
320
+ table.percent_graph td.covered {
321
+ height: 10px;
322
+ background: #00f000;
323
+ }
324
+
325
+ table.percent_graph td.uncovered {
326
+ height: 10px;
327
+ background: #e00000;
328
+ }
329
+
330
+ table.percent_graph td.NA {
331
+ height: 10px;
332
+ background: #eaeaea;
333
+ }
334
+
335
+ table.report {
336
+ border-collapse: collapse;
337
+ width: 100%;
338
+ }
339
+
340
+ table.report td.heading {
341
+ background: #dcecff;
342
+ border: #d0d0d0 1px solid;
343
+ font-weight: bold;
344
+ text-align: center;
345
+ }
346
+
347
+ table.report td.heading:hover {
348
+ background: #c0ffc0;
349
+ }
350
+
351
+ table.report td.text {
352
+ border: #d0d0d0 1px solid;
353
+ }
354
+
355
+ table.report td.value {
356
+ text-align: right;
357
+ border: #d0d0d0 1px solid;
358
+ }
359
+ table.report tr.light {
360
+ background-color: rgb(240, 240, 245);
361
+ }
362
+ table.report tr.dark {
363
+ background-color: rgb(230, 230, 235);
364
+ }
365
+ EOS
366
+
367
+ DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage"}
368
+ def initialize(opts = {})
369
+ options = DEFAULT_OPTS.clone.update(opts)
370
+ super(options)
371
+ @dest = options[:destdir]
372
+ @color = options[:color]
373
+ @fsr = options[:fsr]
374
+ @span_class_index = 0
375
+ end
376
+
377
+ def execute
378
+ return if @files.empty?
379
+ FileUtils.mkdir_p @dest
380
+ create_index(File.join(@dest, "index.html"))
381
+ each_file_pair_sorted do |filename, fileinfo|
382
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
383
+ end
384
+ end
385
+
386
+ def mangle_filename(base)
387
+ base.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
388
+ end
389
+
390
+ private
391
+
392
+ def blurb
393
+ xmlish_ {
394
+ p_ {
395
+ t_{ "Generated using the " }
396
+ a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
397
+ t_{ "rcov code coverage analysis tool for Ruby" }
398
+ }
399
+ t_{ " version #{Rcov::VERSION}." }
400
+ }
401
+ }.pretty
402
+ end
403
+
404
+ def output_color_table?
405
+ true
406
+ end
407
+
408
+ def default_color
409
+ "rgb(240, 240, 245)"
410
+ end
411
+
412
+ def default_title
413
+ "C0 code coverage information"
414
+ end
415
+
416
+ def format_overview(*file_infos)
417
+ table_text = xmlish_ {
418
+ table_(:class => "report") {
419
+ thead_ {
420
+ tr_ {
421
+ ["Name", "Total lines", "Lines of code", "Total coverage",
422
+ "Code coverage"].each do |heading|
423
+ td_(:class => "heading") { heading }
424
+ end
425
+ }
426
+ }
427
+ tbody_ {
428
+ color_class_index = 1
429
+ color_classes = %w[light dark]
430
+ file_infos.each do |f|
431
+ color_class_index += 1
432
+ color_class_index %= color_classes.size
433
+ tr_(:class => color_classes[color_class_index]) {
434
+ td_ {
435
+ case f.name
436
+ when "TOTAL":
437
+ t_ { "TOTAL" }
438
+ else
439
+ a_(:href => mangle_filename(f.name)){ t_ { f.name } }
440
+ end
441
+ }
442
+ [f.num_lines, f.num_code_lines].each do |value|
443
+ td_(:class => "value") { tt_{ value } }
444
+ end
445
+ [f.total_coverage, f.code_coverage].each do |value|
446
+ value *= 100
447
+ td_ {
448
+ table_(:cellpadding => 0, :cellspacing => 0, :align => "right") {
449
+ tr_ {
450
+ td_ {
451
+ tt_ { "%3.1f%%" % value }
452
+ x_ "&nbsp;"
453
+ }
454
+ ivalue = value.round
455
+ td_ {
456
+ table_(:class => "percent_graph", :cellpadding => 0,
457
+ :cellspacing => 0, :width => 100) {
458
+ tr_ {
459
+ td_(:class => "covered", :width => ivalue)
460
+ td_(:class => "uncovered", :width => (100-ivalue))
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ }
467
+ end
468
+ }
469
+ end
470
+ }
471
+ }
472
+ }
473
+ table_text.pretty
474
+ end
475
+
476
+ class SummaryFileInfo
477
+ def initialize(obj); @o = obj end
478
+ %w[num_lines num_code_lines code_coverage total_coverage].each do |m|
479
+ define_method(m){ @o.send(m) }
480
+ end
481
+ def name; "TOTAL" end
482
+ end
483
+
484
+ def create_index(destname)
485
+ files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
486
+ title = default_title
487
+ output = xhtml_ { html_ {
488
+ head_ {
489
+ title_{ title }
490
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
491
+ style_(:type => "text/css") { CSS_PROLOG }
492
+ script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
493
+ }
494
+ body_ {
495
+ h3_{
496
+ t_{ title }
497
+ }
498
+ p_ {
499
+ t_{ "Generated on #{Time.new.to_s} with " }
500
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
501
+ }
502
+ p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
503
+ hr_
504
+ x_{ format_overview(*files) }
505
+ hr_
506
+ x_{ blurb }
507
+ p_ {
508
+ a_(:href => "http://validator.w3.org/check/referer") {
509
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
510
+ :alt => "Valid XHTML 1.1!", :height => 31, :width => 88)
511
+ }
512
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
513
+ img_(:style => "border:0;width:88px;height:31px",
514
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
515
+ :alt => "Valid CSS!")
516
+ }
517
+ }
518
+ }
519
+ } }
520
+ lines = output.pretty.to_a
521
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
522
+ File.open(destname, "w") do |f|
523
+ f.puts lines
524
+ end
525
+ end
526
+
527
+ def format_lines(file)
528
+ result = ""
529
+ last = nil
530
+ end_of_span = ""
531
+ format_line = "%#{file.num_lines.to_s.size}d"
532
+ file.num_lines.times do |i|
533
+ line = file.lines[i]
534
+ marked = file.coverage[i]
535
+ count = file.counts[i]
536
+ spanclass = span_class(file, marked, count)
537
+ if spanclass != last
538
+ result += end_of_span
539
+ case spanclass
540
+ when nil
541
+ end_of_span = ""
542
+ else
543
+ result += %[<span class="#{spanclass}">]
544
+ end_of_span = "</span>"
545
+ end
546
+ end
547
+ result += %[<a name="line#{i+1}" />] + (format_line % (i+1)) +
548
+ " " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
549
+ last = spanclass
550
+ end
551
+ result += end_of_span
552
+ "<pre>#{result}</pre>"
553
+ end
554
+
555
+ def create_cross_refs(filename, lineno, linetext)
556
+ return linetext unless @callsite_analyzer
557
+ @cross_ref_idx ||= 0
558
+ ret = ""
559
+ refs = cross_references_for(filename, lineno)
560
+ return linetext unless refs
561
+ refs = refs.sort_by{|k,count| count}
562
+ ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
563
+ ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
564
+ ret << %[\nThis method was called by:\n\n]
565
+ known_files = sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
566
+ refs.reverse_each do |dst, count|
567
+ dstfile = normalize_filename(dst.file)
568
+ dstline = dst.line
569
+ calling_method = dst.calling_method
570
+ label = "%7d %s" %
571
+ [count, CGI.escapeHTML("#{dstfile}:#{dstline} in '#{calling_method}'")]
572
+ if known_files.include? dstfile
573
+ ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
574
+ else
575
+ ret << label
576
+ end
577
+ ret << "\n"
578
+ end
579
+ ret << "</span>"
580
+ end
581
+
582
+ def span_class(sourceinfo, marked, count)
583
+ @span_class_index ^= 1
584
+ case marked
585
+ when true
586
+ "marked#{@span_class_index}"
587
+ when :inferred
588
+ "inferred#{@span_class_index}"
589
+ else
590
+ "uncovered#{@span_class_index}"
591
+ end
592
+ end
593
+
594
+ def create_file(destfile, fileinfo)
595
+ #$stderr.puts "Generating #{destfile.inspect}"
596
+ body = format_overview(fileinfo) + format_lines(fileinfo)
597
+ title = fileinfo.name + " - #{default_title}"
598
+ do_ctable = output_color_table?
599
+ output = xhtml_ { html_ {
600
+ head_ {
601
+ title_{ title }
602
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
603
+ style_(:type => "text/css") { CSS_PROLOG }
604
+ script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
605
+ style_(:type => "text/css") { h_ { colorscale } }
606
+ }
607
+ body_ {
608
+ h3_{ t_{ default_title } }
609
+ p_ {
610
+ t_{ "Generated on #{Time.new.to_s} with " }
611
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
612
+ }
613
+ hr_
614
+ if do_ctable
615
+ # this kludge needed to ensure .pretty doesn't mangle it
616
+ x_ { <<EOS
617
+ <pre><span class='marked0'>Code reported as executed by Ruby looks like this...
618
+ </span><span class='marked1'>and this: this line is also marked as covered.
619
+ </span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
620
+ </span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
621
+ </span><span class='uncovered0'>Finally, here&apos;s a line marked as not executed.
622
+ </span></pre>
623
+ EOS
624
+ }
625
+ end
626
+ x_{ body }
627
+ hr_
628
+ x_ { blurb }
629
+ p_ {
630
+ a_(:href => "http://validator.w3.org/check/referer") {
631
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
632
+ :alt => "Valid XHTML 1.0!", :height => 31, :width => 88)
633
+ }
634
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
635
+ img_(:style => "border:0;width:88px;height:31px",
636
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
637
+ :alt => "Valid CSS!")
638
+ }
639
+ }
640
+ }
641
+ } }
642
+ # .pretty needed to make sure DOCTYPE is in a separate line
643
+ lines = output.pretty.to_a
644
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
645
+ File.open(destfile, "w") do |f|
646
+ f.puts lines
647
+ end
648
+ end
649
+
650
+ def colorscale
651
+ colorscalebase =<<EOF
652
+ span.run%d {
653
+ background-color: rgb(%d, %d, %d);
654
+ display: block;
655
+ }
656
+ EOF
657
+ cscale = ""
658
+ 101.times do |i|
659
+ if @color
660
+ r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
661
+ r = (r * 255).to_i
662
+ g = (g * 255).to_i
663
+ b = (b * 255).to_i
664
+ else
665
+ r = g = b = 255 - i
666
+ end
667
+ cscale << colorscalebase % [i, r, g, b]
668
+ end
669
+ cscale
670
+ end
671
+
672
+ # thanks to kig @ #ruby-lang for this one
673
+ def hsv2rgb(h,s,v)
674
+ return [v,v,v] if s == 0
675
+ h = h/60.0
676
+ i = h.floor
677
+ f = h-i
678
+ p = v * (1-s)
679
+ q = v * (1-s*f)
680
+ t = v * (1-s*(1-f))
681
+ case i
682
+ when 0
683
+ r = v
684
+ g = t
685
+ b = p
686
+ when 1
687
+ r = q
688
+ g = v
689
+ b = p
690
+ when 2
691
+ r = p
692
+ g = v
693
+ b = t
694
+ when 3
695
+ r = p
696
+ g = q
697
+ b = v
698
+ when 4
699
+ r = t
700
+ g = p
701
+ b = v
702
+ when 5
703
+ r = v
704
+ g = p
705
+ b = q
706
+ end
707
+ [r,g,b]
708
+ end
709
+ end
710
+
711
+ class HTMLProfiling < HTMLCoverage
712
+
713
+ DEFAULT_OPTS = {:destdir => "profiling"}
714
+ def initialize(opts = {})
715
+ options = DEFAULT_OPTS.clone.update(opts)
716
+ super(options)
717
+ @max_cache = {}
718
+ @median_cache = {}
719
+ end
720
+
721
+ def default_title
722
+ "Bogo-profile information"
723
+ end
724
+
725
+ def default_color
726
+ if @color
727
+ "rgb(179,205,255)"
728
+ else
729
+ "rgb(255, 255, 255)"
730
+ end
731
+ end
732
+
733
+ def output_color_table?
734
+ false
735
+ end
736
+
737
+ def span_class(sourceinfo, marked, count)
738
+ full_scale_range = @fsr # dB
739
+ nz_count = sourceinfo.counts.select{|x| x && x != 0}
740
+ nz_count << 1 # avoid div by 0
741
+ max = @max_cache[sourceinfo] ||= nz_count.max
742
+ #avg = @median_cache[sourceinfo] ||= 1.0 *
743
+ # nz_count.inject{|a,b| a+b} / nz_count.size
744
+ median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
745
+ max ||= 2
746
+ max = 2 if max == 1
747
+ if marked == true
748
+ count = 1 if !count || count == 0
749
+ idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) /
750
+ Math.log(10)
751
+ idx = idx.to_i
752
+ idx = 0 if idx < 0
753
+ idx = 100 if idx > 100
754
+ "run#{idx}"
755
+ else
756
+ nil
757
+ end
758
+ end
759
+ end
760
+
761
+ end # Rcov
762
+
763
+ #{{{ only run if executed directly
764
+
765
+ #{{{ "main" code
766
+ options = OpenStruct.new
767
+ options.color = true
768
+ options.range = 30.0
769
+ options.profiling = false
770
+ options.destdir = nil
771
+ options.loadpaths = []
772
+ options.textmode = false
773
+ options.skip = Rcov::Formatter::DEFAULT_OPTS[:ignore]
774
+ options.include = []
775
+ options.nohtml = false
776
+ options.test_unit_only = false
777
+ options.sort = :name
778
+ options.sort_reverse = false
779
+ options.output_threshold = 101
780
+ options.replace_prog_name = false
781
+ options.callsites = false
782
+
783
+ EXTRA_HELP = <<-EOF
784
+
785
+ You can run several programs at once:
786
+ rcov something.rb somethingelse.rb
787
+
788
+ The parameters to be passed to the program under inspection can be specified
789
+ after --:
790
+
791
+ rcov -Ilib -t something.rb -- --theseopts --are --given --to --something.rb
792
+
793
+ ARGV will be set to the specified parameters after --.
794
+ Keep in mind that all the programs are run under the same process
795
+ (i.e. they just get Kernel#load()'ed in sequence).
796
+
797
+ $PROGRAM_NAME (aka. $0) will be set before each file is load()ed if
798
+ --replace-progname is used.
799
+ EOF
800
+
801
+ #{{{ OptionParser
802
+ opts = OptionParser.new do |opts|
803
+ opts.banner = <<-EOF
804
+ rcov #{Rcov::VERSION} #{Rcov::RELEASE_DATE}
805
+ Usage: rcov [options] <script1.rb> [script2.rb] [-- --extra-options]
806
+ EOF
807
+ opts.separator ""
808
+ opts.separator "Options:"
809
+ opts.on("-o", "--output PATH", "Destination directory.") do |dir|
810
+ options.destdir = dir
811
+ end
812
+ opts.on("-I", "--include PATHS",
813
+ "Prepend PATHS to $: (colon separated list)") do |paths|
814
+ options.loadpaths = paths.split(/:/)
815
+ end
816
+ opts.on("--test-unit-only",
817
+ "Only trace code executed in TestCases.") do
818
+ options.test_unit_only = true
819
+ end
820
+ opts.on("-n", "--no-color", "Create colorblind-safe output.") do
821
+ options.color = false
822
+ end
823
+ opts.on("-i", "--include-file PATTERNS",
824
+ "Generate info for files matching a",
825
+ "pattern (comma-separated regexp list)") do |list|
826
+ begin
827
+ regexps = list.split(/,/).map{|x| Regexp.new(x) }
828
+ options.include += regexps
829
+ rescue RegexpError => e
830
+ raise OptionParser::InvalidArgument, e.message
831
+ end
832
+ end
833
+ opts.on("-x", "--exclude PATTERNS",
834
+ "Don't generate info for files matching a",
835
+ "pattern (comma-separated regexp list)") do |list|
836
+ begin
837
+ regexps = list.split(/,/).map{|x| Regexp.new x}
838
+ options.skip += regexps
839
+ rescue RegexpError => e
840
+ raise OptionParser::InvalidArgument, e.message
841
+ end
842
+ end
843
+ opts.on("--exclude-only PATTERNS",
844
+ "Skip info only for files matching the",
845
+ "given patterns.") do |list|
846
+ begin
847
+ options.skip = list.split(/,/).map{|x| Regexp.new(x) }
848
+ rescue RegexpError => e
849
+ raise OptionParser::InvalidArgument, e.message
850
+ end
851
+ end
852
+ opts.on("--rails", "Skip config/, environment/ and vendor/.") do
853
+ options.skip.concat [%r{\bvendor/},%r{\bconfig/},%r{\benvironment/}]
854
+ end
855
+ opts.on("--[no-]callsites", "Show callsites in generated XHTML report.",
856
+ "(much slower; disabled by default)") do |val|
857
+ options.callsites = val
858
+ end
859
+ opts.on("-p", "--profile", "Generate bogo-profiling info.") do
860
+ options.profiling = true
861
+ options.destdir ||= "profiling"
862
+ end
863
+ opts.on("-r", "--range RANGE", Float,
864
+ "Color scale range for profiling info (dB).") do |val|
865
+ options.range = val
866
+ end
867
+ opts.on("-T", "--text-report", "Dump detailed plain-text report to stdout.",
868
+ "(filename, LoC, total lines, coverage)") do
869
+ options.textmode = :report
870
+ end
871
+ opts.on("-t", "--text-summary", "Dump plain-text summary to stdout.") do
872
+ options.textmode = :summary
873
+ end
874
+ opts.on("--text-counts", "Dump execution counts in plaintext.") do
875
+ options.textmode = :counts
876
+ end
877
+ opts.on("--text-coverage", "Dump coverage info to stdout, using",
878
+ "ANSI color sequences unless -n.") do
879
+ options.textmode = :coverage
880
+ end
881
+ opts.on("--no-html", "Don't generate HTML output.",
882
+ "(no output unless text output specified)") do
883
+ options.nohtml = true
884
+ end
885
+ opts.on("--sort CRITERION", [:name, :loc, :coverage],
886
+ "Sort files in the output by the specified",
887
+ "field (name, loc, coverage)") do |criterion|
888
+ options.sort = criterion
889
+ end
890
+ opts.on("--sort-reverse", "Reverse files in the output.") do
891
+ options.sort_reverse = true
892
+ end
893
+ opts.on("--threshold INT", "Only list files with coverage < INT %.",
894
+ "(default: 101)") do |threshold|
895
+ begin
896
+ threshold = Integer(threshold)
897
+ raise if threshold <= 0 || threshold > 101
898
+ rescue Exception
899
+ raise OptionParser::InvalidArgument, threshold
900
+ end
901
+ options.output_threshold = threshold
902
+ end
903
+ opts.on("--only-uncovered", "Same as --threshold 100") do
904
+ options.output_threshold = 100
905
+ end
906
+ opts.on("--replace-progname", "Replace $0 when loading the .rb files.") do
907
+ options.replace_prog_name = true
908
+ end
909
+ opts.on("-w", "Turn warnings on (like ruby).") do
910
+ $VERBOSE = true
911
+ end
912
+ opts.on("--no-rcovrt", "Do not use the optimized C runtime.",
913
+ "(will run 30-300 times slower)") do
914
+ $rcov_do_not_use_rcovrt = true
915
+ end
916
+ opts.separator ""
917
+ opts.on_tail("-h", "--help", "Show help message") do
918
+ require 'pp'
919
+ puts opts
920
+ puts <<EOF
921
+
922
+ Files matching any of the following regexps will be omitted in the report(s):
923
+ #{PP.pp(options.skip, "").chomp}
924
+ EOF
925
+ puts EXTRA_HELP
926
+ exit
927
+ end
928
+ opts.on_tail("--version", "Show version") do
929
+ puts "rcov " + Rcov::VERSION + " " + Rcov::RELEASE_DATE
930
+ exit
931
+ end
932
+ end
933
+
934
+ if (idx = ARGV.index("--"))
935
+ extra_args = ARGV[idx+1..-1]
936
+ ARGV.replace(ARGV[0,idx])
937
+ else
938
+ extra_args = []
939
+ end
940
+
941
+ begin
942
+ opts.parse! ARGV
943
+ rescue OptionParser::InvalidOption, OptionParser::InvalidArgument,
944
+ OptionParser::MissingArgument => e
945
+ puts opts
946
+ puts
947
+ puts e.message
948
+ exit(-1)
949
+ end
950
+ options.destdir ||= "coverage"
951
+ unless ARGV[0]
952
+ puts opts
953
+ exit
954
+ end
955
+
956
+ # {{{ set loadpath
957
+ options.loadpaths.reverse_each{|x| $:.unshift x}
958
+
959
+ #{{{ require 'rcov': do it only now in order to be able to run rcov on itself
960
+ # since we need to set $: before.
961
+
962
+ require 'rcov'
963
+
964
+ if options.callsites
965
+ $rcov_callsite_analyzer = Rcov::CallSiteAnalyzer.new
966
+ $rcov_callsite_analyzer.install_hook
967
+ else
968
+ $rcov_callsite_analyzer = nil
969
+ end
970
+
971
+ # {{{ create formatters
972
+ formatters = []
973
+ make_formatter = lambda do |klass|
974
+ klass.new(:destdir => options.destdir, :color => options.color,
975
+ :fsr => options.range, :textmode => options.textmode,
976
+ :ignore => options.skip, :dont_ignore => options.include,
977
+ :sort => options.sort,
978
+ :sort_reverse => options.sort_reverse,
979
+ :output_threshold => options.output_threshold,
980
+ :callsite_analyzer => $rcov_callsite_analyzer)
981
+ end
982
+
983
+ unless options.nohtml
984
+ if options.profiling
985
+ formatters << make_formatter[Rcov::HTMLProfiling]
986
+ else
987
+ formatters << make_formatter[Rcov::HTMLCoverage]
988
+ end
989
+ end
990
+ textual_formatters = {:counts => Rcov::TextCoverage, :coverage => Rcov::TextCoverage,
991
+ :summary => Rcov::TextSummary, :report => Rcov::TextReport}
992
+
993
+ if textual_formatters[options.textmode]
994
+ formatters << make_formatter[textual_formatters[options.textmode]]
995
+ end
996
+
997
+ $rcov_code_coverage_analyzer = Rcov::CodeCoverageAnalyzer.new
998
+
999
+ # must be registered before test/unit puts its own
1000
+ END {
1001
+ $rcov_code_coverage_analyzer.remove_hook
1002
+ $rcov_callsite_analyzer.remove_hook if $rcov_callsite_analyzer
1003
+ $rcov_code_coverage_analyzer.dump_coverage_info(formatters)
1004
+ if formatters.all?{|formatter| formatter.sorted_file_pairs.empty? }
1005
+ require 'pp'
1006
+ $stderr.puts <<-EOF
1007
+
1008
+ No file to analyze was found. All the files loaded by rcov matched one of the
1009
+ following expressions, and were thus ignored:
1010
+ #{PP.pp(options.skip, "").chomp}
1011
+
1012
+ You can solve this by doing one or more of the following:
1013
+ * rename the files not to be ignored so they don't match the above regexps
1014
+ * use --include-file to give a list of patterns for files not to be ignored
1015
+ * use --exclude-only to give the new list of regexps to match against
1016
+ * structure your code as follows:
1017
+ test/test_*.rb for the test cases
1018
+ lib/**/*.rb for the target source code whose coverage you want
1019
+ making sure that the test/test_*.rb files are loading from lib/, e.g. by
1020
+ using the -Ilib command-line argument, adding
1021
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
1022
+ to test/test_*.rb, or running rcov via a Rakefile (read the RDoc
1023
+ documentation or README.rake in the source distribution).
1024
+ EOF
1025
+ exit(-1)
1026
+ end
1027
+ }
1028
+
1029
+ if options.test_unit_only
1030
+ require 'test/unit'
1031
+ module Test::Unit
1032
+ class TestCase
1033
+ remove_method(:run) if instance_methods.include? "run"
1034
+ def run(result)
1035
+ yield(STARTED, name)
1036
+ @_result = result
1037
+ begin
1038
+ $rcov_code_coverage_analyzer.run_hooked do
1039
+ setup
1040
+ __send__(@method_name)
1041
+ end
1042
+ rescue AssertionFailedError => e
1043
+ add_failure(e.message, e.backtrace)
1044
+ rescue StandardError, ScriptError
1045
+ add_error($!)
1046
+ ensure
1047
+ begin
1048
+ $rcov_code_coverage_analyzer.run_hooked { teardown }
1049
+ rescue AssertionFailedError => e
1050
+ add_failure(e.message, e.backtrace)
1051
+ rescue StandardError, ScriptError
1052
+ add_error($!)
1053
+ end
1054
+ end
1055
+ result.add_run
1056
+ yield(FINISHED, name)
1057
+ end
1058
+ end
1059
+ end
1060
+ else
1061
+ $rcov_code_coverage_analyzer.install_hook
1062
+ end
1063
+
1064
+ #{{{ Load scripts
1065
+ pending_scripts = ARGV.clone
1066
+ ARGV.replace extra_args
1067
+ until pending_scripts.empty?
1068
+ prog = pending_scripts.shift
1069
+ if options.replace_prog_name
1070
+ $0 = File.basename(File.expand_path(prog))
1071
+ end
1072
+ load prog
1073
+ end
1074
+
1075
+
1076
+ # xx-0.1.0-1 follows
1077
+ __END__
1078
+ # xx can be redistributed and used under the following conditions
1079
+ # (just keep the following copyright notice, list of conditions and disclaimer
1080
+ # in order to satisfy rcov's "Ruby license" and xx's license simultaneously).
1081
+ #
1082
+ #ePark Labs Public License version 1
1083
+ #Copyright (c) 2005, ePark Labs, Inc. and contributors
1084
+ #All rights reserved.
1085
+ #
1086
+ #Redistribution and use in source and binary forms, with or without modification,
1087
+ #are permitted provided that the following conditions are met:
1088
+ #
1089
+ # 1. Redistributions of source code must retain the above copyright notice, this
1090
+ # list of conditions and the following disclaimer.
1091
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
1092
+ # this list of conditions and the following disclaimer in the documentation
1093
+ # and/or other materials provided with the distribution.
1094
+ # 3. Neither the name of ePark Labs nor the names of its contributors may be
1095
+ # used to endorse or promote products derived from this software without
1096
+ # specific prior written permission.
1097
+ #
1098
+ #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1099
+ #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1100
+ #WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1101
+ #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
1102
+ #ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1103
+ #(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
1104
+ #LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
1105
+ #ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1106
+ #(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
1107
+ #SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1108
+
1109
+ unless defined? $__xx_rb__
1110
+
1111
+ require "rexml/document"
1112
+
1113
+
1114
+ module XX
1115
+ #--{{{
1116
+ VERSION = "0.1.0"
1117
+
1118
+ %w(
1119
+ CRAZY_LIKE_A_HELL
1120
+ PERMISSIVE
1121
+ STRICT
1122
+ ANY
1123
+ ).each{|c| const_set c, c}
1124
+
1125
+ class Document
1126
+ #--{{{
1127
+ attr "doc"
1128
+ attr "stack"
1129
+ attr "size"
1130
+
1131
+ def initialize *a, &b
1132
+ #--{{{
1133
+ @doc = ::REXML::Document::new(*a, &b)
1134
+ @stack = [@doc]
1135
+ @size = 0
1136
+ #--}}}
1137
+ end
1138
+ def top
1139
+ #--{{{
1140
+ @stack.last
1141
+ #--}}}
1142
+ end
1143
+ def push element
1144
+ #--{{{
1145
+ @stack.push element
1146
+ #--}}}
1147
+ end
1148
+ def pop
1149
+ #--{{{
1150
+ @stack.pop unless @stack.size == 1
1151
+ #--}}}
1152
+ end
1153
+ def tracking_additions
1154
+ #--{{{
1155
+ n = @size
1156
+ yield
1157
+ return @size - n
1158
+ #--}}}
1159
+ end
1160
+ def to_str port = ""
1161
+ #--{{{
1162
+ @doc.write port, indent=-1, transitive=false, ie_hack=true
1163
+ port
1164
+ #--}}}
1165
+ end
1166
+ alias_method "to_s", "to_str"
1167
+ def pretty port = ''
1168
+ #--{{{
1169
+ @doc.write port, indent=2, transitive=false, ie_hack=true
1170
+ port
1171
+ #--}}}
1172
+ end
1173
+ def create element
1174
+ #--{{{
1175
+ push element
1176
+ begin
1177
+ object = nil
1178
+ additions =
1179
+ tracking_additions do
1180
+ object = yield element if block_given?
1181
+ end
1182
+ if object and additions.zero?
1183
+ self << object
1184
+ end
1185
+ ensure
1186
+ pop
1187
+ end
1188
+ self << element
1189
+ element
1190
+ #--}}}
1191
+ end
1192
+ def << object
1193
+ #--{{{
1194
+ t, x = top, object
1195
+
1196
+ if x
1197
+ case t
1198
+ when ::REXML::Document
1199
+
1200
+ begin
1201
+ t <<
1202
+ case x
1203
+ when ::REXML::Document
1204
+ x.root || ::REXML::Text::new(x.to_s)
1205
+ when ::REXML::Element
1206
+ x
1207
+ when ::REXML::CData
1208
+ x
1209
+ when ::REXML::Text
1210
+ x
1211
+ else # string
1212
+ ::REXML::Text::new(x.to_s)
1213
+ end
1214
+ rescue
1215
+ if t.respond_to? "root"
1216
+ t = t.root
1217
+ retry
1218
+ else
1219
+ raise
1220
+ end
1221
+ end
1222
+
1223
+ when ::REXML::Element
1224
+ t <<
1225
+ case x
1226
+ when ::REXML::Document
1227
+ x.root || ::REXML::Text::new(x.to_s)
1228
+ when ::REXML::Element
1229
+ x
1230
+ when ::REXML::CData
1231
+ #::REXML::Text::new(x.write(""))
1232
+ x
1233
+ when ::REXML::Text
1234
+ x
1235
+ else # string
1236
+ ::REXML::Text::new(x.to_s)
1237
+ end
1238
+
1239
+ when ::REXML::Text
1240
+ t <<
1241
+ case x
1242
+ when ::REXML::Document
1243
+ x.write ""
1244
+ when ::REXML::Element
1245
+ x.write ""
1246
+ when ::REXML::CData
1247
+ x.write ""
1248
+ when ::REXML::Text
1249
+ x.write ""
1250
+ else # string
1251
+ x.to_s
1252
+ end
1253
+
1254
+ else # other - try anyhow
1255
+ t <<
1256
+ case x
1257
+ when ::REXML::Document
1258
+ x.write ""
1259
+ when ::REXML::Element
1260
+ x.write ""
1261
+ when ::REXML::CData
1262
+ x.write ""
1263
+ when ::REXML::Text
1264
+ x.write ""
1265
+ else # string
1266
+ x.to_s
1267
+ end
1268
+ end
1269
+ end
1270
+
1271
+ @size += 1
1272
+ self
1273
+ #--}}}
1274
+ end
1275
+ #--}}}
1276
+ end
1277
+
1278
+ module Markup
1279
+ #--{{{
1280
+ class Error < ::StandardError; end
1281
+
1282
+ module InstanceMethods
1283
+ #--{{{
1284
+ def method_missing m, *a, &b
1285
+ #--{{{
1286
+ m = m.to_s
1287
+
1288
+ tag_method, tag_name = xx_class::xx_tag_method_name m
1289
+
1290
+ c_method_missing = xx_class::xx_config_for "method_missing", xx_which
1291
+ c_tags = xx_class::xx_config_for "tags", xx_which
1292
+
1293
+ pat =
1294
+ case c_method_missing
1295
+ when ::XX::CRAZY_LIKE_A_HELL
1296
+ %r/.*/
1297
+ when ::XX::PERMISSIVE
1298
+ %r/_$/o
1299
+ when ::XX::STRICT
1300
+ %r/_$/o
1301
+ else
1302
+ super
1303
+ end
1304
+
1305
+ super unless m =~ pat
1306
+
1307
+ if c_method_missing == ::XX::STRICT
1308
+ super unless c_tags.include? tag_name
1309
+ end
1310
+
1311
+ ret, defined = nil
1312
+
1313
+ begin
1314
+ xx_class::xx_define_tmp_method tag_method
1315
+ xx_class::xx_define_tag_method tag_method, tag_name
1316
+ ret = send tag_method, *a, &b
1317
+ defined = true
1318
+ ensure
1319
+ xx_class::xx_remove_tag_method tag_method unless defined
1320
+ end
1321
+
1322
+ ret
1323
+ #--}}}
1324
+ end
1325
+ def xx_tag_ tag_name, *a, &b
1326
+ #--{{{
1327
+ tag_method, tag_name = xx_class::xx_tag_method_name tag_name
1328
+
1329
+ ret, defined = nil
1330
+
1331
+ begin
1332
+ xx_class::xx_define_tmp_method tag_method
1333
+ xx_class::xx_define_tag_method tag_method, tag_name
1334
+ ret = send tag_method, *a, &b
1335
+ defined = true
1336
+ ensure
1337
+ xx_class::xx_remove_tag_method tag_method unless defined
1338
+ end
1339
+
1340
+ ret
1341
+ #--}}}
1342
+ end
1343
+ alias_method "g_", "xx_tag_"
1344
+ def xx_which *argv
1345
+ #--{{{
1346
+ @xx_which = nil unless defined? @xx_which
1347
+ if argv.empty?
1348
+ @xx_which
1349
+ else
1350
+ xx_which = @xx_which
1351
+ begin
1352
+ @xx_which = argv.shift
1353
+ return yield
1354
+ ensure
1355
+ @xx_which = xx_which
1356
+ end
1357
+ end
1358
+ #--}}}
1359
+ end
1360
+ def xx_with_doc_in_effect *a, &b
1361
+ #--{{{
1362
+ @xx_docs ||= []
1363
+ doc = ::XX::Document::new(*a)
1364
+ ddoc = doc.doc
1365
+ begin
1366
+ @xx_docs.push doc
1367
+ b.call doc if b
1368
+
1369
+ doctype = xx_config_for "doctype", xx_which
1370
+ if doctype
1371
+ unless ddoc.doctype
1372
+ doctype = ::REXML::DocType::new doctype unless
1373
+ ::REXML::DocType === doctype
1374
+ ddoc << doctype
1375
+ end
1376
+ end
1377
+
1378
+ xmldecl = xx_config_for "xmldecl", xx_which
1379
+ if xmldecl
1380
+ if ddoc.xml_decl == ::REXML::XMLDecl::default
1381
+ xmldecl = ::REXML::XMLDecl::new xmldecl unless
1382
+ ::REXML::XMLDecl === xmldecl
1383
+ ddoc << xmldecl
1384
+ end
1385
+ end
1386
+
1387
+ return doc
1388
+ ensure
1389
+ @xx_docs.pop
1390
+ end
1391
+ #--}}}
1392
+ end
1393
+ def xx_doc
1394
+ #--{{{
1395
+ @xx_docs.last rescue raise "no xx_doc in effect!"
1396
+ #--}}}
1397
+ end
1398
+ def xx_text_ *objects, &b
1399
+ #--{{{
1400
+ doc = xx_doc
1401
+
1402
+ text =
1403
+ ::REXML::Text::new("",
1404
+ respect_whitespace=true, parent=nil
1405
+ )
1406
+
1407
+ objects.each do |object|
1408
+ text << object.to_s if object
1409
+ end
1410
+
1411
+ doc.create text, &b
1412
+ #--}}}
1413
+ end
1414
+ alias_method "text_", "xx_text_"
1415
+ alias_method "t_", "xx_text_"
1416
+ def xx_markup_ *objects, &b
1417
+ #--{{{
1418
+ doc = xx_doc
1419
+
1420
+ doc2 = ::REXML::Document::new ""
1421
+
1422
+ objects.each do |object|
1423
+ (doc2.root ? doc2.root : doc2) << ::REXML::Document::new(object.to_s)
1424
+ end
1425
+
1426
+
1427
+ ret = doc.create doc2, &b
1428
+ puts doc2.to_s
1429
+ STDIN.gets
1430
+ ret
1431
+ #--}}}
1432
+ end
1433
+ alias_method "x_", "xx_markup_"
1434
+ def xx_any_ *objects, &b
1435
+ #--{{{
1436
+ doc = xx_doc
1437
+ nothing = %r/.^/m
1438
+
1439
+ text =
1440
+ ::REXML::Text::new("",
1441
+ respect_whitespace=true, parent=nil, raw=true, entity_filter=nil, illegal=nothing
1442
+ )
1443
+
1444
+ objects.each do |object|
1445
+ text << object.to_s if object
1446
+ end
1447
+
1448
+ doc.create text, &b
1449
+ #--}}}
1450
+ end
1451
+ alias_method "h_", "xx_any_"
1452
+ remove_method "x_" if instance_methods.include? "x_"
1453
+ alias_method "x_", "xx_any_" # supplant for now
1454
+ def xx_cdata_ *objects, &b
1455
+ #--{{{
1456
+ doc = xx_doc
1457
+
1458
+ cdata = ::REXML::CData::new ""
1459
+
1460
+ objects.each do |object|
1461
+ cdata << object.to_s if object
1462
+ end
1463
+
1464
+ doc.create cdata, &b
1465
+ #--}}}
1466
+ end
1467
+ alias_method "c_", "xx_cdata_"
1468
+ def xx_parse_attributes string
1469
+ #--{{{
1470
+ string = string.to_s
1471
+ tokens = string.split %r/,/o
1472
+ tokens.map{|t| t.sub!(%r/[^=]+=/){|key_eq| key_eq.chop << " : "}}
1473
+ xx_parse_yaml_attributes(tokens.join(','))
1474
+ #--}}}
1475
+ end
1476
+ alias_method "att_", "xx_parse_attributes"
1477
+ def xx_parse_yaml_attributes string
1478
+ #--{{{
1479
+ require "yaml"
1480
+ string = string.to_s
1481
+ string = "{" << string unless string =~ %r/^\s*[{]/o
1482
+ string = string << "}" unless string =~ %r/[}]\s*$/o
1483
+ obj = ::YAML::load string
1484
+ raise ArgumentError, "<#{ obj.class }> not Hash!" unless Hash === obj
1485
+ obj
1486
+ #--}}}
1487
+ end
1488
+ alias_method "at_", "xx_parse_yaml_attributes"
1489
+ alias_method "yat_", "xx_parse_yaml_attributes"
1490
+ def xx_class
1491
+ #--{{{
1492
+ @xx_class ||= self.class
1493
+ #--}}}
1494
+ end
1495
+ def xx_tag_method_name *a, &b
1496
+ #--{{{
1497
+ xx_class.xx_tag_method_name(*a, &b)
1498
+ #--}}}
1499
+ end
1500
+ def xx_define_tmp_method *a, &b
1501
+ #--{{{
1502
+ xx_class.xx_define_tmp_methodr(*a, &b)
1503
+ #--}}}
1504
+ end
1505
+ def xx_define_tag_method *a, &b
1506
+ #--{{{
1507
+ xx_class.xx_define_tag_method(*a, &b)
1508
+ #--}}}
1509
+ end
1510
+ def xx_remove_tag_method *a, &b
1511
+ #--{{{
1512
+ xx_class.xx_tag_remove_method(*a, &b)
1513
+ #--}}}
1514
+ end
1515
+ def xx_ancestors
1516
+ #--{{{
1517
+ raise Error, "no xx_which in effect" unless xx_which
1518
+ xx_class.xx_ancestors xx_which
1519
+ #--}}}
1520
+ end
1521
+ def xx_config
1522
+ #--{{{
1523
+ xx_class.xx_config
1524
+ #--}}}
1525
+ end
1526
+ def xx_config_for *a, &b
1527
+ #--{{{
1528
+ xx_class.xx_config_for(*a, &b)
1529
+ #--}}}
1530
+ end
1531
+ def xx_configure *a, &b
1532
+ #--{{{
1533
+ xx_class.xx_configure(*a, &b)
1534
+ #--}}}
1535
+ end
1536
+ #--}}}
1537
+ end
1538
+
1539
+ module ClassMethods
1540
+ #--{{{
1541
+ def xx_tag_method_name m
1542
+ #--{{{
1543
+ m = m.to_s
1544
+ tag_method, tag_name = m, m.gsub(%r/_+$/, "")
1545
+ [ tag_method, tag_name ]
1546
+ #--}}}
1547
+ end
1548
+ def xx_define_tmp_method m
1549
+ #--{{{
1550
+ define_method(m){ raise NotImplementedError, m.to_s }
1551
+ #--}}}
1552
+ end
1553
+ def xx_define_tag_method tag_method, tag_name = nil
1554
+ #--{{{
1555
+ tag_method = tag_method.to_s
1556
+ tag_name ||= tag_method.gsub %r/_+$/, ""
1557
+
1558
+ remove_method tag_method if instance_methods.include? tag_method
1559
+ module_eval <<-code, __FILE__, __LINE__+1
1560
+ def #{ tag_method } *a, &b
1561
+ hashes, nothashes = a.partition{|x| Hash === x}
1562
+
1563
+ doc = xx_doc
1564
+ element = ::REXML::Element::new '#{ tag_name }'
1565
+
1566
+ hashes.each{|h| h.each{|k,v| element.add_attribute k.to_s, v}}
1567
+ nothashes.each{|nh| element << ::REXML::Text::new(nh.to_s)}
1568
+
1569
+ doc.create element, &b
1570
+ end
1571
+ code
1572
+ tag_method
1573
+ #--}}}
1574
+ end
1575
+ def xx_remove_tag_method tag_method
1576
+ #--{{{
1577
+ remove_method tag_method rescue nil
1578
+ #--}}}
1579
+ end
1580
+ def xx_ancestors xx_which = self
1581
+ #--{{{
1582
+ list = []
1583
+ ancestors.each do |a|
1584
+ list << a if a < xx_which
1585
+ end
1586
+ xx_which.ancestors.each do |a|
1587
+ list << a if a <= Markup
1588
+ end
1589
+ list
1590
+ #--}}}
1591
+ end
1592
+ def xx_config
1593
+ #--{{{
1594
+ @@xx_config ||= Hash::new{|h,k| h[k] = {}}
1595
+ #--}}}
1596
+ end
1597
+ def xx_config_for key, xx_which = nil
1598
+ #--{{{
1599
+ key = key.to_s
1600
+ xx_which ||= self
1601
+ xx_ancestors(xx_which).each do |a|
1602
+ if xx_config[a].has_key? key
1603
+ return xx_config[a][key]
1604
+ end
1605
+ end
1606
+ nil
1607
+ #--}}}
1608
+ end
1609
+ def xx_configure key, value, xx_which = nil
1610
+ #--{{{
1611
+ key = key.to_s
1612
+ xx_which ||= self
1613
+ xx_config[xx_which][key] = value
1614
+ #--}}}
1615
+ end
1616
+ #--}}}
1617
+ end
1618
+
1619
+ extend ClassMethods
1620
+ include InstanceMethods
1621
+
1622
+ def self::included other, *a, &b
1623
+ #--{{{
1624
+ ret = super
1625
+ other.module_eval do
1626
+ include Markup::InstanceMethods
1627
+ extend Markup::ClassMethods
1628
+ class << self
1629
+ define_method("included", Markup::XX_MARKUP_RECURSIVE_INCLUSION_PROC)
1630
+ end
1631
+ end
1632
+ ret
1633
+ #--}}}
1634
+ end
1635
+ XX_MARKUP_RECURSIVE_INCLUSION_PROC = method("included").to_proc
1636
+
1637
+ xx_configure "method_missing", XX::PERMISSIVE
1638
+ xx_configure "tags", []
1639
+ xx_configure "doctype", nil
1640
+ xx_configure "xmldecl", nil
1641
+ #--}}}
1642
+ end
1643
+
1644
+ module XHTML
1645
+ #--{{{
1646
+ include Markup
1647
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
1648
+
1649
+ def xhtml_ which = XHTML, *a, &b
1650
+ #--{{{
1651
+ xx_which(which) do
1652
+ doc = xx_with_doc_in_effect(*a, &b)
1653
+ ddoc = doc.doc
1654
+ root = ddoc.root
1655
+ if root and root.name and root.name =~ %r/^html$/i
1656
+ if root.attribute("lang",nil).nil? or root.attribute("lang",nil).to_s.empty?
1657
+ root.add_attribute "lang", "en"
1658
+ end
1659
+ if root.attribute("xml:lang").nil? or root.attribute("xml:lang").to_s.empty?
1660
+ root.add_attribute "xml:lang", "en"
1661
+ end
1662
+ if root.namespace.nil? or root.namespace.to_s.empty?
1663
+ root.add_namespace "http://www.w3.org/1999/xhtml"
1664
+ end
1665
+ end
1666
+ doc
1667
+ end
1668
+ #--}}}
1669
+ end
1670
+
1671
+ module Strict
1672
+ #--{{{
1673
+ include XHTML
1674
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")
1675
+ xx_configure "tags", %w(
1676
+ html head body div span DOCTYPE title link meta style p
1677
+ h1 h2 h3 h4 h5 h6 strong em abbr acronym address bdo blockquote cite q code
1678
+ ins del dfn kbd pre samp var br a base img
1679
+ area map object param ul ol li dl dt dd table
1680
+ tr td th tbody thead tfoot col colgroup caption form input
1681
+ textarea select option optgroup button label fieldset legend script noscript b
1682
+ i tt sub sup big small hr
1683
+ )
1684
+ xx_configure "method_missing", ::XX::STRICT
1685
+
1686
+ def xhtml_ which = XHTML::Strict, *a, &b
1687
+ #--{{{
1688
+ super(which, *a, &b)
1689
+ #--}}}
1690
+ end
1691
+ #--}}}
1692
+ end
1693
+
1694
+ module Transitional
1695
+ #--{{{
1696
+ include XHTML
1697
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
1698
+ def xhtml_ which = XHTML::Transitional, *a, &b
1699
+ #--{{{
1700
+ super(which, *a, &b)
1701
+ #--}}}
1702
+ end
1703
+ #--}}}
1704
+ end
1705
+ #--}}}
1706
+ end
1707
+
1708
+ module HTML4
1709
+ #--{{{
1710
+ include Markup
1711
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN")
1712
+
1713
+ def html4_ which = HTML4, *a, &b
1714
+ #--{{{
1715
+ xx_which(which){ xx_with_doc_in_effect(*a, &b) }
1716
+ #--}}}
1717
+ end
1718
+
1719
+ module Strict
1720
+ #--{{{
1721
+ include HTML4
1722
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN")
1723
+ xx_configure "tags", %w(
1724
+ html head body div span DOCTYPE title link meta style p
1725
+ h1 h2 h3 h4 h5 h6 strong em abbr acronym address bdo blockquote cite q code
1726
+ ins del dfn kbd pre samp var br a base img
1727
+ area map object param ul ol li dl dt dd table
1728
+ tr td th tbody thead tfoot col colgroup caption form input
1729
+ textarea select option optgroup button label fieldset legend script noscript b
1730
+ i tt sub sup big small hr
1731
+ )
1732
+ xx_configure "method_missing", ::XX::STRICT
1733
+ def html4_ which = HTML4::Strict, *a, &b
1734
+ #--{{{
1735
+ super(which, *a, &b)
1736
+ #--}}}
1737
+ end
1738
+ #--}}}
1739
+ end
1740
+
1741
+ module Transitional
1742
+ #--{{{
1743
+ include HTML4
1744
+ xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN")
1745
+ def html4_ which = HTML4::Transitional, *a, &b
1746
+ #--{{{
1747
+ super(which, *a, &b)
1748
+ #--}}}
1749
+ end
1750
+ #--}}}
1751
+ end
1752
+ #--}}}
1753
+ end
1754
+ HTML = HTML4
1755
+
1756
+ module XML
1757
+ #--{{{
1758
+ include Markup
1759
+ xx_configure "xmldecl", ::REXML::XMLDecl::new
1760
+
1761
+ def xml_ *a, &b
1762
+ #--{{{
1763
+ xx_which(XML){ xx_with_doc_in_effect(*a, &b)}
1764
+ #--}}}
1765
+ end
1766
+ #--}}}
1767
+ end
1768
+ #--}}}
1769
+ end
1770
+
1771
+ $__xx_rb__ = __FILE__
1772
+ end
1773
+
1774
+
1775
+
1776
+
1777
+
1778
+
1779
+
1780
+
1781
+
1782
+
1783
+ #
1784
+ # simple examples - see samples/ dir for more complete examples
1785
+ #
1786
+
1787
+ if __FILE__ == $0
1788
+
1789
+ class Table < ::Array
1790
+ include XX::XHTML::Strict
1791
+ include XX::HTML4::Strict
1792
+ include XX::XML
1793
+
1794
+ def doc
1795
+ html_{
1796
+ head_{ title_{ "xhtml/html4/xml demo" } }
1797
+
1798
+ div_{
1799
+ h_{ "< malformed html & un-escaped symbols" }
1800
+ }
1801
+
1802
+ t_{ "escaped & text > <" }
1803
+
1804
+ x_{ "<any_valid> xml </any_valid>" }
1805
+
1806
+ div_(:style => :sweet){
1807
+ em_ "this is a table"
1808
+
1809
+ table_(:width => 42, :height => 42){
1810
+ each{|row| tr_{ row.each{|cell| td_ cell } } }
1811
+ }
1812
+ }
1813
+
1814
+ script_(:type => :dangerous){ cdata_{ "javascript" } }
1815
+ }
1816
+ end
1817
+ def to_xhtml
1818
+ xhtml_{ doc }
1819
+ end
1820
+ def to_html4
1821
+ html4_{ doc }
1822
+ end
1823
+ def to_xml
1824
+ xml_{ doc }
1825
+ end
1826
+ end
1827
+
1828
+ table = Table[ %w( 0 1 2 ), %w( a b c ) ]
1829
+
1830
+ methods = %w( to_xhtml to_html4 to_xml )
1831
+
1832
+ methods.each do |method|
1833
+ 2.times{ puts "-" * 42 }
1834
+ puts(table.send(method).pretty)
1835
+ puts
1836
+ end
1837
+
1838
+ end
1839
+ # vi: set sw=4: