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 |