boxwerk 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +26 -3
- data/README.md +70 -226
- data/exe/boxwerk +0 -8
- data/lib/boxwerk/cli.rb +12 -44
- data/lib/boxwerk/graph.rb +8 -66
- data/lib/boxwerk/loader.rb +33 -161
- data/lib/boxwerk/package.rb +5 -14
- data/lib/boxwerk/registry.rb +1 -12
- data/lib/boxwerk/setup.rb +5 -26
- data/lib/boxwerk/version.rb +1 -1
- data/lib/boxwerk.rb +0 -5
- metadata +4 -17
- data/example/Gemfile +0 -6
- data/example/Gemfile.lock +0 -66
- data/example/README.md +0 -130
- data/example/app.rb +0 -101
- data/example/package.yml +0 -6
- data/example/packages/finance/lib/invoice.rb +0 -51
- data/example/packages/finance/lib/tax_calculator.rb +0 -26
- data/example/packages/finance/package.yml +0 -10
- data/example/packages/util/lib/calculator.rb +0 -21
- data/example/packages/util/lib/geometry.rb +0 -26
- data/example/packages/util/package.yml +0 -5
- data/sig/boxwerk.rbs +0 -4
data/example/Gemfile.lock
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: ..
|
|
3
|
-
specs:
|
|
4
|
-
boxwerk (0.1.0)
|
|
5
|
-
irb (~> 1.16)
|
|
6
|
-
|
|
7
|
-
GEM
|
|
8
|
-
remote: https://rubygems.org/
|
|
9
|
-
specs:
|
|
10
|
-
bigdecimal (4.0.1)
|
|
11
|
-
concurrent-ruby (1.3.6)
|
|
12
|
-
date (3.5.1)
|
|
13
|
-
erb (6.0.1)
|
|
14
|
-
i18n (1.14.8)
|
|
15
|
-
concurrent-ruby (~> 1.0)
|
|
16
|
-
io-console (0.8.2)
|
|
17
|
-
irb (1.16.0)
|
|
18
|
-
pp (>= 0.6.0)
|
|
19
|
-
rdoc (>= 4.0.0)
|
|
20
|
-
reline (>= 0.4.2)
|
|
21
|
-
money (7.0.0)
|
|
22
|
-
bigdecimal
|
|
23
|
-
i18n (~> 1.9)
|
|
24
|
-
pp (0.6.3)
|
|
25
|
-
prettyprint
|
|
26
|
-
prettyprint (0.2.0)
|
|
27
|
-
psych (5.3.1)
|
|
28
|
-
date
|
|
29
|
-
stringio
|
|
30
|
-
rdoc (7.0.3)
|
|
31
|
-
erb
|
|
32
|
-
psych (>= 4.0.0)
|
|
33
|
-
tsort
|
|
34
|
-
reline (0.6.3)
|
|
35
|
-
io-console (~> 0.5)
|
|
36
|
-
stringio (3.2.0)
|
|
37
|
-
tsort (0.2.0)
|
|
38
|
-
|
|
39
|
-
PLATFORMS
|
|
40
|
-
arm64-darwin-25
|
|
41
|
-
ruby
|
|
42
|
-
|
|
43
|
-
DEPENDENCIES
|
|
44
|
-
boxwerk!
|
|
45
|
-
money
|
|
46
|
-
|
|
47
|
-
CHECKSUMS
|
|
48
|
-
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
49
|
-
boxwerk (0.1.0)
|
|
50
|
-
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
51
|
-
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
52
|
-
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
|
|
53
|
-
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
|
54
|
-
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
55
|
-
irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806
|
|
56
|
-
money (7.0.0) sha256=6de776ee00aced8b9a435d3aac25ce8c600566625809b3d69bbe0c319e941dd5
|
|
57
|
-
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
|
58
|
-
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
59
|
-
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
|
|
60
|
-
rdoc (7.0.3) sha256=dfe3d0981d19b7bba71d9dbaeb57c9f4e3a7a4103162148a559c4fc687ea81f9
|
|
61
|
-
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
62
|
-
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
|
|
63
|
-
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
64
|
-
|
|
65
|
-
BUNDLED WITH
|
|
66
|
-
4.0.3
|
data/example/README.md
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# Boxwerk Example Application
|
|
2
|
-
|
|
3
|
-
This directory contains a complete example of a Boxwerk application demonstrating strict package isolation and dependency management.
|
|
4
|
-
|
|
5
|
-
## Architecture
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
example/
|
|
9
|
-
├── Gemfile # Gem dependencies (money gem)
|
|
10
|
-
├── package.yml # Root package (imports finance)
|
|
11
|
-
├── app.rb # Application entry point
|
|
12
|
-
└── packages/
|
|
13
|
-
├── finance/
|
|
14
|
-
│ ├── package.yml # Exports Invoice, TaxCalculator; imports util
|
|
15
|
-
│ └── lib/
|
|
16
|
-
│ ├── invoice.rb
|
|
17
|
-
│ └── tax_calculator.rb
|
|
18
|
-
└── util/
|
|
19
|
-
├── package.yml # Exports Calculator, Geometry
|
|
20
|
-
└── lib/
|
|
21
|
-
├── calculator.rb
|
|
22
|
-
└── geometry.rb
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Dependency Graph
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
util (isolated box)
|
|
29
|
-
└── Calculator, Geometry
|
|
30
|
-
|
|
31
|
-
finance (isolated box)
|
|
32
|
-
├── imports: util (Calculator renamed to UtilCalculator)
|
|
33
|
-
└── Invoice, TaxCalculator
|
|
34
|
-
|
|
35
|
-
root (isolated box)
|
|
36
|
-
├── imports: finance
|
|
37
|
-
└── Finance::Invoice, Finance::TaxCalculator available
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Running the Example
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
cd example
|
|
44
|
-
RUBY_BOX=1 boxwerk run app.rb
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Architecture:**
|
|
48
|
-
- Root box: Contains gems (including money gem) and Boxwerk runtime
|
|
49
|
-
- Util box: Contains Calculator and Geometry classes
|
|
50
|
-
- Finance box: Contains Invoice and TaxCalculator (imports Calculator from Util box as UtilCalculator)
|
|
51
|
-
- Root package box: Runs app.rb (imports from Finance box)
|
|
52
|
-
|
|
53
|
-
**Important:** ALL packages (including root) run in isolated boxes. The main Ruby process only contains gems and the Boxwerk runtime.
|
|
54
|
-
|
|
55
|
-
### Interactive Console
|
|
56
|
-
|
|
57
|
-
You can also start an IRB console in the root package context:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
cd example
|
|
61
|
-
RUBY_BOX=1 boxwerk console
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
This gives you an interactive session with all imports available (e.g., `Finance::Invoice`).
|
|
65
|
-
|
|
66
|
-
## What This Demonstrates
|
|
67
|
-
|
|
68
|
-
### ✓ Strict Isolation
|
|
69
|
-
- `Finance::Invoice` and `Finance::TaxCalculator` are accessible (explicit import)
|
|
70
|
-
- `Calculator` and `Geometry` are NOT accessible (transitive dependency - not imported)
|
|
71
|
-
- `UtilCalculator` is NOT accessible (only available in Finance package's box)
|
|
72
|
-
- `Invoice` at top level is NOT accessible (must use `Finance::` namespace)
|
|
73
|
-
|
|
74
|
-
### ✓ Namespace Control
|
|
75
|
-
- Finance package exports are grouped under `Finance::` module
|
|
76
|
-
- Import strategies control how dependencies are wired
|
|
77
|
-
|
|
78
|
-
### ✓ Selective Rename Strategy
|
|
79
|
-
- Finance imports `Calculator` from util and renames it to `UtilCalculator`
|
|
80
|
-
- This demonstrates the selective rename import strategy
|
|
81
|
-
|
|
82
|
-
### ✓ Transitive Dependency Blocking
|
|
83
|
-
- Root imports Finance
|
|
84
|
-
- Finance imports Util
|
|
85
|
-
- Root cannot access Util classes (no transitive access)
|
|
86
|
-
|
|
87
|
-
### ✓ Gem Access
|
|
88
|
-
- Money gem is globally accessible in all packages
|
|
89
|
-
- Gems from Gemfile are auto-required and available everywhere
|
|
90
|
-
|
|
91
|
-
### ✓ Clean API Surface
|
|
92
|
-
- Each package explicitly declares exports
|
|
93
|
-
- Consumers only see what's exported
|
|
94
|
-
- Internal implementation details remain hidden
|
|
95
|
-
|
|
96
|
-
## Package Configuration
|
|
97
|
-
|
|
98
|
-
### Root Package (`package.yml`)
|
|
99
|
-
|
|
100
|
-
```yaml
|
|
101
|
-
imports:
|
|
102
|
-
- packages/finance # Default namespace strategy
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Finance Package (`packages/finance/package.yml`)
|
|
106
|
-
|
|
107
|
-
```yaml
|
|
108
|
-
exports:
|
|
109
|
-
- Invoice
|
|
110
|
-
- TaxCalculator
|
|
111
|
-
|
|
112
|
-
imports:
|
|
113
|
-
# Import Calculator from util and rename it to UtilCalculator
|
|
114
|
-
- packages/util:
|
|
115
|
-
Calculator: UtilCalculator
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Util Package (`packages/util/package.yml`)
|
|
119
|
-
|
|
120
|
-
```yaml
|
|
121
|
-
exports:
|
|
122
|
-
- Calculator
|
|
123
|
-
- Geometry
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Requirements
|
|
127
|
-
|
|
128
|
-
- Ruby 4.0+ with `Ruby::Box` support
|
|
129
|
-
- `RUBY_BOX=1` environment variable must be set
|
|
130
|
-
- Boxwerk gem installed or loaded from `../lib`
|
data/example/app.rb
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Example Boxwerk Application
|
|
4
|
-
# Run with: RUBY_BOX=1 boxwerk app.rb
|
|
5
|
-
|
|
6
|
-
puts '=' * 70
|
|
7
|
-
puts 'Boxwerk Application'
|
|
8
|
-
puts '=' * 70
|
|
9
|
-
puts ''
|
|
10
|
-
|
|
11
|
-
puts 'Creating invoice...'
|
|
12
|
-
invoice = Finance::Invoice.new(tax_rate: 0.15)
|
|
13
|
-
invoice.add_item('Web Development', 200_000) # $2000.00 in cents
|
|
14
|
-
invoice.add_item('Design Work', 150_000) # $1500.00 in cents
|
|
15
|
-
invoice.add_item('Consulting', 80_000) # $800.00 in cents
|
|
16
|
-
|
|
17
|
-
data = invoice.to_h
|
|
18
|
-
|
|
19
|
-
puts "\nInvoice Details:"
|
|
20
|
-
data[:items].each_with_index do |item, i|
|
|
21
|
-
amount_dollars = item[:amount] / 100.0
|
|
22
|
-
puts " #{i + 1}. #{item[:description]}: $#{format('%.2f', amount_dollars)}"
|
|
23
|
-
end
|
|
24
|
-
puts ''
|
|
25
|
-
puts " Subtotal: $#{format('%.2f', data[:subtotal] / 100.0)}"
|
|
26
|
-
puts " Tax (15%): $#{format('%.2f', data[:tax] / 100.0)}"
|
|
27
|
-
puts " Total: $#{format('%.2f', data[:total] / 100.0)}"
|
|
28
|
-
puts ''
|
|
29
|
-
|
|
30
|
-
puts 'Testing isolation...'
|
|
31
|
-
# Finance::Invoice should be available
|
|
32
|
-
begin
|
|
33
|
-
test_invoice = Finance::Invoice.new
|
|
34
|
-
puts ' ✓ Finance::Invoice accessible'
|
|
35
|
-
rescue NameError => e
|
|
36
|
-
puts " ✗ Finance::Invoice not accessible: #{e.message}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Finance::TaxCalculator should be available
|
|
40
|
-
begin
|
|
41
|
-
Finance::TaxCalculator
|
|
42
|
-
puts ' ✓ Finance::TaxCalculator accessible'
|
|
43
|
-
rescue NameError => e
|
|
44
|
-
puts " ✗ Finance::TaxCalculator not accessible: #{e.message}"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# UtilCalculator should NOT be available (transitive dependency)
|
|
48
|
-
begin
|
|
49
|
-
UtilCalculator.add(1, 2)
|
|
50
|
-
puts ' ✗ ERROR: UtilCalculator leaked from transitive dependency!'
|
|
51
|
-
rescue NameError
|
|
52
|
-
puts ' ✓ UtilCalculator not accessible (correct isolation)'
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Invoice should NOT be at top level (only in Finance namespace)
|
|
56
|
-
begin
|
|
57
|
-
Invoice.new
|
|
58
|
-
puts ' ✗ ERROR: Invoice available at top level!'
|
|
59
|
-
rescue NameError
|
|
60
|
-
puts ' ✓ Invoice only accessible via Finance namespace'
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Calculator should NOT be available (transitive dependency from util package)
|
|
64
|
-
begin
|
|
65
|
-
Calculator.add(1, 2)
|
|
66
|
-
puts ' ✗ ERROR: Calculator leaked from transitive dependency!'
|
|
67
|
-
rescue NameError
|
|
68
|
-
puts ' ✓ Calculator not accessible (correct isolation)'
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Geometry should NOT be available (transitive dependency from util package)
|
|
72
|
-
begin
|
|
73
|
-
Geometry.circle_area(5)
|
|
74
|
-
puts ' ✗ ERROR: Geometry leaked from transitive dependency!'
|
|
75
|
-
rescue NameError
|
|
76
|
-
puts ' ✓ Geometry not accessible (correct isolation)'
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Money gem SHOULD be accessible (gems are global, not isolated)
|
|
80
|
-
begin
|
|
81
|
-
test_money = Money.new(100, 'USD')
|
|
82
|
-
puts ' ✓ Money gem accessible (gems are global)'
|
|
83
|
-
rescue NameError => e
|
|
84
|
-
puts " ✗ ERROR: Money gem not accessible: #{e.message}"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
puts ''
|
|
88
|
-
puts '=' * 70
|
|
89
|
-
puts '✓ Application completed successfully'
|
|
90
|
-
puts '=' * 70
|
|
91
|
-
puts ''
|
|
92
|
-
puts 'Boxwerk CLI setup process:'
|
|
93
|
-
puts ' 1. `boxwerk run app.rb` found root package.yml'
|
|
94
|
-
puts ' 2. Built dependency graph (util → finance → root)'
|
|
95
|
-
puts ' 3. Validated no circular dependencies'
|
|
96
|
-
puts ' 4. Booted packages in topological order (all in isolated boxes)'
|
|
97
|
-
puts ' 5. Executed app.rb in root package box with Finance imported'
|
|
98
|
-
puts ''
|
|
99
|
-
puts 'Key difference: ALL packages (including root) run in isolated boxes.'
|
|
100
|
-
puts 'The main Ruby process only contains gems and the Boxwerk runtime.'
|
|
101
|
-
puts ''
|
data/example/package.yml
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Invoice represents a financial invoice with line items and tax calculation
|
|
4
|
-
# Uses the Money gem for precise currency handling
|
|
5
|
-
class Invoice
|
|
6
|
-
attr_reader :items, :tax_rate
|
|
7
|
-
|
|
8
|
-
def initialize(tax_rate: TaxCalculator::STANDARD_RATE)
|
|
9
|
-
@items = []
|
|
10
|
-
@tax_rate = tax_rate
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def add_item(description, amount_cents)
|
|
14
|
-
@items << {
|
|
15
|
-
description: description,
|
|
16
|
-
amount: Money.new(amount_cents, 'USD'),
|
|
17
|
-
}
|
|
18
|
-
self
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def subtotal
|
|
22
|
-
@items.reduce(Money.new(0, 'USD')) { |sum, item| sum + item[:amount] }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def tax
|
|
26
|
-
(subtotal * tax_rate).round
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def total
|
|
30
|
-
subtotal + tax
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def to_h
|
|
34
|
-
{
|
|
35
|
-
items:
|
|
36
|
-
@items.map do |item|
|
|
37
|
-
{ description: item[:description], amount: item[:amount].cents }
|
|
38
|
-
end,
|
|
39
|
-
subtotal: subtotal.cents,
|
|
40
|
-
tax_rate: tax_rate,
|
|
41
|
-
tax: tax.cents,
|
|
42
|
-
total: total.cents,
|
|
43
|
-
}
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def self.quick_invoice(amount_cents, tax_rate: TaxCalculator::STANDARD_RATE)
|
|
47
|
-
invoice = new(tax_rate: tax_rate)
|
|
48
|
-
invoice.add_item('Service', amount_cents)
|
|
49
|
-
invoice
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# TaxCalculator provides tax calculation utilities
|
|
4
|
-
# Uses the imported Util package for calculations
|
|
5
|
-
class TaxCalculator
|
|
6
|
-
STANDARD_RATE = 0.10 # 10%
|
|
7
|
-
LUXURY_RATE = 0.20 # 20%
|
|
8
|
-
|
|
9
|
-
def self.calculate_tax(amount, rate = STANDARD_RATE)
|
|
10
|
-
# Use UtilCalculator from selective import
|
|
11
|
-
UtilCalculator.multiply(amount, rate)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.calculate_total(amount, rate = STANDARD_RATE)
|
|
15
|
-
tax = calculate_tax(amount, rate)
|
|
16
|
-
# Use UtilCalculator.add from selective import
|
|
17
|
-
UtilCalculator.add(amount, tax)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def self.reverse_calculate(total_with_tax, rate = STANDARD_RATE)
|
|
21
|
-
# Calculate original amount from total including tax
|
|
22
|
-
# Formula: original = total / (1 + rate)
|
|
23
|
-
divisor = UtilCalculator.add(1, rate)
|
|
24
|
-
UtilCalculator.divide(total_with_tax, divisor)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Calculator provides basic arithmetic operations
|
|
4
|
-
class Calculator
|
|
5
|
-
def self.add(a, b)
|
|
6
|
-
a + b
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def self.subtract(a, b)
|
|
10
|
-
a - b
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.multiply(a, b)
|
|
14
|
-
a * b
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.divide(a, b)
|
|
18
|
-
raise ArgumentError, 'Cannot divide by zero' if b.zero?
|
|
19
|
-
a.to_f / b
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Geometry provides geometric calculations
|
|
4
|
-
class Geometry
|
|
5
|
-
PI = 3.14159265359
|
|
6
|
-
|
|
7
|
-
def self.circle_area(radius)
|
|
8
|
-
PI * radius * radius
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def self.circle_circumference(radius)
|
|
12
|
-
2 * PI * radius
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.rectangle_area(width, height)
|
|
16
|
-
width * height
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.rectangle_perimeter(width, height)
|
|
20
|
-
2 * (width + height)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.triangle_area(base, height)
|
|
24
|
-
(base * height) / 2.0
|
|
25
|
-
end
|
|
26
|
-
end
|
data/sig/boxwerk.rbs
DELETED