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 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
@@ -0,0 +1,16 @@
1
+ explicit export is vital so you don't throw out all of your imports as exports
2
+
3
+ the idea doesn't really work without this
4
+
5
+ ```ruby
6
+ export def x
7
+
8
+ end
9
+
10
+ export class X
11
+
12
+ end
13
+
14
+ export 3, as: Something
15
+ ```
16
+
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 ;)
@@ -0,0 +1,5 @@
1
+ export def assert_equal(expected, actual)
2
+ if expected != actual
3
+ raise "assertion failed: #{expected} != #{actual}"
4
+ end
5
+ end
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
@@ -0,0 +1,3 @@
1
+ module Rixby
2
+ VERSION = '1.0.0'
3
+ 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: []