Skip to content

Rewrite the transformer: real shell parsing, full operator support, no dependencies (0.4.0) - #11

Merged
jaykv merged 1 commit into
mainfrom
overhaul
Oct 12, 2026
Merged

jaykv merged 1 commit into
mainfrom
overhaul

Conversation

@jaykv

@jaykv jaykv commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Summary

Rewrites the transformer around a real shell lexer and a small runtime helper, replacing the string surgery on tokenized lines. Adds full operator support, fixes a set of correctness bugs, and removes both dependencies. Motivated by cliffy, which generates CLIs from $ / > lines in YAML run blocks.

Bugs fixed

  • A quote or backslash in an argument produced invalid or silently different Python: 'say "hi"' was a syntax error and 'a\nb' became a real newline.
  • Python strings that contain = $ or ($ (for example "total = $5") were rewritten as commands.
  • a = $x | y assigned a Popen object instead of the output, and pipes leaked cmd1 / fout variables, open files and unfinished processes.
  • && raised NotImplementedError; operators only worked with spaces around them.
  • > shell commands lost their quoting before the shell saw them.
  • A trailing # comment was passed to the command as arguments.
  • python -m pybash crashed with no arguments and never ran a script as __main__.

New

  • Operators in $ commands: | |& && || ; < > >> 2> 2>> &> &>> 2>&1 >&2, with or without spaces. Background &, here-documents and other file descriptors raise a clear error that points to > shell mode.
  • $(command) capture, several captures per line, and capture after return / +=.
  • Trailing-backslash line continuation. One output line per input line, so tracebacks keep the original line numbers.
  • {{ expr }} accepts any expression, is converted with str() and is always exactly one argument; \{{ and \f{ write the markers literally.
  • Interpolated values in > shell lines are quoted for where they sit (bare, inside "...", inside '...'), so a value cannot become shell syntax. {{! expr }} opts out.
  • PybashSyntaxError (a SyntaxError) with filename, lineno, offset and text of the original line.
  • pybash command with --show and -c; an import hook built on importlib.
  • pybash.header() / runtime_source(), so a host can emit code that imports the runtime helper or carries a pasted copy and needs no pybash at run time.

Project

  • Python 3.11-3.15, zero dependencies (ideas and token-utils are gone), typed (mypy --strict), ruff.
  • poetry / flake8 / black / isort replaced by hatchling + uv. CI matrix on ubuntu and macOS; publishing via PyPI trusted publishing.
  • Version 0.4.0.

Behaviour changes

All listed under "Upgrading from 0.3" in the README. The main ones:

  • Arguments reach the command exactly as a shell would pass them, so code that relied on the old backslash handling (such as $echo \\n... printing a newline) changes.
  • Interpolated values in > lines are quoted by default.
  • $a > f1 > f2 writes the output to both files; before, the second file received a copy of the first.
  • Generated lists read ["ls", "-la"] and the output no longer gains a trailing newline.
  • Python 3.11 is the new floor.

Maintainer action

  • Configure a PyPI trusted publisher for this repo (workflow publish.yml, environment pypi); the old token-based workflow is removed.
  • cliffy's next release depends on 0.4.0 being on PyPI.

How verified

  • 55 tests (transform expectations plus tests that execute the generated code, the import hook and the CLI) pass locally on Python 3.11, 3.12, 3.13, 3.14 and 3.15 on macOS.
  • ruff format --check, ruff check and mypy --strict are clean.
  • cliffy's own test suite (188 tests at the time) passes against this branch with no cliffy changes.
  • The GitHub workflows have not run before this PR.

🤖 Generated with Claude Code

…o dependencies

The transformer is rebuilt around a shell lexer and a small runtime instead of
string surgery on tokenized lines.

Bugs fixed
- A quote or backslash in an argument produced invalid or silently different
  Python ('a\nb' became a newline, 'say "hi"' was a syntax error).
- Python strings containing "= $" or "($" were rewritten as commands.
- `a = $x | y` assigned a Popen object and leaked cmd1/fout variables; pipes
  left processes and file handles open.
- `&&` raised NotImplementedError; operators needed surrounding spaces.
- `>` shell commands lost their quoting before reaching the shell.
- A trailing `# comment` was passed to the command as arguments.
- `python -m pybash` with no arguments crashed and never ran scripts as __main__.

New
- Operators: && || ; |& 2> 2>> &> &>> 2>&1 >&2, with or without spaces.
- `$(command)` capture, several captures per line, capture after return/+=.
- Backslash line continuation; one output line per input line.
- `{{ expr }}` accepts any expression and is str()-converted; `\{{` escapes it.
- Interpolation in `>` commands is quoted for its context; `{{! expr }}` opts out.
- PybashSyntaxError with filename/lineno/offset/text of the original line.
- `pybash` command with --show and -c; import hook without the ideas package.
- header()/runtime_source() so hosts can emit code with or without a pybash
  runtime dependency.

Project
- Python 3.11-3.15, zero dependencies, typed (mypy strict), ruff.
- poetry/flake8/black/isort replaced by hatchling + uv; CI matrix and PyPI
  trusted publishing.
- Version 0.4.0. See "Upgrading from 0.3" in the README for behavior changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Repository owner deleted a comment from sourcery-ai Bot Oct 12, 2026
@jaykv
jaykv merged commit ac900b5 into main Oct 12, 2026
10 checks passed
@jaykv
jaykv deleted the overhaul branch October 12, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant