mirror of
https://github.com/lloeki/package-ruby.git
synced 2025-12-06 01:54:41 +01:00
preview
This commit is contained in:
commit
32b99b62bf
20 changed files with 644 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/Gemfile.lock
|
||||||
|
/.yardoc
|
||||||
|
/doc
|
||||||
|
/coverage
|
||||||
|
*.gem
|
||||||
2
.rspec
Normal file
2
.rspec
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
--color
|
||||||
|
--require spec_helper
|
||||||
3
.rubocop.yml
Normal file
3
.rubocop.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
TrivialAccessors:
|
||||||
|
ExactNameMatch: true
|
||||||
|
AllowPredicates: true
|
||||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
language: ruby
|
||||||
|
ruby:
|
||||||
|
- 1.9.3
|
||||||
|
- 2.0.0
|
||||||
|
- 2.1.3
|
||||||
|
script:
|
||||||
|
rake spec rubocop
|
||||||
3
Gemfile
Normal file
3
Gemfile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2014 Loic Nageleisen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
211
README.md
Normal file
211
README.md
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
# Pak - A namespaced package system for Ruby
|
||||||
|
|
||||||
|
## Why, oh why?
|
||||||
|
|
||||||
|
If you are:
|
||||||
|
|
||||||
|
- sick of side effects when requiring?
|
||||||
|
- think writing namespaces when you're already nested deep in directories is
|
||||||
|
not quite DRY?
|
||||||
|
- want easier reloading/dependency tracking/source mapping?
|
||||||
|
|
||||||
|
You've come to the right place.
|
||||||
|
|
||||||
|
## A quick note about require, monkeys, and use cases
|
||||||
|
|
||||||
|
I have no issue *per se* with monkey-patching, require being side-effectful WRT
|
||||||
|
namespacing by design, and classes (modules, really) being open. The trouble
|
||||||
|
is, most of the time it's not what we need, and more often than not it gets in
|
||||||
|
the way in terrible ways. Hence, this, taking inspiration from Python and Go.
|
||||||
|
|
||||||
|
## An example, for good measure
|
||||||
|
|
||||||
|
Take a module named `foo.rb`:
|
||||||
|
|
||||||
|
````ruby
|
||||||
|
HELLO = 'FOO'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
'i am foo'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Importing `foo` will make it accessible to `self`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import('foo', as: :method)
|
||||||
|
p foo #=> #<Package foo>
|
||||||
|
p foo.name #=> "foo"
|
||||||
|
p foo.hello #=> "i am foo"
|
||||||
|
p foo::HELLO #=> "FOO"
|
||||||
|
```
|
||||||
|
|
||||||
|
To avoid pollution (especially with `main` being a special case of `Object`),
|
||||||
|
import defines a `.foo` method on the caller's `class << self`, so that `foo`
|
||||||
|
may not become accessible from too much unexpected places.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class ABC; end
|
||||||
|
p ABC.new.foo # NoMethodError
|
||||||
|
```
|
||||||
|
|
||||||
|
You can import under another name to prevent conflict:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import('foo', as: :fee)
|
||||||
|
p fee #=> #<Package foo>
|
||||||
|
p fee.name #=> "foo"
|
||||||
|
p fee.hello #=> "i am foo"
|
||||||
|
p fee::HELLO #=> "FOO"
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can import as a const:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import('foo', to: :const)
|
||||||
|
p Foo #=> #<Package foo>
|
||||||
|
p Foo.name #=> "foo"
|
||||||
|
p Foo.hello #=> "i am foo"
|
||||||
|
p Foo::HELLO #=> "FOO"
|
||||||
|
```
|
||||||
|
|
||||||
|
And if that doesn't suit you, you can import as a local:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
qux = import('foo', to: nil)
|
||||||
|
p qux
|
||||||
|
puts qux.name
|
||||||
|
puts qux.hello
|
||||||
|
puts qux::HELLO
|
||||||
|
```
|
||||||
|
|
||||||
|
From a package `bar.rb`, you can import `foo`...:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import 'foo'
|
||||||
|
|
||||||
|
HELLO = 'BAR'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
"i am bar and I can access #{foo.name}"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
...all without polluting anyone:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import('bar')
|
||||||
|
p bar
|
||||||
|
p bar.name
|
||||||
|
p bar.hello
|
||||||
|
p foo # NameError
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `foo` as used by `bar` is visible to the world:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
p bar.foo
|
||||||
|
p bar.foo.name
|
||||||
|
```
|
||||||
|
|
||||||
|
Packages can be nested. Here's a surprising `foo/baz.rb` file:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
HELLO = 'BAZ'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
'i am baz'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
You can guess how to use it:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
import('foo/baz')
|
||||||
|
p baz # #<Package foo/baz>
|
||||||
|
p baz.name
|
||||||
|
p baz.hello
|
||||||
|
p foo # NameError
|
||||||
|
```
|
||||||
|
|
||||||
|
Importing a package will load the package only once, as future import calls
|
||||||
|
will reuse the cached version. Loaded packages can be listed and manipulated,
|
||||||
|
allowing a reload for future instances.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
foo.object_id #=> 70151878063900
|
||||||
|
p Package.loaded #=> {"foo.rb"=>#<Package foo>, ...}
|
||||||
|
# old_foo = Package.delete("foo")
|
||||||
|
old_foo = Package.loaded.delete("foo.rb")
|
||||||
|
import 'foo'
|
||||||
|
foo.object_id #=> 70151879713940
|
||||||
|
```
|
||||||
|
|
||||||
|
`foo` in `bar` will be reloaded once bar itself is reloaded. The logic is that
|
||||||
|
while you *may* want new code to be reloaded by old code sometimes, you'd
|
||||||
|
rather not have old code call new code in an incompatible manner. So, to
|
||||||
|
minimize surprise, global (i.e unscoped const) reload is declared a bad thing
|
||||||
|
and module scoped reload is favored.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
bar.foo.object_id == old_foo.object_id #=> true
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependency tracking becomes easy, and reloading a whole graph just as well:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
bar.dependencies #=> 'foo'
|
||||||
|
bar = bar.reload! # evicts dependencies recursively and reimports bar
|
||||||
|
|
||||||
|
## Wishlist: setting locals directly
|
||||||
|
|
||||||
|
I hoped to be able to have an implicit syntax similar to Python or Go allowing
|
||||||
|
for real local variable setting, but this seems unlikely given how local
|
||||||
|
variables are set up by the Ruby VM: although you can get the
|
||||||
|
`binding.of_caller`, modifying the binding doesn't *create* the variable as a
|
||||||
|
caller's local. As such, you can guess how being forced to do `foo = nil;
|
||||||
|
import 'foo'` is not really useful (and entirely arcane) when compared to `foo
|
||||||
|
= import 'foo'`.
|
||||||
|
|
||||||
|
See how [`bind_local_variable_set`][1] works on a binding, defining new vars
|
||||||
|
dynamically inside the binding but outside the local table, resulting in the
|
||||||
|
following behavior (excerpted form Ruby's own inline doc):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def foo
|
||||||
|
a = 1
|
||||||
|
b = binding
|
||||||
|
b.local_variable_set(:a, 2) # set existing local variable `a'
|
||||||
|
b.local_variable_set(:b, 3) # create new local variable `b'
|
||||||
|
# `b' exists only in binding.
|
||||||
|
b.local_variable_get(:a) #=> 2
|
||||||
|
b.local_variable_get(:b) #=> 3
|
||||||
|
p a #=> 2
|
||||||
|
p b #=> NameError
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
A good way to look at the local table is to use RubyVM ISeq features:
|
||||||
|
|
||||||
|
> puts RubyVM::InstructionSequence.disasm(-> { foo=42 })
|
||||||
|
== disasm: <RubyVM::InstructionSequence:block in __pry__@(pry)>=========
|
||||||
|
== catch table
|
||||||
|
| catch type: redo st: 0002 ed: 0009 sp: 0000 cont: 0002
|
||||||
|
| catch type: next st: 0002 ed: 0009 sp: 0000 cont: 0009
|
||||||
|
|------------------------------------------------------------------------
|
||||||
|
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
|
||||||
|
[ 2] foo
|
||||||
|
0000 trace 256 ( 13)
|
||||||
|
0002 trace 1
|
||||||
|
0004 putobject 42
|
||||||
|
0006 dup
|
||||||
|
0007 setlocal_OP__WC__0 2
|
||||||
|
0009 trace 512
|
||||||
|
0011 leave
|
||||||
|
=> nil
|
||||||
|
|
||||||
|
That's because, IIUC, the local variables table is basically fixed and cannot
|
||||||
|
be changed, so the binding works around that with dynavars, but it doesn't
|
||||||
|
bubble up to the function local table.
|
||||||
|
|
||||||
|
[1]: https://github.com/ruby/ruby/blob/6b6ba319ea4a5afe445bad918a214b7d5691fd7c/proc.c#L473
|
||||||
21
Rakefile
Normal file
21
Rakefile
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
require 'yard'
|
||||||
|
YARD::Rake::YardocTask.new do |t|
|
||||||
|
t.files = ['lib/**/*.rb']
|
||||||
|
t.options = %w(- README.md LICENSE CONTRIBUTING)
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'rspec/core'
|
||||||
|
require 'rspec/core/rake_task'
|
||||||
|
desc 'Run all specs in spec directory (excluding plugin specs)'
|
||||||
|
RSpec::Core::RakeTask.new
|
||||||
|
|
||||||
|
require 'rubocop/rake_task'
|
||||||
|
RuboCop::RakeTask.new
|
||||||
|
|
||||||
|
desc 'Run RSpec with code coverage'
|
||||||
|
task :coverage do
|
||||||
|
ENV['COVERAGE'] = 'yes'
|
||||||
|
Rake::Task['spec'].execute
|
||||||
|
end
|
||||||
|
|
||||||
|
task default: :spec
|
||||||
151
lib/package.rb
Normal file
151
lib/package.rb
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
require 'binding_of_caller'
|
||||||
|
|
||||||
|
#
|
||||||
|
class Package < Module
|
||||||
|
#
|
||||||
|
module KernelMethods
|
||||||
|
NS_SEP = '/'
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# Return the package as a value
|
||||||
|
def import(caller_binding, namespace, as: nil, to: :method)
|
||||||
|
to ||= :value
|
||||||
|
|
||||||
|
send("import_to_#{to}", caller_binding, namespace, as: as)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return the package as a value
|
||||||
|
def import_to_value(_binding, namespace, as: nil)
|
||||||
|
Package.new(namespace)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Assign the package to a local variable in the caller's binding
|
||||||
|
# Return the package as a value
|
||||||
|
# /!\ Experimental
|
||||||
|
def import_to_local(caller_binding, namespace, as: nil)
|
||||||
|
sym = (as || ns_last(namespace)).to_sym
|
||||||
|
setter = caller_binding.eval(<<-RUBY)
|
||||||
|
#{sym} = nil
|
||||||
|
-> (v) { #{sym} = v }
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
setter.call(Package.new(namespace))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define a method in the caller's context that hands out the package
|
||||||
|
# Return the package as a value
|
||||||
|
def import_to_method(caller_binding, namespace, as: nil)
|
||||||
|
sym = (as || ns_last(namespace)).to_sym
|
||||||
|
clr = caller_binding.eval('self')
|
||||||
|
setter = clr.instance_eval(<<-RUBY)
|
||||||
|
-> (v) { define_singleton_method(:#{sym}) { v }; v }
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
setter.call(Package.new(namespace))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set a const to the package in the caller's context
|
||||||
|
# Return the package as a value
|
||||||
|
def import_to_const(caller_binding, namespace, as: nil)
|
||||||
|
sym = (as || ns_classify(ns_last(namespace)).to_sym)
|
||||||
|
clr = caller_binding.eval('self')
|
||||||
|
target = clr.respond_to?(:const_set) ? clr : clr.class
|
||||||
|
setter = target.instance_eval(<<-RUBY)
|
||||||
|
-> (v) { const_set(:#{sym}, v) }
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
setter.call(Package.new(namespace))
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_from_filename(ns)
|
||||||
|
ns.gsub('/', NS_SEP).gsub(/\.rb$/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_to_filename(ns)
|
||||||
|
ns.gsub(NS_SEP, '/') + '.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_last(ns)
|
||||||
|
ns.split(NS_SEP).last
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_classify(namespace)
|
||||||
|
namespace.split(NS_SEP).map! do |v|
|
||||||
|
v.split('_').map!(&:capitalize).join('')
|
||||||
|
end.join('::')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def new(file)
|
||||||
|
file = KernelMethods.ns_to_filename(file)
|
||||||
|
self[file] ||= super(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
loaded[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(key, value)
|
||||||
|
loaded[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def loaded
|
||||||
|
@store ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(ns)
|
||||||
|
@store.delete(ns_to_filename(ns))
|
||||||
|
end
|
||||||
|
|
||||||
|
def reload!
|
||||||
|
@store = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def path
|
||||||
|
$LOAD_PATH
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(file)
|
||||||
|
@source_file = file
|
||||||
|
@name = KernelMethods.ns_from_filename(file)
|
||||||
|
load_in_module(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :name
|
||||||
|
alias_method :to_s, :name
|
||||||
|
|
||||||
|
def load(file, wrap = false)
|
||||||
|
wrap ? super : load_in_module(File.join(dir, file))
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
format("#<#{self.class.name}(#{name}):0x%014x>", object_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_in_module(file)
|
||||||
|
module_eval(IO.read(file),
|
||||||
|
File.expand_path(file))
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_added(name)
|
||||||
|
module_function(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
module Kernel
|
||||||
|
def import(*args)
|
||||||
|
caller_binding = args.last.is_a?(Binding) ? args.pop : binding.of_caller(1)
|
||||||
|
args.unshift(caller_binding)
|
||||||
|
Package::KernelMethods.import(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
1
lib/pak.rb
Normal file
1
lib/pak.rb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
require_relative 'package'
|
||||||
20
pak.gemspec
Normal file
20
pak.gemspec
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'pak'
|
||||||
|
s.version = '1.0.0'
|
||||||
|
s.licenses = ['MIT']
|
||||||
|
s.summary = 'Packaged namespacing for Ruby'
|
||||||
|
s.description = 'Implicit namespacing and package definition, '\
|
||||||
|
'inspired by Python, Go, CommonJS.'
|
||||||
|
s.authors = ['Loic Nageleisen']
|
||||||
|
s.email = 'loic.nageleisen@gmail.com'
|
||||||
|
s.files = Dir['lib/*']
|
||||||
|
|
||||||
|
s.add_development_dependency 'rake', '~> 10.3'
|
||||||
|
s.add_development_dependency 'rspec', '~> 3.0'
|
||||||
|
s.add_development_dependency 'simplecov'
|
||||||
|
s.add_development_dependency 'rubocop'
|
||||||
|
s.add_development_dependency 'yard', '~> 0.8.7'
|
||||||
|
s.add_development_dependency 'binding_of_caller'
|
||||||
|
|
||||||
|
s.add_development_dependency 'pry'
|
||||||
|
end
|
||||||
0
spec/fixtures/empty_package.rb
vendored
Normal file
0
spec/fixtures/empty_package.rb
vendored
Normal file
68
spec/package_spec.rb
Normal file
68
spec/package_spec.rb
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
require 'package'
|
||||||
|
|
||||||
|
RSpec.describe Package do
|
||||||
|
context 'instance' do
|
||||||
|
let(:package) { Package.new('spec/fixtures/empty_package') }
|
||||||
|
|
||||||
|
it { expect(package).to be_a Module }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe 'Kernel#import' do
|
||||||
|
before(:each) { Package.reload! }
|
||||||
|
|
||||||
|
[Module, Object].each do |ctx|
|
||||||
|
let(:package_name) { 'spec/fixtures/empty_package' }
|
||||||
|
let(:target) { nil }
|
||||||
|
let(:pak) do
|
||||||
|
t = target
|
||||||
|
n = package_name
|
||||||
|
context.instance_eval { import(n, to: t) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "called inside #{ctx.name}.new" do
|
||||||
|
let(:context) { ctx.new }
|
||||||
|
|
||||||
|
it 'should import a package as a value' do
|
||||||
|
expect(pak).to be_a Package
|
||||||
|
expect(context).not_to respond_to :empty_package
|
||||||
|
expect do
|
||||||
|
context.instance_eval { EmptyPackage }
|
||||||
|
end.to raise_error NameError
|
||||||
|
expect do
|
||||||
|
context.instance_eval { empty_package }
|
||||||
|
end.to raise_error NameError
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should import a package as a method' do
|
||||||
|
pak = nil
|
||||||
|
context = Module.new do
|
||||||
|
pak = import('spec/fixtures/empty_package', to: :method)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(context).to respond_to :empty_package
|
||||||
|
expect(context.empty_package).to eq pak
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should import a package as a const' do
|
||||||
|
pak = nil
|
||||||
|
context = Module.new do
|
||||||
|
pak = import('spec/fixtures/empty_package', to: :const)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(context).to have_constant :EmptyPackage
|
||||||
|
expect(context::EmptyPackage).to eq pak
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should import a package as a local' do
|
||||||
|
pending 'not ready yet'
|
||||||
|
|
||||||
|
context = Module.new do
|
||||||
|
import('spec/fixtures/empty_package', to: :local)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(context.instance_eval { empty_package }).to be_a Package
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
26
spec/spec_helper.rb
Normal file
26
spec/spec_helper.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
if ENV['COVERAGE'] == 'yes'
|
||||||
|
require 'simplecov'
|
||||||
|
SimpleCov.start
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'support/matchers'
|
||||||
|
require 'pry'
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.expect_with :rspec do |expectations|
|
||||||
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||||
|
end
|
||||||
|
|
||||||
|
config.mock_with :rspec do |mocks|
|
||||||
|
mocks.verify_partial_doubles = true
|
||||||
|
end
|
||||||
|
|
||||||
|
config.disable_monkey_patching!
|
||||||
|
config.warnings = true
|
||||||
|
|
||||||
|
config.default_formatter = 'doc' if config.files_to_run.one?
|
||||||
|
|
||||||
|
config.order = :random
|
||||||
|
|
||||||
|
Kernel.srand config.seed
|
||||||
|
end
|
||||||
5
spec/support/matchers.rb
Normal file
5
spec/support/matchers.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
RSpec::Matchers.define :have_constant do |const|
|
||||||
|
match do |owner|
|
||||||
|
owner.const_defined?(const)
|
||||||
|
end
|
||||||
|
end
|
||||||
47
test.rb
Normal file
47
test.rb
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
$LOAD_PATH.push File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
|
||||||
|
|
||||||
|
require 'pry'
|
||||||
|
require 'binding_of_caller'
|
||||||
|
require 'package'
|
||||||
|
|
||||||
|
|
||||||
|
if __FILE__ == $PROGRAM_NAME
|
||||||
|
Dir.chdir('test')
|
||||||
|
|
||||||
|
import('foo')
|
||||||
|
p foo
|
||||||
|
puts foo.name
|
||||||
|
puts foo.hello
|
||||||
|
puts foo::HELLO
|
||||||
|
|
||||||
|
import('foo', as: :fee)
|
||||||
|
p fee
|
||||||
|
puts fee.name
|
||||||
|
puts fee.hello
|
||||||
|
puts fee::HELLO
|
||||||
|
|
||||||
|
import('foo', to: :const)
|
||||||
|
p Foo
|
||||||
|
puts Foo.name
|
||||||
|
puts Foo.hello
|
||||||
|
puts Foo::HELLO
|
||||||
|
|
||||||
|
import('bar')
|
||||||
|
p bar
|
||||||
|
p bar.name
|
||||||
|
p bar.hello
|
||||||
|
p bar.foo
|
||||||
|
p bar.foo.name
|
||||||
|
|
||||||
|
import('foo/baz', as: 'baz2')
|
||||||
|
p baz2.name
|
||||||
|
|
||||||
|
import('foo/baz')
|
||||||
|
p baz.name
|
||||||
|
|
||||||
|
p Package.loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
class ABC; end
|
||||||
|
|
||||||
|
p ABC.new.foo
|
||||||
7
test/bar.rb
Normal file
7
test/bar.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import 'foo'
|
||||||
|
|
||||||
|
HELLO = 'BAR'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
"i am bar and I can access #{foo.name}"
|
||||||
|
end
|
||||||
5
test/foo.rb
Normal file
5
test/foo.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
HELLO = 'FOO'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
'i am foo'
|
||||||
|
end
|
||||||
5
test/foo/baz.rb
Normal file
5
test/foo/baz.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
HELLO = 'BAZ'
|
||||||
|
|
||||||
|
def hello
|
||||||
|
'i am baz'
|
||||||
|
end
|
||||||
38
test2.rb
Normal file
38
test2.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
require 'pp'
|
||||||
|
#require 'pry'
|
||||||
|
require 'binding_of_caller'
|
||||||
|
|
||||||
|
def cards
|
||||||
|
ace = 'of hearts'
|
||||||
|
queen = 'of diamonds'
|
||||||
|
binding
|
||||||
|
end
|
||||||
|
|
||||||
|
c = cards
|
||||||
|
|
||||||
|
pp c.eval('ace')
|
||||||
|
pp c.eval('ace = "of spades"')
|
||||||
|
pp c.eval('ace')
|
||||||
|
pp c.eval('foo = 42')
|
||||||
|
pp c.eval('foo')
|
||||||
|
|
||||||
|
def set_baz(b)
|
||||||
|
b.eval('baz = 44')
|
||||||
|
pp b.eval('baz')
|
||||||
|
pp binding.callers
|
||||||
|
end
|
||||||
|
|
||||||
|
def meh
|
||||||
|
foo = 42
|
||||||
|
bar = 43
|
||||||
|
#baz = nil
|
||||||
|
|
||||||
|
set_baz binding
|
||||||
|
binding
|
||||||
|
end
|
||||||
|
|
||||||
|
m = meh
|
||||||
|
|
||||||
|
pp m.eval('foo')
|
||||||
|
pp m.eval('bar')
|
||||||
|
pp m.eval('baz')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue