commonjs_modules 0.0.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 +7 -0
- data/README.md +70 -0
- data/examples/1_basic.rb +8 -0
- data/examples/2_annonymous_classes.rb +19 -0
- data/examples/3_classic_classes_module.rb +24 -0
- data/examples/4_classic_classes_export.rb +23 -0
- data/examples/5_default_import/exporting_annonymous_class.rb +13 -0
- data/examples/5_default_import/exporting_class.rb +9 -0
- data/examples/5_default_import/using_export.rb +5 -0
- data/examples/5_default_import/using_export_with_block.rb +7 -0
- data/examples/5_default_import/using_module.rb +4 -0
- data/examples/6_settings.rb +11 -0
- data/examples/7_implicit_export/export_annonymous_classs.rb +8 -0
- data/examples/7_implicit_export/export_classs.rb +5 -0
- data/examples/8_refinements/refinements.rb +7 -0
- data/examples/8_refinements/refinements_lib.rb +9 -0
- data/examples/9_modules/modules.rb +5 -0
- data/examples/9_modules/modules_lib.rb +7 -0
- data/examples/9_using_import.rb +27 -0
- data/examples/runner.rb +12 -0
- data/lib/import.rb +167 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01f2805d82cb9ecedf647b202119a9307ad8b408c693db12c15191d2a516f312
|
4
|
+
data.tar.gz: 5fbd2f54d96ddb04074caa187cb791e2bd388a8fdd3a0f39069f2ca87af20e33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 04f6920a4b215c1418b44accda06f0569fdac2dd3b78634769864155a705347814957f639f87fad36652f9fee0f33eba27a43bc48194e0dbd821a264843ff7ed
|
7
|
+
data.tar.gz: 7a982632f08a49bc2a873ab012024ba2c1039ca446143ada3f7d9ef643d835441634f4bfcd153f99123611095be62b9c74bedb18505a5808822bfb20a13223d7
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# About
|
2
|
+
|
3
|
+
[![Gem version][GV img]][Gem version]
|
4
|
+
[![Build status][BS img]][Build status]
|
5
|
+
[![Coverage status][CS img]][Coverage status]
|
6
|
+
[![CodeClimate status][CC img]][CodeClimate status]
|
7
|
+
|
8
|
+
This is experimental [CommonJS modules](http://wiki.commonjs.org/wiki/Modules) implementation in Ruby. The main difference is that in this implementation everything is local, there isn't any messing with the global namespace. It has a lot of advantages include [hot code reloading](http://romeda.org/blog/2010/01/hot-code-loading-in-nodejs.html).
|
9
|
+
|
10
|
+
# Usage
|
11
|
+
|
12
|
+
From `ruby -Ilib -S pry`:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
require 'import'
|
16
|
+
|
17
|
+
sys = import('examples/1_basic')
|
18
|
+
# => #<Imports::Export:0x00007f8dae26cd00
|
19
|
+
# @__DATA__={
|
20
|
+
# :language=>"Ruby", :VERSION_=>"0.0.1",
|
21
|
+
# :say_hello=>#<Method #say_hello>},
|
22
|
+
# @__FILE__="examples/1_basic.rb">
|
23
|
+
|
24
|
+
sys.language
|
25
|
+
# => "Ruby"
|
26
|
+
|
27
|
+
sys.say_hello
|
28
|
+
# => "Hello World!"
|
29
|
+
```
|
30
|
+
|
31
|
+
## `Kernel#import`
|
32
|
+
|
33
|
+
`Kernel#import` is a substitute for:
|
34
|
+
|
35
|
+
- `Kernel#require` when used with a path relative to `$LOAD_PATH` or
|
36
|
+
- `Kernel#require_relative` when used with a path starting with `./` or `../`.
|
37
|
+
|
38
|
+
# Discussion
|
39
|
+
|
40
|
+
### Usage of modules
|
41
|
+
|
42
|
+
This makes use of Ruby modules for namespacing obsolete. Obviously, they still have their use as mixins.
|
43
|
+
|
44
|
+
This is a great news. With one global namespace, it's necessary to go full on with the namespacing craziness having all these `LibName::SubModule::Module::ClassName` and dealing either with horrible nesting or with potential for missing module on which we want to definie a class.
|
45
|
+
|
46
|
+
Without a global namespace, everything is essentially flat. If we import a `Task`, there's no chance of colision, because we import everything manually and it's crystal clear where every single thing is coming from.
|
47
|
+
|
48
|
+
### Why bother if no one else is using it?
|
49
|
+
|
50
|
+
Even though all the gems out there are using the global namespace, it doesn't matter, it still a great way to organise your code. It plays well with the traditional approach.
|
51
|
+
|
52
|
+
### Usage of refinements
|
53
|
+
|
54
|
+
### YARD and RDoc
|
55
|
+
|
56
|
+
# TODO
|
57
|
+
|
58
|
+
- Rename examples, implicit is only `export ClassName`.
|
59
|
+
- exports.default = Class.new {}. What .name to set? The file I guess.
|
60
|
+
- What happens when include is used on a file level? I think this makes refinements obsolete as well UNLESS it's for the core classes such as String or Regexp.
|
61
|
+
|
62
|
+
[Gem version]: https://rubygems.org/gems/commonjs_modules
|
63
|
+
[Build status]: https://travis-ci.org/botanicus/commonjs_modules
|
64
|
+
[Coverage status]: https://coveralls.io/github/botanicus/commonjs_modules
|
65
|
+
[CodeClimate status]: https://codeclimate.com/github/botanicus/commonjs_modules/maintainability
|
66
|
+
|
67
|
+
[GV img]: https://badge.fury.io/rb/commonjs_modules.svg
|
68
|
+
[BS img]: https://travis-ci.org/botanicus/commonjs_modules.svg?branch=master
|
69
|
+
[CS img]: https://img.shields.io/coveralls/botanicus/commonjs_modules.svg
|
70
|
+
[CC img]: https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability
|
data/examples/1_basic.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Run with ./examples/runner.rb examples/file.rb
|
2
|
+
|
3
|
+
privateClass = Class.new do
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
exports.Task = Class.new(privateClass) do
|
10
|
+
def schedule
|
11
|
+
:from_task
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
export ScheduledTask: Class.new(exports.Task) {
|
16
|
+
def schedule
|
17
|
+
:from_scheduled_task
|
18
|
+
end
|
19
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Run with ./examples/runner.rb examples/file.rb
|
2
|
+
|
3
|
+
# Using Kernel#require, we'd import this class into the global namespace.
|
4
|
+
# Using Kernel#import, we won't, as everything is evaluated in a context
|
5
|
+
# of a name context object.
|
6
|
+
class PrivateClass
|
7
|
+
end
|
8
|
+
|
9
|
+
# Class inherits from Object, so no matter what we do, here all the puts
|
10
|
+
# and everything will be available again.
|
11
|
+
class Task < PrivateClass
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ScheduledTask < Task
|
18
|
+
end
|
19
|
+
|
20
|
+
# Here we use a different export name to verify that the class name doesn't get
|
21
|
+
# overriden (class_name.name reports Task resp. ScheduledTask rather than
|
22
|
+
# the underscored version).
|
23
|
+
exports._Task = Task
|
24
|
+
exports._ScheduledTask = ScheduledTask
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Run with ./examples/runner.rb examples/file.rb
|
2
|
+
|
3
|
+
# Using Kernel#require, we'd import this class into the global namespace.
|
4
|
+
# Using Kernel#import, we won't, as everything is evaluated in a context
|
5
|
+
# of a name context object.
|
6
|
+
class PrivateClass
|
7
|
+
end
|
8
|
+
|
9
|
+
# Class inherits from Object, so no matter what we do, here all the puts
|
10
|
+
# and everything will be available again.
|
11
|
+
class Task < PrivateClass
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ScheduledTask < Task
|
18
|
+
end
|
19
|
+
|
20
|
+
# Here we use a different export name to verify that the class name doesn't get
|
21
|
+
# overriden (class_name.name reports Task resp. ScheduledTask rather than
|
22
|
+
# the underscored version).
|
23
|
+
export _Task: Task, _ScheduledTask: ScheduledTask
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Run with ./examples/runner.rb examples/file.rb
|
2
|
+
|
3
|
+
# NOTE that do/end block cannot be used.
|
4
|
+
export default: Class.new {
|
5
|
+
# TODO: 06/06/2018 With this, exports.default shows the right thing,
|
6
|
+
# but exports.default.new still shows just an instance of an anonymous class.
|
7
|
+
def self.inspect
|
8
|
+
'Test'
|
9
|
+
end
|
10
|
+
|
11
|
+
def test
|
12
|
+
end
|
13
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Run with ./examples/runner.rb examples/file.rb
|
2
|
+
|
3
|
+
# Assigning to a constant will make the variable available within classes.
|
4
|
+
# FIXME: 06/06/2018 relative paths don't work as of now.
|
5
|
+
# Task = import('./3_classic_classes')._Task
|
6
|
+
Task = import('examples/3_classic_classes_module')._Task
|
7
|
+
Test = import('examples/5_default_import/using_export')
|
8
|
+
|
9
|
+
# Assigning to a local variable will make the variable available only within the top-level context.
|
10
|
+
# FIXME: 06/06/2018 relative paths don't work as of now.
|
11
|
+
# sys = import('./1_basic')
|
12
|
+
sys = import('examples/1_basic')
|
13
|
+
|
14
|
+
def exports.method_using_imported_library_as_a_constant
|
15
|
+
Task.new("Repair the bike")
|
16
|
+
end
|
17
|
+
|
18
|
+
def exports.method_using_imported_library_as_a_variable
|
19
|
+
sys.language
|
20
|
+
end
|
21
|
+
|
22
|
+
export test_proc: Proc.new { self }
|
23
|
+
|
24
|
+
# Here we are in Export instance, NOT in Context!
|
25
|
+
def exports.method_using_kernel_methods
|
26
|
+
self
|
27
|
+
end
|
data/examples/runner.rb
ADDED
data/lib/import.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
module Imports
|
2
|
+
def self.register
|
3
|
+
@register ||= Hash.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.resolve_path(path)
|
7
|
+
# import('./test') and import('../test') behave like require_relative.
|
8
|
+
if path.start_with?('.')
|
9
|
+
caller_file = caller_locations.first.absolute_path
|
10
|
+
|
11
|
+
unless caller_file
|
12
|
+
raise "Error when importing #{path}: caller[0] is #{caller[0]}"
|
13
|
+
end
|
14
|
+
|
15
|
+
base_dir = caller_file.split('/')[0..-2].join('/')
|
16
|
+
path = File.expand_path("#{base_dir}/#{path}")
|
17
|
+
end
|
18
|
+
|
19
|
+
if File.file?(path)
|
20
|
+
fullpath = path
|
21
|
+
elsif File.file?("#{path}.rb")
|
22
|
+
fullpath = "#{path}.rb"
|
23
|
+
else
|
24
|
+
$:.each do |directory|
|
25
|
+
choices = [File.join(directory, path), File.join(directory, "#{path}.rb")]
|
26
|
+
fullpath = choices.find { |choice| File.file?(choice) }
|
27
|
+
end
|
28
|
+
if ! defined?(fullpath) || fullpath.nil?
|
29
|
+
raise LoadError, "no such file to import -- #{path}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
File.expand_path(fullpath)
|
34
|
+
end
|
35
|
+
|
36
|
+
module DSL
|
37
|
+
private
|
38
|
+
|
39
|
+
# export default: TaskList
|
40
|
+
# export Task: Task, TL: TaskList
|
41
|
+
# export { MyClass } # export as default
|
42
|
+
def export(*args, &block)
|
43
|
+
if block && args.empty?
|
44
|
+
value = block.call
|
45
|
+
raise TypeError.new if value.nil?
|
46
|
+
args << {default: block.call}
|
47
|
+
elsif block && ! args.empty?
|
48
|
+
raise "Block #{block.inspect} provided along with #{args.inspect}, only 1 can be passed."
|
49
|
+
end
|
50
|
+
|
51
|
+
if args.length == 1 && args.first.is_a?(Hash)
|
52
|
+
hash = args.first
|
53
|
+
if hash.keys.include?(:default) && hash.keys.length == 1
|
54
|
+
# export default: MyClass
|
55
|
+
exports.default = hash[:default]
|
56
|
+
elsif hash.keys.include?(:default) && hash.keys.length > 1
|
57
|
+
# export default: MyClass, something_else: MyOtherClass
|
58
|
+
raise "Default export detected, but it wasn't the only export: #{hash.keys.inspect}"
|
59
|
+
else
|
60
|
+
# export Task: Task, TL: TaskList
|
61
|
+
hash.keys.each do |key|
|
62
|
+
exports.send(:"#{key}=", hash[key])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
else
|
66
|
+
# export MyClass
|
67
|
+
args.each do |object|
|
68
|
+
if object.nil?
|
69
|
+
raise TypeError.new("Exported object cannot be nil!")
|
70
|
+
end
|
71
|
+
|
72
|
+
exports.__DATA__[object.name.to_sym] = object
|
73
|
+
rescue NoMethodError
|
74
|
+
raise ArgumentError.new("Every object has to respond to #name. Export the module manually using exports.name = object if the object doesn't respond to #name.")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Context < BasicObject
|
81
|
+
include DSL
|
82
|
+
|
83
|
+
attr_reader :exports
|
84
|
+
def initialize(path)
|
85
|
+
@exports = Export.new(path)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.const_missing(name)
|
89
|
+
::Object.const_get(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
KERNEL_METHODS_DELEGATED = [:import, :require, :raise, :puts, :p]
|
93
|
+
|
94
|
+
def method_missing(name, *args, &block)
|
95
|
+
super unless KERNEL_METHODS_DELEGATED.include? name
|
96
|
+
::Kernel.send(name, *args, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def respond_to_missing?(name, include_private = false)
|
100
|
+
KERNEL_METHODS_DELEGATED.include?(name) or super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Only def exports.something; end is evaluated in the context of Export.
|
105
|
+
# Hence these are not recommented for bigger things, but it works fine
|
106
|
+
# for a collection of few methods that don't need a separate class.
|
107
|
+
class Export
|
108
|
+
attr_reader :__FILE__, :__DATA__
|
109
|
+
def initialize(path)
|
110
|
+
@__FILE__, @__DATA__ = path, ::Hash.new
|
111
|
+
end
|
112
|
+
|
113
|
+
# Register methods.
|
114
|
+
def singleton_method_added(method)
|
115
|
+
@__DATA__[method] = self.method(method)
|
116
|
+
|
117
|
+
@__DATA__[method].define_singleton_method(:inspect) do
|
118
|
+
"#<#{self.class} ##{method}>"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Register variables and constants.
|
123
|
+
def method_missing(method, *args, &block)
|
124
|
+
if method.to_s.match(/=$/) && args.length == 1 && block.nil?
|
125
|
+
object_name = method.to_s[0..-2].to_sym
|
126
|
+
object = args.first
|
127
|
+
|
128
|
+
@__DATA__[object_name] = object
|
129
|
+
|
130
|
+
if object.is_a?(Class) && object.name.nil?
|
131
|
+
object.define_singleton_method(:name) { object_name.to_s }
|
132
|
+
object.define_singleton_method(:inspect) { object_name.to_s }
|
133
|
+
end
|
134
|
+
elsif @__DATA__.has_key?(method)
|
135
|
+
@__DATA__[method]
|
136
|
+
else
|
137
|
+
super(method, *args, &block)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def respond_to_missing?(method, include_private = false)
|
142
|
+
(method.to_s.match(/=$/) && args.length == 1 && block.nil?) || @__DATA__.has_key?(method)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module Kernel
|
148
|
+
def import(path)
|
149
|
+
absolute_path = Imports.resolve_path(path)
|
150
|
+
|
151
|
+
object = Imports.register[absolute_path] ||= begin
|
152
|
+
code = File.read(absolute_path)
|
153
|
+
object = Imports::Context.new(path)
|
154
|
+
object.instance_eval(code, path)
|
155
|
+
object
|
156
|
+
end
|
157
|
+
|
158
|
+
keys = object.exports.__DATA__.keys
|
159
|
+
if keys.include?(:default) && keys.length == 1
|
160
|
+
return object.exports.default
|
161
|
+
elsif keys.include?(:default) && keys.length > 1
|
162
|
+
raise "Default export detected, but it wasn't the only export: #{keys.inspect}"
|
163
|
+
else
|
164
|
+
return object.exports
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: commonjs_modules
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James C Russell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-06 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "."
|
14
|
+
email: james@101ideas.cz
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- examples/1_basic.rb
|
21
|
+
- examples/2_annonymous_classes.rb
|
22
|
+
- examples/3_classic_classes_module.rb
|
23
|
+
- examples/4_classic_classes_export.rb
|
24
|
+
- examples/5_default_import/exporting_annonymous_class.rb
|
25
|
+
- examples/5_default_import/exporting_class.rb
|
26
|
+
- examples/5_default_import/using_export.rb
|
27
|
+
- examples/5_default_import/using_export_with_block.rb
|
28
|
+
- examples/5_default_import/using_module.rb
|
29
|
+
- examples/6_settings.rb
|
30
|
+
- examples/7_implicit_export/export_annonymous_classs.rb
|
31
|
+
- examples/7_implicit_export/export_classs.rb
|
32
|
+
- examples/8_refinements/refinements.rb
|
33
|
+
- examples/8_refinements/refinements_lib.rb
|
34
|
+
- examples/9_modules/modules.rb
|
35
|
+
- examples/9_modules/modules_lib.rb
|
36
|
+
- examples/9_using_import.rb
|
37
|
+
- examples/runner.rb
|
38
|
+
- lib/import.rb
|
39
|
+
homepage: http://github.com/botanicus/commonjs_modules
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.7.6
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: ''
|
63
|
+
test_files: []
|