class Gem::Version
Die Klasse Version verarbeitet Zeichenketten-Versionen zu vergleichbaren Werten. Eine Versionszeichenkette sollte normalerweise eine Reihe von Zahlen sein, die durch Punkte getrennt sind. Jeder Teil (Ziffern, die durch Punkte getrennt sind) wird als eigene Zahl betrachtet und diese werden zum Sortieren verwendet. So sortiert beispielsweise 3.10 höher als 3.2, da zehn größer als zwei ist.
Wenn ein Teil Buchstaben enthält (derzeit werden nur a-z unterstützt), dann gilt diese Version als Vorabveröffentlichung (prerelease). Versionen mit einem Vorabveröffentlichungs-Teil im N-ten Teil sortieren niedriger als Versionen mit N-1 Teilen. Vorabveröffentlichungs-Teile werden alphabetisch sortiert, unter Verwendung der normalen Ruby-Zeichenketten-Sortierregeln. Wenn ein Vorabveröffentlichungs-Teil sowohl Buchstaben als auch Zahlen enthält, wird er in mehrere Teile aufgeteilt, um ein erwartetes Sortierverhalten zu gewährleisten (1.0.a10 wird zu 1.0.a.10 und ist größer als 1.0.a9).
Vorabveröffentlichungen sortieren zwischen echten Veröffentlichungen (neueste zu älteste)
-
1.0
-
1.0.b1
-
1.0.a.2
-
0.9
Wenn Sie eine Versionsbeschränkung angeben möchten, die sowohl Vorabveröffentlichungen als auch reguläre Veröffentlichungen der 1.x-Serie umfasst, ist dies der beste Weg
s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0'
Wie sich Software ändert
Benutzer erwarten, eine Versionsbeschränkung angeben zu können, die ihnen eine gewisse begründete Erwartung gibt, dass neue Versionen einer Bibliothek mit ihrer Software funktionieren, wenn die Versionsbeschränkung wahr ist, und nicht funktionieren, wenn die Versionsbeschränkung falsch ist. Mit anderen Worten: Das perfekte System akzeptiert alle kompatiblen Versionen der Bibliothek und lehnt alle inkompatiblen Versionen ab.
Bibliotheken ändern sich auf 3 Arten (nun ja, mehr als 3, aber konzentrieren wir uns hier!)
-
Die Änderung kann nur ein Implementierungsdetail sein und hat keine Auswirkungen auf die Client-Software.
-
Die Änderung kann neue Funktionen hinzufügen, dies jedoch auf eine Weise, die für Client-Software, die für eine frühere Version geschrieben wurde, weiterhin kompatibel ist.
-
Die Änderung kann die öffentliche Schnittstelle der Bibliothek so ändern, dass alte Software nicht mehr kompatibel ist.
An dieser Stelle sind einige Beispiele angebracht. Angenommen, ich habe eine Stack-Klasse, die eine push und eine pop Methode unterstützt.
Beispiele für Änderungen der Kategorie 1
-
Umstellung von einer Array-basierten Implementierung auf eine verkettete Listen-basierte Implementierung.
-
Bereitstellung eines automatischen (und transparenten) Backing Stores für große Stacks.
Beispiele für Änderungen der Kategorie 2 könnten sein
-
Hinzufügen einer
depth-Methode, um die aktuelle Tiefe des Stacks zurückzugeben. -
Hinzufügen einer
top-Methode, die die aktuelle Spitze des Stacks zurückgibt (ohne den Stack zu ändern). -
Ändern von
pushso, dass es das gepushte Element zurückgibt (zuvor hatte es keinen brauchbaren Rückgabewert).
Beispiele für Änderungen der Kategorie 3 könnten sein
-
Ändert
popso, dass es keinen Wert mehr zurückgibt (Sie müssentopverwenden, um die Spitze des Stacks zu erhalten). -
Umbenennung der Methoden in
push_itemundpop_item.
RubyGems Rational Versionierung
-
Versionen werden durch drei nicht-negative ganze Zahlen dargestellt, die durch Punkte getrennt sind (z. B. 3.1.4). Die erste ganze Zahl ist die „Major“-Versionsnummer, die zweite ganze Zahl ist die „Minor“-Versionsnummer und die dritte ganze Zahl ist die „Build“-Nummer.
-
Eine Änderung der Kategorie 1 (Implementierungsdetail) erhöht die Build-Nummer.
-
Eine Änderung der Kategorie 2 (abwärtskompatibel) erhöht die Minor-Versionsnummer und setzt die Build-Nummer zurück.
-
Eine Änderung der Kategorie 3 (inkompatibel) erhöht die Major-Build-Nummer und setzt die Minor- und Build-Nummern zurück.
-
Jede „öffentliche“ Veröffentlichung eines Gems sollte eine andere Versionsnummer haben. Normalerweise bedeutet dies eine Erhöhung der Build-Nummer. Das bedeutet, dass ein Entwickler den ganzen Tag Builds generieren kann, aber sobald er eine öffentliche Veröffentlichung vornimmt, muss die Versionsnummer aktualisiert werden.
Beispiele
Arbeiten wir den Lebenszyklus eines Projekts mit unserem obigen Stack-Beispiel durch.
Version0.0.1-
Die ursprüngliche Stack-Klasse wird veröffentlicht.
Version0.0.2-
Umstellung auf eine verkettete Listenimplementierung, weil das cooler ist.
Version0.1.0-
Hinzufügen einer
depth-Methode. Version1.0.0-
Hinzufügen von
topund machen vonpopnil zurückgeben (popgab früher das alte oberste Element zurück). Version1.1.0-
pushgibt jetzt den gepushten Wert zurück (es gab früher nil zurück). Version1.1.1-
Fehler in der Implementierung der verketteten Liste behoben.
Version1.1.2-
Fehler behoben, der im letzten Fix eingeführt wurde.
Client A benötigt einen Stack mit grundlegender Push/Pop-Funktionalität. Er schreibt gegen die ursprüngliche Schnittstelle (kein top), daher sieht seine Versionsbeschränkung so aus
gem 'stack', '>= 0.0'
Im Wesentlichen ist jede Version für Client A in Ordnung. Eine inkompatible Änderung der Bibliothek wird ihm Ärger bereiten, aber er ist bereit, das Risiko einzugehen (wir nennen Client A optimistisch).
Client B ist wie Client A, außer bei zwei Dingen: (1) Er verwendet die Methode depth und (2) er macht sich Sorgen über zukünftige Inkompatibilitäten, daher schreibt er seine Versionsbeschränkung so
gem 'stack', '~> 0.1'
Die Methode depth wurde in Version 0.1.0 eingeführt, daher ist diese Version oder alles spätere in Ordnung, solange die Version unter Version 1.0 bleibt, wo Inkompatibilitäten eingeführt werden. Wir nennen Client B pessimistisch, weil er sich Sorgen über inkompatible zukünftige Änderungen macht (es ist in Ordnung, pessimistisch zu sein!).
Verhinderung von Version-Katastrophen
Von: www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html
Nehmen wir an, Sie hängen vom fnord-Gem der Version 2.y.z ab. Wenn Sie Ihre Abhängigkeit als „>= 2.0.0“ angeben, sind Sie also auf der sicheren Seite, oder? Was passiert, wenn fnord 3.0 herauskommt und nicht abwärtskompatibel mit 2.y.z ist? Ihre Sachen werden aufgrund der Verwendung von „>=“ kaputt gehen. Der bessere Weg ist, Ihre Abhängigkeit mit einem „ungefähren“ Versionsspezifizierer („~>“) anzugeben. Sie sind etwas verwirrend, also hier ist, wie die Abhängigkeitsspezifizierer funktionieren
Specification From ... To (exclusive) ">= 3.0" 3.0 ... ∞ "~> 3.0" 3.0 ... 4.0 "~> 3.0.0" 3.0.0 ... 3.1 "~> 3.5" 3.5 ... 4.0 "~> 3.5.0" 3.5.0 ... 3.6 "~> 3" 3.0 ... 4.0
Für das letzte Beispiel werden einstellige Versionen automatisch mit einer Null erweitert, um ein sinnvolles Ergebnis zu erzielen.
Öffentliche Klassenmethoden
Source
# File lib/rubygems/version.rb, line 173 def self.correct?(version) version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s) end
Wahr, wenn die Zeichenkette version den Anforderungen von RubyGems entspricht.
Source
# File lib/rubygems/version.rb, line 184 def self.create(input) if self === input # check yourself before you wreck yourself input else new input end end
Source
# File lib/rubygems/version.rb, line 206 def initialize(version) unless self.class.correct?(version) raise ArgumentError, "Malformed version number string #{version}" end # If version is an empty string convert it to 0 version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version)) @version = version.to_s # optimization to avoid allocation when given an integer, since we know # it's to_s won't have any spaces or dashes unless version.is_a?(Integer) @version = @version.strip @version.gsub!("-",".pre.") end @version = -@version @segments = nil end
Konstruiert eine Version aus der Zeichenkette version. Eine Versionszeichenkette ist eine Reihe von Ziffern oder ASCII-Buchstaben, die durch Punkte getrennt sind.
Öffentliche Instanzmethoden
Source
# File lib/rubygems/version.rb, line 345 def <=>(other) if String === other return unless self.class.correct?(other) return self <=> self.class.new(other) end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments lhsegments = canonical_segments rhsegments = other.canonical_segments lhsize = lhsegments.size rhsize = rhsegments.size limit = (lhsize > rhsize ? rhsize : lhsize) i = 0 while i < limit lhs = lhsegments[i] rhs = rhsegments[i] i += 1 next if lhs == rhs return -1 if String === lhs && Numeric === rhs return 1 if Numeric === lhs && String === rhs return lhs <=> rhs end lhs = lhsegments[i] if lhs.nil? rhs = rhsegments[i] while i < rhsize return 1 if String === rhs return -1 unless rhs.zero? rhs = rhsegments[i += 1] end else while i < lhsize return -1 if String === lhs return 1 unless lhs.zero? lhs = lhsegments[i += 1] end end 0 end
Vergleicht diese Version mit other und gibt -1, 0 oder 1 zurück, wenn die andere Version größer, gleich oder kleiner als diese ist. other muss eine Instanz von Gem::Version sein, der Vergleich mit anderen Typen kann eine Ausnahme auslösen.
Source
# File lib/rubygems/version.rb, line 327 def approximate_recommendation segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 recommendation = "~> #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end
Eine empfohlene Version für die Verwendung mit einer ~> Anforderung.
Source
# File lib/rubygems/version.rb, line 232 def bump @@bump[self] ||= begin segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop if segments.size > 1 segments[-1] = segments[-1].succ self.class.new segments.join(".") end end
Gibt ein neues Versions-Objekt zurück, bei dem die vorletzte Revisionsnummer um eins erhöht ist (z. B. 5.3.1 => 5.4).
Vorabveröffentlichungs-Teile (alpha), z. B. 5.3.1.b.2 => 5.4, werden ignoriert.
Source
# File lib/rubygems/version.rb, line 397 def canonical_segments @canonical_segments ||= begin # remove trailing 0 segments, using dot or letter as anchor # may leave a trailing dot which will be ignored by partition_segments canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "") # remove 0 segments before the first letter in a prerelease version canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease? partition_segments(canonical_version) end end
Entfernt nachgestellte Nullen-Segmente vor dem ersten Buchstaben oder am Ende der Version
Source
# File lib/rubygems/version.rb, line 247 def eql?(other) self.class === other && @version == other.version end
Source
# File lib/rubygems/version.rb, line 408 def freeze prerelease? _segments canonical_segments super end
Object#freeze aufSource
# File lib/rubygems/version.rb, line 267 def marshal_dump [@version] end
Gibt nur die rohe Versionszeichenkette aus, nicht das vollständige Objekt. Es ist eine Zeichenkette zur Abwärtskompatibilität (RubyGems 1.3.5 und früher).
Source
# File lib/rubygems/version.rb, line 275 def marshal_load(array) string = array[0] raise TypeError, "wrong version string" unless string.is_a?(String) initialize string end
Lädt benutzerdefiniertes Marshal-Format. Es ist eine Zeichenkette zur Abwärtskompatibilität (RubyGems 1.3.5 und früher).
Source
# File lib/rubygems/version.rb, line 295 def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end
Eine Version gilt als Vorabveröffentlichung, wenn sie einen Buchstaben enthält.
Source
# File lib/rubygems/version.rb, line 310 def release @@release[self] ||= if prerelease? segments = self.segments segments.pop while segments.any? {|s| String === s } self.class.new segments.join(".") else self end end
Die Veröffentlichung für diese Version (z. B. 1.2.0.a -> 1.2.0). Nicht-Vorabveröffentlichungs-Versionen geben sich selbst zurück.
Geschützte Instanzmethoden
Source
# File lib/rubygems/version.rb, line 417 def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load. # since this version object is cached in @@all, its @segments should be frozen @segments ||= partition_segments(@version) end
Source
# File lib/rubygems/version.rb, line 424 def partition_segments(ver) ver.scan(/\d+|[a-z]+/i).map! do |s| /\A\d/.match?(s) ? s.to_i : -s end.freeze end