ExamplesFilter

class
Superclass
Hoe::ManualGen::PageFilter

A filter for inline example code or command-line sessions – does syntax-checking for some languages and captioning.

Examples are enclosed in XML processing instructions like so:

<?example {language: ruby, testable: true, caption: "A fine example"} ?>
   a = 1
   puts a
<?end example ?>

This will be pulled out into a preformatted section in the HTML, highlighted as Ruby source, checked for valid syntax, and annotated with the specified caption. Valid keys in the example PI are:

language

Specifies which (machine) language the example is in.

testable

If set and there is a testing function for the given language, run it and append any errors to the output.

caption

A small blurb to put below the pulled-out example in the HTML.

Constants

DEFAULTS
EndPI
ExamplePI

PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'

Public Class Methods

anchor
new( *args )

Defer loading of dependenies until the filter is loaded

# File lib/hoe/manualgen/examples-filter.rb, line 64
def initialize( *args )
        require 'pathname'
        require 'strscan'
        require 'yaml'
        require 'rcodetools/xmpfilter'
        require 'digest/md5'
        require 'tmpdir'
        require 'erb'
end

Public Instance Methods

anchor
highlight( content, options, lang )

Highlights the given content in language lang.

# File lib/hoe/manualgen/examples-filter.rb, line 223
def highlight( content, options, lang )
        source = ERB::Util.html_escape( content )
        return %Q{\n\n<pre class="brush:#{lang}">#{source}</pre>\n\n}
end
anchor
parse_options( args )

Parse an options hash for filtering from the given args, which can either be a plain String, in which case it is assumed to be the name of the language the example is in, or a Hash of configuration options.

# File lib/hoe/manualgen/examples-filter.rb, line 155
def parse_options( args )
        args = "{ #{args} }" unless args.strip[0] == ?{
        args = YAML.load( args )

        # Convert to Symbol keys and value
        args.keys.each do |k|
                newval = args.delete( k )
                next if newval.nil? || (newval.respond_to?(:size) && newval.size == 0)
                args[ k.to_sym ] = newval.respond_to?( :to_sym ) ? newval.to_sym : newval
        end
        return DEFAULTS.merge( args )
end
anchor
process( source, page, metadata )

Process the given source for <?example … ?> processing-instructions, calling out

# File lib/hoe/manualgen/examples-filter.rb, line 80
def process( source, page, metadata )
        scanner = StringScanner.new( source )

        buffer = ''
        until scanner.eos?
                startpos = scanner.pos

                # If we find an example
                if scanner.skip_until( ExamplePI )
                        contents = ''

                        # Append the interstitial content to the buffer
                        if ( scanner.pos - startpos > scanner.matched.length )
                                offset = scanner.pos - scanner.matched.length - 1
                                buffer << scanner.string[ startpos..offset ]
                        end

                        # Append everything up to it to the buffer and save the contents of
                        # the tag
                        params = scanner[1]

                        # Now find the end of the example or complain
                        contentpos = scanner.pos
                        scanner.skip_until( EndPI ) or
                                raise "Unterminated example at line %d" %
                                        [ scanner.string[0..scanner.pos].count("\n") ]

                        # Now build the example and append to the buffer
                        if ( scanner.pos - contentpos > scanner.matched.length )
                                offset = scanner.pos - scanner.matched.length - 1
                                contents = scanner.string[ contentpos..offset ]
                        end

                        trace "Processing with params: %p, contents: %p" % [ params, contents ]
                        buffer << self.process_example( params, contents, page )
                else
                        break
                end

        end
        buffer << scanner.rest
        scanner.terminate

        return buffer
end
anchor
process_example( params, body, page )

Filter out 'example' macros, doing syntax highlighting, and running 'testable' examples through a validation process appropriate to the language the example is in.

# File lib/hoe/manualgen/examples-filter.rb, line 130
def process_example( params, body, page )
        options = self.parse_options( params )
        caption = options.delete( :caption )
        content = ''
        lang = options.delete( :language ).to_s

        # Test it if it's testable
        if options[:testable]
                content = test_content( body, lang, page )
        else
                content = body
        end

        # Strip trailing blank lines and syntax-highlight
        content = highlight( content.strip, options, lang )
        caption = %Q{<div class="caption">} + caption.to_s + %Q{</div>} if caption

        return %Q{<notextile><div class="example #{lang}-example">%s%s</div></notextile>} %
                [content, caption || '']
end
anchor
test_content( body, language, page )

Test the given content with a rule specific to the given language.

# File lib/hoe/manualgen/examples-filter.rb, line 170
def test_content( body, language, page )
        case language.to_sym
        when :ruby
                return self.test_ruby_content( body, page )

        when :yaml
                return self.test_yaml_content( body, page )

        else
                return body
        end
end
anchor
test_ruby_content( source, page )

Test the specified Ruby content for valid syntax

# File lib/hoe/manualgen/examples-filter.rb, line 185
def test_ruby_content( source, page )
        # $stderr.puts "Testing ruby content..."
        libdir = Pathname.new( __FILE__ ).dirname.parent.parent.parent + 'lib'
        extdir = Pathname.new( __FILE__ ).dirname.parent.parent.parent + 'ext'

        options = Rcodetools::XMPFilter::INITIALIZE_OPTS.dup
        options[:include_paths] |= [ libdir.to_s, extdir.to_s ]
        options[:width] = 60

        if page.config['example_prelude']
                prelude = page.config['example_prelude']
                trace "  prepending prelude:\n#{prelude}"
                source = prelude.strip + "\n" + source.strip
        else
                trace "  no prelude; page config is: %p" % [ page.config ]
        end

        rval = Rcodetools::XMPFilter.run( source, options )

        trace "test output: ", rval
        return rval.join
rescue Exception => err
        return "%s while testing: %s\n  %s" %
                [ err.class.name, err.message, err.backtrace.join("\n  ") ]
end
anchor
test_yaml_content( source, metadata )

Test the specified YAML content for valid syntax

# File lib/hoe/manualgen/examples-filter.rb, line 213
def test_yaml_content( source, metadata )
        YAML.load( source )
rescue YAML::Error => err
        return "# Invalid YAML: " + err.message + "\n" + source
else
        return source
end