ad_hoc_template 0.2.0 → 0.3.0

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