bundle_hack 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +5 -0
- data/.rubocop.yml +3 -3
- data/.ruby-version +1 -0
- data/Gemfile +10 -1
- data/Makefile +5 -0
- data/README.md +4 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bundle_hack.gemspec +8 -3
- data/config/header.rb +4 -0
- data/config/locales/en.yml +6 -0
- data/lib/bundle_hack.rb +31 -1
- data/lib/bundle_hack/command.rb +13 -0
- data/lib/bundle_hack/config_writer.rb +37 -0
- data/lib/bundle_hack/errors.rb +8 -0
- data/lib/bundle_hack/gem.rb +62 -0
- data/lib/bundle_hack/gem_cloner.rb +19 -0
- data/lib/bundle_hack/gem_locator.rb +44 -0
- data/lib/bundle_hack/gemfile_parser.rb +134 -0
- data/lib/bundle_hack/gemfile_writer.rb +59 -0
- data/lib/bundle_hack/gemspec_cloner.rb +48 -0
- data/lib/bundle_hack/gemspec_sources/base.rb +15 -0
- data/lib/bundle_hack/gemspec_sources/cache.rb +50 -0
- data/lib/bundle_hack/gemspec_sources/path.rb +17 -0
- data/lib/bundle_hack/gemspec_sources/rubygems.rb +21 -0
- data/lib/bundle_hack/sandbox.rb +40 -0
- data/lib/bundle_hack/version.rb +1 -1
- data/plugins.rb +3 -0
- metadata +87 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3c07038f47d540e8eab90ea819c3a85e5672af8de6aca3e61e388cde3654406
|
4
|
+
data.tar.gz: 91dde30d342fee125343b625d3d7f5a4349cb6d6679208403de4b44fc81f7bf1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ae6b9769b44c75e47f67c6d72fe1a7286eeb2a21c89f14884742bf6f6eb1d3b5bcdae0798aeabb1394ba84efb06b07edb6a92a4312968bf82f823575e938e3d
|
7
|
+
data.tar.gz: 4b10042cc4bffe5271747702bb36fa5f66b0e0ae31e2536f26bf3852c3aa1c96557f325e46809b0bd43a80db1b5d80b13683ebff99adbce1fa6bd140cce1e70d
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -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
|
-
- '
|
11
|
+
- 'spec/dummy/**/*'
|
12
|
+
- 'spec/fixtures/**/*'
|
13
|
+
- 'config/header.rb'
|
data/.ruby-version
ADDED
@@ -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
|
-
#
|
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
|
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
|
data/bin/rake
ADDED
@@ -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")
|
data/bin/rspec
ADDED
@@ -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")
|
data/bin/rubocop
ADDED
@@ -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")
|
data/bundle_hack.gemspec
CHANGED
@@ -20,11 +20,16 @@ Gem::Specification.new do |spec|
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
spec.bindir = 'bin'
|
23
|
-
spec.executables =
|
23
|
+
spec.executables = []
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
-
spec.
|
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 '
|
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
|
data/config/header.rb
ADDED
data/lib/bundle_hack.rb
CHANGED
@@ -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
|
-
|
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,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,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
|
data/lib/bundle_hack/version.rb
CHANGED
data/plugins.rb
ADDED
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.
|
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-
|
11
|
+
date: 2018-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
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: '
|
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: '
|
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.
|
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:
|
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.
|
185
|
+
rubygems_version: 2.7.6
|
110
186
|
signing_key:
|
111
187
|
specification_version: 4
|
112
188
|
summary: Safely hack gems in your bundle
|