rable 0.1.14

A Rust implementation of the Parable bash parser — complete GNU Bash 5.3-compatible parsing with Python bindings
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.93.0"
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo fmt --all -- --check
      - run: cargo clippy --all-targets -- -D warnings

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.93.0"
      - uses: Swatinem/rust-cache@v2
      - run: cargo test --all-targets
      - name: Oracle compatibility report
        run: cargo test oracle_test_suite -- --nocapture 2>&1 | tail -15

  python:
    name: Python Bindings
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.93.0"
      - uses: Swatinem/rust-cache@v2
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Create venv and install maturin
        run: |
          python -m venv .venv
          .venv/bin/pip install --upgrade pip maturin
      - name: Build Python bindings
        run: .venv/bin/maturin develop
      - name: Test Python bindings
        run: |
          .venv/bin/python -c "
          from rable import parse, ParseError, MatchedPairError
          # Basic parsing
          nodes = parse('echo hello')
          assert nodes[0].to_sexp() == '(command (word \"echo\") (word \"hello\"))'
          # Error handling
          try:
              parse('if')
              assert False, 'Should have raised ParseError'
          except ParseError:
              pass
          # Extglob
          nodes = parse('echo @(a|b)', extglob=True)
          assert len(nodes) == 1
          print('Python binding tests passed')
          "
      - name: Install Parable and run compatibility test
        run: |
          .venv/bin/pip install git+https://github.com/ldayton/Parable.git
          .venv/bin/python -c "
          import sys; import rable as p; sys.modules['parable'] = p
          exec(open('tests/run_tests.py').read())
          " -- tests/parable/

  benchmark:
    name: Benchmark
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.93.0"
      - uses: Swatinem/rust-cache@v2
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Setup and benchmark
        run: |
          python -m venv .venv
          .venv/bin/pip install --upgrade pip maturin
          .venv/bin/maturin develop --release
          .venv/bin/pip install git+https://github.com/ldayton/Parable.git
          .venv/bin/python tests/benchmark.py

  check:
    name: CI Status
    if: always()
    needs: [lint, test, python]
    runs-on: ubuntu-latest
    steps:
      - name: Validate CI results
        run: |
          if [[ "${{ needs.lint.result }}" != "success" || "${{ needs.test.result }}" != "success" || "${{ needs.python.result }}" != "success" ]]; then
            echo "CI failed"
            exit 1
          fi