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