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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/ad_hoc_template.gemspec +1 -1
  3. data/lib/ad_hoc_template/command_line_interface.rb +53 -26
  4. data/lib/ad_hoc_template/config_manager.rb +129 -0
  5. data/lib/ad_hoc_template/default_tag_formatter.rb +6 -1
  6. data/lib/ad_hoc_template/entry_format_generator.rb +83 -12
  7. data/lib/ad_hoc_template/parser.rb +64 -59
  8. data/lib/ad_hoc_template/recipe_manager.rb +168 -0
  9. data/lib/ad_hoc_template/record_reader.rb +73 -16
  10. data/lib/ad_hoc_template/utils.rb +29 -0
  11. data/lib/ad_hoc_template/version.rb +1 -1
  12. data/lib/ad_hoc_template.rb +77 -25
  13. data/samples/en/inner_iteration/data.aht +21 -0
  14. data/samples/en/inner_iteration/data.csv +5 -0
  15. data/samples/en/inner_iteration/data.yaml +14 -0
  16. data/samples/en/inner_iteration/data2.yaml +14 -0
  17. data/samples/en/inner_iteration/for_csv.sh +3 -0
  18. data/samples/en/inner_iteration/template.html +18 -0
  19. data/samples/en/recipe/main.aht +9 -0
  20. data/samples/en/recipe/template.html +20 -0
  21. data/samples/for_recipe.sh +3 -0
  22. data/samples/ja/inner_iteration/data.aht +21 -0
  23. data/samples/ja/inner_iteration/data.csv +5 -0
  24. data/samples/ja/inner_iteration/data.yaml +14 -0
  25. data/samples/ja/inner_iteration/data2.yaml +14 -0
  26. data/samples/ja/inner_iteration/for_csv.sh +3 -0
  27. data/samples/ja/inner_iteration/template.html +18 -0
  28. data/samples/ja/recipe/main.aht +9 -0
  29. data/samples/ja/recipe/template.html +20 -0
  30. data/samples/recipe.yaml +34 -0
  31. data/spec/ad_hoc_template_spec.rb +71 -1
  32. data/spec/command_line_interface_spec.rb +105 -11
  33. data/spec/config_manager_spec.rb +142 -0
  34. data/spec/default_tag_formatter_spec.rb +13 -0
  35. data/spec/entry_format_generator_spec.rb +160 -17
  36. data/spec/parser_spec.rb +64 -20
  37. data/spec/recipe_manager_spec.rb +419 -0
  38. data/spec/record_reader_spec.rb +122 -1
  39. data/spec/test_data/en/inner_iteration/data.csv +5 -0
  40. data/spec/test_data/en/recipe/expected_result.html +32 -0
  41. data/spec/test_data/en/recipe/main.aht +9 -0
  42. data/spec/test_data/en/recipe/template.html +20 -0
  43. data/spec/test_data/ja/inner_iteration/data.csv +5 -0
  44. data/spec/test_data/ja/recipe/expected_result.html +32 -0
  45. data/spec/test_data/ja/recipe/main.aht +9 -0
  46. data/spec/test_data/ja/recipe/template.html +20 -0
  47. data/spec/test_data/recipe.yaml +34 -0
  48. metadata +47 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a41e80d33bd430c264ac2704bf7850baf3bcebc7
4
- data.tar.gz: f10b0463f915e33987e5ac04e4928737eba11fc2
3
+ metadata.gz: ddd8ddbfdc477efb70980b40c3e16848108df9ea
4
+ data.tar.gz: fb506b375a6781451f00f32c6a88b58a2fab210c
5
5
  SHA512:
6
- metadata.gz: 0b423fd73c6d59c11975fadddccdf56697a15f9784510277b23c4011c66b0d235b111285201ca24659c72b1456294990939a172bfd8998e86a441c57e16ac426
7
- data.tar.gz: bfb7de0f42fd16cba6e2c75ee0e2d7764c86cd2e4d135e38f1cc05cc271b9dc45681a351ddc84d68175d85c6019df23c99b94202efc650f981ce2b1063a56f10
6
+ metadata.gz: c5e5ef55188915bd037bf8b20e67b5a3226f54aa1037345510dc5a10319f4cf59711a7627c35c52c716f5633abe3170b9b6f09b56c04ec5b71a90b47c56746d9
7
+ data.tar.gz: 9246fcb22fabbab24a8866ef612474121d5acc42cb4fa65a333e717aac26cc4dc7a4ebdd9d708c99eb0cdf8294fc207d98c0a43a5c69e2a2459650700dd0598d
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
- spec.add_runtime_dependency "pseudohikiparser", "0.0.5.develop"
20
+ spec.add_runtime_dependency "pseudohikiparser", "0.0.6.develop"
21
21
  spec.add_runtime_dependency "optparse_plus"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -2,9 +2,15 @@
2
2
 
3
3
  require 'ad_hoc_template'
4
4
  require 'optparse_plus'
5
+ require 'ad_hoc_template/config_manager'
6
+ require 'ad_hoc_template/recipe_manager'
7
+ require 'ad_hoc_template/utils'
8
+
9
+ AdHocTemplate::ConfigManager.require_local_settings
5
10
 
6
11
  module AdHocTemplate
7
12
  class CommandLineInterface
13
+ include Utils
8
14
  attr_accessor :output_filename, :template_data, :record_data, :tag_type, :data_format
9
15
  attr_writer :output_empty_entry
10
16
 
@@ -25,18 +31,12 @@ module AdHocTemplate
25
31
  /\At(sv)?/i => :tsv,
26
32
  }
27
33
 
28
- FILE_EXTENTIONS = {
29
- /\.ya?ml\Z/i => :yaml,
30
- /\.json\Z/i => :json,
31
- /\.csv\Z/i => :csv,
32
- /\.tsv\Z/i => :tsv,
33
- }
34
-
35
34
  def initialize
36
35
  @tag_formatter = AdHocTemplate::DefaultTagFormatter.new
37
36
  @output_filename = nil
38
37
  @tag_type = :default
39
38
  @data_format = nil
39
+ @force_update = false
40
40
  end
41
41
 
42
42
  def parse_command_line_options
@@ -50,6 +50,10 @@ module AdHocTemplate
50
50
  opt.on(:data_format) {|data_format| choose_data_format(data_format) }
51
51
  opt.on(:tag_config) {|tag_config_yaml| register_user_defined_tag_type(tag_config_yaml) }
52
52
  opt.on(:entry_format) {|entry_format| @output_empty_entry = true }
53
+ opt.on(:init_local_settings) { init_local_settings }
54
+ opt.on(:recipe_template) { @output_recipe_template = true }
55
+ opt.on(:cooking_recipe) {|recipe_yaml| @recipe_yaml = recipe_yaml }
56
+ opt.on(:force_update) { @force_update = true }
53
57
 
54
58
  opt.parse!
55
59
  end
@@ -78,7 +82,26 @@ module AdHocTemplate
78
82
 
79
83
  def generate_entry_format
80
84
  tree = Parser.parse(@template_data, @tag_type)
81
- EntryFormatGenerator.extract_labels(tree, @data_format)
85
+ EntryFormatGenerator.extract_form(tree, @data_format)
86
+ end
87
+
88
+ def init_local_settings
89
+ AdHocTemplate::ConfigManager.init_local_settings
90
+ config_dir = File.expand_path(AdHocTemplate::ConfigManager::LOCAL_SETTINGS_DIR)
91
+ puts "Please edit configuration files created in #{config_dir}"
92
+ exit
93
+ end
94
+
95
+ def generate_recipe_template(templates)
96
+ encoding = Encoding.default_external.names[0]
97
+ AdHocTemplate::EntryFormatGenerator.
98
+ extract_recipes_from_template_files(templates,
99
+ @tag_type)
100
+ end
101
+
102
+ def update_output_files_in_recipe(recipe)
103
+ AdHocTemplate::RecipeManager.update_output_files_in_recipe(recipe,
104
+ @force_update)
82
105
  end
83
106
 
84
107
  def open_output
@@ -93,8 +116,15 @@ module AdHocTemplate
93
116
 
94
117
  def execute
95
118
  parse_command_line_options
119
+ return update_output_files_in_recipe(@recipe_yaml) if @recipe_yaml
96
120
  read_input_files
97
- output = @output_empty_entry ? generate_entry_format : render
121
+ output = if @output_empty_entry
122
+ generate_entry_format
123
+ elsif @output_recipe_template
124
+ generate_recipe_template(ARGV)
125
+ else
126
+ render
127
+ end
98
128
  open_output {|out| out.print output }
99
129
  end
100
130
 
@@ -126,23 +156,6 @@ module AdHocTemplate
126
156
  return format if iteration_label.nil? or iteration_label.empty?
127
157
  { format => iteration_label }
128
158
  end
129
-
130
- def guess_file_format(filename)
131
- if_any_regex_match(FILE_EXTENTIONS, filename) do |ext_re, format|
132
- return format
133
- end
134
- end
135
-
136
- def if_any_regex_match(regex_table, target, failure_message=nil)
137
- regex_table.each do |re, paired_value|
138
- if re =~ target
139
- yield re, paired_value
140
- return
141
- end
142
- end
143
- STDERR.puts failure_message if failure_message
144
- nil
145
- end
146
159
  end
147
160
  end
148
161
 
@@ -167,3 +180,17 @@ entry_format:
167
180
  short: "-e"
168
181
  long: "--entry-format"
169
182
  description: "Extract tag labels from a template and generate an empty data entry format"
183
+ init_local_settings:
184
+ long: "--init-local-settings"
185
+ description: "Generate configuration files for local settings"
186
+ recipe_template:
187
+ short: "-R"
188
+ long: "--recipe-template"
189
+ description: "Generate recipe entries for template files given on the command line"
190
+ cooking_recipe:
191
+ short: "-c [recipe_yaml]"
192
+ long: "--cooking-recipe [=recipe_yaml]"
193
+ description: "Update output files specified in the recipe file"
194
+ force_update:
195
+ long: "--force-update"
196
+ description: "Update output files in recipe, even when they are newer than template/data files"
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+
5
+ module AdHocTemplate
6
+ class ConfigManager
7
+ LOCAL_SETTINGS_DIR = '~/.ad_hoc_template/'
8
+ SETTINGS_FILE_NAME = 'settings.rb'
9
+ TAG_DEF_FILE_NAME = 'user_defined_tag.yaml'
10
+ LOCAL_SETTINGS_FILE = File.join(LOCAL_SETTINGS_DIR,
11
+ SETTINGS_FILE_NAME)
12
+
13
+ def self.require_local_settings
14
+ settings_file = File.expand_path(LOCAL_SETTINGS_FILE)
15
+ if File.exist? settings_file
16
+ require settings_file
17
+ end
18
+ end
19
+
20
+ def self.configure(&config_block)
21
+ module_eval(&config_block)
22
+ end
23
+
24
+ def self.user_defined_tag(def_yaml_path)
25
+ yaml_source = File.read(expand_path(def_yaml_path))
26
+ AdHocTemplate::Parser.register_user_defined_tag_type(yaml_source)
27
+ end
28
+
29
+ def self.assign_format_label(format_label, &func)
30
+ AdHocTemplate::DefaultTagFormatter.assign_format(format_label, &func)
31
+ end
32
+
33
+ def self.define_label_format(&block)
34
+ AdHocTemplate::DefaultTagFormatter.module_eval(&block)
35
+ end
36
+
37
+ def self.init_local_settings
38
+ config_dir = File.expand_path(LOCAL_SETTINGS_DIR)
39
+ settings_rb = File.expand_path(LOCAL_SETTINGS_FILE)
40
+ custom_tag_yaml = File.join(config_dir, TAG_DEF_FILE_NAME)
41
+ FileUtils.mkdir(config_dir) unless File.exist? config_dir
42
+ create_unless_exist(settings_rb, @local_settings_template)
43
+ create_unless_exist(custom_tag_yaml, @custom_tag_template)
44
+ end
45
+
46
+ def self.expand_path(path)
47
+ unless /\A[\.\/]/ =~ path
48
+ path = File.join(LOCAL_SETTINGS_DIR, path)
49
+ end
50
+ File.expand_path(path)
51
+ end
52
+
53
+ def self.create_unless_exist(path, content)
54
+ return if File.exist? path
55
+ open(path, "w") {|file| file.print content }
56
+ end
57
+
58
+ private_class_method :expand_path
59
+ private_class_method :create_unless_exist
60
+
61
+ @local_settings_template = <<SETTING_TEMPLATE
62
+ AdHocTemplate.local_settings do
63
+ ##
64
+ # If you want to define your own tags for templates,
65
+ # prepare a definition file in YAML format:
66
+ #
67
+ # An example of definition in YAML -----
68
+ # ---
69
+ # tag_name: :default
70
+ # tag: ["<%", "%>"]
71
+ # iteration_tag: ["<%#", "#%>"]
72
+ # fallback_tag: ["<%*", "*%>"]
73
+ # remove_indent: false
74
+ #
75
+ # And read the file by "user_defined_tag NAME_OF_YAML_FILE":
76
+ #
77
+ user_defined_tag 'user_defined_tag.yaml'
78
+
79
+ ##
80
+ # If you want to define a custom label format,
81
+ # there are two ways to realize that.
82
+ # Suppose you have data in YAML and want to
83
+ # present the value of date field in French style:
84
+ #
85
+ # YAML data -----
86
+ # ---
87
+ # date: 2016/09/28
88
+ # other_field: ...
89
+ # ...
90
+ #
91
+ # Template -----
92
+ #
93
+ # Today = <%= date %>
94
+ # Aujourd'hui = <%fd date %>
95
+ #
96
+ # Expected result -----
97
+ #
98
+ # Today = 2016/09/28
99
+ # Aujourd'hui = 28/09/2016
100
+ #
101
+ # # The first way -----
102
+ #
103
+ # define_label_format do
104
+ # def french_date(var, record)
105
+ # record[var].split(/\\//).reverse.join('/')
106
+ # end
107
+ #
108
+ # assign_format french_date: 'fd'
109
+ # end
110
+ #
111
+ # # The second way -----
112
+ #
113
+ # assign_format_label('fd') do |var, record|
114
+ # record[var].split(/\\//).reverse.join('/')
115
+ # end
116
+ #
117
+ end
118
+ SETTING_TEMPLATE
119
+
120
+ @custom_tag_template =<<TAG_TEMPLATE
121
+ ---
122
+ tag_name: :default
123
+ tag: ["<%", "%>"]
124
+ iteration_tag: ["<%#", "#%>"]
125
+ fallback_tag: ["<%*", "*%>"]
126
+ remove_indent: false
127
+ TAG_TEMPLATE
128
+ end
129
+ end
@@ -8,7 +8,12 @@ module AdHocTemplate
8
8
  }
9
9
 
10
10
  def self.assign_format(format_label, &func)
11
- FUNCTION_TABLE[format_label] = func
11
+ if format_label.kind_of? Hash and not func
12
+ func_name, label = format_label.to_a.flatten
13
+ FUNCTION_TABLE[label] = func_name
14
+ else
15
+ FUNCTION_TABLE[format_label] = func
16
+ end
12
17
  end
13
18
 
14
19
  def find_function(format_label)
@@ -8,42 +8,113 @@ module AdHocTemplate
8
8
  @labels = {}
9
9
  end
10
10
 
11
- def visit(tree)
11
+ def visit(tree, memo)
12
12
  case tree
13
- when Parser::IterationTagNode, Parser::FallbackTagNode
14
- visit_iteration_tag_node(tree)
13
+ when Parser::IterationNode, Parser::FallbackNode
14
+ visit_iteration_tag_node(tree, memo)
15
15
  when Parser::TagNode
16
16
  @labels[tree.join.strip] = nil
17
17
  when Parser::Node
18
- tree.each {|node| node.accept(self) }
18
+ tree.each {|node| node.accept(self, memo) }
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
23
 
24
- def visit_iteration_tag_node(tree)
24
+ def visit_iteration_tag_node(tree, memo)
25
25
  if iteration_label = tree.type
26
26
  sub_checker = self.class.new
27
27
  @labels[iteration_label] = [sub_checker.labels]
28
- tree.each { |node| node.accept(sub_checker) }
28
+ tree.each { |node| node.accept(sub_checker, memo) }
29
29
  else
30
- tree.each {|node| node.accept(self) }
30
+ tree.each {|node| node.accept(self, memo) }
31
31
  end
32
32
  end
33
33
  end
34
34
 
35
- def self.extract_labels(parsed_template, target_format=nil)
36
- labels = extract_labels_as_ruby_objects(parsed_template)
35
+ def self.extract_form(parsed_template, target_format=nil, memo=nil)
36
+ labels = extract_form_as_ruby_objects(parsed_template, memo)
37
+ labels = pull_up_inner_iterations(labels)
37
38
 
38
39
  RecordReader.dump(labels, target_format)
39
40
  end
40
41
 
41
- def self.extract_labels_as_ruby_objects(parsed_template)
42
+ def self.extract_recipes_from_template_files(template_paths, tag_type=:default,
43
+ encoding=Encoding.default_external.names[0])
44
+ recipes = template_paths.map do |path|
45
+ full_path = File.expand_path(path)
46
+ template_source = open(full_path) {|file| file.read }
47
+ extract_recipe(template_source, path, tag_type, encoding)
48
+ end
49
+
50
+ recipes.join
51
+ end
52
+
53
+ def self.extract_recipe(template_source, template_path,
54
+ tag_type=:default, encoding='UTF-8')
55
+ recipe = recipe_entry(template_path, tag_type, encoding)
56
+ parsed_template = Parser.parse(template_source, tag_type)
57
+ extract_iteration_labels(parsed_template).each do |label|
58
+ recipe['blocks'].push recipe_block_entry(label) if label.start_with? '#'
59
+ end
60
+
61
+ RecordReader.dump(recipe, :yaml)
62
+ end
63
+
64
+ def self.extract_iteration_labels(parsed_template, memo=nil)
65
+ labels = extract_form_as_ruby_objects(parsed_template, memo)
66
+ pull_up_inner_iterations(labels).keys
67
+ end
68
+
69
+ def self.extract_form_as_ruby_objects(parsed_template, memo)
42
70
  label_checker = LabelChecker.new
43
- parsed_template.accept(label_checker)
71
+ parsed_template.accept(label_checker, memo)
44
72
  label_checker.labels
45
73
  end
46
74
 
47
- private_class_method :extract_labels_as_ruby_objects
75
+ def self.pull_up_inner_iterations(labels)
76
+ each_iteration_label(labels) do |label|
77
+ labels[label].each do |record|
78
+ each_iteration_label(record) do |key|
79
+ inner_label = [label, key.sub(/\A#/, '')].join('|')
80
+ labels[inner_label] = record.delete(key)
81
+ end
82
+ end
83
+ end
84
+ labels
85
+ end
86
+
87
+ def self.each_iteration_label(labels)
88
+ labels.keys.each do |label|
89
+ yield label if labels[label].kind_of? Array
90
+ end
91
+ end
92
+
93
+ def self.recipe_entry(template_path, tag_type, encoding)
94
+ recipe = {
95
+ 'template' => template_path,
96
+ 'tag_type' => tag_type,
97
+ 'template_encoding' => encoding,
98
+ 'data' => nil,
99
+ 'data_format' => nil,
100
+ 'data_encoding' => nil,
101
+ 'output_file' => nil,
102
+ 'blocks' => []
103
+ }
104
+ end
105
+
106
+ def self.recipe_block_entry(label)
107
+ {
108
+ 'label' => label,
109
+ 'data' => nil,
110
+ 'data_format' => nil,
111
+ 'data_encoding' => nil,
112
+ }
113
+ end
114
+
115
+ private_class_method :extract_form_as_ruby_objects
116
+ private_class_method :pull_up_inner_iterations
117
+ private_class_method :each_iteration_label
118
+ private_class_method :recipe_entry, :recipe_block_entry
48
119
  end
49
120
  end
@@ -18,31 +18,35 @@ module AdHocTemplate
18
18
  end
19
19
 
20
20
  def contains_any_value_assigned_tag_node?(record)
21
- self.select {|n| n.kind_of?(TagNode) }.each do |node|
22
- if node.kind_of? IterationTagNode
23
- return true if any_value_assigned_to_iteration_tag?(node, record)
24
- else
25
- val = record[node.join.strip]
26
- return true if val and not val.empty?
27
- end
21
+ each_tag_node do |node|
22
+ return true if node.contains_any_value_assigned_tag_node?(record)
28
23
  end
29
- false
30
24
  end
31
25
 
32
26
  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
27
+ each_tag_node {|node| return true if node.contains_any_value_tag? }
28
+ end
29
+
30
+ def inner_iteration_tag_labels
31
+ names = []
32
+ each_tag_node do |node|
33
+ next unless node.kind_of? IterationNode
34
+ names.push node.type if node.type
35
+ if inner_names = node.inner_iteration_tag_labels
36
+ names.concat inner_names
39
37
  end
40
38
  end
41
- false
39
+
40
+ names unless names.empty?
42
41
  end
43
42
 
44
43
  private
45
44
 
45
+ def each_tag_node
46
+ each {|node| yield node if node.kind_of?(TagNode) }
47
+ false
48
+ end
49
+
46
50
  def assign_value_to_type(first_leaf)
47
51
  if first_leaf.kind_of? String and /\A\s/ =~ first_leaf
48
52
  return first_leaf.sub(/\A#{LINE_END_STR}/, "")
@@ -55,45 +59,60 @@ module AdHocTemplate
55
59
  sep = /\A\S*#{LINE_END_STR}/ =~ first_leaf ? LINE_END_RE : /\s+/
56
60
  first_leaf.split(sep, 2)
57
61
  end
58
-
59
- def empty_sub_records?(record, node)
60
- sub_records = record[node.type]
61
- return true if sub_records.nil? or sub_records.empty?
62
- sub_records.each do |rec|
63
- return false if rec.values.find {|val| val and not val.empty? }
64
- end
65
- end
66
-
67
- def any_value_assigned_to_iteration_tag?(tag_node, record)
68
- if tag_node.type
69
- not empty_sub_records?(record, tag_node)
70
- elsif tag_node.kind_of? FallbackTagNode
71
- false
72
- else
73
- tag_node.contains_any_value_assigned_tag_node?(record)
74
- end
75
- end
76
62
  end
77
63
 
78
- class IterationTagNode < TagNode
64
+ class IterationNode < TagNode
79
65
  def assign_value_to_type(first_leaf)
80
66
  return first_leaf unless first_leaf.kind_of? String
81
67
 
82
68
  if /\A[^\s:]*:\s/ =~ first_leaf
83
- @type, remaining_part = first_leaf.split(/:\s/, 2)
69
+ @type, remaining_part = first_leaf.split(/:(?:#{LINE_END_STR}|\s)/, 2)
84
70
  @type = @type.empty? ? nil : '#'.freeze + @type
85
71
  return remaining_part
86
72
  end
87
73
 
88
74
  first_leaf.sub(/\A#{LINE_END_STR}/, '')
89
75
  end
76
+
77
+ def contains_any_value_assigned_tag_node?(record)
78
+ return not_empty_sub_records?(record) if type
79
+ each_tag_node do |node|
80
+ return true if node.contains_any_value_assigned_tag_node?(record)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def not_empty_sub_records?(record)
87
+ sub_records = record[type]
88
+ return false if sub_records.nil? or sub_records.empty?
89
+ sub_records.each do |rec|
90
+ return true if rec.values.any? {|val| val and not val.empty? }
91
+ end
92
+ false
93
+ end
90
94
  end
91
95
 
92
- class FallbackTagNode < TagNode
96
+ class FallbackNode < TagNode
93
97
  def assign_value_to_type(first_leaf)
94
98
  return first_leaf unless first_leaf.kind_of? String
95
99
  first_leaf.sub(/\A#{LINE_END_STR}/, '')
96
100
  end
101
+
102
+ def contains_any_value_assigned_tag_node?(record)
103
+ false
104
+ end
105
+ end
106
+
107
+ class ValueNode < TagNode
108
+ def contains_any_value_assigned_tag_node?(record)
109
+ val = record[self.join.strip]
110
+ val and not val.empty?
111
+ end
112
+
113
+ def contains_any_value_tag?
114
+ true
115
+ end
97
116
  end
98
117
 
99
118
  class Leaf < Parser::Leaf; end
@@ -120,26 +139,12 @@ module AdHocTemplate
120
139
 
121
140
  def assign_type(tag, iteration_tag, fallback_tag)
122
141
  node_tag_pairs = [
123
- [TagNode, tag],
124
- [IterationTagNode, iteration_tag],
125
- [FallbackTagNode, fallback_tag]
142
+ [ValueNode, *tag],
143
+ [IterationNode, *iteration_tag],
144
+ [FallbackNode, *fallback_tag]
126
145
  ]
127
146
 
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
142
- end
147
+ @head, @tail, @head_of, @tail_of = PseudoHiki.associate_nodes_with_tags(node_tag_pairs)
143
148
  end
144
149
 
145
150
  register
@@ -172,7 +177,7 @@ module AdHocTemplate
172
177
  end
173
178
 
174
179
  def self.remove_indents_and_newlines_if_necessary(str, tag_name)
175
- node_types = [IterationTagNode, FallbackTagNode]
180
+ node_types = [IterationNode, FallbackNode]
176
181
  tag_type = TagType[tag_name]
177
182
  if TagType[tag_name].remove_iteration_indent
178
183
  str = remove_indent_before_iteration_tags(str, tag_type)
@@ -183,8 +188,8 @@ module AdHocTemplate
183
188
 
184
189
  def self.remove_indent_before_iteration_tags(template_source, tag_type)
185
190
  start_tag, end_tag = [
186
- tag_type.head_of[IterationTagNode],
187
- tag_type.tail_of[IterationTagNode],
191
+ tag_type.head_of[IterationNode],
192
+ tag_type.tail_of[IterationNode],
188
193
  ].map {|tag| Regexp.escape(tag) }
189
194
  template_source.gsub(/^([ \t]+#{start_tag}\S*#{LINE_END_STR})/) {|s| s.lstrip }
190
195
  .gsub(/^([ \t]+#{end_tag}#{LINE_END_STR})/) {|s| s.lstrip }
@@ -192,8 +197,8 @@ module AdHocTemplate
192
197
 
193
198
  def self.remove_indent_before_fallback_tags(template_source, tag_type)
194
199
  tag_re_str = [
195
- tag_type.head_of[FallbackTagNode],
196
- tag_type.tail_of[FallbackTagNode],
200
+ tag_type.head_of[FallbackNode],
201
+ tag_type.tail_of[FallbackNode],
197
202
  ].map {|tag| Regexp.escape(tag) }.join('|')
198
203
  template_source.gsub(/^([ \t]+(?:#{tag_re_str})#{LINE_END_STR})/) {|s| s.lstrip }
199
204
  end