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