Module: QB::Package::Version::From
- Defined in:
- lib/qb/package/version/from.rb
Overview
Module of factory methods to create QB::Package::Version instances from other objects (strings, QB::Package::Version, etc.)
Class Method Summary collapse
-
.class_for(**values) ⇒ Class<QB::Package::Version>
Get class to instantiate for prop values - either QB::Package::Version or a specialized subclass like Leveled.
-
.docker_tag(source) ⇒ QB::Package::Version
Parse Docker image tag version and create an instance.
-
.file(path) ⇒ QB::Package::Version
Load a QB::Package::Version from a file.
-
.gemver(source) ⇒ QB::Package::Version
Create an instance from a Gem-style version.
-
.identifier_for(value) ⇒ String | Integer
Parse and/or validate version identifiers.
- .object(object) ⇒ Object
-
.prop_values(**values) ⇒ QB::Package::Version
Instantiate an instance from prop values, using From.class_for to choose the possible specialized class.
- .repo(repo_or_path, add_build: true) ⇒ Object
- .segment_for(string) ⇒ Object
-
.semver(source, strict: false) ⇒ Object
Load a SemVer¹ string into a QB::Package::Version.
- .split_identifiers(string) ⇒ Object
-
.string(source) ⇒ QB::Package::Version
Parse string version into an instance.
Instance Method Summary collapse
Class Method Details
.class_for(**values) ⇒ Class<QB::Package::Version>
Get class to instantiate for prop values - either QB::Package::Version or a specialized subclass like Leveled.
26 27 28 29 30 31 32 |
# File 'lib/qb/package/version/from.rb', line 26 def self.class_for **values if QB::Package::Version::Leveled.level_for **values QB::Package::Version::Leveled else QB::Package::Version end end |
.docker_tag(source) ⇒ QB::Package::Version
Parse Docker image tag version and create an instance.
255 256 257 258 |
# File 'lib/qb/package/version/from.rb', line 255 def self.docker_tag source source = source.to_s unless source.is_a?( String ) self.string( source.gsub( '_', '+' ) ).merge raw: source end |
.file(path) ⇒ QB::Package::Version
Load a QB::Package::Version from a file.
Just reads the file and passes the contents to string.
318 319 320 |
# File 'lib/qb/package/version/from.rb', line 318 def self.file path string File.read( path ) end |
.gemver(source) ⇒ QB::Package::Version
Create an instance from a Gem-style version.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/qb/package/version/from.rb', line 54 def self.gemver source gem_version = case source when ::Gem::Version source else ::Gem::Version.new source.to_s end # release segments are everything before a string release_segments = gem_version.segments.take_while { |seg| !seg.is_a?(String) } prerelease_segments = gem_version.segments[release_segments.length..-1] prop_values \ raw: source.to_s, major: release_segments[0], minor: release_segments[1] || 0, patch: release_segments[2] || 0, revision: release_segments[3..-1] || [], prerelease: prerelease_segments, build: [] end |
.identifier_for(value) ⇒ String | Integer
Parse and/or validate version identifiers.
See QB::Package::Version for details on identifiers.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/qb/package/version/from.rb', line 98 def self.identifier_for value case value when QB::Package::Version::NUMBER_IDENTIFIER_RE value.to_i when QB::Package::Version::MIXED_SEGMENT value else raise ArgumentError.new binding.erb <<~END Can't parse identifier <%= value.inspect %> Expected one of: 1. <%= QB::Package::Version::NUMBER_IDENTIFIER_RE %> 2. <%= QB::Package::Version::MIXED_SEGMENT %> END end end |
.object(object) ⇒ Object
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/qb/package/version/from.rb', line 288 def self.object object case object when String string object when Hash prop_values **object when ::Gem::Version gemver object else raise TypeError.new binding.erb <<-END `object` must be String, Hash or Gem::Version Found: <%= object.pretty_inspect %> END end end |
.prop_values(**values) ⇒ QB::Package::Version
Instantiate an instance from prop values, using class_for to choose the possible specialized class.
43 44 45 |
# File 'lib/qb/package/version/from.rb', line 43 def self.prop_values **values class_for( **values ).new **values end |
.repo(repo_or_path, add_build: true) ⇒ Object
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/qb/package/version/from.rb', line 324 def self.repo repo_or_path, add_build: true repo = t.match repo_or_path, QB::Repo, repo_or_path, t.path, QB::Repo.method( :from_path ) version_path = repo.root_path / 'VERSION' file_version = file version_path if add_build && file_version.level? && file_version.dev? file_version.build_version \ branch: repo.branch, ref: repo.head_short, dirty: !repo.clean? else file_version end end |
.segment_for(string) ⇒ Object
118 119 120 |
# File 'lib/qb/package/version/from.rb', line 118 def self.segment_for string split_identifiers( string ).map { |s| identifier_for s } end |
.semver(source, strict: false) ⇒ Object
Load a SemVer¹ string into a QB::Package::Version.
¹ Through a combination of need, failure and frustration we are a wee bit looser than the SemVer spec. This really comes from needing to be able to handle more than three release segments because:
- Well, some projects use more than three and we can't change that.
- It seemed over-complicated to add another "almost-semver" parsing option.
Details below. You can enforce our best attempt at pure SemVer with the
strict:
keyword option.
Gory Details
Oh, semver... what a pain you're been.
Right now, I just finished writing our own parser. It probably has a lot of problems and I can't imagine it conforms to the spec even where it's meant to. I didn't want to go this road, it was out of desperation.
First, QB was shelling-out to Node and using it's semver package, since that seems to kind of be the de-facto reference implementation of the spec.
That was far too slow to process large lists of version like you might
get from git tag
, and it means we depended on Node and had to bundle
the semver
package in or install it otherwise, which was a pain for
a single function call.
Yeah, there are other ways to go about it, but they all suck too.
Next I tried the [semver2][Ruby semver2] Ruby gem. It never struck me as
super solid, and when faced with 1.2.3.4-pre
-style versions it just
tossed everything after the 3
and didn't mention it, which led me to
toss it too.
This happen when I realized we couldn't side-step "fourth release segment"
because I needed the system to handle OpenResty's Docker image versions,
which are M.m.p.r
format, leading to add the
QB::Package::Version#revision property and write my own parsing logic
here.
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 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/qb/package/version/from.rb', line 190 def self.semver source, strict: false source = source.to_s unless source.is_a?( String ) identifier_for_ref = method :identifier_for if source.include?( '-' ) && source.include?( '+' ) && source.index( '-' ) < source.index( '+' ) release_str, _, rest = source.partition '-' pre_str, _, build_str = rest.partition '+' elsif source.include?( '+' ) release_str, _, build_str = source.partition '+' pre_str = '' elsif source.include?( '-' ) release_str, _, pre_str = source.partition '-' build_str = '' else release_str = source pre_str = build_str = '' end release_segs, pre_segs, build_segs = \ [release_str, pre_str, build_str].map { |str| split_identifiers( str ).map &identifier_for_ref } # Check release segments length if strict && release_segs.length != 3 raise NRSER::ArgumentError.new \ "Strict SemVer versions *MUST* have at exactly 3 release segments", source: source, release_segments: release_segs elsif release_segs.length < 3 raise NRSER::ArgumentError.new \ "SemVer versions *MUST* have at lease 3 release segments", source: source, release_segments: release_segs end prop_values **{ raw: source, major: release_segs[0], minor: release_segs[1], patch: release_segs[2], revision: release_segs[3..-1] || [], prerelease: pre_segs, build: build_segs, }.compact end |
.split_identifiers(string) ⇒ Object
82 83 84 |
# File 'lib/qb/package/version/from.rb', line 82 def self.split_identifiers string string.split QB::Package::Version::IDENTIFIER_SEPARATOR end |
.string(source) ⇒ QB::Package::Version
Parse string version into an instance. Accept Semver, Ruby Gem and Docker image tag formats.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/qb/package/version/from.rb', line 269 def self.string source source = source.to_s unless source.is_a?( String ) t.non_empty_str.check source if source.include? '_' docker_tag source elsif ( source.include?( '-' ) || source.include?( '+' ) ) && source =~ /\A\d+\.\d+\.\d+/ semver source else gemver source end end |
Instance Method Details
#npm_version(source) ⇒ Object
243 244 245 |
# File 'lib/qb/package/version/from.rb', line 243 def npm_version source semver source, strict: true end |