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.
@@ -1,293 +1,239 @@
1
- class Eggshell::Bundles::Basics
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
- BasicBlocks.new.set_processor(proc)
7
- SectionBlocks.new.set_processor(proc)
8
- InlineMacros.new.set_processor(proc)
9
- BasicMacros.new.set_processor(proc)
10
- ControlMacros.new.set_processor(proc)
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
- # `table` block parameters:
17
- # - `row.classes`: defaults to `['odd', 'even']`. The number of elements represents the number of cycles.
18
- class BasicBlocks
19
- include Eggshell::BlockHandler
20
-
21
- CELL_ATTR_START = '!'
22
- CELL_ATTR_END = '<!'
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 set_processor(proc)
25
- @proc = proc
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
- def start(name, line, buffer, indents = '', indent_level = 0)
30
- set_block_params(name)
31
- bp = @block_params[name]
32
-
33
- if name == '#' || name == '-' || name == 'ol' || name == 'ul'
34
- @type = :list
35
- @lines = []
36
- @lines << [line, indent_level, name == '#' ? 'ol' : 'ul'] if name == '#' || name == '-'
37
- @indent_start = indent_level
38
- return COLLECT
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 name == 'table' || name == '/' || name == '|'
42
- @type = :table
43
- @lines = []
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
- if name == '>'
49
- @type = :dt
50
- @lines = [line.split('::', 2)]
51
- return COLLECT
72
+ @end_html = end_html
73
+ return BH::COLLECT_RAW
52
74
  end
53
75
 
54
- # assume block text
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 collect(line, buff, indents = '', indent_level = 0)
64
- line = '' if !line
65
- ret = COLLECT
66
- if @type == :list
67
- ret = do_list(line, buff, indents, indent_level)
68
- elsif @type == :table
69
- ret = do_table(line, buff, indents, indent_level)
70
- elsif @type == :dt
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
- ret = do_default(line, buff, indents, indent_level)
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
- return ret
99
+
100
+ return BH::RETRY
76
101
  end
77
102
 
78
- def do_list(line, buff, indents = '', indent_level = 0)
79
- ret = COLLECT
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
- order_stack << "#{"\t"*indent}<#{type}>"
104
- otype_stack << type
105
- elsif indent < (otype_stack.length-1)
106
- count = otype_stack.length - 1 - indent
107
- while count > 0
108
- ltype = otype_stack.pop
109
- order_stack << "#{"\t"*count}</#{ltype}>\n#{"\t"*(count-1)}</li>"
110
- count -= 1
111
- end
112
- end
113
- order_stack << "#{"\t"*indent}<li>#{@proc.fmt_line(line)}</li>"
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
- # close nested lists
117
- d = otype_stack.length
118
- c = 1
119
- otype_stack.each do |type|
120
- ident = d - c
121
- order_stack << "#{"\t" * ident}</#{type}>#{c == d ? '' : "</li>"}"
122
- c += 1
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
- def do_table(line, buff, indents = '', indent_level = 0)
130
- ret = COLLECT
131
- if line[0] == '|' || line[0] == '/'
132
- @lines << line
133
- else
134
- ret = (line[0] != '\\' && line != '') ? RETRY : DONE
135
- map = @block_params['table'] || {}
136
- tbl_class = map['class'] || ''
137
- tbl_style = map['style'] || ''
138
- tbl_attrib = ''
139
- if map['attribs'].is_a?(String)
140
- tbl_attrib = map['attribs']
141
- elsif map['attribs'].is_a?(Hash)
142
- map['attribs'].each do |key,val|
143
- tbl_attrib = "#{tbl_attrib} #{key}='#{val}'"
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
- buff << '</tbody>'
198
- if cols.length > 0
199
- # @todo process footer
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
- ret
166
+ return BH::RETRY
207
167
  end
208
168
 
209
- def do_dt(line, buff, indents = '', indent_level = 0)
210
- ret = COLLECT
211
- if line == '' || line[0] != '>'
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
- ret
173
+ return BH::RETRY
227
174
  end
228
175
 
229
- def do_default(line, buff, indents = '', indent_level = 0)
230
- ret = COLLECT
231
- blank = false
232
-
233
- raw = @type == 'raw'
234
- pre = @type == 'pre'
235
- nofmt = raw
236
-
237
- # we need to group indented lines as part of block, especially if it's otherwise empty
238
- if raw || pre || @type == 'bq'
239
- raw = true
240
- # strip off first indent
241
- if indent_level > 0
242
- #idx = indents.length / indent_level
243
- line = indents + line
244
- if pre
245
- line = "\n#{line}"
246
- elsif line == ''
247
- line = ' '
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
- else
250
- blank = line == ''
251
- if pre
252
- line = "\n#{line}"
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
- end
255
- else
256
- line = " " if line == '\\'
257
- blank = line == ''
258
- end
259
-
260
- if blank
261
- @lines.delete('')
262
- start_tag = ''
263
- end_tag = ''
264
- content = ''
265
- if @type != 'raw'
266
- bp = @block_params[@type]
267
- attrs = bp['attributes'] || {}
268
- attrs['class'] = bp['class'] || ''
269
- attrs['style'] = bp['style'] || ''
270
- attrs['id'] = bp['id'] || ''
271
- @type = 'blockquote' if @type == 'bq'
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
- content = @lines.join("\n")
227
+ cols = line[1..line.length].split('|') if line[0] == '\\'
280
228
  end
229
+ rows += 1
230
+ end
281
231
 
282
- buff << content
283
- @lines = []
284
- ret = DONE
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
- ret
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
- attribs = attribs.gsub(/class=(['"])/, 'class=$1' + "td-col-#{colnum}")
311
- if olen == attribs.length
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 << @proc.fmt_line(val)
269
+ buff << @eggshell.expand_formatting(val)
322
270
  buff << "</#{tag}>"
323
271
  return buff.join('')
324
272
  end
325
273
  end
326
274
 
327
- # Handles blocks that deal with sectioning of content.
328
- <<-docblock
329
- h1. Header 1
330
- h2. Header 2
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 set_processor(proc)
355
- @proc = proc
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
- def start(name, line, buffer, indents = '', indent_level = 0)
362
- set_block_params(name)
363
- bp = @block_params[name]
389
+ START = /^(h[1-6]|section|toc)[(.]/
364
390
 
365
- if name[0] == 'h'
366
- if name == 'hr'
367
- buffer << "<hr />"
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 = name[1].to_i
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
- attrs['id'] = id
383
- title = @proc.fmt_line(line)
420
+ bp['id'] = id
421
+ title = @eggshell.expand_formatting(line)
384
422
 
385
- buffer << "#{create_tag(name, attrs)}#{title}</#{name}>"
423
+ out << "#{create_tag(type, bp)}#{title}</#{type}>"
386
424
 
387
- @header_list << {:level => lvl, :id => lid, :title => title, :tag => name}
425
+ @header_list << {:level => lvl, :id => lid, :title => title, :tag => type}
388
426
  @header_idx[lid] = @header_list.length - 1
389
427
  end
390
- return DONE
391
- elsif name == 'section'
392
- attrs = bp['attributes'] || {}
393
- attrs['class'] = bp['class'] || ''
394
- attrs['style'] = bp['style'] || ''
395
- attrs['id'] = bp['id'] || ''
396
- buffer << create_tag('section', attrs)
397
- @header_list << name
398
- return DONE
399
- elsif name == 'end-section'
400
- buffer << '</section>'
401
- @header_list << name
402
- return DONE
403
- elsif name == 'toc'
404
- @toc_template = TOC_TEMPLATE.clone
405
- return COLLECT
406
- end
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 == 'section'
414
- buffer << @proc.fmt_line(@toc_template[:section]) if @toc_template[:section]
415
- elsif entry == 'section_end'
416
- buffer << @proc.fmt_line(@toc_template[:section_end]) if @toc_template[:section_end]
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 = @toc_template[entry[:tag]] || @toc_template[:default]
419
- buffer << @proc.fmt_line(
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
- buffer << @proc.fmt_line(@toc_template[:end]) if @toc_template[:end]
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 InlineMacros
435
- include Eggshell::MacroHandler
436
-
437
- def initialize
438
- @capvar = nil
439
- @collbuff = nil
440
- @depth = 0
441
- end
442
-
443
- HASH_FMT_DECORATORS = {
444
- '[*' => '<b>',
445
- '[**' => '<strong>',
446
- '[_' => '<i>',
447
- '[__' => '<em>',
448
- '*]'=> '</b>',
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
- def set_processor(eggshell)
459
- @proc = eggshell
460
- @proc.register_macro(self, '[!', '[~', '[^', '[.', '[*', '[**', '[/', '[//', '[_', '[-')
479
+ ESCAPE_MAP_HTML = {
480
+ '<' => '&lt;',
481
+ '>' => '&gt;',
482
+ '/<' => '&laquo;',
483
+ '>/' => '&raquo;',
484
+ '/\'' => '&lsquo;',
485
+ '\'/' => '&rsquo;',
486
+ '/"' => '&ldquo;',
487
+ '"/' => '&rdquo;',
488
+ 'c' => '&copy;',
489
+ 'r' => '&reg;',
490
+ '!' => '&iexcl;',
491
+ '-' => '&ndash;',
492
+ '--' => '&mdash;',
493
+ '&' => '&amp;'
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 process(buffer, macname, args, lines, depth)
464
- prefix = macname[0..1]
465
- textpart = args.shift
466
- tag = nil
467
-
468
- case prefix
469
- when '[^'
470
- tag = 'sup'
471
- when '[.'
472
- tag = 'sub'
473
- when '[*'
474
- tag = macname == '[**' ? 'strong' : 'b'
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
- buffer << restructure_html(tag, textpart ? textpart.strip : textpart, args)
502
- end
503
-
504
- def restructure_html(tag, text, attributes = [])
505
- buff = "<#{tag}"
506
- attributes.each do |attrib|
507
- key, val = attrib.split(':', 2)
508
- # @todo html escape?
509
- if val
510
- buff = "#{buff} #{key}=\"#{val.gsub('\\|', '|')}\""
511
- else
512
- buff = "#{buff} #{key}"
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 text == nil
517
- buff += ' />'
561
+ if tagopen
562
+ return "#{create_tag(tagname, atts)}#{text}</#{tagname}>"
518
563
  else
519
- buff = "#{buff}>#{text}</#{tag}>"
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
- def set_processor(eggshell)
540
- @proc = eggshell
541
- @proc.register_macro(self, *%w(! = capture var include process parse_test))
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(buffer, macname, args, lines, depth)
545
- if macname == '!'
546
- @proc.vars[:block_params] = @proc.expr_eval(args)
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
- proclines = @proc.expr_eval(args[0])
550
- proclines = proclines.split(/[\r\n]+/) if proclines.is_a?(String)
551
- buffer << @proc.process(proclines, depth + 1) if proclines.is_a?(Array)
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
- @proc.vars[key] = val
610
+ val = args[1]
569
611
  end
570
-
571
- # @todo fix this so it's possible to set things like arr[0].setter, etc.
572
- # ptrs = EE.retrieve_var(key, @proc.vars, {}, true)
573
- # val = val.is_a?(Array) && val[0] == :var ? EE.retrieve_var(val[1], @proc.vars, {}) : val
574
- # if ptrs
575
- # EE.set_var(ptrs[0], ptrs[1][0], ptrs[1][1], val)
576
- # else
577
- # @proc.vars[key] = val
578
- # end
579
- end
580
- elsif macname == 'include'
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, buffer, depth, opts)
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, depth, opts = {})
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 = @proc.expand_expr(inc.strip)
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
- @proc.vars[:include_paths].each do |root|
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 @proc.vars[:include_root]
610
- checks << "#{@proc.vars[:include_root]}#{inc}"
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
- @proc.vars[:include_stack] << inc
620
- @proc.context.push_line_counter
677
+ @vars[:include_stack] << inc
621
678
  begin
622
- buff << @proc.process(lines, depth + 1)
623
- @proc._debug("include: 200 #{inc}")
679
+ buff << @eggshell.process(lines, 0, call_depth + 1)
680
+ @eggshell._debug("include: 200 #{inc}")
624
681
  rescue => ex
625
- @proc._error("include: 500 #{inc}: #{ex.message}")
682
+ @eggshell._error("include: 500 #{inc}: #{ex.message}#{ex.backtrace.join("\n\t")}")
626
683
  end
627
684
 
628
- @proc.vars[:include_stack].pop
629
- @proc.context.pop_line_counter
685
+ @vars[:include_stack].pop
630
686
  break
631
687
  else
632
- @proc._warn("include: 404 #{inc}")
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
- class ControlMacros
640
- include Eggshell::MacroHandler
705
+ def each(&block)
706
+ counter = 0
707
+ struct = Eggshell::ExpressionEvaluator.struct(@cond)
641
708
 
642
- def initialize
643
- @stack = []
644
- @state = []
645
- @macstack = []
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
- def set_processor(eggshell)
649
- @proc = eggshell
650
- @proc.register_macro(self, *%w(if elsif else loop for while break next))
651
- end
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
- def process(buffer, macname, args, lines, depth)
654
- p0 = args.is_a?(Array) ? args[0] : nil
655
- lines ? lines.delete('') : ''
737
+ @state = []
738
+ # @todo set loop limits from opts or defaults
739
+ end
656
740
 
657
- macname = macname.to_sym
658
- st = @state[depth]
659
- if !@state[depth]
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[depth] = st
747
+ @state[call_depth] = st
663
748
  # erase nested state
664
- @state[depth+1] = nil
749
+ @state[call_depth+1] = nil
665
750
  end
666
751
 
667
- if macname == :for || macname == :loop
668
- p0 = p0 || {}
669
- st[:var] = p0['var']
670
- st[:start] = p0['start']
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
- # in for, construct range that can be looped over
685
- # in loop, detect 'each' method
686
- if macname == :for
687
- st[:item] = st[:var]
688
- looper = Range.new(st[:start], st[:stop]).step(st[:step]).to_a
689
- elsif macname == :loop
690
- begin
691
- looper = st[:iter].is_a?(Array) && st[:iter][0].is_a?(Symbol) ? @proc.expr_eval(st[:iter]) : st[:iter]
692
- looper = nil if !looper.respond_to?(:each)
693
- loop_is_map = looper.is_a?(Hash)
694
- rescue
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 = 0
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
- @proc.vars[st[:counter]] = i1
795
+ @eggshell.vars[st[:counter]] = i1
706
796
  val = i2
707
797
  else
708
798
  val = i1
709
- @proc.vars[st[:counter]] = counter
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
- @proc.vars[st[:item]] = val.is_a?(Array) && val[0].is_a?(Symbol) ? @proc.expr_eval(val) : val
714
- # divert lines to collector
715
- if st[:collect]
716
- lines.each do |_line|
717
- if _line.is_a?(String)
718
- collector << @proc.expand_expr(_line)
719
- elsif _line.is_a?(Eggshell::Block)
720
- _line.process(collector)
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
- process_lines(lines, buffer, depth + 1, st[:raw])
833
+ collector << @eggshell.assemble(lines, call_depth + 1)
725
834
  end
726
- break if st[:break]
727
835
 
728
- counter += 1
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[depth] = nil
746
- elsif macname == :while
747
- raw = args[1]
748
- while @proc.expr_eval(p0)
749
- process_lines(lines, buffer, depth + 1, raw)
750
- break if st[:break]
751
- end
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] = @proc.expr_eval(cond_struct)[0]
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 = depth - 1
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