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 +4 -4
- data/README.md +1 -1
- data/lib/ad_hoc_template.rb +24 -3
- data/lib/ad_hoc_template/command_line_interface.rb +27 -11
- data/lib/ad_hoc_template/entry_format_generator.rb +49 -0
- data/lib/ad_hoc_template/parser.rb +127 -58
- data/lib/ad_hoc_template/pseudohiki_formatter.rb +1 -1
- data/lib/ad_hoc_template/record_reader.rb +294 -160
- data/lib/ad_hoc_template/version.rb +1 -1
- data/spec/ad_hoc_template_spec.rb +151 -5
- data/spec/command_line_interface_spec.rb +107 -7
- data/spec/entry_format_generator_spec.rb +163 -0
- data/spec/parser_spec.rb +114 -18
- data/spec/pseudohiki_formatter_spec.rb +2 -2
- data/spec/record_reader_spec.rb +253 -9
- metadata +6 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a41e80d33bd430c264ac2704bf7850baf3bcebc7
         | 
| 4 | 
            +
              data.tar.gz: f10b0463f915e33987e5ac04e4928737eba11fc2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0b423fd73c6d59c11975fadddccdf56697a15f9784510277b23c4011c66b0d235b111285201ca24659c72b1456294990939a172bfd8998e86a441c57e16ac426
         | 
| 7 | 
            +
              data.tar.gz: bfb7de0f42fd16cba6e2c75ee0e2d7764c86cd2e4d135e38f1cc05cc271b9dc45681a351ddc84d68175d85c6019df23c99b94202efc650f981ce2b1063a56f10
         | 
    
        data/README.md
    CHANGED
    
    
    
        data/lib/ad_hoc_template.rb
    CHANGED
    
    | @@ -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 =  | 
| 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. | 
| 66 | 
            -
             | 
| 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  | 
| 72 | 
            -
                  AdHocTemplate. | 
| 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 | 
            -
                   | 
| 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 | 
            -
                   | 
| 96 | 
            -
             | 
| 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 | 
            -
                   | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 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( | 
| 114 | 
            -
                  iteration_label  | 
| 115 | 
            -
                   | 
| 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 | 
            -
                     | 
| 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 | 
| 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 : | 
| 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 | 
            -
                     | 
| 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 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 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 | 
            -
                   | 
| 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  | 
| 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 | 
            -
                   | 
| 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
         |