Documentation
¶
Overview ¶
Package linebased parses line-based scripts and provides template expansion.
A linebased script is a sequence of expressions. Each expression has a command name (the first word) and a body (everything that follows). No quotes, no escaping. The simplicity is the point: what you write is what you get.
echo hello world set path /usr/local/bin
Multi-line bodies use tab-indented continuation lines:
sql query users SELECT id, name FROM users WHERE active = true
Templates let you define reusable expressions with parameters:
define greet name echo Hello, $name! greet Alice greet Bob
And includes let you compose scripts from multiple files:
include common.lb
Syntax ¶
The grammar in EBNF:
script = { expression } .
expression = { comment } ( command | blankline ) .
comment = "#" text newline .
command = name [ whitespace text ] newline { continuation } .
continuation = tab text newline .
blankline = newline .
name = nonwhitespace { nonwhitespace } .
whitespace = " " | tab .
nonwhitespace = (* any character except space, tab, newline *) .
text = (* any characters except newline *) .
tab = "\t" .
newline = "\n" .
A command line begins with a name (one or more non-whitespace characters). Everything after the name through the newline becomes part of the body. Continuation lines must start with exactly one tab, which is stripped. Lines starting with space are syntax errors.
Comments ¶
Lines starting with # are comments. They attach to the following expression and are available in the Comment field of Expanded:
# Set the greeting message. # This supports multiple lines. echo Hello, World!
Comments inside template bodies work the same way.
Templates ¶
Define templates with the "define" builtin. The first line names the template and lists parameters. Continuation lines form the template body:
define greet name echo Hello, $name!
Invoke templates by name. Arguments are split by whitespace, with the final argument consuming remaining text:
greet Alice # echo Hello, Alice! greet "Bob Smith" # echo Hello, "Bob Smith"! (quotes are literal)
Parameter references use $name or ${name}. The braced form allows adjacent text:
define shout word
echo ${word}!!!
Templates can invoke other templates:
define inner x echo $x define outer y inner $y outer hello # echo hello
Constraints:
- Recursion is forbidden.
- Templates cannot be redefined.
- Expanded templates cannot contain "define".
Includes ¶
The "include" builtin reads another file and processes it inline:
include helpers include common
Include paths must be simple filenames without directory separators. The ".linebased" extension is added automatically, so "include helpers" opens "helpers.linebased" from the fs.FS passed to NewExpandingDecoder.
Included files can define templates used by the including file. Include cycles are detected and reported as errors.
Blank Lines ¶
Blank lines produce expressions with empty names. This preserves the visual structure of the source:
echo first echo second
The blank line between commands appears in the expression stream. Like commands, blank lines can have preceding comments.
Unknown Commands ¶
Commands that are not templates pass through unchanged. This allows scripts to define their own command vocabulary:
define echo tail echo hello # your code interprets "echo" custom arg1 arg2 # your code interprets "custom"
Error Handling ¶
Errors during parsing or expansion are reported as ExpressionError with location information:
for expr, err := range linebased.Expand("script.lb", fsys) {
if err != nil {
log.Fatal(err) // includes file:line
}
// process expr
}
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseArgs2 ¶
ParseArgs2 splits s into two whitespace-separated arguments. The second argument contains any remaining text after the first split.
func ParseArgs3 ¶
ParseArgs3 splits s into three whitespace-separated arguments. The third argument contains any remaining text after the first two splits.
Types ¶
type Args ¶
type Args []string
Args represents a list of arguments extracted from an expression's tail.
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder reads line-based expressions from an input stream.
func NewDecoder ¶
NewDecoder creates a new Decoder that reads from r.
func (*Decoder) Decode ¶
func (d *Decoder) Decode() (Expression, error)
Decode reads the next Expression from the input and returns it. It returns io.EOF when there are no more expressions to read. It returns an error if the input is malformed.
type Expanded ¶
type Expanded struct {
Expression
// File returns the source filename where this expression was parsed.
File string
// Stack contains the call stack of the template expansions that
// produced this expression.
Stack []Expanded
}
Expanded represents a parsed expression from the input stream, capturing both its content and context within the template expansion process.
Expressions are intended to be produced by [Expressions] or [Expand], not built manually.
func (*Expanded) Caller ¶
Caller returns the immediate template call that produced the expression, or the zero Expression if the expression is top-level.
func (*Expanded) String ¶
String formats the expression as parseable source text. Reconstructs the original syntax with normalized whitespace, preserving continuation line structure when the tail starts with newlines.
For example:
echo hello world
The Name becomes "echo" and Tail becomes "hello world\n". String returns "echo hello world\n".
define greet name echo Hello, $name!
The Name becomes "define" and Tail becomes "greet name\necho Hello, $name!\n". String returns "define greet name\necho Hello, $name!\n".
func (*Expanded) Where ¶
Where returns a location string showing where this expression appears and executes. The format is filename:line: template@localline where template identifies the execution context and localline shows the line within that template.
For example, "example.txt:42: bar@5" means the expression appears at line 42 of "example.txt" and executes as line 5 within template "bar". Top-level expressions show "main" as the template.
type ExpandingDecoder ¶
type ExpandingDecoder struct {
// contains filtered or unexported fields
}
ExpandingDecoder reads expressions from a linebased script, expanding any templates defined in-line. It is analogous to Decoder but produces Expanded values with template expansion applied.
Create an ExpandingDecoder with NewExpandingDecoder, then call ExpandingDecoder.Decode repeatedly until it returns io.EOF.
func NewExpandingDecoder ¶
func NewExpandingDecoder(name string, fsys fs.FS) *ExpandingDecoder
NewExpandingDecoder creates an ExpandingDecoder that reads from the named file in fsys, expanding any templates defined in-line.
Include paths are rooted at fsys. For example, if main.lb contains "include lib/utils.lb", the decoder opens "lib/utils.lb" from fsys directly. There is no relative path resolution - all includes are absolute paths within the filesystem.
Expressions with names that do not match a template are passed through as-is. Invalid expansions are reported as ExpressionError.
func (*ExpandingDecoder) Decode ¶
func (d *ExpandingDecoder) Decode() (Expanded, error)
Decode reads and returns the next expanded expression. It returns io.EOF when there are no more expressions. After Decode returns an error (other than io.EOF), subsequent calls return the same error.
func (*ExpandingDecoder) SetRoot ¶
func (d *ExpandingDecoder) SetRoot(root string)
SetRoot sets a prefix for file paths in error messages. This is useful when the fsys is rooted at a subdirectory but you want error messages to show paths relative to a parent directory (e.g., the module root).
For example, if fsys is rooted at "." but tests are in "pkg/testdata/", calling SetRoot("pkg/") will cause error messages to show "pkg/testdata/file.lb" instead of just "testdata/file.lb".
type Expression ¶
type Expression struct {
// Line is the line number where the expression body starts (1-indexed).
// This is the line number of the command or blank line, not the preceding comments.
Line int
// Comment contains any leading comment lines (including the leading '#')
// and blank lines that immediately preceded this expression.
// Each line is terminated with a newline when present.
Comment string
// Name is the command name of the expression,
// which is the first word of the command line.
Name string
// Body is everything in the expression after the command name,
// including continuation lines, each without their leading tab.
Body string
}
Expression represents a line-based expression consisting of an optional command with continuation lines, preceded by zero or more comment lines.
func (Expression) ParseArgs ¶
func (e Expression) ParseArgs(n int) Args
ParseArgs splits the tail into at most n whitespace-separated arguments. The final argument contains any remaining text after the first n-1 splits. Returns empty if n is zero or the tail is empty.
type ExpressionError ¶
type ExpressionError struct {
Expanded // The expression where the error occurred.
Err error // The error.
}
ExpressionError reports an error that occurred while expanding or executing a linebased expression along with the expression that caused it.
func (*ExpressionError) Error ¶
func (e *ExpressionError) Error() string
Error reports the error message prefixed with the expression location.
func (*ExpressionError) Unwrap ¶
func (e *ExpressionError) Unwrap() error
type SyntaxError ¶
type SyntaxError struct {
Line int // line number (1-indexed)
Message string // error message without line prefix
Err error // underlying error, if any
}
SyntaxError represents a syntax error in the input.
func (*SyntaxError) Error ¶
func (e *SyntaxError) Error() string
func (*SyntaxError) Unwrap ¶
func (e *SyntaxError) Unwrap() error
Directories
¶
| Path | Synopsis |
|---|---|
|
Package checks provides helpers for checking values in linebased scripts.
|
Package checks provides helpers for checking values in linebased scripts. |
|
cmd
|
|
|
linebased
command
Command linebased provides tooling for linebased files.
|
Command linebased provides tooling for linebased files. |