chunker/lib/chunker.rb
author mahlon
Sat, 08 Nov 2008 18:59:05 +0000
branchruby-modules
changeset 1 9e127bf6e84f
child 2 e5c705047540
permissions -rw-r--r--
Initial commit of chunker, a ruby module to aid with data blocks.

#
# 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