#!/usr/bin/ruby # # Chunker: A convenience library for parsing __END__ tokens consistently. # # == Version # # $Id$ # # == Author # # * Mahlon E. Smith # # :include: LICENSE # ### Namespace for the datablock parser. ### module Chunker require 'strscan' require 'stringio' # SVN Revision # SVNRev = %q$Rev$ # SVN Id # SVNId = %q$Id$ # Package version # VERSION = '0.1' ### Parser class for __END__ data blocks. ### Find each __TOKEN__ within the __END__, and put each into a ### DATA_TOKEN constant within the namespace that included us. ### class DataParser # The mark for a DATA block. # END_TOKEN = /^__END__\r?\n/ # The mark for a 'sub' block. # CHUNK_TOKEN = /^__([A-Z\_0-9]+)__\r?\n/ ### Constructor: Given a +klass+ and an +io+ to the class file, ### extract the data blocks and install constants. ### def initialize( klass, io ) io.open if io.closed? end_string = io.read.split( END_TOKEN, 2 ).last @klass = klass @scanner = StringScanner.new( end_string ) io.close if @scanner.check_until( CHUNK_TOKEN ) # put each chunk into its own constant self.extract_blocks else # no sub blocks, put the whole mess into DATA_END @klass.const_set( :DATA_END, StringIO.new( end_string ) ) end end ######### protected ######### ### Parse the current +io+ for data blocks, set contents to ### IO constants in the including class. ### def extract_blocks label = nil while @scanner.scan_until( CHUNK_TOKEN ) and ! @scanner.eos? data = '' # First pass, __END__ contents (until next token, instead # of entire data block.) # if label.nil? label = 'END' data = @scanner.pre_match @scanner.pos = self.next_position else label = @scanner[1] if data = @scanner.scan_until( CHUNK_TOKEN ) # Pull the next token text out of the data, set up the next pass # data = data[ 0, data.length - @scanner[0].length ] @scanner.pos = self.next_position else # No additional blocks # data = @scanner.rest end end # Add the IO constant to the class that included me. # @klass.const_set( "DATA_#{label}".to_sym, StringIO.new( data ) ) end end ### Return the next scanner position for searching. ### def next_position return @scanner.pos - @scanner[0].length end end ### Hook included: Find the file path for how we arrived here, and open ### it as an IO object. Parse the IO for data block tokens. ### def self.included( klass ) # klass.instance_eval{ __FILE__ } awww, nope. # __FILE__ won't work here, so we find the filename via caller(). # io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' ) DataParser.new( klass, io ) end end