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
@@ -1,293 +1,239 @@
|
|
1
|
-
|
1
|
+
module Eggshell::Bundles::Basic
|
2
2
|
BUNDLE_ID = 'basics'
|
3
3
|
EE = Eggshell::ExpressionEvaluator
|
4
|
+
BH = Eggshell::BlockHandler
|
5
|
+
MH = Eggshell::MacroHandler
|
6
|
+
FH = Eggshell::FormatHandler
|
4
7
|
|
5
8
|
def self.new_instance(proc, opts = nil)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
TextBlocks.new.set_processor(proc, opts)
|
10
|
+
TableBlock.new.set_processor(proc, opts)
|
11
|
+
ListBlocks.new.set_processor(proc, opts)
|
12
|
+
SectionBlocks.new.set_processor(proc, opts)
|
13
|
+
|
14
|
+
CoreMacros.new.set_processor(proc, opts)
|
15
|
+
ControlLoopMacros.new.set_processor(proc, opts)
|
16
|
+
|
17
|
+
BasicFormatHandlers.new.set_processor(proc, opts)
|
11
18
|
|
12
|
-
proc.register_functions('', StdFunctions::FUNC_NAMES)
|
19
|
+
#proc.register_functions('', StdFunctions::FUNC_NAMES)
|
13
20
|
proc.register_functions('sprintf', Kernel)
|
14
21
|
end
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
include
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
class TextBlocks
|
24
|
+
include BH
|
25
|
+
include BH::BlockParams
|
26
|
+
include BH::HtmlUtils
|
27
|
+
|
28
|
+
# HTML tags that have end-block checks. any block starting with one of these tags will have
|
29
|
+
# its contents passed through until end of the tag (essentially, raw)
|
30
|
+
# @todo what else should be treated?
|
31
|
+
HTML_BLOCK = /^<(style|script|table|dl|select|textarea|\!--|\?)/
|
32
|
+
HTML_BLOCK_END = {
|
33
|
+
'<!--' => '-->',
|
34
|
+
'<?' => '\\?>'
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# For lines starting with only these tags, accept as-is
|
38
|
+
HTML_PASSTHRU = /^\s*<(\/?(html|head|meta|link|title|body|br|section|div|blockquote|p|pre))/
|
23
39
|
|
24
|
-
def
|
25
|
-
@
|
26
|
-
@proc.register_block(self, *%w(table pre p bq div raw ol ul # - / | >))
|
40
|
+
def initialize
|
41
|
+
@block_types = ['p', 'bq', 'pre', 'div', 'raw', 'html_pass', 'html_block']
|
27
42
|
end
|
28
43
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if
|
34
|
-
@
|
35
|
-
@
|
36
|
-
|
37
|
-
|
38
|
-
|
44
|
+
START_TEXT = /^(p|bq|pre|raw|div)[(.]/
|
45
|
+
|
46
|
+
def can_handle(line)
|
47
|
+
match = START_TEXT.match(line)
|
48
|
+
if match
|
49
|
+
@block_type = match[1]
|
50
|
+
if @block_type == 'pre' || @block_type == 'raw'
|
51
|
+
return BH::COLLECT_RAW
|
52
|
+
else
|
53
|
+
return BH::COLLECT
|
54
|
+
end
|
39
55
|
end
|
40
|
-
|
41
|
-
if
|
42
|
-
@
|
43
|
-
|
44
|
-
@lines << line if name != 'table'
|
45
|
-
return COLLECT
|
56
|
+
|
57
|
+
if line.match(HTML_PASSTHRU)
|
58
|
+
@block_type = 'html_pass'
|
59
|
+
return BH::DONE
|
46
60
|
end
|
61
|
+
|
62
|
+
html = line.match(HTML_BLOCK)
|
63
|
+
if html
|
64
|
+
@block_type = 'html_block'
|
65
|
+
|
66
|
+
end_html = HTML_BLOCK_END["<#{html[1]}"]
|
67
|
+
end_html = "</#{html[1]}>$" if !end_html
|
68
|
+
if line.match(end_html)
|
69
|
+
return BH::DONE
|
70
|
+
end
|
47
71
|
|
48
|
-
|
49
|
-
|
50
|
-
@lines = [line.split('::', 2)]
|
51
|
-
return COLLECT
|
72
|
+
@end_html = end_html
|
73
|
+
return BH::COLLECT_RAW
|
52
74
|
end
|
53
75
|
|
54
|
-
|
55
|
-
@type = name
|
56
|
-
if line.index(name) == 0
|
57
|
-
line = line.lstrip
|
58
|
-
end
|
59
|
-
@lines = [@type == 'raw' ? @proc.expand_expr(line) : @proc.fmt_line(line)]
|
60
|
-
return @type == 'raw' ? COLLECT_RAW : COLLECT
|
76
|
+
return BH::RETRY
|
61
77
|
end
|
62
|
-
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
ret = do_dt(line, buff, indents, indent_level)
|
78
|
+
|
79
|
+
def continue_with(line)
|
80
|
+
if @block_type == 'html_block'
|
81
|
+
done = line.match(@end_html)
|
82
|
+
if done
|
83
|
+
return BH::DONE
|
84
|
+
else
|
85
|
+
return BH::COLLECT
|
86
|
+
end
|
72
87
|
else
|
73
|
-
|
88
|
+
if @block_type == 'pre' || @block_type == 'raw'
|
89
|
+
if line && line != ''
|
90
|
+
return BH::COLLECT
|
91
|
+
end
|
92
|
+
elsif line
|
93
|
+
if line == '' || line.match(HTML_PASSTHRU) != nil || line.match(HTML_BLOCK) != nil
|
94
|
+
return BH::RETRY
|
95
|
+
end
|
96
|
+
return BH::COLLECT
|
97
|
+
end
|
74
98
|
end
|
75
|
-
|
99
|
+
|
100
|
+
return BH::RETRY
|
76
101
|
end
|
77
102
|
|
78
|
-
def
|
79
|
-
|
80
|
-
lstrip = line.lstrip
|
81
|
-
if line && (lstrip[0] == '#' || lstrip[0] == '-')
|
82
|
-
@lines << [line[1..-1].strip, indent_level+@indent_start, lstrip[0] == '#' ? 'ol' : 'ul']
|
83
|
-
else
|
84
|
-
# if non-empty line, reprocess this line but also process buffer
|
85
|
-
ret = (line && line != '') ? RETRY : DONE
|
86
|
-
order_stack = []
|
87
|
-
otype_stack = []
|
88
|
-
last = nil
|
89
|
-
@lines.each do |tuple|
|
90
|
-
line, indent, type = tuple
|
91
|
-
# normalize indent
|
92
|
-
indent -= @indent_start
|
93
|
-
if order_stack.length == 0
|
94
|
-
order_stack << "<#{type}>"
|
95
|
-
otype_stack << type
|
96
|
-
# @todo make sure that previous item was a list
|
97
|
-
# remove closing li to enclose sublist
|
98
|
-
elsif indent > (otype_stack.length-1) && order_stack.length > 0
|
99
|
-
last = order_stack[-1]
|
100
|
-
last = last[0...last.length-5]
|
101
|
-
order_stack[-1] = last
|
103
|
+
def process(type, args, lines, out, call_depth = 0)
|
104
|
+
buff = []
|
102
105
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
106
|
+
if type == 'html_pass' || type == 'html_block'
|
107
|
+
out << @eggshell.expand_expr(lines.join("\n"))
|
108
|
+
else
|
109
|
+
tagname = type == 'bq' ? 'blockquote' : type
|
110
|
+
args = [] if !args
|
111
|
+
bp = get_block_params(type, args[0])
|
112
|
+
raw = type == 'pre' || type == 'raw'
|
113
|
+
|
114
|
+
line_break = raw ? '' : '<br />'
|
115
|
+
lines.each do |line|
|
116
|
+
str = line.is_a?(Eggshell::Line) ? line.to_s : line
|
117
|
+
str.chomp!
|
118
|
+
buff << str
|
114
119
|
end
|
115
120
|
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
# @todo don't call expand_expr if raw && args[0]['no_expand']?
|
122
|
+
buff = buff.join("#{line_break}\n")
|
123
|
+
buff = @eggshell.expand_formatting(buff) if !raw
|
124
|
+
buff = @eggshell.unescape(@eggshell.expand_expr(buff))
|
125
|
+
|
126
|
+
if type != 'raw'
|
127
|
+
out << [create_tag(type, bp), buff, "</#{type}>"].join('')
|
128
|
+
else
|
129
|
+
out << buff
|
123
130
|
end
|
124
|
-
buff << order_stack.join("\n")
|
125
131
|
end
|
126
|
-
ret
|
127
132
|
end
|
133
|
+
end
|
128
134
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
end
|
146
|
-
row_classes = map['row.classes']
|
147
|
-
row_classes = ['odd', 'even'] if !row_classes.is_a?(Array)
|
148
|
-
|
149
|
-
@proc.vars['t.row'] = 0
|
150
|
-
buff << "<table class='#{tbl_class}' style='#{tbl_style}'#{tbl_attrib}>"
|
151
|
-
cols = []
|
152
|
-
rows = 0
|
153
|
-
rc = 0
|
154
|
-
@lines.each do |line|
|
155
|
-
ccount = 0
|
156
|
-
if line[0] == '/' && rows == 0
|
157
|
-
cols = line[1..line.length].split('|')
|
158
|
-
buff << "<thead><tr class='#{map['head.class']}'>"
|
159
|
-
cols.each do |col|
|
160
|
-
buff << "\t#{fmt_cell(col, true, ccount)}"
|
161
|
-
ccount += 1
|
162
|
-
end
|
163
|
-
buff << '</tr></thead>'
|
164
|
-
buff << '<tbody>'
|
165
|
-
elsif line[0] == '/'
|
166
|
-
# implies footer
|
167
|
-
cols = line[1..line.length].split('|')
|
168
|
-
buff << "<tfoot><tr class='#{map['foot.class']}'>"
|
169
|
-
cols.each do |col|
|
170
|
-
buff << "\t#{fmt_cell(col, true, ccount)}"
|
171
|
-
ccount += 1
|
172
|
-
end
|
173
|
-
buff << '</tr></tfoot>'
|
174
|
-
elsif line[0] == '|' || line[0..1] == '|>'
|
175
|
-
idx = 1
|
176
|
-
sep = /(?<!\\)\|/
|
177
|
-
if line[1] == '>'
|
178
|
-
idx = 2
|
179
|
-
sep = /(?<!\\)\|\>/
|
180
|
-
end
|
181
|
-
cols = line[idx..line.length].split(sep)
|
182
|
-
@proc.vars['t.row'] = rc
|
183
|
-
rclass = row_classes[rc % row_classes.length]
|
184
|
-
buff << "<tr class='tr-row-#{rc} #{rclass}'>"
|
185
|
-
cols.each do |col|
|
186
|
-
buff << "\t#{fmt_cell(col, false, ccount)}"
|
187
|
-
ccount += 1
|
188
|
-
end
|
189
|
-
buff << '</tr>'
|
190
|
-
rc += 1
|
191
|
-
else
|
192
|
-
cols = line[1..line.length].split('|') if line[0] == '\\'
|
193
|
-
end
|
194
|
-
rows += 1
|
195
|
-
end
|
135
|
+
"""
|
136
|
+
/head|head
|
137
|
+
|col|col|col
|
138
|
+
|>col|>col|>col
|
139
|
+
|!@att=val att2=val2@!col|col
|
140
|
+
/foot|foot
|
141
|
+
"""
|
142
|
+
class TableBlock
|
143
|
+
include BH
|
144
|
+
include BH::BlockParams
|
145
|
+
include BH::HtmlUtils
|
146
|
+
|
147
|
+
def initialize
|
148
|
+
@block_types = ['table']
|
149
|
+
end
|
196
150
|
|
197
|
-
|
198
|
-
|
199
|
-
|
151
|
+
# @todo support opening row with !@ (which would then get applied to <tr>)
|
152
|
+
DELIM1 = '|'
|
153
|
+
DELIM2 = '|>'
|
154
|
+
HEADER = '/'
|
155
|
+
CELL_ATTR_START = '!@'
|
156
|
+
CELL_ATTR_IDX = 1
|
157
|
+
CELL_ATTR_END = '@!'
|
158
|
+
|
159
|
+
def can_handle(line)
|
160
|
+
if !@block_type
|
161
|
+
if line.match(/^(table[(.]?|\||\|>|\/)/)
|
162
|
+
@block_type = 'table'
|
163
|
+
return BH::COLLECT
|
200
164
|
end
|
201
|
-
buff << "</table>"
|
202
|
-
@proc.vars['table.class'] = ''
|
203
|
-
@proc.vars['table.style'] = ''
|
204
|
-
@proc.vars['table.attribs'] = ''
|
205
165
|
end
|
206
|
-
|
166
|
+
return BH::RETRY
|
207
167
|
end
|
208
168
|
|
209
|
-
def
|
210
|
-
|
211
|
-
|
212
|
-
ret = DONE
|
213
|
-
ret = RETRY if line[0] != '>'
|
214
|
-
|
215
|
-
buff << "<dl class='#{@proc.vars['dd.class']}'>"
|
216
|
-
@lines.each do |line|
|
217
|
-
key = line[0]
|
218
|
-
val = line[1]
|
219
|
-
buff << "<dt class='#{@proc.vars['dt.class']}'>#{key}</dt><dd class='#{@proc.vars['dd.class']}'>#{val}</dd>"
|
220
|
-
end
|
221
|
-
buff << "</dl>"
|
222
|
-
else
|
223
|
-
@lines << line[1..-1].split('::', 2)
|
224
|
-
ret = COLLECT
|
169
|
+
def continue_with(line)
|
170
|
+
if line.match(/^(\||\|>|\/)/)
|
171
|
+
return BH::COLLECT
|
225
172
|
end
|
226
|
-
|
173
|
+
return BH::RETRY
|
227
174
|
end
|
228
175
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
176
|
+
T_ROW = 't.row'
|
177
|
+
def process(type, args, lines, out, call_depth = 0)
|
178
|
+
args = [] if !args
|
179
|
+
bp = get_block_params(type, args[0])
|
180
|
+
row_classes = bp['row.classes']
|
181
|
+
row_classes = ['odd', 'even'] if !row_classes.is_a?(Array)
|
182
|
+
|
183
|
+
@eggshell.vars[T_ROW] = 0
|
184
|
+
out << create_tag('table', bp)
|
185
|
+
cols = []
|
186
|
+
rows = 0
|
187
|
+
rc = 0
|
188
|
+
lines.each do |line_obj|
|
189
|
+
ccount = 0
|
190
|
+
line = line_obj.line
|
191
|
+
if line[0] == '/' && rows == 0
|
192
|
+
cols = line[1..line.length].split('|')
|
193
|
+
out << "<thead><tr class='#{map['head.class']}'>"
|
194
|
+
cols.each do |col|
|
195
|
+
out << "\t#{fmt_cell(col, true, ccount)}"
|
196
|
+
ccount += 1
|
248
197
|
end
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
198
|
+
out << '</tr></thead>'
|
199
|
+
out << '<tbody>'
|
200
|
+
elsif line[0] == '/'
|
201
|
+
# implies footer
|
202
|
+
cols = line[1..line.length].split('|')
|
203
|
+
out << "<tfoot><tr class='#{map['foot.class']}'>"
|
204
|
+
cols.each do |col|
|
205
|
+
out << "\t#{fmt_cell(col, true, ccount)}"
|
206
|
+
ccount += 1
|
253
207
|
end
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
# @todo get attributes from block param
|
273
|
-
start_tag = create_tag(@type, attrs)
|
274
|
-
end_tag = "</#{@type}>"
|
275
|
-
join = @type == 'pre' ? "" : "<br />\n"
|
276
|
-
|
277
|
-
content = "#{start_tag}#{@lines.join(join)}#{end_tag}"
|
208
|
+
out << '</tr></tfoot>'
|
209
|
+
elsif line[0] == DELIM1 || line[0..1] == DELIM2
|
210
|
+
idx = 1
|
211
|
+
sep = /(?<!\\)\|/
|
212
|
+
if line[1] == '>'
|
213
|
+
idx = 2
|
214
|
+
sep = /(?<!\\)\|\>/
|
215
|
+
end
|
216
|
+
cols = line[idx..line.length].split(sep)
|
217
|
+
@eggshell.vars[T_ROW] = rc
|
218
|
+
rclass = row_classes[rc % row_classes.length]
|
219
|
+
out << "<tr class='tr-row-#{rc} #{rclass}'>"
|
220
|
+
cols.each do |col|
|
221
|
+
out << "\t#{fmt_cell(col, false, ccount)}"
|
222
|
+
ccount += 1
|
223
|
+
end
|
224
|
+
out << '</tr>'
|
225
|
+
rc += 1
|
278
226
|
else
|
279
|
-
|
227
|
+
cols = line[1..line.length].split('|') if line[0] == '\\'
|
280
228
|
end
|
229
|
+
rows += 1
|
230
|
+
end
|
281
231
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
else
|
286
|
-
line = !nofmt ? @proc.fmt_line(line) : @proc.expand_expr(line)
|
287
|
-
@lines << line
|
288
|
-
ret = raw ? COLLECT_RAW : COLLECT
|
232
|
+
out << '</tbody>'
|
233
|
+
if cols.length > 0
|
234
|
+
# @todo process footer
|
289
235
|
end
|
290
|
-
|
236
|
+
out << "</table>"
|
291
237
|
end
|
292
238
|
|
293
239
|
def fmt_cell(val, header = false, colnum = 0)
|
@@ -297,19 +243,21 @@ class Eggshell::Bundles::Basics
|
|
297
243
|
|
298
244
|
if val[0] == '\\'
|
299
245
|
val = val[1..val.length]
|
300
|
-
elsif val[0] == CELL_ATTR_START
|
246
|
+
elsif val[0..CELL_ATTR_IDX] == CELL_ATTR_START
|
301
247
|
rt = val.index(CELL_ATTR_END)
|
302
248
|
if rt
|
303
|
-
attribs = val[1...rt]
|
249
|
+
attribs = val[CELL_ATTR_IDX+1...rt]
|
304
250
|
val = val[rt+CELL_ATTR_END.length..val.length]
|
305
251
|
end
|
306
252
|
end
|
307
253
|
|
308
254
|
# inject column position via class
|
309
255
|
olen = attribs.length
|
310
|
-
|
311
|
-
if
|
256
|
+
match = attribs.match(/class=(['"])([^'"]*)(['"])/)
|
257
|
+
if !match
|
312
258
|
attribs += " class='td-col-#{colnum}'"
|
259
|
+
else
|
260
|
+
attribs = attribs.gsub(match[0], "class='#{match[2]} td-col-#{colnum}'")
|
313
261
|
end
|
314
262
|
|
315
263
|
buff << "<#{tag} #{attribs}>"
|
@@ -318,59 +266,149 @@ class Eggshell::Bundles::Basics
|
|
318
266
|
val = val[1..val.length]
|
319
267
|
end
|
320
268
|
|
321
|
-
buff << @
|
269
|
+
buff << @eggshell.expand_formatting(val)
|
322
270
|
buff << "</#{tag}>"
|
323
271
|
return buff.join('')
|
324
272
|
end
|
325
273
|
end
|
326
274
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
h3({class: 'header-3', id: 'custom-id'}). Header 3
|
332
|
-
|
333
|
-
section.
|
334
|
-
h1. Another Header 1 inside a section
|
335
|
-
end-section.
|
336
|
-
|
337
|
-
!# outputs a table of contents based on all headers accumulated
|
338
|
-
!# define the output of each header (or use 'default' as fallback)
|
339
|
-
toc.
|
340
|
-
start: <div class='toc'>
|
341
|
-
end: </div>
|
342
|
-
default: <div class='toc-$level'><a href='$id'>$title</a></div>
|
343
|
-
h4: <div class='toc-h4 special-exception'><a href='$id'>$title</a></div>
|
344
|
-
section: <div class='toc-section-wrap'>
|
345
|
-
section_end: </div>
|
346
|
-
docblock
|
347
|
-
class SectionBlocks
|
348
|
-
include Eggshell::BlockHandler
|
275
|
+
class ListBlocks
|
276
|
+
include BH
|
277
|
+
include BH::BlockParams
|
278
|
+
include BH::HtmlUtils
|
349
279
|
|
280
|
+
def initialize
|
281
|
+
@block_types = ['ul', 'ol', 'dl']
|
282
|
+
end
|
283
|
+
|
284
|
+
START_LIST = /^(ol|ul|dl)[(.]/
|
285
|
+
START_LIST_SHORT = /^\s*([#>-])/
|
286
|
+
|
287
|
+
def can_handle(line)
|
288
|
+
match = START_LIST.match(line)
|
289
|
+
if match
|
290
|
+
@block_type = match[1]
|
291
|
+
return BH::COLLECT
|
292
|
+
end
|
293
|
+
|
294
|
+
match = START_LIST_SHORT.match(line)
|
295
|
+
if match
|
296
|
+
if match[1] == '>'
|
297
|
+
@block_type = 'dl'
|
298
|
+
else
|
299
|
+
@block_type = match[1] == '-' ? 'ul' : 'ol'
|
300
|
+
end
|
301
|
+
return BH::COLLECT
|
302
|
+
end
|
303
|
+
|
304
|
+
return BH::RETRY
|
305
|
+
end
|
306
|
+
|
307
|
+
def continue_with(line)
|
308
|
+
if line == nil || line == "" || !START_LIST_SHORT.match(line)
|
309
|
+
return BH::RETRY
|
310
|
+
else
|
311
|
+
return BH::COLLECT
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# @todo support ability to add attributes to sub-lists (maybe '[-#] @\(....\)')
|
316
|
+
def process(type, args, lines, out, call_depth = 0)
|
317
|
+
if type == 'dl'
|
318
|
+
# @todo
|
319
|
+
else
|
320
|
+
order_stack = []
|
321
|
+
otype_stack = []
|
322
|
+
last = nil
|
323
|
+
first_type = nil
|
324
|
+
|
325
|
+
if lines[0] && !lines[0].line.match(/[-#]/)
|
326
|
+
line = lines.shift
|
327
|
+
end
|
328
|
+
|
329
|
+
lines.each do |line_obj|
|
330
|
+
line = line_obj.line
|
331
|
+
indent = line_obj.indent_lvl
|
332
|
+
ltype = line[0] == '-' ? 'ul' : 'ol'
|
333
|
+
line = line[1..line.length].strip
|
334
|
+
|
335
|
+
if order_stack.length == 0
|
336
|
+
# use the given type to start; infer for sub-lists
|
337
|
+
order_stack << create_tag(type, get_block_params(type, args[0]))
|
338
|
+
otype_stack << type
|
339
|
+
# @todo make sure that previous item was a list
|
340
|
+
# remove closing li to enclose sublist
|
341
|
+
elsif indent > (otype_stack.length-1) && order_stack.length > 0
|
342
|
+
last = order_stack[-1]
|
343
|
+
last = last[0...last.length-5]
|
344
|
+
order_stack[-1] = last
|
345
|
+
|
346
|
+
order_stack << "#{"\t"*indent}<#{ltype}>"
|
347
|
+
otype_stack << ltype
|
348
|
+
elsif indent < (otype_stack.length-1)
|
349
|
+
count = otype_stack.length - 1
|
350
|
+
while count > indent
|
351
|
+
ltype = otype_stack.pop
|
352
|
+
order_stack << "#{"\t"*count}</#{ltype}>\n#{"\t"*(count-1)}</li>"
|
353
|
+
count -= 1
|
354
|
+
end
|
355
|
+
end
|
356
|
+
order_stack << "#{"\t"*indent}<li>#{line}</li>"
|
357
|
+
end
|
358
|
+
|
359
|
+
# close nested lists
|
360
|
+
d = otype_stack.length
|
361
|
+
c = 1
|
362
|
+
otype_stack.reverse.each do |ltype|
|
363
|
+
ident = d - c
|
364
|
+
order_stack << "#{"\t" * ident}</#{ltype}>\n#{ident-1 >= 0 ? "\t"*(ident-1) : ''}#{c == d ? '' : "</li>"}"
|
365
|
+
c += 1
|
366
|
+
end
|
367
|
+
out << @eggshell.expand_all(order_stack.join("\n"))
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class SectionBlocks
|
373
|
+
include BH
|
374
|
+
include BH::BlockParams
|
375
|
+
include BH::HtmlUtils
|
376
|
+
|
377
|
+
SECTION ='section'
|
378
|
+
SECTION_END = 'section-end'
|
350
379
|
TOC_TEMPLATE = {
|
351
380
|
:default => "<div class='toc-h$level'><a href='\#$id'>$title</a></div>"
|
352
381
|
}
|
353
382
|
|
354
|
-
def
|
355
|
-
@
|
356
|
-
@proc.register_block(self, *%w(h1 h2 h3 h4 h5 h6 hr section end-section toc))
|
383
|
+
def initialize
|
384
|
+
@block_types = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', SECTION, SECTION_END, 'toc']
|
357
385
|
@header_list = []
|
358
386
|
@header_idx = {}
|
359
387
|
end
|
360
388
|
|
361
|
-
|
362
|
-
set_block_params(name)
|
363
|
-
bp = @block_params[name]
|
389
|
+
START = /^(h[1-6]|section|toc)[(.]/
|
364
390
|
|
365
|
-
|
366
|
-
|
367
|
-
|
391
|
+
def can_handle(line)
|
392
|
+
match = START.match(line)
|
393
|
+
if match
|
394
|
+
@block_type = match[1]
|
395
|
+
return @block_type != 'toc' ? BH::DONE : BH::COLLECT
|
396
|
+
end
|
397
|
+
return BH::RETRY
|
398
|
+
end
|
399
|
+
|
400
|
+
def process(type, args, lines, out, call_depth = 0)
|
401
|
+
bp = get_block_params(type, args[0])
|
402
|
+
line = lines[0]
|
403
|
+
line = line.line.strip if line.is_a?(Eggshell::Line)
|
404
|
+
|
405
|
+
if type[0] == 'h'
|
406
|
+
if type == 'hr'
|
407
|
+
out << create_tag(type, bp, false)
|
368
408
|
else
|
369
|
-
lvl =
|
370
|
-
attrs = bp['attributes'] || {}
|
371
|
-
attrs['class'] = bp['class'] || ''
|
372
|
-
attrs['style'] = bp['style'] || ''
|
409
|
+
lvl = type[1].to_i
|
373
410
|
|
411
|
+
# assign unique id
|
374
412
|
id = bp['id'] || line.downcase.strip.gsub(/[^a-z0-9_-]+/, '-')
|
375
413
|
lid = id
|
376
414
|
i = 1
|
@@ -379,205 +417,212 @@ class Eggshell::Bundles::Basics
|
|
379
417
|
i += 1
|
380
418
|
end
|
381
419
|
id = lid
|
382
|
-
|
383
|
-
title = @
|
420
|
+
bp['id'] = id
|
421
|
+
title = @eggshell.expand_formatting(line)
|
384
422
|
|
385
|
-
|
423
|
+
out << "#{create_tag(type, bp)}#{title}</#{type}>"
|
386
424
|
|
387
|
-
@header_list << {:level => lvl, :id => lid, :title => title, :tag =>
|
425
|
+
@header_list << {:level => lvl, :id => lid, :title => title, :tag => type}
|
388
426
|
@header_idx[lid] = @header_list.length - 1
|
389
427
|
end
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
end
|
408
|
-
|
409
|
-
def collect(line, buffer, indents = '', indent_level = 0)
|
410
|
-
if line == '' || !line
|
411
|
-
buffer << @proc.fmt_line(@toc_template[:start]) if @toc_template[:start]
|
428
|
+
elsif type == SECTION
|
429
|
+
out << create_tag(SECTION, bp)
|
430
|
+
@header_list << type
|
431
|
+
elsif type == SECTION_END
|
432
|
+
out << '</section>'
|
433
|
+
@header_list << type
|
434
|
+
elsif type == 'toc'
|
435
|
+
# first, parse the toc definitions from lines
|
436
|
+
toc_template = TOC_TEMPLATE.clone
|
437
|
+
lines.each do |line_obj|
|
438
|
+
line = line_obj.is_a?(Eggshell::Line) ? line_obj.line : line
|
439
|
+
key, val = line.split(':', 2)
|
440
|
+
toc_template[key.to_sym] = val
|
441
|
+
end
|
442
|
+
|
443
|
+
# now go through collected headers and sections and generate toc
|
444
|
+
out << @eggshell.expand_formatting(toc_template[:start]) if toc_template[:start]
|
412
445
|
@header_list.each do |entry|
|
413
|
-
if entry ==
|
414
|
-
|
415
|
-
elsif entry ==
|
416
|
-
|
446
|
+
if entry == SECTION
|
447
|
+
out << @eggshell.expand_formatting(toc_template[:section]) if toc_template[:section]
|
448
|
+
elsif entry == SECTION_END
|
449
|
+
out << @eggshell.expand_formatting(toc_template[:section_end]) if toc_template[:section_end]
|
417
450
|
elsif entry.is_a?(Hash)
|
418
|
-
tpl =
|
419
|
-
|
451
|
+
tpl = toc_template[entry[:tag]] || toc_template[:default]
|
452
|
+
out << @eggshell.expand_formatting(
|
420
453
|
tpl.gsub('$id', entry[:id]).gsub('$title', entry[:title]).gsub('$level', entry[:level].to_s)
|
421
454
|
)
|
422
455
|
end
|
423
456
|
end
|
424
|
-
|
425
|
-
return DONE
|
426
|
-
else
|
427
|
-
key, val = line.split(':', 2)
|
428
|
-
@toc_template[key.to_sym] = val
|
429
|
-
return COLLECT
|
457
|
+
out << @eggshell.expand_formatting(toc_template[:end]) if toc_template[:end]
|
430
458
|
end
|
431
459
|
end
|
432
460
|
end
|
433
461
|
|
434
|
-
class
|
435
|
-
include
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
'[
|
445
|
-
'[
|
446
|
-
'[
|
447
|
-
'[
|
448
|
-
'
|
449
|
-
'**]' => '</strong>',
|
450
|
-
'_]' => '</i>',
|
451
|
-
'__]' => '</em>',
|
452
|
-
'[-_' => '<u>',
|
453
|
-
'_-]' => '</u>',
|
454
|
-
'[-' => '<strike>',
|
455
|
-
'-]' => '</strike>'
|
462
|
+
class BasicFormatHandlers
|
463
|
+
include FH
|
464
|
+
include FH::Utils
|
465
|
+
include BH::HtmlUtils
|
466
|
+
|
467
|
+
TAG_MAP = {
|
468
|
+
'[*' => 'b',
|
469
|
+
'[**' => 'strong',
|
470
|
+
'[/' => 'i',
|
471
|
+
'[//' => 'em',
|
472
|
+
'[__' => 'u',
|
473
|
+
'[-' => 'strike',
|
474
|
+
'[^' => 'sup',
|
475
|
+
'[_' => 'sub',
|
476
|
+
'[[' => 'span'
|
456
477
|
}.freeze
|
457
478
|
|
458
|
-
|
459
|
-
|
460
|
-
|
479
|
+
ESCAPE_MAP_HTML = {
|
480
|
+
'<' => '<',
|
481
|
+
'>' => '>',
|
482
|
+
'/<' => '«',
|
483
|
+
'>/' => '»',
|
484
|
+
'/\'' => '‘',
|
485
|
+
'\'/' => '’',
|
486
|
+
'/"' => '“',
|
487
|
+
'"/' => '”',
|
488
|
+
'c' => '©',
|
489
|
+
'r' => '®',
|
490
|
+
'!' => '¡',
|
491
|
+
'-' => '–',
|
492
|
+
'--' => '—',
|
493
|
+
'&' => '&'
|
494
|
+
}.freeze
|
495
|
+
|
496
|
+
def initialize
|
497
|
+
@fmt_delimeters = [
|
498
|
+
['[*', '*]'], # bold
|
499
|
+
['[**', '**]'], # strong
|
500
|
+
['[/', '/]'], # italic
|
501
|
+
['[//', '//]'], # emphasis
|
502
|
+
['[__', '__]'], # underline
|
503
|
+
['[-', '-]'], # strike
|
504
|
+
['[^', '^]'], # superscript
|
505
|
+
['[_', '_]'], # subscript,
|
506
|
+
['[[', ']]'], # span
|
507
|
+
['[~', '~]'], # anchor
|
508
|
+
['[!', '!]'], # image
|
509
|
+
['%{', '}%', true], # entity expansion
|
510
|
+
['`', '`', '%{'], # code, backtick
|
511
|
+
['{{', '}}', '%{'] # code, normal
|
512
|
+
]
|
461
513
|
end
|
462
514
|
|
463
|
-
def
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
when '[/'
|
476
|
-
tag = macname == '[//' ? 'em' : 'i'
|
477
|
-
when '[-'
|
478
|
-
tag = 'strike'
|
479
|
-
when '[_'
|
480
|
-
tag = 'u'
|
481
|
-
when '[~'
|
482
|
-
tag = 'a'
|
483
|
-
link = textpart ? textpart.strip : textpart
|
484
|
-
text = nil
|
485
|
-
if link == ''
|
486
|
-
text = ''
|
487
|
-
elsif link.index('; ') == nil
|
488
|
-
textpart = link
|
489
|
-
args.unshift('href:'+ link);
|
490
|
-
else
|
491
|
-
textpart, link = link.split('; ')
|
492
|
-
link = '' if !link
|
493
|
-
args.unshift('href:'+link)
|
494
|
-
end
|
495
|
-
when '[!'
|
496
|
-
tag = 'img'
|
497
|
-
args.unshift('src:'+textpart)
|
498
|
-
textpart = nil
|
515
|
+
def format(tag, str)
|
516
|
+
if tag == '{{' || tag == '`'
|
517
|
+
cls = tag == '{{' ? 'normal' : 'backtick'
|
518
|
+
return "<code class='#{cls}'>#{str}</code>"
|
519
|
+
elsif tag == '%{'
|
520
|
+
# @todo find a way to expand char map and handle unmapped strings at runtime
|
521
|
+
buff = ''
|
522
|
+
str.split(' ').each do |part|
|
523
|
+
c = ESCAPE_MAP_HTML[part]
|
524
|
+
buff += c || part
|
525
|
+
end
|
526
|
+
return buff
|
499
527
|
end
|
500
528
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
529
|
+
st = tag[0..1]
|
530
|
+
args = parse_args(str.strip)
|
531
|
+
akey = BH::BlockParams::ATTRIBUTES
|
532
|
+
atts = {akey => {}}
|
533
|
+
atts[akey].update(args.pop)
|
534
|
+
|
535
|
+
tagname = nil
|
536
|
+
tagopen = true
|
537
|
+
text = args[0]
|
538
|
+
|
539
|
+
if tag == '[~'
|
540
|
+
link, text = args
|
541
|
+
link = '' if !link
|
542
|
+
text = '' if !text
|
543
|
+
if text.strip == ''
|
544
|
+
text = link
|
513
545
|
end
|
546
|
+
atts[akey]['href'] = link if link != ''
|
547
|
+
tagname = 'a'
|
548
|
+
elsif tag == '[!'
|
549
|
+
link, alt = args
|
550
|
+
link = '' if !link
|
551
|
+
alt = '' if !alt
|
552
|
+
|
553
|
+
atts[akey]['src'] = link if link != ''
|
554
|
+
atts[akey]['alt'] = alt if alt != ''
|
555
|
+
tagname = 'img'
|
556
|
+
tagopen = false
|
557
|
+
else
|
558
|
+
tagname = TAG_MAP[tag]
|
514
559
|
end
|
515
560
|
|
516
|
-
if
|
517
|
-
|
561
|
+
if tagopen
|
562
|
+
return "#{create_tag(tagname, atts)}#{text}</#{tagname}>"
|
518
563
|
else
|
519
|
-
|
564
|
+
return create_tag(tagname, atts, false)
|
520
565
|
end
|
521
|
-
buff
|
522
|
-
end
|
523
|
-
end
|
524
|
-
|
525
|
-
# Macros:
|
526
|
-
#
|
527
|
-
# - `include`
|
528
|
-
# - `capture`
|
529
|
-
# - `var`
|
530
|
-
class BasicMacros
|
531
|
-
include Eggshell::MacroHandler
|
532
|
-
|
533
|
-
def initialize
|
534
|
-
@capvar = nil
|
535
|
-
@collbuff = nil
|
536
|
-
@depth = 0
|
537
566
|
end
|
567
|
+
end
|
538
568
|
|
539
|
-
|
540
|
-
|
541
|
-
|
569
|
+
# These macros are highly recommended to always be part of an instance of the processor.
|
570
|
+
#
|
571
|
+
# {{!}} sets default parameter values (block parameters) so that they don't have to be
|
572
|
+
# specified for every block instance.
|
573
|
+
#
|
574
|
+
# {{process}} always dynamic processing of content. This allows previous macros to build
|
575
|
+
# up a document dynamically.
|
576
|
+
class CoreMacros
|
577
|
+
include MH
|
578
|
+
include BH::BlockParams
|
579
|
+
|
580
|
+
CAP_OUT = 'capture_output'
|
581
|
+
|
582
|
+
def set_processor(proc, opts = nil)
|
583
|
+
opts = {} if !opts
|
584
|
+
@opts = opts
|
585
|
+
@eggshell = proc
|
586
|
+
@eggshell.add_macro_handler(self, '=', '!', 'process', 'capture', 'raw')
|
587
|
+
@eggshell.add_macro_handler(self, 'include') if !@opts['macro.include.off']
|
588
|
+
@vars = @eggshell.vars
|
589
|
+
|
590
|
+
@vars[:include_stack] = []
|
591
|
+
if opts['include.options']
|
592
|
+
opts['include.options'].each do |k,v|
|
593
|
+
@vars[k.to_sym] = v
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
if @vars[:include_paths].length == 0
|
598
|
+
@vars[:include_paths] = [Dir.getwd]
|
599
|
+
end
|
542
600
|
end
|
543
601
|
|
544
|
-
def process(
|
545
|
-
if
|
546
|
-
@
|
547
|
-
elsif macname == 'process'
|
602
|
+
def process(name, args, lines, out, call_depth = 0)
|
603
|
+
if name == '='
|
604
|
+
# @todo expand args[0]?
|
548
605
|
if args[0]
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
end
|
553
|
-
elsif macname == 'capture'
|
554
|
-
# @todo check args for fragment to parse
|
555
|
-
return if !lines
|
556
|
-
var = args[0]
|
557
|
-
@proc.vars[var] = @proc.process(lines, depth)
|
558
|
-
elsif macname == 'var' || macname == '='
|
559
|
-
# @todo support multiple vars via lines
|
560
|
-
# @todo expand value if expression
|
561
|
-
if args.length >= 2
|
562
|
-
key = args[0]
|
563
|
-
val = args[1]
|
564
|
-
|
565
|
-
if val.is_a?(Array) && val[0].is_a?(Symbol)
|
566
|
-
@proc.vars[key] = @proc.expr_eval(val)
|
606
|
+
val = nil
|
607
|
+
if args[1].is_a?(Array) && args[1][0].is_a?(Symbol)
|
608
|
+
val = @eggshell.expr_eval(args[1])
|
567
609
|
else
|
568
|
-
|
610
|
+
val = args[1]
|
569
611
|
end
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
612
|
+
@eggshell.vars[args[0]] = val
|
613
|
+
end
|
614
|
+
elsif name == '!'
|
615
|
+
block_name = args[0]
|
616
|
+
block_params = args[1]
|
617
|
+
set_block_params(block_name, block_params) if block_name
|
618
|
+
elsif name == 'process'
|
619
|
+
|
620
|
+
elsif name == 'capture'
|
621
|
+
var = args[0] || CAP_OUT
|
622
|
+
captured = @eggshell.assemble(lines, call_depth + 1)
|
623
|
+
captured = @eggshell.expand_expr(captured)
|
624
|
+
@eggshell.vars[var] = captured
|
625
|
+
elsif name == 'include'
|
581
626
|
paths = args[0]
|
582
627
|
opts = args[1] || {}
|
583
628
|
if opts['encoding']
|
@@ -588,167 +633,218 @@ class Eggshell::Bundles::Basics
|
|
588
633
|
if lines && lines.length > 0
|
589
634
|
paths = lines
|
590
635
|
end
|
591
|
-
do_include(paths,
|
636
|
+
do_include(paths, out, call_depth, opts)
|
637
|
+
elsif name == 'raw'
|
638
|
+
lines.each do |unit|
|
639
|
+
if unit.is_a?(Array)
|
640
|
+
if unit[0] == :block
|
641
|
+
unit[Eggshell::ParseTree::IDX_LINES].each do |line|
|
642
|
+
out << line.to_s
|
643
|
+
end
|
644
|
+
else
|
645
|
+
out << @eggshell.assemble(unit)
|
646
|
+
end
|
647
|
+
else
|
648
|
+
out << line
|
649
|
+
end
|
650
|
+
end
|
592
651
|
end
|
593
652
|
end
|
594
|
-
|
595
|
-
def do_include(paths, buff,
|
596
|
-
@proc.vars[:include_stack] = [] if !@proc.vars[:include_stack]
|
653
|
+
|
654
|
+
def do_include(paths, buff, call_depth, opts = {})
|
597
655
|
paths = [paths] if !paths.is_a?(Array)
|
598
656
|
# @todo check all include paths?
|
599
657
|
paths.each do |inc|
|
600
|
-
inc =
|
658
|
+
inc = inc.line if inc.is_a?(Eggshell::Line)
|
659
|
+
inc = @eggshell.expand_expr(inc.strip)
|
601
660
|
checks = []
|
602
661
|
if inc[0] != '/'
|
603
|
-
@
|
662
|
+
@vars[:include_paths].each do |root|
|
604
663
|
checks << "#{root}/#{inc}"
|
605
664
|
end
|
606
665
|
# @todo if :include_root, expand path and check that it's under the root, otherwise, sandbox
|
607
666
|
else
|
608
667
|
# sandboxed root include
|
609
|
-
if @
|
610
|
-
checks << "#{@
|
668
|
+
if @eggshell.vars[:include_root]
|
669
|
+
checks << "#{@vars[:include_root]}#{inc}"
|
611
670
|
else
|
612
671
|
checks << inc
|
613
672
|
end
|
614
673
|
end
|
615
|
-
|
616
674
|
checks.each do |inc|
|
617
675
|
if File.exists?(inc)
|
618
676
|
lines = IO.readlines(inc, $/, opts)
|
619
|
-
@
|
620
|
-
@proc.context.push_line_counter
|
677
|
+
@vars[:include_stack] << inc
|
621
678
|
begin
|
622
|
-
buff << @
|
623
|
-
@
|
679
|
+
buff << @eggshell.process(lines, 0, call_depth + 1)
|
680
|
+
@eggshell._debug("include: 200 #{inc}")
|
624
681
|
rescue => ex
|
625
|
-
@
|
682
|
+
@eggshell._error("include: 500 #{inc}: #{ex.message}#{ex.backtrace.join("\n\t")}")
|
626
683
|
end
|
627
684
|
|
628
|
-
@
|
629
|
-
@proc.context.pop_line_counter
|
685
|
+
@vars[:include_stack].pop
|
630
686
|
break
|
631
687
|
else
|
632
|
-
@
|
688
|
+
@eggshell._warn("include: 404 #{inc}")
|
633
689
|
end
|
634
690
|
end
|
635
691
|
end
|
636
692
|
end
|
637
693
|
end
|
694
|
+
|
695
|
+
# Provides iteration and conditional functionality.
|
696
|
+
class ControlLoopMacros
|
697
|
+
include MH
|
698
|
+
|
699
|
+
class WhileLoopWrapper
|
700
|
+
def initialize(eggshell, cond)
|
701
|
+
@eggshell = eggshell
|
702
|
+
@cond = cond
|
703
|
+
end
|
638
704
|
|
639
|
-
|
640
|
-
|
705
|
+
def each(&block)
|
706
|
+
counter = 0
|
707
|
+
struct = Eggshell::ExpressionEvaluator.struct(@cond)
|
641
708
|
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
709
|
+
cond = @eggshell.expr_eval(struct)
|
710
|
+
while cond
|
711
|
+
yield(counter)
|
712
|
+
counter += 1
|
713
|
+
cond = @eggshell.expr_eval(struct)
|
714
|
+
end
|
715
|
+
end
|
646
716
|
end
|
647
717
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
718
|
+
# pre.
|
719
|
+
# @if("expression") {}
|
720
|
+
# @elsif("expression") {}
|
721
|
+
# else {}
|
722
|
+
#
|
723
|
+
# #! modes:
|
724
|
+
# #! 'raw' (default): collects all generated lines as raw (unless there are sub-macros, which would just collect the output as as-is)
|
725
|
+
# #! 'eval': evaluates each unit via Eggshell::Processor.assemble
|
726
|
+
# #! note that interpolated expressions are expanded in raw mode
|
727
|
+
# #
|
728
|
+
# @for({'start': 0, 'stop': var, 'step': 1, 'items': ..., 'item': 'varname', 'counter': 'varname'}[, mode])
|
729
|
+
def set_processor(proc, opts = nil)
|
730
|
+
opts = {} if !opts
|
731
|
+
@opts = opts
|
732
|
+
@eggshell = proc
|
733
|
+
@eggshell.add_macro_handler(self, *%w(if elsif else loop for while break next))
|
734
|
+
@vars = @eggshell.vars
|
735
|
+
@eggshell.vars[:loop_max_limit] = 1000
|
652
736
|
|
653
|
-
|
654
|
-
|
655
|
-
|
737
|
+
@state = []
|
738
|
+
# @todo set loop limits from opts or defaults
|
739
|
+
end
|
656
740
|
|
657
|
-
|
658
|
-
|
659
|
-
|
741
|
+
def process(name, args, lines, out, call_depth = 0)
|
742
|
+
macname = name.to_sym
|
743
|
+
st = @state[call_depth]
|
744
|
+
if !@state[call_depth]
|
660
745
|
st = {:type => macname}
|
661
746
|
|
662
|
-
@state[
|
747
|
+
@state[call_depth] = st
|
663
748
|
# erase nested state
|
664
|
-
@state[
|
749
|
+
@state[call_depth+1] = nil
|
665
750
|
end
|
666
751
|
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
st[:stop] = p0['stop']
|
672
|
-
st[:step] = p0['step'] || 1
|
673
|
-
st[:iter] = p0['items'] || 'items'
|
674
|
-
st[:item] = p0['item'] || 'item'
|
675
|
-
st[:counter] = p0['counter'] || 'counter'
|
676
|
-
st[:raw] = p0['raw'] # @todo inherit if not set?
|
677
|
-
st[:collect] = p0['collect']
|
678
|
-
st[:agg_block] = p0['aggregate_block']
|
679
|
-
|
680
|
-
mbuff = []
|
752
|
+
p0 = args[0]
|
753
|
+
if macname == :for || macname == :loop || macname == :while
|
754
|
+
p1 = args[1] || 'raw'
|
755
|
+
|
681
756
|
looper = nil
|
682
757
|
loop_is_map = false
|
683
758
|
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
759
|
+
if macname == :while
|
760
|
+
if p0
|
761
|
+
looper = WhileLoopWrapper.new(@eggshell, p0)
|
762
|
+
end
|
763
|
+
else
|
764
|
+
p0 ||= 'true'
|
765
|
+
st[:iter] = p0['items'] || nil
|
766
|
+
st[:item] = p0['item'] || 'item'
|
767
|
+
st[:var] = p0['var']
|
768
|
+
st[:start] = p0['start']
|
769
|
+
st[:stop] = p0['stop']
|
770
|
+
st[:step] = p0['step'] || 1
|
771
|
+
st[:counter] = p0['counter'] || 'counter'
|
772
|
+
|
773
|
+
if st[:iter].is_a?(Array)
|
774
|
+
st[:start] = 0 if !st[:start]
|
775
|
+
st[:stop] = st[:iter].length - 1 if !st[:stop]
|
776
|
+
st[:step] = 1 if !st[:step]
|
777
|
+
looper = Range.new(st[:start], st[:stop]).step(st[:step]).to_a
|
778
|
+
elsif st[:iter].respond_to?(:each)
|
779
|
+
looper = st[:iter]
|
780
|
+
loop_is_map = true
|
695
781
|
end
|
696
782
|
end
|
697
783
|
|
698
|
-
collector =
|
784
|
+
collector = out
|
785
|
+
raw = p1 == 'raw'
|
786
|
+
|
699
787
|
if looper
|
700
|
-
counter =
|
788
|
+
counter = -1
|
701
789
|
looper.each do |i1, i2|
|
790
|
+
counter += 1
|
791
|
+
break if @eggshell.vars[:loop_max_limit] == counter
|
702
792
|
# for maps, use key as the counter
|
703
793
|
val = nil
|
704
794
|
if loop_is_map
|
705
|
-
@
|
795
|
+
@eggshell.vars[st[:counter]] = i1
|
706
796
|
val = i2
|
707
797
|
else
|
708
798
|
val = i1
|
709
|
-
@
|
799
|
+
@eggshell.vars[st[:counter]] = counter
|
710
800
|
end
|
711
801
|
|
712
802
|
# inject value into :item -- if it's an expression, evaluate first
|
713
|
-
@
|
714
|
-
|
715
|
-
if
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
803
|
+
@eggshell.vars[st[:item]] = val.is_a?(Array) && val[0].is_a?(Symbol) ? @eggshell.expr_eval(val) : val
|
804
|
+
|
805
|
+
# if doing raw, pass through block lines with variable expansion. preserve object type (e.g. Line or String);
|
806
|
+
# sub-macros will get passed collector var as output to assemble().
|
807
|
+
# otherwise, call assemble() on all lines
|
808
|
+
if raw
|
809
|
+
lines.each do |unit|
|
810
|
+
if unit.is_a?(Array)
|
811
|
+
if unit[0] == :block
|
812
|
+
unit[Eggshell::ParseTree::IDX_LINES].each do |line|
|
813
|
+
nline = line
|
814
|
+
if line.is_a?(String)
|
815
|
+
nline = @eggshell.expand_expr(line)
|
816
|
+
else
|
817
|
+
# rather than expand_expr on raw, assume line.line is a subset of line.raw
|
818
|
+
_raw = line.raw
|
819
|
+
_line = @eggshell.expand_expr(line.line)
|
820
|
+
_raw = _raw.gsub(line.line, _line) if _raw
|
821
|
+
nline = line.replace(_line, _raw)
|
822
|
+
end
|
823
|
+
collector << nline
|
824
|
+
end
|
825
|
+
else
|
826
|
+
@eggshell.assemble([unit], call_depth + 1, {:out => collector})
|
827
|
+
end
|
828
|
+
else
|
829
|
+
collector << @eggshell.expand_expr(line.to_s)
|
721
830
|
end
|
722
831
|
end
|
723
832
|
else
|
724
|
-
|
833
|
+
collector << @eggshell.assemble(lines, call_depth + 1)
|
725
834
|
end
|
726
|
-
break if st[:break]
|
727
835
|
|
728
|
-
|
836
|
+
break if st[:break]
|
729
837
|
end
|
730
838
|
end
|
731
839
|
|
732
|
-
# since there are lines here, process collected as an aggregated set of blocks
|
733
|
-
if collector.length > 0
|
734
|
-
if st[:collect] == 'aggregate'
|
735
|
-
if st[:agg_block]
|
736
|
-
collector.unshift(st[:agg_block])
|
737
|
-
end
|
738
|
-
process_lines(collector, buffer, depth + 1, false)
|
739
|
-
else
|
740
|
-
buffer << collector.join("\n")
|
741
|
-
end
|
742
|
-
end
|
743
|
-
|
744
840
|
# clear state
|
745
|
-
@state[
|
746
|
-
elsif macname == :while
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
841
|
+
@state[call_depth] = nil
|
842
|
+
# elsif macname == :while
|
843
|
+
# raw = args[1]
|
844
|
+
# while @eggshell.expr_eval(p0)
|
845
|
+
# process_lines(lines, buffer, depth + 1, raw)
|
846
|
+
# break if st[:break]
|
847
|
+
# end
|
752
848
|
elsif macname == :if || macname == :elsif || macname == :else
|
753
849
|
cond = p0
|
754
850
|
st[:if] = true if macname == :if
|
@@ -770,7 +866,7 @@ class Eggshell::Bundles::Basics
|
|
770
866
|
if macname != :else
|
771
867
|
if !st[:cond_eval]
|
772
868
|
cond_struct = Eggshell::ExpressionEvaluator.struct(cond)
|
773
|
-
st[:cond_eval] = @
|
869
|
+
st[:cond_eval] = @eggshell.expr_eval(cond_struct)
|
774
870
|
end
|
775
871
|
else
|
776
872
|
st[:cond_eval] = true
|
@@ -778,11 +874,12 @@ class Eggshell::Bundles::Basics
|
|
778
874
|
|
779
875
|
if st[:cond_eval] && !st[:cond_met]
|
780
876
|
st[:cond_met] = true
|
781
|
-
process_lines(lines, buffer, depth + 1)
|
877
|
+
#process_lines(lines, buffer, depth + 1)
|
878
|
+
@eggshell.assemble(lines, call_depth + 1, {:out => out})
|
782
879
|
end
|
783
880
|
elsif macname == :break
|
784
881
|
lvl = p0 || 1
|
785
|
-
i =
|
882
|
+
i = call_depth - 1
|
786
883
|
|
787
884
|
# set breaks at each found loop until # of levels reached
|
788
885
|
while i >= 0
|
@@ -812,43 +909,7 @@ class Eggshell::Bundles::Basics
|
|
812
909
|
end
|
813
910
|
end
|
814
911
|
end
|
815
|
-
|
816
|
-
def process_lines(lines, buffer, depth, raw = false)
|
817
|
-
return if !lines
|
818
|
-
ln = []
|
819
|
-
lines.each do |line|
|
820
|
-
if line.is_a?(Eggshell::Block)
|
821
|
-
if ln.length > 0
|
822
|
-
buffer << (raw ? @proc.expand_expr(ln.join("\n")) : @proc.process(ln, depth))
|
823
|
-
ln = []
|
824
|
-
end
|
825
|
-
line.process(buffer, depth)
|
826
|
-
if @state[depth-1][:next]
|
827
|
-
@state[depth-1][:next] = false
|
828
|
-
break
|
829
|
-
end
|
830
|
-
else
|
831
|
-
ln << line
|
832
|
-
end
|
833
|
-
end
|
834
|
-
if ln.length > 0
|
835
|
-
buffer << (raw ? @proc.expand_expr(ln.join("\n")) : @proc.process(ln, depth))
|
836
|
-
end
|
837
|
-
end
|
838
|
-
|
839
|
-
protected :process_lines
|
840
912
|
end
|
841
|
-
|
842
|
-
# Baseline functions.
|
843
|
-
# @todo catch exceptions???
|
844
|
-
module StdFunctions
|
845
|
-
# Repeats `str` by a given `amt`
|
846
|
-
def self.str_repeat(str, amt)
|
847
|
-
return str * amt
|
848
|
-
end
|
849
|
-
|
850
|
-
FUNC_NAMES = %w(str_repeat).freeze
|
851
|
-
end
|
852
|
-
|
913
|
+
|
853
914
|
include Eggshell::Bundles::Bundle
|
854
915
|
end
|