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 +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
|