diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..6cde7a0 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,6 @@ +- id: gwp-basis-check + name: Check labelled GWP values + entry: python3 gwp_basis_check_hook.py + language: system + pass_filenames: true + files: \.(?:py|ts|js|json|md|html?|csv|tsv|ya?ml)$ diff --git a/README.md b/README.md index 180e012..1ce0115 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,36 @@ python3 gwp_basis_check.py --self-test Exit codes: `0` nothing found · `1` findings · `2` nothing was checkable. +### Run it with pre-commit + +Add this repository to a project's `.pre-commit-config.yaml`: + +```yaml +- repo: https://github.com/greencalculus/gwp-basis-check + rev: v1 + hooks: + - id: gwp-basis-check +``` + +The hook considers common source, table and documentation formats. A finding +blocks the commit; `NOT CHECKED` does not, because most ordinary files do not +contain a labelled GWP table. + +### Run it in GitHub Actions + +Use the composite action with one or more space-separated paths or glob +patterns: + +```yaml +- uses: greencalculus/gwp-basis-check@v1 + with: + paths: 'src/**/*.py data/*.csv' +``` + +As with the hook, unmatched or unreadable shapes remain visibly `NOT CHECKED` +without failing unrelated CI; a finding or a genuine runtime failure still +fails the step. + ## What file formats does it read? - a record carrying a gas name plus `ar4`/`ar5`/`ar6` fields — JSON, TypeScript, Python, SQL seed rows diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..97c6046 --- /dev/null +++ b/action.yml @@ -0,0 +1,39 @@ +name: GWP basis check +description: Check labelled GWP values against the IPCC report they name. + +inputs: + paths: + description: Space-separated file paths or glob patterns to check. + required: false + default: "**/*.{py,ts,js,json,md,html,csv,tsv,yml,yaml}" + +runs: + using: composite + steps: + - name: Check labelled GWP values + shell: bash + run: | + shopt -s globstar nullglob + files=() + for pattern in ${{ inputs.paths }}; do + matches=( $pattern ) + files+=( "${matches[@]}" ) + done + + status=2 + if (( ${#files[@]} )); then + set +e + python3 "$GITHUB_ACTION_PATH/gwp_basis_check.py" "${files[@]}" + status=$? + set -e + else + echo "NOT CHECKED — no paths matched the supplied patterns." + fi + + # 2 is an expected no-match result, not a failed CI check. Preserve + # other non-zero statuses so a genuine action/runtime failure is not + # mistaken for a clean scan. + if [[ "$status" -eq 0 || "$status" -eq 2 ]]; then + exit 0 + fi + exit "$status" diff --git a/gwp_basis_check.py b/gwp_basis_check.py index c8b37d3..cb6f061 100644 --- a/gwp_basis_check.py +++ b/gwp_basis_check.py @@ -415,6 +415,7 @@ def check_reference(): def self_test(): import tempfile, os + from gwp_basis_check_hook import hook_exit_code ok = True print("REFERENCE INTEGRITY") errs = check_reference() @@ -436,6 +437,10 @@ def self_test(): ok &= good print(f" [{'PASS' if good else 'FAIL'}] {label[:58]:60} " f"{'got ' + str(sorted(types)) if types else 'clean'}") + hook_good = (hook_exit_code(0) == 0 and hook_exit_code(1) == 1 + and hook_exit_code(2) == 0) + ok &= hook_good + print(f" [{'PASS' if hook_good else 'FAIL'}] pre-commit permits NOT CHECKED results") print(f"\nreference {REF.get('version')} · reports {'/'.join(b.upper() for b in BASES)}") print("ALL PASS" if ok else "FAILURES ABOVE") return 0 if ok else 1 diff --git a/gwp_basis_check_hook.py b/gwp_basis_check_hook.py new file mode 100644 index 0000000..06981bf --- /dev/null +++ b/gwp_basis_check_hook.py @@ -0,0 +1,19 @@ +"""The pre-commit entry point: findings block, unreadable files do not.""" + +import sys + +from gwp_basis_check import main + + +def hook_exit_code(status): + """Map the checker result to the pre-commit contract. + + Exit 2 means a file was not in a form this checker can read. That is an + expected result when a broad pre-commit file filter sees an ordinary source + file, so it must not reject the commit. Findings (1) still block it. + """ + return 0 if status == 2 else status + + +if __name__ == "__main__": + sys.exit(hook_exit_code(main()))