ad_hoc_template 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|