devpack 0.2.1 → 0.3.3

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
2
  SHA256:
3
- metadata.gz: 9502f5bb5412be236b272450cf30b9410a37676ce9a8ff7a769e01e08ebedc0f
4
- data.tar.gz: f96bfcfbd97d530ee6a93ff54a8fef02066e5206c468d50d0ef0e4e8501055bc
3
+ metadata.gz: 3e708e92800fdb16f17e5f15decb2d89fd935ea0c2b473f243fa78b472f571f6
4
+ data.tar.gz: 24924053b2ee1c90ebc75e5eb0a8f7ee45d7196c63a059da761f68da5c0f1ee5
5
5
  SHA512:
6
- metadata.gz: b94fee5740d2a0a57db65fdb9533d63397f0a1200babc95ea48104789084eab958570d0e659ba76cae1bbfc65e00839ab822ba9ebe300e8edc66d7490d28f266
7
- data.tar.gz: 8d05d1b3839e26b658357613fa6f5a3f2c2251aa939aa6ac68b509cc9733530dddf21024933c11b84e52894bd48c7290fc713307b1e1882c04bd1d0b477d7eb6
6
+ metadata.gz: 935db175be08d441d07fb6ad177e2bfa80b971dad327f6f0d3f761f4507a00883bae57414c20bebf19451bef5318aecc3a1d93d0b0591c2fe06dd769b99f1514
7
+ data.tar.gz: '089ba6844efef952c3b2f72fbc27d6e87c770f29c6bc39f57e97f0a4000874ea380a829926297f846cdee386e4ab5e661a5dc98c4c02ca35408fbf4381d17948'
data/.rubocop.yml CHANGED
@@ -3,52 +3,6 @@ Metrics/BlockLength:
3
3
  - "spec/**/*"
4
4
 
5
5
  AllCops:
6
+ NewCops: enable
6
7
  Exclude:
7
8
  - "spec/fixtures/**/*"
8
-
9
- Layout/EmptyLinesAroundAttributeAccessor:
10
- Enabled: true
11
- Layout/SpaceAroundMethodCallOperator:
12
- Enabled: true
13
- Lint/DeprecatedOpenSSLConstant:
14
- Enabled: true
15
- Lint/MixedRegexpCaptureTypes:
16
- Enabled: true
17
- Lint/RaiseException:
18
- Enabled: true
19
- Lint/StructNewOverride:
20
- Enabled: true
21
- Style/ExponentialNotation:
22
- Enabled: true
23
- Style/HashEachMethods:
24
- Enabled: true
25
- Style/HashTransformKeys:
26
- Enabled: true
27
- Style/HashTransformValues:
28
- Enabled: true
29
- Style/RedundantFetchBlock:
30
- Enabled: true
31
- Style/RedundantRegexpCharacterClass:
32
- Enabled: true
33
- Style/RedundantRegexpEscape:
34
- Enabled: true
35
- Style/SlicingWithRange:
36
- Enabled: true
37
- Lint/DuplicateElsifCondition:
38
- Enabled: true
39
- Style/AccessorGrouping:
40
- Enabled: true
41
- Style/ArrayCoercion:
42
- Enabled: true
43
- Style/BisectedAttrAccessor:
44
- Enabled: true
45
- Style/CaseLikeIf:
46
- Enabled: true
47
- Style/HashAsLastArrayItem:
48
- Enabled: true
49
- Style/HashLikeCase:
50
- Enabled: true
51
- Style/RedundantAssignment:
52
- Enabled: true
53
- Style/RedundantFileExtensionInRequire:
54
- Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.8
data/README.md CHANGED
@@ -4,44 +4,46 @@ Include a single gem in your `Gemfile` to allow developers to optionally include
4
4
 
5
5
  ## Installation
6
6
 
7
- Add the gem to your `Gemfile`:
7
+ Create a file named `.devpack` in your project's directory, or in any parent directory:
8
+
9
+ ```
10
+ # .devpack
11
+ awesome_print
12
+ byebug
13
+ better_errors
14
+
15
+ # Optionally specify a version:
16
+ pry:0.13.1
17
+ ```
18
+
19
+ Add _Devpack_ to any project's `Gemfile`:
8
20
 
9
21
  ```ruby
22
+ # Gemfile
10
23
  group :development, :test do
11
- gem 'devpack', '~> 0.2.1'
24
+ gem 'devpack', '~> 0.3.3'
12
25
  end
13
26
  ```
14
27
 
15
- And rebuild your bundle:
28
+ Rebuild your bundle:
16
29
 
17
30
  ```bash
18
- $ bundle install
31
+ bundle install
19
32
  ```
20
33
 
21
34
  ## Usage
22
35
 
23
- Create a file named `.devpack` in your project's directory:
24
-
25
- ```
26
- # .devpack
27
- awesome_print
28
- byebug
29
- better_errors
30
-
31
- # Optionally specify a version:
32
- pry:0.13.1
33
- ```
36
+ Load _Devpack_ (if your gems are not auto-loaded as in e.g. a _Rails_ application environment):
34
37
 
35
- All listed gems will be automatically required when _Devpack_ is loaded.
36
-
37
- If your gems are not auto-loaded (e.g. by _Rails_) then you must require the gem:
38
38
  ```ruby
39
39
  require 'devpack'
40
40
  ```
41
41
 
42
- Any gems that fail to load (due to `LoadError`) will generate a warning.
42
+ _Devpack_ will attempt to load all configured gems immediately, providing feedback to _stderr_. All dependencies are loaded with `require` after being recursively verified for compatibily with bundled gems before loading.
43
+
44
+ It is recommended to use a [global configuration](#global-configuration).
43
45
 
44
- It is recommended that `.devpack` is added to your `.gitignore`.
46
+ When using a per-project configuration, `.devpack` files should be added to `.gitignore`.
45
47
 
46
48
  ### Initializers
47
49
 
@@ -68,7 +70,7 @@ Bullet.enable = true
68
70
  ```
69
71
 
70
72
  ### Global Configuration
71
-
73
+ <a name="global-configuration"></a>
72
74
  To configure globally simply save your `.devpack` configuration file to any parent directory of your project directory, e.g. `~/.devpack`.
73
75
 
74
76
  This strategy also applies to `.devpack_initializers`.
data/devpack.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Allow developers to optionally include a set of development gems without adding to the Gemfile.'
13
13
  spec.homepage = 'https://github.com/bobf/devpack'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = spec.homepage
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'byebug', '~> 11.1'
29
29
  spec.add_development_dependency 'rspec', '~> 3.9'
30
30
  spec.add_development_dependency 'rspec-its', '~> 1.3'
31
- spec.add_development_dependency 'rubocop', '~> 0.88.0'
31
+ spec.add_development_dependency 'rubocop', '~> 1.8'
32
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
32
33
  spec.add_development_dependency 'strong_versions', '~> 0.4.4'
33
34
  end
@@ -4,7 +4,9 @@ module Devpack
4
4
  # Locates gems by searching in paths listed in GEM_PATH
5
5
  class GemGlob
6
6
  def find(name)
7
- matched_paths(name).max { |a, b| version(a) <=> version(b) }
7
+ matched_paths(name)
8
+ .sort { |a, b| version(a) <=> version(b) }
9
+ .reverse
8
10
  end
9
11
 
10
12
  private
@@ -14,9 +16,9 @@ module Devpack
14
16
  end
15
17
 
16
18
  def gem_paths
17
- return [] unless ENV.key?('GEM_PATH')
19
+ return [] if gem_path.nil?
18
20
 
19
- ENV.fetch('GEM_PATH').split(':').map { |path| Pathname.new(path) }
21
+ gem_path.split(':').map { |path| Pathname.new(path) }
20
22
  end
21
23
 
22
24
  def match?(name_with_version, basename)
@@ -40,5 +42,12 @@ module Devpack
40
42
  def version(path)
41
43
  Gem::Version.new(File.split(path).last.rpartition('-').last)
42
44
  end
45
+
46
+ def gem_path
47
+ return ENV['GEM_PATH'] if ENV.key?('GEM_PATH')
48
+ return ENV['GEM_HOME'] if ENV.key?('GEM_HOME')
49
+
50
+ nil
51
+ end
43
52
  end
44
53
  end
@@ -4,30 +4,72 @@ module Devpack
4
4
  # Locates relevant gemspec for a given gem and provides a full list of paths
5
5
  # for all `require_paths` listed in gemspec.
6
6
  class GemSpec
7
- def initialize(glob, name)
7
+ def initialize(glob, name, requirement)
8
8
  @name = name
9
9
  @glob = glob
10
+ @requirement = requirement
11
+ @dependency = Gem::Dependency.new(@name, @requirement)
10
12
  end
11
13
 
12
14
  def require_paths(visited = Set.new)
13
- return [] unless gemspec_path&.exist? && gem_path&.exist?
15
+ raise GemNotFoundError, @requirement.nil? ? '-' : required_version if gemspec.nil?
14
16
 
15
- (immediate_require_paths + dependency_require_paths(visited))
16
- .compact.flatten.uniq
17
+ (immediate_require_paths + dependency_require_paths(visited)).compact.flatten.uniq
17
18
  end
18
19
 
19
20
  def gemspec
20
- @gemspec ||= Gem::Specification.load(gemspec_path.to_s)
21
+ @gemspec ||= gemspecs.find do |spec|
22
+ next false if spec.nil?
23
+
24
+ @dependency.requirement.satisfied_by?(spec.version) && compatible?(spec)
25
+ end
21
26
  end
22
27
 
23
28
  private
24
29
 
30
+ def compatible?(spec)
31
+ return false if spec.nil?
32
+ return false if incompatible_version_loaded?(spec)
33
+
34
+ compatible_specs?(Gem.loaded_specs.values, [@dependency] + spec.runtime_dependencies)
35
+ end
36
+
37
+ def incompatible_version_loaded?(spec)
38
+ matched = Gem.loaded_specs[spec.name]
39
+ return false if matched.nil?
40
+
41
+ matched.version != spec.version
42
+ end
43
+
44
+ def required_version
45
+ @requirement.requirements.first.last.version
46
+ end
47
+
48
+ def compatible_specs?(specs, dependencies)
49
+ specs.all? { |spec| compatible_dependencies?(dependencies, spec) }
50
+ end
51
+
52
+ def compatible_dependencies?(dependencies, spec)
53
+ dependencies.all? { |dependency| compatible_dependency?(dependency, spec) }
54
+ end
55
+
56
+ def compatible_dependency?(dependency, spec)
57
+ return false if spec.nil?
58
+ return true unless dependency.name == spec.name
59
+
60
+ dependency.requirement.satisfied_by?(spec.version)
61
+ end
62
+
63
+ def gemspecs
64
+ @gemspecs ||= gemspec_paths.map { |path| Gem::Specification.load(path.to_s) }
65
+ end
66
+
25
67
  def dependency_require_paths(visited)
26
68
  dependencies.map do |dependency|
27
69
  next nil if visited.include?(dependency)
28
70
 
29
71
  visited << dependency
30
- GemSpec.new(@glob, name_with_version(dependency)).require_paths(visited)
72
+ GemSpec.new(@glob, dependency.name, dependency.requirement).require_paths(visited)
31
73
  end
32
74
  end
33
75
 
@@ -35,34 +77,28 @@ module Devpack
35
77
  gemspec.runtime_dependencies
36
78
  end
37
79
 
38
- def gem_path
39
- return nil if located_gem.nil?
80
+ def gem_paths
81
+ return nil if candidates.empty?
40
82
 
41
- Pathname.new(located_gem)
83
+ candidates.map { |candidate| Pathname.new(candidate) }
42
84
  end
43
85
 
44
- def gemspec_path
45
- return nil if gem_path.nil?
86
+ def gemspec_paths
87
+ return [] if gem_paths.nil?
46
88
 
47
- gem_path.join('..', '..', 'specifications', "#{gem_path.basename}.gemspec")
48
- .expand_path
89
+ gem_paths.map do |path|
90
+ path.join('..', '..', 'specifications', "#{path.basename}.gemspec").expand_path
91
+ end
49
92
  end
50
93
 
51
94
  def immediate_require_paths
52
95
  gemspec
53
96
  .require_paths
54
- .map { |path| gem_path.join(path).to_s }
55
- end
56
-
57
- def name_with_version(dependency)
58
- spec = dependency.to_spec
59
- "#{spec.name}:#{spec.version}"
60
- rescue Gem::MissingSpecError
61
- dependency.name
97
+ .map { |path| File.join(gemspec.full_gem_path, path) }
62
98
  end
63
99
 
64
- def located_gem
65
- @located_gem ||= @glob.find(@name)
100
+ def candidates
101
+ @candidates ||= @glob.find(@name)
66
102
  end
67
103
  end
68
104
  end
data/lib/devpack/gems.rb CHANGED
@@ -5,8 +5,11 @@ module Devpack
5
5
  class Gems
6
6
  include Timeable
7
7
 
8
- def initialize(config)
8
+ def initialize(config, glob = GemGlob.new)
9
9
  @config = config
10
+ @gem_glob = glob
11
+ @failures = []
12
+ @missing = []
10
13
  end
11
14
 
12
15
  def load
@@ -14,35 +17,56 @@ module Devpack
14
17
 
15
18
  gems, time = timed { load_devpack }
16
19
  names = gems.map(&:first)
17
- warn(Messages.loaded(@config.devpack_path, gems, time.round(2)))
20
+ summarize(gems, time)
18
21
  names
19
22
  end
20
23
 
21
24
  private
22
25
 
26
+ def summarize(gems, time)
27
+ @failures.each do |failure|
28
+ warn(:error, Messages.failure(failure[:name], failure[:message]))
29
+ end
30
+ warn(:success, Messages.loaded(@config.devpack_path, gems, time.round(2)))
31
+ warn(:info, Messages.install_missing(@missing)) unless @missing.empty?
32
+ end
33
+
23
34
  def load_devpack
24
- @config.requested_gems.map { |name| load_gem(name) }.compact
35
+ @config.requested_gems.map do |requested|
36
+ name, _, version = requested.partition(':')
37
+ load_gem(name, version.empty? ? nil : Gem::Requirement.new("= #{version}"))
38
+ end.compact
25
39
  end
26
40
 
27
- def load_gem(name)
28
- [name, activate(name)]
41
+ def load_gem(name, requirement)
42
+ [name, activate(name, requirement)]
29
43
  rescue LoadError => e
30
- warn(Messages.failure(name, load_error_message(e)))
44
+ deactivate(name)
45
+ @failures << { name: name, message: load_error_message(e) }
46
+ nil
47
+ rescue GemNotFoundError => e
48
+ @missing << { name: name, version: e.message == '-' ? nil : e.message }
31
49
  nil
32
50
  end
33
51
 
34
- def activate(name)
35
- spec = GemSpec.new(gem_glob, name)
52
+ def activate(name, version)
53
+ spec = GemSpec.new(@gem_glob, name, version)
36
54
  update_load_path(spec.require_paths)
37
- loaded = Kernel.require(name)
55
+ # NOTE: do this before we require, because some gems use the gemspec to
56
+ # declare their version...
38
57
  Gem.loaded_specs[name] = spec.gemspec
58
+ loaded = Kernel.require(name)
39
59
  spec.gemspec&.activated = true
40
60
  spec.gemspec&.instance_variable_set(:@loaded, true)
41
61
  loaded
42
62
  end
43
63
 
44
- def warn(message)
45
- Devpack.warn(message)
64
+ def deactivate(name)
65
+ Gem.loaded_specs.delete(name)
66
+ end
67
+
68
+ def warn(level, message)
69
+ Devpack.warn(level, message)
46
70
  end
47
71
 
48
72
  def load_error_message(error)
@@ -51,10 +75,6 @@ module Devpack
51
75
  %[(#{error.message})\n#{error.backtrace.join("\n")}]
52
76
  end
53
77
 
54
- def gem_glob
55
- @gem_glob ||= GemGlob.new
56
- end
57
-
58
78
  def update_load_path(paths)
59
79
  $LOAD_PATH.concat(paths)
60
80
  end
@@ -15,7 +15,7 @@ module Devpack
15
15
  return if path.nil?
16
16
 
17
17
  args = path, initializers, time.round(2)
18
- Devpack.warn(Messages.loaded_initializers(*args))
18
+ Devpack.warn(:success, Messages.loaded_initializers(*args))
19
19
  end
20
20
 
21
21
  private
@@ -27,7 +27,7 @@ module Devpack
27
27
  def load_initializer(path)
28
28
  require path
29
29
  rescue ScriptError, StandardError => e
30
- Devpack.warn(Messages.initializer_failure(path, message(e)))
30
+ Devpack.warn(:error, Messages.initializer_failure(path, message(e)))
31
31
  nil
32
32
  end
33
33
 
@@ -6,8 +6,7 @@ module Devpack
6
6
  class << self
7
7
  def failure(name, error_message)
8
8
  base = "Failed to load `#{name}`"
9
- install = "bundle exec gem install #{name}"
10
- "#{base}. Try `#{install}` #{error_message}"
9
+ "#{base}. #{error_message}"
11
10
  end
12
11
 
13
12
  def initializer_failure(path, error_message)
@@ -26,11 +25,44 @@ module Devpack
26
25
  "Loaded #{initializers.compact.size} initializer(s) from '#{path}' in #{time} seconds"
27
26
  end
28
27
 
28
+ def install_missing(missing)
29
+ gems = missing.map do |spec|
30
+ spec[:version].nil? ? spec[:name] : "#{spec[:name]}==#{spec[:version]}"
31
+ end
32
+
33
+ "Install #{missing.size} missing gem(s): #{color(:cyan) { command(gems) }}"
34
+ end
35
+
36
+ def test
37
+ puts "#{color(:green) { 'green' }} #{color(:red) { 'red' }} #{color(:blue) { 'blue' }}"
38
+ puts "#{color(:cyan) { 'cyan' }} #{color(:yellow) { 'yellow' }} #{color(:magenta) { 'magenta' }}"
39
+ end
40
+
41
+ def color(name)
42
+ "#{palette.fetch(name)}#{yield}#{palette.fetch(:reset)}"
43
+ end
44
+
29
45
  private
30
46
 
31
47
  def indented(message)
32
48
  message.split("\n").map { |line| " #{line}" }.join("\n")
33
49
  end
50
+
51
+ def command(gems)
52
+ "bundle exec gem install #{gems.join(' ')}"
53
+ end
54
+
55
+ def palette
56
+ {
57
+ reset: "\e[39m",
58
+ red: "\e[31m",
59
+ green: "\e[32m",
60
+ yellow: "\e[33m",
61
+ blue: "\e[34m",
62
+ magenta: "\e[35m",
63
+ cyan: "\e[36m"
64
+ }
65
+ end
34
66
  end
35
67
  end
36
68
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Devpack
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.3'
5
5
  end
data/lib/devpack.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'pathname'
5
+ require 'set'
5
6
 
6
7
  require 'devpack/timeable'
7
8
  require 'devpack/config'
@@ -16,9 +17,11 @@ require 'devpack/version'
16
17
  module Devpack
17
18
  class Error < StandardError; end
18
19
 
20
+ class GemNotFoundError < Error; end
21
+
19
22
  class << self
20
- def warn(message)
21
- prefixed = message.split("\n").map { |line| "[devpack] #{line}" }.join("\n")
23
+ def warn(level, message)
24
+ prefixed = message.split("\n").map { |line| "#{prefix(level)} #{line}" }.join("\n")
22
25
  Kernel.warn(prefixed)
23
26
  end
24
27
 
@@ -37,6 +40,20 @@ module Devpack
37
40
  def config
38
41
  @config ||= Devpack::Config.new(Dir.pwd)
39
42
  end
43
+
44
+ private
45
+
46
+ def prefix(level)
47
+ "#{Messages.color(:blue) { '[' }}devpack#{Messages.color(:blue) { ']' }} #{icon(level)}"
48
+ end
49
+
50
+ def icon(level)
51
+ {
52
+ success: Messages.color(:green) { '✓' },
53
+ info: Messages.color(:cyan) { 'ℹ' },
54
+ error: Messages.color(:red) { '✗' }
55
+ }.fetch(level)
56
+ end
40
57
  end
41
58
  end
42
59
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devpack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-03 00:00:00.000000000 Z
11
+ date: 2021-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -58,14 +58,28 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.88.0
61
+ version: '1.8'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.88.0
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: strong_versions
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -91,6 +105,7 @@ files:
91
105
  - ".gitignore"
92
106
  - ".rspec"
93
107
  - ".rubocop.yml"
108
+ - ".ruby-version"
94
109
  - CHANGELOG.md
95
110
  - Gemfile
96
111
  - LICENSE.txt
@@ -117,7 +132,7 @@ metadata:
117
132
  homepage_uri: https://github.com/bobf/devpack
118
133
  source_code_uri: https://github.com/bobf/devpack
119
134
  changelog_uri: https://github.com/bobf/devpack/blob/master/CHANGELOG.md
120
- post_install_message:
135
+ post_install_message:
121
136
  rdoc_options: []
122
137
  require_paths:
123
138
  - lib
@@ -125,15 +140,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
140
  requirements:
126
141
  - - ">="
127
142
  - !ruby/object:Gem::Version
128
- version: 2.3.0
143
+ version: 2.5.0
129
144
  required_rubygems_version: !ruby/object:Gem::Requirement
130
145
  requirements:
131
146
  - - ">="
132
147
  - !ruby/object:Gem::Version
133
148
  version: '0'
134
149
  requirements: []
135
- rubygems_version: 3.1.2
136
- signing_key:
150
+ rubyforge_project:
151
+ rubygems_version: 2.7.6.2
152
+ signing_key:
137
153
  specification_version: 4
138
154
  summary: Conveniently tailor your development environment
139
155
  test_files: []