eggshell 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/eggshell.rb ADDED
@@ -0,0 +1,758 @@
1
+ # Eggshell.
2
+ module Eggshell
3
+
4
+ # Tracks line and line count. Correctness depends
5
+ class LineCounter
6
+ def initialize(file = nil)
7
+ @file = file
8
+ @l_stack = []
9
+ @l_count = []
10
+ push
11
+ end
12
+
13
+ def line
14
+ return @l_stack[-1]
15
+ end
16
+
17
+ def line_num
18
+ c = 0
19
+ @l_count.each do |lc|
20
+ c += lc
21
+ end
22
+ return c
23
+ end
24
+
25
+ def file
26
+ @file
27
+ end
28
+
29
+ # Sets the current line and offset. If offset is not nil, resets the counter to this value.
30
+ # A sample situation where this is needed:
31
+ #
32
+ # pre.
33
+ # block here
34
+ # \
35
+ # @macro {
36
+ # macro line 1
37
+ # macro line 2
38
+ # }
39
+ # \
40
+ # last block
41
+ #
42
+ # During execution, `@macro` would push a new count state. After execution is over, it's popped,
43
+ # leaving the line number at the macro start. The main process loop, however, is keeping track
44
+ # of all the lines during its own execution, so it can set the offset to the actual line again.
45
+ def new_line(line, offset = nil)
46
+ @l_stack[-1] = line
47
+ @l_count[-1] = offset != nil ? offset : @l_count[-1] + 1
48
+ end
49
+
50
+ def push
51
+ @l_stack << nil
52
+ @l_count << 0
53
+ end
54
+
55
+ def pop
56
+ @l_stack.pop
57
+ @l_count.pop
58
+ nil
59
+ end
60
+ end
61
+
62
+ class Processor
63
+ BLOCK_MATCH = /^([a-z0-9_-]+\.|[|\/#><*+-]+)/
64
+ BLOCK_MATCH_PARAMS = /^([a-z0-9_-]+|[|\/#><*+-]+)\(/
65
+
66
+ def initialize
67
+ @context = Eggshell::ProcessorContext.new
68
+ @vars = @context.vars
69
+ @funcs = @context.funcs
70
+ @macros = @context.macros
71
+ @blocks = @context.blocks
72
+ @block_params = @context.block_params
73
+ @expr_cache = @context.expr_cache
74
+
75
+ @noop_macro = Eggshell::MacroHandler::Defaults::NoOpHandler.new
76
+ @noop_block = Eggshell::BlockHandler::Defaults::NoOpHandler.new
77
+ end
78
+
79
+ attr_reader :context
80
+
81
+ def register_macro(handler, *macros)
82
+ macros.each do |mac|
83
+ @macros[mac] = handler
84
+ _trace "register_macro: #{mac}: #{handler}"
85
+ end
86
+ end
87
+
88
+ def register_block(handler, *blocks)
89
+ blocks.each do |block|
90
+ @blocks[block] = handler
91
+ _trace "register_block: #{block}: #{handler}"
92
+ end
93
+ end
94
+
95
+ # Registers a function for embedded expressions. Functions are grouped into namespaces,
96
+ # and a handler can be assigned to handle all function calls within that namespace, or
97
+ # a specific set of functions within the namespace. The root namespace is a blank string.
98
+ #
99
+ # @param String func_key In the form `ns` or `ns:func_name`. For functions in the
100
+ # root namespace, do `:func_name`.
101
+ # @param Object handler
102
+ # @param Array func_names If `func_key` only refers to a namespace but the handler
103
+ # needs to only handle a subset of functions, supply the list of function names here.
104
+ def register_functions(func_key, handler, func_names = nil)
105
+ if !func_key.index(':') && func_names.is_a?(Array)
106
+ func_names.each do |fname|
107
+ @funcs[func_key+func_name] = handler
108
+ end
109
+ else
110
+ @funcs[func_key] = handler
111
+ end
112
+ end
113
+
114
+ def _error(msg)
115
+ $stderr.write("[ERROR] #{msg}\n")
116
+ end
117
+
118
+ def _warn(msg)
119
+ $stderr.write("[WARN] #{msg}\n")
120
+ end
121
+
122
+ def _info(msg)
123
+ return if @vars['log.level'] < '1'
124
+ $stderr.write("[INFO] #{msg}\n")
125
+ end
126
+
127
+ def _debug(msg)
128
+ return if @vars['log.level'] < '2'
129
+ $stderr.write("[DEBUG] #{msg}\n")
130
+ end
131
+
132
+ def _trace(msg)
133
+ return if @vars['log.level'] < '3'
134
+ $stderr.write("[TRACE] #{msg}\n")
135
+ end
136
+
137
+ attr_reader :vars
138
+
139
+ def expr_eval(struct)
140
+ return Eggshell::ExpressionEvaluator.expr_eval(struct, @vars, @funcs)
141
+ end
142
+
143
+ # Expands expressions (`\${}`) and macro calls (`\@@macro\@@`).
144
+ def expand_expr(expr)
145
+ # replace dynamic placeholders
146
+ # @todo expand to actual expressions
147
+ buff = []
148
+ esc = false
149
+ exp = false
150
+ mac = false
151
+
152
+ toks = expr.gsub(/\\[trn]/, HASH_LINE_ESCAPE).split(/(\\|\$\{|\}|@@|"|')/)
153
+ i = 0
154
+
155
+ plain_str = ''
156
+ expr_str = ''
157
+ quote = nil
158
+ expr_delim = nil
159
+
160
+ while i < toks.length
161
+ tok = toks[i]
162
+ i += 1
163
+ next if tok == ''
164
+
165
+ if esc
166
+ plain_str += tok
167
+ esc = false
168
+ next
169
+ end
170
+
171
+ if exp
172
+ if quote
173
+ expr_str += tok
174
+ if tok == quote
175
+ quote = nil
176
+ end
177
+ elsif tok == '"' || tok == "'"
178
+ expr_str += tok
179
+ quote = tok
180
+ elsif tok == expr_delim
181
+ struct = @expr_cache[expr_str]
182
+
183
+ if !struct
184
+ struct = Eggshell::ExpressionEvaluator.struct(expr_str)
185
+ @expr_cache[expr_str] = struct
186
+ end
187
+
188
+ if !mac
189
+ buff << expr_eval(struct)
190
+ else
191
+ args = struct[0]
192
+ macro = args[1]
193
+ args = args[2] || []
194
+ macro_handler = @macros[macro]
195
+ if macro_handler
196
+ macro_handler.process(buff, macro, args, nil, -1)
197
+ else
198
+ _warn("macro (inline) not found: #{macro}")
199
+ end
200
+ end
201
+
202
+ exp = false
203
+ mac = false
204
+ expr_delim = nil
205
+ expr_str = ''
206
+ else
207
+ expr_str += tok
208
+ end
209
+ # only unescape if not in expression, since expression needs to be given as-is
210
+ elsif tok == '\\'
211
+ esc = true
212
+ next
213
+ elsif tok == '${' || tok == '@@'
214
+ if plain_str != ''
215
+ buff << plain_str
216
+ plain_str = ''
217
+ end
218
+ exp = true
219
+ expr_delim = '}'
220
+ if tok == '@@'
221
+ mac = true
222
+ expr_delim = tok
223
+ end
224
+ else
225
+ plain_str += tok
226
+ end
227
+ end
228
+
229
+ # if exp -- throw exception?
230
+ buff << plain_str if plain_str != ''
231
+ return buff.join('')
232
+ end
233
+
234
+ TAB = "\t"
235
+ TAB_SPACE = ' '
236
+
237
+ # html tags that have end-block checks. any block starting with one of these tags will have
238
+ # its contents passed through until end of the tag
239
+ # @todo what else should be treated?
240
+ HTML_BLOCK = /^<(style|script|table|dl|select|textarea|\!--|\?)/
241
+ HTML_BLOCK_END = {
242
+ '<!--' => '-->',
243
+ '<?' => '\\?>'
244
+ }.freeze
245
+
246
+ # For lines starting with only these tags, accept as-is
247
+ HTML_PASSTHRU = /^\s*<(\/?(html|head|meta|link|title|body|br|section|div|blockquote|p|pre))/
248
+
249
+ HASH_LINE_ESCAPE = {
250
+ "\\n" => "\n",
251
+ "\\r" => "\r",
252
+ "\\t" => "\t",
253
+ "\\\\" => "\\"
254
+ }
255
+
256
+ # @param Boolean is_default If true, associates these parameters with the
257
+ # `block_type` used in `get_block_param()` or explicitly in third parameter.
258
+ # @param String block_type
259
+ def set_block_params(params, is_default = false, block_type = nil)
260
+ if block_type && is_default
261
+ @block_params[block_type] = params
262
+ else
263
+ @block_params[:pending] = params
264
+ @block_param_default = is_default
265
+ end
266
+ end
267
+
268
+ # Gets the block parameters for a block type, and merges default values if available.
269
+ def get_block_params(block_type)
270
+ bp = @block_params.delete(:pending)
271
+ if @block_params_default
272
+ if block_type && bp
273
+ @block_params[block_type] = bp if bp
274
+ end
275
+ @block_params_default = false
276
+ bp = {} if !bp
277
+ else
278
+ bp = {} if !bp
279
+ default = @block_params[block_type]
280
+ if default
281
+ default.each do |key,val|
282
+ if !bp.has_key?(key) && val
283
+ bp[key] = val.clone
284
+ end
285
+ end
286
+ end
287
+ end
288
+ return bp
289
+ end
290
+
291
+ # Iterates through each line of a source document and processes block-level items
292
+ # @param Fixnum call_depth For macro processing. Allows accurate tracking of nested
293
+ # block macros.
294
+ def process(lines, call_depth = 0)
295
+ buff = []
296
+ order_stack = []
297
+ otype_stack = []
298
+ in_table = false
299
+ in_html = false
300
+ end_html = nil
301
+ in_block = false
302
+ in_dl = false
303
+
304
+ macro = nil
305
+ macro_blocks = []
306
+ macro_handler = nil
307
+ macro_depth = call_depth + 1
308
+
309
+ block = nil
310
+ ext_line = nil
311
+
312
+ block_handler = nil
313
+ block_handler_raw = false
314
+ block_handler_indent = 0
315
+
316
+ i = 0
317
+
318
+ begin
319
+ while (i <= lines.length)
320
+ line = nil
321
+ indent_level = 0
322
+ indents = ''
323
+
324
+ # special condition to get a dangling line
325
+ if i == lines.length
326
+ if ext_line
327
+ line = ext_line
328
+ ext_line = nil
329
+ else
330
+ break
331
+ end
332
+ else
333
+ line = lines[i]
334
+ end
335
+ i += 1
336
+
337
+ if line.is_a?(Block)
338
+ line.process(buff)
339
+ next
340
+ end
341
+
342
+ orig = line
343
+ oline = line
344
+
345
+ # @todo configurable space tab?
346
+ offset = 0
347
+ tablen = 0
348
+ if line[0] == TAB || line[0..3] == TAB_SPACE
349
+ tab = line[0] == TAB ? TAB : TAB_SPACE
350
+ tablen = tab.length
351
+ indent_level += 1
352
+ offset = tablen
353
+ while line[offset...offset+tablen] == tab
354
+ indent_level += 1
355
+ offset += tablen
356
+ end
357
+ # if block_handler_indent > 0
358
+ # indent_level -= block_handler_indent
359
+ # offset -= (tablen * block_handler_indent)
360
+ # end
361
+ indents = line[0...offset]
362
+ line = line[offset..-1]
363
+ end
364
+
365
+ line = line.rstrip
366
+ line_end = ''
367
+ if line.length < oline.length
368
+ line_end = oline[line.length..-1]
369
+ end
370
+
371
+ # if line end in \, buffer and continue to next line;
372
+ # join buffered line once \ no longer at end
373
+ if line[-1] == '\\' && line.length > 1
374
+ if line[-2] != '\\'
375
+ # special case: if a line consists of a single \, assume line ending is wanted,
376
+ # otherwise join directly with previous line
377
+ if line == '\\'
378
+ line = line_end
379
+ else
380
+ line = line[0..-2]
381
+ end
382
+
383
+ if ext_line
384
+ ext_line += indents + line
385
+ else
386
+ ext_line = indents + line
387
+ end
388
+ next
389
+ else
390
+ line = line[0..-2]
391
+ end
392
+ end
393
+
394
+ # join this line with last line and terminate last line
395
+ if ext_line
396
+ line = ext_line + line
397
+ ext_line = nil
398
+ end
399
+ oline = line
400
+
401
+ if line[0..1] == '!#'
402
+ next
403
+ end
404
+
405
+ # relative indenting
406
+ if block_handler_indent > 0
407
+ indents = indents[(tablen*block_handler_indent)..-1]
408
+ end
409
+
410
+ if block_handler_raw
411
+ stat = block_handler.collect(line, buff, indents, indent_level - block_handler_indent)
412
+ if stat != Eggshell::BlockHandler::COLLECT_RAW
413
+ block_handler_raw = false
414
+ if stat != Eggshell::BlockHandler::COLLECT
415
+ block_handler = nil
416
+ if stat == Eggshell::BlockHandler::RETRY
417
+ i -= 1
418
+ end
419
+ end
420
+ end
421
+ next
422
+ end
423
+
424
+ # macro processing
425
+ if line[0] == '@'
426
+ macro = nil
427
+ args = nil
428
+ delim = nil
429
+
430
+ if line.index(' ') || line.index('(') || line.index('{')
431
+ # since the macro statement is essentially a function call, parse the line as an expression
432
+ expr_struct = ExpressionEvaluator.struct(line)
433
+ fn = expr_struct.shift
434
+ if fn.is_a?(Array) && fn[0] == :fn
435
+ macro = fn[1][1..fn[1].length]
436
+ args = fn[2]
437
+ if expr_struct[-1].is_a?(Array) && expr_struct[-1][0] == :brace_op
438
+ delim = expr_struct[-1][1]
439
+ end
440
+ end
441
+ else
442
+ macro = line[1..line.length]
443
+ end
444
+
445
+ # special case: block parameter
446
+ if macro == '!'
447
+ set_block_params(args[0], args[1], args[2])
448
+ next
449
+ end
450
+
451
+ macro_handler = @macros[macro]
452
+ if macro_handler
453
+ if delim
454
+ if block
455
+ nblock = Eggshell::Block.new(macro, macro_handler, args, block.cur.depth + 1, delim)
456
+ block.push(nblock)
457
+ else
458
+ block = Eggshell::Block.new(macro, macro_handler, args, macro_depth, delim)
459
+ end
460
+ else
461
+ if block
462
+ block.collect(Eggshell::Block.new(macro, macro_handler, args, macro_depth, nil))
463
+ else
464
+ macro_handler.process(buff, macro, args, nil, macro_depth)
465
+ end
466
+ end
467
+ else
468
+ _warn("macro not found: #{macro} | #{line}")
469
+ end
470
+ next
471
+ elsif block
472
+ if line == block.cur.delim
473
+ lb = block.pop
474
+ if !block.cur
475
+ block.process(buff)
476
+ block = nil
477
+ end
478
+ else
479
+ block.cur.collect(orig.rstrip)
480
+ end
481
+ next
482
+ end
483
+
484
+ if block_handler
485
+ stat = block_handler.collect(line, buff, indents, indent_level - block_handler_indent)
486
+
487
+ if stat == Eggshell::BlockHandler::COLLECT_RAW
488
+ block_handler_raw = true
489
+ elsif stat != Eggshell::BlockHandler::COLLECT
490
+ block_handler = nil
491
+ block_handler_raw = false
492
+ if stat == Eggshell::BlockHandler::RETRY
493
+ i -= 1
494
+ end
495
+ end
496
+ line = nil
497
+ next
498
+ end
499
+
500
+ if line.match(HTML_PASSTHRU)
501
+ if block_handler
502
+ block_handler.collect(nil, buff)
503
+ block_handler = nil
504
+ end
505
+ buff << fmt_line(line)
506
+ next
507
+ end
508
+
509
+ # html block processing
510
+ html = line.match(HTML_BLOCK)
511
+ if html
512
+ end_html = HTML_BLOCK_END["<#{html[1]}"]
513
+ end_html = "</#{html[1]}>$" if !end_html
514
+ if !line.match(end_html)
515
+ in_html = true
516
+ end
517
+
518
+ line = @vars['html.no_eval'] ? orig : expand_expr(orig)
519
+ buff << line.rstrip
520
+
521
+ next
522
+ elsif in_html
523
+ if line == ''
524
+ buff << line
525
+ else
526
+ line = @vars['html.no_eval'] ? orig : expand_expr(orig)
527
+ buff << line.rstrip
528
+ end
529
+
530
+ if line.match(end_html)
531
+ in_html = false
532
+ end_html = nil
533
+ @vars.delete('html.no_eval')
534
+ end
535
+ next
536
+ end
537
+
538
+ # @todo try to map indent to a block handler
539
+ next if line == ''
540
+
541
+ # check if the block starts off and matches against any handlers; if not, assign 'p' as default
542
+ # two checks: `block(params).`; `block.`
543
+ block_type = nil
544
+ bt = line.match(BLOCK_MATCH_PARAMS)
545
+ if bt
546
+ idx0 = bt[0].length
547
+ idx1 = line.index(').', idx0)
548
+ if idx1
549
+ block_type = line[0..idx0-2]
550
+ params = line[0...idx1+1].strip
551
+ line = line[idx1+2..line.length] || ''
552
+ if params != ''
553
+ struct = Eggshell::ExpressionEvaluator.struct(params)
554
+ arg0 = struct[0][2][0]
555
+ arg1 = struct[0][2][1]
556
+ arg0 = expr_eval(arg0) if arg0
557
+ set_block_params(arg0, arg1)
558
+ end
559
+ end
560
+ else
561
+ block_type = line.match(BLOCK_MATCH)
562
+ if block_type && block_type[0].strip != ''
563
+ block_type = block_type[1]
564
+ len = block_type.length
565
+ block_type = block_type[0..-2] if block_type[-1] == '.'
566
+ line = line[len..line.length] || ''
567
+ else
568
+ block_type = nil
569
+ end
570
+ end
571
+
572
+
573
+ block_type = 'p' if !block_type
574
+ block_handler_indent = indent_level
575
+ block_handler = @blocks[block_type]
576
+ block_handler = @noop_block if !block_handler
577
+ stat = block_handler.start(block_type, line.lstrip, buff, indents, indent_level)
578
+ # block handler won't continue to next line; clear and possibly retry
579
+ if stat == Eggshell::BlockHandler::COLLECT_RAW
580
+ block_handler_raw = true
581
+ elsif stat != Eggshell::BlockHandler::COLLECT
582
+ block_handler = nil
583
+ if stat == Eggshell::BlockHandler::RETRY
584
+ i -= 1
585
+ end
586
+ else
587
+ line = nil
588
+ end
589
+ end
590
+
591
+ if block_handler
592
+ block_handler.collect(line, buff, indents, indent_level - block_handler_indent) if line
593
+ block_handler.collect(nil, buff)
594
+ end
595
+ rescue => ex
596
+ _error "Exception approximately on line: #{line}"
597
+ _error ex.message + "\t#{ex.backtrace.join("\n\t")}"
598
+ _error "vars = #{@vars.inspect}"
599
+ end
600
+
601
+ return buff.join("\n")
602
+ end
603
+
604
+ HASH_FMT_DECORATORS = {
605
+ '[*' => '<b>',
606
+ '[**' => '<strong>',
607
+ '[_' => '<i>',
608
+ '[__' => '<em>',
609
+ '*]'=> '</b>',
610
+ '**]' => '</strong>',
611
+ '_]' => '</i>',
612
+ '__]' => '</em>',
613
+ '[-_' => '<u>',
614
+ '_-]' => '</u>',
615
+ '[-' => '<strike>',
616
+ '-]' => '</strike>'
617
+ }.freeze
618
+
619
+ HASH_HTML_ESCAPE = {
620
+ "'" => '&#039;',
621
+ '"' => '&quot;',
622
+ '<' => '&lt;',
623
+ '>' => '&gt;',
624
+ '&' => '&amp;'
625
+ }.freeze
626
+
627
+ # @todo more chars
628
+ def html_escape(str)
629
+ return str.gsub(/("|'|<|>|&)/, HASH_HTML_ESCAPE)
630
+ end
631
+
632
+ # Symbols in conjunction with '[' prefix and ']' suffix that define shortcut macros.
633
+ # While extensible, the standard macros are: `*`, `**`, `_`, `__`, `-`, `-_`, `^`, `.`
634
+ MACRO_OPS = "%~=!?.^\\/*_+-"
635
+ INLINE_MARKUP_REGEX_OP = Regexp.new("\\[[#{MACRO_OPS}]+")
636
+ INLINE_MARKUP = Regexp.new("(`|\\{\\{|\\}\\}|\\[[#{MACRO_OPS}]+|[#{MACRO_OPS}]+\\]|\\\\|\\|)")
637
+
638
+ # Expands markup for a specific line.
639
+ def fmt_line(expr)
640
+ buff = []
641
+ bt = false
642
+ cd = false
643
+ esc = false
644
+
645
+ macro = false
646
+ macro_buff = ''
647
+
648
+ inline_op = nil
649
+ inline_delim = nil
650
+ inline_args = nil
651
+ inline_part = nil
652
+ inline_esc = false
653
+
654
+ # split and preserve delimiters: ` {{ }} [[ ]]
655
+ # - preserve contents of code blocks (only replacing unescaped placeholder values)
656
+ ## - handle anchor and image
657
+ toks = expr.gsub(/\\[trn]/, HASH_LINE_ESCAPE).split(INLINE_MARKUP)
658
+ i = 0
659
+
660
+ while i < toks.length
661
+ part = toks[i]
662
+ i += 1
663
+ next if part == ''
664
+
665
+ if esc
666
+ buff << '\\' if part == '\\' || part[0..1] == '${'
667
+ buff << part
668
+ esc = false
669
+ elsif part == '\\'
670
+ esc = true
671
+ elsif part == '`' && !cd
672
+ if !bt
673
+ bt = true
674
+ buff << "<code class='tick'>"
675
+ else
676
+ bt = false
677
+ buff << '</code>'
678
+ end
679
+ elsif part == '{{' && !bt
680
+ cd = true
681
+ buff << "<code class='norm'>"
682
+ elsif part == '}}' && !bt
683
+ buff << '</code>'
684
+ cd = false
685
+ elsif bt || cd
686
+ buff << html_escape(expand_expr(part))
687
+ elsif (part[0] == '[' && part.match(INLINE_MARKUP_REGEX_OP))
688
+ # parse OP + {term or '|'}* + DELIM
689
+ inline_op = part
690
+ i = expand_macro_brackets(inline_op, i, toks, buff)
691
+ inline_op = nil
692
+ else
693
+ buff << part
694
+ end
695
+ end
696
+ # if inline_op
697
+ # inline_args << inline_part if inline_part
698
+ # @macros[inline_op].process(buff, inline_op, inline_args, nil, nil)
699
+ # end
700
+ return expand_expr(buff.join(''))
701
+ end
702
+
703
+ def expand_macro_brackets(inline_op, i, toks, buff)
704
+ inline_delim = inline_op.reverse.gsub('[', ']')
705
+ inline_args = []
706
+ inline_part = nil
707
+ inline_esc = false
708
+
709
+ # @todo check for quotes?
710
+ # @todo make this a static function
711
+ while i < toks.length
712
+ part = toks[i]
713
+ i += 1
714
+
715
+ if inline_esc
716
+ inline_part += part
717
+ inline_esc = false if part != ''
718
+ elsif part == '\\'
719
+ esc = true
720
+ elsif part.match(INLINE_MARKUP_REGEX_OP)
721
+ i = expand_macro_brackets(part, i, toks, buff)
722
+ inline_part = '' if !inline_part
723
+ inline_part += buff.pop
724
+ elsif part.end_with?(inline_delim) || part.end_with?('/]')
725
+ # in the case where a special char immediately precedes end delimiter, move it on to
726
+ # the inline body (e.g. `[//emphasis.//]` or `[*bold.*]`)
727
+ len = part.end_with?(inline_delim) ? inline_delim.length : 2
728
+ if part.length > len
729
+ inline_part += part[0...-len]
730
+ end
731
+ break
732
+ elsif part == '|'
733
+ inline_args << inline_part
734
+ inline_part = nil
735
+ else
736
+ inline_part = '' if !inline_part
737
+ inline_part += part
738
+ end
739
+ end
740
+
741
+ inline_args << inline_part if inline_part
742
+ if @macros[inline_op]
743
+ @macros[inline_op].process(buff, inline_op, inline_args, nil, nil)
744
+ else
745
+ buff << "#{inline_op}#{inline_args.join('|')}#{inline_delim}"
746
+ end
747
+
748
+ return i
749
+ end
750
+ end
751
+ end
752
+
753
+ require_relative './eggshell/block.rb'
754
+ require_relative './eggshell/processor-context.rb'
755
+ require_relative './eggshell/expression-evaluator.rb'
756
+ require_relative './eggshell/macro-handler.rb'
757
+ require_relative './eggshell/block-handler.rb'
758
+ require_relative './eggshell/bundles.rb'