One-file projects

Keep a whole project in one readable text file, <Name>.shotsystem, that you can diff, edit by hand and run with shot run.

A .shotsystem file holds every folder, script, value and scenario of a project: one line per instance, one tab per level, and each script's text as it is. shot new Wallet.shotsystem makes an empty one:

shotsystem 1

id 15382021e71e627f423aae6d70735f91

A whole project

Read a project top to bottom, like a tree of folders. This one is whole: a module and its types, a value with an attribute and a tag, and a scenario that plays the module.

Wallet.shotsystem

shotsystem 1
export Wallet.Bank

id 15382021e71e627f423aae6d70735f91
Folder Wallet #c27ea5a5
    Script Bank #3f9a1c2e
        source
            types script.Parent.Types

            module Bank = {
                doc { Coins held }
                Balance : Coins = 0

                doc { Adds coins and returns the new balance }
                function Deposit(amount : Coins) : Coins
                    module.Balance += amount
                    return module.Balance
                end
            }
    StringValue Currency #55aa0f31
        Value = "Coins"
        attribute Max = 20
        tag Tuning
    Script Types #9e21b7c4
        source
            public type Coins = number
scenario Deposit #0b5e7d13
    source
        local Bank = require(root.Wallet.Bank)
        assert(Bank.Deposit(5) == 5, "the first deposit gives 5")
        print("balance", Bank.Balance)

shot tree --project Wallet.shotsystem prints what it holds:

Wallet.shotsystem
./  (the project)
  Wallet/
    Bank.shot
    Currency  (StringValue)
    Types.shot
Scenarios/  (never part of the program)
  Deposit.luau

and shot run Wallet.shotsystem Deposit plays the scenario:

ok     Wallet:Scenarios.Deposit
  balance 5
1 of 1 scenario(s) passed

The header

Say what the file is and what the project offers in its first lines. The header is every line up to the first empty line: shotsystem 1 comes first, then export Wallet.Bank, the one script or module the project exports, as a dotted path from the project root (see THE EXPORT below). shot write can't change the header, so you edit these lines by hand, while shot doesn't have the project open (see WHILE SHOT HAS IT OPEN below).

The id line

The project carries its own id. After the empty line comes id and the id shot new made: leave it as it is. A file without one gets a new one the next time it is saved.

Lines with no indent between the id line and the first instance belong to the project root itself:

Cart.shotsystem

shotsystem 1

id 15382021e71e627f423aae6d70735f91
attribute Version = 2
tag Draft
Folder Cart #4f1d8e2a

shot props . --project Cart.shotsystem reads them:

attributes:
  Version = 2  (number)
tags: Draft

Instances

Every object of a project is one line you can read at a glance: its class, its name and its id.

StringValue Currency #55aa0f31

One tab deeper come the instance's own lines, in this order:

  1. 1. its Value, on the value classes (see THE CLASSES below);
  2. 2. its attribute lines;
  3. 3. its tag lines;
  4. 4. a script's source block;
  5. 5. its children, each an instance line of its own.

A name with spaces or signs goes in quotes, and so does such a tag:

Folder "Daily notes" #70000001
    StringValue First #70000002
        Value = "Buy milk"
        tag "To do"

The classes

Build a project from folders, scripts and values. These are the classes, with the one property a value class has, Value:

Foldera folder
ScriptShotScript; the tools show it as <Name>.shot
Luauplain Luau; the tools show it as <Name>.luau
StringValueValue = "Coins"
NumberValueValue = 1.5
IntValueValue = 99, a whole number
BoolValueValue = true
ObjectValueValue = #8b03e5f1, another instance by its id, or Value = root, the project root
Color3ValueValue = Color3(0.2, 0.4, 1)
Vector3ValueValue = Vector3(10, 0, 10)

A Value line is written only when it isn't the default: "", 0, false, no instance, Color3(0, 0, 0) or Vector3(0, 0, 0). The other classes have no properties in the file: keep anything else on them as attributes.

Attributes and tags

Hang any setting on any instance, and mark instances with tags. attribute <Name> = <value> sets an attribute, and the value's form is its type:

"My shop"text, in double quotes, escaped as in JSON
0.25a number
truetrue or false
Vector2(4, 8)a Vector2
Vector3(0, 5, -2)a Vector3
Color3(1, 0.5, 0)a Color3, each part from 0 to 1
UDim(0, 6)a UDim: scale, offset
UDim2(0.5, 0, 1, -10)a UDim2: x scale, x offset, y scale, y offset
CFrame(1, 2, 3)a CFrame at that position
NumberRange(1, 10)a NumberRange: min, max
Rect(0, 0, 64, 32)a Rect: x0, y0, x1, y1
BrickColor(21)a BrickColor, by its number

tag <Tag> adds a tag, and tags keep the order you write them in. A value class's Value takes the same forms.

Settings.shotsystem

shotsystem 1

id 6a0e3c95d17b4f28b9e1c04d5a7f3e62
Folder Settings #4d2a91c7
    attribute Title = "My shop"
    attribute Rate = 0.25
    attribute Open = true
    attribute Offset = Vector2(4, 8)
    attribute Spawn = Vector3(0, 5, -2)
    attribute Tint = Color3(1, 0.5, 0)
    attribute Pad = UDim(0, 6)
    attribute Size = UDim2(0.5, 0, 1, -10)
    attribute Where = CFrame(1, 2, 3)
    attribute Range = NumberRange(1, 10)
    attribute Box = Rect(0, 0, 64, 32)
    attribute Brick = BrickColor(21)
    tag Saved
    StringValue Currency #8b03e5f1
        Value = "Coins"
    ObjectValue Home #e7c1290a
        Value = #8b03e5f1
    ObjectValue Top #19f4b6d3
        Value = root

shot props Settings --project Settings.shotsystem reads them back:

Settings  (Folder)
attributes:
  Box = 0, 0, 64, 32  (Rect)
  Brick = Bright red  (BrickColor)
  Offset = 4, 8  (Vector2)
  Open = true  (boolean)
  Pad = 0, 6  (UDim)
  Range = 1, 10  (NumberRange)
  Rate = 0.25  (number)
  Size = {0.5, 0}, {1, -10}  (UDim2)
  Spawn = 0, 5, -2  (Vector3)
  Tint = 1, 0.5, 0  (Color3)
  Title = My shop  (string)
  Where = 1, 2, 3  (CFrame)
tags: Saved
children (3): Currency, Home, Top (shot tree Settings)

Scripts as plain text

Read and diff a script right in the project file. Under source, every line of the script sits one tab deeper than the source line, as it is: nothing is escaped or quoted, and an empty line stays empty. A Script holds ShotScript (see Overview), a Luau plain Luau. When the text doesn't end with a newline, the line says so:

Luau Answer #a4b9d6e1
    source (no newline at end)
        return 42

Scenarios

Keep the code that plays a project inside the project. A scenario is a line scenario <Name> #<id> with no indent, after the instances, and its source block of Luau. It is never part of the program: only shot run plays it, and its code sees script, require and root, the project root, so require(root.Wallet.Bank) reaches the module in the whole project above (see Running a project).

The export

Give a project one thing to offer: a program to run, or a library its scenarios play. The header's export line names it (shot new writes none: add it by hand), and its kind decides what shot run does:

  • A program is a Luau module, or a Script that declares no class, module or func. shot run runs it once, printing as it goes.
  • A library is a Script that declares a module, class or func. shot run lists its scenarios, and shot run Wallet.shotsystem Deposit (or A,B, or all) plays them.

Greeter.shotsystem

shotsystem 1
export Greeter.Main

id 9c4b2e7f0a1d4e8b8f6a3c5d7e9b1f20
Folder Greeter #d1e6a3b8
    StringValue Greeting #e2f7b4c9
        Value = "Hello"
    Luau Main #f3a8c5d0
        source
            local greeter = script.Parent

            local function greet(name : string) : string
                return greeter.Greeting.Value .. ", " .. name
            end

            print(greet("Ada"))
    Luau Answer #a4b9d6e1
        source (no newline at end)
            return 42

shot run Greeter.shotsystem prints:

Hello, Ada

and shot run Wallet.shotsystem, on the library of the whole project above, lists:

Wallet.shotsystem exports the library Wallet.Bank: it runs through its scenarios (shot run <file> <name>, <A,B> or all)
  Deposit

An export that isn't there, or isn't a script or a module, is refused. The path is dotted: export Wallet/Bank is refused the same way.

Wallet.shotsystem exports Wallet.Bnk, and it has no Wallet.Bnk
Wallet.shotsystem exports Wallet.Currency, a StringValue: an export is a script or a module

Id kinds

Rename an instance and whatever points at it still finds it: the id stays with the instance. Every instance line ends in # and 8 lowercase hex digits, and the tools make them: shot create, and shot write of a new file, give the new instance one, and shot import gives a whole folder fresh ones.

In the Settings project above, shot create IntValue Settings Limit --project Settings.shotsystem adds

    IntValue Limit #85329efc

and after shot set Settings/Currency Name Money --project Settings.shotsystem the ObjectValue Home still points at it, by its id:

    ObjectValue Home #e7c1290a
        Value = #8b03e5f1
    IntValue Limit #85329efc
    StringValue Money #8b03e5f1
        Value = "Coins"

shot props Settings/Home --project Settings.shotsystem:

Settings/Home.instance  (ObjectValue)
  Value = Settings/Money

When you write lines by hand, leave the id off a new one. When the file is saved, a line without an id gets one, a line whose id an earlier line already has gets a new one, and an ObjectValue pointing at an id no line has is cleared. So to copy instances, copy their lines: the copy gets new ids.

Orders.shotsystem, as written

shotsystem 1

id 15382021e71e627f423aae6d70735f91
Folder Orders #a41c07e2
    Folder Cart #b52d18f3
        IntValue Items #c63e29a4
            Value = 2
    Folder Cart2 #b52d18f3
        IntValue Items #c63e29a4
            Value = 2

Orders.shotsystem, after shot tree --project Orders.shotsystem

shotsystem 1

id 15382021e71e627f423aae6d70735f91
Folder Orders #a41c07e2
    Folder Cart #b52d18f3
        IntValue Items #c63e29a4
            Value = 2
    Folder Cart2 #29ac9c90
        IntValue Items #88b85b53
            Value = 2

As folders and files

Use shot's file commands on a project as on a folder on disk. They see it as folders and files (shot props shows an instance's lines):

Foldera folder
Script<Name>.shot
Luau<Name>.luau
any other instance<Name>.instance: its class on line 1, then its own lines without the indent
a scenarioScenarios/<Name>.luau
the header.shotsystem at the root

shot map --project Wallet.shotsystem lists them:

Shot project Wallet.shotsystem: 2 files in 1 folders

Wallet/
  Bank.shot          module Bank
  Types.shot         types        public Coins
  Currency.instance  StringValue

shot export Wallet.shotsystem out writes them as real files:

out/
    .shotsystem
    Scenarios/
        Deposit.luau
    Wallet/
        Bank.shot
        Currency.instance
        Types.shot

out/Wallet/Currency.instance

StringValue
Value = "Coins"
attribute Max = 20
tag Tuning

shot import out Copy.shotsystem makes a new project file from such a folder, header included. The trip loses some things: shot export leaves out a Folder's attributes and tags and the project root's, and shot import gives every instance a new id, so an ObjectValue's Value no longer points anywhere and is cleared. The Settings project above comes back with no attributes or tag on Settings and no Value on Home. shot import won't write over a file:

Copy.shotsystem already exists (export it or pick another name)

Writing an .instance file changes its instance in place and keeps its id. With that text, but Value = "Gems" and attribute Max = 50, in currency.txt,

shot write Wallet/Currency.instance --input currency.txt --project Wallet.shotsystem

replies

replaced Wallet/Currency.instance (4 lines)

check:
0 errors (2 files checked, 0.1 s)

and the project file then holds

    StringValue Currency #55aa0f31
        Value = "Gems"
        attribute Max = 50
        tag Tuning

Writing it by hand

Write the lines in any order and let shot tidy them. A file you write may list children in any order, put an instance's own lines in any order before its children, leave empty lines between instances and end its lines in CRLF. Whenever shot saves the file, after a change or right after opening a file it had to fix, it writes the tidy form; a file that is only out of order stays as you wrote it until the next change.

Shop.shotsystem, as written (with CRLF line ends)

shotsystem 1

id 2f8c6a1e9d0b47c3a5e4f6b7c8d9e0a1
Folder Shop #a41c07e2
    NumberValue Price
        attribute Sale = true
        Value = 4.5
        attribute Label = "Hat"

    IntValue Stock #b52d18f3
        Value = 0

    Folder Cart #b52d18f3
        BoolValue Open #c63e29a4
            tag Saved
            Value = true

Shop.shotsystem, after shot tree --project Shop.shotsystem

shotsystem 1

id 2f8c6a1e9d0b47c3a5e4f6b7c8d9e0a1
Folder Shop #a41c07e2
    Folder Cart #2d73e0ee
        BoolValue Open #c63e29a4
            Value = true
            tag Saved
    NumberValue Price #65692af1
        Value = 4.5
        attribute Label = "Hat"
        attribute Sale = true
    IntValue Stock #b52d18f3
  • Children are sorted by name. Under each instance come its Value, then its attributes by name, then its tags as written.
  • The empty lines are gone, and every line ends in LF.
  • Stock's Value = 0 is the default, so it is left out.
  • Price had no id and got one. Cart repeated Stock's id, so Cart, the later line, got a new one.

A source block keeps its text exactly, so a script typed with CRLF line ends keeps them. Next to the file, shot also keeps dated copies of it in Shop.history/, in the same format (shot history lists them).

While shot has it open

Edit the file by hand while shot doesn't have the project open, and every edit is read. A command with --project, or shot export, opens the project, and it stays open for the commands after it: they read and change the open copy, and save it to the file after each change. So while it is open, shot doesn't see a hand edit, and its next change writes over it. shot run Wallet.shotsystem reads the file without opening the project.

Change Currency's Value = "Coins" to "Gems" in an editor while the whole project above is open, and shot props Wallet/Currency --project Wallet.shotsystem still says

Wallet/Currency.instance  (StringValue)
  Value = Coins

then shot tag Wallet/Currency Shown --project Wallet.shotsystem saves the open copy, and the file says Value = "Coins" again.

To hand-edit a project that is open, close it first: shot help lists the command that stops it, under ShotSystem files. Then edit the file, and the next command reads your edit. While the project is open, change an instance or a script by writing its file with shot write (see AS FOLDERS AND FILES above). The header has no such command, so edit it while the project is closed.

Mistakes

Find a slip by its line number. A file with a mistake is refused whole, with the line and what was expected, and stays as it is: shot run and every command with --project refuse it. The messages:

line 1: a ShotSystem file starts with the line `shotsystem 1`
line 5: lines are indented with tabs, not spaces
line 5: indented 2 tabs, but this line can be indented at most 1 (one more than the instance it belongs to)
line 4: expected a name after Folder (a name with spaces or signs is written in quotes, like "My name")
line 4: expected #id after the name (# and 8 lowercase hex digits, like #3f9a1c2e; a name with spaces is written in quotes)
line 6: StringValue.Value takes a string ("text"); expected a value: "text", a number, true, false, Vector3(1, 2, 3) and the like
line 6: IntValue.Value takes a whole number
line 6: a StringValue has no property Size (it has Value)
line 5: a Folder has no property Value
line 5: attribute Open takes a value: "text", a number, true, false, Vector3(1, 2, 3) and the like
line 5: attribute Spawn takes Vector3(x, y, z)
line 6: Folder Wallet (line 4) has children above this line; an instance's properties, attributes, tags and source go before its children
line 8: Luau Main has a second source