chunker/lib/chunker.rb
branchruby-modules
changeset 1 9e127bf6e84f
child 2 e5c705047540
--- /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
+