rixby 1.0.0
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/.tool-versions +1 -0
- data/LICENSE +21 -0
- data/NOTES.md +16 -0
- data/README.md +125 -0
- data/example/assertion.rb +5 -0
- data/example/canvas.rb +18 -0
- data/example/canvas_test.rb +17 -0
- data/example/shapes.rb +24 -0
- data/lib/ixby.rb +1 -0
- data/lib/rixby/import_dsl.rb +44 -0
- data/lib/rixby/version.rb +3 -0
- data/lib/rixby.rb +93 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fc379f400b5e2b6213b52f64b4cc87c30138fab3f3ced3a0935b1e13a6a133ae
|
|
4
|
+
data.tar.gz: b6d14233a8f412d5a8de2bd9daa284e0a96ef22dd2c59e741909abe513bb186a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 761bea6040e72373f4a2d58591b8b4240c8678bfd1ba60cd2b7c545e268c89caefd16a25a92df2cccce002d52545d1c1f864bc0faa758ec9e9c4d6e166784f40
|
|
7
|
+
data.tar.gz: 6bddb361c92dd554440bed262f621d0fdeb1c0ccf423915dab7787cde2e484d69dd9b25e0ed2eb2b40c5b2fc54e38f99e4d866ac762b3f64d446db1f2c1aafd7
|
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 4.0.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Christiansen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/NOTES.md
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Rixby
|
|
2
|
+
|
|
3
|
+
Rixby uses Ruby 4.0's new `Ruby::Box` namespacing system to add explicit `import`/`export` syntax to Ruby, similar to ES Modules.
|
|
4
|
+
|
|
5
|
+
Export the items you want to make public, and import only the items you need.
|
|
6
|
+
This makes the source of methods or classes explicit.
|
|
7
|
+
|
|
8
|
+
Unlike `require`, it also prevents files from "leaking" into each other.
|
|
9
|
+
(That is, if `b` imports `a` and `c` imports `b`, `c` does not see `b`'s imports from `a`.)
|
|
10
|
+
|
|
11
|
+
<table>
|
|
12
|
+
<tr>
|
|
13
|
+
<td>shapes.rb</td><td>main.rb</td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<td>
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
class Rectangle export
|
|
20
|
+
# ...
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Circle export
|
|
24
|
+
# ...
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
export def unit_square = Rectangle.new(1, 1)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</td>
|
|
31
|
+
<td>
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
import { from 'shapes', :Circle, :unit_square }
|
|
35
|
+
|
|
36
|
+
p Circle.new(...)
|
|
37
|
+
p unit_square
|
|
38
|
+
# `Rectangle` is not available here
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
</td>
|
|
42
|
+
|
|
43
|
+
</tr>
|
|
44
|
+
|
|
45
|
+
> [!TIP]
|
|
46
|
+
> For a more complete example, see the `example` directory.
|
|
47
|
+
> Start from `canvas.rb`.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
> [!CAUTION]
|
|
51
|
+
> This is a proof-of-concept/experiment.
|
|
52
|
+
> **Do not use this in production!**
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Rixby requires Ruby 4.0+.
|
|
58
|
+
|
|
59
|
+
1. Install the gem: `gem install rixby` or `bundle add rixby`
|
|
60
|
+
2. Enable the experimental box feature: `export RUBY_BOX=1`
|
|
61
|
+
3. Run Ruby with `-rixby` to make `import` and `export` available everywhere:
|
|
62
|
+
* (This is an `ubygems`-style alias - it's really `-r ixby`)
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
ruby -rixby main.rb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Export classes or modules by calling `export` within their body:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class X
|
|
72
|
+
export
|
|
73
|
+
# ...
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# or, more concisely, this also works
|
|
77
|
+
class X export
|
|
78
|
+
# ...
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Export methods by prepending `export` to their definition:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
export def something
|
|
86
|
+
# ...
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
To import classes, modules, or methods, use one of these two forms of `import`:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# symbol names of each item to import ...........
|
|
94
|
+
import { from 'file', :something_to_import, :something_else_to_import }
|
|
95
|
+
|
|
96
|
+
# Or, import everything
|
|
97
|
+
import { all 'file' }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## How does it work?
|
|
101
|
+
|
|
102
|
+
`Ruby::Box` is Ruby 4.0's new namespacing feature.
|
|
103
|
+
You can execute code in a box, and it acts as an isolated namespace for that code.
|
|
104
|
+
|
|
105
|
+
Rixby works by executing each imported file in a box.
|
|
106
|
+
Rixby injects `export` into the top-level namespace of the box, and tracks calls to it using an instance variable on the box.
|
|
107
|
+
Later, `import` reads from that instance variables to extract classes/modules/methods and copy them into the current box.
|
|
108
|
+
|
|
109
|
+
The `import` syntax needs some explaining - it uses a block even though there's no obvious need to:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# This is what Rixby uses:
|
|
113
|
+
import { from 'file', :A, :B }
|
|
114
|
+
|
|
115
|
+
# Couldn't this just be...?
|
|
116
|
+
import 'file', :A, :B
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This is a sly trick to give `import` a `Binding` for the parent scope.
|
|
120
|
+
This enables Rixby to define the imported methods or constants within that parent scope.
|
|
121
|
+
The alternative would be to require `binding` to be passed explicitly to each `import` call, but this is much uglier.
|
|
122
|
+
|
|
123
|
+
## Why is it called that?
|
|
124
|
+
|
|
125
|
+
**R**u**by** **I**mport E**x**port... look, it's not the name that matters ;)
|
data/example/canvas.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { from 'shapes', :Circle, :Rectangle }
|
|
2
|
+
|
|
3
|
+
class Canvas export
|
|
4
|
+
def initialize
|
|
5
|
+
@shapes = []
|
|
6
|
+
end
|
|
7
|
+
attr_reader :shapes
|
|
8
|
+
|
|
9
|
+
def add_circle(radius)
|
|
10
|
+
shapes << Circle.new(radius)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_rectangle(width, height)
|
|
14
|
+
shapes << Rectangle.new(width, height)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def total_area = shapes.map(&:area).sum
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { from 'canvas', :Canvas }
|
|
2
|
+
import { from 'shapes', :Rectangle, :Circle }
|
|
3
|
+
import { from 'assertion', :assert_equal }
|
|
4
|
+
|
|
5
|
+
export def canvas_demo
|
|
6
|
+
canvas = Canvas.new
|
|
7
|
+
canvas.add_circle(2)
|
|
8
|
+
canvas.add_rectangle(4, 5)
|
|
9
|
+
|
|
10
|
+
assert_equal 2, canvas.shapes.length
|
|
11
|
+
assert_equal Circle.new(2), canvas.shapes[0]
|
|
12
|
+
assert_equal Rectangle.new(4, 5), canvas.shapes[1]
|
|
13
|
+
|
|
14
|
+
puts canvas.total_area
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
canvas_demo
|
data/example/shapes.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class Rectangle export
|
|
2
|
+
def initialize(width, height)
|
|
3
|
+
@width = width
|
|
4
|
+
@height = height
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
attr_reader :width, :height
|
|
8
|
+
def area = width * height
|
|
9
|
+
|
|
10
|
+
def ==(other) = other.is_a?(Rectangle) && width == other.width && height == other.height
|
|
11
|
+
def hash = [width, height].hash
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Circle export
|
|
15
|
+
def initialize(radius)
|
|
16
|
+
@radius = radius
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :radius
|
|
20
|
+
def area = radius * radius * Math::PI
|
|
21
|
+
|
|
22
|
+
def ==(other) = other.is_a?(Circle) && radius == other.radius
|
|
23
|
+
def hash = [radius].hash
|
|
24
|
+
end
|
data/lib/ixby.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'rixby'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Rixby
|
|
2
|
+
module ImportDsl
|
|
3
|
+
# Evaluation target for the `import` block
|
|
4
|
+
class ImportDslReceiver
|
|
5
|
+
def initialize
|
|
6
|
+
@filename = nil
|
|
7
|
+
@imports = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :filename, :imports
|
|
11
|
+
|
|
12
|
+
def from(filename, *imports)
|
|
13
|
+
@filename = filename
|
|
14
|
+
@imports = imports
|
|
15
|
+
|
|
16
|
+
if @imports.empty?
|
|
17
|
+
raise ArgumentError, 'import list cannot be empty. if you want to import everything, use `all` instead'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@imports.each do |import|
|
|
21
|
+
unless import.is_a?(Symbol)
|
|
22
|
+
raise TypeError, "imported names must be symbols, but got: #{import}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def all(filename)
|
|
28
|
+
@filename = filename
|
|
29
|
+
@imports = :all
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.evaluate(&blk)
|
|
34
|
+
recv = ImportDslReceiver.new
|
|
35
|
+
recv.instance_exec(&blk)
|
|
36
|
+
|
|
37
|
+
if recv.filename.nil?
|
|
38
|
+
raise RuntimeError, 'import block did not specify anything to import'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
recv
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/rixby.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require_relative 'rixby/import_dsl'
|
|
2
|
+
|
|
3
|
+
# Store boxes for imported files as an instance variable on the main box.
|
|
4
|
+
# Unlike a constant, this means all boxes are accessing the same value.
|
|
5
|
+
if Ruby::Box.current == Ruby::Box.main
|
|
6
|
+
Ruby::Box.main.instance_variable_set(:@__rixby_boxes, {})
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def import(&blk)
|
|
10
|
+
unless block_given?
|
|
11
|
+
raise 'you must pass a block to `import` describing the items to import'
|
|
12
|
+
end
|
|
13
|
+
block_binding = blk.binding
|
|
14
|
+
|
|
15
|
+
import_desc = Rixby::ImportDsl.evaluate(&blk)
|
|
16
|
+
filename = import_desc.filename
|
|
17
|
+
|
|
18
|
+
relative_to = File.dirname(block_binding.source_location[0])
|
|
19
|
+
absolute_filename = File.expand_path(filename, relative_to)
|
|
20
|
+
|
|
21
|
+
# Add `.rb` automatically if that would create a valid path
|
|
22
|
+
if !File.exist?(absolute_filename) && File.exist?(absolute_filename + '.rb')
|
|
23
|
+
absolute_filename += '.rb'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
imported_boxes = Ruby::Box.main.instance_variable_get(:@__rixby_boxes)
|
|
27
|
+
if imported_boxes.has_key?(absolute_filename)
|
|
28
|
+
box = imported_boxes[absolute_filename]
|
|
29
|
+
else
|
|
30
|
+
box = Ruby::Box.new
|
|
31
|
+
box.instance_variable_set(:@__rixby_imported, true)
|
|
32
|
+
box.require(__FILE__)
|
|
33
|
+
box.require(absolute_filename)
|
|
34
|
+
imported_boxes[absolute_filename] = box
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
exports = box.instance_variable_get(:@__rixby_exports)
|
|
38
|
+
case import_desc.imports
|
|
39
|
+
when :all
|
|
40
|
+
imports = exports.keys
|
|
41
|
+
when Array
|
|
42
|
+
imports = import_desc.imports
|
|
43
|
+
else
|
|
44
|
+
raise 'internal error: malformed import array'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
imports.each do |import|
|
|
48
|
+
export = exports[import] or raise(KeyError, "no export named `#{import}`")
|
|
49
|
+
|
|
50
|
+
case export
|
|
51
|
+
when Method
|
|
52
|
+
block_binding.receiver.define_singleton_method(import, &export)
|
|
53
|
+
when Module
|
|
54
|
+
block_binding.receiver.class.const_set(import, export)
|
|
55
|
+
else
|
|
56
|
+
raise "internal error: unsupported export type #{export}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def export(item)
|
|
62
|
+
# If this file isn't being imported, we can ignore `export`.
|
|
63
|
+
# This is relevant if you `export` from the main file, where `singleton_method` doesn't seem to be
|
|
64
|
+
# able to look up methods correctly.
|
|
65
|
+
unless Ruby::Box.current.instance_variable_get(:@__rixby_imported)
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
case item
|
|
70
|
+
when Symbol
|
|
71
|
+
key = item
|
|
72
|
+
# Try both - sometimes one works, sometimes the other, I'm not sure why.
|
|
73
|
+
value = method(item) rescue singleton_method(item)
|
|
74
|
+
when Module
|
|
75
|
+
key = item.name.split('::').last.to_sym
|
|
76
|
+
value = item
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, "unsupported item for export: #{item}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
box = Ruby::Box.current
|
|
82
|
+
unless box.instance_variable_defined?(:@__rixby_exports)
|
|
83
|
+
box.instance_variable_set(:@__rixby_exports, {})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
exports = box.instance_variable_get(:@__rixby_exports)
|
|
87
|
+
exports[key] = value
|
|
88
|
+
end
|
|
89
|
+
EXPORT_METHOD = method(:export)
|
|
90
|
+
|
|
91
|
+
Module.define_method(:export) do
|
|
92
|
+
EXPORT_METHOD.(self)
|
|
93
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rixby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Aaron Christiansen
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
email:
|
|
13
|
+
- aaronc20000@gmail.com
|
|
14
|
+
executables: []
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- ".tool-versions"
|
|
19
|
+
- LICENSE
|
|
20
|
+
- NOTES.md
|
|
21
|
+
- README.md
|
|
22
|
+
- example/assertion.rb
|
|
23
|
+
- example/canvas.rb
|
|
24
|
+
- example/canvas_test.rb
|
|
25
|
+
- example/shapes.rb
|
|
26
|
+
- lib/ixby.rb
|
|
27
|
+
- lib/rixby.rb
|
|
28
|
+
- lib/rixby/import_dsl.rb
|
|
29
|
+
- lib/rixby/version.rb
|
|
30
|
+
homepage: https://github.com/aaronc81/rixby
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
homepage_uri: https://github.com/aaronc81/rixby
|
|
35
|
+
source_code_uri: https://github.com/aaronc81/rixby
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 4.0.0
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 4.0.3
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Modular file import/export for Ruby
|
|
53
|
+
test_files: []
|