class CheckPlease::Path

TODO: this class is getting a bit large; maybe split out some of the stuff that uses flags?

Constants

SEPARATOR

Attributes

segments[R]
to_s[R]

Public Class Methods

new(name_or_segments = []) click to toggle source
# File lib/check_please/path.rb, line 17
def initialize(name_or_segments = [])
  case name_or_segments
  when String, Symbol, Numeric, nil
    string = name_or_segments.to_s
    if string =~ %r(//)
      raise InvalidPath, "paths cannot have empty segments"
    end

    names = string.split(SEPARATOR)
    names.shift until names.empty? || names.first =~ /\S/
    segments = PathSegment.reify(names)
  when Array
    segments = PathSegment.reify(name_or_segments)
  else
    raise InvalidPath, "not sure what to do with #{name_or_segments.inspect}"
  end

  @segments = Array(segments)

  @to_s = SEPARATOR + @segments.join(SEPARATOR)
  freeze
rescue InvalidPathSegment => e
  raise InvalidPath, e.message
end
root() click to toggle source
# File lib/check_please/path.rb, line 10
def self.root
  new('/')
end

Public Instance Methods

+(new_basename) click to toggle source
# File lib/check_please/path.rb, line 42
def +(new_basename)
  new_segments = self.segments.dup
  new_segments << new_basename # don't reify here; it'll get done on Path#initialize
  self.class.new(new_segments)
end
==(other) click to toggle source
# File lib/check_please/path.rb, line 48
def ==(other)
  self.to_s == other.to_s
end
ancestors() click to toggle source
# File lib/check_please/path.rb, line 52
def ancestors
  list = []
  p = self
  loop do
    break if p.root?
    p = p.parent
    list.unshift p
  end
  list.reverse
end
basename() click to toggle source
# File lib/check_please/path.rb, line 63
def basename
  segments.last.to_s
end
depth() click to toggle source
# File lib/check_please/path.rb, line 67
def depth
  1 + segments.length
end
excluded?(flags) click to toggle source
# File lib/check_please/path.rb, line 71
def excluded?(flags)
  return false if root? # that would just be silly

  return true if too_deep?(flags)
  return true if explicitly_excluded?(flags)
  return true if implicitly_excluded?(flags)

  false
end
inspect() click to toggle source
# File lib/check_please/path.rb, line 81
def inspect
  "<#{self.class.name} '#{to_s}'>"
end
key_to_match_by(flags) click to toggle source
# File lib/check_please/path.rb, line 85
def key_to_match_by(flags)
  key_exprs = unpack_key_exprs(flags.match_by_key)
  # NOTE: match on parent because if self.to_s == '/foo', MBK '/foo/:id' should return 'id'
  matches = key_exprs.select { |e| e.parent.match?(self) }

  case matches.length
  when 0 ; nil
  when 1 ; matches.first.segments.last.key
  else   ; raise "More than one match_by_key expression for path '#{self}': #{matches.map(&:to_s).inspect}"
  end
end
match?(path_or_string) click to toggle source
# File lib/check_please/path.rb, line 101
def match?(path_or_string)
  # If the strings are literally equal, we're good..
  return true if self == path_or_string

  # Otherwise, compare segments: do we have the same number, and do they all #match?
  other = reify(path_or_string)
  return false if other.depth != self.depth

  seg_pairs = self.segments.zip(other.segments)
  seg_pairs.all? { |a, b| a.match?(b) }
end
match_by_value?(flags) click to toggle source
# File lib/check_please/path.rb, line 97
def match_by_value?(flags)
  flags.match_by_value.any? { |e| e.match?(self) }
end
parent() click to toggle source
# File lib/check_please/path.rb, line 113
def parent
  return nil if root? # TODO: consider the Null Object pattern
  self.class.new(segments[0..-2])
end
root?() click to toggle source
# File lib/check_please/path.rb, line 118
def root?
  @segments.empty?
end

Private Instance Methods

ancestor_on_list?(paths) click to toggle source

O(n^2) check to see if any of the path's ancestors are on a list (as of this writing, this should never actually happen, but I'm being thorough)

# File lib/check_please/path.rb, line 126
def ancestor_on_list?(paths)
  paths.any? { |path|
    ancestors.any? { |ancestor| ancestor.match?(path) }
  }
end
explicitly_excluded?(flags) click to toggle source
# File lib/check_please/path.rb, line 132
def explicitly_excluded?(flags)
  return false if flags.reject_paths.empty?
  return true if self_on_list?(flags.reject_paths)
  return true if ancestor_on_list?(flags.reject_paths)
  false
end
implicitly_excluded?(flags) click to toggle source
# File lib/check_please/path.rb, line 139
def implicitly_excluded?(flags)
  return false if flags.select_paths.empty?
  return false if self_on_list?(flags.select_paths)
  return false if ancestor_on_list?(flags.select_paths)
  true
end
key_exprs() click to toggle source

A path of “/foo/:id/bar/:name” has two key expressions:

  • “/foo/:id”

  • “/foo/:id/bar/:name”

# File lib/check_please/path.rb, line 149
def key_exprs
  ( [self] + ancestors )
    .reject { |path| path.root? }
    .select { |path| path.segments.last&.key_expr? }
end
self_on_list?(paths) click to toggle source

O(n) check to see if the path itself is on a list

# File lib/check_please/path.rb, line 156
def self_on_list?(paths)
  paths.any? { |path| self.match?(path) }
end
too_deep?(flags) click to toggle source
# File lib/check_please/path.rb, line 160
def too_deep?(flags)
  return false if flags.max_depth.nil?
  depth > flags.max_depth
end
unpack_key_exprs(path_list) click to toggle source
# File lib/check_please/path.rb, line 165
def unpack_key_exprs(path_list)
  path_list
    .map { |path| path.send(:key_exprs) }
    .flatten
    .uniq { |e| e.to_s } # use the block form so we don't have to implement #hash and #eql? in horrible ways
end