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/README.adoc ADDED
@@ -0,0 +1,874 @@
1
+ = Versionian: Declarative versioning schemes
2
+ :toclevels:3
3
+ :toc:
4
+
5
+ image:https://img.shields.io/gem/v/versionian.svg[RubyGems Version]
6
+ image:https://img.shields.io/github/license/lutaml/versionian.svg[License]
7
+ image:https://github.com/lutaml/versionian/actions/workflows/rake.yml/badge.svg["Build", link="https://github.com/lutaml/versionian/actions/workflows/rake.yml"]
8
+
9
+ == Purpose
10
+
11
+ Versionian is a Ruby library for declaring, parsing, comparing, and rendering
12
+ version schemes. It provides model-driven primitives for defining how versions
13
+ work, supporting semantic versioning, calendar versioning, and unlimited custom
14
+ schemes through declarative YAML configuration.
15
+
16
+ === Why Versionian?
17
+
18
+ Ruby's built-in `Gem::Version` handles semantic versioning well, but real-world
19
+ projects use diverse versioning schemes that `Gem::Version` cannot parse or
20
+ compare correctly:
21
+
22
+ * **Calendar versioning** (`2024.01.17`, `2024.03`) - `Gem::Version` parses these but cannot enforce date validation
23
+ * **Single-number versioning** (`5`, `5+hotfix`, `5-beta`) - `Gem::Version` treats postfixes incorrectly
24
+ * **Human-readable versions** (`Alpha.1.5`, `Beta.2.0`) - `Gem::Version` cannot compare these
25
+ * **Hash-based versions** (`2024.01.abc123`) - `Gem::Version` has no concept of commit hashes
26
+ * **Custom schemes** - Every project has unique versioning needs
27
+
28
+ === How Versionian is different
29
+
30
+ [cols="h,1,1",options="header"]
31
+ |===
32
+ |Feature |Gem::Version |Versionian
33
+
34
+ |Supported schemes
35
+ |Semantic versioning only
36
+ |4 built-in schemes, unlimited custom
37
+
38
+ |Extensibility
39
+ |Not extensible
40
+ |Define custom schemes via declarative YAML or Ruby
41
+
42
+ |Component types
43
+ |Integers and dot-separated strings
44
+ |Integer, Float, Enum, DatePart, Hash, Postfix, Prerelease, String, Wildcard
45
+
46
+ |Comparison strategy
47
+ |Fixed SemVer rules
48
+ |Per-scheme comparison (lexicographic arrays)
49
+
50
+ |Validation
51
+ |Basic format validation
52
+ |Type-aware validation (date ranges, enum values, hash formats)
53
+
54
+ |Programmatic building
55
+ |Not supported
56
+ |Build versions from component values
57
+ |===
58
+
59
+ Versionian fills the gap between `Gem::Version`'s semantic versioning focus and the diverse versioning needs of real-world projects.
60
+
61
+ == Features
62
+
63
+ * <<built-in-schemes,Built-in version schemes>>
64
+ * <<declarative-schemes,Declarative custom schemes>>
65
+ * <<discovering-schemes,Discovering available schemes>>
66
+ * <<component-types,Extensible component type system>>
67
+ * <<range-matching,Version range matching>>
68
+ * <<yaml-config,YAML configuration with safe loading>>
69
+ * <<custom-schemes,Custom Ruby schemes>>
70
+
71
+ == Quick start
72
+
73
+ Get started with Versionian in three steps:
74
+
75
+ .Require the library
76
+ [source,ruby]
77
+ ----
78
+ require 'versionian'
79
+ ----
80
+
81
+ .Parse and compare versions
82
+ [source,ruby]
83
+ ----
84
+ # Use built-in semantic versioning
85
+ scheme = Versionian.get_scheme(:semantic)
86
+
87
+ version = scheme.parse("1.12.0")
88
+ puts version.to_s # => "1.12.0"
89
+
90
+ # Compare versions
91
+ result = scheme.compare("1.12.0", "2.0.0")
92
+ puts result # => -1 (less than)
93
+ ----
94
+
95
+ .Declare a custom scheme via YAML
96
+ [source,yaml]
97
+ ----
98
+ # schemes/my_scheme.yaml
99
+ name: my_scheme
100
+ type: declarative
101
+ description: Custom version scheme
102
+ components:
103
+ - name: major
104
+ type: integer
105
+ separator: "."
106
+ - name: minor
107
+ type: integer
108
+ separator: "."
109
+ - name: patch
110
+ type: integer
111
+ optional: true
112
+ ----
113
+
114
+ [source,ruby]
115
+ ----
116
+ # Load and use custom scheme
117
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/my_scheme.yaml')
118
+ Versionian.register_scheme(:my_scheme, scheme)
119
+
120
+ version = scheme.parse("1.2.3")
121
+ puts version.major # => 1
122
+ ----
123
+
124
+ == Architecture
125
+
126
+ Versionian follows a model-driven architecture where version schemes define
127
+ their own parsing, comparison, and rendering behavior.
128
+
129
+ === Lexicographic array comparison
130
+
131
+ Versionian uses lexicographic array comparison instead of weighted integer sums.
132
+ Each version stores a `comparable_array` where each element represents a
133
+ component in comparison order:
134
+
135
+ [source,ruby]
136
+ ----
137
+ # Version stores array for comparison
138
+ version.comparable_array # => [2024, 1, 17, :rc, 1]
139
+
140
+ # Comparison is element-by-element
141
+ [2024, 1, 17] <=> [2024, 1, 18] # => -1
142
+ [2024, 1, 17] <=> [2024, 2, 1] # => -1
143
+ [2024, 1, 17] <=> [2024, 1, 17] # => 0
144
+ ----
145
+
146
+ This approach avoids integer overflow, provides better performance, and allows
147
+ each component type to define its own comparison semantics.
148
+
149
+ === Parse versus build
150
+
151
+ Versionian provides two ways to create version identifiers:
152
+
153
+ * **Parse**: Extract components from a version string
154
+ * **Build**: Create a version from component values
155
+
156
+ [source,ruby]
157
+ ----
158
+ # Parse: from string
159
+ version = scheme.parse("1.12.0")
160
+
161
+ # Build: from component values
162
+ version = scheme.build(major: 1, minor: 12, patch: 0)
163
+ ----
164
+
165
+ Both methods return the same `VersionIdentifier` object with a
166
+ `comparable_array` for comparison.
167
+
168
+ === Object model
169
+
170
+ .Versionian object model
171
+ [source]
172
+ ----
173
+ Versionian::VersionScheme (abstract)
174
+ ├── parse(version_string) -> VersionIdentifier
175
+ ├── build(component_values) -> VersionIdentifier
176
+ ├── compare_arrays(a, b) -> Integer
177
+ ├── render(version) -> String
178
+ └── matches_range?(version_string, range) -> Boolean
179
+
180
+ Implemented Schemes:
181
+ ├── Semantic (SemVer)
182
+ ├── CalVer (Calendar versioning)
183
+ ├── SoloVer (Single number with postfix)
184
+ ├── WendtVer (Auto-incrementing)
185
+ └── Declarative (Custom segment-based)
186
+
187
+ VersionIdentifier objects:
188
+ ├── raw_string (original input)
189
+ ├── scheme (reference to scheme)
190
+ ├── components (array of VersionComponent)
191
+ └── comparable_array (for lexicographic comparison)
192
+
193
+ Component type registry:
194
+ ├── Integer (numeric sequences)
195
+ ├── Float (IEEE754 floats)
196
+ ├── String (arbitrary text)
197
+ ├── Enum (ordered stages)
198
+ ├── DatePart (calendar components)
199
+ ├── Prerelease (SemVer prereleases)
200
+ ├── Postfix (SoloVer suffixes)
201
+ ├── Hash (git commit hashes)
202
+ └── Custom types (user-defined)
203
+ ----
204
+
205
+ == Installation
206
+
207
+ Add this line to your application's Gemfile:
208
+
209
+ [source,ruby]
210
+ ----
211
+ gem 'versionian'
212
+ ----
213
+
214
+ And then execute:
215
+
216
+ [source,shell]
217
+ ----
218
+ bundle install
219
+ ----
220
+
221
+ Or install it yourself as:
222
+
223
+ [source,shell]
224
+ ----
225
+ gem install versionian
226
+ ----
227
+
228
+ == Features
229
+
230
+ [[built-in-schemes]]
231
+ === Built-in schemes
232
+
233
+ [[built-in-schemes-general]]
234
+ ==== General
235
+
236
+ Versionian provides built-in support for common versioning schemes.
237
+ Each scheme is pre-registered and ready to use.
238
+
239
+ [[built-in-schemes-usage]]
240
+ ==== Usage
241
+
242
+ [source,ruby]
243
+ ----
244
+ scheme = Versionian.get_scheme(:semantic) <1>
245
+ version = scheme.parse("1.12.0") <2>
246
+ result = scheme.compare("1.12.0", "2.0.0") <3>
247
+ ----
248
+ <1> Get a built-in scheme by name.
249
+ <2> Parse a version string.
250
+ <3> Compare two versions (returns -1, 0, or 1).
251
+
252
+ Where,
253
+
254
+ `scheme`:: A VersionScheme instance.
255
+ `version`:: A VersionIdentifier object.
256
+ `result`:: Integer: -1 (less than), 0 (equal), 1 (greater than).
257
+
258
+ [[built-in-schemes-list]]
259
+ ==== Supported schemes
260
+
261
+ [cols="1,1,1",options="header"]
262
+ |===
263
+ |Scheme |Format |Description
264
+
265
+ |SemVer
266
+ |`MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]`
267
+ |Semantic versioning per semver.org
268
+
269
+ |CalVer
270
+ |`YYYY.MM.DD`, `YYYY.MM`, `YY.MM.DD`
271
+ |Calendar versioning with date validation
272
+
273
+ |SoloVer
274
+ |`N[+|-]postfix`
275
+ |Single number with optional postfix
276
+
277
+ |WendtVer
278
+ |`MAJOR.MINOR.PATCH.BUILD`
279
+ |Auto-incrementing with carryover
280
+ |===
281
+
282
+ [[declarative-schemes]]
283
+ === Declarative custom schemes
284
+
285
+ [[declarative-schemes-general]]
286
+ ==== General
287
+
288
+ Declarative schemes define version formats using segment definitions instead of
289
+ regular expressions. Each component specifies its type, separator, prefix, and
290
+ optional status, allowing the parser to extract components in O(n) time.
291
+
292
+ Benefits over regex-based patterns:
293
+
294
+ * **No ReDoS vulnerabilities** - State machine parsing is immune to catastrophic backtracking
295
+ * **O(n) performance** - Single pass through the string
296
+ * **Declarative syntax** - Clear, readable component definitions
297
+ * **Type-aware parsing** - Each component type handles its own validation
298
+
299
+ [[declarative-schemes-yaml]]
300
+ ==== YAML schema
301
+
302
+ [source,yaml]
303
+ ----
304
+ name: my_scheme <1>
305
+ type: declarative <2>
306
+ description: Custom scheme <3>
307
+ components: <4>
308
+ - name: major
309
+ type: integer
310
+ separator: "." <5>
311
+ - name: minor
312
+ type: integer
313
+ separator: "." <6>
314
+ - name: patch
315
+ type: integer <7>
316
+ ----
317
+ <1> Unique scheme identifier (symbol).
318
+ <2> Scheme type: `declarative`.
319
+ <3> Optional description of the scheme.
320
+ <4> Array of component definitions in order.
321
+ <5> Separator that precedes this component.
322
+ <6> Each segment can have its own separator.
323
+ <7> Last segment typically has no separator.
324
+
325
+ Where,
326
+
327
+ `name`:: Symbol identifier for the scheme.
328
+ `type:: Scheme type (`declarative`).
329
+ `description:: Human-readable description.
330
+ `components:: Array of component definitions.
331
+
332
+ .Component attributes
333
+
334
+ [cols="1,1",options="header"]
335
+ |===
336
+ |Attribute |Purpose
337
+
338
+ |name
339
+ |Component name (symbol)
340
+
341
+ |type
342
+ |Component type (integer, string, enum, date_part, etc.)
343
+
344
+ |separator
345
+ |String that precedes this component (e.g., `"."`)
346
+
347
+ |prefix
348
+ |String that identifies an optional component (e.g., `"-"`, `"+"`)
349
+
350
+ |optional
351
+ |Boolean: true if component may be absent
352
+
353
+ |include_prefix_in_value
354
+ |Boolean: include prefix in value for component type parsing
355
+
356
+ |subtype
357
+ |For date_part: `year`, `month`, `day`, `week`
358
+
359
+ |values
360
+ |For enum type: allowed values
361
+
362
+ |order
363
+ |For enum type: comparison order
364
+
365
+ |validate
366
+ |Hash with `min`/`max` validation rules
367
+ |===
368
+
369
+ [[declarative-schemes-loading]]
370
+ ==== Loading declarative schemes
371
+
372
+ [source,ruby]
373
+ ----
374
+ # Load from YAML file
375
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/custom.yaml')
376
+
377
+ # Register for use
378
+ Versionian.register_scheme(:custom, scheme)
379
+
380
+ # Use the scheme
381
+ version = scheme.parse("1.2.3")
382
+ puts version.major # => 1
383
+ ----
384
+
385
+ [[declarative-schemes-examples]]
386
+ ==== Declarative examples
387
+
388
+ [[semantic-declarative]]
389
+ ===== Semantic versioning
390
+
391
+ [source,yaml]
392
+ ----
393
+ name: semantic
394
+ type: declarative
395
+ description: Semantic versioning
396
+ components:
397
+ - name: major
398
+ type: integer
399
+ separator: "."
400
+ - name: minor
401
+ type: integer
402
+ separator: "."
403
+ - name: patch
404
+ type: integer
405
+ - name: prerelease
406
+ type: string
407
+ prefix: "-"
408
+ optional: true
409
+ - name: build
410
+ type: string
411
+ prefix: "+"
412
+ optional: true
413
+ ----
414
+
415
+ Usage:
416
+ [source,ruby]
417
+ ----
418
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/semantic.yaml')
419
+
420
+ v = scheme.parse("1.2.3-alpha.1+build.123")
421
+ v.major # => 1
422
+ v.minor # => 2
423
+ v.patch # => 3
424
+ v.prerelease # => "alpha.1"
425
+ v.build # => "build.123"
426
+
427
+ # Optional components are nil when absent
428
+ v = scheme.parse("1.2.3")
429
+ v.prerelease # => nil
430
+ v.build # => nil
431
+ ----
432
+
433
+ [[calver-declarative]]
434
+ ===== Calendar versioning
435
+
436
+ [source,yaml]
437
+ ----
438
+ name: calver
439
+ type: declarative
440
+ description: Calendar versioning YYYY.MM.DD
441
+ components:
442
+ - name: year
443
+ type: date_part
444
+ subtype: year
445
+ separator: "."
446
+ - name: month
447
+ type: date_part
448
+ subtype: month
449
+ separator: "."
450
+ - name: day
451
+ type: date_part
452
+ subtype: day
453
+ ----
454
+
455
+ Usage:
456
+ [source,ruby]
457
+ ----
458
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/calver.yaml')
459
+
460
+ v = scheme.parse("2024.01.17")
461
+ v.year # => 2024
462
+ v.month # => 1 (not "01")
463
+ v.day # => 17
464
+
465
+ # Invalid dates raise ParseError
466
+ scheme.parse("2024.13.17") # => Invalid month '13'
467
+ scheme.parse("2024.02.30") # => Invalid day '30'
468
+ ----
469
+
470
+ [[solover-declarative]]
471
+ ===== SoloVer with postfix
472
+
473
+ [source,yaml]
474
+ ----
475
+ name: solover
476
+ type: declarative
477
+ description: Single number with optional postfix
478
+ components:
479
+ - name: number
480
+ type: integer
481
+ - name: postfix
482
+ type: postfix
483
+ prefix: "+"
484
+ optional: true
485
+ include_prefix_in_value: true <1>
486
+ ----
487
+ <1> Postfix type handles prefix internally, so include it in the value.
488
+
489
+ Usage:
490
+ [source,ruby]
491
+ ----
492
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/solover.yaml')
493
+
494
+ v = scheme.parse("5")
495
+ v.number # => 5
496
+ v.postfix # => nil
497
+
498
+ v = scheme.parse("5+hotfix")
499
+ v.number # => 5
500
+ v.postfix # => {:prefix=>"+", :identifier=>"hotfix"}
501
+
502
+ v = scheme.parse("5-beta")
503
+ v.number # => 5
504
+ v.postfix # => {:prefix=>"-", :identifier=>"beta"}
505
+ ----
506
+
507
+ [[enum-declarative]]
508
+ ===== Enum with custom ordering
509
+
510
+ [source,yaml]
511
+ ----
512
+ name: stage
513
+ type: declarative
514
+ description: Release stage with ordering
515
+ components:
516
+ - name: major
517
+ type: integer
518
+ separator: "."
519
+ - name: minor
520
+ type: integer
521
+ separator: "."
522
+ - name: stage
523
+ type: enum
524
+ prefix: "-"
525
+ optional: true
526
+ values: [alpha, beta, rc, stable]
527
+ order: [alpha, beta, rc, stable]
528
+ ----
529
+
530
+ Usage:
531
+ [source,ruby]
532
+ ----
533
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/stage.yaml')
534
+
535
+ v = scheme.parse("1.2.0-beta")
536
+ v.stage # => :beta
537
+
538
+ # Comparison respects order
539
+ scheme.compare("1.2.0-alpha", "1.2.0-beta") # => -1 (alpha < beta)
540
+ scheme.compare("1.2.0-rc", "1.2.0-stable") # => -1 (rc < stable)
541
+ ----
542
+
543
+ [[discovering-schemes]]
544
+ === Discovering available schemes
545
+
546
+ Versionian provides several ways to discover available schemes:
547
+
548
+ .List registered schemes
549
+ [source,ruby]
550
+ ----
551
+ # List all registered scheme names
552
+ scheme_names = Versionian.scheme_registry.registered
553
+ puts scheme_names.inspect # => [:semantic, :calver, :solover, :wendtver]
554
+
555
+ # Check if a scheme exists
556
+ Versionian.scheme_registry.registered.include?(:semantic) # => true
557
+ ----
558
+
559
+ .Detect scheme from version string
560
+ [source,ruby]
561
+ ----
562
+ # Auto-detect which scheme matches a version string
563
+ detected = Versionian.detect_scheme("1.2.3")
564
+ puts detected.name # => :semantic
565
+
566
+ detected = Versionian.detect_scheme("2024.01.17")
567
+ puts detected.name # => :calver
568
+
569
+ detected = Versionian.detect_scheme("5+hotfix")
570
+ puts detected.name # => :solover
571
+
572
+ # Returns nil for unrecognised version strings
573
+ detected = Versionian.detect_scheme("not-a-version")
574
+ puts detected # => nil
575
+ ----
576
+
577
+ .Get and use a scheme
578
+ [source,ruby]
579
+ ----
580
+ # Get a scheme by name
581
+ scheme = Versionian.get_scheme(:semantic)
582
+
583
+ # Check scheme capabilities
584
+ puts scheme.supports?("1.2.3") # => true
585
+ puts scheme.supports?("invalid") # => false
586
+
587
+ # Parse and compare
588
+ version = scheme.parse("1.12.0")
589
+ puts version.to_s # => "1.12.0"
590
+
591
+ # Raises error for unknown schemes
592
+ begin
593
+ Versionian.get_scheme(:unknown)
594
+ rescue Versionian::Errors::InvalidSchemeError => e
595
+ puts e.message # => "Unknown scheme: unknown"
596
+ end
597
+ ----
598
+
599
+ [[component-types]]
600
+ === Component type system
601
+
602
+ [[component-types-general]]
603
+ ==== General
604
+
605
+ Versionian provides built-in component types for parsing and comparing version
606
+ components. Each type defines its own parsing, comparison, and formatting behavior.
607
+
608
+ [[component-types-table]]
609
+ ==== Available types
610
+
611
+ [cols="1,1,1",options="header"]
612
+ |===
613
+ |Type |Purpose |Comparison
614
+
615
+ |Integer
616
+ |Numeric sequences (major, minor, patch)
617
+ |Numeric
618
+
619
+ |Float
620
+ |FloatVer, IEEE754 floats
621
+ |Float comparison
622
+
623
+ |String
624
+ |Arbitrary text
625
+ |Lexicographic
626
+
627
+ |Enum
628
+ |Ordered stages (alpha < beta < rc)
629
+ |Order array index
630
+
631
+ |DatePart
632
+ |Calendar components (year, month, day, week)
633
+ |Numeric with validation
634
+
635
+ |Prerelease
636
+ |SemVer prereleases (alpha.1, beta.2)
637
+ |SemVer rules (numeric < alphanumeric)
638
+
639
+ |Postfix
640
+ |SoloVer suffixes (+hotfix, -beta)
641
+ |Prefix-sensitive (none < + < -)
642
+
643
+ |Hash
644
+ |Git commit hashes
645
+ |Length first, then lexicographic
646
+
647
+ |Wildcard
648
+ |Ignored components
649
+ |Always equal
650
+ |===
651
+
652
+ [[component-types-reference]]
653
+ ==== Type reference
654
+
655
+ .Integer type
656
+ [source,yaml]
657
+ ----
658
+ components:
659
+ - name: major
660
+ type: integer
661
+ ----
662
+
663
+ .Float type
664
+ [source,yaml]
665
+ ----
666
+ components:
667
+ - name: version
668
+ type: float
669
+ ----
670
+
671
+ .String type
672
+ [source,yaml]
673
+ ----
674
+ components:
675
+ - name: build
676
+ type: string
677
+ ----
678
+
679
+ .Enum type with custom ordering
680
+ [source,yaml]
681
+ ----
682
+ components:
683
+ - name: stage
684
+ type: enum
685
+ prefix: "-"
686
+ optional: true
687
+ values: [alpha, beta, rc, stable]
688
+ order: [alpha, beta, rc, stable]
689
+ ----
690
+
691
+ .DatePart type with validation
692
+ [source,yaml]
693
+ ----
694
+ components:
695
+ - name: year
696
+ type: date_part
697
+ subtype: year
698
+ separator: "."
699
+ - name: month
700
+ type: date_part
701
+ subtype: month
702
+ separator: "."
703
+ - name: day
704
+ type: date_part
705
+ subtype: day
706
+ ----
707
+
708
+ .Prerelease type (SemVer)
709
+ [source,yaml]
710
+ ----
711
+ components:
712
+ - name: prerelease
713
+ type: prerelease
714
+ prefix: "-"
715
+ optional: true
716
+ ----
717
+
718
+ .Postfix type (SoloVer)
719
+ [source,yaml]
720
+ ----
721
+ components:
722
+ - name: postfix
723
+ type: postfix
724
+ prefix: "+"
725
+ optional: true
726
+ include_prefix_in_value: true
727
+ ----
728
+
729
+ .Hash type
730
+ [source,yaml]
731
+ ----
732
+ components:
733
+ - name: hash
734
+ type: hash
735
+ ----
736
+
737
+ [[range-matching]]
738
+ === Version range matching
739
+
740
+ [[range-matching-general]]
741
+ ==== General
742
+
743
+ Versionian supports matching versions against range specifications.
744
+
745
+ [[range-matching-types]]
746
+ ==== Range types
747
+
748
+ * **equals**: Exact version match
749
+ * **before**: Less than boundary version
750
+ * **after**: Greater than or equal to boundary version
751
+ * **between**: Inclusive range between two versions
752
+
753
+ [[range-matching-usage]]
754
+ ==== Usage
755
+
756
+ [source,ruby]
757
+ ----
758
+ scheme = Versionian.get_scheme(:semantic)
759
+
760
+ # After a specific version (inclusive)
761
+ range = Versionian::VersionRange.new(:after, scheme, version: "1.12.0")
762
+ range.matches?("1.13.0") # => true
763
+ range.matches?("1.12.0") # => true
764
+ range.matches?("1.11.0") # => false
765
+
766
+ # Between two versions
767
+ range = Versionian::VersionRange.new(:between, scheme, from: "1.12.0", to: "2.0.0")
768
+ range.matches?("1.15.0") # => true
769
+ range.matches?("2.1.0") # => false
770
+ ----
771
+
772
+ [[custom-schemes]]
773
+ === Custom Ruby schemes
774
+
775
+ [[custom-schemes-general]]
776
+ ==== General
777
+
778
+ For complex versioning schemes, you can create custom scheme classes in Ruby.
779
+
780
+ [[custom-schemes-example]]
781
+ ==== Custom scheme example
782
+
783
+ [source,ruby]
784
+ ----
785
+ class FloatVerScheme < Versionian::VersionScheme
786
+ def initialize(name: :floatver, description: "Float-based versioning")
787
+ super
788
+ end
789
+
790
+ def parse(version_string)
791
+ float_value = Float(version_string)
792
+ Versionian::VersionIdentifier.new(
793
+ raw_string: version_string,
794
+ scheme: self,
795
+ components: [],
796
+ comparable_array: [float_value]
797
+ )
798
+ end
799
+
800
+ def compare_arrays(a, b)
801
+ a.first <=> b.first # Direct float comparison
802
+ end
803
+
804
+ def render(version)
805
+ version.raw_string
806
+ end
807
+ end
808
+
809
+ # Register and use
810
+ Versionian.register_scheme(:floatver, FloatVerScheme.new)
811
+ ----
812
+
813
+ [[yaml-config]]
814
+ === YAML configuration
815
+
816
+ [[yaml-config-safety]]
817
+ ==== Safe loading
818
+
819
+ Versionian uses `Psych.safe_load` with a restricted set of permitted classes for
820
+ security:
821
+
822
+ [source,ruby]
823
+ ----
824
+ ALLOWED_CLASSES = [Symbol, Integer, String, Array, Hash, TrueClass, FalseClass, NilClass].freeze
825
+ ----
826
+
827
+ [[yaml-config-loading]]
828
+ ==== Loading schemes
829
+
830
+ [source,ruby]
831
+ ----
832
+ # From file
833
+ scheme = Versionian::SchemeLoader.from_yaml_file('schemes/custom.yaml')
834
+
835
+ # From string
836
+ yaml_string = <<~YAML
837
+ name: custom
838
+ type: declarative
839
+ components:
840
+ - { name: major, type: integer, separator: "." }
841
+ - { name: minor, type: integer }
842
+ YAML
843
+
844
+ scheme = Versionian::SchemeLoader.from_yaml_string(yaml_string)
845
+ ----
846
+
847
+ == Development
848
+
849
+ After checking out the repo, run:
850
+
851
+ [source,shell]
852
+ ----
853
+ bundle install
854
+ ----
855
+
856
+ Run tests:
857
+
858
+ [source,shell]
859
+ ----
860
+ bundle exec rspec
861
+ ----
862
+
863
+ Run linting:
864
+
865
+ [source,shell]
866
+ ----
867
+ bundle exec rubocop
868
+ ----
869
+
870
+ == Copyright and license
871
+
872
+ Copyright Ribose.
873
+
874
+ MIT License - see LICENSE file for details.