#!/usr/bin/env ruby # rcov Copyright (c) 2004-2006 Mauricio Fernandez # # rcov originally based on # module COVERAGE__ originally (c) NAKAMURA Hiroshi # module PrettyCoverage originally (c) Simon Strandgaard # # rewritten & extended by Mauricio Fernández # # See LEGAL and LICENSE for additional licensing information. # require 'cgi' require 'rbconfig' require 'optparse' require 'ostruct' # load xx-0.1.0-1 eval File.read(File.expand_path(__FILE__)).gsub(/.*^__END__$/m,"") # extend XX module XX module XMLish include Markup def xmlish_ *a, &b xx_which(XMLish){ xx_with_doc_in_effect(*a, &b)} end end end SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ require 'rcov/version' require 'rcov/report' #{{{ "main" code options = OpenStruct.new options.color = true options.range = 30.0 options.profiling = false options.destdir = nil options.loadpaths = [] options.textmode = false options.skip = Rcov::Formatter::DEFAULT_OPTS[:ignore] options.include = [] options.html = true options.comments_run_by_default = false options.test_unit_only = false options.sort = :name options.sort_reverse = false options.output_threshold = 101 options.replace_prog_name = false options.callsites = false options.crossrefs = false options.coverage_diff_file = "coverage.info" options.coverage_diff_mode = :compare options.coverage_diff_save = false options.diff_cmd = "diff" options.report_cov_bug_for = nil options.aggregate_file = nil options.gcc_output = false options.show_validator_links = true EXTRA_HELP = <<-EOF You can run several programs at once: rcov something.rb somethingelse.rb The parameters to be passed to the program under inspection can be specified after --: rcov -Ilib -t something.rb -- --theseopts --are --given --to --something.rb ARGV will be set to the specified parameters after --. Keep in mind that all the programs are run under the same process (i.e. they just get Kernel#load()'ed in sequence). $PROGRAM_NAME (aka. $0) will be set before each file is load()ed if --replace-progname is used. EOF #{{{ OptionParser opts = OptionParser.new do |opts| opts.banner = <<-EOF rcov #{Rcov::VERSION} #{Rcov::RELEASE_DATE} Usage: rcov [options] [script2.rb] [-- --extra-options] EOF opts.separator "" opts.separator "Options:" opts.on("-o", "--output PATH", "Destination directory.") do |dir| options.destdir = dir end opts.on("-I", "--include PATHS", "Prepend PATHS to $: (colon separated list)") do |paths| options.loadpaths = paths.split(/:/) end opts.on("--[no-]comments", "Mark all comments by default.", "(default: --no-comments)") do |comments_run_p| options.comments_run_by_default = comments_run_p end opts.on("--test-unit-only", "Only trace code executed in TestCases.") do options.test_unit_only = true end opts.on("-n", "--no-color", "Create colorblind-safe output.") do options.color = false end opts.on("-i", "--include-file PATTERNS", "Generate info for files matching a", "pattern (comma-separated regexp list)") do |list| begin regexps = list.split(/,/).map{|x| Regexp.new(x) } options.include += regexps rescue RegexpError => e raise OptionParser::InvalidArgument, e.message end end opts.on("-x", "--exclude PATTERNS", "Don't generate info for files matching a", "pattern (comma-separated regexp list)") do |list| begin regexps = list.split(/,/).map{|x| Regexp.new x} options.skip += regexps rescue RegexpError => e raise OptionParser::InvalidArgument, e.message end end opts.on("--exclude-only PATTERNS", "Skip info only for files matching the", "given patterns.") do |list| begin options.skip = list.split(/,/).map{|x| Regexp.new(x) } rescue RegexpError => e raise OptionParser::InvalidArgument, e.message end end opts.on("--rails", "Skip config/, environment/ and vendor/.") do options.skip.concat [%r{\bvendor/},%r{\bconfig/},%r{\benvironment/}] end opts.on("--[no-]callsites", "Show callsites in generated XHTML report.", "(somewhat slower; disabled by default)") do |val| options.callsites = val end opts.on("--[no-]xrefs", "Generate fully cross-referenced report.", "(includes --callsites)") do |val| options.crossrefs = val options.callsites ||= val end opts.on("-p", "--profile", "Generate bogo-profiling info.") do options.profiling = true options.destdir ||= "profiling" end opts.on("-r", "--range RANGE", Float, "Color scale range for profiling info (dB).") do |val| options.range = val end opts.on("-a", "--annotate", "Generate annotated source code.") do options.html = false options.textmode = :annotate options.crossrefs = true options.callsites = true options.skip = [ %r!/test/unit/! ] end opts.on("-T", "--text-report", "Dump detailed plain-text report to stdout.", "(filename, LoC, total lines, coverage)") do options.textmode = :report end opts.on("-t", "--text-summary", "Dump plain-text summary to stdout.") do options.textmode = :summary end opts.on("--text-counts", "Dump execution counts in plaintext.") do options.textmode = :counts end opts.on("--text-coverage", "Dump coverage info to stdout, using", "ANSI color sequences unless -n.") do options.textmode = :coverage end opts.on("--gcc", "Dump uncovered line in GCC error format.") do options.gcc_output = true end opts.on("--aggregate FILE", "Aggregate data from previous runs", "in FILE. Overwrites FILE with the", "merged data. FILE is created if", "necessary.") do |file| options.aggregate_file = file end opts.on("-D [FILE]", "--text-coverage-diff [FILE]", "Compare code coverage with saved state", "in FILE, defaults to coverage.info.", "Implies --comments.") do |file| options.textmode = :coverage_diff options.comments_run_by_default = true if options.coverage_diff_save raise "You shouldn't use --save and --text-coverage-diff at a time." end options.coverage_diff_mode = :compare options.coverage_diff_file = file if file && !file.empty? end opts.on("--save [FILE]", "Save coverage data to FILE,", "for later use with rcov -D.", "(default: coverage.info)") do |file| options.coverage_diff_save = true options.coverage_diff_mode = :record if options.textmode == :coverage_diff raise "You shouldn't use --save and --text-coverage-diff at a time." end options.coverage_diff_file = file if file && !file.empty? end opts.on("--[no-]html", "Generate HTML output.", "(default: --html)") do |val| options.html = val end opts.on("--sort CRITERION", [:name, :loc, :coverage], "Sort files in the output by the specified", "field (name, loc, coverage)") do |criterion| options.sort = criterion end opts.on("--sort-reverse", "Reverse files in the output.") do options.sort_reverse = true end opts.on("--threshold INT", "Only list files with coverage < INT %.", "(default: 101)") do |threshold| begin threshold = Integer(threshold) raise if threshold <= 0 || threshold > 101 rescue Exception raise OptionParser::InvalidArgument, threshold end options.output_threshold = threshold end opts.on("--[no-]validator-links", "Add link to W3C's validation services.", "(default: true)") do |show_validator_links| options.show_validator_links = show_validator_links end opts.on("--only-uncovered", "Same as --threshold 100") do options.output_threshold = 100 end opts.on("--replace-progname", "Replace $0 when loading the .rb files.") do options.replace_prog_name = true end opts.on("-w", "Turn warnings on (like ruby).") do $VERBOSE = true end opts.on("--no-rcovrt", "Do not use the optimized C runtime.", "(will run 30-300 times slower)") do $rcov_do_not_use_rcovrt = true end opts.on("--diff-cmd PROGNAME", "Use PROGNAME for --text-coverage-diff.", "(default: diff)") do |cmd| options.diff_cmd = cmd end opts.separator "" opts.on_tail("-h", "--help", "Show extended help message") do require 'pp' puts opts puts < e puts opts puts puts e.message exit(-1) end options.destdir ||= "coverage" unless ARGV[0] puts opts exit end # {{{ set loadpath options.loadpaths.reverse_each{|x| $:.unshift x} #{{{ require 'rcov': do it only now in order to be able to run rcov on itself # since we need to set $: before. require 'rcov' options.callsites = true if options.report_cov_bug_for options.textmode = :gcc if !options.textmode and options.gcc_output def rcov_load_aggregate_data(file) require 'zlib' begin old_data = nil Zlib::GzipReader.open(file){|gz| old_data = Marshal.load(gz) } rescue old_data = {} end old_data || {} end def rcov_save_aggregate_data(file) require 'zlib' Zlib::GzipWriter.open(file) do |f| Marshal.dump({:callsites => $rcov_callsite_analyzer, :coverage => $rcov_code_coverage_analyzer}, f) end end if options.callsites if options.aggregate_file saved_aggregate_data = rcov_load_aggregate_data(options.aggregate_file) if saved_aggregate_data[:callsites] $rcov_callsite_analyzer = saved_aggregate_data[:callsites] end end $rcov_callsite_analyzer ||= Rcov::CallSiteAnalyzer.new $rcov_callsite_analyzer.install_hook else $rcov_callsite_analyzer = nil end # {{{ create formatters formatters = [] make_formatter = lambda do |klass| klass.new(:destdir => options.destdir, :color => options.color, :fsr => options.range, :textmode => options.textmode, :ignore => options.skip, :dont_ignore => options.include, :sort => options.sort, :sort_reverse => options.sort_reverse, :output_threshold => options.output_threshold, :callsite_analyzer => $rcov_callsite_analyzer, :coverage_diff_mode => options.coverage_diff_mode, :coverage_diff_file => options.coverage_diff_file, :callsites => options.callsites, :cross_references => options.crossrefs, :diff_cmd => options.diff_cmd, :comments_run_by_default => options.comments_run_by_default, :gcc_output => options.gcc_output, :validator_links => options.show_validator_links ) end if options.html if options.profiling formatters << make_formatter[Rcov::HTMLProfiling] else formatters << make_formatter[Rcov::HTMLCoverage] end end textual_formatters = {:counts => Rcov::FullTextReport, :coverage => Rcov::FullTextReport, :gcc => Rcov::FullTextReport, :annotate => Rcov::RubyAnnotation, :summary => Rcov::TextSummary, :report => Rcov::TextReport, :coverage_diff => Rcov::TextCoverageDiff} if textual_formatters[options.textmode] formatters << make_formatter[textual_formatters[options.textmode]] end formatters << make_formatter[Rcov::TextCoverageDiff] if options.coverage_diff_save if options.aggregate_file saved_aggregate_data ||= rcov_load_aggregate_data(options.aggregate_file) if saved_aggregate_data[:coverage] $rcov_code_coverage_analyzer = saved_aggregate_data[:coverage] end end $rcov_code_coverage_analyzer ||= Rcov::CodeCoverageAnalyzer.new # must be registered before test/unit puts its own END { $rcov_code_coverage_analyzer.remove_hook $rcov_callsite_analyzer.remove_hook if $rcov_callsite_analyzer rcov_save_aggregate_data(options.aggregate_file) if options.aggregate_file $rcov_code_coverage_analyzer.dump_coverage_info(formatters) if options.report_cov_bug_for defsite = $rcov_callsite_analyzer.defsite(options.report_cov_bug_for) if !defsite $stderr.puts <<-EOF Couldn't find definition site of #{options.report_cov_bug_for}. Was it executed at all? EOF exit(-1) end lines, mark_info, count_info = $rcov_code_coverage_analyzer.data(defsite.file) puts < (include "rcov" in the subject to get past the spam filters), or post it to http://eigenclass.org/hiki.rb?rcov+#{VERSION} Thank you! ============================================================================= Bug report generated on #{Time.new} Ruby version: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) Platform: #{RUBY_PLATFORM} rcov version: #{Rcov::VERSION} rcovrt loaded? #{$".any?{|x| /\brcovrt\b/ =~ x} } using RubyGems? #{$".any?{|x| /\brubygems\b/ =~ x} } Command-line arguments: #{$ORIGINAL_ARGV.inspect} Coverage analysis bug in: #{options.report_cov_bug_for} Line(s) ____________ should be ______ (red/green). Raw coverage information (feel free to remove useless data, but please leave some context around the faulty lines): EOF defsite.line.upto(SCRIPT_LINES__[defsite.file].size) do |i| puts "%7d:%5d:%s" % [count_info[i-1], i, lines[i-1]] end exit end if formatters.all?{|formatter| formatter.sorted_file_pairs.empty? } require 'pp' $stderr.puts <<-EOF No file to analyze was found. All the files loaded by rcov matched one of the following expressions, and were thus ignored: #{PP.pp(options.skip, "").chomp} You can solve this by doing one or more of the following: * rename the files not to be ignored so they don't match the above regexps * use --include-file to give a list of patterns for files not to be ignored * use --exclude-only to give the new list of regexps to match against * structure your code as follows: test/test_*.rb for the test cases lib/**/*.rb for the target source code whose coverage you want making sure that the test/test_*.rb files are loading from lib/, e.g. by using the -Ilib command-line argument, adding $:.unshift File.join(File.dirname(__FILE__), "..", "lib") to test/test_*.rb, or running rcov via a Rakefile (read the RDoc documentation or README.rake in the source distribution). EOF end } if options.test_unit_only require 'test/unit' module Test::Unit class TestCase remove_method(:run) if instance_methods.include? "run" def run(result) yield(STARTED, name) @_result = result begin $rcov_code_coverage_analyzer.run_hooked do setup __send__(@method_name) end rescue AssertionFailedError => e add_failure(e.message, e.backtrace) rescue StandardError, ScriptError add_error($!) ensure begin $rcov_code_coverage_analyzer.run_hooked { teardown } rescue AssertionFailedError => e add_failure(e.message, e.backtrace) rescue StandardError, ScriptError add_error($!) end end result.add_run yield(FINISHED, name) end end end else $rcov_code_coverage_analyzer.install_hook end #{{{ Load scripts pending_scripts = ARGV.clone ARGV.replace extra_args until pending_scripts.empty? prog = pending_scripts.shift if options.replace_prog_name $0 = File.basename(File.expand_path(prog)) end load prog end # xx-0.1.0-1 follows __END__ # xx can be redistributed and used under the following conditions # (just keep the following copyright notice, list of conditions and disclaimer # in order to satisfy rcov's "Ruby license" and xx's license simultaneously). # #ePark Labs Public License version 1 #Copyright (c) 2005, ePark Labs, Inc. and contributors #All rights reserved. # #Redistribution and use in source and binary forms, with or without modification, #are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # 3. Neither the name of ePark Labs nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED #WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR #ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES #(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; #LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON #ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT #(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS #SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. unless defined? $__xx_rb__ require "rexml/document" module XX #--{{{ VERSION = "0.1.0" %w( CRAZY_LIKE_A_HELL PERMISSIVE STRICT ANY ).each{|c| const_set c, c} class Document #--{{{ attr "doc" attr "stack" attr "size" def initialize *a, &b #--{{{ @doc = ::REXML::Document::new(*a, &b) @stack = [@doc] @size = 0 #--}}} end def top #--{{{ @stack.last #--}}} end def push element #--{{{ @stack.push element #--}}} end def pop #--{{{ @stack.pop unless @stack.size == 1 #--}}} end def tracking_additions #--{{{ n = @size yield return @size - n #--}}} end def to_str port = "" #--{{{ @doc.write port, indent=-1, transitive=false, ie_hack=true port #--}}} end alias_method "to_s", "to_str" def pretty port = '' #--{{{ @doc.write port, indent=2, transitive=false, ie_hack=true port #--}}} end def create element #--{{{ push element begin object = nil additions = tracking_additions do object = yield element if block_given? end if object and additions.zero? self << object end ensure pop end self << element element #--}}} end def << object #--{{{ t, x = top, object if x case t when ::REXML::Document begin t << case x when ::REXML::Document x.root || ::REXML::Text::new(x.to_s) when ::REXML::Element x when ::REXML::CData x when ::REXML::Text x else # string ::REXML::Text::new(x.to_s) end rescue if t.respond_to? "root" t = t.root retry else raise end end when ::REXML::Element t << case x when ::REXML::Document x.root || ::REXML::Text::new(x.to_s) when ::REXML::Element x when ::REXML::CData #::REXML::Text::new(x.write("")) x when ::REXML::Text x else # string ::REXML::Text::new(x.to_s) end when ::REXML::Text t << case x when ::REXML::Document x.write "" when ::REXML::Element x.write "" when ::REXML::CData x.write "" when ::REXML::Text x.write "" else # string x.to_s end else # other - try anyhow t << case x when ::REXML::Document x.write "" when ::REXML::Element x.write "" when ::REXML::CData x.write "" when ::REXML::Text x.write "" else # string x.to_s end end end @size += 1 self #--}}} end #--}}} end module Markup #--{{{ class Error < ::StandardError; end module InstanceMethods #--{{{ def method_missing m, *a, &b #--{{{ m = m.to_s tag_method, tag_name = xx_class::xx_tag_method_name m c_method_missing = xx_class::xx_config_for "method_missing", xx_which c_tags = xx_class::xx_config_for "tags", xx_which pat = case c_method_missing when ::XX::CRAZY_LIKE_A_HELL %r/.*/ when ::XX::PERMISSIVE %r/_$/o when ::XX::STRICT %r/_$/o else super end super unless m =~ pat if c_method_missing == ::XX::STRICT super unless c_tags.include? tag_name end ret, defined = nil begin xx_class::xx_define_tmp_method tag_method xx_class::xx_define_tag_method tag_method, tag_name ret = send tag_method, *a, &b defined = true ensure xx_class::xx_remove_tag_method tag_method unless defined end ret #--}}} end def xx_tag_ tag_name, *a, &b #--{{{ tag_method, tag_name = xx_class::xx_tag_method_name tag_name ret, defined = nil begin xx_class::xx_define_tmp_method tag_method xx_class::xx_define_tag_method tag_method, tag_name ret = send tag_method, *a, &b defined = true ensure xx_class::xx_remove_tag_method tag_method unless defined end ret #--}}} end alias_method "g_", "xx_tag_" def xx_which *argv #--{{{ @xx_which = nil unless defined? @xx_which if argv.empty? @xx_which else xx_which = @xx_which begin @xx_which = argv.shift return yield ensure @xx_which = xx_which end end #--}}} end def xx_with_doc_in_effect *a, &b #--{{{ @xx_docs ||= [] doc = ::XX::Document::new(*a) ddoc = doc.doc begin @xx_docs.push doc b.call doc if b doctype = xx_config_for "doctype", xx_which if doctype unless ddoc.doctype doctype = ::REXML::DocType::new doctype unless ::REXML::DocType === doctype ddoc << doctype end end xmldecl = xx_config_for "xmldecl", xx_which if xmldecl if ddoc.xml_decl == ::REXML::XMLDecl::default xmldecl = ::REXML::XMLDecl::new xmldecl unless ::REXML::XMLDecl === xmldecl ddoc << xmldecl end end return doc ensure @xx_docs.pop end #--}}} end def xx_doc #--{{{ @xx_docs.last rescue raise "no xx_doc in effect!" #--}}} end def xx_text_ *objects, &b #--{{{ doc = xx_doc text = ::REXML::Text::new("", respect_whitespace=true, parent=nil ) objects.each do |object| text << object.to_s if object end doc.create text, &b #--}}} end alias_method "text_", "xx_text_" alias_method "t_", "xx_text_" def xx_markup_ *objects, &b #--{{{ doc = xx_doc doc2 = ::REXML::Document::new "" objects.each do |object| (doc2.root ? doc2.root : doc2) << ::REXML::Document::new(object.to_s) end ret = doc.create doc2, &b puts doc2.to_s STDIN.gets ret #--}}} end alias_method "x_", "xx_markup_" def xx_any_ *objects, &b #--{{{ doc = xx_doc nothing = %r/.^/m text = ::REXML::Text::new("", respect_whitespace=true, parent=nil, raw=true, entity_filter=nil, illegal=nothing ) objects.each do |object| text << object.to_s if object end doc.create text, &b #--}}} end alias_method "h_", "xx_any_" remove_method "x_" if instance_methods.include? "x_" alias_method "x_", "xx_any_" # supplant for now def xx_cdata_ *objects, &b #--{{{ doc = xx_doc cdata = ::REXML::CData::new "" objects.each do |object| cdata << object.to_s if object end doc.create cdata, &b #--}}} end alias_method "c_", "xx_cdata_" def xx_parse_attributes string #--{{{ string = string.to_s tokens = string.split %r/,/o tokens.map{|t| t.sub!(%r/[^=]+=/){|key_eq| key_eq.chop << " : "}} xx_parse_yaml_attributes(tokens.join(',')) #--}}} end alias_method "att_", "xx_parse_attributes" def xx_parse_yaml_attributes string #--{{{ require "yaml" string = string.to_s string = "{" << string unless string =~ %r/^\s*[{]/o string = string << "}" unless string =~ %r/[}]\s*$/o obj = ::YAML::load string raise ArgumentError, "<#{ obj.class }> not Hash!" unless Hash === obj obj #--}}} end alias_method "at_", "xx_parse_yaml_attributes" alias_method "yat_", "xx_parse_yaml_attributes" def xx_class #--{{{ @xx_class ||= self.class #--}}} end def xx_tag_method_name *a, &b #--{{{ xx_class.xx_tag_method_name(*a, &b) #--}}} end def xx_define_tmp_method *a, &b #--{{{ xx_class.xx_define_tmp_methodr(*a, &b) #--}}} end def xx_define_tag_method *a, &b #--{{{ xx_class.xx_define_tag_method(*a, &b) #--}}} end def xx_remove_tag_method *a, &b #--{{{ xx_class.xx_tag_remove_method(*a, &b) #--}}} end def xx_ancestors #--{{{ raise Error, "no xx_which in effect" unless xx_which xx_class.xx_ancestors xx_which #--}}} end def xx_config #--{{{ xx_class.xx_config #--}}} end def xx_config_for *a, &b #--{{{ xx_class.xx_config_for(*a, &b) #--}}} end def xx_configure *a, &b #--{{{ xx_class.xx_configure(*a, &b) #--}}} end #--}}} end module ClassMethods #--{{{ def xx_tag_method_name m #--{{{ m = m.to_s tag_method, tag_name = m, m.gsub(%r/_+$/, "") [ tag_method, tag_name ] #--}}} end def xx_define_tmp_method m #--{{{ define_method(m){ raise NotImplementedError, m.to_s } #--}}} end def xx_define_tag_method tag_method, tag_name = nil #--{{{ tag_method = tag_method.to_s tag_name ||= tag_method.gsub %r/_+$/, "" remove_method tag_method if instance_methods.include? tag_method module_eval <<-code, __FILE__, __LINE__+1 def #{ tag_method } *a, &b hashes, nothashes = a.partition{|x| Hash === x} doc = xx_doc element = ::REXML::Element::new '#{ tag_name }' hashes.each{|h| h.each{|k,v| element.add_attribute k.to_s, v}} nothashes.each{|nh| element << ::REXML::Text::new(nh.to_s)} doc.create element, &b end code tag_method #--}}} end def xx_remove_tag_method tag_method #--{{{ remove_method tag_method rescue nil #--}}} end def xx_ancestors xx_which = self #--{{{ list = [] ancestors.each do |a| list << a if a < xx_which end xx_which.ancestors.each do |a| list << a if a <= Markup end list #--}}} end def xx_config #--{{{ @@xx_config ||= Hash::new{|h,k| h[k] = {}} #--}}} end def xx_config_for key, xx_which = nil #--{{{ key = key.to_s xx_which ||= self xx_ancestors(xx_which).each do |a| if xx_config[a].has_key? key return xx_config[a][key] end end nil #--}}} end def xx_configure key, value, xx_which = nil #--{{{ key = key.to_s xx_which ||= self xx_config[xx_which][key] = value #--}}} end #--}}} end extend ClassMethods include InstanceMethods def self::included other, *a, &b #--{{{ ret = super other.module_eval do include Markup::InstanceMethods extend Markup::ClassMethods class << self define_method("included", Markup::XX_MARKUP_RECURSIVE_INCLUSION_PROC) end end ret #--}}} end XX_MARKUP_RECURSIVE_INCLUSION_PROC = method("included").to_proc xx_configure "method_missing", XX::PERMISSIVE xx_configure "tags", [] xx_configure "doctype", nil xx_configure "xmldecl", nil #--}}} end module XHTML #--{{{ include Markup xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") def xhtml_ which = XHTML, *a, &b #--{{{ xx_which(which) do doc = xx_with_doc_in_effect(*a, &b) ddoc = doc.doc root = ddoc.root if root and root.name and root.name =~ %r/^html$/i if root.attribute("lang",nil).nil? or root.attribute("lang",nil).to_s.empty? root.add_attribute "lang", "en" end if root.attribute("xml:lang").nil? or root.attribute("xml:lang").to_s.empty? root.add_attribute "xml:lang", "en" end if root.namespace.nil? or root.namespace.to_s.empty? root.add_namespace "http://www.w3.org/1999/xhtml" end end doc end #--}}} end module Strict #--{{{ include XHTML xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd") xx_configure "tags", %w( html head body div span DOCTYPE title link meta style p h1 h2 h3 h4 h5 h6 strong em abbr acronym address bdo blockquote cite q code ins del dfn kbd pre samp var br a base img area map object param ul ol li dl dt dd table tr td th tbody thead tfoot col colgroup caption form input textarea select option optgroup button label fieldset legend script noscript b i tt sub sup big small hr ) xx_configure "method_missing", ::XX::STRICT def xhtml_ which = XHTML::Strict, *a, &b #--{{{ super(which, *a, &b) #--}}} end #--}}} end module Transitional #--{{{ include XHTML xx_configure "doctype", %(html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") def xhtml_ which = XHTML::Transitional, *a, &b #--{{{ super(which, *a, &b) #--}}} end #--}}} end #--}}} end module HTML4 #--{{{ include Markup xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN") def html4_ which = HTML4, *a, &b #--{{{ xx_which(which){ xx_with_doc_in_effect(*a, &b) } #--}}} end module Strict #--{{{ include HTML4 xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN") xx_configure "tags", %w( html head body div span DOCTYPE title link meta style p h1 h2 h3 h4 h5 h6 strong em abbr acronym address bdo blockquote cite q code ins del dfn kbd pre samp var br a base img area map object param ul ol li dl dt dd table tr td th tbody thead tfoot col colgroup caption form input textarea select option optgroup button label fieldset legend script noscript b i tt sub sup big small hr ) xx_configure "method_missing", ::XX::STRICT def html4_ which = HTML4::Strict, *a, &b #--{{{ super(which, *a, &b) #--}}} end #--}}} end module Transitional #--{{{ include HTML4 xx_configure "doctype", %(html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN") def html4_ which = HTML4::Transitional, *a, &b #--{{{ super(which, *a, &b) #--}}} end #--}}} end #--}}} end HTML = HTML4 module XML #--{{{ include Markup xx_configure "xmldecl", ::REXML::XMLDecl::new def xml_ *a, &b #--{{{ xx_which(XML){ xx_with_doc_in_effect(*a, &b)} #--}}} end #--}}} end #--}}} end $__xx_rb__ = __FILE__ end # # simple examples - see samples/ dir for more complete examples # if __FILE__ == $0 class Table < ::Array include XX::XHTML::Strict include XX::HTML4::Strict include XX::XML def doc html_{ head_{ title_{ "xhtml/html4/xml demo" } } div_{ h_{ "< malformed html & un-escaped symbols" } } t_{ "escaped & text > <" } x_{ " xml " } div_(:style => :sweet){ em_ "this is a table" table_(:width => 42, :height => 42){ each{|row| tr_{ row.each{|cell| td_ cell } } } } } script_(:type => :dangerous){ cdata_{ "javascript" } } } end def to_xhtml xhtml_{ doc } end def to_html4 html4_{ doc } end def to_xml xml_{ doc } end end table = Table[ %w( 0 1 2 ), %w( a b c ) ] methods = %w( to_xhtml to_html4 to_xml ) methods.each do |method| 2.times{ puts "-" * 42 } puts(table.send(method).pretty) puts end end # vi: set sw=4: # Here is Emacs setting. DO NOT REMOVE! # Local Variables: # ruby-indent-level: 4 # End: