UNPKG

13.3 kBJavaScriptView Raw
1"use strict";
2// -----------------------------
3// The Version class processes string versions into comparable
4// values. A version string should normally be a series of numbers
5// separated by periods. Each part (digits separated by periods) is
6// considered its own number, and these are used for sorting. So for
7// instance, 3.10 sorts higher than 3.2 because ten is greater than
8// two.
9//
10// If any part contains letters (currently only a-z are supported) then
11// that version is considered prerelease. Versions with a prerelease
12// part in the Nth part sort less than versions with N-1
13// parts. Prerelease parts are sorted alphabetically using the normal
14// Ruby string sorting rules. If a prerelease part contains both
15// letters and numbers, it will be broken into multiple parts to
16// provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is
17// greater than 1.0.a9).
18//
19// Prereleases sort between real releases (newest to oldest):
20//
21// 1. 1.0
22// 2. 1.0.b1
23// 3. 1.0.a.2
24// 4. 0.9
25//
26// If you want to specify a version restriction that includes both prereleases
27// and regular releases of the 1.x series this is the best way:
28//
29// s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0'
30//
31// == How Software Changes
32//
33// Users expect to be able to specify a version constraint that gives them
34// some reasonable expectation that new versions of a library will work with
35// their software if the version constraint is true, and not work with their
36// software if the version constraint is false. In other words, the perfect
37// system will accept all compatible versions of the library and reject all
38// incompatible versions.
39//
40// Libraries change in 3 ways (well, more than 3, but stay focused here!).
41//
42// 1. The change may be an implementation detail only and have no effect on
43// the client software.
44// 2. The change may add new features, but do so in a way that client software
45// written to an earlier version is still compatible.
46// 3. The change may change the public interface of the library in such a way
47// that old software is no longer compatible.
48//
49// Some examples are appropriate at this point. Suppose I have a Stack class
50// that supports a <tt>push</tt> and a <tt>pop</tt> method.
51//
52// === Examples of Category 1 changes:
53//
54// * Switch from an array based implementation to a linked-list based
55// implementation.
56// * Provide an automatic (and transparent) backing store for large stacks.
57//
58// === Examples of Category 2 changes might be:
59//
60// * Add a <tt>depth</tt> method to return the current depth of the stack.
61// * Add a <tt>top</tt> method that returns the current top of stack (without
62// changing the stack).
63// * Change <tt>push</tt> so that it returns the item pushed (previously it
64// had no usable return value).
65//
66// === Examples of Category 3 changes might be:
67//
68// * Changes <tt>pop</tt> so that it no longer returns a value (you must use
69// <tt>top</tt> to get the top of the stack).
70// * Rename the methods to <tt>push_item</tt> and <tt>pop_item</tt>.
71//
72// == RubyGems Rational Versioning
73//
74// * Versions shall be represented by three non-negative integers, separated
75// by periods (e.g. 3.1.4). The first integers is the "major" version
76// number, the second integer is the "minor" version number, and the third
77// integer is the "build" number.
78//
79// * A category 1 change (implementation detail) will increment the build
80// number.
81//
82// * A category 2 change (backwards compatible) will increment the minor
83// version number and reset the build number.
84//
85// * A category 3 change (incompatible) will increment the major build number
86// and reset the minor and build numbers.
87//
88// * Any "public" release of a gem should have a different version. Normally
89// that means incrementing the build number. This means a developer can
90// generate builds all day long, but as soon as they make a public release,
91// the version must be updated.
92//
93// === Examples
94//
95// Let's work through a project lifecycle using our Stack example from above.
96//
97// Version 0.0.1:: The initial Stack class is release.
98// Version 0.0.2:: Switched to a linked=list implementation because it is
99// cooler.
100// Version 0.1.0:: Added a <tt>depth</tt> method.
101// Version 1.0.0:: Added <tt>top</tt> and made <tt>pop</tt> return nil
102// (<tt>pop</tt> used to return the old top item).
103// Version 1.1.0:: <tt>push</tt> now returns the value pushed (it used it
104// return nil).
105// Version 1.1.1:: Fixed a bug in the linked list implementation.
106// Version 1.1.2:: Fixed a bug introduced in the last fix.
107//
108// Client A needs a stack with basic push/pop capability. They write to the
109// original interface (no <tt>top</tt>), so their version constraint looks like:
110//
111// gem 'stack', '>= 0.0'
112//
113// Essentially, any version is OK with Client A. An incompatible change to
114// the library will cause them grief, but they are willing to take the chance
115// (we call Client A optimistic).
116//
117// Client B is just like Client A except for two things: (1) They use the
118// <tt>depth</tt> method and (2) they are worried about future
119// incompatibilities, so they write their version constraint like this:
120//
121// gem 'stack', '~> 0.1'
122//
123// The <tt>depth</tt> method was introduced in version 0.1.0, so that version
124// or anything later is fine, as long as the version stays below version 1.0
125// where incompatibilities are introduced. We call Client B pessimistic
126// because they are worried about incompatible future changes (it is OK to be
127// pessimistic!).
128//
129// == Preventing Version Catastrophe:
130//
131// From: http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
132//
133// Let's say you're depending on the fnord gem version 2.y.z. If you
134// specify your dependency as ">= 2.0.0" then, you're good, right? What
135// happens if fnord 3.0 comes out and it isn't backwards compatible
136// with 2.y.z? Your stuff will break as a result of using ">=". The
137// better route is to specify your dependency with an "approximate" version
138// specifier ("~>"). They're a tad confusing, so here is how the dependency
139// specifiers work:
140//
141// Specification From ... To (exclusive)
142// ">= 3.0" 3.0 ... &infin;
143// "~> 3.0" 3.0 ... 4.0
144// "~> 3.0.0" 3.0.0 ... 3.1
145// "~> 3.5" 3.5 ... 4.0
146// "~> 3.5.0" 3.5.0 ... 3.6
147// "~> 3" 3.0 ... 4.0
148//
149// For the last example, single-digit versions are automatically extended with
150// a zero to give a sensible result.
151Object.defineProperty(exports, "__esModule", { value: true });
152const VERSION_PATTERN = '[0-9]+(\\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?';
153const ANCHORED_VERSION_PATTERN = new RegExp(`^\\s*(${VERSION_PATTERN})?\\s*$`);
154class GemVersion {
155 // @@all = {}
156 // def self.new version # :nodoc:
157 // return super unless GemVersion == self
158 // @@all[version] ||= super
159 // end
160 // -----------------------------
161 // Constructs a Version from the +version+ string. A version string is a
162 // series of digits or ASCII letters separated by dots.
163 constructor(version) {
164 if (!GemVersion.isCorrect(version)) {
165 throw new Error(`Malformed version number string ${version}`);
166 }
167 this.version = String(version).trim().replace('-', '.pre.');
168 }
169 // include Comparable
170 // -----------------------------
171 // A string representation of this Version.
172 toString() {
173 return this.version;
174 }
175 // -----------------------------
176 // True if the +version+ string matches RubyGems' requirements.
177 static isCorrect(version) {
178 return ANCHORED_VERSION_PATTERN.test(version);
179 }
180 // -----------------------------
181 // Factory method to create a Version object. Input may be a Version
182 // or a String. Intended to simplify client code.
183 //
184 // ver1 = Version.create('1.3.17') // -> (Version object)
185 // ver2 = Version.create(ver1) // -> (ver1)
186 // ver3 = Version.create(nil) // -> nil
187 static create(input) {
188 if (input instanceof GemVersion) {
189 return input;
190 }
191 if (!input) {
192 return undefined;
193 }
194 return new GemVersion(input);
195 }
196 // -----------------------------
197 // Return a new version object where the next to the last revision
198 // number is one greater (e.g., 5.3.1 => 5.4).
199 //
200 // Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored.
201 // def bump
202 // @bump ||= begin
203 // segments = self.segments.dup
204 // segments.pop while segments.any? { |s| String === s }
205 // segments.pop if segments.size > 1
206 // segments[-1] = segments[-1].succ
207 // self.class.new segments.join(".")
208 // end
209 // end
210 bump() {
211 if (!this._bump) {
212 const segments = this.getSegments();
213 while (segments.some((x) => typeof x === 'string')) {
214 segments.pop();
215 }
216 if (segments.length > 1) {
217 segments.pop();
218 }
219 const last = segments.pop();
220 segments.push(Number(last) + 1);
221 this._bump = new GemVersion(segments.join('.'));
222 }
223 return this._bump;
224 }
225 // -----------------------------
226 // A Version is only eql? to another version if it's specified to the
227 // same precision. Version "1.0" is not the same as version "1".
228 isIdentical(other) {
229 return other instanceof GemVersion && other.version === this.version;
230 }
231 // def hash # :nodoc:
232 // @version.hash
233 // end
234 // def init_with coder # :nodoc:
235 // yaml_initialize coder.tag, coder.map
236 // end
237 // def inspect # :nodoc:
238 // "#<#{self.class} #{version.inspect}>"
239 // end
240 // -----------------------------
241 // Dump only the raw version string, not the complete object. It's a
242 // string for backwards (RubyGems 1.3.5 and earlier) compatibility.
243 // def marshal_dump
244 // [version]
245 // end
246 // -----------------------------
247 // Load custom marshal format. It's a string for backwards (RubyGems
248 // 1.3.5 and earlier) compatibility.
249 // def marshal_load array
250 // initialize array[0]
251 // end
252 // def yaml_initialize(tag, map) # :nodoc:
253 // @version = map['version']
254 // @segments = nil
255 // @hash = nil
256 // end
257 // def to_yaml_properties # :nodoc:
258 // ["@version"]
259 // end
260 // def encode_with coder # :nodoc:
261 // coder.add 'version', @version
262 // end
263 // -----------------------------
264 // A version is considered a prerelease if it contains a letter.
265 isPrerelease() {
266 if (this._isPrerelease === undefined) {
267 this._isPrerelease = /[a-zA-Z]/.test(this.version);
268 }
269 return this._isPrerelease;
270 }
271 // def pretty_print q # :nodoc:
272 // q.text "GemVersion.new(#{version.inspect})"
273 // end
274 // -----------------------------
275 // The release for this version (e.g. 1.2.0.a -> 1.2.0).
276 // Non-prerelease versions return themselves.
277 release() {
278 if (!this._release) {
279 if (this.isPrerelease) {
280 const segments = this.getSegments();
281 while (segments.some((x) => typeof x === 'string')) {
282 segments.pop();
283 }
284 this._release = new GemVersion(segments.join('.'));
285 }
286 else {
287 this._release = this;
288 }
289 }
290 return this._release;
291 }
292 // def segments # :nodoc:
293 // // segments is lazy so it can pick up version values that come from
294 // // old marshaled versions, which don't go through marshal_load.
295 // @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
296 // /^\d+$/ =~ s ? s.to_i : s
297 // end
298 // end
299 getSegments() {
300 return this.version
301 .match(/[0-9]+|[a-z]+/gi)
302 .map((s) => (/^\d+$/.test(s) ? Number(s) : s));
303 }
304 compare(other) {
305 if (!(other instanceof GemVersion)) {
306 return undefined;
307 }
308 if (other.version === this.version) {
309 return 0;
310 }
311 const lhsegments = this.getSegments();
312 const rhsegments = other.getSegments();
313 const lhsize = lhsegments.length;
314 const rhsize = rhsegments.length;
315 const limit = (lhsize > rhsize ? lhsize : rhsize) - 1;
316 let i = 0;
317 while (i <= limit) {
318 const lhs = lhsegments[i] || 0;
319 const rhs = rhsegments[i] || 0;
320 i += 1;
321 if (lhs == rhs) {
322 continue;
323 }
324 if (isString(lhs) && isNumber(rhs)) {
325 return -1;
326 }
327 if (isNumber(lhs) && isString(rhs)) {
328 return 1;
329 }
330 if (lhs < rhs) {
331 return -1;
332 }
333 if (lhs > rhs) {
334 return 1;
335 }
336 }
337 return 0;
338 }
339}
340exports.GemVersion = GemVersion;
341function isString(val) {
342 return typeof val === 'string';
343}
344function isNumber(val) {
345 return typeof val === 'number';
346}
347GemVersion.VERSION_PATTERN = VERSION_PATTERN;
348//# sourceMappingURL=gem-version.js.map
\No newline at end of file