Class: Vers::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/vers/parser.rb

Overview

Parses vers URI strings and package manager specific version ranges

This class handles parsing of vers URI format (e.g., "vers:npm/>=1.2.3|<2.0.0") and provides extensible support for different package ecosystem syntaxes.

Examples

parser = Vers::Parser.new
range = parser.parse("vers:npm/>=1.2.3|<2.0.0")
range.contains?("1.5.0")  # => true

Constant Summary collapse

VERS_URI_REGEX =

Regex for parsing vers URI format

/\Avers:([^\/]+)\/(.+)\z/
NPM_CARET_REGEX =

Pre-compiled regex patterns for common npm patterns

/\A\^(.+)\z/
NPM_TILDE_REGEX =
/\A~(.+)\z/
NPM_HYPHEN_REGEX =
/\A(.+?)\s+-\s+(.+)\z/
NPM_X_RANGE_MAJOR_REGEX =
/\A(\d+)\.x\z/
NPM_X_RANGE_MINOR_REGEX =
/\A(\d+)\.(\d+)\.x\z/
OPERATOR_PREFIX_REGEX =
/\A[><=!]+/
MAX_INPUT_LENGTH =

Maximum accepted length for a range string at parse/parse_native entry points. Range strings concatenate multiple constraints so this is set higher than Version::MAX_LENGTH while still bounding split/regex work to a few KB.

2048
MAX_CONSTRAINTS =

Maximum number of |-separated or ||-separated constraints in a single range. The exclusion loop in parse_constraints does O(n^2 log n) work as each != splits an interval and reconstructs the range; capping n keeps the worst case under a few thousand interval operations.

64
@@parser_cache =

Cache for parsed ranges to improve performance

{}
@@cache_size_limit =
500

Instance Method Summary collapse

Instance Method Details

#parse(vers_string) ⇒ VersionRange

Parses a vers URI string into a VersionRange

Examples

parser = Vers::Parser.new
parser.parse("vers:npm/>=1.2.3|<2.0.0")
parser.parse("vers:gem/~>1.0")
parser.parse("vers:pypi/==1.2.3")

Parameters:

  • vers_string (String)

    The vers URI string to parse

Returns:

Raises:

  • (ArgumentError)

    if the vers string is invalid



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/vers/parser.rb', line 62

def parse(vers_string)
  validate_input_length!(vers_string)

  if vers_string == "*"
    return VersionRange.unbounded
  end

  match = vers_string.match(VERS_URI_REGEX)
  raise ArgumentError, "Invalid vers URI format: #{vers_string}" unless match

  scheme = match[1]
  constraints_string = match[2]

  parse_constraints(constraints_string, scheme)
end

#parse_native(range_string, scheme) ⇒ VersionRange

Parses a native package manager version range into a VersionRange

Examples

parser = Vers::Parser.new
parser.parse_native("^1.2.3", "npm")
parser.parse_native("~> 1.0", "gem")
parser.parse_native(">=1.0,<2.0", "pypi")

Parameters:

  • range_string (String)

    The native version range string

  • scheme (String)

    The package manager scheme (npm, gem, pypi, etc.)

Returns:



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
# File 'lib/vers/parser.rb', line 92

def parse_native(range_string, scheme)
  validate_input_length!(range_string)

  case scheme
  when "npm"
    parse_npm_range(range_string)
  when "gem", "rubygems"
    parse_gem_range(range_string)
  when "pypi"
    parse_pypi_range(range_string)
  when "maven"
    parse_maven_range(range_string)
  when "cargo"
    parse_npm_range(range_string)
  when "nuget"
    parse_nuget_range(range_string)
  when "hex", "elixir"
    parse_hex_range(range_string)
  when "go", "golang"
    parse_go_range(range_string)
  when "deb", "debian"
    parse_debian_range(range_string)
  when "rpm"
    parse_rpm_range(range_string)
  else
    # Fall back to generic constraint parsing
    parse_constraints(range_string, scheme)
  end
end

#to_vers_string(version_range, scheme) ⇒ String

Converts a VersionRange back to a vers URI string

Parameters:

  • version_range (VersionRange)

    The version range to convert

  • scheme (String)

    The package manager scheme

Returns:

  • (String)

    The vers URI string



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/vers/parser.rb', line 129

def to_vers_string(version_range, scheme)
  return "vers:#{scheme}/*" if version_range.unbounded?
  return "vers:#{scheme}/" if version_range.empty?

  intervals = version_range.raw_constraints || version_range.intervals
  constraints = []

  # Detect != pattern: two intervals (-∞,V) ∪ (V,+∞)
  if intervals.length == 2
    a, b = intervals
    if a.min.nil? && !a.max_inclusive && b.max.nil? && !b.min_inclusive && a.max == b.min
      constraints << "!=#{a.max}"
      constraints.sort_by! { |c| sort_key_for_constraint(c) }
      return "vers:#{scheme}/#{constraints.join('|')}"
    end
  end

  intervals.each do |interval|
    if interval.min == interval.max && interval.min_inclusive && interval.max_inclusive
      # Exact version
      constraints << interval.min.to_s
    else
      # Range constraints
      if interval.min
        operator = interval.min_inclusive ? ">=" : ">"
        constraints << "#{operator}#{interval.min}"
      end

      if interval.max
        operator = interval.max_inclusive ? "<=" : "<"
        constraints << "#{operator}#{interval.max}"
      end
    end
  end

  constraints.sort_by! { |c| sort_key_for_constraint(c) }

  "vers:#{scheme}/#{constraints.join('|')}"
end