equilibrium 0.1.0 → 0.2.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -3
  3. data/README.md +41 -37
  4. data/lib/equilibrium/analyzer.rb +16 -14
  5. data/lib/equilibrium/canonical_version_mapper.rb +19 -0
  6. data/lib/equilibrium/catalog_builder.rb +31 -15
  7. data/lib/equilibrium/cli.rb +17 -172
  8. data/lib/equilibrium/commands/actual_command.rb +36 -0
  9. data/lib/equilibrium/commands/analyze_command.rb +49 -0
  10. data/lib/equilibrium/commands/catalog_command.rb +38 -0
  11. data/lib/equilibrium/commands/expected_command.rb +36 -0
  12. data/lib/equilibrium/commands/uncatalog_command.rb +42 -0
  13. data/lib/equilibrium/commands/version_command.rb +16 -0
  14. data/lib/equilibrium/mixins/error_handling.rb +34 -0
  15. data/lib/equilibrium/mixins/input_output.rb +40 -0
  16. data/lib/equilibrium/registry_client.rb +57 -45
  17. data/lib/equilibrium/repository_tags_service.rb +32 -0
  18. data/lib/equilibrium/repository_url_validator.rb +14 -0
  19. data/lib/equilibrium/schema_validator.rb +10 -2
  20. data/lib/equilibrium/schemas/analyzer_output.rb +8 -2
  21. data/lib/equilibrium/schemas/catalog.rb +16 -9
  22. data/lib/equilibrium/schemas/expected_actual.rb +1 -1
  23. data/lib/equilibrium/schemas/registry_api.rb +1 -1
  24. data/lib/equilibrium/summary_formatter.rb +1 -1
  25. data/lib/equilibrium/tag_data_builder.rb +16 -0
  26. data/lib/equilibrium/tag_processor.rb +24 -14
  27. data/lib/equilibrium/tag_sorter.rb +39 -0
  28. data/lib/equilibrium/tags_operation_service.rb +32 -0
  29. data/lib/equilibrium/version.rb +1 -1
  30. data/lib/equilibrium.rb +1 -1
  31. metadata +18 -4
  32. data/lib/equilibrium/semantic_version.rb +0 -24
@@ -8,6 +8,14 @@ module Equilibrium
8
8
  new.compute_virtual_tags(semantic_tags)
9
9
  end
10
10
 
11
+ def self.filter_semantic_tags(tagged_digests)
12
+ new.filter_semantic_tags(tagged_digests)
13
+ end
14
+
15
+ def self.filter_mutable_tags(tagged_digests)
16
+ new.filter_mutable_tags(tagged_digests)
17
+ end
18
+
11
19
  def compute_virtual_tags(semantic_tags)
12
20
  return {"digests" => {}, "canonical_versions" => {}} if semantic_tags.empty?
13
21
 
@@ -55,30 +63,32 @@ module Equilibrium
55
63
  {"digests" => digests, "canonical_versions" => canonical_versions}
56
64
  end
57
65
 
58
- def filter_semantic_tags(all_tags)
59
- # Filter semantic tags (canonical_tags.json): exact major.minor.patch format
60
- filtered = all_tags.select { |tag, _| semantic_version?(tag) }
61
- # Sort by key in reverse order (matching original jq: sort_by(.key) | reverse)
62
- filtered.sort_by { |tag, _| tag }.reverse.to_h
66
+ def filter_semantic_tags(tagged_digests)
67
+ tagged_digests.select { |tag, _| semantic_version?(tag) }
63
68
  end
64
69
 
65
- def filter_mutable_tags(all_tags)
66
- # Filter mutable tags (actual_tags.json): latest, digits, or major.minor format
67
- filtered = all_tags.select { |tag, _| mutable_tag?(tag) }
68
- # Sort by key in reverse order (matching original jq: sort_by(.key) | reverse)
69
- filtered.sort_by { |tag, _| tag }.reverse.to_h
70
+ def filter_mutable_tags(tagged_digests)
71
+ tagged_digests.select { |tag, _| mutable_tag?(tag) }
70
72
  end
71
73
 
72
74
  private
73
75
 
74
76
  def semantic_version?(tag)
75
- SemanticVersion.valid?(tag)
77
+ # Strictly validate MAJOR.MINOR.PATCH format where:
78
+ # - MAJOR, MINOR, PATCH are non-negative integers
79
+ # - No leading zeros (except for '0' itself)
80
+ # - No prefixes (like 'v1.2.3', 'release-1.2.3')
81
+ # - No suffixes (like '1.2.3-alpha', '1.2.3+build')
82
+ # - No prereleases (like '1.2.3-rc.1', '1.2.3-beta.2')
83
+ tag.match?(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/)
76
84
  end
77
85
 
78
86
  def mutable_tag?(tag)
79
- tag.match?(/^latest$/) ||
80
- tag.match?(/^[0-9]+$/) ||
81
- tag.match?(/^[0-9]+\.[0-9]+$/)
87
+ # Validate mutable tags: latest, major versions (digits only), or major.minor format
88
+ # - 'latest' is the special case for the highest overall version
89
+ # - Major versions: non-negative integers without leading zeros (e.g., '1', '0', '42')
90
+ # - Minor versions: MAJOR.MINOR format with same zero-leading rules (e.g., '1.2', '0.9')
91
+ tag.match?(/^(latest|(0|[1-9]\d*)(\.(0|[1-9]\d*))?)$/)
82
92
  end
83
93
  end
84
94
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Equilibrium
4
+ class TagSorter
5
+ # Sort tags in descending version order: latest first, then major versions (descending), then minor versions (descending)
6
+ def self.sort_descending(tags_hash)
7
+ return {} if tags_hash.nil? || tags_hash.empty?
8
+
9
+ sorted = {}
10
+
11
+ # Add latest first if present
12
+ if tags_hash.key?("latest")
13
+ sorted["latest"] = tags_hash["latest"]
14
+ end
15
+
16
+ # Sort other tags by version (descending)
17
+ other_tags = tags_hash.keys.reject { |k| k == "latest" }
18
+ sorted_tags = other_tags.sort_by do |tag|
19
+ if tag.match?(/^[0-9]+$/)
20
+ # Major version: sort by numeric value (descending)
21
+ [-tag.to_i]
22
+ elsif tag.match?(/^[0-9]+\.[0-9]+$/)
23
+ # Minor version: sort by version (descending)
24
+ parts = tag.split(".").map(&:to_i)
25
+ [-parts[0], -parts[1]]
26
+ else
27
+ # Fallback for any unexpected formats - sort alphabetically
28
+ [1, tag]
29
+ end
30
+ end
31
+
32
+ sorted_tags.each do |tag|
33
+ sorted[tag] = tags_hash[tag]
34
+ end
35
+
36
+ sorted
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "repository_tags_service"
4
+ require_relative "tag_data_builder"
5
+
6
+ module Equilibrium
7
+ class TagsOperationService
8
+ def self.generate_expected_output(repository_url)
9
+ tag_data = RepositoryTagsService.generate_expected_tags(repository_url)
10
+ repository_name = repository_url.split("/").last
11
+
12
+ TagDataBuilder.build_output(
13
+ repository_url,
14
+ repository_name,
15
+ tag_data["digests"],
16
+ tag_data["canonical_versions"]
17
+ )
18
+ end
19
+
20
+ def self.generate_actual_output(repository_url)
21
+ tag_data = RepositoryTagsService.generate_actual_tags(repository_url)
22
+ repository_name = repository_url.split("/").last
23
+
24
+ TagDataBuilder.build_output(
25
+ repository_url,
26
+ repository_name,
27
+ tag_data["digests"],
28
+ tag_data["canonical_versions"]
29
+ )
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Equilibrium
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/equilibrium.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "equilibrium/version"
4
- require_relative "equilibrium/semantic_version"
5
4
  require_relative "equilibrium/registry_client"
6
5
  require_relative "equilibrium/tag_processor"
6
+ require_relative "equilibrium/tag_sorter"
7
7
  require_relative "equilibrium/catalog_builder"
8
8
  require_relative "equilibrium/analyzer"
9
9
  require_relative "equilibrium/schemas/expected_actual"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: equilibrium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Hsu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-05 00:00:00.000000000 Z
11
+ date: 2025-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -71,17 +71,30 @@ files:
71
71
  - exe/equilibrium
72
72
  - lib/equilibrium.rb
73
73
  - lib/equilibrium/analyzer.rb
74
+ - lib/equilibrium/canonical_version_mapper.rb
74
75
  - lib/equilibrium/catalog_builder.rb
75
76
  - lib/equilibrium/cli.rb
77
+ - lib/equilibrium/commands/actual_command.rb
78
+ - lib/equilibrium/commands/analyze_command.rb
79
+ - lib/equilibrium/commands/catalog_command.rb
80
+ - lib/equilibrium/commands/expected_command.rb
81
+ - lib/equilibrium/commands/uncatalog_command.rb
82
+ - lib/equilibrium/commands/version_command.rb
83
+ - lib/equilibrium/mixins/error_handling.rb
84
+ - lib/equilibrium/mixins/input_output.rb
76
85
  - lib/equilibrium/registry_client.rb
86
+ - lib/equilibrium/repository_tags_service.rb
87
+ - lib/equilibrium/repository_url_validator.rb
77
88
  - lib/equilibrium/schema_validator.rb
78
89
  - lib/equilibrium/schemas/analyzer_output.rb
79
90
  - lib/equilibrium/schemas/catalog.rb
80
91
  - lib/equilibrium/schemas/expected_actual.rb
81
92
  - lib/equilibrium/schemas/registry_api.rb
82
- - lib/equilibrium/semantic_version.rb
83
93
  - lib/equilibrium/summary_formatter.rb
94
+ - lib/equilibrium/tag_data_builder.rb
84
95
  - lib/equilibrium/tag_processor.rb
96
+ - lib/equilibrium/tag_sorter.rb
97
+ - lib/equilibrium/tags_operation_service.rb
85
98
  - lib/equilibrium/version.rb
86
99
  - tmp/.gitkeep
87
100
  homepage: https://github.com/TonyCTHsu/equilibrium
@@ -90,7 +103,8 @@ licenses:
90
103
  metadata:
91
104
  homepage_uri: https://github.com/TonyCTHsu/equilibrium
92
105
  source_code_uri: https://github.com/TonyCTHsu/equilibrium
93
- changelog_uri: https://github.com/TonyCTHsu/equilibrium/blob/master/CHANGELOG.md
106
+ changelog_uri: https://github.com/TonyCTHsu/equilibrium/blob/v0.2.0/CHANGELOG.md
107
+ github_repo: ssh://github.com/TonyCTHsu/equilibrium
94
108
  post_install_message:
95
109
  rdoc_options: []
96
110
  require_paths:
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Equilibrium
4
- module SemanticVersion
5
- # Strictly validate MAJOR.MINOR.PATCH format where:
6
- # - MAJOR, MINOR, PATCH are non-negative integers
7
- # - No leading zeros (except for '0' itself)
8
- # - No prefixes (like 'v1.2.3', 'release-1.2.3')
9
- # - No suffixes (like '1.2.3-alpha', '1.2.3+build')
10
- # - No prereleases (like '1.2.3-rc.1', '1.2.3-beta.2')
11
- def self.valid?(tag)
12
- # Strict regex: each component must be either '0' or a number without leading zeros
13
- return false unless tag.match?(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/)
14
-
15
- # Additional validation: ensure it's a valid Gem::Version
16
- begin
17
- Gem::Version.new(tag)
18
- true
19
- rescue ArgumentError
20
- false
21
- end
22
- end
23
- end
24
- end