chunker/lib/chunker.rb
branchruby-modules
changeset 2 e5c705047540
parent 1 9e127bf6e84f
child 4 01a3332bfe0a
equal deleted inserted replaced
1:9e127bf6e84f 2:e5c705047540
       
     1 #!/usr/bin/ruby
     1 #
     2 #
     2 # Chunker!
     3 # Chunker: A convenience library for parsing __END__ tokens consistently.
     3 #
     4 #
     4 #	Mahlon E. Smith <mahlon@martini.nu>
     5 # == Version
     5 #
     6 #
     6 
     7 #	$Id$
       
     8 #
       
     9 # == Author
       
    10 #
       
    11 # * Mahlon E. Smith <mahlon@martini.nu>
       
    12 #
       
    13 # :include: LICENSE
       
    14 #
     7 
    15 
     8 ### Namespace for the datablock parser.
    16 ### Namespace for the datablock parser.
     9 ###
    17 ###
    10 module Chunker
    18 module Chunker
    11 
    19 
    24 	#
    32 	#
    25 	VERSION = '0.1'
    33 	VERSION = '0.1'
    26 
    34 
    27 
    35 
    28 	### Parser class for __END__ data blocks.
    36 	### Parser class for __END__ data blocks.
    29 	### Find each __MARKER__ within the __END__, and put each into a
    37 	### Find each __TOKEN__ within the __END__, and put each into a
    30 	### DATA_MARKER constant within the namespace that included us.
    38 	### DATA_TOKEN constant within the namespace that included us.
    31 	###
    39 	###
    32 	class DataParser
    40 	class DataParser
    33 
    41 
    34 		# The mark for a DATA block.
    42 		# The mark for a DATA block.
    35 		#
    43 		#
    36 		END_MARKER = /^__END__\r?\n/
    44 		END_TOKEN = /^__END__\r?\n/
    37 
    45 
    38 		# The mark for a 'sub' block.
    46 		# The mark for a 'sub' block.
    39 		#
    47 		#
    40 		CHUNK_MARKER = /^__([A-Z\_0-9]+)__\r?\n/
    48 		CHUNK_TOKEN = /^__([A-Z\_0-9]+)__\r?\n/
    41 
    49 
    42 
    50 
    43 		### Constructor: Given a +klass+ and an +io+ to the class file,
    51 		### Constructor: Given a +klass+ and an +io+ to the class file,
    44 		### extract the data blocks and install constants.
    52 		### extract the data blocks and install constants.
    45 		###
    53 		###
    46 		def initialize( klass, io )
    54 		def initialize( klass, io )
    47 			io.open if io.closed?
    55 			io.open if io.closed?
    48 			end_string = io.read.split( END_MARKER, 2 ).last
    56 			end_string = io.read.split( END_TOKEN, 2 ).last
    49 
    57 
    50 			@klass   = klass
    58 			@klass   = klass
    51 			@scanner = StringScanner.new( end_string )
    59 			@scanner = StringScanner.new( end_string )
    52 			io.close
    60 			io.close
    53 
    61 
    54 			if @scanner.check_until( CHUNK_MARKER )
    62 			if @scanner.check_until( CHUNK_TOKEN )
    55 				# put each chunk into its own constant
    63 				# put each chunk into its own constant
    56 				self.extract_blocks
    64 				self.extract_blocks
    57 			else
    65 			else
    58 				# no sub blocks, put the whole mess into DATA_END
    66 				# no sub blocks, put the whole mess into DATA_END
    59 				@klass.const_set( :DATA_END, StringIO.new( end_string ) )
    67 				@klass.const_set( :DATA_END, StringIO.new( end_string ) )
    69 		### IO constants in the including class.
    77 		### IO constants in the including class.
    70 		###
    78 		###
    71 		def extract_blocks
    79 		def extract_blocks
    72 			label = nil
    80 			label = nil
    73 
    81 
    74 			while @scanner.scan_until( CHUNK_MARKER ) and ! @scanner.eos?
    82 			while @scanner.scan_until( CHUNK_TOKEN ) and ! @scanner.eos?
    75 				data = ''
    83 				data = ''
    76 
    84 
    77 				# First pass, __END__ contents (until next marker, instead
    85 				# First pass, __END__ contents (until next token, instead
    78 				# of entire data block.)
    86 				# of entire data block.)
    79 				#
    87 				#
    80 				if label.nil?
    88 				if label.nil?
    81 					label = 'END'
    89 					label = 'END'
    82 					data  = @scanner.pre_match
    90 					data  = @scanner.pre_match
    83 
    91 
    84 					@scanner.pos = self.next_position
    92 					@scanner.pos = self.next_position
    85 				else
    93 				else
    86 					label = @scanner[1]
    94 					label = @scanner[1]
    87 
    95 
    88 					if data = @scanner.scan_until( CHUNK_MARKER )
    96 					if data = @scanner.scan_until( CHUNK_TOKEN )
    89 						# Pull the next marker text out of the data, set up the next pass
    97 						# Pull the next token text out of the data, set up the next pass
    90 						#
    98 						#
    91 						data         = data[ 0, data.length - @scanner[0].length ]
    99 						data         = data[ 0, data.length - @scanner[0].length ]
    92 						@scanner.pos = self.next_position
   100 						@scanner.pos = self.next_position
    93 					else
   101 					else
    94 						# No additional blocks
   102 						# No additional blocks
   110 			return @scanner.pos - @scanner[0].length
   118 			return @scanner.pos - @scanner[0].length
   111 		end
   119 		end
   112 	end
   120 	end
   113 
   121 
   114 
   122 
   115 	### Included hook: Find the file path for how we arrived here, and open
   123 	### Hook included: Find the file path for how we arrived here, and open
   116 	### it as an IO object. __FILE__ won't work, so we find it via caller().
   124 	### it as an IO object.  Parse the IO for data block tokens.
   117 	### Start parsing this file for data blocks.
       
   118 	###
   125 	###
   119     def self.included( klass )
   126     def self.included( klass )
   120 		# klass.instance_eval{ __FILE__ }   awww, nope.
   127 		# klass.instance_eval{ __FILE__ }   awww, nope.
       
   128 		# __FILE__ won't work here, so we find the filename via caller().
       
   129 		#
   121 		io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' )
   130 		io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' )
       
   131 
   122 		DataParser.new( klass, io )
   132 		DataParser.new( klass, io )
   123     end
   133     end
   124 end
   134 end
   125 
   135