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 CHANGED
@@ -1,18 +1,24 @@
1
- Namespaces for ruby <hack>
2
- ==========================
1
+ Namespaces for ruby
2
+ ===================
3
3
 
4
- Brings a namespace system to ruby. By loading the .rb files into a temporary
5
- module, it is possible to avoid polluting the global namespace and select what to import
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 system, but it was adapted
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
@@ -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
- include Namespace
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
- # Loads a file according to the +namespace+ in the context of a module,
26
- # and returns that object.
27
- #
28
- # It's not a Python import, where the namespace is available. What you
29
- # get here, is a sort of Sandbox where constants defined in the file are
30
- # not polluting the global namespace but available on the returned object.
31
- # Since the returned object is a Module, it can be included in the current
32
- # module if you're also in the contect of a "Sandbox".
33
- #
34
- # #import has been chosen because the ruby namespace is already crowded
35
- # (require, load and include are already taken)
36
- #
37
- def import(namespace)
38
- namespace = namespace.to_s.gsub(/[\/\\:]+/, ':') # allow symbols
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
- # @__namespace__ is not available outside of the ruby top-level
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
- # Cache lookup
50
- ns = Namespace::cache[namespace]
51
- return ns if ns
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
- file = nil
54
- # File lookup
55
- file_path = namespace[1..-1].gsub(':', File::SEPARATOR) + '.rb'
56
- $LOAD_PATH.each do |path|
57
- path = File.join(path, file_path)
58
- if File.exists? path
59
- file = path
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
- raise LoadError, "no such file to load -- #{file_path}" unless file
51
+ # Cache
52
+ @cache[namespace] = ns
65
53
 
66
- file_content = File.read(file_path)
67
- # Pre-process __NAMESPACE__ keyword to act like __FILE__
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
- # Make sure NS is new (in the cases where the current NS requires another NS)
72
- Object.send(:remove_const, :NS) rescue nil
73
- # String is on 1 line to make sure line-errors are preserved
74
- ns = eval("module NS; @__namespace__ = #{namespace.inspect}; include Namespace::Def; #{file_content}; self; end", TOPLEVEL_BINDING, file)
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
- # Cache
77
- Namespace::cache[namespace] = ns
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
- return ns
80
- ensure
81
- Object.send(:remove_const, :NS) rescue nil
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
- end
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
- # Make #import available to all
86
- class Object; include Namespace; end
89
+ mod
90
+ end
87
91
 
88
- if __FILE__ == $0
89
- require 'irb'
90
- require 'irb/completion'
91
- IRB.start
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
@@ -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
@@ -0,0 +1,4 @@
1
+
2
+ def b; "b"; end
3
+
4
+ class B; end
@@ -0,0 +1,4 @@
1
+ # this should return module b
2
+ def return_b
3
+ import('b')
4
+ end
@@ -0,0 +1,5 @@
1
+ # Used to test name conflict
2
+
3
+ def a; "foo"; end
4
+
5
+ import :a
@@ -0,0 +1,2 @@
1
+
2
+ def dir; "dir"; end
@@ -0,0 +1,2 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'namespace'
@@ -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
- - 0
9
- version: "1.0"
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: 2010-11-20 00:00:00 +00:00
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 a hack that allows you to load specific
23
- ruby files in the context of a Module, thus avoiding global namespace
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: Bringing namespaces to Ruby
72
+ summary: Ruby namespaces experiment
69
73
  test_files: []
70
74