--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chunker/lib/chunker.rb Sat Nov 08 18:59:05 2008 +0000
@@ -0,0 +1,125 @@
+#
+# Chunker!
+#
+# Mahlon E. Smith <mahlon@martini.nu>
+#
+
+
+### 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 __MARKER__ within the __END__, and put each into a
+ ### DATA_MARKER constant within the namespace that included us.
+ ###
+ class DataParser
+
+ # The mark for a DATA block.
+ #
+ END_MARKER = /^__END__\r?\n/
+
+ # The mark for a 'sub' block.
+ #
+ CHUNK_MARKER = /^__([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_MARKER, 2 ).last
+
+ @klass = klass
+ @scanner = StringScanner.new( end_string )
+ io.close
+
+ if @scanner.check_until( CHUNK_MARKER )
+ # 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_MARKER ) and ! @scanner.eos?
+ data = ''
+
+ # First pass, __END__ contents (until next marker, 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_MARKER )
+ # Pull the next marker 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
+
+
+ ### Included hook: Find the file path for how we arrived here, and open
+ ### it as an IO object. __FILE__ won't work, so we find it via caller().
+ ### Start parsing this file for data blocks.
+ ###
+ def self.included( klass )
+ # klass.instance_eval{ __FILE__ } awww, nope.
+ io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' )
+ DataParser.new( klass, io )
+ end
+end
+