Class: OpenAPISourceTools::Ordering::KeyPath

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/openapi/sourcetools/order.rb

Overview

Allowed keys starting from root, with pattern, any or multiple any key. Initialized with the path to the order array.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent_keys, regexp_prefix) ⇒ KeyPath

the parent_keys is processed to an array of regular expressions and :multi. A '' is eventually replaced with regular expression that accepts anything. A '**' is replaced by :multi that matches at least one key during match. A string starting with regexp_prefix is changed to a regular expression. Other string is turned to anchored regular expression that matches only the string. The order of '' and '**' can be modified to minimize occurrences of :multi.



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
# File 'lib/openapi/sourcetools/order.rb', line 41

def initialize(parent_keys, regexp_prefix)
  @multiple = false
  @items = []
  parent_keys.each do |key|
    if key == '*'
      if @items.last == :multi
        # :multi, :one is same as :one, :multi as at least 2 arbitrary items.
        @items.pop
        @items.push(:one, :multi)
      else
        @items.push(:one)
      end
    elsif key == '**'
      @multiple = true
      if @items.last == :multi
        # :multi, :multi is same as :one, :multi as at least 2 arbitrary items.
        @items.pop
        @items.push(:one, :multi)
      else
        @items.push(:multi)
      end
    elsif key.start_with?(regexp_prefix)
      @items.push(Regexp.new(key[regexp_prefix.size..]))
    else
      @items.push(Regexp.new("^#{Regexp.escape(key)}$"))
    end
  end
  @items.each_with_index do |item, idx|
    @items[idx] = ANY if item == :one
  end
  @first_multiple = @items.index(:multi) || @items.size
end

Instance Attribute Details

#first_multipleObject (readonly)

Returns the value of attribute first_multiple.



33
34
35
# File 'lib/openapi/sourcetools/order.rb', line 33

def first_multiple
  @first_multiple
end

#itemsObject (readonly)

Returns the value of attribute items.



33
34
35
# File 'lib/openapi/sourcetools/order.rb', line 33

def items
  @items
end

#multipleObject (readonly)

Returns the value of attribute multiple.



33
34
35
# File 'lib/openapi/sourcetools/order.rb', line 33

def multiple
  @multiple
end

Instance Method Details

#<=>(other) ⇒ Object



127
128
129
130
131
132
133
# File 'lib/openapi/sourcetools/order.rb', line 127

def <=>(other)
  # The purpose is to place longest "exact" items to the end.
  # Hence items.size, and the more multiple, the earlier.
  d = @items.size <=> other.items.size
  return d unless d.zero?
  -@items.count(:multi) <=> -other.items.count(:multi)
end

#match?(key_path) ⇒ Boolean

Performs base checks whether match is possible and if so, obtains indexes od matching patterns and starts search using those.

Returns:

  • (Boolean)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/openapi/sourcetools/order.rb', line 106

def match?(key_path)
  return true if @items.empty? && key_path.empty?
  slack = key_path.size - @items.size
  return false if slack.negative? # Path too short.
  return false if slack.positive? && !@multiple # Path too long.
  # Last key must match last item.
  return false unless @items.last == :multi || @items.last.match?(key_path.last)
  # First key must match first item.
  return false unless @items.first == :multi || @items.first.match?(key_path.first)
  return true if key_path.size <= 2 # Tested both already.
  # Form arrays of possible match indexes for search. Omit first and last.
  index_arrays = []
  (1...(key_path.size - 1)).each do |idx|
    key = key_path[idx]
    idxs = matching_item_indexes(key, idx)
    return false if idxs.empty?
    index_arrays.push(idxs)
  end
  search(index_arrays, 0, @items.first == :multi ? 0 : 1)
end

#matching_item_indexes(key, skip_non_multis) ⇒ Object

Finds indexes of regular expressions that match key, skipping given number.



75
76
77
78
79
80
81
82
# File 'lib/openapi/sourcetools/order.rb', line 75

def matching_item_indexes(key, skip_non_multis)
  indexes = []
  ([skip_non_multis, @first_multiple].min...@items.size).each do |idx|
    item = @items[idx]
    indexes.push(idx) if item == :multi || item.match?(key)
  end
  indexes
end

#search(index_arrays, array_index, position_index) ⇒ Object

Given possible matches, uses position_index to check for matches relevant to the current situation and checks recursively if a solution ending to the desired point can be found.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/openapi/sourcetools/order.rb', line 87

def search(index_arrays, array_index, position_index)
  if array_index == index_arrays.size
    # Last key is known to match last item, so if that were tested, it would pass.
    return position_index == @items.size - 1
  end
  if @items[position_index] == :multi
    # Multi absorbs current key and releases to next position.
    return true if search(index_arrays, array_index + 1, position_index + 1)
    # Multi absorbs current key and stays for more.
    search(index_arrays, array_index + 1, position_index)
  else
    # Only current item to match with.
    return false if index_arrays[array_index].index(position_index).nil?
    search(index_arrays, array_index + 1, position_index + 1)
  end
end