chunker/lib/chunker.rb
author mahlon
Thu, 04 Jun 2009 17:28:02 +0000
branchruby-modules
changeset 3 233041485364
parent 2 e5c705047540
child 4 01a3332bfe0a
permissions -rw-r--r--
Require spec implicitly.

#!/usr/bin/ruby
#
# Chunker: A convenience library for parsing __END__ tokens consistently.
#
# == Version
#
#	$Id$
#
# == Author
#
# * Mahlon E. Smith <mahlon@martini.nu>
#
# :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