gemsolver 0.2.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bd1bc00574ad277bb5ecd22d84c7c8de6fc57140009245bc75dfd2ca9d186e02
4
+ data.tar.gz: 10f8060f235b69be05a7ba35885a3840d327fddf28d18ca4f1d752b091a534b9
5
+ SHA512:
6
+ metadata.gz: 560a4b12aff71e5f38425cc9c84b454919d766cb58632f857377103032d701c39d991ac81667007b36f9b4d64df3da950960652cc512d8f5c46b3069e76c0f92
7
+ data.tar.gz: 32d0e043c9f8224c9135f9e09b5b931c178e7223ff24d2dc9137a56cfd8c6810c41bcb32754277d073f369501c6689eb828cadfbc493b1d4dfc7cb29e7a790b4
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Gem Solver
2
+ Gem Solver is a plugin for Rails that caches Ruby gems from any [rubygems.org](https://rubygems.org) compatible host.
3
+ It is built primarly to work with [GemCache](https://github.com/pinecat/gemcache).
4
+
5
+ ## Installation
6
+ To use GemSolver in your rails project, first:
7
+ ```
8
+ bundle add gemsolver
9
+ ```
10
+
11
+ then:
12
+ ```
13
+ rails g gemsolver:install
14
+ ```
15
+
16
+ ## Usage
17
+ ```ruby
18
+ class Gemfu
19
+ include GemSolver
20
+
21
+ attr_reader :name, :requirements, :info_file, :stories, :versions, :version, :info, :quick_file, :gem_file
22
+
23
+ def initialize(gem)
24
+ @name = gem.name
25
+ @requirements = gem.requirements_list
26
+
27
+ @info_file = fetch_info
28
+ @stories = InfoParser.parse(@info_file)
29
+ @versions = SemVerParser.new(@stories, @requirements, @name)
30
+ @version = @versions.available.first
31
+ @info = InfoParser.info(@version, @stories)
32
+
33
+ @quick_file = fetch_quick
34
+ @gem_file = fetch_gem
35
+ end
36
+ end
37
+
38
+ gem = Gem::Dependency.new("colorize", Gem::Requirement.new(["~> 1.0"]))
39
+ gemfu = Gemfu.new(gem)
40
+
41
+ File.binwrite("path/to/colorize.gem", gemfu.gem_file)
42
+ ```
43
+
44
+ ## Contributing
45
+ Bug reports and pull requests are welcome on Github at https://github.com/pinecat/gemsolver/issues and https://github.com/pinecat/gemsolver/pulls, respectively.
46
+
47
+ ## License
48
+ The gem is available as open source under the terms of the [BSD 3-Clause License](https://opensource.org/license/bsd-3-clause/).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ module GemSolver
2
+ #
3
+ # Raised when /info/<gem> cannot be found on the remote gem host.
4
+ #
5
+ class GemNotFoundError < StandardError
6
+ def initialize(name)
7
+ super("The '#{name}' gem could not be found on '#{@@host}'")
8
+ end
9
+ end
10
+
11
+ #
12
+ # Raised when an unrecognized version constraint is passed in a dependency.
13
+ #
14
+ class InvalidVersionConstraintError < StandardError
15
+ def initialize(operator)
16
+ super("The semantic versioning contraint '#{operator}' is not recognized")
17
+ end
18
+ end
19
+
20
+ #
21
+ # Raised when the version constraints are not inclusive enough to return a version.
22
+ #
23
+ class NoAvailableVersionsError < StandardError
24
+ def initialize(name)
25
+ super("There are no versions of '#{name}' that meet the specified version constraints")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,91 @@
1
+ module GemSolver
2
+ class InfoParser
3
+ # The version of the gem from the info entry.
4
+ attr_reader :version
5
+
6
+ # A list of Gem::Dependencies from the info entry.
7
+ attr_reader :dependencies
8
+
9
+ # The SHA256 checksum of the gem from the info entry.
10
+ attr_reader :checksum
11
+
12
+ # The required Ruby version of the gem from the info entry.
13
+ attr_reader :ruby
14
+
15
+ # The required rubygems version of the gem from the info entry.
16
+ attr_reader :rubygems
17
+
18
+ def initialize(entry)
19
+ # Split line into two parts:
20
+ # (0) Semantic version and runtime dependencies
21
+ # (1) Checksum and required ruby/rubygems versions
22
+ split = entry.split("|")
23
+ ver_and_deps = split[0].strip
24
+ sha_and_ruby = split[1].strip
25
+
26
+ # Parse each part of the entry in the info file
27
+ parts = parse_version(ver_and_deps)
28
+ parse_dependencies(parts)
29
+ parts = parse_checksum(sha_and_ruby)
30
+ parts = parse_ruby(parts)
31
+ parse_rubygems(parts)
32
+ end
33
+
34
+ def self.info(version, stories)
35
+ stories.each do |s|
36
+ return s if s.version == version
37
+ end
38
+ nil
39
+ end
40
+
41
+ def self.parse(raw)
42
+ stories = []
43
+ raw.lines.drop(1).each do |l|
44
+ stories << new(l)
45
+ end
46
+ stories.reverse
47
+ end
48
+
49
+ private
50
+
51
+ def parse_version(raw)
52
+ return if raw.blank?
53
+
54
+ parts = raw.split(" ", 2)
55
+ @version = Gem::Version.new(parts[0])
56
+ parts.drop(1)
57
+ end
58
+
59
+ def parse_dependencies(parts)
60
+ return if parts.blank?
61
+
62
+ parts = parts.split(",").flatten
63
+ @dependencies = []
64
+ parts.each do |p|
65
+ name, requirements = p.split(":")
66
+ @dependencies << Gem::Dependency.new(name, requirements)
67
+ end
68
+ end
69
+
70
+ def parse_checksum(raw)
71
+ return if raw.blank?
72
+
73
+ parts = raw.split(",")
74
+ @checksum = parts[0].split(":")[1]
75
+ parts.drop(1)
76
+ end
77
+
78
+ def parse_ruby(parts)
79
+ return if parts.blank?
80
+
81
+ @ruby = Gem::Dependency.new("ruby", parts[0].split(":")[1].split("&"))
82
+ parts.drop(1)
83
+ end
84
+
85
+ def parse_rubygems(parts)
86
+ return if parts.blank?
87
+
88
+ @rubygems = Gem::Dependency.new("rubygems", parts[0].split(":")[1].split("&"))
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,79 @@
1
+ module GemSolver
2
+ #
3
+ # Creates a list of appropriate gem and dependency versions
4
+ # based on semantic versioning requirements. The latest
5
+ # possible verions will always come first in the list.
6
+ #
7
+ class SemVerParser
8
+ class Constraint
9
+ def initialize(operator, version)
10
+ @operator = operator.intern
11
+ @version = Gem::Version.new(version)
12
+ end
13
+
14
+ def appropriate?(version)
15
+ version.send(@operator, @version)
16
+ end
17
+ end
18
+
19
+ # List of appropriate semantic versions, with the latest versions at the front of the list.
20
+ attr_reader :available
21
+
22
+ def initialize(stories, requirements, name)
23
+ constraints = constrain(requirements)
24
+
25
+ @available = []
26
+ stories.each do |s|
27
+ one_constraint_failed = false
28
+ constraints.each do |c|
29
+ unless c.appropriate?(s.version)
30
+ one_constraint_failed = true
31
+ break
32
+ end
33
+ end
34
+ @available << s.version unless one_constraint_failed
35
+ end
36
+
37
+ raise NoAvailableVersionsError.new(name) if @available.blank?
38
+ end
39
+
40
+ private
41
+
42
+ def constrain(requirements)
43
+ constraints = []
44
+ requirements.each do |r|
45
+ operator, version = r.split(" ")
46
+
47
+ case operator
48
+ when "~>"
49
+ # Pessimistic versioning (i.e. approximately greater than)
50
+ constraints << Constraint.new(">=", version)
51
+
52
+ if version.include?("-")
53
+ version = version.split("-")[0]
54
+ end
55
+
56
+ if version.include?("+")
57
+ version = version.split("+")[0]
58
+ end
59
+
60
+ mmp = version.split(".").map { |s| s.to_i }
61
+ mmp.pop unless mmp.length == 1
62
+ mmp[-1] += 1
63
+ version = mmp.join(".")
64
+
65
+ constraints << Constraint.new("<", version)
66
+ when ">", ">=", "<", "<="
67
+ constraints << Constraint.new(operator, version)
68
+ when "="
69
+ constraints << Constraint.new("==", version)
70
+ else
71
+ raise InvalidVersionConstraintError.new(operator)
72
+ end
73
+
74
+ end
75
+ constraints
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,4 @@
1
+ module GemCSolver
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module GemSolver
2
+ VERSION = "0.2.1"
3
+ end
data/lib/gemsolver.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "gemsolver/version"
2
+ require "gemsolver/railtie"
3
+ require "gemsolver/errors"
4
+ require "gemsolver/parsers/info_parser"
5
+ require "gemsolver/parsers/sem_ver_parser"
6
+ require "net/http"
7
+ require "stringio"
8
+ require "rubygems/indexer"
9
+
10
+ module GemSolver
11
+ #
12
+ # The host of the remote gem server.
13
+ # The default is https://rubygems.org.
14
+ #
15
+ mattr_accessor :host
16
+ @@host = "https://rubygems.org"
17
+
18
+ #
19
+ # Set configuration options.
20
+ # This is typically done in the initializer ('rails g gemcache:install')
21
+ #
22
+ def self.setup
23
+ yield self
24
+ end
25
+
26
+ #
27
+ # Fetch required gem info file.
28
+ #
29
+ # @return [String] The (YAML based?) info file.
30
+ #
31
+ def fetch_info
32
+ info_uri = URI.parse("#{@@host}/info/#{@name}")
33
+ response = Net::HTTP.get_response(info_uri)
34
+ raise GemNotFoundError.new(@name) unless response.is_a? Net::HTTPSuccess
35
+ response.body
36
+ end
37
+
38
+ #
39
+ # Fetch required gem quick file.
40
+ #
41
+ # @return [String] The binary (compressed) quick file.
42
+ #
43
+ def fetch_quick
44
+ quick_uri = URI.parse("#{@@host}/quick/Marshal.4.8/#{@name}-#{@version}.gemspec.rz")
45
+ response = Net::HTTP.get_response(quick_uri)
46
+ raise GemNotFoundError.new(@name) unless response.is_a? Net::HTTPSuccess
47
+ response.body
48
+ end
49
+
50
+ #
51
+ # Fetch required gem file.
52
+ #
53
+ # @return [String] The binary (compressed) gem file.
54
+ #
55
+ def fetch_gem
56
+ gem_uri = URI.parse("#{@@host}/gems/#{@name}-#{@version}.gem")
57
+ response = Net::HTTP.get_response(gem_uri)
58
+ raise GemNotFoundError(@name) unless response.is_a? Net::HTTPSuccess
59
+ response.body
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Gemsolver
6
+ class InstallGenerator < Rails::Generators::Base
7
+ desc "Create an initializer file for Gem Solver configuration options"
8
+ def create_initializer_file
9
+ create_file "config/initializers/gemsolver.rb", <<~RUBY
10
+ # frozen_string_literal: true
11
+
12
+ # GemCache configuration options.
13
+ GemSolver.setup do |config|
14
+ # The remote host to cache ruby gems from. This option may be overriden
15
+ # directly or by setting the 'RAILS_GEMCACHE_HOST' environment
16
+ # variable. The default option is https://rubygems.org. The remote host
17
+ # must follow the distrubution pattern of rubygems.org, otherwise
18
+ # GemCache will be unable to fetch gems from it.
19
+ config.host = ENV["RAILS_GEMSOLVE_HOST"] || "https://rubygems.org"
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :gemcache do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemsolver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Rory Dudley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.7.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.7.2
27
+ description: Cache Ruby gems from a rubygems.org compatible host.
28
+ email:
29
+ - rory.dudley@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - Rakefile
36
+ - lib/gemsolver.rb
37
+ - lib/gemsolver/errors.rb
38
+ - lib/gemsolver/parsers/info_parser.rb
39
+ - lib/gemsolver/parsers/sem_ver_parser.rb
40
+ - lib/gemsolver/railtie.rb
41
+ - lib/gemsolver/version.rb
42
+ - lib/generators/gemcache/install_generator.rb
43
+ - lib/tasks/gemcache_tasks.rake
44
+ homepage: https://github.com/pinecat/gemcache
45
+ licenses:
46
+ - BSD 3-Clause
47
+ metadata:
48
+ allowed_push_host: https://rubygems.org
49
+ homepage_uri: https://github.com/pinecat/gemcache
50
+ source_code_uri: https://github.com/pinecat/gemcache
51
+ changelog_uri: https://github.com/pinecat/gemcache/blob/master/CHANGELOG.md
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.4.19
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Cache Ruby gems.
71
+ test_files: []