ad_hoc_template 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -0
- data/.travis.yml +7 -1
- data/Gemfile +3 -2
- data/ad_hoc_template.gemspec +4 -3
- data/lib/ad_hoc_template.rb +51 -69
- data/lib/ad_hoc_template/command_line_interface.rb +36 -37
- data/lib/ad_hoc_template/config_manager.rb +5 -10
- data/lib/ad_hoc_template/default_tag_formatter.rb +8 -8
- data/lib/ad_hoc_template/entry_format_generator.rb +24 -13
- data/lib/ad_hoc_template/parser.rb +144 -59
- data/lib/ad_hoc_template/pseudohiki_formatter.rb +4 -2
- data/lib/ad_hoc_template/recipe_manager.rb +19 -27
- data/lib/ad_hoc_template/record_reader.rb +59 -56
- data/lib/ad_hoc_template/shim.rb +26 -0
- data/lib/ad_hoc_template/utils.rb +11 -5
- data/lib/ad_hoc_template/version.rb +3 -1
- data/output.html +14 -0
- data/spec/command_line_interface_spec.rb +31 -0
- data/spec/config_manager_spec.rb +4 -4
- data/spec/entry_format_generator_spec.rb +1 -1
- data/spec/parser_spec.rb +15 -1
- data/spec/recipe_manager_spec.rb +14 -14
- metadata +26 -9
@@ -1,14 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AdHocTemplate
|
4
4
|
class DefaultTagFormatter
|
5
5
|
FUNCTION_TABLE = {
|
6
|
-
|
7
|
-
|
6
|
+
'=' => :default,
|
7
|
+
'h' => :html_encode,
|
8
8
|
}
|
9
9
|
|
10
10
|
def self.assign_format(format_label, &func)
|
11
|
-
if format_label.kind_of?
|
11
|
+
if format_label.kind_of?(Hash) && !func
|
12
12
|
func_name, label = format_label.to_a.flatten
|
13
13
|
FUNCTION_TABLE[label] = func_name
|
14
14
|
else
|
@@ -17,25 +17,25 @@ module AdHocTemplate
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def find_function(format_label)
|
20
|
-
FUNCTION_TABLE[format_label]
|
20
|
+
FUNCTION_TABLE[format_label] || :default
|
21
21
|
end
|
22
22
|
|
23
23
|
def format(format_label, var, record)
|
24
24
|
func = find_function(format_label)
|
25
25
|
case func
|
26
26
|
when Symbol, String
|
27
|
-
|
27
|
+
send(func, var, record)
|
28
28
|
else
|
29
29
|
func.call(var, record)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def default(var, record)
|
34
|
-
record[var]||"[#{var}]"
|
34
|
+
record[var] || "[#{var}]"
|
35
35
|
end
|
36
36
|
|
37
37
|
def html_encode(var, record)
|
38
|
-
HtmlElement.escape(record[var]||var)
|
38
|
+
HtmlElement.escape(record[var] || var)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AdHocTemplate
|
4
4
|
module EntryFormatGenerator
|
5
|
+
DEFAULT_ENCODING = Encoding.default_external.names[0]
|
6
|
+
|
5
7
|
class LabelChecker
|
6
8
|
attr_reader :labels
|
7
9
|
def initialize
|
@@ -22,13 +24,15 @@ module AdHocTemplate
|
|
22
24
|
private
|
23
25
|
|
24
26
|
def visit_iteration_tag_node(tree, memo)
|
25
|
-
|
27
|
+
sub_checker = self
|
28
|
+
iteration_label = tree.type
|
29
|
+
|
30
|
+
if iteration_label
|
26
31
|
sub_checker = self.class.new
|
27
32
|
@labels[iteration_label] = [sub_checker.labels]
|
28
|
-
tree.each { |node| node.accept(sub_checker, memo) }
|
29
|
-
else
|
30
|
-
tree.each {|node| node.accept(self, memo) }
|
31
33
|
end
|
34
|
+
|
35
|
+
tree.each {|node| node.accept(sub_checker, memo) }
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
@@ -39,17 +43,23 @@ module AdHocTemplate
|
|
39
43
|
RecordReader.dump(labels, target_format)
|
40
44
|
end
|
41
45
|
|
42
|
-
def self.extract_recipes_from_template_files(template_paths,
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
extract_recipe(template_source, path, tag_type, encoding)
|
46
|
+
def self.extract_recipes_from_template_files(template_paths,
|
47
|
+
tag_type=:default,
|
48
|
+
encoding=DEFAULT_ENCODING)
|
49
|
+
recipes = map_read_files(template_paths, encoding) do |path, src|
|
50
|
+
extract_recipe(src, path, tag_type, encoding)
|
48
51
|
end
|
49
52
|
|
50
53
|
recipes.join
|
51
54
|
end
|
52
55
|
|
56
|
+
def self.map_read_files(paths, encoding=DEFAULT_ENCODING)
|
57
|
+
paths.map do |path|
|
58
|
+
full_path = File.expand_path(path)
|
59
|
+
yield path, File.open(full_path, "rb:BOM|#{encoding}", &:read)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
53
63
|
def self.extract_recipe(template_source, template_path,
|
54
64
|
tag_type=:default, encoding='UTF-8')
|
55
65
|
recipe = recipe_entry(template_path, tag_type, encoding)
|
@@ -91,7 +101,7 @@ module AdHocTemplate
|
|
91
101
|
end
|
92
102
|
|
93
103
|
def self.recipe_entry(template_path, tag_type, encoding)
|
94
|
-
|
104
|
+
{
|
95
105
|
'template' => template_path,
|
96
106
|
'tag_type' => tag_type,
|
97
107
|
'template_encoding' => encoding,
|
@@ -99,7 +109,7 @@ module AdHocTemplate
|
|
99
109
|
'data_format' => nil,
|
100
110
|
'data_encoding' => nil,
|
101
111
|
'output_file' => nil,
|
102
|
-
'blocks' => []
|
112
|
+
'blocks' => [],
|
103
113
|
}
|
104
114
|
end
|
105
115
|
|
@@ -112,6 +122,7 @@ module AdHocTemplate
|
|
112
122
|
}
|
113
123
|
end
|
114
124
|
|
125
|
+
private_class_method :map_read_files
|
115
126
|
private_class_method :extract_form_as_ruby_objects
|
116
127
|
private_class_method :pull_up_inner_iterations
|
117
128
|
private_class_method :each_iteration_label
|
@@ -1,7 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'pseudohiki/inlineparser'
|
4
|
+
require 'htmlelement'
|
5
5
|
|
6
6
|
module AdHocTemplate
|
7
7
|
LINE_END_RE = /(?:\r?\n|\r)/
|
@@ -13,7 +13,7 @@ module AdHocTemplate
|
|
13
13
|
|
14
14
|
def push(node=TreeStack::Node.new)
|
15
15
|
first_leaf = node[0]
|
16
|
-
node[0] = assign_value_to_type(first_leaf) if empty?
|
16
|
+
node[0] = assign_value_to_type(first_leaf) if empty? && first_leaf
|
17
17
|
super
|
18
18
|
end
|
19
19
|
|
@@ -27,19 +27,35 @@ module AdHocTemplate
|
|
27
27
|
each_tag_node {|node| return true if node.contains_any_value_tag? }
|
28
28
|
end
|
29
29
|
|
30
|
+
def contains_any_fallback_tag?
|
31
|
+
any? {|sub_node| sub_node.kind_of? Parser::FallbackNode }
|
32
|
+
end
|
33
|
+
|
30
34
|
def inner_iteration_tag_labels
|
31
35
|
names = []
|
32
36
|
each_tag_node do |node|
|
33
37
|
next unless node.kind_of? IterationNode
|
34
38
|
names.push node.type if node.type
|
35
|
-
|
36
|
-
|
37
|
-
end
|
39
|
+
inner_names = node.inner_iteration_tag_labels
|
40
|
+
names.concat inner_names if inner_names
|
38
41
|
end
|
39
42
|
|
40
43
|
names unless names.empty?
|
41
44
|
end
|
42
45
|
|
46
|
+
def cast(node_type=Parser::TagNode)
|
47
|
+
node_type.new.concat(clone)
|
48
|
+
end
|
49
|
+
|
50
|
+
def select_fallback_nodes
|
51
|
+
nodes = select {|sub_node| sub_node.kind_of? Parser::FallbackNode }
|
52
|
+
nodes.empty? ? nil : nodes
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_sub_nodes(data_loader, memo)
|
56
|
+
map {|leaf| leaf.accept(data_loader, memo) }.join
|
57
|
+
end
|
58
|
+
|
43
59
|
private
|
44
60
|
|
45
61
|
def each_tag_node
|
@@ -48,11 +64,11 @@ module AdHocTemplate
|
|
48
64
|
end
|
49
65
|
|
50
66
|
def assign_value_to_type(first_leaf)
|
51
|
-
if first_leaf.kind_of?
|
52
|
-
return first_leaf.sub(/\A#{LINE_END_STR}/,
|
67
|
+
if first_leaf.kind_of?(String) && /\A\s/ =~ first_leaf
|
68
|
+
return first_leaf.sub(/\A#{LINE_END_STR}/, '')
|
53
69
|
end
|
54
70
|
@type, first_leaf_content = split_by_newline_or_spaces(first_leaf)
|
55
|
-
first_leaf_content||
|
71
|
+
first_leaf_content || ''
|
56
72
|
end
|
57
73
|
|
58
74
|
def split_by_newline_or_spaces(first_leaf)
|
@@ -62,12 +78,30 @@ module AdHocTemplate
|
|
62
78
|
end
|
63
79
|
|
64
80
|
class IterationNode < TagNode
|
81
|
+
class InnerLabel
|
82
|
+
attr_reader :inner_label
|
83
|
+
|
84
|
+
def self.labels(inner_labels, cur_label)
|
85
|
+
inner_labels.map {|label| new(label, cur_label) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(inner_label, cur_label)
|
89
|
+
@inner_label = inner_label
|
90
|
+
@label, @key = inner_label.sub(/\A#/, '').split(/\|/, 2)
|
91
|
+
@cur_label = cur_label
|
92
|
+
end
|
93
|
+
|
94
|
+
def full_label(record)
|
95
|
+
[@cur_label, @label, record[@key]].join('|')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
65
99
|
def assign_value_to_type(first_leaf)
|
66
100
|
return first_leaf unless first_leaf.kind_of? String
|
67
101
|
|
68
102
|
if /\A[^\s:]*:\s/ =~ first_leaf
|
69
103
|
@type, remaining_part = first_leaf.split(/:(?:#{LINE_END_STR}|\s)/, 2)
|
70
|
-
@type = @type.empty? ? nil : '#'
|
104
|
+
@type = @type.empty? ? nil : '#' + @type
|
71
105
|
return remaining_part
|
72
106
|
end
|
73
107
|
|
@@ -81,13 +115,19 @@ module AdHocTemplate
|
|
81
115
|
end
|
82
116
|
end
|
83
117
|
|
118
|
+
def inner_labels
|
119
|
+
return unless @type
|
120
|
+
labels = inner_iteration_tag_labels
|
121
|
+
InnerLabel.labels(labels, @type) if labels
|
122
|
+
end
|
123
|
+
|
84
124
|
private
|
85
125
|
|
86
126
|
def not_empty_sub_records?(record)
|
87
127
|
sub_records = record[type]
|
88
|
-
return false if sub_records.nil?
|
128
|
+
return false if sub_records.nil? || sub_records.empty?
|
89
129
|
sub_records.each do |rec|
|
90
|
-
return true if rec.values.any? {|val| val
|
130
|
+
return true if rec.values.any? {|val| val && !val.empty? }
|
91
131
|
end
|
92
132
|
false
|
93
133
|
end
|
@@ -99,15 +139,20 @@ module AdHocTemplate
|
|
99
139
|
first_leaf.sub(/\A#{LINE_END_STR}/, '')
|
100
140
|
end
|
101
141
|
|
102
|
-
def contains_any_value_assigned_tag_node?(
|
142
|
+
def contains_any_value_assigned_tag_node?(_record)
|
103
143
|
false
|
104
144
|
end
|
145
|
+
|
146
|
+
def format_sub_nodes(data_loader, memo)
|
147
|
+
node = cast(Parser::IterationNode)
|
148
|
+
node.contains_any_value_tag? ? node.accept(data_loader, memo) : node.join
|
149
|
+
end
|
105
150
|
end
|
106
151
|
|
107
152
|
class ValueNode < TagNode
|
108
153
|
def contains_any_value_assigned_tag_node?(record)
|
109
|
-
val = record[
|
110
|
-
val
|
154
|
+
val = record[join.strip]
|
155
|
+
val && !val.empty?
|
111
156
|
end
|
112
157
|
|
113
158
|
def contains_any_value_tag?
|
@@ -118,7 +163,34 @@ module AdHocTemplate
|
|
118
163
|
class Leaf < Parser::Leaf; end
|
119
164
|
|
120
165
|
class TagType
|
121
|
-
|
166
|
+
PREDEFINED = {
|
167
|
+
default: [
|
168
|
+
['<%', '%>'], ['<%#', '#%>'],
|
169
|
+
['<%*', '*%>'], false,
|
170
|
+
],
|
171
|
+
square_brackets: [
|
172
|
+
['[[', ']]'], ['[[#', '#]]'],
|
173
|
+
['[[*', '*]]'], false,
|
174
|
+
],
|
175
|
+
curly_brackets: [
|
176
|
+
['{{', '}}'], ['{{#', '#}}'],
|
177
|
+
['{{*', '*}}'], false,
|
178
|
+
],
|
179
|
+
xml_like1: [
|
180
|
+
['<!--%', '%-->'], ['<iterate>', '</iterate>'],
|
181
|
+
['<fallback>', '</fallback>'], true,
|
182
|
+
],
|
183
|
+
xml_like2: [
|
184
|
+
['<fill>', '</fill>'], ['<iterate>', '</iterate>'],
|
185
|
+
['<fallback>', '</fallback>'], true,
|
186
|
+
],
|
187
|
+
xml_comment_like: [
|
188
|
+
['<!--%', '%-->'], ['<!--%iterate%-->', '<!--%/iterate%-->'],
|
189
|
+
['<!--%fallback%-->', '<!--%/fallback%-->'], true,
|
190
|
+
],
|
191
|
+
}.freeze
|
192
|
+
|
193
|
+
attr_reader :head, :tail, :token_pat, :strip_iteration_indent
|
122
194
|
attr_reader :head_of, :tail_of
|
123
195
|
@types = {}
|
124
196
|
|
@@ -126,60 +198,59 @@ module AdHocTemplate
|
|
126
198
|
@types[tag_name]
|
127
199
|
end
|
128
200
|
|
129
|
-
def self.register(tag_name
|
130
|
-
fallback_tag
|
131
|
-
@types[tag_name] = new(tag, iteration_tag,
|
201
|
+
def self.register(tag_name, tag, iteration_tag,
|
202
|
+
fallback_tag, strip_iteration_indent=false)
|
203
|
+
@types[tag_name] = new(tag, iteration_tag,
|
204
|
+
fallback_tag, strip_iteration_indent)
|
132
205
|
end
|
133
206
|
|
134
|
-
def initialize(tag, iteration_tag, fallback_tag,
|
207
|
+
def initialize(tag, iteration_tag, fallback_tag, strip_iteration_indent)
|
135
208
|
assign_type(tag, iteration_tag, fallback_tag)
|
136
209
|
@token_pat = PseudoHiki.compile_token_pat(@head.keys, @tail.keys)
|
137
|
-
@
|
210
|
+
@strip_iteration_indent = strip_iteration_indent
|
138
211
|
end
|
139
212
|
|
140
213
|
def assign_type(tag, iteration_tag, fallback_tag)
|
141
214
|
node_tag_pairs = [
|
142
215
|
[ValueNode, *tag],
|
143
216
|
[IterationNode, *iteration_tag],
|
144
|
-
[FallbackNode, *fallback_tag]
|
217
|
+
[FallbackNode, *fallback_tag],
|
145
218
|
]
|
146
219
|
|
147
|
-
@head, @tail, @head_of, @tail_of =
|
220
|
+
@head, @tail, @head_of, @tail_of = map_nodes_to_tags(node_tag_pairs)
|
221
|
+
end
|
222
|
+
|
223
|
+
def map_nodes_to_tags(node_tag_pairs)
|
224
|
+
PseudoHiki.associate_nodes_with_tags(node_tag_pairs)
|
148
225
|
end
|
149
226
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
register(:xml_like1, ["<!--%", "%-->"], ["<iterate>", "</iterate>"], ["<fallback>", "</fallback>"], true)
|
154
|
-
register(:xml_like2, ["<fill>", "</fill>"], ["<iterate>", "</iterate>"], ["<fallback>", "</fallback>"], true)
|
155
|
-
register(:xml_comment_like, ["<!--%", "%-->"], ["<!--%iterate%-->", "<!--%/iterate%-->"], ["<!--%fallback%-->", "<!--%/fallback%-->"], true)
|
227
|
+
private :map_nodes_to_tags
|
228
|
+
|
229
|
+
PREDEFINED.each {|tag_name, tags| register(tag_name, *tags) }
|
156
230
|
end
|
157
231
|
|
158
232
|
class UserDefinedTagTypeConfigError < StandardError; end
|
159
233
|
|
160
234
|
def self.parse(str, tag_name=:default)
|
161
235
|
str = remove_indents_and_newlines_if_necessary(str, tag_name)
|
162
|
-
new(str, TagType[tag_name]).parse
|
236
|
+
new(str, TagType[tag_name]).parse!.tree
|
163
237
|
end
|
164
238
|
|
165
239
|
def self.register_user_defined_tag_type(config_source)
|
166
|
-
config = YAML.
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
config[
|
173
|
-
config["iteration_tag"],
|
174
|
-
config["fallback_tag"],
|
175
|
-
config["remove_indent"] || false)
|
240
|
+
config = YAML.safe_load(config_source, [Symbol])
|
241
|
+
check_validity_of_config(config)
|
242
|
+
TagType.register(registered_tag_name = config['tag_name'].to_sym,
|
243
|
+
config['tag'],
|
244
|
+
config['iteration_tag'],
|
245
|
+
config['fallback_tag'],
|
246
|
+
config['remove_indent'] || false)
|
176
247
|
registered_tag_name
|
177
248
|
end
|
178
249
|
|
179
250
|
def self.remove_indents_and_newlines_if_necessary(str, tag_name)
|
180
251
|
node_types = [IterationNode, FallbackNode]
|
181
252
|
tag_type = TagType[tag_name]
|
182
|
-
if TagType[tag_name].
|
253
|
+
if TagType[tag_name].strip_iteration_indent
|
183
254
|
str = remove_indent_before_iteration_tags(str, tag_type)
|
184
255
|
str = remove_indent_before_fallback_tags(str, tag_type)
|
185
256
|
end
|
@@ -187,20 +258,19 @@ module AdHocTemplate
|
|
187
258
|
end
|
188
259
|
|
189
260
|
def self.remove_indent_before_iteration_tags(template_source, tag_type)
|
190
|
-
start_tag, end_tag =
|
191
|
-
|
192
|
-
|
193
|
-
].map {|tag| Regexp.escape(tag) }
|
194
|
-
template_source.gsub(/^([ \t]+#{start_tag}\S*#{LINE_END_STR})/) {|s| s.lstrip }
|
195
|
-
.gsub(/^([ \t]+#{end_tag}#{LINE_END_STR})/) {|s| s.lstrip }
|
261
|
+
start_tag, end_tag = regexp_escape_tag_pair(tag_type, IterationNode)
|
262
|
+
template_source.gsub(/^([ \t]+#{start_tag}\S*#{LINE_END_STR})/, &:lstrip)
|
263
|
+
.gsub(end_tag_alone_re(end_tag), &:lstrip)
|
196
264
|
end
|
197
265
|
|
198
266
|
def self.remove_indent_before_fallback_tags(template_source, tag_type)
|
199
|
-
tag_re_str =
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
267
|
+
tag_re_str = regexp_escape_tag_pair(tag_type, FallbackNode).join('|')
|
268
|
+
template_source.gsub(end_tag_alone_re(tag_re_str), &:lstrip)
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.regexp_escape_tag_pair(tag_type, node_class)
|
272
|
+
[tag_type.head_of[node_class],
|
273
|
+
tag_type.tail_of[node_class],].map {|tag| Regexp.escape(tag) }
|
204
274
|
end
|
205
275
|
|
206
276
|
def self.remove_trailing_newline_of_end_tags(node_types, source, tag_type)
|
@@ -210,10 +280,24 @@ module AdHocTemplate
|
|
210
280
|
end
|
211
281
|
end
|
212
282
|
|
283
|
+
def self.end_tag_alone_re(tag)
|
284
|
+
/^([ \t]+(?:#{tag})#{LINE_END_STR})/
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.check_validity_of_config(config)
|
288
|
+
%w[tag_name tag iteration_tag fallback_tag].each do |item|
|
289
|
+
config[item] || raise(UserDefinedTagTypeConfigError,
|
290
|
+
"\"#{item}\" should be defined.")
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
213
294
|
private_class_method(:remove_indents_and_newlines_if_necessary,
|
214
295
|
:remove_indent_before_iteration_tags,
|
215
296
|
:remove_indent_before_fallback_tags,
|
216
|
-
:
|
297
|
+
:regexp_escape_tag_pair,
|
298
|
+
:remove_trailing_newline_of_end_tags,
|
299
|
+
:end_tag_alone_re,
|
300
|
+
:check_validity_of_config)
|
217
301
|
|
218
302
|
def initialize(source, tag)
|
219
303
|
@tag = tag
|
@@ -221,13 +305,14 @@ module AdHocTemplate
|
|
221
305
|
super()
|
222
306
|
end
|
223
307
|
|
224
|
-
def parse
|
225
|
-
|
226
|
-
next if @tag.tail[token] == current_node.class
|
227
|
-
next if @tag.head[token]
|
228
|
-
|
308
|
+
def parse!
|
309
|
+
@tokens.each do |token|
|
310
|
+
next if @tag.tail[token] == current_node.class && pop
|
311
|
+
next if @tag.head[token] && push(@tag.head[token].new)
|
312
|
+
push Leaf.create(token)
|
229
313
|
end
|
230
314
|
|
315
|
+
@tokens = nil
|
231
316
|
self
|
232
317
|
end
|
233
318
|
end
|