Command Line (CLI)

Command Line (CLI)

Luma provides a unified cli handle for working with command-line arguments, flags, interactive user input, and program output.

The API is designed around type-driven parsing, with simple defaults for common cases and optional configuration for advanced usage.

A single cli handle covers all command-line interaction in a Luma program.

Creating a CLI handle

c: cli = cli()
  • Creates a CLI handle for the current program
  • Command-line arguments are read implicitly
  • Only one cli handle is typically needed

Named flags

Flags are named arguments passed using --name=value, --name value, or short forms like -n value.

Basic flags

c: cli = cli()

name: str = c.flag("name", "guest")
count: int = c.flag("count", 0)
verbose: bool = c.flag("verbose", false)

Command-line usage:

luma run app.luma --name=test --count=5 --verbose

The declared type determines how the value is parsed.


Type-driven parsing

The type of the variable receiving the flag controls parsing behavior:

name: str = c.flag("name", "guest")
port: int = c.flag("port", 8080)
rate: float = c.flag("rate", 1.5)
debug: bool = c.flag("debug", false)

If a flag value cannot be parsed into the declared type, the program exits with an error.


Optional flags (no default)

Use optional types when a flag may or may not be provided.

config: ?str = c.flag("config")
limit: ?int = c.flag("limit")

if config != nil {
    print("Using config: ${config}")
}
  • Returns nil if the flag is not present
  • Invalid provided values still cause an error

Boolean flags

Boolean flags follow standard CLI conventions:

verbose: bool = c.flag("verbose", false)
Command Result
--verbose true
--verbose=true true
--verbose=false false
not provided false

Positional arguments

Arguments without a -- prefix are treated as positional.

luma run app.luma file1.txt file2.txt --verbose

Get all positional arguments

files: [str] = c.args()

Result:

["file1.txt", "file2.txt"]

Get positional argument by index

first: ?str = c.arg(0)
second: ?str = c.arg(1)
third: ?str = c.arg(2)

Returns nil if the index is out of range.

Mixing flags and positional arguments

Flags can appear anywhere on the command line.

luma run app.luma input.txt --format=json output.txt --verbose
format: str = c.flag("format", "text")
verbose: bool = c.flag("verbose", false)

files: [str] = c.args()  // ["input.txt", "output.txt"]

List flags (repeatable flags)

Flags declared as lists accumulate values across multiple occurrences.

luma run app.luma --tag=a --tag=b --tag=c
tags: [str] = c.flag("tag", list(str))
// tags == ["a", "b", "c"]

ports: [int] = c.flag("port", list(int))
  • Order is preserved
  • If the flag is not provided, the list is empty
  • Single occurrences still produce a list

User input (prompts)

Interactive input is handled via prompt().
This replaces the previously planned wait() function.

Basic prompt

age: int = c.prompt()
name: str = c.prompt()
price: float = c.prompt()
confirm: bool = c.prompt()

The declared type determines how input is parsed.

Prompt with message

name: str = c.prompt("What's your name? ")
age: int = c.prompt("Enter your age: ")

Prompt with options

For advanced behavior, pass an options map.

username: str = c.prompt("Username: ", {
    default: "guest",
    timeout: 30,
    validate: u -> u != ""
})

Prompt options

Option Type Description
default T Value used when input is empty
timeout int Seconds to wait before timeout
validate (T) -> bool Validation function

Prompt with default

port: int = c.prompt("Port: ", {
    default: 8080
})

Pressing enter without input selects the default value.

Prompt with validation

age: int = c.prompt("Age (18+): ", {
    validate: a -> a >= 18
})

Invalid input automatically re-prompts.

Prompt with timeout

Requires an optional type.

answer: ?str = c.prompt("Quick answer (5s): ", {
    timeout: 5
})

if answer == nil {
    print("Too slow!")
}

Output capture

The CLI handle can capture output produced by print().

Basic capture

c.record()
print("Hello")
print("World")
output: str = c.stop()

Captured output:

Hello
World

Selective capture

print("Not captured")

c.record()
print("Captured")
output: str = c.stop()

Multiple captures

c.record()
first: str = c.stop()

c.record()
second: str = c.stop()

Capture for testing

fn test_greeting() {
    c: cli = cli()

    c.record()
    greet("Alice")
    output: str = c.stop()

    if output == "Hello, Alice!\n" {
        print("Test passed")
    }
}

Complete example

c: cli = cli()

verbose: bool = c.flag("verbose", false)
output_file: ?str = c.flag("output")

files: [str] = c.args()

if files.len() == 0 {
    filename: str = c.prompt("Enter filename: ")
    files = [filename]
}

files.walk(i, f) -> {
    if verbose {
        print("Processing: ${f}")
    }
}

if output_file != nil {
    confirm: bool = c.prompt("Save results? (true/false): ")

    if confirm {
        c.record()
        print_results()
        results: str = c.stop()

        file(output_file).write(results)
    }
}

Summary

  • cli() provides a single entry point for command-line interaction
  • Flags and prompts are parsed based on declared types
  • Optional types represent absence, not errors
  • List flags support repeated values naturally
  • Output capture enables testing and processing
  • The common case stays simple; advanced behavior is explicit
Last updated on