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 +7 -0
- data/README.md +48 -0
- data/Rakefile +3 -0
- data/lib/gemsolver/errors.rb +28 -0
- data/lib/gemsolver/parsers/info_parser.rb +91 -0
- data/lib/gemsolver/parsers/sem_ver_parser.rb +79 -0
- data/lib/gemsolver/railtie.rb +4 -0
- data/lib/gemsolver/version.rb +3 -0
- data/lib/gemsolver.rb +61 -0
- data/lib/generators/gemcache/install_generator.rb +24 -0
- data/lib/tasks/gemcache_tasks.rake +4 -0
- metadata +71 -0
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,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
|
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
|
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: []
|