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.
@@ -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: []