Skip to content

Latest commit

 

History

History
472 lines (358 loc) · 13.1 KB

EXAMPLES.md

File metadata and controls

472 lines (358 loc) · 13.1 KB

exform cookbook

A gallery of copy-paste recipes. Every command on this page is run in CI-style verification before release — the output shown is the real output.

New to exform? Read the README first. The one rule to remember: one example is often enough; two removes ambiguity. If exform warns that it had to hardcode part of your input, add a second example that varies that part.

Each recipe shows the program exform inferred (--dry-run) so you can see there's no black box. Jump to a section:


Names & people

"First Last" → "Last, Initial."

$ printf 'John Smith\nGrace Hopper\nAlan Turing\n' | exform \
    -e 'John Smith  => Smith, J.' -e 'Grace Hopper => Hopper, G.'
Smith, J.
Hopper, G.
Turing, A.

"Last, First" → "First Last"  ·  field(ws,1) + ' ' + field(,,0)

$ printf 'Smith, John\nDoe, Jane\n' | exform \
    -e 'Smith, John => John Smith' -e 'Doe, Jane => Jane Doe' -q
John Smith
Jane Doe

Initials  ·  needs two examples so nothing is memorised

$ printf 'Grace Hopper\nAlan Turing\n' | exform \
    -e 'Grace Hopper => G.H.' -e 'Alan Turing => A.T.' -q
G.H.
A.T.

Acronym / initials that generalize to any number of words  ·  line.acronym

Give examples of different lengths and exform folds over every word instead of memorising a fixed number of fields:

$ printf 'Ada King Lovelace\nCher\nJohn Ronald Reuel Tolkien\n' | exform \
    -e 'John Ronald Tolkien => JRT' -e 'Alan Turing => AT' -q
AKL
C
JRRT

Corporate username from a name  ·  line.first_ + field(ws,1).lower

$ printf 'Grace Hopper\nAlan Turing\n' | exform \
    -e 'John Smith => jsmith' -e 'Ada Lovelace => alovelace' -q
ghopper
aturing

Just the first name  ·  field(ws,0)  ·  just the last  ·  field(ws,-1)

$ printf 'John Smith\nGrace Hopper\n' | exform -e 'John Smith => John'
John
Grace

Numbers & IDs

Extract the number from noisy text  ·  one example is enough

$ printf 'Order #12345 shipped\nOrder #42 shipped\n' | exform -e 'Order #12345 shipped => 12345'
12345
42

Add thousands separators  ·  line.group,  ·  like a spreadsheet

$ printf '1234567\n89012\n42\n' | exform -e '1234567 => 1,234,567'
1,234,567
89,012
42

Money-style: prefix a $ and group  ·  '$' + line.group,

$ printf '1234567\n89012\n' | exform -e '1234567 => $1,234,567' -q
$1,234,567
$89,012

Group a number buried in text

$ printf 'Total: 1234567 units\n' | exform -e 'Total: 1234567 units => 1,234,567'
1,234,567

Zero-pad IDs to a fixed width  ·  line.zpad3

$ printf '7\n42\n1000\n' | exform -e '7 => 007'
007
042
1000

Text case & slugs

Title-case a line  ·  line.title  ·  UPPERCASE  ·  line.upper

$ printf 'hello world\nfoo bar baz\n' | exform -e 'hello world => Hello World'
Hello World
Foo Bar Baz

Slugify for URLs/anchors  ·  line.slug  ·  works for any number of words

$ printf 'Hello World\nMy Post: Part 2\nQuick Brown Fox Jumps\n' | exform -e 'Hello World => hello-world'
hello-world
my-post-part-2
quick-brown-fox-jumps

snake_case → kebab-case  ·  (also line.snake to go the other way)

$ printf 'my_var_name\nfoo_bar\n' | exform -e 'my_var_name => my-var-name'
my-var-name
foo-bar

snake_case → camelCase  ·  line.camel  ·  folds over any number of words

Two examples of different word counts remove ambiguity; exform reshapes each word rather than memorising a fixed number of fields (line.pascal gives MyVarName). The word splitter accepts snake_case, kebab-case, spaced and camelCase input alike.

$ printf 'my_var_name\nhttp_request_id\nx\n' | exform \
    -e 'my_var_name => myVarName' -e 'foo_bar => fooBar' -q
myVarName
httpRequestId
x

camelCase → snake_case  ·  line.snakecase  ·  the reverse direction, splits on case boundaries

The same word splitter runs backwards: camelCase / PascalCase / ACRONYM input folds to lower-cased snake_case (line.snakecase), kebab-case (line.kebabcase), or spaced Title Case (line.titlecase).

$ printf 'firstName\nuserId\ngetHTTPResponse\n' | exform \
    -e 'myVariableName => my_variable_name' -e 'firstName => first_name' -q
first_name
user_id
get_http_response

Web & files

Domain from a URL  ·  field(/,2)

$ printf 'https://example.com/x\nhttp://foo.org/y\n' | exform \
    -e 'https://example.com/x => example.com' -e 'http://foo.org/y => foo.org'
example.com
foo.org

Username from an email  ·  field(@,0)

$ printf 'jane.doe@corp.com\nbob.lee@corp.com\n' | exform -e 'jane.doe@corp.com => jane.doe'
jane.doe
bob.lee

Lowercase a whole email  ·  line.lower

$ printf 'JANE@X.COM\nBob@Y.com\n' | exform -e 'JANE@X.COM => jane@x.com'
jane@x.com
bob@y.com

Drop a file extension  ·  two examples pin the multi-dot case

$ printf 'report.pdf\nphoto.jpeg\n' | exform \
    -e 'report.pdf => report' -e 'photo.jpeg => photo' -q
report
photo

Strip a leading @ from handles  ·  two examples so _ is preserved

$ printf '@alice\n@bob_dev\n' | exform -e '@alice => alice' -e '@bob_dev => bob_dev' -q
alice
bob_dev

CSV, columns & key/values

Reorder / relabel columns  ·  two examples pin down which fields move

$ printf '2021,apple,5\n2022,pear,9\n' | exform \
    -e '2021,apple,5 => apple: 5' -e '2022,pear,9 => pear: 9'
apple: 5
pear: 9

Swap two space-separated columns

$ printf 'a b\nc d\n' | exform -e 'a b => b a' -e 'c d => d c' -q
b a
d c

Value from key=value  ·  field(=,1)

$ printf 'name=jane\ncity=oslo\n' | exform -e 'name=jane => jane'
jane
oslo

Dates

ISO → US date, keep it simple

$ printf '2021-05-01\n2022-12-31\n' | exform \
    -e '2021-05-01 => 05/01/2021' -e '2022-12-31 => 12/31/2022' -q
05/01/2021
12/31/2022

Reformat a date and drop a field from a log line

$ printf '2021-05-01 ERROR boom\n2022-12-31 WARN cold\n' | exform \
    -e '2021-05-01 ERROR boom => 01/05/2021 boom' \
    -e '2022-12-31 WARN cold => 31/12/2022 cold'
01/05/2021 boom
31/12/2022 cold

Wrapping & templating

Wrap each line in an HTML tag

$ printf 'hi\nyo\n' | exform -e 'hi => <li>hi</li>' -e 'yo => <li>yo</li>' -q
<li>hi</li>
<li>yo</li>

Turn URLs into Markdown links

$ printf 'https://a.com\nhttps://b.org\n' | exform \
    -e 'https://a.com => [a.com](https://a.com)' \
    -e 'https://b.org => [b.org](https://b.org)' -q
[a.com](https://a.com)
[b.org](https://b.org)

Quote a CSV field

$ printf 'a,1\nb,2\n' | exform -e 'a,1 => "a"' -e 'b,2 => "b"' -q
"a"
"b"

In-line edits (--in-line) — change only what differs

These leave the rest of each line untouched, like sed but by example.

Reformat a date inside a line

$ printf 'commit 2021-03-05 ok\npush 1999-12-31 x\n' \
    | exform --in-line -e 'commit 2021-03-05 ok => commit 2021/03/05 ok'
commit 2021/03/05 ok
push 1999/12/31 x

Uppercase a keyed value, nothing else

$ printf 'level=info a\nlevel=warn b\n' \
    | exform --in-line -e 'level=info a => level=INFO a' -e 'level=warn b => level=WARN b'
level=INFO a
level=WARN b

Wrap a word in brackets

$ printf 'see foo here\nsee bar there\n' \
    | exform --in-line -e 'see foo here => see <foo> here' -e 'see bar there => see <bar> there'
see <foo> here
see <bar> there

Rewrite every match on a line (--all)

$ printf 'a 1 2 3 b\n' | exform --in-line --all -e 'a 1 b => a [1] b'
a [1] [2] [3] b

Reshape one column of a CSV/TSV (--field)

$ printf 'id,date,amt\n1,2021-05-01,100\n2,2022-12-31,250\n' \
    | exform --field 2 -e '2021-05-01 => 05/01/2021' -e '2022-12-31 => 12/31/2022'
id,date,amt
1,05/01/2021,100
2,12/31/2022,250

Only column 2 changes; the other columns (and the header) are left exactly as they were. Use --field-sep $'\t' for TSV, and --field 2,4 to reshape several columns at once.

$ printf 'name\tcity\nJOHN\tnyc\nJANE\tla\n' \
    | exform --field 1 --field-sep $'\t' -e 'JOHN => John' -e 'JANE => Jane'
name	city
John	nyc
Jane	la

Emit a standalone, dependency-free script (--emit python)

$ exform -e 'my_var_name => myVarName' -e 'home_address => homeAddress' --emit python > camelize.py
$ printf 'deep_nested_key\n' | python3 camelize.py
deepNestedKey

The generated camelize.py has no dependency on exform — commit it and run it anywhere Python is available. exform verifies the script reproduces your examples before printing it.

Emit a portable awk one-liner (--emit awk)  ·  for the syntax nobody remembers

$ exform -e '2021,apple => apple,2021' -e '2022,pear => pear,2022' --emit awk
# text transform generated by exform (https://github.com/ingrid-owusu/exform)
# inferred program: field(,,1) + ',' + field(,,0)
# reads stdin, writes stdout; lines missing an expected field are printed best-effort
awk -F, '{ print $2 "," $1 }'

Paste the last line into any pipeline — no exform, no Python, no install needed:

$ printf 'apple,2021\npear,2022\n' | awk -F, '{ print $2 "," $1 }'
2021,apple
2022,pear

Other transforms map to the awk idiom you'd have had to look up — last field, uppercase, zero-pad:

$ exform -e 'a/b/c => c' -e 'x/y/z => z' --emit awk | tail -1
awk -F/ '{ print $NF }'
$ exform -e 'hello => HELLO' -e 'world => WORLD' --emit awk | tail -1
awk '{ print toupper($0) }'

exform runs the generated awk against your examples and refuses to print it if the output disagrees — so a one-liner it hands you is one it has checked. When a transform can't be expressed faithfully in awk (e.g. regex extraction or word-case folding), it says so; use --emit python for those.

JSON lines — "jq by example" (--json-field)

Reshape one string field of each JSON object by example and leave every other key, nested value and type exactly as it was — no jq syntax to learn.

Fix a name field

$ printf '{"user": "Smith, John", "age": 30}\n{"user": "Doe, Jane", "age": 25}\n' \
    | exform --json-field user -e 'Smith, John => John Smith' -e 'Doe, Jane => Jane Doe'
{"user": "John Smith", "age": 30}
{"user": "Jane Doe", "age": 25}

The age field and the object structure are untouched — only the targeted string changes.

Reach into a nested field with a dotted path  ·  two examples generalise to unseen records

$ printf '{"u": {"name": "Smith, John"}}\n{"u": {"name": "Doe, Jane"}}\n{"u": {"name": "Turing, Alan"}}\n' \
    | exform --json-field u.name -e 'Smith, John => John Smith' -e 'Doe, Jane => Jane Doe'
{"u": {"name": "John Smith"}}
{"u": {"name": "Jane Doe"}}
{"u": {"name": "Alan Turing"}}

The third record was never given as an example — exform inferred the rule and applied it. Records missing the key (or where the value isn't a string) pass through unchanged; use --on-error to control genuinely untransformable values.

Rename files by example (--rename)

Pipe exform a list of paths and a couple of old => new examples; it renames the batch. Dry-run by default — add --apply to actually rename. Only the basename is transformed, so the directory is preserved.

Strip a camera prefix and lower-case the extension  ·  two examples generalise to unseen files

$ ls
DSC_1001.JPG   DSC_1002.JPG   DSC_1099.JPG

$ ls | exform --rename -e 'DSC_1001.JPG => 1001.jpg' -e 'DSC_1002.JPG => 1002.jpg'
DSC_1001.JPG  ->  1001.jpg
DSC_1002.JPG  ->  1002.jpg
DSC_1099.JPG  ->  1099.jpg
exform: 3 files would be renamed (dry-run). Re-run with --apply to perform the renames.

$ ls | exform --rename -e 'DSC_1001.JPG => 1001.jpg' -e 'DSC_1002.JPG => 1002.jpg' --apply
exform: renamed 3 files.

DSC_1099.JPG was never given as an example — exform inferred the rule and applied it. If two files would map to the same name, or a target already exists, exform refuses the whole batch without touching anything. Combine with find to rename across a tree: find . -name '*.txt' | exform --rename -e '...' --apply.


Got a recipe exform can't learn, or one worth adding? Open an issue — the maintainer (an AI agent) reads them. If a mapping is genuinely ambiguous from your examples, exform will tell you what it had to assume; add one more varied example and it usually locks on.