namespace 1.0 → 1.2
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.
- data/README.md +31 -8
- data/lib/namespace.rb +83 -73
- data/test/fixtures/a.rb +18 -0
- data/test/fixtures/b.rb +4 -0
- data/test/fixtures/dyn_import.rb +4 -0
- data/test/fixtures/name_conflict.rb +5 -0
- data/test/fixtures/sub/dir.rb +2 -0
- data/test/helper.rb +2 -0
- data/test/test.rb +78 -0
- metadata +13 -9
data/README.md
CHANGED
@@ -1,18 +1,24 @@
|
|
1
|
-
Namespaces for ruby
|
2
|
-
|
1
|
+
Namespaces for ruby
|
2
|
+
===================
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
in the current module.
|
4
|
+
It is an experiment to bring namespaces to ruby. It is not quite clear yet
|
5
|
+
why this would be useful, but it can be interesting nonetheless.
|
7
6
|
|
8
|
-
This system is inspired by the CommonJS module
|
7
|
+
This system is inspired by the CommonJS and Python module systems, adapted
|
9
8
|
for the ruby particularities.
|
10
9
|
|
10
|
+
|
11
|
+
By loading the .rb files into a temporary
|
12
|
+
module, it is possible to avoid polluting the global namespace and select what to import
|
13
|
+
in the current module.
|
14
|
+
|
11
15
|
Feature:
|
16
|
+
|
17
|
+
* Compatible with ruby 1.8.7 and 1.9.2 (and others?)
|
12
18
|
* Possible to avoid namespace collision on the module level
|
13
|
-
* Relative requires (if import is used in the top-level of the file)
|
14
19
|
|
15
20
|
Unfeatures:
|
21
|
+
|
16
22
|
* uses eval and other nasty ruby hacks
|
17
23
|
* classes and namespaces don't mix well because "class X; <<here>>; end"
|
18
24
|
is a new context that doesn't inherit from it's parent.
|
@@ -23,10 +29,27 @@ TODO
|
|
23
29
|
* Handle circular dependencies ?
|
24
30
|
* Avoid eval (but I don't see how)
|
25
31
|
|
32
|
+
Tricks
|
33
|
+
------
|
34
|
+
|
35
|
+
A module can emulate the main object behavior by extending itself. In that way,
|
36
|
+
it makes it's functions directly available to consumption.
|
37
|
+
|
38
|
+
module X
|
39
|
+
extend self
|
40
|
+
def test
|
41
|
+
"x"
|
42
|
+
end
|
43
|
+
test #=> "x"
|
44
|
+
end
|
45
|
+
|
46
|
+
TODO
|
47
|
+
|
26
48
|
Bikeshed
|
27
49
|
--------
|
28
50
|
|
29
51
|
* Should ruby "namespaced" modules have their own $LOAD_PATH and file extensions ?
|
52
|
+
*
|
30
53
|
|
31
54
|
Licence
|
32
55
|
-------
|
@@ -34,4 +57,4 @@ Licence
|
|
34
57
|
Public domain
|
35
58
|
|
36
59
|
Cheers,
|
37
|
-
Jonas
|
60
|
+
Jonas
|
data/lib/namespace.rb
CHANGED
@@ -1,92 +1,102 @@
|
|
1
1
|
# Namespaces for ruby.
|
2
2
|
#
|
3
|
-
# See Namespace#import
|
3
|
+
# See Namespace.open and Namespace#import for more details
|
4
4
|
module Namespace
|
5
5
|
@cache = {}
|
6
|
+
@load_path = $LOAD_PATH
|
6
7
|
class << self;
|
7
8
|
attr_reader :cache
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
module Def
|
12
|
-
def self.included(mod)
|
13
|
-
eigenclass = (class << mod
|
14
|
-
# Override to not show Namespace::NS for all modules
|
15
|
-
def inspect
|
16
|
-
defined?(@__namespace__) ? "<#{@__namespace__} #{(constants+instance_methods).sort.join(', ')}>" : super
|
17
|
-
end
|
18
|
-
self
|
19
|
-
end)
|
20
|
-
eigenclass.send(:include, mod, Namespace)
|
21
|
-
end
|
22
|
-
end
|
9
|
+
attr_reader :load_path
|
23
10
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
11
|
+
# Loads a file according to the +namespace+ in the context of a module,
|
12
|
+
# and returns that object.
|
13
|
+
#
|
14
|
+
# It's not a Python import, where the namespace is available. What you
|
15
|
+
# get here, is a sort of Sandbox where constants defined in the file are
|
16
|
+
# not polluting the global namespace but available on the returned object.
|
17
|
+
# Since the returned object is a Module, it can be included in the current
|
18
|
+
# module if you're also in the context of a "Sandbox".
|
19
|
+
#
|
20
|
+
# #import has been chosen because the ruby namespace is already crowded
|
21
|
+
# (require, load and include are already taken)
|
22
|
+
def open(namespace)
|
23
|
+
namespace = normalize(namespace)
|
24
|
+
|
25
|
+
# Cache lookup
|
26
|
+
ns = @cache[namespace]
|
27
|
+
return ns if ns
|
28
|
+
|
29
|
+
# File lookup
|
30
|
+
file_path = namespace.gsub(':', File::SEPARATOR) + '.rb'
|
31
|
+
file = @load_path.inject(nil) do |file, load_path|
|
32
|
+
path = File.join(load_path, file_path)
|
33
|
+
File.exists?(path) ? path : file
|
34
|
+
end
|
39
35
|
|
40
|
-
|
41
|
-
if defined?(@__namespace__) && namespace[0..0] != ":"
|
42
|
-
# expand namespace
|
43
|
-
namespace = @__namespace__.sub(/:[^:]+$/,'') + ':' + namespace
|
44
|
-
else
|
45
|
-
namespace = ':' + namespace
|
46
|
-
end
|
47
|
-
puts "ns##{namespace}"
|
36
|
+
raise ImportError, "no such file to load -- #{file_path}" unless file
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
|
38
|
+
file_content = File.read(file)
|
39
|
+
# Pre-process __NAMESPACE__ keyword to act like __FILE__
|
40
|
+
# in know... it's not exactly the same... (eg "__FILE__") but it should do the trick
|
41
|
+
file_content.gsub!('__NAMESPACE__', namespace.inspect)
|
52
42
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
break
|
61
|
-
end
|
62
|
-
end
|
43
|
+
ns = Module.new
|
44
|
+
ns.instance_variable_set("@__namespace__", namespace)
|
45
|
+
# Allow calling methods in self, like in global namespace
|
46
|
+
ns.extend(ns)
|
47
|
+
# Adds the #import methods from the DSL
|
48
|
+
ns.extend(Namespace, Namespace::Ext)
|
49
|
+
ns.module_eval(file_content, file)
|
63
50
|
|
64
|
-
|
51
|
+
# Cache
|
52
|
+
@cache[namespace] = ns
|
65
53
|
|
66
|
-
|
67
|
-
|
68
|
-
# in know... it's not exactly the same... (eg "__FILE__") but it should do the trick
|
69
|
-
file_content.gsub!('__NAMESPACE__', namespace.inspect)
|
54
|
+
return ns
|
55
|
+
end
|
70
56
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
57
|
+
# Transforms a string into a normalized namespace identifier
|
58
|
+
def normalize(namespace)
|
59
|
+
namespace.to_s.sub(/^[\/:\s]*/, '').sub(/[\/:\s]*$/, '').gsub(/[\/\\:]+/, '::')
|
60
|
+
end
|
75
61
|
|
76
|
-
#
|
77
|
-
|
62
|
+
# Returns the top name of a namespace
|
63
|
+
#
|
64
|
+
# Example: basename("some::deep::namespace") => "namespace"
|
65
|
+
def basename(namespace)
|
66
|
+
normalize(namespace).gsub(/.*::/,'')
|
67
|
+
end
|
78
68
|
|
79
|
-
|
80
|
-
|
81
|
-
|
69
|
+
# Raises a ScriptError if the module is included
|
70
|
+
def included(mod)
|
71
|
+
raise ScriptError, "Namespace is not designed to be included, only extended. See #{mod}"
|
72
|
+
end
|
73
|
+
private :included
|
82
74
|
end
|
83
|
-
|
75
|
+
|
76
|
+
|
77
|
+
# options[:as] = select the imported name
|
78
|
+
#
|
79
|
+
# THINK: since variable is already returned, is :as necessary?
|
80
|
+
def import(namespace, options={})
|
81
|
+
mod = Namespace::open(namespace)
|
82
|
+
|
83
|
+
name = options[:as] || options['as'] || Namespace.basename(namespace)
|
84
|
+
raise ConflictError, "name `#{name}` is already taken" if method_defined?(name)
|
85
|
+
instance_variable_set("@#{name}", mod)
|
86
|
+
attr_reader name
|
87
|
+
private name
|
84
88
|
|
85
|
-
|
86
|
-
|
89
|
+
mod
|
90
|
+
end
|
87
91
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
+
# Used to override #inspect and #to_s of a namespace
|
93
|
+
module Ext
|
94
|
+
attr_reader :__namespace__
|
95
|
+
alias inspect __namespace__
|
96
|
+
alias to_s __namespace__
|
97
|
+
private :__namespace__
|
98
|
+
end
|
99
|
+
|
100
|
+
class ImportError < ScriptError; end
|
101
|
+
class ConflictError < ScriptError; end
|
92
102
|
end
|
data/test/fixtures/a.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
def assert(cond, msg=nil)
|
2
|
+
raise AssertionError, (msg || "Assertion Error") unless cond
|
3
|
+
end
|
4
|
+
|
5
|
+
def assert_equal(obj1, obj2)
|
6
|
+
assert(obj1 == obj2, "#{obj1} and #{obj2} should be equal")
|
7
|
+
end
|
8
|
+
|
9
|
+
import 'b'
|
10
|
+
import 'sub/dir', :as=>'foo'
|
11
|
+
|
12
|
+
assert(defined? b::B)
|
13
|
+
|
14
|
+
assert_equal("b", b::b)
|
15
|
+
assert_equal("dir", foo::dir)
|
16
|
+
|
17
|
+
def a; "a"; end
|
18
|
+
class A; end
|
data/test/fixtures/b.rb
ADDED
data/test/helper.rb
ADDED
data/test/test.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class TestNamespace < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Namespace.cache.clear
|
7
|
+
Namespace.load_path.replace([File.expand_path('../fixtures', __FILE__)])
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_basics
|
11
|
+
a = Namespace.open 'a'
|
12
|
+
assert(a.const_defined?(:A))
|
13
|
+
assert(a.method_defined?(:a))
|
14
|
+
|
15
|
+
assert(a::A.kind_of? Module)
|
16
|
+
assert_equal("a", a.a)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_importing_namespace
|
20
|
+
m = Module.new
|
21
|
+
m.extend Namespace
|
22
|
+
ret = m.module_eval do
|
23
|
+
import(:a).class
|
24
|
+
end
|
25
|
+
assert_equal(Module, ret)
|
26
|
+
|
27
|
+
assert_raise(ScriptError) do
|
28
|
+
m.send(:include, Namespace)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_naming
|
33
|
+
a = Namespace.open 'a'
|
34
|
+
assert_equal('a', a.to_s)
|
35
|
+
assert_equal('a', a.inspect)
|
36
|
+
|
37
|
+
# THIS test is broken. If I remember well, ruby finds the module name internally
|
38
|
+
# so it can't be overridden.
|
39
|
+
# To fix this, we would have to override the #to_s, #inspect and #name
|
40
|
+
# methods of all the exported modules and classes. Not good :-/
|
41
|
+
#assert_equal('a::A', a::A.name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_naming_conflict
|
45
|
+
assert_raises(Namespace::ConflictError) do
|
46
|
+
Namespace::open('name_conflict')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_normalization
|
51
|
+
norm_test = proc do |output, namespace|
|
52
|
+
assert_equal(output, Namespace::normalize(namespace))
|
53
|
+
end
|
54
|
+
|
55
|
+
norm_test["simple", "simple"]
|
56
|
+
norm_test["simple", :simple]
|
57
|
+
norm_test["sub::dir", "sub/dir"]
|
58
|
+
norm_test["sub::dir", "sub:dir"]
|
59
|
+
norm_test["sub::dir", "sub::dir"]
|
60
|
+
norm_test["strip", "/strip"]
|
61
|
+
norm_test["strip", "strip/"]
|
62
|
+
norm_test["strip", " ::strip/ "]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_basename
|
66
|
+
base_test = proc do |output, namespace|
|
67
|
+
assert_equal(output, Namespace::basename(namespace))
|
68
|
+
end
|
69
|
+
base_test["simple", "simple"]
|
70
|
+
base_test["dir", "sub::dir"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_dynimport
|
74
|
+
a = Namespace.open 'dyn_import'
|
75
|
+
b = Namespace.open 'b'
|
76
|
+
assert_equal(a.return_b, b)
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: namespace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 15
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 1
|
8
|
-
-
|
9
|
-
version: "1.
|
7
|
+
- 2
|
8
|
+
version: "1.2"
|
10
9
|
platform: ruby
|
11
10
|
authors:
|
12
11
|
- Jonas Pfenniger
|
@@ -14,13 +13,13 @@ autorequire:
|
|
14
13
|
bindir: bin
|
15
14
|
cert_chain: []
|
16
15
|
|
17
|
-
date:
|
16
|
+
date: 2011-01-05 00:00:00 +00:00
|
18
17
|
default_executable:
|
19
18
|
dependencies: []
|
20
19
|
|
21
20
|
description: |-
|
22
|
-
This module is
|
23
|
-
|
21
|
+
This module is an experiment to bring namespaces to ruby.
|
22
|
+
Each imported file is loaded in it's own context,thus avoiding global namespace
|
24
23
|
pollution
|
25
24
|
email: jonas@pfenniger.name
|
26
25
|
executables: []
|
@@ -32,6 +31,13 @@ extra_rdoc_files: []
|
|
32
31
|
files:
|
33
32
|
- README.md
|
34
33
|
- lib/namespace.rb
|
34
|
+
- test/fixtures/a.rb
|
35
|
+
- test/fixtures/b.rb
|
36
|
+
- test/fixtures/dyn_import.rb
|
37
|
+
- test/fixtures/name_conflict.rb
|
38
|
+
- test/fixtures/sub/dir.rb
|
39
|
+
- test/helper.rb
|
40
|
+
- test/test.rb
|
35
41
|
has_rdoc: true
|
36
42
|
homepage: https://github.com/zimbatm/namespace.rb
|
37
43
|
licenses: []
|
@@ -46,7 +52,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
52
|
requirements:
|
47
53
|
- - ">="
|
48
54
|
- !ruby/object:Gem::Version
|
49
|
-
hash: 3
|
50
55
|
segments:
|
51
56
|
- 0
|
52
57
|
version: "0"
|
@@ -55,7 +60,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
60
|
requirements:
|
56
61
|
- - ">="
|
57
62
|
- !ruby/object:Gem::Version
|
58
|
-
hash: 3
|
59
63
|
segments:
|
60
64
|
- 0
|
61
65
|
version: "0"
|
@@ -65,6 +69,6 @@ rubyforge_project:
|
|
65
69
|
rubygems_version: 1.3.7
|
66
70
|
signing_key:
|
67
71
|
specification_version: 3
|
68
|
-
summary:
|
72
|
+
summary: Ruby namespaces experiment
|
69
73
|
test_files: []
|
70
74
|
|