ad_hoc_template 0.4.0 → 0.4.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 +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
|