eggshell 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd2753240c406b052a69a3c8858a54de232db6d0
4
- data.tar.gz: a9dc32169269a7098689daab365f566a514c8596
3
+ metadata.gz: e8f0c6e2c0d46c6a179f5ec5b92860f89191cb49
4
+ data.tar.gz: 6a2796788c613161da16ea59f2fbee7e3287e116
5
5
  SHA512:
6
- metadata.gz: 26ea82772d6c94e1ac78040338f6bbad8e9f2afe5be53506deb0cdc8a7bc106dd76e73ccf2373840c3f811c407458135fe5eb7691c204790c1ed254172c014f0
7
- data.tar.gz: 676b738ace9f4ccc2358034a13b55110a6a101bc86731c6f082f867b25c9fc592996f35b116aa56e2d0b81bfcd068e73402bdaff21c780832dbf1bf4a6e081c7
6
+ metadata.gz: 0c0c82ea72213e521a12dba7aa595f3ccedb7dee48141df53c30ffae2fb49de2afc5eba567a1fb608fbdd844316aacb03c547145aa3d1d5082343d27541a11de
7
+ data.tar.gz: 36468590248a2d9c2d4638915166e56985d59ba989c00f3934dd88089705a6f85a7b80d6af39fd1f5f92df340f69b17e6d2906bf3449951d5288d0aba2e588c0
data/bin/eggshell CHANGED
@@ -59,5 +59,6 @@ end
59
59
  # @todo parse any additional opts via command line or local project?
60
60
 
61
61
  $eggshell._info("PROCESSING #{f}")
62
- output = $eggshell.process(IO.readlines(f))
62
+ # @todo check opts for encoding, default to utf-8
63
+ output = $eggshell.process(IO.readlines(f, {:encoding => 'utf-8'}))
63
64
  puts output
data/lib/eggshell.rb CHANGED
@@ -29,6 +29,10 @@ module Eggshell
29
29
  Line.new(line, @tab_str, @indent_lvl, @line_num, raw)
30
30
  end
31
31
 
32
+ def match(regex)
33
+ @line.match(regex)
34
+ end
35
+
32
36
  attr_reader :line, :tab_str, :indent_lvl, :line_num
33
37
  end
34
38
 
@@ -57,6 +61,8 @@ module Eggshell
57
61
  end
58
62
 
59
63
  require_relative './eggshell/expression-evaluator.rb'
64
+ require_relative './eggshell/expression-evaluator/lexer.rb'
65
+ require_relative './eggshell/stream.rb'
60
66
  require_relative './eggshell/format-handler.rb'
61
67
  require_relative './eggshell/block-handler.rb'
62
68
  require_relative './eggshell/macro-handler.rb'
@@ -187,6 +187,26 @@ module Eggshell::BlockHandler
187
187
  end
188
188
  buff.join()
189
189
  end
190
+
191
+ # Given an attribute string in the form `key1="value" key2="value"` and a map containing
192
+ # new attribute keys, either append to attribute string or inject values into an existing
193
+ # attribute.
194
+ # @param String attribs
195
+ # @param Hash map
196
+ # @param Boolean escape If true (default), calls {@see html_escape()} on each value.
197
+ def inject_attribs(attribs, map = {}, escape = true)
198
+ map.each do |key, val|
199
+ key = key.to_s if key.is_a?(Symbol)
200
+ olen = attribs.length
201
+ match = attribs.match(/#{key}=(['"])([^'"]*)(['"])/)
202
+ if !match
203
+ attribs += " #{key}='#{escape ? html_escape(val) : val}'"
204
+ else
205
+ attribs = attribs.gsub(match[0], "#{key}='#{match[2]} #{escape ? html_escape(val) : val}'")
206
+ end
207
+ end
208
+ attribs
209
+ end
190
210
 
191
211
  def css_string(map)
192
212
  css = []
@@ -0,0 +1,31 @@
1
+ # Standard functions to perform basic operations for common objects.
2
+ module Eggshell::Bundles::BasicFunctions
3
+ def self.length(obj)
4
+ if obj.respond_to?(:length)
5
+ obj.length
6
+ else
7
+ nil
8
+ end
9
+ end
10
+
11
+ def self.str_match(haystack, needle)
12
+ end
13
+
14
+ def self.str_split(str, delim, limit = nil)
15
+ end
16
+
17
+ def self.arr_push(arr, *items)
18
+ end
19
+
20
+ def self.arr_pop(arr)
21
+ end
22
+
23
+ def self.arr_delete(arr, index)
24
+ end
25
+
26
+ def self.map_push(map, key, val)
27
+ end
28
+
29
+ def self.map_delete(map, key)
30
+ end
31
+ end
@@ -13,11 +13,13 @@ module Eggshell::Bundles::Basic
13
13
 
14
14
  CoreMacros.new.set_processor(proc, opts)
15
15
  ControlLoopMacros.new.set_processor(proc, opts)
16
+ DataLoaderMacro.new.set_processor(proc, opts)
16
17
 
17
18
  BasicFormatHandlers.new.set_processor(proc, opts)
18
19
 
19
20
  #proc.register_functions('', StdFunctions::FUNC_NAMES)
20
- proc.register_functions('sprintf', Kernel)
21
+ proc.register_functions(Kernel, 'sprintf')
22
+ proc.register_functions(Eggshell::Bundles::BasicFunctions, 'length,str_match,str_split,arr_push,arr_pop,arr_delete,map_put,map_delete')
21
23
  end
22
24
 
23
25
  class TextBlocks
@@ -106,6 +108,7 @@ module Eggshell::Bundles::Basic
106
108
  if type == 'html_pass' || type == 'html_block'
107
109
  out << @eggshell.expand_expr(lines.join("\n"))
108
110
  else
111
+ # @todo if pre starts with blank line, remove it
109
112
  tagname = type == 'bq' ? 'blockquote' : type
110
113
  args = [] if !args
111
114
  bp = get_block_params(type, args[0])
@@ -163,7 +166,7 @@ module Eggshell::Bundles::Basic
163
166
 
164
167
  def can_handle(line)
165
168
  if !@block_type
166
- if line.match(/^(table[(.]?|\||\|>|\/)/) || line.match(SPECIAL_DEF)
169
+ if line.match(/^(table[(.]?|\||\|>|\/|!@)/) || line.match(SPECIAL_DEF)
167
170
  @block_type = 'table'
168
171
  return BH::COLLECT
169
172
  end
@@ -172,7 +175,7 @@ module Eggshell::Bundles::Basic
172
175
  end
173
176
 
174
177
  def continue_with(line)
175
- if line.match(/^(\||\|>|\/)/) || line.match(SPECIAL_DEF)
178
+ if line.match(/^(\||\|>|\/|!@)/) || line.match(SPECIAL_DEF)
176
179
  return BH::COLLECT
177
180
  end
178
181
  return BH::RETRY
@@ -212,13 +215,16 @@ module Eggshell::Bundles::Basic
212
215
  caption[:attributes] = parse_args(atts, true)[0]
213
216
  else
214
217
  atts = parse_args(atts, true)[0]
215
- puts atts.inspect
216
218
  colgroup << create_tag('col', attrib_string(atts), false)
217
219
  end
218
220
  elsif line[0] == '/' && rc == 0
219
221
  cols = line[1..line.length].split('|')
220
222
  out << "<thead><tr class='#{bp['head.class']}'>"
221
223
  cols.each do |col|
224
+ col = col.strip
225
+ if col == '' && bp['emptycell']
226
+ col = bp['emptycell']
227
+ end
222
228
  out << "\t#{fmt_cell(col, true, ccount)}"
223
229
  ccount += 1
224
230
  end
@@ -229,14 +235,29 @@ module Eggshell::Bundles::Basic
229
235
  cols = line[1..line.length].split('|')
230
236
  out << "<tfoot><tr class='#{bp['foot.class']}'>"
231
237
  cols.each do |col|
238
+ col = col.strip
239
+ if col == '' && bp['emptycell']
240
+ col = bp['emptycell']
241
+ end
232
242
  out << "\t#{fmt_cell(col, true, ccount)}"
233
243
  ccount += 1
234
244
  end
235
245
  out << '</tr></tfoot>'
236
246
  break
237
- elsif line[0] == DELIM1 || line[0..1] == DELIM2
247
+ elsif line[0] == DELIM1 || line[0..1] == DELIM2 || line[0..1] == CELL_ATTR_START
238
248
  out << '<tbody>' if rc == 0
239
249
  idx = 1
250
+ rattribs = ''
251
+
252
+ if line[0..1] == CELL_ATTR_START
253
+ # @todo look for delim instead of '@!'?
254
+ rt = line.index(CELL_ATTR_END)
255
+ if rt
256
+ rattribs = line[2...rt]
257
+ line = line[rt+CELL_ATTR_END.length..line.length]
258
+ end
259
+ end
260
+
240
261
  sep = /(?<!\\)\|/
241
262
  if line[1] == '>'
242
263
  idx = 2
@@ -245,8 +266,14 @@ module Eggshell::Bundles::Basic
245
266
  cols = line[idx..line.length].split(sep)
246
267
  @eggshell.vars[T_ROW] = rc
247
268
  rclass = row_classes[rc % row_classes.length]
248
- out << "<tr class='tr-row-#{rc} #{rclass}'>"
269
+ rattribs = inject_attribs(rattribs, {:class=>"tr-row-#{rc} #{rclass}"})
270
+
271
+ out << "<tr #{rattribs}>"
249
272
  cols.each do |col|
273
+ col = col.strip
274
+ if col == '' && bp['emptycell']
275
+ col = bp['emptycell']
276
+ end
250
277
  out << "\t#{fmt_cell(col, false, ccount)}"
251
278
  ccount += 1
252
279
  end
@@ -291,13 +318,7 @@ module Eggshell::Bundles::Basic
291
318
  end
292
319
 
293
320
  # inject column position via class
294
- olen = attribs.length
295
- match = attribs.match(/class=(['"])([^'"]*)(['"])/)
296
- if !match
297
- attribs += " class='td-col-#{colnum}'"
298
- else
299
- attribs = attribs.gsub(match[0], "class='#{match[2]} td-col-#{colnum}'")
300
- end
321
+ attribs = inject_attribs(attribs, {:class=>"td-col-#{colnum}"})
301
322
 
302
323
  buff << "<#{tag} #{attribs}>"
303
324
  cclass =
@@ -361,12 +382,12 @@ module Eggshell::Bundles::Basic
361
382
  last = nil
362
383
  first_type = nil
363
384
 
364
- if lines[0] && !lines[0].line.match(/[-#]/)
385
+ if lines[0] && !lines[0].match(/[-#]/)
365
386
  line = lines.shift
366
387
  end
367
388
 
368
389
  lines.each do |line_obj|
369
- line = line_obj.line
390
+ line = line_obj.is_a?(Eggshell::Line) ? line_obj.line : line_obj
370
391
  indent = line_obj.indent_lvl
371
392
  ltype = line[0] == '-' ? 'ul' : 'ol'
372
393
  line = line[1..line.length].strip
@@ -626,7 +647,7 @@ module Eggshell::Bundles::Basic
626
647
  opts = {} if !opts
627
648
  @opts = opts
628
649
  @eggshell = proc
629
- @eggshell.add_macro_handler(self, '=', '!', 'process', 'capture', 'raw', 'pipe')
650
+ @eggshell.add_macro_handler(self, '=', 'var', '!', 'process', 'capture', 'raw', 'pipe')
630
651
  @eggshell.add_macro_handler(self, 'include') if !@opts['macro.include.off']
631
652
  @vars = @eggshell.vars
632
653
 
@@ -643,7 +664,7 @@ module Eggshell::Bundles::Basic
643
664
  end
644
665
 
645
666
  def process(name, args, lines, out, call_depth = 0)
646
- if name == '='
667
+ if name == '=' || name == 'var'
647
668
  # @todo expand args[0]?
648
669
  if args[0]
649
670
  val = nil
@@ -702,21 +723,8 @@ module Eggshell::Bundles::Basic
702
723
  paths.each do |inc|
703
724
  inc = inc.line if inc.is_a?(Eggshell::Line)
704
725
  inc = @eggshell.expand_expr(inc.strip)
705
- checks = []
706
- if inc[0] != '/'
707
- @vars[:include_paths].each do |root|
708
- checks << "#{root}/#{inc}"
709
- end
710
- # @todo if :include_root, expand path and check that it's under the root, otherwise, sandbox
711
- else
712
- # sandboxed root include
713
- if @eggshell.vars[:include_root]
714
- checks << "#{@vars[:include_root]}#{inc}"
715
- else
716
- checks << inc
717
- end
718
- end
719
- checks.each do |inc|
726
+
727
+ CoreMacros.make_include_paths(inc, @vars).each do |inc|
720
728
  if File.exists?(inc)
721
729
  lines = IO.readlines(inc, $/, opts)
722
730
  @vars[:include_stack] << inc
@@ -735,6 +743,26 @@ module Eggshell::Bundles::Basic
735
743
  end
736
744
  end
737
745
  end
746
+
747
+ def self.make_include_paths(inc, opts)
748
+ checks = []
749
+ if inc[0] != '/'
750
+ if opts[:include_paths]
751
+ opts[:include_paths].each do |root|
752
+ checks << "#{root}/#{inc}"
753
+ end
754
+ # @todo if :include_root, expand path and check that it's under the root, otherwise, sandbox
755
+ end
756
+ else
757
+ # sandboxed root include
758
+ if opts[:include_root]
759
+ checks << "#{opts[:include_root]}#{inc}"
760
+ else
761
+ checks << inc
762
+ end
763
+ end
764
+ checks
765
+ end
738
766
  end
739
767
 
740
768
  # Provides iteration and conditional functionality.
@@ -782,6 +810,18 @@ module Eggshell::Bundles::Basic
782
810
  @state = []
783
811
  # @todo set loop limits from opts or defaults
784
812
  end
813
+
814
+ def chain_type(name)
815
+ if name == 'if'
816
+ return [MH::CHAIN_START, name]
817
+ elsif name == 'elsif'
818
+ return [MH::CHAIN_CONTINUE, 'if']
819
+ elsif name == 'else'
820
+ return [MH::CHAIN_END, 'if']
821
+ end
822
+
823
+ [MH::CHAIN_NONE, nil]
824
+ end
785
825
 
786
826
  def process(name, args, lines, out, call_depth = 0)
787
827
  macname = name.to_sym
@@ -816,6 +856,10 @@ module Eggshell::Bundles::Basic
816
856
  st[:counter] = p0['counter'] || 'counter'
817
857
 
818
858
  if st[:iter].is_a?(Array)
859
+ if st[:iter][0].is_a?(Symbol)
860
+ st[:iter] = @eggshell.expr_eval(st[:iter])
861
+ end
862
+
819
863
  st[:start] = 0 if !st[:start]
820
864
  st[:stop] = st[:iter].length - 1 if !st[:stop]
821
865
  st[:step] = 1 if !st[:step]
@@ -840,12 +884,13 @@ module Eggshell::Bundles::Basic
840
884
  @eggshell.vars[st[:counter]] = i1
841
885
  val = i2
842
886
  else
843
- val = i1
887
+ val = st[:iter][i1]
844
888
  @eggshell.vars[st[:counter]] = counter
845
889
  end
846
890
 
847
891
  # inject value into :item -- if it's an expression, evaluate first
848
- @eggshell.vars[st[:item]] = val.is_a?(Array) && val[0].is_a?(Symbol) ? @eggshell.expr_eval(val) : val
892
+ iter_item = val.is_a?(Array) && val[0].is_a?(Symbol) ? @eggshell.expr_eval(val) : val
893
+ @eggshell.vars[st[:item]] = iter_item
849
894
 
850
895
  # if doing raw, pass through block lines with variable expansion. preserve object type (e.g. Line or String);
851
896
  # sub-macros will get passed collector var as output to assemble().
@@ -900,7 +945,7 @@ module Eggshell::Bundles::Basic
900
945
  end
901
946
 
902
947
  last_action = st[:last_action]
903
- st[:last_action] = macname.to_sym
948
+ st[:last_action] = macname
904
949
 
905
950
  # @todo more checks (e.g. no elsif after else, no multiple else, etc.)
906
951
  if !st[:if] || (macname != :else && !cond)
@@ -910,8 +955,9 @@ module Eggshell::Bundles::Basic
910
955
 
911
956
  if macname != :else
912
957
  if !st[:cond_eval]
913
- cond_struct = Eggshell::ExpressionEvaluator.struct(cond)
914
- st[:cond_eval] = @eggshell.expr_eval(cond_struct)
958
+ #cond_struct = Eggshell::ExpressionEvaluator.struct(cond)
959
+ st[:cond_eval] = @eggshell.expr_eval(cond)
960
+ #puts "#{cond.inspect} => #{st[:cond_eval]}"
915
961
  end
916
962
  else
917
963
  st[:cond_eval] = true
@@ -939,7 +985,7 @@ module Eggshell::Bundles::Basic
939
985
  end
940
986
  elsif macname == :next
941
987
  lvl = p0 || 1
942
- i = depth - 1
988
+ i = call_depth - 1
943
989
 
944
990
  # set breaks at each found loop until # of levels reached
945
991
  while i >= 0
@@ -956,5 +1002,112 @@ module Eggshell::Bundles::Basic
956
1002
  end
957
1003
  end
958
1004
 
1005
+ # Allows you to parse and load various data formats into a variable. Data loaders
1006
+ # are registered through class methods.
1007
+ #
1008
+ # Usage:
1009
+ #
1010
+ # pre.
1011
+ # @dataload('json', 'varname') {/
1012
+ # {"jsonkey": value}
1013
+ # /}
1014
+ # \
1015
+ # @dataload('yaml', 'varname', 'include_file')
1016
+ #
1017
+ # If a file is specified for inclusion, it will be restricted to any path constraints
1018
+ # defined by the `@include` macro.
1019
+ class DataLoaderMacro
1020
+ include MH
1021
+
1022
+ module Loader
1023
+ def parse(content)
1024
+ end
1025
+
1026
+ def parse_io(io)
1027
+ parse(io.read)
1028
+ end
1029
+ end
1030
+
1031
+ class JsonLoader
1032
+ include Loader
1033
+
1034
+ def parse(content)
1035
+ JSON.load(content.is_a?(Array) ? content.join(' ') : content)
1036
+ end
1037
+ end
1038
+
1039
+ class YamlLoader
1040
+ include Loader
1041
+
1042
+ def parse(content)
1043
+ YAML.load(content.is_a?(Array) ? content.join("\n") : content)
1044
+ end
1045
+ end
1046
+
1047
+ @@handlers = {}
1048
+
1049
+ # Adds a data loading handler. If it's a class, should accept a null constructor.
1050
+ def self.add_loader(type, handler)
1051
+ @@handlers[type] = handler
1052
+ end
1053
+
1054
+ def set_processor(proc, opts = nil)
1055
+ opts = {} if !opts
1056
+ @opts = opts
1057
+ @eggshell = proc
1058
+ @eggshell.add_macro_handler(self, 'dataload')
1059
+ @vars = @eggshell.vars
1060
+
1061
+ @handlers = {}
1062
+ @@handlers.each do |type, handler|
1063
+ if handler.is_a?(Class)
1064
+ @handlers[type] = handler.new
1065
+ else
1066
+ @handlers[type] = handler
1067
+ end
1068
+ end
1069
+ end
1070
+
1071
+ def collection_type(macro)
1072
+ MH::COLLECT_RAW_MACRO
1073
+ end
1074
+
1075
+ def process(name, args, lines, out, call_depth = 0)
1076
+ type = args[0]
1077
+ varname = args[1]
1078
+ src = args[2]
1079
+ if !varname
1080
+ @eggshell._warn("@dataload: no varname specified for #{type}")
1081
+ return
1082
+ end
1083
+
1084
+ handler = @handlers[type]
1085
+ if !handler
1086
+ @eggshell._warn("@dataload: no handler specified for #{type}")
1087
+ return
1088
+ end
1089
+
1090
+ # @todo support external protocols (e.g. http)?
1091
+ if src
1092
+ paths = CoreMacros.make_include_paths(src, @eggshell.vars)
1093
+ paths.each do |path|
1094
+ if File.exists?(path)
1095
+ @eggshell.vars[varname] = handler.parse_io(File.new(path))
1096
+ break
1097
+ end
1098
+ end
1099
+ else
1100
+ raw = lines.join("\n")
1101
+ @eggshell.vars[varname] = handler.parse(raw)
1102
+ end
1103
+ end
1104
+ end
1105
+
959
1106
  include Eggshell::Bundles::Bundle
960
- end
1107
+ end
1108
+
1109
+ require 'json'
1110
+ require_relative './basic-functions.rb'
1111
+
1112
+ Eggshell::Bundles::Basic::DataLoaderMacro.add_loader('json', Eggshell::Bundles::Basic::DataLoaderMacro::JsonLoader.new)
1113
+ Eggshell::Bundles::Basic::DataLoaderMacro.add_loader('yaml', Eggshell::Bundles::Basic::DataLoaderMacro::YamlLoader.new)