ad_hoc_template 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ad_hoc_template.gemspec +1 -1
- data/lib/ad_hoc_template/command_line_interface.rb +53 -26
- data/lib/ad_hoc_template/config_manager.rb +129 -0
- data/lib/ad_hoc_template/default_tag_formatter.rb +6 -1
- data/lib/ad_hoc_template/entry_format_generator.rb +83 -12
- data/lib/ad_hoc_template/parser.rb +64 -59
- data/lib/ad_hoc_template/recipe_manager.rb +168 -0
- data/lib/ad_hoc_template/record_reader.rb +73 -16
- data/lib/ad_hoc_template/utils.rb +29 -0
- data/lib/ad_hoc_template/version.rb +1 -1
- data/lib/ad_hoc_template.rb +77 -25
- data/samples/en/inner_iteration/data.aht +21 -0
- data/samples/en/inner_iteration/data.csv +5 -0
- data/samples/en/inner_iteration/data.yaml +14 -0
- data/samples/en/inner_iteration/data2.yaml +14 -0
- data/samples/en/inner_iteration/for_csv.sh +3 -0
- data/samples/en/inner_iteration/template.html +18 -0
- data/samples/en/recipe/main.aht +9 -0
- data/samples/en/recipe/template.html +20 -0
- data/samples/for_recipe.sh +3 -0
- data/samples/ja/inner_iteration/data.aht +21 -0
- data/samples/ja/inner_iteration/data.csv +5 -0
- data/samples/ja/inner_iteration/data.yaml +14 -0
- data/samples/ja/inner_iteration/data2.yaml +14 -0
- data/samples/ja/inner_iteration/for_csv.sh +3 -0
- data/samples/ja/inner_iteration/template.html +18 -0
- data/samples/ja/recipe/main.aht +9 -0
- data/samples/ja/recipe/template.html +20 -0
- data/samples/recipe.yaml +34 -0
- data/spec/ad_hoc_template_spec.rb +71 -1
- data/spec/command_line_interface_spec.rb +105 -11
- data/spec/config_manager_spec.rb +142 -0
- data/spec/default_tag_formatter_spec.rb +13 -0
- data/spec/entry_format_generator_spec.rb +160 -17
- data/spec/parser_spec.rb +64 -20
- data/spec/recipe_manager_spec.rb +419 -0
- data/spec/record_reader_spec.rb +122 -1
- data/spec/test_data/en/inner_iteration/data.csv +5 -0
- data/spec/test_data/en/recipe/expected_result.html +32 -0
- data/spec/test_data/en/recipe/main.aht +9 -0
- data/spec/test_data/en/recipe/template.html +20 -0
- data/spec/test_data/ja/inner_iteration/data.csv +5 -0
- data/spec/test_data/ja/recipe/expected_result.html +32 -0
- data/spec/test_data/ja/recipe/main.aht +9 -0
- data/spec/test_data/ja/recipe/template.html +20 -0
- data/spec/test_data/recipe.yaml +34 -0
- metadata +47 -4
@@ -0,0 +1,168 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ad_hoc_template'
|
4
|
+
require 'ad_hoc_template/utils'
|
5
|
+
|
6
|
+
module AdHocTemplate
|
7
|
+
class RecipeManager
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
attr_reader :output_file, :template_encoding, :template
|
11
|
+
attr_reader :records, :recipe
|
12
|
+
|
13
|
+
def self.update_output_files_in_recipe(recipe, force_update=false)
|
14
|
+
recipe_source = open(File.expand_path(recipe)) {|file| file.read }
|
15
|
+
recipes = YAML.load_stream(recipe_source)
|
16
|
+
recipes.each do |recipe|
|
17
|
+
manager = new(recipe)
|
18
|
+
if manager.modified_after_last_output? or force_update
|
19
|
+
manager.update_output_file
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(recipe_source)
|
25
|
+
@default = {}
|
26
|
+
read_recipe(recipe_source)
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_recipe(recipe_source)
|
30
|
+
@recipe = if recipe_source.kind_of? String
|
31
|
+
RecordReader::YAMLReader.read_record(recipe_source)
|
32
|
+
else
|
33
|
+
recipe_source
|
34
|
+
end
|
35
|
+
setup_default!(@recipe)
|
36
|
+
@template_encoding = @default['template_encoding']
|
37
|
+
@output_file = @default['output_file']
|
38
|
+
@recipe
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_records
|
42
|
+
@records = prepare_block_data(@recipe).tap do |main_block|
|
43
|
+
@recipe['blocks'].each do |block_source|
|
44
|
+
block = prepare_block_data(block_source)
|
45
|
+
block.keys.each do |key|
|
46
|
+
main_block[key] ||= block[key]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare_block_data(block)
|
53
|
+
determine_data_format!(block)
|
54
|
+
return {} unless block['data']
|
55
|
+
data_source = read_file(block['data'],
|
56
|
+
block['data_encoding'])
|
57
|
+
data_format = prepare_data_format(block)
|
58
|
+
RecordReader.read_record(data_source, data_format)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_template
|
62
|
+
template_path = File.expand_path(@recipe['template'])
|
63
|
+
template_source = open(template_path,
|
64
|
+
open_mode(@template_encoding)) do |file|
|
65
|
+
file.read
|
66
|
+
end
|
67
|
+
tag_type = @recipe['tag_type'] || :default
|
68
|
+
tag_type = tag_type.to_sym unless tag_type.kind_of? Symbol
|
69
|
+
@template = Parser.parse(template_source, tag_type)
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_output_file
|
73
|
+
@records ||= load_records
|
74
|
+
parse_template
|
75
|
+
content = AdHocTemplate::DataLoader.format(@template, @records)
|
76
|
+
mode = @template_encoding ? "wb:#{@template_encoding}" : 'wb'
|
77
|
+
if @output_file
|
78
|
+
open(File.expand_path(@output_file), mode) {|file| file.print content }
|
79
|
+
else
|
80
|
+
STDOUT.print content
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def modified_after_last_output?
|
85
|
+
return true unless @output_file
|
86
|
+
output_path = File.expand_path(@output_file)
|
87
|
+
return true unless File.exist? output_path
|
88
|
+
output_time = File.mtime(output_path)
|
89
|
+
return true if modified_time(@recipe['template']) >= output_time
|
90
|
+
data_files = @recipe['blocks'].map {|block| block['data'] }
|
91
|
+
data_files.unshift(@recipe['data'])
|
92
|
+
data_files.any? {|data_file| modified_time(data_file) >= output_time }
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def setup_default!(recipe)
|
98
|
+
recipe.each do |key, val|
|
99
|
+
@default[key] = val unless val.kind_of? Array
|
100
|
+
end
|
101
|
+
|
102
|
+
setup_sub_blocks!(recipe)
|
103
|
+
setup_main_label
|
104
|
+
end
|
105
|
+
|
106
|
+
def setup_sub_blocks!(recipe)
|
107
|
+
unless recipe['blocks']
|
108
|
+
recipe['blocks'] = []
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
recipe['blocks'].each do |block|
|
113
|
+
@default.keys.each do |key|
|
114
|
+
block[key] ||= @default[key]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def setup_main_label
|
120
|
+
if data_format = @default['data_format'] and
|
121
|
+
[:csv, :tsv].include? data_format
|
122
|
+
@default['label'] ||= RecordReader::CSVReader::HEADER_POSITION::LEFT
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def determine_data_format!(block)
|
127
|
+
data_format = block['data_format']
|
128
|
+
if not data_format and block['data']
|
129
|
+
data_format = guess_file_format(block['data'])
|
130
|
+
end
|
131
|
+
|
132
|
+
block['data_format'] ||= data_format
|
133
|
+
end
|
134
|
+
|
135
|
+
def read_file(file_name, encoding)
|
136
|
+
open(File.expand_path(file_name),
|
137
|
+
open_mode(encoding)) do |file|
|
138
|
+
file.read
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def open_mode(encoding)
|
143
|
+
encoding ||= Encoding.default_external.names[0]
|
144
|
+
mode = "rb"
|
145
|
+
return mode unless encoding and not encoding.empty?
|
146
|
+
bom = /\AUTF/i =~ encoding ? 'BOM|' : ''
|
147
|
+
mode += ":#{bom}#{encoding}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def prepare_data_format(block)
|
151
|
+
data_format = block['data_format']
|
152
|
+
if not data_format or data_format.empty?
|
153
|
+
data_format = :default
|
154
|
+
end
|
155
|
+
data_format = data_format.to_sym
|
156
|
+
return data_format unless [:csv, :tsv].include? data_format
|
157
|
+
if label = block['label']
|
158
|
+
label = label.sub(/\A#/, '')
|
159
|
+
data_format = { data_format => label }
|
160
|
+
end
|
161
|
+
data_format
|
162
|
+
end
|
163
|
+
|
164
|
+
def modified_time(filename)
|
165
|
+
File.mtime(File.expand_path(filename))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -8,7 +8,7 @@ module AdHocTemplate
|
|
8
8
|
module RecordReader
|
9
9
|
module YAMLReader
|
10
10
|
def self.read_record(yaml_data)
|
11
|
-
YAML.load(yaml_data)
|
11
|
+
RecordReader.convert_values_to_string(YAML.load(yaml_data))
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.dump(config_data)
|
@@ -19,7 +19,7 @@ module AdHocTemplate
|
|
19
19
|
|
20
20
|
module JSONReader
|
21
21
|
def self.read_record(json_data)
|
22
|
-
JSON.parse(json_data)
|
22
|
+
RecordReader.convert_values_to_string(JSON.parse(json_data))
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.dump(config_data)
|
@@ -34,19 +34,21 @@ module AdHocTemplate
|
|
34
34
|
tsv: "\t"
|
35
35
|
}
|
36
36
|
|
37
|
+
module HEADER_POSITION
|
38
|
+
TOP = '__header_top__'
|
39
|
+
LEFT = '__header_left__'
|
40
|
+
end
|
41
|
+
|
37
42
|
class NotSupportedError < StandardError; end
|
38
43
|
|
39
44
|
def self.read_record(csv_data, config={ csv: nil })
|
40
45
|
label, sep = parse_config(config)
|
41
|
-
header, *data =
|
42
|
-
|
43
|
-
if label
|
44
|
-
|
45
|
-
elsif records.length == 1
|
46
|
-
records[0]
|
47
|
-
else
|
48
|
-
records
|
46
|
+
header, *data = csv_to_array(csv_data, sep, label)
|
47
|
+
csv_records = data.map {|row| convert_to_hash(header, row) }
|
48
|
+
if label and label.index('|')
|
49
|
+
return compose_inner_iteration_records(csv_records, label)
|
49
50
|
end
|
51
|
+
compose_record(csv_records, label)
|
50
52
|
end
|
51
53
|
|
52
54
|
def self.dump(config_data, col_sep=COL_SEP[:csv])
|
@@ -84,8 +86,51 @@ module AdHocTemplate
|
|
84
86
|
return label, col_sep
|
85
87
|
end
|
86
88
|
|
89
|
+
def self.csv_to_array(csv_data, col_sep, label)
|
90
|
+
array = CSV.new(csv_data, col_sep: col_sep).to_a
|
91
|
+
if not label or label == HEADER_POSITION::LEFT
|
92
|
+
array = array.transpose
|
93
|
+
end
|
94
|
+
array
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.compose_record(csv_records, label)
|
98
|
+
if label
|
99
|
+
{ '#' + label => csv_records }
|
100
|
+
elsif csv_records.length == 1
|
101
|
+
csv_records[0]
|
102
|
+
else
|
103
|
+
csv_records
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.compose_inner_iteration_records(csv_records, given_label,
|
108
|
+
main_record={})
|
109
|
+
outer_label, inner_label, key = ('#' + given_label).split(/\|/, 3)
|
110
|
+
values = inner_iteration_records(csv_records, key)
|
111
|
+
labels = inner_iteration_labels(outer_label, inner_label, values.keys)
|
112
|
+
unless main_record[outer_label]
|
113
|
+
main_record[outer_label] = values.keys.map {|k| { key => k } }
|
114
|
+
end
|
115
|
+
values.keys.each {|k| main_record[labels[k]] = values[k] }
|
116
|
+
main_record
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.inner_iteration_records(csv_records, key)
|
120
|
+
values = Hash.new {|h, k| h[k] = [] }
|
121
|
+
csv_records.each {|record| values[record[key]].push record }
|
122
|
+
values
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.inner_iteration_labels(outer_label, inner_label, keys)
|
126
|
+
labels = keys.inject({}) do |h, key|
|
127
|
+
h[key] = [outer_label, inner_label, key].join('|')
|
128
|
+
h
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
87
132
|
def self.csv_compatible_format?(data)
|
88
|
-
iteration_blocks_count = data.values.
|
133
|
+
iteration_blocks_count = data.values.count {|v| v.kind_of? Array }
|
89
134
|
iteration_blocks_count == 0 or (iteration_blocks_count == 1 && data.size == 1)
|
90
135
|
end
|
91
136
|
|
@@ -111,6 +156,10 @@ module AdHocTemplate
|
|
111
156
|
end
|
112
157
|
|
113
158
|
private_class_method :convert_to_hash, :parse_config
|
159
|
+
private_class_method :csv_to_array, :compose_record
|
160
|
+
private_class_method :compose_inner_iteration_records
|
161
|
+
private_class_method :inner_iteration_records
|
162
|
+
private_class_method :inner_iteration_labels
|
114
163
|
private_class_method :csv_compatible_format?, :hashes_to_arrays
|
115
164
|
private_class_method :find_sub_records, :array_to_csv
|
116
165
|
end
|
@@ -134,6 +183,7 @@ module AdHocTemplate
|
|
134
183
|
ITERATION_HEAD = /\A\/\/\/@#/o
|
135
184
|
EMPTY_LINE = /\A#{LINE_END_STR}\Z/o
|
136
185
|
ITERATION_MARK = /\A#/o
|
186
|
+
COMMENT_HEAD = /\A\/\/\/\//
|
137
187
|
READERS_RE = {
|
138
188
|
key_value: SEPARATOR,
|
139
189
|
iteration: ITERATION_HEAD,
|
@@ -274,6 +324,7 @@ module AdHocTemplate
|
|
274
324
|
end
|
275
325
|
|
276
326
|
def read(line)
|
327
|
+
return if COMMENT_HEAD =~ line
|
277
328
|
key, value = line.split(SEPARATOR, 2)
|
278
329
|
@stack.current_record[key] = value.chomp
|
279
330
|
end
|
@@ -294,7 +345,7 @@ module AdHocTemplate
|
|
294
345
|
case line
|
295
346
|
when BLOCK_HEAD
|
296
347
|
setup_new_block(line, String.new)
|
297
|
-
when EMPTY_LINE
|
348
|
+
when EMPTY_LINE, COMMENT_HEAD
|
298
349
|
block_value << line unless block_value.empty?
|
299
350
|
else
|
300
351
|
block_value << line
|
@@ -405,10 +456,16 @@ module AdHocTemplate
|
|
405
456
|
end
|
406
457
|
|
407
458
|
def self.parse_if_necessary(source)
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
459
|
+
source.kind_of?(String) ? read_record(source) : source
|
460
|
+
end
|
461
|
+
|
462
|
+
def self.convert_values_to_string(data)
|
463
|
+
data.each do |k, v|
|
464
|
+
if v.kind_of? Array
|
465
|
+
v.each {|sub_rec| convert_values_to_string(sub_rec) }
|
466
|
+
elsif v and not v.kind_of? String
|
467
|
+
data[k] = v.to_s
|
468
|
+
end
|
412
469
|
end
|
413
470
|
end
|
414
471
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module AdHocTemplate
|
4
|
+
module Utils
|
5
|
+
FILE_EXTENTIONS = {
|
6
|
+
/\.ya?ml\Z/i => :yaml,
|
7
|
+
/\.json\Z/i => :json,
|
8
|
+
/\.csv\Z/i => :csv,
|
9
|
+
/\.tsv\Z/i => :tsv,
|
10
|
+
}
|
11
|
+
|
12
|
+
def guess_file_format(filename)
|
13
|
+
if_any_regex_match(FILE_EXTENTIONS, filename) do |ext_re, format|
|
14
|
+
return format
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def if_any_regex_match(regex_table, target, failure_message=nil)
|
19
|
+
regex_table.each do |re, paired_value|
|
20
|
+
if re =~ target
|
21
|
+
yield re, paired_value
|
22
|
+
return
|
23
|
+
end
|
24
|
+
end
|
25
|
+
STDERR.puts failure_message if failure_message
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/ad_hoc_template.rb
CHANGED
@@ -4,9 +4,28 @@ require "ad_hoc_template/record_reader"
|
|
4
4
|
require "ad_hoc_template/default_tag_formatter"
|
5
5
|
require "ad_hoc_template/pseudohiki_formatter"
|
6
6
|
require "ad_hoc_template/entry_format_generator"
|
7
|
+
require "ad_hoc_template/config_manager"
|
7
8
|
|
8
9
|
module AdHocTemplate
|
9
10
|
class DataLoader
|
11
|
+
class InnerLabel
|
12
|
+
attr_reader :inner_label
|
13
|
+
|
14
|
+
def self.labels(inner_labels, cur_label)
|
15
|
+
inner_labels.map {|label| new(label, cur_label) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(inner_label, cur_label)
|
19
|
+
@inner_label = inner_label
|
20
|
+
@label, @key = inner_label.sub(/\A#/, ''.freeze).split(/\|/, 2)
|
21
|
+
@cur_label = cur_label
|
22
|
+
end
|
23
|
+
|
24
|
+
def full_label(record)
|
25
|
+
[@cur_label, @label, record[@key]].join('|')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
10
29
|
def self.format(template, record, tag_formatter=DefaultTagFormatter.new)
|
11
30
|
if record.kind_of? Array
|
12
31
|
return format_multi_records(template, record, tag_formatter)
|
@@ -26,45 +45,42 @@ module AdHocTemplate
|
|
26
45
|
@tag_formatter = tag_formatter
|
27
46
|
end
|
28
47
|
|
29
|
-
def visit(tree)
|
48
|
+
def visit(tree, memo)
|
30
49
|
case tree
|
31
|
-
when Parser::
|
32
|
-
format_iteration_tag(tree)
|
33
|
-
when Parser::
|
50
|
+
when Parser::IterationNode
|
51
|
+
format_iteration_tag(tree, memo)
|
52
|
+
when Parser::FallbackNode
|
34
53
|
''.freeze
|
35
|
-
when Parser::
|
36
|
-
|
54
|
+
when Parser::ValueNode
|
55
|
+
format_value_tag(tree, memo)
|
37
56
|
when Parser::Leaf
|
38
57
|
tree.join
|
39
58
|
else
|
40
|
-
tree.map {|node| node.accept(self) }
|
59
|
+
tree.map {|node| node.accept(self, memo) }
|
41
60
|
end
|
42
61
|
end
|
43
62
|
|
44
|
-
def format_iteration_tag(
|
45
|
-
|
46
|
-
tag_node = cast(tag_node)
|
47
|
-
fallback_nodes = tag_node.select {|sub_node| sub_node.kind_of? Parser::FallbackTagNode }
|
63
|
+
def format_iteration_tag(iteration_tag_node, memo)
|
64
|
+
tag_node = cast(iteration_tag_node)
|
48
65
|
|
49
|
-
|
66
|
+
prepare_sub_records(iteration_tag_node).map do |record|
|
50
67
|
if tag_node.contains_any_value_assigned_tag_node?(record)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
format_fallback_tags(fallback_nodes, record)
|
68
|
+
visit_with_sub_record(tag_node, record, memo)
|
69
|
+
elsif fallback_nodes = select_fallback_nodes(tag_node)
|
70
|
+
format_fallback_tags(fallback_nodes, record, memo)
|
55
71
|
else
|
56
72
|
"".freeze
|
57
73
|
end
|
58
74
|
end
|
59
75
|
end
|
60
76
|
|
61
|
-
def
|
62
|
-
leafs = tag_node.map {|leaf| leaf.accept(self) }
|
77
|
+
def format_value_tag(tag_node, memo)
|
78
|
+
leafs = tag_node.map {|leaf| leaf.accept(self, memo) }
|
63
79
|
@tag_formatter.format(tag_node.type, leafs.join.strip, @record)
|
64
80
|
end
|
65
81
|
|
66
|
-
def format(tree)
|
67
|
-
tree.accept(self).join
|
82
|
+
def format(tree, memo=nil)
|
83
|
+
tree.accept(self, memo).join
|
68
84
|
end
|
69
85
|
|
70
86
|
private
|
@@ -73,12 +89,44 @@ module AdHocTemplate
|
|
73
89
|
node_type.new.concat(node.clone)
|
74
90
|
end
|
75
91
|
|
76
|
-
def
|
92
|
+
def prepare_sub_records(tag_node)
|
93
|
+
cur_label = tag_node.type
|
94
|
+
sub_records = @record[cur_label]||[@record]
|
95
|
+
return sub_records unless cur_label
|
96
|
+
inner_labels = tag_node.inner_iteration_tag_labels
|
97
|
+
return sub_records unless inner_labels
|
98
|
+
inner_labels = InnerLabel.labels(inner_labels, cur_label)
|
99
|
+
sub_records.map do |record|
|
100
|
+
prepare_inner_iteration_records(record, inner_labels)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def prepare_inner_iteration_records(record, inner_labels)
|
105
|
+
new_record = nil
|
106
|
+
inner_labels.each do |label|
|
107
|
+
if inner_data = @record[label.full_label(record)]
|
108
|
+
new_record ||= record.dup
|
109
|
+
new_record[label.inner_label] = inner_data
|
110
|
+
end
|
111
|
+
end
|
112
|
+
new_record || record
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_with_sub_record(tag_node, record, memo)
|
116
|
+
data_loader = AdHocTemplate::DataLoader.new(record, @tag_formatter)
|
117
|
+
tag_node.map {|leaf| leaf.accept(data_loader, memo) }.join
|
118
|
+
end
|
119
|
+
|
120
|
+
def select_fallback_nodes(tag_node)
|
121
|
+
tags = tag_node.select {|sub_node| sub_node.kind_of? Parser::FallbackNode }
|
122
|
+
tags.empty? ? nil : tags
|
123
|
+
end
|
124
|
+
|
125
|
+
def format_fallback_tags(fallback_nodes, record, memo)
|
77
126
|
data_loader = AdHocTemplate::DataLoader.new(record, @tag_formatter)
|
78
|
-
fallback_nodes
|
79
|
-
|
80
|
-
|
81
|
-
node.contains_any_value_tag? ? node.accept(data_loader) : node.join
|
127
|
+
fallback_nodes.map do |fallback_node|
|
128
|
+
node = cast(fallback_node, Parser::IterationNode)
|
129
|
+
node.contains_any_value_tag? ? node.accept(data_loader, memo) : node.join
|
82
130
|
end
|
83
131
|
end
|
84
132
|
end
|
@@ -89,4 +137,8 @@ module AdHocTemplate
|
|
89
137
|
record = RecordReader.read_record(record_data, data_format)
|
90
138
|
DataLoader.format(tree, record, tag_formatter)
|
91
139
|
end
|
140
|
+
|
141
|
+
def self.local_settings(&config_block)
|
142
|
+
ConfigManager.configure(&config_block)
|
143
|
+
end
|
92
144
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
///@#authors
|
2
|
+
|
3
|
+
name: Albert Camus
|
4
|
+
|
5
|
+
name: Marcel Aymé
|
6
|
+
|
7
|
+
///@#authors|works|Albert Camus
|
8
|
+
|
9
|
+
title: L'Étranger
|
10
|
+
year: 1942
|
11
|
+
|
12
|
+
title: La Peste
|
13
|
+
year: 1947
|
14
|
+
|
15
|
+
///@#authors|works|Marcel Aymé
|
16
|
+
|
17
|
+
title: Le Passe-muraille
|
18
|
+
year: 1943
|
19
|
+
|
20
|
+
title: Les Contes du chat perché
|
21
|
+
year: 1934-1946
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
"#authors":
|
3
|
+
- name: Albert Camus
|
4
|
+
- name: Marcel Aymé
|
5
|
+
"#authors|works|Albert Camus":
|
6
|
+
- title: L'Étranger
|
7
|
+
year: 1942
|
8
|
+
- title: La Peste
|
9
|
+
year: 1947
|
10
|
+
"#authors|works|Marcel Aymé":
|
11
|
+
- title: Le Passe-muraille
|
12
|
+
year: 1943
|
13
|
+
- title: Les Contes du chat perché
|
14
|
+
year: 1934-1946
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
"#authors":
|
3
|
+
- name: Albert Camus
|
4
|
+
"#works|name":
|
5
|
+
- title: L'Étranger
|
6
|
+
year: 1942
|
7
|
+
- title: La Peste
|
8
|
+
year: 1947
|
9
|
+
- name: Marcel Aymé
|
10
|
+
"#works|name":
|
11
|
+
- title: Le Passe-muraille
|
12
|
+
year: 1943
|
13
|
+
- title: Les Contes du chat perché
|
14
|
+
year: 1934-1946
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<h1>Authors</h1>
|
2
|
+
|
3
|
+
<!--%iterate%-->authors:
|
4
|
+
<h2><!--%h name %--></h2>
|
5
|
+
|
6
|
+
<table summary="List of <!--%h name %-->'s famous works with publication year">
|
7
|
+
<caption>Famous works</caption>
|
8
|
+
<thead>
|
9
|
+
<tr><th scope="col">Title</th><th scope="col">Publication Year</th></tr>
|
10
|
+
</thead>
|
11
|
+
<tbody>
|
12
|
+
<!--%iterate%-->works|name:
|
13
|
+
<tr><td><!--%h title %--></td><td><!--%h year %--></td></tr>
|
14
|
+
<!--%/iterate%-->
|
15
|
+
</tbody>
|
16
|
+
</table>
|
17
|
+
|
18
|
+
<!--%/iterate%-->
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h1>Famous authors of <!--%h country %--> literature</h1>
|
2
|
+
|
3
|
+
<!--%iterate%-->authors:
|
4
|
+
<h2><!--%h name %--></h2>
|
5
|
+
|
6
|
+
<p>Born in <!--%h birth_year %--></p>
|
7
|
+
|
8
|
+
<table summary="List of <!--%h name %-->'s famous works with publication year">
|
9
|
+
<caption>Famous works</caption>
|
10
|
+
<thead>
|
11
|
+
<tr><th scope="col">Title</th><th scope="col">Publication Year</th></tr>
|
12
|
+
</thead>
|
13
|
+
<tbody>
|
14
|
+
<!--%iterate%-->works|name:
|
15
|
+
<tr><td><!--%h title %--></td><td><!--%h year %--></td></tr>
|
16
|
+
<!--%/iterate%-->
|
17
|
+
</tbody>
|
18
|
+
</table>
|
19
|
+
|
20
|
+
<!--%/iterate%-->
|
@@ -0,0 +1,21 @@
|
|
1
|
+
///@#authors
|
2
|
+
|
3
|
+
name: アルベール・カミユ
|
4
|
+
|
5
|
+
name: マルセル・エイメ
|
6
|
+
|
7
|
+
///@#authors|works|アルベール・カミユ
|
8
|
+
|
9
|
+
title: 異邦人
|
10
|
+
year: 1942
|
11
|
+
|
12
|
+
title: ペスト
|
13
|
+
year: 1947
|
14
|
+
|
15
|
+
///@#authors|works|マルセル・エイメ
|
16
|
+
|
17
|
+
title: 壁抜け男
|
18
|
+
year: 1943
|
19
|
+
|
20
|
+
title: おにごっこ物語
|
21
|
+
year: 1934-1946
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
"#authors":
|
3
|
+
- name: "アルベール・カミユ"
|
4
|
+
- name: "マルセル・エイメ"
|
5
|
+
"#authors|works|アルベール・カミユ":
|
6
|
+
- title: "異邦人"
|
7
|
+
year: '1942'
|
8
|
+
- title: "ペスト"
|
9
|
+
year: '1947'
|
10
|
+
"#authors|works|マルセル・エイメ":
|
11
|
+
- title: "壁抜け男"
|
12
|
+
year: '1943'
|
13
|
+
- title: "おにごっこ物語"
|
14
|
+
year: 1934-1946
|