versionian 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +8 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/README.adoc +874 -0
  6. data/Rakefile +12 -0
  7. data/docs/Gemfile +8 -0
  8. data/docs/_guides/custom-schemes.adoc +110 -0
  9. data/docs/_guides/index.adoc +12 -0
  10. data/docs/_pages/component-types.adoc +151 -0
  11. data/docs/_pages/declarative-schemes.adoc +260 -0
  12. data/docs/_pages/index.adoc +15 -0
  13. data/docs/_pages/range-matching.adoc +102 -0
  14. data/docs/_pages/schemes.adoc +68 -0
  15. data/docs/_references/api.adoc +251 -0
  16. data/docs/_references/index.adoc +13 -0
  17. data/docs/_references/schemes.adoc +410 -0
  18. data/docs/_tutorials/getting-started.adoc +119 -0
  19. data/docs/_tutorials/index.adoc +11 -0
  20. data/docs/index.adoc +287 -0
  21. data/lib/versionian/component_definition.rb +71 -0
  22. data/lib/versionian/component_types/base.rb +19 -0
  23. data/lib/versionian/component_types/date_part.rb +41 -0
  24. data/lib/versionian/component_types/enum.rb +32 -0
  25. data/lib/versionian/component_types/float.rb +23 -0
  26. data/lib/versionian/component_types/hash.rb +21 -0
  27. data/lib/versionian/component_types/integer.rb +23 -0
  28. data/lib/versionian/component_types/postfix.rb +51 -0
  29. data/lib/versionian/component_types/prerelease.rb +70 -0
  30. data/lib/versionian/component_types/registry.rb +35 -0
  31. data/lib/versionian/component_types/string.rb +21 -0
  32. data/lib/versionian/errors/invalid_scheme_error.rb +7 -0
  33. data/lib/versionian/errors/invalid_version_error.rb +7 -0
  34. data/lib/versionian/errors/parse_error.rb +7 -0
  35. data/lib/versionian/parsers/declarative.rb +214 -0
  36. data/lib/versionian/scheme_loader.rb +102 -0
  37. data/lib/versionian/scheme_registry.rb +34 -0
  38. data/lib/versionian/schemes/calver.rb +94 -0
  39. data/lib/versionian/schemes/composite.rb +82 -0
  40. data/lib/versionian/schemes/declarative.rb +138 -0
  41. data/lib/versionian/schemes/pattern.rb +236 -0
  42. data/lib/versionian/schemes/semantic.rb +136 -0
  43. data/lib/versionian/schemes/solover.rb +121 -0
  44. data/lib/versionian/schemes/wendtver.rb +141 -0
  45. data/lib/versionian/version.rb +6 -0
  46. data/lib/versionian/version_component.rb +28 -0
  47. data/lib/versionian/version_identifier.rb +121 -0
  48. data/lib/versionian/version_range.rb +61 -0
  49. data/lib/versionian/version_scheme.rb +68 -0
  50. data/lib/versionian.rb +64 -0
  51. data/lib/versius.rb +5 -0
  52. data/sig/versius.rbs +4 -0
  53. metadata +157 -0
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/docs/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'jekyll'
4
+ gem 'just-the-docs'
5
+ gem 'jekyll-asciidoc'
6
+ gem 'jekyll-seo-tag'
7
+ gem 'jekyll-sitemap'
8
+ gem 'rouge'
@@ -0,0 +1,110 @@
1
+ ---
2
+ layout: default
3
+ title: Custom Schemes
4
+ nav_order: 1
5
+ parent: Guides
6
+ ---
7
+
8
+ = Creating Custom Schemes
9
+
10
+ == General
11
+
12
+ You can create custom versioning schemes by subclassing `Versionian::VersionScheme` or using declarative YAML configuration.
13
+
14
+ == Declarative YAML approach
15
+
16
+ For most custom schemes, the declarative YAML approach is recommended:
17
+
18
+ [source,yaml]
19
+ ----
20
+ . Example scheme defined at `my_scheme.yaml`
21
+ ----
22
+ [source,yaml]
23
+ ----
24
+ name: my_yaml_scheme
25
+ type: declarative
26
+ description: My custom version scheme
27
+ components:
28
+ - name: major
29
+ type: integer
30
+ separator: "."
31
+ - name: minor
32
+ type: integer
33
+ separator: "."
34
+ - name: patch
35
+ type: integer
36
+ ----
37
+
38
+ [source,ruby]
39
+ ----
40
+ scheme = Versionian::SchemeLoader.from_yaml_file('my_scheme.yaml')
41
+ Versionian.register_scheme(:my_scheme, scheme)
42
+
43
+ version = scheme.parse("1.2.3")
44
+ puts version.major # => 1
45
+ puts version.minor # => 2
46
+ ----
47
+
48
+ == Ruby class approach
49
+
50
+ For complex versioning schemes, you can create custom scheme classes in Ruby.
51
+
52
+ [source,ruby]
53
+ ----
54
+ class FloatVerScheme < Versionian::VersionScheme
55
+ def initialize(name: :floatver, description: "Float-based versioning")
56
+ super
57
+ end
58
+
59
+ def parse(version_string)
60
+ float_value = Float(version_string)
61
+ Versionian::VersionIdentifier.new(
62
+ raw_string: version_string,
63
+ scheme: self,
64
+ components: [],
65
+ comparable_array: [float_value]
66
+ )
67
+ end
68
+
69
+ def compare_arrays(a, b)
70
+ a.first <=> b.first # Direct float comparison
71
+ end
72
+
73
+ def render(version)
74
+ version.raw_string
75
+ end
76
+ end
77
+
78
+ # Register and use
79
+ Versionian.register_scheme(:floatver, FloatVerScheme.new)
80
+ ----
81
+
82
+ == Required methods
83
+
84
+ A custom scheme must implement:
85
+
86
+ [cols="2,3"]
87
+ |===
88
+ |Method |Description
89
+
90
+ |#parse(string)
91
+ |Parse version string and return VersionIdentifier
92
+
93
+ |#compare_arrays(a, b)
94
+ |Compare two comparable arrays
95
+
96
+ |#valid?(string)
97
+ |Check if string is valid for this scheme
98
+ |===
99
+
100
+ == Registering custom schemes
101
+
102
+ [source,ruby]
103
+ ----
104
+ # Register with Versionian
105
+ Versionian.register_scheme(:my_scheme, MyScheme.new)
106
+
107
+ # Use like built-in schemes
108
+ scheme = Versionian.get_scheme(:my_scheme)
109
+ version = scheme.parse("1.2.3")
110
+ ----
@@ -0,0 +1,12 @@
1
+ ---
2
+ layout: default
3
+ title: Overview
4
+ nav_order: 1
5
+ has_children: true
6
+ ---
7
+
8
+ = Guides
9
+
10
+ How-to guides and advanced usage documentation.
11
+
12
+ * link:custom-schemes.html[Custom Schemes] - Creating your own version schemes
@@ -0,0 +1,151 @@
1
+ ---
2
+ layout: default
3
+ title: Component Types
4
+ nav_order: 3
5
+ parent: Core Topics
6
+ ---
7
+
8
+ = Component Types
9
+
10
+ == Overview
11
+
12
+ Versionian provides an extensible component type system that allows different versioning
13
+ schemes to parse and compare version components according to their specific rules.
14
+
15
+ == Built-in Component Types
16
+
17
+ === Integer
18
+
19
+ Numeric sequence components (major, minor, patch, etc.)
20
+
21
+ [source,ruby]
22
+ ----
23
+ component = Versionian::ComponentTypes::Integer.parse("42", definition)
24
+ component.value # => 42
25
+
26
+ # Comparison is numeric
27
+ component.to_comparable(42, definition) # => 42
28
+ ----
29
+
30
+ === Float
31
+
32
+ IEEE754 floating point numbers.
33
+
34
+ [source,ruby]
35
+ ----
36
+ component = Versionian::ComponentTypes::Float.parse("3.14", definition)
37
+ component.value # => 3.14
38
+ ----
39
+
40
+ === String
41
+
42
+ Arbitrary text components.
43
+
44
+ [source,ruby]
45
+ ----
46
+ component = Versionian::ComponentTypes::String.parse("beta", definition)
47
+ component.value # => "beta"
48
+ ----
49
+
50
+ === Enum
51
+
52
+ Ordered stages (alpha, beta, rc, stable, etc.)
53
+
54
+ [source,ruby]
55
+ ----
56
+ definition = Versionian::ComponentDefinition.new(
57
+ name: :stage,
58
+ type: :enum,
59
+ values: [:alpha, :beta, :rc, :stable],
60
+ order: [:alpha, :beta, :rc, :stable]
61
+ )
62
+
63
+ component = Versionian::ComponentTypes::Enum.parse("beta", definition)
64
+ component.value # => :beta
65
+
66
+ # Comparison uses order array index
67
+ component.to_comparable(:beta, definition) # => 1
68
+ component.to_comparable(:rc, definition) # => 2
69
+ ----
70
+
71
+ === DatePart
72
+
73
+ Calendar components (year, month, day, week, etc.) with range validation.
74
+
75
+ [source,ruby]
76
+ ----
77
+ definition = Versionian::ComponentDefinition.new(
78
+ name: :month,
79
+ type: :date_part,
80
+ subtype: :month
81
+ )
82
+
83
+ component = Versionian::ComponentTypes::DatePart.parse("06", definition)
84
+ component.value # => 6
85
+
86
+ # Validates ranges (month: 1-12, day: 1-31, etc.)
87
+ ----
88
+
89
+ === Prerelease
90
+
91
+ SemVer-style prerelease identifiers (alpha.1, beta.2, etc.)
92
+
93
+ [source,ruby]
94
+ ----
95
+ component = Versionian::ComponentTypes::Prerelease.parse("alpha.1", definition)
96
+ component.value # => [:alpha, 1]
97
+
98
+ # Comparison follows SemVer rules
99
+ component.to_comparable(:alpha.1, definition) # => [:alpha, 1]
100
+ ----
101
+
102
+ === Postfix
103
+
104
+ SoloVer-style postfixes (+hotfix, -beta, etc.)
105
+
106
+ [source,ruby]
107
+ ----
108
+ component = Versionian::ComponentTypes::Postfix.parse("+hotfix", definition)
109
+ component.value # => {prefix: "+", identifier: "hotfix"}
110
+ ----
111
+
112
+ === Hash
113
+
114
+ Git commit hashes or similar identifiers.
115
+
116
+ [source,ruby]
117
+ ----
118
+ component = Versionian::ComponentTypes::Hash.parse("abc123def", definition)
119
+ component.value # => "abc123def"
120
+
121
+ # Compares by length first, then lexicographically
122
+ component.to_comparable("abc123def", definition) # => [9, "abc123def"]
123
+ ----
124
+
125
+ == Custom Component Types
126
+
127
+ You can register your own component types:
128
+
129
+ [source,ruby]
130
+ ----
131
+ module Versionian
132
+ module ComponentTypes
133
+ class CustomType < Base
134
+ def self.parse(value, definition)
135
+ # Parse logic here
136
+ end
137
+
138
+ def self.to_comparable(value, definition)
139
+ # Return comparable value
140
+ end
141
+
142
+ def self.format(value)
143
+ # Format for display
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ # Register the type
150
+ Versionian::ComponentTypes.register(:custom, Versionian::ComponentTypes::CustomType)
151
+ ----
@@ -0,0 +1,260 @@
1
+ ---
2
+ layout: default
3
+ title: Declarative Schemes
4
+ nav_order: 2
5
+ parent: Core Topics
6
+ ---
7
+
8
+ = Declarative Schemes
9
+
10
+ == General
11
+
12
+ Declarative schemes define version formats using segment definitions instead of regular expressions. Each component specifies its type, separator, prefix, and optional status, allowing the parser to extract components in O(n) time.
13
+
14
+ Benefits over regex-based patterns:
15
+
16
+ * **No ReDoS vulnerabilities** - State machine parsing is immune to catastrophic backtracking
17
+ * **O(n) performance** - Single pass through the string
18
+ * **Declarative syntax** - Clear, readable component definitions
19
+ * **Type-aware parsing** - Each component type handles its own validation
20
+
21
+ == YAML schema
22
+
23
+ [source,yaml]
24
+ ----
25
+ name: my_scheme <1>
26
+ type: declarative <2>
27
+ description: Custom scheme <3>
28
+ components: <4>
29
+ - name: major
30
+ type: integer
31
+ separator: "." <5>
32
+ - name: minor
33
+ type: integer
34
+ separator: "." <6>
35
+ - name: patch
36
+ type: integer <7>
37
+ ----
38
+ <1> Unique scheme identifier (symbol).
39
+ <2> Scheme type: `declarative`.
40
+ <3> Optional description of the scheme.
41
+ <4> Array of component definitions in order.
42
+ <5> Separator that precedes this component.
43
+ <6> Each segment can have its own separator.
44
+ <7> Last segment typically has no separator.
45
+
46
+ Where,
47
+
48
+ `name`:: Symbol identifier for the scheme.
49
+ `type`:: Scheme type (`declarative`).
50
+ `description`:: Human-readable description.
51
+ `components`:: Array of component definitions.
52
+
53
+ .Component attributes
54
+
55
+ [cols="1,1"]
56
+ |===
57
+ |Attribute |Purpose
58
+
59
+ |name
60
+ |Component name (symbol)
61
+
62
+ |type
63
+ |Component type (integer, string, enum, date_part, etc.)
64
+
65
+ |separator
66
+ |String that precedes this component (e.g., `"."`)
67
+
68
+ |prefix
69
+ |String that identifies an optional component (e.g., `"-"`, `"+"`)
70
+
71
+ |optional
72
+ |Boolean: true if component may be absent
73
+
74
+ |include_prefix_in_value
75
+ |Boolean: include prefix in value for component type parsing
76
+
77
+ |subtype
78
+ |For date_part: `year`, `month`, `day`, `week`
79
+
80
+ |values
81
+ |For enum type: allowed values
82
+
83
+ |order
84
+ |For enum type: comparison order
85
+
86
+ |validate
87
+ |Hash with `min`/`max` validation rules
88
+ |===
89
+
90
+ == Loading declarative schemes
91
+
92
+ [source,ruby]
93
+ ----
94
+ # Load from YAML file
95
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/custom.yaml')
96
+
97
+ # Register for use
98
+ Versionian.register_scheme(:custom, scheme)
99
+
100
+ # Use the scheme
101
+ version = scheme.parse("1.2.3")
102
+ puts version.major # => 1
103
+ ----
104
+
105
+ == Examples
106
+
107
+ === Semantic versioning
108
+
109
+ [source,yaml]
110
+ ----
111
+ name: semantic
112
+ type: declarative
113
+ description: Semantic versioning
114
+ components:
115
+ - name: major
116
+ type: integer
117
+ separator: "."
118
+ - name: minor
119
+ type: integer
120
+ separator: "."
121
+ - name: patch
122
+ type: integer
123
+ - name: prerelease
124
+ type: string
125
+ prefix: "-"
126
+ optional: true
127
+ - name: build
128
+ type: string
129
+ prefix: "+"
130
+ optional: true
131
+ ----
132
+
133
+ Usage:
134
+
135
+ [source,ruby]
136
+ ----
137
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/semantic.yaml')
138
+
139
+ v = scheme.parse("1.2.3-alpha.1+build.123")
140
+ v.major # => 1
141
+ v.minor # => 2
142
+ v.patch # => 3
143
+ v.prerelease # => "alpha.1"
144
+ v.build # => "build.123"
145
+
146
+ # Optional components are nil when absent
147
+ v = scheme.parse("1.2.3")
148
+ v.prerelease # => nil
149
+ v.build # => nil
150
+ ----
151
+
152
+ === Calendar versioning
153
+
154
+ [source,yaml]
155
+ ----
156
+ name: calver
157
+ type: declarative
158
+ description: Calendar versioning YYYY.MM.DD
159
+ components:
160
+ - name: year
161
+ type: date_part
162
+ subtype: year
163
+ separator: "."
164
+ - name: month
165
+ type: date_part
166
+ subtype: month
167
+ separator: "."
168
+ - name: day
169
+ type: date_part
170
+ subtype: day
171
+ ----
172
+
173
+ Usage:
174
+
175
+ [source,ruby]
176
+ ----
177
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/calver.yaml')
178
+
179
+ v = scheme.parse("2024.01.17")
180
+ v.year # => 2024
181
+ v.month # => 1 (not "01")
182
+ v.day # => 17
183
+
184
+ # Invalid dates raise ParseError
185
+ scheme.parse("2024.13.17") # => Invalid month '13'
186
+ scheme.parse("2024.02.30") # => Invalid day '30'
187
+ ----
188
+
189
+ === SoloVer with postfix
190
+
191
+ [source,yaml]
192
+ ----
193
+ name: solover
194
+ type: declarative
195
+ description: Single number with optional postfix
196
+ components:
197
+ - name: number
198
+ type: integer
199
+ - name: postfix
200
+ type: postfix
201
+ prefix: "+"
202
+ optional: true
203
+ include_prefix_in_value: true <1>
204
+ ----
205
+ <1> Postfix type handles prefix internally, so include it in the value.
206
+
207
+ Usage:
208
+
209
+ [source,ruby]
210
+ ----
211
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/solover.yaml')
212
+
213
+ v = scheme.parse("5")
214
+ v.number # => 5
215
+ v.postfix # => nil
216
+
217
+ v = scheme.parse("5+hotfix")
218
+ v.number # => 5
219
+ v.postfix # => {:prefix=>"+", :identifier=>"hotfix"}
220
+
221
+ v = scheme.parse("5-beta")
222
+ v.number # => 5
223
+ v.postfix # => {:prefix=>"-", :identifier=>"beta"}
224
+ ----
225
+
226
+ === Enum with custom ordering
227
+
228
+ [source,yaml]
229
+ ----
230
+ name: stage
231
+ type: declarative
232
+ description: Release stage with ordering
233
+ components:
234
+ - name: major
235
+ type: integer
236
+ separator: "."
237
+ - name: minor
238
+ type: integer
239
+ separator: "."
240
+ - name: stage
241
+ type: enum
242
+ prefix: "-"
243
+ optional: true
244
+ values: [alpha, beta, rc, stable]
245
+ order: [alpha, beta, rc, stable]
246
+ ----
247
+
248
+ Usage:
249
+
250
+ [source,ruby]
251
+ ----
252
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/stage.yaml')
253
+
254
+ v = scheme.parse("1.2.0-beta")
255
+ v.stage # => :beta
256
+
257
+ # Comparison respects order
258
+ scheme.compare("1.2.0-alpha", "1.2.0-beta") # => -1 (alpha < beta)
259
+ scheme.compare("1.2.0-rc", "1.2.0-stable") # => -1 (rc < stable)
260
+ ----
@@ -0,0 +1,15 @@
1
+ ---
2
+ layout: default
3
+ title: Overview
4
+ nav_order: 1
5
+ has_children: true
6
+ ---
7
+
8
+ = Core Topics
9
+
10
+ Overview of Versionian's core concepts and features.
11
+
12
+ * link:schemes.html[Version Schemes] - Supported versioning schemes
13
+ * link:declarative-schemes.html[Declarative Schemes] - Custom segment-based schemes
14
+ * link:component-types.html[Component Types] - Type system reference
15
+ * link:range-matching.html[Range Matching] - Version range queries
@@ -0,0 +1,102 @@
1
+ ---
2
+ layout: default
3
+ title: Range Matching
4
+ nav_order: 4
5
+ parent: Core Topics
6
+ ---
7
+
8
+ = Range Matching
9
+
10
+ == Overview
11
+
12
+ Versionian supports matching versions against range specifications, useful for
13
+ dependency constraints, feature flags, and version compatibility checks.
14
+
15
+ == Range Types
16
+
17
+ [cols="2,3"]
18
+ |===
19
+ |Type |Description
20
+
21
+ |:equals
22
+ |Matches a specific version exactly
23
+
24
+ |:before
25
+ |Matches versions less than the boundary
26
+
27
+ |:after
28
+ |Matches versions greater than or equal to the boundary
29
+
30
+ |:between
31
+ |Matches versions within an inclusive range
32
+ |===
33
+
34
+ == Creating Ranges
35
+
36
+ [source,ruby]
37
+ ----
38
+ scheme = Versionian.get_scheme(:semantic)
39
+
40
+ # Exact version
41
+ equals_range = Versionian::VersionRange.new(:equals, scheme, version: "1.12.0")
42
+
43
+ # Before a version
44
+ before_range = Versionian::VersionRange.new(:before, scheme, version: "2.0.0")
45
+
46
+ # After a version (inclusive)
47
+ after_range = Versionian::VersionRange.new(:after, scheme, version: "1.12.0")
48
+
49
+ # Between two versions
50
+ between_range = Versionian::VersionRange.new(:between, scheme, from: "1.12.0", to: "2.0.0")
51
+ ----
52
+
53
+ == Matching Versions
54
+
55
+ [source,ruby]
56
+ ----
57
+ range = Versionian::VersionRange.new(:after, scheme, version: "1.12.0")
58
+
59
+ range.matches?("1.13.0") # => true
60
+ range.matches?("1.12.0") # => true (inclusive)
61
+ range.matches?("1.11.0") # => false
62
+ ----
63
+
64
+ == Scheme Methods
65
+
66
+ You can also use the scheme's `matches_range?` method:
67
+
68
+ [source,ruby]
69
+ ----
70
+ scheme.matches_range?("1.12.0", range) # => true
71
+ ----
72
+
73
+ == Use Cases
74
+
75
+ === Dependency Constraints
76
+
77
+ [source,ruby]
78
+ ----
79
+ require 'version'
80
+
81
+ MIN_VERSION = VersionRange.new(:after, scheme, version: "1.0.0")
82
+ MAX_VERSION = VersionRange.new(:before, scheme, version: "2.0.0")
83
+
84
+ def compatible?(version_string)
85
+ scheme.matches_range?(version_string, MIN_VERSION) &&
86
+ scheme.matches_range?(version_string, MAX_VERSION)
87
+ end
88
+
89
+ compatible?("1.5.0") # => true
90
+ compatible?("2.0.0") # => false
91
+ ----
92
+
93
+ === Feature Flags
94
+
95
+ [source,ruby]
96
+ ----
97
+ FEATURE_VERSION = VersionRange.new(:after, scheme, version: "2.0.0")
98
+
99
+ def new_feature_available?(current_version)
100
+ scheme.matches_range?(current_version, FEATURE_VERSION)
101
+ end
102
+ ----