bundle_hack 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9b7e2f08fc8c5f36bb61d5a8e8627d57140b2018
4
- data.tar.gz: 96738bd9fffcc7f8a79280771c838db1d6a45595
2
+ SHA256:
3
+ metadata.gz: e3c07038f47d540e8eab90ea819c3a85e5672af8de6aca3e61e388cde3654406
4
+ data.tar.gz: 91dde30d342fee125343b625d3d7f5a4349cb6d6679208403de4b44fc81f7bf1
5
5
  SHA512:
6
- metadata.gz: 79c4ad0fd7c2c7d23480e80e9e9d4cfa5725700413c652b91da692a5fa70e5d5d32325f15c86685afba9e88df0e3446ff7602cd2f39da55a4839c02505ba72ed
7
- data.tar.gz: cf1e9f8f73b90d5f4874949d0c4db42039aba54feebfd3683684793e7615be8d1e008bad53cd6a1603b110ea7185a5f033e30bad8c5e9fdaccba5d4850acb2cf
6
+ metadata.gz: 8ae6b9769b44c75e47f67c6d72fe1a7286eeb2a21c89f14884742bf6f6eb1d3b5bcdae0798aeabb1394ba84efb06b07edb6a92a4312968bf82f823575e938e3d
7
+ data.tar.gz: 4b10042cc4bffe5271747702bb36fa5f66b0e0ae31e2536f26bf3852c3aa1c96557f325e46809b0bd43a80db1b5d80b13683ebff99adbce1fa6bd140cce1e70d
data/.gitignore CHANGED
@@ -14,3 +14,8 @@ Gemfile.lock
14
14
 
15
15
  *.swo
16
16
  *.swp
17
+
18
+ .byebug_history
19
+ spec/dummy/Gemfile.hack
20
+ spec/dummy/Gemfile.hack.lock
21
+ spec/dummy/hack/dummy_gem/dummy_gem.gemspec
@@ -1,4 +1,3 @@
1
- # Don't require top-level comment for all modules and classes
2
1
  Style/Documentation:
3
2
  Enabled: false
4
3
 
@@ -6,8 +5,9 @@ Metrics/BlockLength:
6
5
  Exclude:
7
6
  - 'spec/**/*'
8
7
 
9
- # We don't care about the auto-generated dummy Rails app
10
8
  AllCops:
11
9
  Exclude:
12
10
  - 'bin/**/*'
13
- - 'db/schema.rb'
11
+ - 'spec/dummy/**/*'
12
+ - 'spec/fixtures/**/*'
13
+ - 'config/header.rb'
@@ -0,0 +1 @@
1
+ 2.5.3
data/Gemfile CHANGED
@@ -4,5 +4,14 @@ source 'https://rubygems.org'
4
4
 
5
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
- # Specify your gem's dependencies in bundle_hack.gemspec
7
+ # These gems are only used by tests - we need to have these gems available in
8
+ # the current Bundler context (i.e. the context used when running tests) to
9
+ # allow us to use Bundler to locate gem sources etc., otherwise we would need to
10
+ # stub Bundler to hell. With Bundler being so integral to BundleHack I don't
11
+ # think that's an option.
12
+ gem 'another_dummy_gem',
13
+ git: 'https://github.com/bobf/another_dummy_gem', ref: 'ed0e5d21f1ff'
14
+ gem 'dummy_gem', '1.0.0'
15
+ gem 'noop', '0.0.2'
16
+
8
17
  gemspec
@@ -0,0 +1,5 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ bin/rspec
5
+ bin/rubocop
data/README.md CHANGED
@@ -32,10 +32,14 @@ The gem is copied to `hack/rake/` (relative to the same directory as your `Gemfi
32
32
 
33
33
  When you're done, you can restore a specific gem:
34
34
 
35
+ # TODO:
36
+
35
37
  ``` bash
36
38
  $ bundle hack:restore rake
37
39
  ```
38
40
 
41
+ # TODO:
42
+
39
43
  Or restore all hacked gems:
40
44
  ```
41
45
  $ bundle hack:restore:all
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
@@ -20,11 +20,16 @@ Gem::Specification.new do |spec|
20
20
  end
21
21
  end
22
22
  spec.bindir = 'bin'
23
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.executables = []
24
24
  spec.require_paths = ['lib']
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.16'
26
+ spec.add_dependency 'i18n', '>= 0.5'
27
+ spec.add_dependency 'parser', '~> 2.5'
28
+
29
+ spec.add_development_dependency 'byebug', '~> 10.0'
27
30
  spec.add_development_dependency 'rake', '~> 10.0'
28
31
  spec.add_development_dependency 'rspec', '~> 3.0'
29
- spec.add_development_dependency 'rubocop', '~> 0.59.2'
32
+ spec.add_development_dependency 'rspec-its', '~> 1.2'
33
+ spec.add_development_dependency 'rubocop', '~> 0.60.0'
34
+ spec.add_development_dependency 'webmock', '~> 3.4'
30
35
  end
@@ -0,0 +1,4 @@
1
+
2
+ #
3
+ # BundleHack gems:
4
+ #
@@ -0,0 +1,6 @@
1
+ en:
2
+ errors:
3
+ missing_gem: "Requested gem '%{gem}' not found in Gemfile"
4
+ missing_spec: "Unable to locate .gemspec for '%{gem}'"
5
+ parsing: "Error parsing Gemfile: unable to locate '%{gem}'. Please file a bug report."
6
+ type: "Unknown type: %{type}"
@@ -1,7 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+ require 'yaml'
5
+
6
+ require 'i18n'
7
+ require 'parser/current'
8
+
9
+ require 'bundle_hack/command'
10
+ require 'bundle_hack/config_writer'
11
+ require 'bundle_hack/errors'
12
+ require 'bundle_hack/gem'
13
+ require 'bundle_hack/gem_cloner'
14
+ require 'bundle_hack/gem_locator'
15
+ require 'bundle_hack/gemfile_parser'
16
+ require 'bundle_hack/gemfile_writer'
17
+ require 'bundle_hack/gemspec_cloner'
18
+ require 'bundle_hack/sandbox'
19
+ require 'bundle_hack/gemspec_sources/base'
20
+ require 'bundle_hack/gemspec_sources/cache'
21
+ require 'bundle_hack/gemspec_sources/path'
22
+ require 'bundle_hack/gemspec_sources/rubygems'
3
23
  require 'bundle_hack/version'
4
24
 
25
+ I18n.config.available_locales = :en
26
+ I18n.load_path += Dir[
27
+ File.join(File.expand_path('..', __dir__), 'config', 'locales', '**', '*.yml')
28
+ ]
29
+
5
30
  module BundleHack
6
- # Your code goes here...
31
+ GEMFILE = 'Gemfile.hack'
32
+ HACK_DIR = 'hack'
33
+
34
+ def self.root
35
+ Pathname.new(File.dirname(__dir__))
36
+ end
7
37
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class Command < Bundler::Plugin::API
5
+ command 'hack'
6
+ command 'hack:restore'
7
+ command 'hack:restore:all'
8
+
9
+ def exec(command, args)
10
+ Sandbox.new(args.first).create if command == 'hack'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class ConfigWriter
5
+ def initialize(root_path)
6
+ @root_path = root_path
7
+ @config_path = File.join(@root_path, '.bundle', 'config')
8
+ end
9
+
10
+ def create_or_update
11
+ return create_config unless File.exist?(@config_path)
12
+
13
+ update_config
14
+ end
15
+
16
+ private
17
+
18
+ def create_config
19
+ FileUtils.mkdir_p(File.join(@root_path, '.bundle'))
20
+ write_config(config_settings)
21
+ end
22
+
23
+ def update_config
24
+ write_config(YAML.load_file(@config_path).merge(config_settings))
25
+ end
26
+
27
+ def write_config(config)
28
+ File.write(@config_path, config.to_yaml)
29
+ end
30
+
31
+ def config_settings
32
+ {
33
+ 'BUNDLE_GEMFILE' => GEMFILE
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class BundleHackError < StandardError; end
5
+ class MissingGemError < BundleHackError; end
6
+ class MissingSpecError < BundleHackError; end
7
+ class ParsingError < BundleHackError; end
8
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class Gem
5
+ attr_reader :name, :full_name, :version, :path
6
+
7
+ def initialize(definitions, options = {})
8
+ @definitions = definitions
9
+ @name = options.fetch(:name)
10
+ @full_name = options.fetch(:full_name)
11
+ @version = normalized_version(options.fetch(:version))
12
+ @path = Pathname.new(options.fetch(:path))
13
+ end
14
+
15
+ def locations
16
+ @definitions.map { |definition| definition[:locations] }.flatten
17
+ end
18
+
19
+ def params
20
+ find_params || {}
21
+ end
22
+
23
+ private
24
+
25
+ def find_params
26
+ # Because we don't know how the bundle was built (e.g. if it was built
27
+ # with `--without development test`) we have to take a guess at which
28
+ # definition to use for our parameters. In most cases the definition will
29
+ # be in the main block, otherwise we use the `development` definition and,
30
+ # failing that, we take the definition nearest to the end of the Gemfile.
31
+ # This is very much an edge case as this will only be a problem if we pick
32
+ # the wrong one AND the parameters are different to the "real" gem we're
33
+ # using.
34
+ return primary_definition[:params] unless primary_definition.nil?
35
+ return development_definition[:params] unless development_definition.nil?
36
+
37
+ any_definition[:params]
38
+ end
39
+
40
+ def primary_definition
41
+ @definitions.reverse_each.find do |definition|
42
+ definition.fetch(:groups).nil?
43
+ end
44
+ end
45
+
46
+ def development_definition
47
+ @definitions.reverse_each.find do |definition|
48
+ definition.fetch(:groups).include?(:development)
49
+ end
50
+ end
51
+
52
+ def any_definition
53
+ @definitions.last
54
+ end
55
+
56
+ def normalized_version(version)
57
+ # Ruby >= 2.3.0: `version` is a `Gem::Version`
58
+ # Ruby < 2.3.0: `version` is a `String`
59
+ version.respond_to?(:version) ? version.version : version
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class GemCloner
5
+ def initialize(gem, root_path)
6
+ @gem = gem
7
+ @root_path = root_path
8
+ end
9
+
10
+ def clone
11
+ hack_dir = @root_path.join(HACK_DIR)
12
+
13
+ FileUtils.mkdir_p(hack_dir)
14
+ # Sadly there is no `FileUtils.cp_R` so we copy and rename instead
15
+ FileUtils.cp_r(@gem.path, hack_dir)
16
+ FileUtils.mv(hack_dir.join(@gem.full_name), hack_dir.join(@gem.name))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class GemLocator
5
+ def initialize(gemfile_path, gemfile_lock_path)
6
+ @gemfile_path = gemfile_path
7
+ @gemfile_lock_path = gemfile_lock_path
8
+ end
9
+
10
+ def locate(gem_name)
11
+ gem = gems.find { |spec| spec[:name] == gem_name }
12
+ missing_gem_error(gem_name) if gem.nil?
13
+
14
+ gem
15
+ end
16
+
17
+ private
18
+
19
+ def gems
20
+ bundler_specs.map do |spec|
21
+ filtered_spec(spec)
22
+ end
23
+ end
24
+
25
+ def missing_gem_error(gem_name)
26
+ raise MissingGemError, I18n.t('errors.missing_gem', gem: gem_name)
27
+ end
28
+
29
+ def filtered_spec(spec)
30
+ {
31
+ name: spec.name,
32
+ full_name: spec.full_name,
33
+ path: spec.full_gem_path,
34
+ version: spec.version
35
+ }
36
+ end
37
+
38
+ def bundler_specs
39
+ @bundler_specs ||= Bundler::Definition.build(
40
+ @gemfile_path, @gemfile_lock_path, nil
41
+ ).specs
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class GemfileParser
5
+ def initialize(gemfile)
6
+ @gemfile = gemfile
7
+ end
8
+
9
+ def definitions_for(gem_name)
10
+ definitions(by_name(gem_name))
11
+ end
12
+
13
+ private
14
+
15
+ def by_name(gem_name)
16
+ sexps = gems.select do |gem|
17
+ gem[:sexp].children[2].to_sexp_array[1] == gem_name
18
+ end
19
+
20
+ parsing_error(gem_name) if sexps.empty?
21
+
22
+ sexps
23
+ end
24
+
25
+ def gems
26
+ top_level_gems + group_gems
27
+ end
28
+
29
+ def top_level_gems
30
+ gem_sexps_from_children(parsed_gemfile.children).map do |sexp|
31
+ { groups: nil, sexp: sexp }
32
+ end
33
+ end
34
+
35
+ def group_gems
36
+ parsed_gemfile.children.map do |child|
37
+ next unless group?(child)
38
+
39
+ gem_sexps_from_children(child.children[2..-1]).map do |sexp|
40
+ { groups: group_names(child.to_sexp_array), sexp: sexp }
41
+ end
42
+ end.compact.flatten(1)
43
+ end
44
+
45
+ def group_names(sexp_array)
46
+ # sexp_array[1] looks like:
47
+ # [:send, nil, :group, [:sym, :development], [:sym, :test]]
48
+ _send, _, _group, *group_pairs = sexp_array[1]
49
+ group_pairs.map { |_sym, group| group }
50
+ end
51
+
52
+ def group?(sexp)
53
+ sexp_array = sexp.to_sexp_array
54
+ sexp_array[0] == :block && sexp_array[1][2] == :group
55
+ end
56
+
57
+ def gem_sexps_from_children(children)
58
+ children.select do |child|
59
+ sexp_array = child.to_sexp_array
60
+ sexp_array[0] == :send && sexp_array[2] == :gem
61
+ end
62
+ end
63
+
64
+ def parsed_gemfile
65
+ @parsed_gemfile ||= begin
66
+ @gemfile.rewind
67
+ Parser::CurrentRuby.parse(@gemfile.read)
68
+ end
69
+ end
70
+
71
+ def definitions(gems)
72
+ gems.map do |gem|
73
+ {
74
+ groups: gem[:groups],
75
+ locations: location(gem[:sexp]),
76
+ params: hash_params(gem[:sexp])
77
+ }
78
+ end
79
+ end
80
+
81
+ def location(sexp)
82
+ (sexp.location.first_line..sexp.location.last_line).to_a
83
+ end
84
+
85
+ def hash_params(sexp)
86
+ last_arg_sexp = sexp.children.last
87
+ return nil unless last_arg_sexp.to_sexp_array.first == :hash
88
+
89
+ _, *pairs = last_arg_sexp.to_sexp_array
90
+
91
+ Hash[key_value_pairs(pairs)]
92
+ end
93
+
94
+ def key_value_pairs(pairs)
95
+ pairs.map do |_pair_sym, (_type, key), value|
96
+ [key, nested_value(*value)]
97
+ end
98
+ end
99
+
100
+ def nested_value(type, *values)
101
+ return primitive(type) if values.empty?
102
+ return array(values) if type == :array
103
+ return hash(values) if type == :hash
104
+ return values.first if %i[sym str].include?(type)
105
+
106
+ raise ParsingError, I18n.t('errors.type', type: type)
107
+ end
108
+
109
+ def array(values)
110
+ values.map { |type, value_array| nested_value(type, value_array) }
111
+ end
112
+
113
+ def hash(values)
114
+ Hash[array(values)]
115
+ end
116
+
117
+ def primitive(type)
118
+ # rubocop:disable Lint/BooleanSymbol
119
+ case type
120
+ when :false
121
+ false
122
+ when :true
123
+ true
124
+ when :nil
125
+ nil
126
+ end
127
+ # rubocop:enable Lint/BooleanSymbol
128
+ end
129
+
130
+ def parsing_error(gem_name)
131
+ raise ParsingError, I18n.t('errors.parsing', gem: gem_name)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class GemfileWriter
5
+ FILTERED_PARAMS = %i[git path].freeze
6
+
7
+ def initialize(root_path, gemfile_path, gems)
8
+ @root_path = root_path
9
+ @gemfile_path = gemfile_path
10
+ @hacked_gems = gems
11
+ @hacked_gemfile_path = @root_path.join(GEMFILE)
12
+ end
13
+
14
+ def create
15
+ comment_existing_gems
16
+ append_hacked_gems
17
+ end
18
+
19
+ private
20
+
21
+ def comment_existing_gems
22
+ File.write(@hacked_gemfile_path, commented_gemfile)
23
+ end
24
+
25
+ def commented_gemfile
26
+ File.readlines(@gemfile_path).to_enum.with_index(1).map do |line, index|
27
+ next line.chomp unless comment_lines.include?(index)
28
+
29
+ "# #{line.chomp} # managed by BundleHack"
30
+ end.join("\n")
31
+ end
32
+
33
+ def append_hacked_gems
34
+ File.write(
35
+ @hacked_gemfile_path,
36
+ File.read(@hacked_gemfile_path) + header + hacked_gem_definitions
37
+ )
38
+ end
39
+
40
+ def hacked_gem_definitions
41
+ @hacked_gems.map do |gem|
42
+ params = filtered_params(gem.params).merge(path: "hack/#{gem.name}")
43
+ "gem '#{gem.name}', #{params.inspect}"
44
+ end.join("\n")
45
+ end
46
+
47
+ def header
48
+ File.read(BundleHack.root.join('config', 'header.rb'))
49
+ end
50
+
51
+ def comment_lines
52
+ @hacked_gems.map(&:locations).flatten
53
+ end
54
+
55
+ def filtered_params(params)
56
+ params.reject { |key, _value| FILTERED_PARAMS.include?(key) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class GemspecCloner
5
+ def initialize(gem, root_path, options = {})
6
+ @use_cache = options.fetch(:use_cache, true)
7
+ @use_rubygems = options.fetch(:use_rubygems, true)
8
+ @gem = gem
9
+ @root_path = root_path
10
+ @spec_path = @root_path.join(HACK_DIR, @gem.name, "#{@gem.name}.gemspec")
11
+ end
12
+
13
+ def clone
14
+ FileUtils.mkdir_p(@root_path.join(HACK_DIR, @gem.name))
15
+ File.write(@spec_path, find_spec)
16
+ end
17
+
18
+ private
19
+
20
+ def find_spec
21
+ return path_spec unless path_spec.nil?
22
+ return cache_spec unless cache_spec.nil?
23
+ return rubygems_spec unless rubygems_spec.nil?
24
+
25
+ raise MissingSpecError, I18n.t('errors.missing_spec', gem: @gem.name)
26
+ end
27
+
28
+ def spec_from(source)
29
+ source.new(@gem).spec
30
+ end
31
+
32
+ def rubygems_spec
33
+ return nil unless @use_rubygems
34
+
35
+ @rubygems_spec ||= spec_from(GemspecSources::Rubygems)
36
+ end
37
+
38
+ def path_spec
39
+ @path_spec ||= spec_from(GemspecSources::Path)
40
+ end
41
+
42
+ def cache_spec
43
+ return nil unless @use_cache
44
+
45
+ @cache_spec ||= spec_from(GemspecSources::Cache)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ module GemspecSources
5
+ class Base
6
+ def initialize(gem)
7
+ @gem = gem
8
+ end
9
+
10
+ def spec
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ module GemspecSources
5
+ # I couldn't figure out how to do this with `Gem` directly so I've
6
+ # implemented it myself instead - if this can be collapsed into something
7
+ # along the lines of `Gem.from_cache(gem_name, gem_version)` then that would
8
+ # mean all of this code can go away.
9
+ class Cache < Base
10
+ def spec
11
+ path = cache_file_path
12
+ return nil if path.nil?
13
+
14
+ # rubocop:disable Security/MarshalLoad
15
+ Marshal.load(File.read(cache_file_path)).to_ruby
16
+ # rubocop:enable Security/MarshalLoad
17
+ end
18
+
19
+ private
20
+
21
+ def cache_file_path
22
+ cache_dirs.each do |cache_dir|
23
+ cache_file_path = cache_dir.join("#{@gem.full_name}.gemspec")
24
+ return cache_file_path if File.exist?(cache_file_path)
25
+ end
26
+
27
+ nil
28
+ end
29
+
30
+ def cache_dirs
31
+ Dir.entries(base_dir)
32
+ .select { |path| File.directory?(quick_cache(path)) }
33
+ .reject { |path| %w[. ..].include?(path) }
34
+ .map { |path| quick_cache(path) }
35
+ end
36
+
37
+ def quick_cache(path)
38
+ # I think the `4.8` versioning is highly stable. The lead dev of
39
+ # Bundler references it in this post from nearly 5 years ago:
40
+ # https://www.engineyard.com/blog/new-rubygems-index-format
41
+ # So I think # it's okay to make this a hard requirement.
42
+ base_dir.join(path, 'quick', 'Marshal.4.8')
43
+ end
44
+
45
+ def base_dir
46
+ @base_dir ||= Pathname.new(::Gem.spec_cache_dir)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ module GemspecSources
5
+ class Path < Base
6
+ def spec
7
+ # If the gem comes from a path (including git) then we will have the
8
+ # gemspec available in the gem directory (unlike when it comes from
9
+ # rubygems)
10
+ gemspec_path = @gem.path.join("#{@gem.name}.gemspec")
11
+ return nil unless File.exist?(gemspec_path)
12
+
13
+ File.read(gemspec_path)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ module GemspecSources
5
+ class Rubygems < Base
6
+ def spec
7
+ dependency = ::Gem::Dependency.new(@gem.name, @gem.version)
8
+
9
+ # Borrowed from:
10
+ # https://ruby-doc.org/stdlib-2.5.3/libdoc/rubygems/rdoc/Gem.html#method-c-latest_spec_for
11
+ spec_tuples, = ::Gem::SpecFetcher.fetcher.spec_for_dependency(
12
+ dependency
13
+ )
14
+ spec, = spec_tuples.first
15
+ return nil if spec.nil?
16
+
17
+ spec.to_ruby
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleHack
4
+ class Sandbox
5
+ def initialize(gem_name, gemfile_path = nil)
6
+ @gem_name = gem_name
7
+ @gemfile_path = gemfile_path || Bundler.default_gemfile
8
+ @gemfile_lock_path = lock_path(gemfile_path)
9
+ @root_path = Pathname.new(File.dirname(@gemfile_path))
10
+ end
11
+
12
+ def build
13
+ parser = GemfileParser.new(File.open(@gemfile_path))
14
+ gems = locate_gems(parser)
15
+ ConfigWriter.new(@root_path).create_or_update
16
+ GemfileWriter.new(@root_path, @gemfile_path, gems).create
17
+ gems.each do |gem|
18
+ GemCloner.new(gem, @root_path).clone
19
+ GemspecCloner.new(gem, @root_path).clone
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def locate_gems(parser)
26
+ # TODO: Read from .bundle_hack.yml
27
+ [@gem_name].map { |gem_name| locate_gem(gem_name, parser) }
28
+ end
29
+
30
+ def locate_gem(gem_name, parser)
31
+ spec = GemLocator.new(@gemfile_path, @gemfile_lock_path)
32
+ .locate(gem_name)
33
+ Gem.new(parser.definitions_for(gem_name), spec)
34
+ end
35
+
36
+ def lock_path(gemfile_path)
37
+ gemfile_path.nil? ? Bundler.default_lockfile : "#{gemfile_path}.lock"
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleHack
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundle_hack'
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundle_hack
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
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-26 00:00:00.000000000 Z
11
+ date: 2018-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: i18n
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '1.16'
33
+ version: '2.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
20
48
  type: :development
21
49
  prerelease: false
22
50
  version_requirements: !ruby/object:Gem::Requirement
23
51
  requirements:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
- version: '1.16'
54
+ version: '10.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,41 +80,89 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: rubocop
57
99
  requirement: !ruby/object:Gem::Requirement
58
100
  requirements:
59
101
  - - "~>"
60
102
  - !ruby/object:Gem::Version
61
- version: 0.59.2
103
+ version: 0.60.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.60.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.4'
62
118
  type: :development
63
119
  prerelease: false
64
120
  version_requirements: !ruby/object:Gem::Requirement
65
121
  requirements:
66
122
  - - "~>"
67
123
  - !ruby/object:Gem::Version
68
- version: 0.59.2
124
+ version: '3.4'
69
125
  description: Debug fearlessly and restore when you're done
70
126
  email:
71
127
  - robertanthonyfarrell@gmail.com
72
- executables:
73
- - console
74
- - setup
128
+ executables: []
75
129
  extensions: []
76
130
  extra_rdoc_files: []
77
131
  files:
78
132
  - ".gitignore"
79
133
  - ".rspec"
80
134
  - ".rubocop.yml"
135
+ - ".ruby-version"
81
136
  - ".travis.yml"
82
137
  - Gemfile
138
+ - Makefile
83
139
  - README.md
84
140
  - Rakefile
85
141
  - bin/console
142
+ - bin/rake
143
+ - bin/rspec
144
+ - bin/rubocop
86
145
  - bin/setup
87
146
  - bundle_hack.gemspec
147
+ - config/header.rb
148
+ - config/locales/en.yml
88
149
  - lib/bundle_hack.rb
150
+ - lib/bundle_hack/command.rb
151
+ - lib/bundle_hack/config_writer.rb
152
+ - lib/bundle_hack/errors.rb
153
+ - lib/bundle_hack/gem.rb
154
+ - lib/bundle_hack/gem_cloner.rb
155
+ - lib/bundle_hack/gem_locator.rb
156
+ - lib/bundle_hack/gemfile_parser.rb
157
+ - lib/bundle_hack/gemfile_writer.rb
158
+ - lib/bundle_hack/gemspec_cloner.rb
159
+ - lib/bundle_hack/gemspec_sources/base.rb
160
+ - lib/bundle_hack/gemspec_sources/cache.rb
161
+ - lib/bundle_hack/gemspec_sources/path.rb
162
+ - lib/bundle_hack/gemspec_sources/rubygems.rb
163
+ - lib/bundle_hack/sandbox.rb
89
164
  - lib/bundle_hack/version.rb
165
+ - plugins.rb
90
166
  homepage: http://github.com/bobf/bundle_hack
91
167
  licenses: []
92
168
  metadata: {}
@@ -106,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
182
  version: '0'
107
183
  requirements: []
108
184
  rubyforge_project:
109
- rubygems_version: 2.5.2.2
185
+ rubygems_version: 2.7.6
110
186
  signing_key:
111
187
  specification_version: 4
112
188
  summary: Safely hack gems in your bundle