Class: Vers::Parser
- Inherits:
-
Object
- Object
- Vers::Parser
- 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
- NGINX_RANGE_REGEX =
/\A\d+(?:\.\d+)+-\d+(?:\.\d+)+\z/- OPERATOR_PREFIX_REGEX =
/\A[><=!]+/- PUB_VERSION_PREFIX_REGEX =
/\A[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?/- SEMVER_OUTPUT_SCHEMES =
%w[npm cargo nuget composer pub].freeze
- VERS_META_ENCODINGS =
{ "%" => "%25", "|" => "%7C", ">" => "%3E", "<" => "%3C", "=" => "%3D", "!" => "%21", "/" => "%2F", "*" => "%2A", " " => "%20" }.freeze
- 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
Instance Method Summary collapse
-
#parse(vers_string, require_canonical_order: false) ⇒ VersionRange
Parses a vers URI string into a VersionRange.
-
#parse_native(range_string, scheme) ⇒ VersionRange
Parses a native package manager version range into a VersionRange.
-
#to_vers_string(version_range, scheme) ⇒ String
Converts a VersionRange back to a vers URI string.
Instance Method Details
#parse(vers_string, require_canonical_order: false) ⇒ VersionRange
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 |
# File 'lib/vers/parser.rb', line 62 def parse(vers_string, require_canonical_order: false) validate_input_length!(vers_string) return VersionRange.unbounded if vers_string == "*" unless vers_string.is_a?(String) && vers_string.start_with?("vers:") raise ArgumentError, "Invalid vers URI format: #{vers_string}" end if vers_string.match?(/[ \t\r\n]/) raise ArgumentError, "non-canonical VERS: whitespace is not permitted" end remainder = vers_string.delete_prefix("vers:") slash = remainder.index("/") unless slash && slash.positive? raise ArgumentError, "Invalid vers URI format: #{vers_string}" end raw_scheme = remainder[...slash] if raw_scheme != raw_scheme.downcase raise ArgumentError, "non-canonical VERS: type must be lowercase" end scheme = Scheme.canonical(raw_scheme) constraints_string = remainder[(slash + 1)..] if constraints_string.empty? || constraints_string == "*" return VersionRange.unbounded(scheme: scheme) end validate_vers_constraints!(constraints_string, scheme, require_canonical_order) parse_constraints(constraints_string, scheme, decode_versions: true) end |
#parse_native(range_string, scheme) ⇒ VersionRange
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 145 146 147 148 |
# File 'lib/vers/parser.rb', line 108 def parse_native(range_string, scheme) validate_input_length!(range_string) canonical_scheme = Scheme.canonical(scheme) range = case canonical_scheme when "npm" parse_npm_range(range_string, scheme: "npm") when "gem" parse_gem_range(range_string) when "pypi" parse_pypi_range(range_string) when "composer" parse_composer_range(range_string) when "pub" parse_pub_range(range_string) when "conan" parse_conan_range(range_string) when "nginx" parse_nginx_range(range_string) when "openssl" parse_openssl_range(range_string) when "maven" parse_maven_range(range_string) when "cargo" parse_npm_range(range_string, scheme: "cargo") when "nuget" parse_nuget_range(range_string) when "hex" parse_hex_range(range_string) when "go" parse_go_range(range_string) when "deb" parse_debian_range(range_string) when "rpm" parse_rpm_range(range_string) else # Fall back to generic constraint parsing parse_constraints(range_string, canonical_scheme) end range.with_scheme(canonical_scheme) end |
#to_vers_string(version_range, scheme) ⇒ String
Converts a VersionRange back to a vers URI 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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/vers/parser.rb', line 157 def to_vers_string(version_range, scheme) canonical_scheme = Scheme.canonical(scheme) if version_range.scheme && canonical_scheme != version_range.scheme raise ArgumentError, "Cannot serialize a #{version_range.scheme} range as #{canonical_scheme}" end scheme = canonical_scheme if version_range.unbounded? && (!version_range.raw_constraints || version_range.raw_constraints.empty?) return "vers:#{scheme}/*" end if version_range.empty? && (!version_range.raw_constraints || version_range.raw_constraints.empty?) return "vers:#{scheme}/" end intervals = serialization_intervals(version_range, scheme) 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 version = encode_vers_version(normalize_vers_version(a.max, scheme)) constraints << "!=#{version}" sort_constraints!(constraints, scheme) return "vers:#{scheme}/#{constraints.join('|')}" end end intervals.each do |interval| next if interval.unbounded? if interval.min == interval.max && interval.min_inclusive && interval.max_inclusive # Exact version constraints << encode_vers_version(normalize_vers_version(interval.min.to_s, scheme)) else # Range constraints if interval.min operator = interval.min_inclusive ? ">=" : ">" version = encode_vers_version(normalize_vers_version(interval.min, scheme)) constraints << "#{operator}#{version}" end if interval.max operator = interval.max_inclusive ? "<=" : "<" version = encode_vers_version(normalize_vers_version(interval.max, scheme)) constraints << "#{operator}#{version}" end end end version_range.exclusions.each do |version| normalized = normalize_vers_version(version, scheme) constraints << "!=#{encode_vers_version(normalized)}" end sort_constraints!(constraints, scheme) "vers:#{scheme}/#{constraints.join('|')}" end |