Class: Path::Matcher
- Inherits:
-
Object
- Object
- Path::Matcher
- Defined in:
- lib/path/matcher.rb
Overview
Path::Matcher is representation of a single node of a relative path used to find values in a data set.
Defined Under Namespace
Modules: ANY_VALUE
Constant Summary collapse
- SUFF_CHARS =
Shortcut characters that require modification before being turned into a matcher.
Regexp.escape "*?"
- PATH_CHARS =
All special path characters.
Regexp.escape("()|") << SUFF_CHARS
- RESC_CHARS =
Path chars that get regexp escaped.
"*?()|/"
- RANGE_MATCHER =
Matcher for Range path item.
%r{^(\-?\d+)(\.{2,3})(\-?\d+)$}
- ILEN_MATCHER =
Matcher for index,length path item.
%r{^(\-?\d+),(\-?\d+)$}
- ANYVAL_MATCHER =
Matcher allowing any value to be matched.
/^(\?*\*+\?*)*$/
- PATH_CHAR_MATCHER =
Matcher to assert if any unescaped special chars are in a path item.
/(^|[^#{Path::RECH}])([#{PATH_CHARS}])/
Instance Attribute Summary collapse
-
#key ⇒ Object
readonly
Returns the value of attribute key.
-
#recursive ⇒ Object
Returns the value of attribute recursive.
-
#regex_opts ⇒ Object
readonly
Returns the value of attribute regex_opts.
-
#value ⇒ Object
readonly
Returns the value of attribute value.
Instance Method Summary collapse
-
#==(other) ⇒ Object
:nodoc:.
-
#each_data_item(data, &block) ⇒ Object
Universal iterator for Hash and Array like objects.
-
#find_in(data, path = nil, &block) ⇒ Object
Finds data with the given key and value matcher, optionally recursive.
-
#initialize(opts = {}) ⇒ Matcher
constructor
New instance of Matcher.
-
#match_node(node, value) ⇒ Object
Check if data key or value is a match for nested data searches.
-
#parse_node(str) ⇒ Object
Decide whether to make path item matcher a regex, range, array, or string.
Constructor Details
#initialize(opts = {}) ⇒ Matcher
New instance of Matcher. Options supported:
- :key
-
String - The path item key to match.
- :value
-
String - The path item value to match.
- :recursive
-
Boolean - Look for path item recursively.
- :regex_opts
-
Fixnum - representing the Regexp options.
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/path/matcher.rb', line 43 def initialize opts={} @regex_opts = opts[:regex_opts] @recursive = !!opts[:recursive] @key = nil @key = parse_node opts[:key] if opts[:key] && !opts[:key].to_s.empty? @value = nil @value = parse_node opts[:value] if opts[:value] && !opts[:value].to_s.empty? end |
Instance Attribute Details
#key ⇒ Object (readonly)
Returns the value of attribute key.
33 34 35 |
# File 'lib/path/matcher.rb', line 33 def key @key end |
#recursive ⇒ Object
Returns the value of attribute recursive.
34 35 36 |
# File 'lib/path/matcher.rb', line 34 def recursive @recursive end |
#regex_opts ⇒ Object (readonly)
Returns the value of attribute regex_opts.
33 34 35 |
# File 'lib/path/matcher.rb', line 33 def regex_opts @regex_opts end |
#value ⇒ Object (readonly)
Returns the value of attribute value.
33 34 35 |
# File 'lib/path/matcher.rb', line 33 def value @value end |
Instance Method Details
#==(other) ⇒ Object
:nodoc:
57 58 59 60 61 62 |
# File 'lib/path/matcher.rb', line 57 def == other # :nodoc: self.class == other.class && @key == other.key && @value == other.value && @regex_opts == other.regex_opts end |
#each_data_item(data, &block) ⇒ Object
Universal iterator for Hash and Array like objects. The data argument must either respond to both :each_with_index and :length, or respond to :has_key? and :each yielding a key/value pair.
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/path/matcher.rb', line 70 def each_data_item data, &block if data.respond_to?(:has_key?) && data.respond_to?(:each) data.each(&block) elsif data.respond_to?(:each_with_index) && data.respond_to?(:length) # We need to iterate through the array this way # in case items in it get deleted. (data.length - 1).downto(0) do |i| block.call i, data[i] end end end |
#find_in(data, path = nil, &block) ⇒ Object
Finds data with the given key and value matcher, optionally recursive. Yields data, key and path Array when block is given. Returns an Array of path arrays.
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 |
# File 'lib/path/matcher.rb', line 89 def find_in data, path=nil, &block return [] unless Array === data || Hash === data paths = [] path ||= Path::Match.new path = Path::Match.new path if path.class == Array each_data_item data do |key, value| c_path = path.dup << key found, kmatch = match_node(@key, key) if @key found, vmatch = match_node(@value, value) if @value && (!@key || found) c_path.append_splat self, key if @recursive if found c_path.matches.concat kmatch.to_a c_path.matches.concat vmatch.to_a f_path = c_path.dup f_path.splat[-1][-1].pop if @key && !f_path.splat.empty? yield data, key, f_path if block_given? paths << f_path end paths.concat find_in(data[key], c_path, &block) if @recursive end paths end |
#match_node(node, value) ⇒ Object
Check if data key or value is a match for nested data searches. Returns an array with a boolean expressing if the value matched the node, and the matches found.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/path/matcher.rb', line 127 def match_node node, value return if ANY_VALUE != node && (Array === value || Hash === value) if node.class == value.class node == value elsif Regexp === node match = node.match value.to_s return false unless match match = match.size > 1 ? match[1..-1] : match.to_a [true, match] elsif Range === node stat = node.include? value.to_i match = [value.to_i] if stat [stat, match] elsif ANY_VALUE == node [true, [value]] else value.to_s == node.to_s end end |
#parse_node(str) ⇒ Object
Decide whether to make path item matcher a regex, range, array, or string.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/path/matcher.rb', line 157 def parse_node str case str when nil, ANYVAL_MATCHER ANY_VALUE when RANGE_MATCHER Range.new $1.to_i, $3.to_i, ($2 == "...") when ILEN_MATCHER Range.new $1.to_i, ($1.to_i + $2.to_i), true when String if @regex_opts || str =~ PATH_CHAR_MATCHER # Remove extra suffix characters str.gsub! %r{(^|[^#{Path::RECH}])(\*+\?*)}, '\1*' str.gsub! %r{(^|[^#{Path::RECH}])\*+}, '\1*' str = Regexp.escape str # Remove escaping from special path characters str.gsub! %r{#{Path::RECH}([#{PATH_CHARS}])}, '\1' str.gsub! %r{#{Path::RECH}([#{RESC_CHARS}])}, '\1' str.gsub! %r{(^|[^#{Path::RECH}])([#{SUFF_CHARS}])}, '\1(.\2)' str.gsub! %r{(^|[^\.#{Path::RECH}])([#{SUFF_CHARS}])}, '\1(.\2)' Regexp.new "\\A(?:#{str})\\Z", @regex_opts else str.gsub %r{#{Path::RECH}([^#{Path::RECH}]|$)}, '\1' end else str end end |