devpack 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0853562f5cbb43d560d36d6913f3e1a7bc44ffb49561183146b65a6c69c2f6bb'
4
- data.tar.gz: 63cb7a8c6d3bed3bb8806c7a2b3dcd12a82728cfe177c7e7c8f4f6f64112b071
3
+ metadata.gz: 9502f5bb5412be236b272450cf30b9410a37676ce9a8ff7a769e01e08ebedc0f
4
+ data.tar.gz: f96bfcfbd97d530ee6a93ff54a8fef02066e5206c468d50d0ef0e4e8501055bc
5
5
  SHA512:
6
- metadata.gz: b814b628151622a7780f74356d77e94528a1053eb14cac5334faa5fb2784ec0114773d0c641cde3091ba0b12dde8010ed74efbed2f457cea5581abb9fc95fbb1
7
- data.tar.gz: 0cea088ec5ba97520eab7734813d972b03cab7332447e3ff3b375716c54bfa87ddcee5168e06fc99c5f469fc4d79a15b599b26d8488e0c8c926ad1871322d62c
6
+ metadata.gz: b94fee5740d2a0a57db65fdb9533d63397f0a1200babc95ea48104789084eab958570d0e659ba76cae1bbfc65e00839ab822ba9ebe300e8edc66d7490d28f266
7
+ data.tar.gz: 8d05d1b3839e26b658357613fa6f5a3f2c2251aa939aa6ac68b509cc9733530dddf21024933c11b84e52894bd48c7290fc713307b1e1882c04bd1d0b477d7eb6
data/.gitignore CHANGED
@@ -10,3 +10,5 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  Gemfile.lock
13
+
14
+ devpack-*.gem
@@ -2,6 +2,10 @@ Metrics/BlockLength:
2
2
  Exclude:
3
3
  - "spec/**/*"
4
4
 
5
+ AllCops:
6
+ Exclude:
7
+ - "spec/fixtures/**/*"
8
+
5
9
  Layout/EmptyLinesAroundAttributeAccessor:
6
10
  Enabled: true
7
11
  Layout/SpaceAroundMethodCallOperator:
@@ -30,3 +34,21 @@ Style/RedundantRegexpEscape:
30
34
  Enabled: true
31
35
  Style/SlicingWithRange:
32
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
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Core functionality implemented. Load a `.devpack` configuration file from current directory or, if not present, from a parent directory. Attempt to locate and `require` all listed gems.
6
+
7
+ ## 0.1.1
8
+
9
+ Use `GEM_PATH` instead of `GEM_HOME` to locate gems.
10
+
11
+ Optimise load time by searching non-recursively in `/gems` directory (for each path listed in `GEM_PATH`).
12
+
13
+ Load latest version of gem by default. Allow specifying version with rubygems syntax `example:0.1.0`.
14
+
15
+ Permit comments in config file.
16
+
17
+ Use `Gem::Specification` to load "rendered" gemspec (i.e. the file created by rubygems when the gem is installed). This version of the gemspec will load very quickly so no need to do custom gemspec parsing any more. This also accounts for "missing" gemspecs.
18
+
19
+ ## 0.1.2
20
+
21
+ Recursively include gem dependencies in `$LOAD_PATH` rather than assuming that any dependencies are already loaded.
22
+
23
+ Include original error message when warning that a gem was unable to be loaded.
24
+
25
+ ## 0.1.3
26
+
27
+ Use a more appropriate method of identifying the latest version of a gem (use `Gem::Version` to sort matched gem paths).
28
+
29
+ Fix edge case where e.g. `pry-rails-0.1.0` was matching for `pry` due to naive match logic. Split on last dash instead of first (i.e. don't assume gems will not have a dash in their name; last dash separates gem name from version in directory name).
30
+
31
+ ## 0.2.0
32
+
33
+ Add support for initializers. Files located in a `.devpack_initializers` directory will be loaded after gems configured in `.devpack` have been loaded. When using _Rails_ these files will be loaded using the `after_initialize` hook. Thanks to @joshmn for this idea: https://github.com/bobf/devpack/issues/1
34
+
35
+ Show full tracebacks of load errors when `DEVPACK_DEBUG` is set in environment.
36
+
37
+ Rename `DISABLE_DEVPACK` environment variable to `DEVPACK_DISABLE` for consistency.
38
+
39
+ ## 0.2.1
40
+
41
+ Fully activate gem on load: add gem spec to `Gem.loaded_specs` and set instance variables `@loaded` and `@activated` to `true`. This mimics `Gem::Specification#activate` to ensure that anything that depends on these characteristics will function as normal.
data/Makefile CHANGED
@@ -3,3 +3,7 @@ test:
3
3
  bundle exec rspec
4
4
  bundle exec rubocop
5
5
  bundle exec strong_versions
6
+
7
+ .PHONY: build
8
+ build: test
9
+ bundle exec gem build devpack.gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Devpack
2
2
 
3
- Conveniently load a set of gems to tailor your development environment without modifying your application's _Gemfile_. Configurable globally or per-project.
3
+ Include a single gem in your `Gemfile` to allow developers to optionally include their preferred set of development gems without cluttering the `Gemfile`. Configurable globally or per-project.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,7 +8,7 @@ Add the gem to your `Gemfile`:
8
8
 
9
9
  ```ruby
10
10
  group :development, :test do
11
- gem 'devpack', '~> 0.1.0'
11
+ gem 'devpack', '~> 0.2.1'
12
12
  end
13
13
  ```
14
14
 
@@ -20,27 +20,71 @@ $ bundle install
20
20
 
21
21
  ## Usage
22
22
 
23
- Create a file named `.devpack` in your project's directory (or any parent directory) containing a list of gems you wish to load:
23
+ Create a file named `.devpack` in your project's directory:
24
24
 
25
25
  ```
26
26
  # .devpack
27
27
  awesome_print
28
28
  byebug
29
29
  better_errors
30
+
31
+ # Optionally specify a version:
32
+ pry:0.13.1
33
+ ```
34
+
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
+ ```ruby
39
+ require 'devpack'
40
+ ```
41
+
42
+ Any gems that fail to load (due to `LoadError`) will generate a warning.
43
+
44
+ It is recommended that `.devpack` is added to your `.gitignore`.
45
+
46
+ ### Initializers
47
+
48
+ Custom initializers can be loaded by creating a directory named `.devpack_initializers` containing a set of `.rb` files.
49
+
50
+ Initializers will be loaded in alphabetical order after all gems listed in the `.devpack` configuration file have been loaded.
51
+
52
+ Initializers that fail to load (for any reason) will generate a warning.
53
+
54
+ ```ruby
55
+ # .devpack_initializers/pry.rb
56
+
57
+ Pry.config.pager = false
30
58
  ```
31
59
 
32
- All listed gems will be automatically required.
60
+ #### Rails
33
61
 
34
- It is recommended that the `.devpack` file is added to your `.gitignore`:
62
+ If _Rails_ is detected then files in the `.devpack_initializers` directory will be loaded using the _Rails_ `after_initialize` hook (i.e. after all other frameworks have been initialized).
35
63
 
64
+ ```ruby
65
+ # .devpack_initializers/bullet.rb
66
+
67
+ Bullet.enable = true
36
68
  ```
37
- # .gitignore
38
- .devpack
69
+
70
+ ### Global Configuration
71
+
72
+ To configure globally simply save your `.devpack` configuration file to any parent directory of your project directory, e.g. `~/.devpack`.
73
+
74
+ This strategy also applies to `.devpack_initializers`.
75
+
76
+ ### Disabling
77
+
78
+ To disable _Devpack_ set the environment variable `DEVPACK_DISABLE` to any value:
79
+ ```bash
80
+ DEVPACK_DISABLE=1 bundle exec ruby myapp.rb
39
81
  ```
40
82
 
41
- To disable _Devpack_ set the environment variable `DISABLE_DEVPACK` to any value:
83
+ ### Debugging
84
+
85
+ To see the full traceback of any errors encountered at load time set the environment variable `DEVPACK_DEBUG` to any value:
42
86
  ```bash
43
- DISABLE_DEVPACK=1 bundle exec ruby myapp.rb
87
+ DEVPACK_DEBUG=1 bundle exec ruby myapp.rb
44
88
  ```
45
89
 
46
90
  ## License
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ['git@bob.frl']
10
10
 
11
11
  spec.summary = 'Conveniently tailor your development environment'
12
- spec.description = 'Provide a list of gems to load in your own environment'
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
15
  spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
@@ -28,6 +28,6 @@ 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.86.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.88.0'
32
32
  spec.add_development_dependency 'strong_versions', '~> 0.4.4'
33
33
  end
@@ -3,12 +3,46 @@
3
3
  require 'rubygems'
4
4
  require 'pathname'
5
5
 
6
- require 'devpack/version'
6
+ require 'devpack/timeable'
7
+ require 'devpack/config'
7
8
  require 'devpack/gems'
8
- require 'devpack/gem_specification_context'
9
+ require 'devpack/gem_glob'
10
+ require 'devpack/gem_spec'
11
+ require 'devpack/initializers'
12
+ require 'devpack/messages'
13
+ require 'devpack/version'
9
14
 
15
+ # Provides helper method for writing warning messages.
10
16
  module Devpack
11
17
  class Error < StandardError; end
18
+
19
+ class << self
20
+ def warn(message)
21
+ prefixed = message.split("\n").map { |line| "[devpack] #{line}" }.join("\n")
22
+ Kernel.warn(prefixed)
23
+ end
24
+
25
+ def debug?
26
+ ENV.key?('DEVPACK_DEBUG')
27
+ end
28
+
29
+ def disabled?
30
+ ENV.key?('DEVPACK_DISABLE')
31
+ end
32
+
33
+ def rails?
34
+ defined?(Rails::Railtie)
35
+ end
36
+
37
+ def config
38
+ @config ||= Devpack::Config.new(Dir.pwd)
39
+ end
40
+ end
12
41
  end
13
42
 
14
- Devpack::Gems.new('.').load unless ENV.key?('DISABLE_DEVPACK')
43
+ unless Devpack.disabled?
44
+ require 'devpack/railtie' if Devpack.rails?
45
+
46
+ Devpack::Gems.new(Devpack.config).load
47
+ Devpack::Initializers.new(Devpack.config).load unless Devpack.rails?
48
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Locates and parses .devpack config file
5
+ class Config
6
+ FILENAME = '.devpack'
7
+ INITIALIZERS_DIRECTORY_NAME = '.devpack_initializers'
8
+ MAX_PARENTS = 100 # Avoid infinite loops (symlinks/weird file systems)
9
+
10
+ def initialize(pwd)
11
+ @pwd = Pathname.new(pwd)
12
+ end
13
+
14
+ def requested_gems
15
+ return nil if devpack_path.nil?
16
+
17
+ File.readlines(devpack_path)
18
+ .map(&filter_comments)
19
+ .compact
20
+ end
21
+
22
+ def devpack_path
23
+ @devpack_path ||= located_path(@pwd, FILENAME, :file)
24
+ end
25
+
26
+ def devpack_initializers_path
27
+ @devpack_initializers_path ||= located_path(@pwd, INITIALIZERS_DIRECTORY_NAME, :directory)
28
+ end
29
+
30
+ def devpack_initializer_paths
31
+ devpack_initializers_path&.glob(File.join('**', '*.rb'))&.map(&:to_s)&.sort || []
32
+ end
33
+
34
+ private
35
+
36
+ def located_path(next_parent, filename, type)
37
+ loop.with_index(1) do |_, index|
38
+ return nil if index > MAX_PARENTS
39
+
40
+ path = next_parent.join(filename)
41
+ next_parent = next_parent.parent
42
+ next unless File.exist?(path) && File.public_send("#{type}?", path)
43
+
44
+ return path
45
+ end
46
+ end
47
+
48
+ def filter_comments
49
+ proc do |line|
50
+ stripped = line.strip
51
+ next nil if stripped.empty?
52
+ next nil if stripped.start_with?('#')
53
+
54
+ stripped.gsub(/\s*#.*$/, '') # Remove inline comments (like this one)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Locates gems by searching in paths listed in GEM_PATH
5
+ class GemGlob
6
+ def find(name)
7
+ matched_paths(name).max { |a, b| version(a) <=> version(b) }
8
+ end
9
+
10
+ private
11
+
12
+ def glob
13
+ @glob ||= gem_paths.map { |path| Dir.glob(path.join('gems', '*')) }.flatten
14
+ end
15
+
16
+ def gem_paths
17
+ return [] unless ENV.key?('GEM_PATH')
18
+
19
+ ENV.fetch('GEM_PATH').split(':').map { |path| Pathname.new(path) }
20
+ end
21
+
22
+ def match?(name_with_version, basename)
23
+ name, _, version = name_with_version.partition(':')
24
+ return true if version.empty? && basename.rpartition('-').first == name
25
+ return true if !version.empty? && basename == "#{name}-#{version}"
26
+
27
+ false
28
+ end
29
+
30
+ def matched_paths(name)
31
+ glob.select do |path|
32
+ pathname = Pathname.new(path)
33
+ next unless pathname.directory?
34
+
35
+ basename = pathname.basename.to_s
36
+ match?(name, basename)
37
+ end
38
+ end
39
+
40
+ def version(path)
41
+ Gem::Version.new(File.split(path).last.rpartition('-').last)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Locates relevant gemspec for a given gem and provides a full list of paths
5
+ # for all `require_paths` listed in gemspec.
6
+ class GemSpec
7
+ def initialize(glob, name)
8
+ @name = name
9
+ @glob = glob
10
+ end
11
+
12
+ def require_paths(visited = Set.new)
13
+ return [] unless gemspec_path&.exist? && gem_path&.exist?
14
+
15
+ (immediate_require_paths + dependency_require_paths(visited))
16
+ .compact.flatten.uniq
17
+ end
18
+
19
+ def gemspec
20
+ @gemspec ||= Gem::Specification.load(gemspec_path.to_s)
21
+ end
22
+
23
+ private
24
+
25
+ def dependency_require_paths(visited)
26
+ dependencies.map do |dependency|
27
+ next nil if visited.include?(dependency)
28
+
29
+ visited << dependency
30
+ GemSpec.new(@glob, name_with_version(dependency)).require_paths(visited)
31
+ end
32
+ end
33
+
34
+ def dependencies
35
+ gemspec.runtime_dependencies
36
+ end
37
+
38
+ def gem_path
39
+ return nil if located_gem.nil?
40
+
41
+ Pathname.new(located_gem)
42
+ end
43
+
44
+ def gemspec_path
45
+ return nil if gem_path.nil?
46
+
47
+ gem_path.join('..', '..', 'specifications', "#{gem_path.basename}.gemspec")
48
+ .expand_path
49
+ end
50
+
51
+ def immediate_require_paths
52
+ gemspec
53
+ .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
62
+ end
63
+
64
+ def located_gem
65
+ @located_gem ||= @glob.find(@name)
66
+ end
67
+ end
68
+ end
@@ -1,135 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Devpack
4
- # Parses .devpack file and provides/loads a list of specified gems
4
+ # Loads requested gems from configuration
5
5
  class Gems
6
- FILENAME = '.devpack'
7
- MAX_PARENTS = 100 # Avoid infinite loops (symlinks/weird file systems)
6
+ include Timeable
8
7
 
9
- def initialize(path)
10
- @load_path = $LOAD_PATH
11
- @gem_home = Pathname.new(ENV['GEM_HOME'])
12
- @path = Pathname.new(path)
8
+ def initialize(config)
9
+ @config = config
13
10
  end
14
11
 
15
12
  def load
16
- path = devpack_path
17
- return [] if path.nil?
13
+ return [] if @config.requested_gems.nil?
18
14
 
19
- gems, time = timed { load_devpack(path) }
15
+ gems, time = timed { load_devpack }
20
16
  names = gems.map(&:first)
21
- warn(loaded_message(path, gems, time))
17
+ warn(Messages.loaded(@config.devpack_path, gems, time.round(2)))
22
18
  names
23
19
  end
24
20
 
25
21
  private
26
22
 
27
- def timed
28
- start = Time.now.utc
29
- result = yield
30
- [result, Time.now.utc - start]
31
- end
32
-
33
- def load_devpack(path)
34
- gem_list(path).map { |name| load_gem(name) }.compact
35
- end
36
-
37
- def devpack_path
38
- return default_devpack_path if File.exist?(default_devpack_path)
39
- return parent_devpack_path unless parent_devpack_path.nil?
40
-
41
- nil
42
- end
43
-
44
- def default_devpack_path
45
- @path.join(FILENAME)
46
- end
47
-
48
- def gem_list(path)
49
- File.readlines(path).map(&:chomp)
23
+ def load_devpack
24
+ @config.requested_gems.map { |name| load_gem(name) }.compact
50
25
  end
51
26
 
52
27
  def load_gem(name)
53
- # TODO: Decide what to do when Bundler is not defined.
54
- # Do we want to support this scenario ?
55
- update_load_path(name) if defined?(Bundler)
56
- [name, Kernel.require(name)]
57
- rescue LoadError
58
- warn(failure_message(name))
28
+ [name, activate(name)]
29
+ rescue LoadError => e
30
+ warn(Messages.failure(name, load_error_message(e)))
59
31
  nil
60
32
  end
61
33
 
62
- def parent_devpack_path
63
- next_parent = @path.parent
64
- loop.with_index(1) do |_, index|
65
- break if index >= MAX_PARENTS
66
-
67
- next_parent = next_parent.parent
68
- break if next_parent == next_parent.parent
69
-
70
- path = next_parent.join(FILENAME)
71
- next unless File.exist?(path)
72
-
73
- return path
74
- end
34
+ def activate(name)
35
+ spec = GemSpec.new(gem_glob, name)
36
+ update_load_path(spec.require_paths)
37
+ loaded = Kernel.require(name)
38
+ Gem.loaded_specs[name] = spec.gemspec
39
+ spec.gemspec&.activated = true
40
+ spec.gemspec&.instance_variable_set(:@loaded, true)
41
+ loaded
75
42
  end
76
43
 
77
44
  def warn(message)
78
- Kernel.warn("[devpack] #{message}")
45
+ Devpack.warn(message)
79
46
  end
80
47
 
81
- def gems_glob
82
- @gems_glob ||= Dir.glob(@gem_home.join('gems', '**', '*'))
83
- end
84
-
85
- def gem_path(name)
86
- found = gems_glob.find do |path|
87
- pathname = Pathname.new(path)
88
- next unless pathname.directory?
89
-
90
- # TODO: We should allow optionally specifying a version and default to loading
91
- # the latest version available.
92
- pathname.basename.to_s.start_with?("#{name}-")
93
- end
94
-
95
- found.nil? ? nil : Pathname.new(found)
96
- end
97
-
98
- def update_load_path(name)
99
- path = gem_path(name)
100
- return if path.nil?
48
+ def load_error_message(error)
49
+ return "(#{error.message})" unless Devpack.debug?
101
50
 
102
- $LOAD_PATH.concat(require_paths(path, name))
51
+ %[(#{error.message})\n#{error.backtrace.join("\n")}]
103
52
  end
104
53
 
105
- def require_paths(gem_path, name)
106
- gemspec_path = gem_path.join("#{name}.gemspec")
107
- lib_path = gem_path.join('lib')
108
- # REVIEW: Some gems don't have a .gemspec - need to understand how they are loaded.
109
- # Use `/lib` for now if it exists as this will work for vast majority of cases.
110
- return [lib_path] if !gemspec_path.exist? && lib_path.exist?
111
-
112
- gemspec = File.read(gemspec_path.to_s)
113
- GemSpecificationContext.class_eval(gemspec)
114
- full_require_paths(gem_path)
115
- end
116
-
117
- def full_require_paths(base_path)
118
- GemSpecificationContext.require_paths.map { |path| base_path.join(path) }
54
+ def gem_glob
55
+ @gem_glob ||= GemGlob.new
119
56
  end
120
57
 
121
- def failure_message(name)
122
- base = "Failed to load `#{name}`"
123
- install = (defined?(Bundler) ? 'bundle exec ' : '') + "gem install #{name}"
124
- "#{base}. Try `#{install}`"
125
- end
126
-
127
- def loaded_message(path, gems, time)
128
- already_loaded = gems.size - gems.reject { |_, loaded| loaded }.size
129
- base = "Loaded #{already_loaded} development gem(s) from '#{path}' in #{time} seconds"
130
- return "#{base}." if already_loaded == gems.size
131
-
132
- "#{base} (#{gems.size - already_loaded} gem(s) were already loaded by environment)."
58
+ def update_load_path(paths)
59
+ $LOAD_PATH.concat(paths)
133
60
  end
134
61
  end
135
62
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Loads requested initializers from configuration
5
+ class Initializers
6
+ include Timeable
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def load
13
+ initializers, time = timed { load_initializers }
14
+ path = @config.devpack_initializers_path
15
+ return if path.nil?
16
+
17
+ args = path, initializers, time.round(2)
18
+ Devpack.warn(Messages.loaded_initializers(*args))
19
+ end
20
+
21
+ private
22
+
23
+ def load_initializers
24
+ @config.devpack_initializer_paths.map { |path| load_initializer(path) }
25
+ end
26
+
27
+ def load_initializer(path)
28
+ require path
29
+ rescue ScriptError, StandardError => e
30
+ Devpack.warn(Messages.initializer_failure(path, message(e)))
31
+ nil
32
+ end
33
+
34
+ def message(error)
35
+ return "(#{error.class.name} - #{error.message&.split("\n")&.first})" unless Devpack.debug?
36
+
37
+ %[(#{error.class.name})\n#{error.message}\n#{error.backtrace.join("\n")}]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Generates output messages.
5
+ class Messages
6
+ class << self
7
+ def failure(name, error_message)
8
+ base = "Failed to load `#{name}`"
9
+ install = "bundle exec gem install #{name}"
10
+ "#{base}. Try `#{install}` #{error_message}"
11
+ end
12
+
13
+ def initializer_failure(path, error_message)
14
+ "Failed to load initializer `#{path}`: #{error_message}"
15
+ end
16
+
17
+ def loaded(path, gems, time)
18
+ already_loaded = gems.size - gems.reject { |_, loaded| loaded }.size
19
+ base = "Loaded #{already_loaded} development gem(s) from '#{path}' in #{time} seconds"
20
+ return "#{base}." if already_loaded == gems.size
21
+
22
+ "#{base} (#{gems.size - already_loaded} gem(s) were already loaded by environment)."
23
+ end
24
+
25
+ def loaded_initializers(path, initializers, time)
26
+ "Loaded #{initializers.compact.size} initializer(s) from '#{path}' in #{time} seconds"
27
+ end
28
+
29
+ private
30
+
31
+ def indented(message)
32
+ message.split("\n").map { |line| " #{line}" }.join("\n")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devpack
4
+ # Loads Devpack initializers after standard Rails initializers have been loaded.
5
+ class Railtie < Rails::Railtie
6
+ config.after_initialize { Devpack::Initializers.new(Devpack.config).load }
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides result and run time of a given block.
4
+ module Timeable
5
+ def timed
6
+ start = Time.now.utc
7
+ result = yield
8
+ [result, Time.now.utc - start]
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Devpack
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
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.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-04 00:00:00.000000000 Z
11
+ date: 2020-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.86.0
61
+ version: 0.88.0
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.86.0
68
+ version: 0.88.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: strong_versions
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.4.4
83
- description: Provide a list of gems to load in your own environment
83
+ description: Allow developers to optionally include a set of development gems without
84
+ adding to the Gemfile.
84
85
  email:
85
86
  - git@bob.frl
86
87
  executables: []
@@ -90,6 +91,7 @@ files:
90
91
  - ".gitignore"
91
92
  - ".rspec"
92
93
  - ".rubocop.yml"
94
+ - CHANGELOG.md
93
95
  - Gemfile
94
96
  - LICENSE.txt
95
97
  - Makefile
@@ -99,8 +101,14 @@ files:
99
101
  - bin/setup
100
102
  - devpack.gemspec
101
103
  - lib/devpack.rb
102
- - lib/devpack/gem_specification_context.rb
104
+ - lib/devpack/config.rb
105
+ - lib/devpack/gem_glob.rb
106
+ - lib/devpack/gem_spec.rb
103
107
  - lib/devpack/gems.rb
108
+ - lib/devpack/initializers.rb
109
+ - lib/devpack/messages.rb
110
+ - lib/devpack/railtie.rb
111
+ - lib/devpack/timeable.rb
104
112
  - lib/devpack/version.rb
105
113
  homepage: https://github.com/bobf/devpack
106
114
  licenses:
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Devpack
4
- # Stubbed instance_eval context to evaluate a .gemspec file and extract required_paths
5
- # Avoids doing expensive operations (e.g. attempting to run `git ls-files`) which are
6
- # typically invoked when using `Gem::Specification.load`.
7
- class GemSpecificationContext
8
- def initialize(*_); end
9
-
10
- class << self
11
- attr_accessor :require_paths
12
-
13
- @require_paths = []
14
-
15
- def require(*_); end
16
-
17
- def __dir__
18
- '.'
19
- end
20
-
21
- def require_relative(*_); end
22
- end
23
-
24
- module Gem
25
- # Stubbed Rubygems Gem::Specification. Everything except `require_paths=` is a no-op.
26
- # Catches errors for missing constants and attempts to set them in the class_eval context.
27
- # Constants are set recursively by setting each constant to GemSpecificationContext.
28
- class Specification
29
- def initialize(*_)
30
- GemSpecificationContext.require_paths = []
31
- begin
32
- yield self
33
- rescue NameError => e
34
- __devpack_resolved_receiver(e).const_set(e.name, GemSpecificationContext)
35
- retry
36
- end
37
- end
38
-
39
- def require_paths=(paths)
40
- GemSpecificationContext.require_paths.concat(paths)
41
- end
42
-
43
- private
44
-
45
- def __devpack_resolved_receiver(error)
46
- error.receiver
47
- rescue ArgumentError
48
- GemSpecificationContext
49
- end
50
-
51
- # rubocop:disable Style/MethodMissingSuper
52
- def method_missing(method_name, *args)
53
- return self unless Kernel.respond_to?(method_name)
54
-
55
- Kernel.public_send(method_name, *args)
56
- end
57
- # rubocop:enable Style/MethodMissingSuper
58
-
59
- def respond_to_missing?(*_)
60
- true
61
- end
62
- end
63
- end
64
- end
65
- end