script Go Package

repository·main·Indexed 41 days ago

https://github.com/rsc/script

A Go implementation of a scripting language used internally by the go command. It provides a script engine capable of executing commands with support for negation (!), optionality (?), and conditional guards ([cond]). The package includes a set of default Unix-like commands for file manipulation, environment management, and process execution, as well as a framework for implementing custom commands and conditions.

Tokens
5.6K
Snippets
26
Records
34
Agent score
50%

What's inside script

  1. Script Syntax: Variable Expansion and Quoting

    main

    The script engine performs environment variable expansion and supports quoting:

    • Variable Expansion: Variables like $VAR are expanded within arguments. Special variables : (path separator) and / (path separator) are expanded but not inherited in subprocesses.
    • Single Quotes: Surrounding text with '...' disables both word splitting and environment variable expansion.
    • Escaping Quotes: To include a literal single quote inside a quoted block, use two single quotes: 'Don''t'.
    • Comments: A line starting with # is a comment and also marks the beginning of a new log section.
  2. Script Syntax: Command Prefixes and Conditions

    main

    The script language supports several modifiers for commands:

    • Negation (!): !command means the command must fail for the script to continue. If it succeeds, the script stops with an error.
    • Optionality (?): ?command means the command may succeed or fail, but the script will continue regardless.
    • Conditions ([cond]): [condition] command runs the command only if the condition is met.
      • Multiple conditions: [cond1] [cond2] command (all must be satisfied).
      • Negated condition: [!cond] command (runs only if the condition is NOT met).
      • Prefix conditions: [prefix:suffix] command (uses a condition that requires a suffix, like [GOOS:linux]).
  3. Initialize a script engine with NewEngine

    main

    Use NewEngine() to create an Engine instance configured with a default set of commands and conditions that resemble a simplified Unix shell. An Engine can execute multiple scripts concurrently and stores the configuration for available commands (Cmds) and conditions (Conds).

    engine := script.NewEngine()
  4. Create a custom command with Command()

    main

    You can define your own commands using the Command function. It requires a CmdUsage struct (defining summary, arguments, and details) and a run function. The run function must return a WaitFunc (to handle asynchronous completion) and an error.

    myCmd := script.Command(
        script.CmdUsage{
            Summary: "my custom command",
            Args:    "arg1...",
        },
        func(s *script.State, args ...string) (script.WaitFunc, error) {
            // implementation
            return func(s *script.State) (string, string, error) {
                return "output", "", nil
            }, nil
        },
    )
  5. Create symbolic links with Symlink()

    main

    Creates a symbolic link using the syntax path -> target.

    Note: The target argument is not interpreted via the script's Path() method; it is treated as a path relative to the directory containing the link.

    // Usage: path -> target
    sym := script.Symlink()
  6. Run an external executable with Exec()

    main

    The Exec command runs an arbitrary program as a subprocess. It uses the script's current PATH to locate the executable.

    When the script's context is canceled, Exec sends an interrupt signal and waits for a specified waitDelay before forcefully killing the process. Note that exec does not terminate the script itself if the program fails.

    // Example: running 'ls -l' with a 100ms grace period for interrupts
    execCmd := script.Exec(func(cmd *exec.Cmd) error { 
        return cmd.Process.Signal(os.Interrupt) 
    }, 100*time.Millisecond)
  7. Manage the State lifecycle with CloseAndWait

    main

    To properly shut down a script execution, call CloseAndWait. This method:

    1. Cancels the State's internal context.
    2. Waits for all running background commands to finish.
    3. Flushes the internal script log to the provided io.Writer.

    If any background command ended in an unexpected state, CloseAndWait returns a non-nil error.

    err := state.CloseAndWait(os.Stdout)
    if err != nil {
        // handle error
    }
  8. Create boolean conditions with BoolCondition()

    main

    Use BoolCondition to create a condition that always returns a fixed truth value. These conditions reject the use of suffixes.

    // Example of a static boolean condition
    cond := script.BoolCondition("is_root", os.Geteuid() == 0)
  9. Create prefix conditions with PrefixCondition()

    main

    Use PrefixCondition to create a condition that accepts a suffix string during evaluation. This is useful for conditions that check equality against a provided value (e.g., runtime.GOOS == <suffix>).

    // Example of a condition that uses a suffix
    cond := script.PrefixCondition("runtime.GOOS == <suffix>", func(s *script.State, suffix string) (bool, error) {
        return suffix == runtime.GOOS, nil
    })
  10. Create custom conditions with Condition()

    main

    Use Condition to create a condition that evaluates based on the current execution *State. This type of condition does not support suffixes; providing a suffix to its Eval method will return ErrUsage.

    // Example of creating a condition that checks a state property
    cond := script.Condition("is_debug", func(s *script.State) (bool, error) {
        return s.IsDebug(), nil
    })
  11. Execute a script with Engine.Execute

    main

    The Execute method reads and runs a script from a *bufio.Reader. It writes the execution log to the provided io.Writer.

    Behavior:

    • It stops and returns an error at the first command that fails unexpectedly.
    • Errors are returned with a file:line: prefix.
    • If a command succeeds or fails according to its expected status (using ! or ? prefixes), execution continues.
    • Background commands (ending in &) are not stopped when Execute returns; use State.CloseAndWait or the Wait command to manage them.
    • Lines starting with # denote a new section and trigger a log flush of the previous section.
    err := engine.Execute(state, "myscript.sh", scriptReader, logWriter)
  12. Access command output and logging

    main

    The State tracks the output of the most recently executed command and provides a meta-log for the script engine:

    • Stdout() string: Returns the standard output from the last command run.
    • Stderr() string: Returns the standard error from the last command run.
    • Logf(format string, args ...any): Writes formatted messages to the script's internal log (acting as a meta-stderr). This does not affect the Stdout or Stderr buffers.
    • Getwd() string: Returns the current working directory (pwd) used for the next command.