ad_hoc_template 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77dedc981af5ec62410d30504f9d41bb01fb78b3
4
- data.tar.gz: 090b330043f0d60d385e613dc50a617d85fe6dba
3
+ metadata.gz: a41e80d33bd430c264ac2704bf7850baf3bcebc7
4
+ data.tar.gz: f10b0463f915e33987e5ac04e4928737eba11fc2
5
5
  SHA512:
6
- metadata.gz: e96dd5a5f56ded725d6fd1d5c8a02ec5855dbed793200da55e4594ac9f832a441383963eee8f2f46b61dcd21002295264f5f3ddbae891ee6f3ebba2ee41c0c0b
7
- data.tar.gz: 46682a5ae8479d813c14b5019ba7150d4db4a1e5199f2a1252ed1a971faec73ed9ba832327761998ccc631925a95047ea4efdc22b30b26d53c06fc85792a23e9
6
+ metadata.gz: 0b423fd73c6d59c11975fadddccdf56697a15f9784510277b23c4011c66b0d235b111285201ca24659c72b1456294990939a172bfd8998e86a441c57e16ac426
7
+ data.tar.gz: bfb7de0f42fd16cba6e2c75ee0e2d7764c86cd2e4d135e38f1cc05cc271b9dc45681a351ddc84d68175d85c6019df23c99b94202efc650f981ce2b1063a56f10
data/README.md CHANGED
@@ -27,7 +27,7 @@ The following is an example of template format:
27
27
  ```
28
28
  a test string with tags (<%= key1 %> and <%= key2 %>) in it
29
29
 
30
- <%#iteration_block
30
+ <%#iteration_block:
31
31
  the value of sub_key1 is <%= sub_key1 %>
32
32
  the value of sub_key2 is <%= sub_key2 %>
33
33
 
@@ -3,6 +3,7 @@ require "ad_hoc_template/parser"
3
3
  require "ad_hoc_template/record_reader"
4
4
  require "ad_hoc_template/default_tag_formatter"
5
5
  require "ad_hoc_template/pseudohiki_formatter"
6
+ require "ad_hoc_template/entry_format_generator"
6
7
 
7
8
  module AdHocTemplate
8
9
  class DataLoader
@@ -29,6 +30,8 @@ module AdHocTemplate
29
30
  case tree
30
31
  when Parser::IterationTagNode
31
32
  format_iteration_tag(tree)
33
+ when Parser::FallbackTagNode
34
+ ''.freeze
32
35
  when Parser::TagNode
33
36
  format_tag(tree)
34
37
  when Parser::Leaf
@@ -40,12 +43,15 @@ module AdHocTemplate
40
43
 
41
44
  def format_iteration_tag(tag_node)
42
45
  sub_records = @record[tag_node.type]||[@record]
43
- tag_node = Parser::TagNode.new.concat(tag_node.clone)
46
+ tag_node = cast(tag_node)
47
+ fallback_nodes = tag_node.select {|sub_node| sub_node.kind_of? Parser::FallbackTagNode }
44
48
 
45
49
  sub_records.map do |record|
46
50
  if tag_node.contains_any_value_assigned_tag_node?(record)
47
51
  data_loader = AdHocTemplate::DataLoader.new(record, @tag_formatter)
48
52
  tag_node.map {|leaf| leaf.accept(data_loader) }.join
53
+ elsif not fallback_nodes.empty?
54
+ format_fallback_tags(fallback_nodes, record)
49
55
  else
50
56
  "".freeze
51
57
  end
@@ -60,10 +66,25 @@ module AdHocTemplate
60
66
  def format(tree)
61
67
  tree.accept(self).join
62
68
  end
69
+
70
+ private
71
+
72
+ def cast(node, node_type=Parser::TagNode)
73
+ node_type.new.concat(node.clone)
74
+ end
75
+
76
+ def format_fallback_tags(fallback_nodes, record)
77
+ data_loader = AdHocTemplate::DataLoader.new(record, @tag_formatter)
78
+ fallback_nodes = fallback_nodes.map {|node| cast(node, Parser::IterationTagNode) }
79
+ fallback_nodes = cast(fallback_nodes)
80
+ fallback_nodes.map do |node|
81
+ node.contains_any_value_tag? ? node.accept(data_loader) : node.join
82
+ end
83
+ end
63
84
  end
64
85
 
65
- def self.convert(record_data, template, tag_type=:default, data_format=:default,
66
- tag_formatter=DefaultTagFormatter.new)
86
+ def self.render(record_data, template, tag_type=:default, data_format=:default,
87
+ tag_formatter=DefaultTagFormatter.new)
67
88
  tree = Parser.parse(template, tag_type)
68
89
  record = RecordReader.read_record(record_data, data_format)
69
90
  DataLoader.format(tree, record, tag_formatter)
@@ -6,6 +6,7 @@ require 'optparse_plus'
6
6
  module AdHocTemplate
7
7
  class CommandLineInterface
8
8
  attr_accessor :output_filename, :template_data, :record_data, :tag_type, :data_format
9
+ attr_writer :output_empty_entry
9
10
 
10
11
  TAG_RE_TO_TYPE = {
11
12
  /\Ad(efault)?/i => :default,
@@ -13,6 +14,7 @@ module AdHocTemplate
13
14
  /\As(quare_brackets)?/i => :square_brackets,
14
15
  /\Axml_like1/i => :xml_like1,
15
16
  /\Axml_like2/i => :xml_like2,
17
+ /\Axml_comment_like/i => :xml_comment_like,
16
18
  }
17
19
 
18
20
  FORMAT_RE_TO_FORMAT = {
@@ -47,6 +49,7 @@ module AdHocTemplate
47
49
  opt.on(:tag_type) {|given_type| choose_tag_type(given_type) }
48
50
  opt.on(:data_format) {|data_format| choose_data_format(data_format) }
49
51
  opt.on(:tag_config) {|tag_config_yaml| register_user_defined_tag_type(tag_config_yaml) }
52
+ opt.on(:entry_format) {|entry_format| @output_empty_entry = true }
50
53
 
51
54
  opt.parse!
52
55
  end
@@ -68,11 +71,16 @@ module AdHocTemplate
68
71
  @record_data = record ? File.read(record) : ARGF.read
69
72
  end
70
73
 
71
- def convert
72
- AdHocTemplate.convert(@record_data, @template_data, @tag_type,
74
+ def render
75
+ AdHocTemplate.render(@record_data, @template_data, @tag_type,
73
76
  @data_format, @tag_formatter)
74
77
  end
75
78
 
79
+ def generate_entry_format
80
+ tree = Parser.parse(@template_data, @tag_type)
81
+ EntryFormatGenerator.extract_labels(tree, @data_format)
82
+ end
83
+
76
84
  def open_output
77
85
  if @output_filename
78
86
  open(@output_filename, "wb") do |out|
@@ -86,22 +94,26 @@ module AdHocTemplate
86
94
  def execute
87
95
  parse_command_line_options
88
96
  read_input_files
89
- open_output {|out| out.print convert }
97
+ output = @output_empty_entry ? generate_entry_format : render
98
+ open_output {|out| out.print output }
90
99
  end
91
100
 
92
101
  private
93
102
 
94
103
  def choose_tag_type(given_type)
95
- if_any_regex_match(TAG_RE_TO_TYPE, given_type,
96
- "The given type is not found. The default tag is chosen.") do |re, tag_type|
104
+ err_msg = "The given type is not found. The default tag is chosen."
105
+
106
+ if_any_regex_match(TAG_RE_TO_TYPE, given_type, err_msg) do |re, tag_type|
97
107
  @tag_type = tag_type
98
108
  end
99
109
  end
100
110
 
101
111
  def choose_data_format(data_format)
102
- if_any_regex_match(FORMAT_RE_TO_FORMAT, data_format,
103
- "The given format is not found. The default format is chosen.") do |re, format|
104
- @data_format = [:csv, :tsv].include?(format) ? make_csv_option(data_format, format) : format
112
+ err_msg = "The given format is not found. The default format is chosen."
113
+ format_part, label_part = data_format.split(/:/, 2)
114
+
115
+ if_any_regex_match(FORMAT_RE_TO_FORMAT, format_part, err_msg) do |re, format|
116
+ @data_format = [:csv, :tsv].include?(format) ? make_csv_option(label_part, format) : format
105
117
  end
106
118
  end
107
119
 
@@ -110,9 +122,9 @@ module AdHocTemplate
110
122
  @tag_type = Parser.register_user_defined_tag_type(config)
111
123
  end
112
124
 
113
- def make_csv_option(data_format, format)
114
- iteration_label = data_format.sub(/\A(csv|tsv):?/, "")
115
- iteration_label.empty? ? format : { format => iteration_label }
125
+ def make_csv_option(iteration_label, format)
126
+ return format if iteration_label.nil? or iteration_label.empty?
127
+ { format => iteration_label }
116
128
  end
117
129
 
118
130
  def guess_file_format(filename)
@@ -151,3 +163,7 @@ tag_config:
151
163
  short: "-u [tag_config.yaml]"
152
164
  long: "--user-defined-tag [=tag_config.yaml]"
153
165
  description: "Configure a user-defined tag. The configuration file is in YAML format."
166
+ entry_format:
167
+ short: "-e"
168
+ long: "--entry-format"
169
+ description: "Extract tag labels from a template and generate an empty data entry format"
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module AdHocTemplate
4
+ module EntryFormatGenerator
5
+ class LabelChecker
6
+ attr_reader :labels
7
+ def initialize
8
+ @labels = {}
9
+ end
10
+
11
+ def visit(tree)
12
+ case tree
13
+ when Parser::IterationTagNode, Parser::FallbackTagNode
14
+ visit_iteration_tag_node(tree)
15
+ when Parser::TagNode
16
+ @labels[tree.join.strip] = nil
17
+ when Parser::Node
18
+ tree.each {|node| node.accept(self) }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def visit_iteration_tag_node(tree)
25
+ if iteration_label = tree.type
26
+ sub_checker = self.class.new
27
+ @labels[iteration_label] = [sub_checker.labels]
28
+ tree.each { |node| node.accept(sub_checker) }
29
+ else
30
+ tree.each {|node| node.accept(self) }
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.extract_labels(parsed_template, target_format=nil)
36
+ labels = extract_labels_as_ruby_objects(parsed_template)
37
+
38
+ RecordReader.dump(labels, target_format)
39
+ end
40
+
41
+ def self.extract_labels_as_ruby_objects(parsed_template)
42
+ label_checker = LabelChecker.new
43
+ parsed_template.accept(label_checker)
44
+ label_checker.labels
45
+ end
46
+
47
+ private_class_method :extract_labels_as_ruby_objects
48
+ end
49
+ end
@@ -4,30 +4,19 @@ require "pseudohiki/inlineparser"
4
4
  require "htmlelement"
5
5
 
6
6
  module AdHocTemplate
7
+ LINE_END_RE = /(?:\r?\n|\r)/
8
+ LINE_END_STR = '(?:\r?\n|\r)'
9
+
7
10
  class Parser < TreeStack
8
11
  class TagNode < Parser::Node
9
12
  attr_reader :type
10
13
 
11
14
  def push(node=TreeStack::Node.new)
12
- node[0] = assign_type(node[0]) if self.empty?
15
+ first_leaf = node[0]
16
+ node[0] = assign_value_to_type(first_leaf) if empty? and first_leaf
13
17
  super
14
18
  end
15
19
 
16
- def assign_type(first_leaf)
17
- if not first_leaf.kind_of? String or /\A\s/ =~ first_leaf
18
- return first_leaf.sub(/\A(?:\r?\n|\r)/, "")
19
- end
20
- @type, first_leaf_content = split_by_newline_or_spaces(first_leaf)
21
- @type = '#'.freeze + @type if kind_of? IterationTagNode
22
- first_leaf_content||""
23
- end
24
-
25
- def split_by_newline_or_spaces(first_leaf)
26
- sep = /\A\S*(?:\r?\n|\r)/ =~ first_leaf ? /(?:\r?\n|\r)/ : /\s+/
27
- first_leaf.split(sep, 2)
28
- end
29
- private :assign_type, :split_by_newline_or_spaces
30
-
31
20
  def contains_any_value_assigned_tag_node?(record)
32
21
  self.select {|n| n.kind_of?(TagNode) }.each do |node|
33
22
  if node.kind_of? IterationTagNode
@@ -40,8 +29,33 @@ module AdHocTemplate
40
29
  false
41
30
  end
42
31
 
32
+ def contains_any_value_tag?
33
+ select {|n| n.kind_of?(TagNode) }.each do |node|
34
+ case node
35
+ when IterationTagNode, FallbackTagNode
36
+ return node.contains_any_value_tag?
37
+ when TagNode
38
+ return true
39
+ end
40
+ end
41
+ false
42
+ end
43
+
43
44
  private
44
45
 
46
+ def assign_value_to_type(first_leaf)
47
+ if first_leaf.kind_of? String and /\A\s/ =~ first_leaf
48
+ return first_leaf.sub(/\A#{LINE_END_STR}/, "")
49
+ end
50
+ @type, first_leaf_content = split_by_newline_or_spaces(first_leaf)
51
+ first_leaf_content||""
52
+ end
53
+
54
+ def split_by_newline_or_spaces(first_leaf)
55
+ sep = /\A\S*#{LINE_END_STR}/ =~ first_leaf ? LINE_END_RE : /\s+/
56
+ first_leaf.split(sep, 2)
57
+ end
58
+
45
59
  def empty_sub_records?(record, node)
46
60
  sub_records = record[node.type]
47
61
  return true if sub_records.nil? or sub_records.empty?
@@ -53,18 +67,40 @@ module AdHocTemplate
53
67
  def any_value_assigned_to_iteration_tag?(tag_node, record)
54
68
  if tag_node.type
55
69
  not empty_sub_records?(record, tag_node)
70
+ elsif tag_node.kind_of? FallbackTagNode
71
+ false
56
72
  else
57
73
  tag_node.contains_any_value_assigned_tag_node?(record)
58
74
  end
59
75
  end
60
76
  end
61
77
 
62
- class IterationTagNode < TagNode; end
78
+ class IterationTagNode < TagNode
79
+ def assign_value_to_type(first_leaf)
80
+ return first_leaf unless first_leaf.kind_of? String
81
+
82
+ if /\A[^\s:]*:\s/ =~ first_leaf
83
+ @type, remaining_part = first_leaf.split(/:\s/, 2)
84
+ @type = @type.empty? ? nil : '#'.freeze + @type
85
+ return remaining_part
86
+ end
87
+
88
+ first_leaf.sub(/\A#{LINE_END_STR}/, '')
89
+ end
90
+ end
91
+
92
+ class FallbackTagNode < TagNode
93
+ def assign_value_to_type(first_leaf)
94
+ return first_leaf unless first_leaf.kind_of? String
95
+ first_leaf.sub(/\A#{LINE_END_STR}/, '')
96
+ end
97
+ end
98
+
63
99
  class Leaf < Parser::Leaf; end
64
100
 
65
101
  class TagType
66
102
  attr_reader :head, :tail, :token_pat, :remove_iteration_indent
67
- attr_reader :iteration_start, :iteration_end
103
+ attr_reader :head_of, :tail_of
68
104
  @types = {}
69
105
 
70
106
  def self.[](tag_name)
@@ -72,72 +108,111 @@ module AdHocTemplate
72
108
  end
73
109
 
74
110
  def self.register(tag_name=:default, tag=["<%", "%>"], iteration_tag=["<%#", "#%>"],
75
- remove_iteration_indent=false)
76
- @types[tag_name] = new(tag, iteration_tag, remove_iteration_indent)
111
+ fallback_tag=["<%*", "*%>"], remove_iteration_indent=false)
112
+ @types[tag_name] = new(tag, iteration_tag, fallback_tag, remove_iteration_indent)
77
113
  end
78
114
 
79
- def initialize(tag, iteration_tag, remove_iteration_indent)
80
- assign_type(tag, iteration_tag)
115
+ def initialize(tag, iteration_tag, fallback_tag, remove_iteration_indent)
116
+ assign_type(tag, iteration_tag, fallback_tag)
81
117
  @token_pat = PseudoHiki.compile_token_pat(@head.keys, @tail.keys)
82
118
  @remove_iteration_indent = remove_iteration_indent
83
119
  end
84
120
 
85
- def assign_type(tag, iteration_tag)
86
- @iteration_start, @iteration_end = iteration_tag
87
- @head, @tail = {}, {}
88
- [
121
+ def assign_type(tag, iteration_tag, fallback_tag)
122
+ node_tag_pairs = [
89
123
  [TagNode, tag],
90
- [IterationTagNode, iteration_tag]
91
- ].each do |node_type, head_tail|
92
- head, tail = head_tail
93
- @head[head] = node_type
94
- @tail[tail] = node_type
124
+ [IterationTagNode, iteration_tag],
125
+ [FallbackTagNode, fallback_tag]
126
+ ]
127
+
128
+ setup_attributes(node_tag_pairs)
129
+ end
130
+
131
+ private
132
+
133
+ def setup_attributes(node_tag_pairs)
134
+ @head, @tail, @head_of, @tail_of = {}, {}, {}, {}
135
+
136
+ node_tag_pairs.each do |node, tag|
137
+ head, tail = tag
138
+ @head[head] = node
139
+ @tail[tail] = node
140
+ @head_of[node] = head
141
+ @tail_of[node] = tail
95
142
  end
96
143
  end
97
144
 
98
145
  register
99
- register(:square_brackets, ["[[", "]]"], ["[[#", "#]]"])
100
- register(:curly_brackets, ["{{", "}}"], ["{{#", "#}}"])
101
- register(:xml_like1, ["<!--%", "%-->"], ["<iterate>", "</iterate>"], true)
102
- register(:xml_like2, ["<fill>", "</fill>"], ["<iterate>", "</iterate>"], true)
103
- register(:xml_comment_like, ["<!--%", "%-->"], ["<!--%iterate%-->", "<!--%/iterate%-->"], true)
146
+ register(:square_brackets, ["[[", "]]"], ["[[#", "#]]"], ["[[*", "*]]"])
147
+ register(:curly_brackets, ["{{", "}}"], ["{{#", "#}}"], ["{{*", "*}}"])
148
+ register(:xml_like1, ["<!--%", "%-->"], ["<iterate>", "</iterate>"], ["<fallback>", "</fallback>"], true)
149
+ register(:xml_like2, ["<fill>", "</fill>"], ["<iterate>", "</iterate>"], ["<fallback>", "</fallback>"], true)
150
+ register(:xml_comment_like, ["<!--%", "%-->"], ["<!--%iterate%-->", "<!--%/iterate%-->"], ["<!--%fallback%-->", "<!--%/fallback%-->"], true)
104
151
  end
105
152
 
106
153
  class UserDefinedTagTypeConfigError < StandardError; end
107
154
 
108
155
  def self.parse(str, tag_name=:default)
109
- if TagType[tag_name].remove_iteration_indent
110
- str = remove_indent_before_iteration_tags(str, TagType[tag_name])
111
- end
156
+ str = remove_indents_and_newlines_if_necessary(str, tag_name)
112
157
  new(str, TagType[tag_name]).parse.tree
113
158
  end
114
159
 
115
- def self.remove_indent_before_iteration_tags(template_source, tag_type)
116
- start_tag, end_tag = [
117
- tag_type.iteration_start,
118
- tag_type.iteration_end
119
- ].map {|tag| Regexp.escape(tag) }
120
- template_source.gsub(/^([ \t]+#{start_tag}\S*(?:\r?\n|\r))/) {|s| s.lstrip }
121
- .gsub(/^([ \t]+#{end_tag}(?:\r?\n|\r))/) {|s| s.lstrip }
122
- end
123
-
124
160
  def self.register_user_defined_tag_type(config_source)
125
161
  config = YAML.load(config_source)
126
- %w(tag_name tag iteration_tag).each do |item|
162
+ %w(tag_name tag iteration_tag fallback_tag).each do |item|
127
163
  config[item] || raise(UserDefinedTagTypeConfigError,
128
164
  "\"#{item}\" should be defined.")
129
165
  end
130
166
  TagType.register(registered_tag_name = config["tag_name"].to_sym,
131
167
  config["tag"],
132
168
  config["iteration_tag"],
169
+ config["fallback_tag"],
133
170
  config["remove_indent"] || false)
134
171
  registered_tag_name
135
172
  end
136
173
 
137
- def initialize(str, tag)
174
+ def self.remove_indents_and_newlines_if_necessary(str, tag_name)
175
+ node_types = [IterationTagNode, FallbackTagNode]
176
+ tag_type = TagType[tag_name]
177
+ if TagType[tag_name].remove_iteration_indent
178
+ str = remove_indent_before_iteration_tags(str, tag_type)
179
+ str = remove_indent_before_fallback_tags(str, tag_type)
180
+ end
181
+ remove_trailing_newline_of_end_tags(node_types, str, tag_type)
182
+ end
183
+
184
+ def self.remove_indent_before_iteration_tags(template_source, tag_type)
185
+ start_tag, end_tag = [
186
+ tag_type.head_of[IterationTagNode],
187
+ tag_type.tail_of[IterationTagNode],
188
+ ].map {|tag| Regexp.escape(tag) }
189
+ template_source.gsub(/^([ \t]+#{start_tag}\S*#{LINE_END_STR})/) {|s| s.lstrip }
190
+ .gsub(/^([ \t]+#{end_tag}#{LINE_END_STR})/) {|s| s.lstrip }
191
+ end
192
+
193
+ def self.remove_indent_before_fallback_tags(template_source, tag_type)
194
+ tag_re_str = [
195
+ tag_type.head_of[FallbackTagNode],
196
+ tag_type.tail_of[FallbackTagNode],
197
+ ].map {|tag| Regexp.escape(tag) }.join('|')
198
+ template_source.gsub(/^([ \t]+(?:#{tag_re_str})#{LINE_END_STR})/) {|s| s.lstrip }
199
+ end
200
+
201
+ def self.remove_trailing_newline_of_end_tags(node_types, source, tag_type)
202
+ node_types.inject(source) do |s, node_type|
203
+ end_tag = tag_type.tail_of[node_type]
204
+ s.gsub(/#{Regexp.escape(end_tag)}#{LINE_END_STR}/, end_tag)
205
+ end
206
+ end
207
+
208
+ private_class_method(:remove_indents_and_newlines_if_necessary,
209
+ :remove_indent_before_iteration_tags,
210
+ :remove_indent_before_fallback_tags,
211
+ :remove_trailing_newline_of_end_tags)
212
+
213
+ def initialize(source, tag)
138
214
  @tag = tag
139
- str = remove_trailing_newline_of_iteration_end_tag(str, @tag.iteration_end)
140
- @tokens = PseudoHiki.split_into_tokens(str, @tag.token_pat)
215
+ @tokens = PseudoHiki.split_into_tokens(source, @tag.token_pat)
141
216
  super()
142
217
  end
143
218
 
@@ -150,11 +225,5 @@ module AdHocTemplate
150
225
 
151
226
  self
152
227
  end
153
-
154
- private
155
-
156
- def remove_trailing_newline_of_iteration_end_tag(str, iteration_end_tag)
157
- str.gsub(/#{Regexp.escape(iteration_end_tag)}(?:\r?\n|\r)/, iteration_end_tag)
158
- end
159
228
  end
160
229
  end