ktav 0.3.1

Ktav — a plain configuration format. Three rules, zero indentation, zero quoting. Serde-native.
Documentation
name: Release

# Publish the `ktav` crate to crates.io when a `v*` tag is pushed.
# The publish step only runs after the same fmt/clippy/test/build
# gauntlet that CI uses on every push — so a green tag-push run is
# proof that the contents are publish-ready.
#
# Required secret: `CARGO_REGISTRY_TOKEN` (a crates.io API token with
# the `publish-update` scope, scoped to the `ktav` crate).

on:
  push:
    tags:
      - "v*"
  # Manual override: useful if a tag was pushed before the secret
  # existed and you want to retry without moving the tag.
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to publish (e.g. v0.1.2). Must already exist."
        required: true

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: "0"

jobs:
  verify:
    name: Verify pre-publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          submodules: recursive
          ref: ${{ inputs.tag || github.ref }}

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - uses: Swatinem/rust-cache@v2

      - name: cargo fmt --check
        run: cargo fmt --all -- --check

      - name: cargo clippy
        run: cargo clippy --release --all-targets -- -D warnings

      - name: cargo test (release)
        run: cargo test --release

      - name: cargo build (release)
        run: cargo build --release

  publish:
    name: cargo publish
    needs: verify
    runs-on: ubuntu-latest
    # A protected environment lets you require manual approval on the
    # first push of any new version without re-doing the workflow.
    # Create it once in repo Settings → Environments → New env "crates-io"
    # and gate the CARGO_REGISTRY_TOKEN secret to it.
    environment: crates-io
    steps:
      - uses: actions/checkout@v5
        with:
          submodules: recursive
          ref: ${{ inputs.tag || github.ref }}

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Confirm tag matches Cargo.toml version
        shell: bash
        run: |
          # Drop the leading `v` from the tag (refs/tags/v0.1.2 → 0.1.2).
          ref="${{ inputs.tag || github.ref_name }}"
          tag="${ref#v}"
          ver=$(awk -F'"' '/^version *= *"/ { print $2; exit }' Cargo.toml)
          if [ "$tag" != "$ver" ]; then
            echo "::error::tag '$tag' does not match Cargo.toml version '$ver'"
            exit 1
          fi
          echo "Publishing ktav $ver"

      - name: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish

  github-release:
    name: GitHub Release
    needs: publish
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v5
        with:
          ref: ${{ inputs.tag || github.ref }}

      - name: Extract release notes from CHANGELOG.md
        id: notes
        shell: bash
        run: |
          ref="${{ inputs.tag || github.ref_name }}"
          ver="${ref#v}"
          # Pull the section between `## [<ver>]` and the next `## [`,
          # stripping the leading marker and the trailing blank line.
          notes=$(awk -v v="$ver" '
            $0 ~ "^## \\[" v "\\]" { in_section = 1; next }
            in_section && /^## \[/ { exit }
            in_section { print }
          ' CHANGELOG.md)
          # GitHub Actions multiline output via heredoc.
          {
            echo "body<<__END_NOTES__"
            echo "$notes"
            echo "__END_NOTES__"
          } >> "$GITHUB_OUTPUT"

      - name: Create / update GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ inputs.tag || github.ref_name }}
          name: ${{ inputs.tag || github.ref_name }}
          body: ${{ steps.notes.outputs.body }}
          # Don't fail the release just because there's no asset to
          # attach — the rust crate doesn't ship binaries from this
          # repo (those live in each binding's release flow).
          fail_on_unmatched_files: false