class Strelka::CMS::PageFilter::Example
A filter for inline example code or command-line sessions – does syntax-highlighting (via CodeRay), 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
- END_PI
- EXAMPLE_PI
PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
Public Instance Methods
Highlights the given content
in language lang
.
# File lib/strelka/cms/pagefilter/example.rb, line 226 def highlight( content, lang ) source = ERB::Util.html_escape( content ) return %Q{\n\n<pre><code class="#{lang}">#{source}</code></pre>\n\n} end
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/strelka/cms/pagefilter/example.rb, line 156 def parse_options( args ) args = "{ #{args} }" unless args && 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
Process the given source
for <?example … ?> processing-instructions, calling out
# File lib/strelka/cms/pagefilter/example.rb, line 68 def process( source, page ) scanner = StringScanner.new( source ) buffer = '' until scanner.eos? startpos = scanner.pos # If we find an example if scanner.skip_until( EXAMPLE_PI ) 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( END_PI ) 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 self.log.debug "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
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/strelka/cms/pagefilter/example.rb, line 118 def process_example( params, body, page ) options = self.parse_options( params ) caption = options.delete( :caption ) content = '' lang = options.delete( :language ).to_s self.log.debug "Processing a %p example..." % [ lang ] # Test it if it's testable if options[:testable] content = test_content( body, lang, page ) else content = body end # If the language is something that can itself include PIs, look for the # special '<??end ?>' token and strip the extra '?' if %w[xml html textile xhtml].include?( lang ) content.gsub!( /<\?\?(.*?)\?>/ ) do tag_content = $1 self.log.debug "Unescaping escaped PI %p in example." % [ tag_content ] "<?#{tag_content}?>" end self.log.debug " example is now: %p" % [ content ] end # Strip trailing blank lines and syntax-highlight content = highlight( content.strip, lang ) caption = %{<div class="caption">} + caption.to_s + %{</div>} if caption return %{<notextile><div class="example %s-example">%s%s</div></notextile>} % [lang, content, caption || ''] end
Test the given content
with a rule specific to the given language
.
# File lib/strelka/cms/pagefilter/example.rb, line 171 def test_content( body, language, page ) self.log.debug "Running a testable %p example..." % [ language ] case language.to_sym when :ruby return self.test_ruby_content( body, page ) when :yaml return self.test_yaml_content( body, page ) else self.log.error "...oops, I don't know how to test %p examples." % [ language ] return body end end
Test the specified Ruby content for valid syntax
# File lib/strelka/cms/pagefilter/example.rb, line 188 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] = 80 if page.config['example_prelude'] prelude = page.config['example_prelude'] self.log.debug " prepending prelude:\n#{prelude}" source = prelude.strip + "\n\n" + source.strip else self.log.debug " no prelude; page config is: %p" % [ page.config ] end rval = Rcodetools::XMPFilter.run( source, options ) self.log.debug "test output: %p" % [ rval ] return rval.join rescue Exception => err return "%s while testing: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ] end
Test the specified YAML content for valid syntax
# File lib/strelka/cms/pagefilter/example.rb, line 216 def test_yaml_content( source, metadata ) YAML.load( source ) rescue YAML::Error => err return "# Invalid YAML: " + err.message + "\n" + source else return source end