gemsolver 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: []