rcov 0.5.0.1-mswin32

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/README.rake ADDED
@@ -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.
data/README.rant ADDED
@@ -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
data/Rakefile ADDED
@@ -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:
data/Rantfile ADDED
@@ -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)
data/bin/rcov ADDED
@@ -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: