Skip to content

Tooling

A small pricing app demonstrates carton check, carton rbs, and Steep. It needs the repository’s development bundle for these tools. For runtime boundaries alone, start with Minimal. Source on GitHub.

The app imports Pricing and calls its exported method:

main.rb
require 'carton'
# Carton boundary: the app uses only Pricing's exported method. Its RBS
# declares the return type.
Pricing = import_relative 'pricing'
puts "Price with tax: #{Pricing.total(40)}"

The pricing Entrypoint loads its own tax helper, makes Pricing::Tax private, and exports Pricing:

pricing.rb
# The pricing Entrypoint loads its own Interior in the same Carton Box.
require_relative 'pricing/tax'
module Pricing
private_constant :Tax
def self.total(subtotal) = subtotal + Tax.on(subtotal)
end
# Export: importing pricing returns this module.
export Pricing
pricing/tax.rb
# This helper belongs to the pricing Carton's Interior, not its Public Surface.
module Pricing
module Tax
def self.on(subtotal) = subtotal * 0.1
end
end

violations.rb deliberately reaches the private constant and imports an Interior file:

violations.rb
# Deliberately invalid code for `carton check`; main.rb is the working app.
require 'carton'
Pricing = import_relative 'pricing'
# Ruby's private_constant keeps Tax out of Pricing's Public Surface.
puts Pricing::Tax
# Only the Entrypoint can be imported, never an Interior file.
import_relative 'pricing/tax'

From the repository root, check the app and the violations without running either file:

bundle exec carton check examples/tooling/main.rb
bundle exec carton check examples/tooling/violations.rb # exits 1 with two errors

The test suite asserts both diagnostics. Running violations.rb as Ruby instead stops at its first NameError.

Pricing declares its public method’s argument and return types:

sig/pricing.rbs
# Only the pricing Carton's Public Surface needs an RBS declaration.
module Pricing
def self.total: (Integer subtotal) -> Float
end

carton rbs main.rb generates a signature for the import. It refers to Pricing’s declaration instead of inferring the implementation’s types:

sig/generated/carton.rbs
# Generated by `carton rbs` from the app's imports. Do not edit.
module Kernel
private
def import_relative: ("pricing" path) -> singleton(::Pricing)
| ...
end

Steep reads those signatures and Carton’s own RBS, then checks the app:

Steepfile
# `carton rbs main.rb` types the import; sig/pricing.rbs types its Export.
# In an installed app, `library 'carton'` replaces the '../../sig' path.
target :app do
signature 'sig', '../../sig'
check 'main.rb'
end
cd examples/tooling
bundle exec carton rbs main.rb
bundle exec steep check

The app passes. Adding Pricing.total(40).upcase fails because total returns a Float, which has no upcase method. The test suite checks that failure too. See Types and checks for the commands and their limits.

From the repository root:

bundle exec rake example:tooling

The task runs the app, checks its boundaries, regenerates the import signature, and runs Steep. CI runs the same task with the other examples.