Running a project
Run a program once and watch it print, or play a library's scenarios and see how many pass, with shot run.
What shot run does depends on the project's export, the instance named by the export line of its header. A plain Luau file (.luau) is a program; a ShotScript module, class or func is a library. If you changed the project since it was last saved, the run saves it first, so it always plays what you last wrote.
Run a program
Write a few lines of Luau and watch them print. shot run runs the export once, top to bottom, and each print shows the moment it happens.
Greeter is a program in two Luau files. Its header, the first lines of Greeter.shotsystem, names the export:
.shotsystem
shotsystem 1
export MainMain.luau
local Greeting = require(script.Parent.Words.Greeting)
print("Greeter starts")
for _, name in { "Ada", "Lin" } do
print(Greeting.For(name))
end
print("Greeter done")Words/Greeting.luau
local Greeting = {}
function Greeting.For(name: string): string
if name == "" then
error("a greeting needs a name")
end
return "Hello, " .. name .. "!"
end
return GreetingPut the files in a folder, the header in a file named .shotsystem, and make the project with shot import GreeterFiles Greeter.shotsystem (see One-file projects). Run it:
shot run Greeter.shotsystem
Greeter starts
Hello, Ada!
Hello, Lin!
Greeter doneshot run --project Greeter.shotsystem is the same command, written like every other command on a project.
Read a failure
When something breaks, the run stops and shows you the way it came. Give Greeter an empty name, { "Ada", "", "Lin" }, and shot run Greeter.shotsystem prints:
Greeter starts
Hello, Ada!
error: Greeter:Greeter.Words.Greeting:5: a greeting needs a name
at Greeter:Greeter.Words.Greeting:5 function For
at Greeter:Greeter.Main:5The error: line gives the project, the instance path from its root (the root is named after the project), the line and the message. Under it come the project's lines the error passed through, innermost first. What printed before the error stays, and shot run exits with 1.
For now a line in a .shot file is counted in its compiled Luau: with Greeting written as a .shot module, the same error on line 10 of Greeting.shot is reported as line 16.
Play a library's scenarios
A library doesn't run on its own: its scenarios run it, and shot run counts the ones that pass. Counter's export is a ShotScript module:
.shotsystem
shotsystem 1
export Counter.CounterCounter/Counter.shot
types script.Parent.Types
print("Counter loads")
rootdoc {
Counts things.
}
module Counter = {
Count : number = 0
doc { Adds to the count and returns the new count }
function Add(by : number) : number
module.Count += by
return module.Count
end
}An empty Counter/Types.shot sits beside it (see Types). The export is the instance path from the root, with dots: export Counter/Counter is refused with Counter.shotsystem exports Counter/Counter, and it has no Counter/Counter.
A scenario is a Luau file in the project's Scenarios folder, Scenarios/<Name>.luau, and never part of the export. Counter has three, and shot run without a name lists them:
shot run Counter.shotsystem
Counter.shotsystem exports the library Counter.Counter: it runs through its scenarios (shot run <file> <name>, <A,B> or all)
Adds
Negative
StartsAtZeroName the ones to play:
shot run Counter.shotsystem Adds | plays Adds |
shot run Counter.shotsystem Adds,StartsAtZero | plays those two, in that order |
shot run Counter.shotsystem all | plays every scenario, in name order |
Names match exactly. shot run Adds --project Counter.shotsystem works too.
Scenarios/Adds.luau
local Counter = require(root.Counter.Counter)
Counter.Add(2)
Counter.Add(3)
print("count " .. Counter.Count)
assert(Counter.Count == 5, "two adds should make 5")shot run Counter.shotsystem Adds prints:
ok Counter:Scenarios.Adds
Counter loads
count 5
1 of 1 scenario(s) passedEach scenario gets an ok or FAILED line with what it printed under it, and the last line counts the ones that passed.
Catch what's missing
Write down what the code should do, and a failing scenario shows you where it doesn't. Counter lets the count go below 0:
Scenarios/Negative.luau
local Counter = require(root.Counter.Counter)
Counter.Add(-2)
assert(Counter.Count >= 0, "the count went below 0")shot run Counter.shotsystem all plays all three and exits with 1:
ok Counter:Scenarios.Adds
Counter loads
count 5
FAILED Counter:Scenarios.Negative
Counter loads
error: Counter:Scenarios.Negative:4: the count went below 0
at Counter:Scenarios.Negative:4
ok Counter:Scenarios.StartsAtZero
Counter loads
count 0
2 of 3 scenario(s) passedA name that isn't there fails the same way, with no scenario Nope in Counter.shotsystem (Adds, Negative, StartsAtZero).
Every run starts clean
No scenario leans on another, and no run on the one before. Each program run and each scenario loads every project again, so each module's body runs once in it and its state starts as the code says.
Scenarios/StartsAtZero.luau
local Counter = require(root.Counter.Counter)
print("count " .. Counter.Count)
assert(Counter.Count == 0, "the count should start at 0")shot run Counter.shotsystem Adds,StartsAtZero prints:
ok Counter:Scenarios.Adds
Counter loads
count 5
ok Counter:Scenarios.StartsAtZero
Counter loads
count 0
2 of 2 scenario(s) passedCounter loads once for each scenario, and StartsAtZero sees 0 right after Adds made 5. Inside one run, every require of a module returns the same table.
What a scenario sees
Drive the project from outside, with its whole tree in reach. A scenario's code gets three names: script, the scenario itself; root, the project's root, named after the project, with nothing above it; and require, which takes an instance. Only scenarios get root: a module reaches the rest of its project from script.
Scenarios/Sees.luau
print("script is " .. script.Name .. ", in " .. script.Parent.Name)
print("root is " .. root.Name .. ", parent " .. tostring(root.Parent))
for _, child in root:GetChildren() do
print(" " .. child.Name .. " (" .. child.ClassName .. ")")
endAdd it with shot write Scenarios/Sees.luau --project Counter.shotsystem (the text on stdin; see The shot command), then shot run Counter.shotsystem Sees prints:
ok Counter:Scenarios.Sees
script is Sees, in Scenarios
root is Counter, parent nil
Counter (ModuleScript)
1 of 1 scenario(s) passedThe folder Counter is a ModuleScript because it holds a module of its own name (see Compiled). A program can have scenarios too: shot run Greeter.shotsystem Hello plays Scenarios/Hello.luau instead of the program.
Reach other code
Pull in your own modules by path, a service by name and another project by its alias. A run answers three kinds of require:
require(script.Parent.Words.Greeting) -- a module of this project, by its instance path
require("@shotsystem/ProcessService") -- a service
require("@Bank") -- another project's export, by the alias in this project's headerIn a .shot file, shot check doesn't know the two string requires yet and reports Unknown require: unsupported path, though the file runs. A .luau file checks clean.
Call a service
Start programs and read what they print. ProcessService is the one service today (see ProcessService):
Main.luau
local ProcessService = require("@shotsystem/ProcessService")
local result, why = ProcessService.RunAsync("python", { "-c", "print(6 * 7)" })
if not result then
error("couldn't run python: " .. tostring(why))
end
print("python says " .. result.Stdout)shot run prints python says 42.
Use another project
Build on a library without copying it in. Bank is a library project whose export is Bank/Bank.shot.
Bank's header:
shotsystem 1
export Bank.BankBank/Bank.shot
types script.Parent.Types
rootdoc {
Keeps a balance of coins.
}
module Bank = {
Balance : number = 0
doc { Puts coins in and returns the new balance }
function Deposit(amount : number) : number
module.Balance += amount
return module.Balance
end
}Shop is a program beside it. An external line in its header names Bank's file, relative to Shop's own, and gives it an alias.
Shop's header:
shotsystem 1
export Main
external "Bank.shotsystem" as BankMain.luau
local Bank = require("@Bank")
print("balance " .. Bank.Deposit(5))
print("balance " .. Bank.Deposit(7))shot run Shop.shotsystem prints:
balance 5
balance 12require("@Bank") returns Bank's export, and only the export crosses over: it must be a library, and no other module of Bank can be required from Shop. Every project in a run that uses Bank shares one copy of it.
What stops a run
These mistakes stop a run at once, with a message that names the problem.
A write to _G, which is read only, so modules can't pass state through it:
error: Notes:Notes.Main:2: attempt to modify a readonly tableA require loop, here Cart requiring Prices and Prices requiring Cart. The message gives the chain:
error: require loop at run time: Shop:Shop.Cart -> Shop:Shop.Prices -> Shop:Shop.CartA service that isn't there:
error: Notes:Notes.Main:1: no ShotSystem service NoSuchServiceAn alias with no external line in the project's own header:
error: Notes:Notes.Main:1: require("@Bank"): Notes.shotsystem has no external of that name (a string require is @<Alias> from the project's header, or @shotsystem/<Service>)An external whose export is a program:
error: Cart:Cart.Main:1: "Greeter.shotsystem" exports the script Main, and a script can't be used from another project: only a module or a class canA module of another project that isn't its export, even one reached through an instance the export hands out:
error: Cart:Cart.Main:3: a project can't require another project's modules: Bank:Bank.Bank.LedgerExit codes
0 | the program ended, every scenario played passed, or the scenarios were listed |
1 | the program stopped on an error, a scenario failed or isn't there, or the project couldn't be run |
