named_imports 0.2.0 → 0.2.1

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: cfc9a0d0b8e5f11fc766551ff061c718b590fe78c8a0d389731899953a38aadc
4
- data.tar.gz: 11e50d338348cc168e96fc314bc41981407c3483a5d78db814ed4bb61cd5543e
3
+ metadata.gz: bb15d783f22496923d8eb7770bc934347bbd84a0019fad85380e3e43a29361d7
4
+ data.tar.gz: fb90680e03f17db882f40cd38e93e3d51c729177d90e67bc7b6f5d82fd99fe8d
5
5
  SHA512:
6
- metadata.gz: 01572a85739777dca2eb49050098e9e07ca7c93f7ba7a5716442bc36690e6a6c296b26ed2a1c8b473f68c6ea4097ae67d860a9850a19dadeb58b60b61ef617a7
7
- data.tar.gz: 0074367a1fddfb4b935026e0604ca80555d1bcaf6e6cf5c574dfbf9865ccac6e6ac8462271cc27218c30955300cfb80ad4cd08705d5169c34ca43b2860edb80a
6
+ metadata.gz: 498ea8dd7a20b2e9a5070e2edfa97b31911b471fa93fe28800ad076f69b031b1132ea5bc65d12db206b52c5bb0dd1903dd41ff611fa1b637cb5aa16947bf0445
7
+ data.tar.gz: 88c609ed4e0649b998752474c78d6496978b1adc5144fe294a0ed770561f0120668977c75885fe8e605748f22a137f08b3d2541becd23e8ab2a92d1c2a9db8f1
data/lib/named_imports.rb CHANGED
@@ -1,79 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "named_imports/version"
4
+ require_relative "named_imports/error"
4
5
 
5
6
  module NamedImports
6
- class Error < StandardError; end
7
-
8
7
  class << self
9
- def from(path, constants, context = Object)
10
- path_where_import_occurs = caller(2..2).first
11
- full_path = full_path_for_import(path, path_where_import_occurs)
8
+ def from(raw_path, constant_names, context = Object)
9
+ into_path, import_line = caller_path_and_line
10
+ from_path = full_path_for_import(from_path: raw_path, into_path: into_path)
11
+ sandbox = sandbox_eval(file_path: from_path)
12
+ load_constants(constant_names, from_sandbox: sandbox, into_context: context)
13
+ end
12
14
 
13
- File.open(full_path, "r") do |file|
14
- file_content = file.read
15
- anon_mod = Module.new
15
+ def import(context = Object, &block)
16
+ into_path, import_line = caller_path_and_line
17
+ constant_names = []
16
18
 
17
- anon_mod.define_singleton_method(:from) do |path, constants|
18
- NamedImports.from(path, constants, anon_mod)
19
- end
19
+ begin
20
+ block.call
21
+ rescue => e
22
+ constant_name = constant_name_from_error(e)
20
23
 
21
- anon_mod.define_singleton_method(:import) do |&block|
22
- NamedImports.import(&block)
24
+ if constant_name.nil?
25
+ raise NamedImports::Error::ImportBlockError.new(into_path, import_line, e)
23
26
  end
24
27
 
25
- anon_mod.class_eval(file_content, full_path)
26
-
27
- constants.each do |constant_name|
28
- context.send(:remove_const, constant_name) if context.const_defined?(constant_name, false)
29
- context.const_set(constant_name, anon_mod.const_get(constant_name))
28
+ if !constant_names.include?(constant_name)
29
+ constant_names << constant_name
30
+ context.const_set(constant_name, nil)
30
31
  end
32
+
33
+ retry
31
34
  end
35
+
36
+ constant_names
32
37
  end
33
38
 
34
- def import(context = Object)
35
- constants = []
39
+ def make_named_imports_available(in_context: Object)
40
+ def_method = in_context == Object ? :define_method : :define_singleton_method
36
41
 
37
- begin
38
- yield
39
- rescue NameError => e
40
- raise e unless /uninitialized constant /.match?(e.message)
42
+ in_context.send(def_method, :from) do |path, constants|
43
+ NamedImports.from(path, constants, in_context)
44
+ end
41
45
 
42
- constant_match = e.message.match(/uninitialized constant (?:#<Module:[^>]+>::)?(\w*(?:::\w+)*)$/)
43
- constant_name = constant_match && constant_match[1]
46
+ in_context.send(def_method, :import) do |&block|
47
+ NamedImports.import(in_context, &block)
48
+ end
49
+ end
44
50
 
45
- if constant_name.nil?
46
- raise NameError, "unable to import constant: #{e.message.gsub(/uninitialized constant /, '')}"
47
- end
51
+ private
48
52
 
49
- already_there = constants.include?(constant_name)
53
+ def caller_path_and_line
54
+ caller_path_info = caller(3..3).first
55
+ caller_path_info.match(/\A(.+?):(\d+):/)[1..2]
56
+ end
50
57
 
51
- unless already_there
52
- constants << constant_name
53
- context.const_set(constant_name, nil)
54
- end
58
+ def full_path_for_import(from_path:, into_path:)
59
+ ext = %r{[^/]+\.[^/]+$}.match?(from_path) ? "" : ".rb"
60
+ from_path_with_ext = "#{from_path}#{ext}"
61
+ importer_dir = File.dirname(into_path)
62
+ File.expand_path(from_path_with_ext, importer_dir)
63
+ end
55
64
 
56
- retry
57
- end
65
+ def catchable_missing_constant_error?(error)
66
+ error.is_a?(NameError) && error.message =~ /uninitialized constant /
67
+ end
58
68
 
59
- constants
69
+ def constant_name_from_error(error)
70
+ return unless error.is_a?(NameError)
71
+ constant_matcher = /uninitialized constant (?:#<Module:[^>]+>::)?(\w*(?:::\w+)*)$/
72
+ constant_match = error.message.match(constant_matcher)
73
+ constant_match && constant_match[1]
60
74
  end
61
75
 
62
- private
76
+ def read_content(file_path:)
77
+ File.open(file_path, &:read)
78
+ end
63
79
 
64
- def full_path_for_import(imported_path, importer_path)
65
- ext = %r{[^/]+\.[^/]+$}.match?(imported_path) ? "" : ".rb"
66
- imported_path_with_ext = "#{imported_path}#{ext}"
67
- importer_dir = File.dirname(importer_path)
68
- File.expand_path(imported_path_with_ext, importer_dir)
80
+ def sandbox_eval(file_path:)
81
+ sandbox_module = Module.new
82
+ make_named_imports_available(in_context: sandbox_module)
83
+ ruby_code = read_content(file_path: file_path)
84
+ sandbox_module.class_eval(ruby_code, file_path)
85
+ sandbox_module
69
86
  end
70
- end
71
87
 
72
- Object.define_method(:from) do |path, constants|
73
- NamedImports.from(path, constants)
74
- end
88
+ def load_constants(constant_names, from_sandbox:, into_context:)
89
+ constant_names.each do |constant_name|
90
+ if into_context.const_defined?(constant_name, false)
91
+ into_context.send(:remove_const, constant_name)
92
+ end
75
93
 
76
- Object.define_method(:import) do |&block|
77
- NamedImports.import(&block)
94
+ sandboxed_constant = from_sandbox.const_get(constant_name)
95
+ into_context.const_set(constant_name, sandboxed_constant)
96
+ end
97
+ end
78
98
  end
99
+
100
+ make_named_imports_available
79
101
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NamedImports
4
+ module Error
5
+ class Base < ::StandardError
6
+ end
7
+
8
+ class ImportBlockError < NamedImports::Error::Base
9
+ def initialize(importer_file, importer_line, original_error = nil)
10
+ error_details = []
11
+ error_details << "Something went wrong when evaluating the named import at #{importer_file}:#{importer_line}."
12
+ error_details << "This is likely to be an error in your 'import' block."
13
+ if original_error
14
+ original_error_class = original_error.class.to_s
15
+ article = original_error_class[0].match(/aeiou/i) ? "an" : "a"
16
+ error_details << "The original error was #{article} #{original_error_class}: #{original_error.message}"
17
+ end
18
+
19
+ error_message = error_details.join(" ")
20
+ super(error_message)
21
+
22
+ set_backtrace(original_error.backtrace) if original_error
23
+ end
24
+ end
25
+
26
+ class ImportError < ::NameError
27
+ end
28
+
29
+ class ConstantImportError < NamedImports::Error::ImportError
30
+ def initialize(imported_name, imported_file, importer_file, importer_line)
31
+ super("Unable to import #{imported_name} from #{imported_file} (import occurs in #{importer_file}:#{importer_line})")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NamedImports
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: named_imports
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keegan Leitz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-22 00:00:00.000000000 Z
11
+ date: 2021-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,6 +156,7 @@ files:
156
156
  - bin/console
157
157
  - bin/setup
158
158
  - lib/named_imports.rb
159
+ - lib/named_imports/error.rb
159
160
  - lib/named_imports/version.rb
160
161
  - named_imports.gemspec
161
162
  homepage: https://github.com/kjleitz/named_imports