File: expander.rb

package info (click to toggle)
ruby-mustermann19 0.4.3%2Bgit20160621-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 756 kB
  • ctags: 445
  • sloc: ruby: 7,197; makefile: 3
file content (144 lines) | stat: -rw-r--r-- 4,331 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'mustermann/ast/translator'
require 'mustermann/ast/compiler'

module Mustermann
  module AST
    # Looks at an AST, remembers the important bits of information to do an
    # ultra fast expansion.
    #
    # @!visibility private
    class Expander < Translator
      raises ExpandError

      translate Array do |*args|
        inject(t.pattern) do |pattern, element|
          t.add_to(pattern, t(element, *args))
        end
      end

      translate :capture do |options = {}|
        t.for_capture(node, options)
      end

      translate :named_splat, :splat do
        t.pattern + t.for_capture(node)
      end

      translate :expression do
        t(payload, allow_reserved: operator.allow_reserved)
      end

      translate :root, :group do
        t(payload)
      end

      translate :char do
        t.pattern(t.escape(payload, also_escape: /[\/\?#\&\=%]/).gsub(?%, "%%"))
      end

      translate :separator do
        t.pattern(payload.gsub(?%, "%%"))
      end

      translate :with_look_ahead do
        t.add_to(t(head), t(payload))
      end

      translate :optional do
        nested = t(payload)
        nested += t.pattern unless nested.any? { |n| n.first.empty? }
        nested
      end

      translate :union do
        payload.map { |e| t(e) }.inject(:+)
      end

      # helper method for captures
      # @!visibility private
      def for_capture(node, options = {})
        name = node.name.to_sym
        pattern('%s', name, name => /(?!#{pattern_for(node, options)})./)
      end

      # maps sorted key list to sprintf patterns and filters
      # @!visibility private
      def mappings
        @mappings ||= {}
      end

      # all the known keys
      # @!visibility private
      def keys
        @keys ||= []
      end

      # add a tree for expansion
      # @!visibility private
      def add(ast)
        translate(ast).each do |keys, pattern, filter|
          self.keys.concat(keys).uniq!
          mappings[keys.sort] ||= [keys, pattern, filter]
        end
      end

      # helper method for getting a capture's pattern.
      # @!visibility private
      def pattern_for(node, options = {})
        Compiler.new.decorator_for(node).pattern(options)
      end

      # @see Mustermann::Pattern#expand
      # @!visibility private
      def expand(values)
        values = values.each_with_object({}){ |(key, value), new_hash|
          new_hash[value.instance_of?(Array) ? [key] * value.length : key] = value }
        keys, pattern, filters = mappings.fetch(values.keys.flatten.sort) { error_for(values) }
        filters.each { |key, filter| values[key] &&= escape(values[key], also_escape: filter) }
        pattern % (values[keys] || values.values_at(*keys))
      end

      # @see Mustermann::Pattern#expandable?
      # @!visibility private
      def expandable?(values)
        values = values.keys if values.respond_to? :keys
        values = values.sort if values.respond_to? :sort
        mappings.include? values
      end

      # @see Mustermann::Expander#with_rest
      # @!visibility private
      def expandable_keys(keys)
        mappings.keys.select { |k| (k - keys).empty? }.max_by(&:size) || keys
      end

      # helper method for raising an error for unexpandable values
      # @!visibility private
      def error_for(values)
        expansions = mappings.keys.map(&:inspect).join(" or ")
        raise error_class, "cannot expand with keys %p, possible expansions: %s" % [values.keys.sort, expansions]
      end

      # @see Mustermann::AST::Translator#expand
      # @!visibility private
      def escape(string, *args)
        # URI::Parser is pretty slow, let's not send every string to it, even if it's unnecessary
        string =~ /\A\w*\Z/ ? string : super
      end

      # Turns a sprintf pattern into our secret internal data structure.
      # @!visibility private
      def pattern(string = "", *keys)
        filters = keys.last.is_a?(Hash) ? keys.pop : {}
        [[keys, string, filters]]
      end

      # Creates the product of two of our secret internal data structures.
      # @!visibility private
      def add_to(list, result)
        list << [[], ""] if list.empty?
        list.inject([]) { |l, (k1, p1, f1)| l + result.map { |k2, p2, f2| [k1+k2, p1+p2, f1.merge(f2)] } }
      end
    end
  end
end