2026-04-06 09:22:44 -05:00
|
|
|
`str` : An interactive string manipulation environment
|
|
|
|
|
|
|
|
|
|
Copyright (C) 2026 Garrett Mills <shout@garrettmills.dev>
|
|
|
|
|
|
|
|
|
|
## Syntax Overview
|
|
|
|
|
|
|
|
|
|
String manipulation operations are performed on a "subject" -- a given string you are working on.
|
|
|
|
|
|
|
|
|
|
Most of the time, you do this by pasting a string into the interpreter, then running a series of
|
|
|
|
|
statements that make some changes to the string. Here's an example where a few lines of a TSV file
|
|
|
|
|
have been pasted in as the subject:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
str %> paste
|
|
|
|
|
┌───────────────
|
|
|
|
|
│ 0 │Mazda RX4 21 6 160 110 3.9 2.62 16.46 0 1 4 4
|
|
|
|
|
│ 1 │Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
|
|
|
|
|
│ 2 │Datsun 710 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1
|
|
|
|
|
│ 3 │Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
|
|
|
|
|
│ 4 │Hornet Sportabout 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2
|
|
|
|
|
├───────────────
|
|
|
|
|
│ :: string
|
|
|
|
|
└───────────────
|
|
|
|
|
str %>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Statements in `str` take the form of `<command> [...args]`. For a trivial example, here we use a
|
|
|
|
|
command to replace all instances of `Hornet` with `Foobar`:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
str %> replace Hornet Foobar
|
|
|
|
|
┌───────────────
|
|
|
|
|
│ 0 │Mazda RX4 21 6 160 110 3.9 2.62 16.46 0 1 4 4
|
|
|
|
|
│ 1 │Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
|
|
|
|
|
│ 2 │Datsun 710 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1
|
|
|
|
|
│ 3 │Foobar 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
|
|
|
|
|
│ 4 │Foobar Sportabout 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2
|
|
|
|
|
├───────────────
|
|
|
|
|
│ :: string
|
|
|
|
|
└───────────────
|
|
|
|
|
str %>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notice that I did not have to quote the strings `Hornet` and `Foobar` because they do not contain any
|
|
|
|
|
special syntax characters. The only special syntax characters in `str` are single quotes themselves (`'`),
|
|
|
|
|
whitespace, and opening parens (`(`).
|
|
|
|
|
|
|
|
|
|
Commands are separated either by newlines (pressing enter), or using the `;` separator. For example:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
┌───────────────
|
|
|
|
|
│ 0 │foo bar baz
|
|
|
|
|
├───────────────
|
|
|
|
|
│ :: string
|
|
|
|
|
└───────────────
|
|
|
|
|
str %> replace oo OO; replace ba BA
|
|
|
|
|
┌───────────────
|
|
|
|
|
│ 0 │fOO BAr BAz
|
|
|
|
|
├───────────────
|
|
|
|
|
│ :: string
|
|
|
|
|
└───────────────
|
|
|
|
|
str %>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Data Types
|
|
|
|
|
|
|
|
|
|
- `:: string` - Strings. Does what it says on the tin.
|
|
|
|
|
- `:: int` - Base-10 integers (e.g. for use as offsets).
|
|
|
|
|
- `:: destructured` - A list split by some value (e.g. `foo::bar` split on `::`).
|
|
|
|
|
- These differ from normal lists in that they keep track of their "connective tissue".
|
|
|
|
|
- For example, `foo::bar` split on `::` will give you `[foo, bar]`, but will remember that `bar` is prefixed with `::`
|
|
|
|
|
- This allows you to `join` the destructured back together later on
|
|
|
|
|
- `:: ... :: ()` - Lambdas. Multi-parameter functions. See "Working with Lambdas" below.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Available Commands
|
|
|
|
|
|
|
|
|
|
### I/O & Editor Control
|
|
|
|
|
|
|
|
|
|
#### `exit`
|
|
|
|
|
Stop `str` and exit. This can also be done with `^C`.
|
|
|
|
|
|
|
|
|
|
#### `paste`
|
|
|
|
|
Replace the current subject with the current contents of your clipboard.
|
|
|
|
|
|
|
|
|
|
#### `copy`
|
|
|
|
|
Copy the current subject to your clipboard.
|
|
|
|
|
|
|
|
|
|
#### `infile <path>`
|
|
|
|
|
Replace the current subject with the contents of the given file.
|
|
|
|
|
Example: `infile ~/foo.txt`
|
|
|
|
|
|
|
|
|
|
#### `outfile <path>`
|
|
|
|
|
Write the current subject to the given file (replacing its current contents).
|
|
|
|
|
Example: `outfile ~/foo.txt`
|
|
|
|
|
|
|
|
|
|
#### `assign <value>` / `= <value>`
|
|
|
|
|
Replace the current context with the given value.
|
|
|
|
|
Example: `= 'foo bar'` -> `foo bar`
|
|
|
|
|
Example: `= $a` -> (value of `$a`)
|
|
|
|
|
|
|
|
|
|
#### `clear`
|
|
|
|
|
Clear the current subject (replace it with an empty string).
|
|
|
|
|
|
|
|
|
|
#### `show`
|
|
|
|
|
Print the current subject. This is done by default after every operation.
|
|
|
|
|
|
|
|
|
|
#### `undo`
|
|
|
|
|
Undo the last statement/operation.
|
|
|
|
|
|
|
|
|
|
#### `redo`
|
|
|
|
|
Redo the last undone statement/operation.
|
|
|
|
|
|
|
|
|
|
#### `history`
|
|
|
|
|
Show a log of recent statements/operations.
|
|
|
|
|
|
|
|
|
|
#### `edit`
|
|
|
|
|
Open the current subject in an external text editor (specified by `$EDITOR`).
|
|
|
|
|
Once you save and close the editor, the modified contents will be loaded back into `str`.
|
|
|
|
|
A crutch.
|
|
|
|
|
|
|
|
|
|
#### `save [<statefile>]`
|
|
|
|
|
Save the current state of the `str` session to the given statefile (default `~/.str.json`).
|
|
|
|
|
|
|
|
|
|
#### `load [<statefile>]`
|
|
|
|
|
Restore a `str` session from a saved state file (default `~/.str.json`).
|
|
|
|
|
|
|
|
|
|
#### `runfile <path>`
|
|
|
|
|
Execute the contents of the given file as a series of `str` commands.
|
|
|
|
|
|
2026-04-09 18:00:59 -05:00
|
|
|
#### `script <path> [...<args>]`
|
|
|
|
|
Execute an external script and replace the current subject with the returned output.
|
|
|
|
|
The current subject is passed as the first argument, followed by the optional additional `args`.
|
|
|
|
|
|
2026-04-06 09:22:44 -05:00
|
|
|
|
|
|
|
|
### Basic String Operations
|
|
|
|
|
|
|
|
|
|
#### `enclose [<with>]`
|
|
|
|
|
Wrap the string on either side with the given string (default = `(`).
|
|
|
|
|
If the string is one half of a matching pair (e.g. `(`, `[`, `{`, &c), surround with the matching pair.
|
|
|
|
|
Example: `foo` -> `enclose [` -> `[foo]`
|
|
|
|
|
|
|
|
|
|
#### `lower`
|
|
|
|
|
Convert the string to lower-case.
|
|
|
|
|
Example: `FoO` -> `lower` -> `foo`
|
|
|
|
|
|
|
|
|
|
#### `upper`
|
|
|
|
|
Convert the string to upper-case.
|
|
|
|
|
Example: `FoO` -> `upper` -> `FOO`
|
|
|
|
|
|
|
|
|
|
#### `lsub <offset> [<length>]`
|
|
|
|
|
Take a left-substring starting at the given `offset`. Stop after the given `length`, if provided.
|
|
|
|
|
Example: `abcdef` -> `lsub 1 3` -> `bcd`
|
|
|
|
|
|
|
|
|
|
#### `rsub <offset> [<length>]`
|
|
|
|
|
Take a right-substring starting at the given `offset`. Stop after the given `length`, if provided.
|
|
|
|
|
Example: `abcdef` -> `rsub 1 3` -> `cde`
|
|
|
|
|
|
|
|
|
|
#### `prefix <with>`
|
|
|
|
|
Prefix the current subject with the given string.
|
|
|
|
|
Example: `bar` -> `prefix foo` -> `foobar`
|
|
|
|
|
|
|
|
|
|
#### `suffix <with>`
|
|
|
|
|
Suffix the current subject with the given string.
|
|
|
|
|
Example: `foo` -> `suffix bar` -> `foobar`
|
|
|
|
|
|
|
|
|
|
#### `quote [<with>]`
|
|
|
|
|
Quote the string with the given quotemark (default `'`).
|
|
|
|
|
If the string is already quoted with a standard quotemark, strip it off first.
|
|
|
|
|
Example: `'bar'` -> `quote "` -> `"bar"`
|
|
|
|
|
|
|
|
|
|
#### `unquote [<with>]`
|
|
|
|
|
Unquote the string if it is quoted with a standard quotemark (or `with`, if provided explicitly).
|
|
|
|
|
Example: `"foo"` -> `unquote` -> `foo`
|
|
|
|
|
|
|
|
|
|
#### `replace <find> <with>`
|
|
|
|
|
Replace all instances of `find` with the given `with`.
|
|
|
|
|
Example: `foobaz` -> `replace baz bar` -> `foobar`
|
|
|
|
|
|
|
|
|
|
#### `rev`
|
|
|
|
|
Reverse the given string (or destructured list).
|
|
|
|
|
Example: `abcdef` -> `rev` -> `fedcba`
|
|
|
|
|
|
|
|
|
|
#### `trim [<type>] [<char>]`
|
|
|
|
|
Trim leading/trailing instances of `char` from the subject (by default, any whitespace).
|
|
|
|
|
By default, `trim` uses the `both` type, but supports the following modes:
|
|
|
|
|
|
|
|
|
|
- `start`/`left` - only from the start of the string
|
|
|
|
|
- `end`/`right` - only from the end of the string
|
|
|
|
|
- `both` - from both start and end of the string
|
|
|
|
|
- `lines` - remove any empty lines
|
|
|
|
|
|
|
|
|
|
Example: ` foo ` -> `trim left` -> `foo `
|
|
|
|
|
Example: `fffubarfff` -> `trim both f` -> `ubar`
|
|
|
|
|
|
|
|
|
|
#### `indent <space|tab> [<level>]`
|
|
|
|
|
Reindent the current subject with tabs or spaces.
|
|
|
|
|
If a `level` is provided, adjust the indentation to that level.
|
|
|
|
|
Example: ` word` -> `indent tab` -> `\tword`
|
|
|
|
|
|
|
|
|
|
#### `concat [...<strings>]`
|
|
|
|
|
Replace the current subject with the result of concatenating the provided strings together.
|
|
|
|
|
Mostly useful in conjunction with variables.
|
|
|
|
|
Example: `concat foo bar` -> `foobar`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Multi-part Manipulation (line-wise, word-wise, destructured)
|
|
|
|
|
|
|
|
|
|
#### `line ...<subcommand>`
|
|
|
|
|
Execute the given `subcommand` on every line of the subject.
|
|
|
|
|
Example: `foo\nbar` -> `line prefix --` -> `--foo\n--bar`
|
|
|
|
|
|
|
|
|
|
#### `word ...<subcommand>`
|
|
|
|
|
Execute the given `subcommand` on every word of the subject, preserving whitespace.
|
|
|
|
|
Example: `foo bar baz` -> `word prefix --` -> `--foo --bar --baz`
|
|
|
|
|
|
|
|
|
|
#### `each ...<subcommand>`
|
|
|
|
|
Execute the given `subcommand` on every part of a destructured subject.
|
|
|
|
|
Example: `[foo, bar, baz]` -> `each prefix A` -> `[Afoo, Abar, Abaz]`
|
|
|
|
|
|
|
|
|
|
#### `on <word|line|index> <index> ...<subcommand>`
|
|
|
|
|
Execute the given subcommand on the specified word/line/index from the current subject.
|
|
|
|
|
`word` and `line` apply to strings that have not been destructured.
|
|
|
|
|
`index` is applied for destructured subjects. For destructured subjects you may omit the type (e.g. `on 4 ...`).
|
|
|
|
|
Example: `foo bar baz` -> `on word 1 rsub 1` -> `foo ba baz`
|
|
|
|
|
|
|
|
|
|
#### `drop <word|line|index> <index>`
|
|
|
|
|
Delete the specified word/line/index from the current subject.
|
|
|
|
|
`word` and `line` apply to strings that have not been destructured.
|
|
|
|
|
`index` is applied for destructured subjects. For destructured subjects you may omit the type (e.g. `drop 4`).
|
|
|
|
|
Example: `foo bar baz` -> `drop word 1` -> `foo baz`
|
|
|
|
|
|
|
|
|
|
#### `contains <find>`
|
|
|
|
|
If the subject contains the given substring, keep it. Otherwise, replace it with an empty string.
|
|
|
|
|
Most often used in conjunction with `line`, `word`, or `each` for filtering.
|
|
|
|
|
Example: `afoo \n bfoo \n cbar` -> `line contains foo` -> `afoo \n bfoo \n `
|
|
|
|
|
|
|
|
|
|
#### `missing <find>`
|
|
|
|
|
If the subject contains the given substring, replace it with an empty string. Otherwise, keep it.
|
|
|
|
|
Most often used in conjunction with `line`, `word`, or `each` for filtering.
|
|
|
|
|
Example: `afoo \n bfoo \n cbar` -> `line missing foo` -> `\n \n cbar`
|
|
|
|
|
|
|
|
|
|
#### `lines`
|
|
|
|
|
Destructure the current subject into individual lines.
|
|
|
|
|
Example: `foo \n bar` -> `lines` -> `[foo, bar]`
|
|
|
|
|
|
|
|
|
|
#### `words`
|
|
|
|
|
Destructure the current subject into individual words.
|
|
|
|
|
Example: `foo bar \n baz` -> `words` -> `[foo, bar, baz]`
|
|
|
|
|
|
|
|
|
|
#### `split <on> [<limit>]`
|
|
|
|
|
Destructure the current subject based on the given delimiter.
|
|
|
|
|
If a `limit` is provided, split the subject no more than the given number of times.
|
|
|
|
|
Example: `foo::bar::baz::ban` -> `split :: 2` -> `[foo, bar, baz::ban]`
|
|
|
|
|
|
|
|
|
|
#### `chunk <every> <line|word|char>`
|
|
|
|
|
Destructure the current subject into chunks based on `every` Nth line/word/character.
|
|
|
|
|
Example: `a b c d e f` -> `chunk 2 word` -> `[a b, c d, e f]`
|
|
|
|
|
|
|
|
|
|
#### `join [<on>]`
|
|
|
|
|
Join the current destructured subject back together using the given delimiter.
|
|
|
|
|
If no delimiter is provided, it will preserve the existing delimiters between substrings.
|
|
|
|
|
If string is not destructured, joins the lines in the string.
|
|
|
|
|
Example: `[foo, bar]` -> `join ::` -> `foo::bar`
|
|
|
|
|
|
|
|
|
|
#### `sort [<asc|desc>]`
|
|
|
|
|
Sort the items in the destructured subject alphabetically either `asc`ending (default) or `desc`ending.
|
|
|
|
|
If string is not destructured, sorts the lines in the string.
|
|
|
|
|
Example: `[foo, bar]` -> `sort` -> `[bar, foo]`
|
|
|
|
|
|
|
|
|
|
#### `unique`
|
|
|
|
|
Filter out duplicate values from the destructured subject.
|
|
|
|
|
If string is not destructured, filters out duplicate lines in the string.
|
|
|
|
|
Example: `[foo, bar, foo]` -> `unique` -> `[foo, bar]`
|
|
|
|
|
|
|
|
|
|
#### `zip <with>`
|
|
|
|
|
Interleave the current destructured subject with the given other destructured subject.
|
|
|
|
|
Example: Say `$a` is `[1, 2, 3]` and subject is `[a, b, c]` -> `zip $a` -> `[a, 1, b, 2, c, 3]`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Working with Variables
|
|
|
|
|
|
|
|
|
|
Variables in `str` start with a dollar sign and may be alphanumeric with underscores (e.g. `$my_var`).
|
|
|
|
|
|
|
|
|
|
Assign a variable using the standard syntax: `$a = 'mystr'`
|
|
|
|
|
|
|
|
|
|
You can then use them anywhere you may use a string: `split $a`
|
|
|
|
|
|
|
|
|
|
There are also several commands for interactive with variables, defined below.
|
|
|
|
|
|
|
|
|
|
#### `to <var>`
|
|
|
|
|
Store the current subject into the given variable.
|
|
|
|
|
Example: `foo bar` -> `to $a`
|
|
|
|
|
|
|
|
|
|
#### `from <var>`
|
|
|
|
|
Replace the current subject with the value of the given variable.
|
|
|
|
|
Example: `from $a`
|
|
|
|
|
|
|
|
|
|
#### `set <var> <value>`
|
|
|
|
|
Alternative syntax for variable assignment.
|
|
|
|
|
Example: `set $a 'mystr'` is equivalent to `$a = 'mystr'`
|
|
|
|
|
|
|
|
|
|
#### `over <var> ...<subcommand>`
|
|
|
|
|
Execute the given `subcommand` against the contents of `var` instead of the current subject.
|
|
|
|
|
Example: `over $a replace foo bar`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Working with Lambdas (functions, closures, &c)
|
|
|
|
|
|
|
|
|
|
`str` supports Lambda functions. These are defined as a series of commands which may optionally
|
|
|
|
|
have some number of parameters. For example:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
$myReplacer = (|$a $b| split $a; join $b)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This trivial lambda takes two parameters, splits the subject on `$a` and joins it back together on `$b` (effectively, `replace`).
|
|
|
|
|
|
|
|
|
|
You may omit the parameter list if the lambda takes no parameters. For example, this basic TSV parser:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
$myTSVParser = (
|
|
|
|
|
trim;
|
|
|
|
|
trim lines;
|
|
|
|
|
lines;
|
|
|
|
|
each split \t
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `call <lambda> [...<params>]`
|
|
|
|
|
Execute the given lambda with the provided parameters, replacing the current context with the result.
|
|
|
|
|
Example: `call $myReplacer foo bar`
|
|
|
|
|
|
|
|
|
|
`call` operates over the current context. However, you can execute lambdas over the contents of a variable using `over`:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
str %> $a = 'foo bar'
|
|
|
|
|
str %> over $a call $myReplacer foo bar
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`call` also supports partial application. For example:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
str %> over $myFooReplacer call $myReplacer foo
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You can then call the partially-applied lambda with the remaining parameter(s):
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
str %> call $myFooReplacer bar
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Misc
|
|
|
|
|
|
|
|
|
|
#### `lipsum <num> <word|line|para>`
|
|
|
|
|
Replace the current subject with "Lorem ipsum..." placeholder text.
|
|
|
|
|
Can generate individual words, lines, or paragraphs.
|
|
|
|
|
Example: `lipsum 4 word` -> `lorem ipsum dolor sit`
|
2026-04-10 09:58:41 -05:00
|
|
|
|
|
|
|
|
#### User-specific setup files
|
|
|
|
|
The file `~/.str.rc` is automatically executed when `str` starts up.
|
|
|
|
|
You can use this to define a user-specific environment.
|