commonjs_modules 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ exports.language = 'Ruby'
4
+ exports.VERSION_ = '0.0.1'
5
+
6
+ def exports.say_hello
7
+ return "Hello World!"
8
+ end
@@ -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,9 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ export Test: class Test
4
+ def test
5
+ end
6
+
7
+ self # Without this, it won't return the right thing.
8
+ end
9
+
@@ -0,0 +1,5 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ class Test; end
4
+
5
+ export default: Test
@@ -0,0 +1,7 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ class Test
4
+ end
5
+
6
+ # Obviously this can be used with an annonymous class or class N; self; end as well.
7
+ export { Test }
@@ -0,0 +1,4 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ class Test; end
4
+ exports.Test = Test
@@ -0,0 +1,11 @@
1
+ require 'ostruct'
2
+
3
+ # Usage:
4
+ # settings = import('examples/6_settings')
5
+ # settings.algolia.app_id
6
+
7
+ # Remember, nil cannot be exported.
8
+ exports.algolia = OpenStruct.new(
9
+ app_id: 'ABCD',
10
+ api_key: '12CD'
11
+ )
@@ -0,0 +1,8 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ # TODO: 06/06/2018 Define inspect based for this.
4
+ export Class.new {
5
+ def self.name
6
+ 'Test'
7
+ end
8
+ }
@@ -0,0 +1,5 @@
1
+ # Run with ./examples/runner.rb examples/file.rb
2
+
3
+ export class Test
4
+ self # Required.
5
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby -Ilib -rimport
2
+
3
+ # TODO: 06/06/2018 Fix this.
4
+ using import('examples/8_refinements/modules_lib')
5
+
6
+ puts "Test."
7
+
@@ -0,0 +1,9 @@
1
+ export do
2
+ Module.new do
3
+ refine Kernel do
4
+ def puts(message)
5
+ super("<red>#{message}</red>")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby -Ilib -rimport
2
+
3
+ include import('examples/8_refinements/modules_lib')
4
+
5
+ puts "Test."
@@ -0,0 +1,7 @@
1
+ export do
2
+ Module.new do
3
+ def puts(message)
4
+ super("<red>#{message}</red>")
5
+ end
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby -Ilib
2
+
3
+ require 'import'
4
+ require 'pry'
5
+
6
+ path = ARGV.shift
7
+ unless path && File.exist?(path)
8
+ abort "Usage: #{$0} examples/1_basic.rb"
9
+ end
10
+
11
+ exports = import(path)
12
+ binding.pry
@@ -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: []