Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0886f2e
chore(repo): set up the php repo with its tools
MobileMage Oct 2, 2026
e1fd432
chore(repo): close gaps in the portability and coverage checks
MobileMage Oct 2, 2026
b934633
chore(repo): update the spec submodule
MobileMage Oct 2, 2026
1249829
feat(core): add stateName and precisionForAccuracy from spec data
MobileMage Oct 2, 2026
71c6f2b
test(core): cover the generator and loader failure paths
MobileMage Oct 2, 2026
8f1afc5
feat(core): add normalize with a generated NFKC table
MobileMage Oct 2, 2026
e09ccf2
fix(core): keep the Unicode notice with the NFKC table
MobileMage Oct 2, 2026
101f853
feat(core): parse forms, lengths, characters and legacy codes
MobileMage Oct 2, 2026
571c036
chore(repo): update the spec submodule
MobileMage Oct 2, 2026
b658501
test(core): pin the byte rule and two dash forms of the table
MobileMage Oct 2, 2026
218b652
feat(core): check states and segments, and suggest typo fixes
MobileMage Oct 2, 2026
4f768b2
chore(repo): update the spec submodule
MobileMage Oct 2, 2026
d16ed33
refactor(core): name segments as the glossary does
MobileMage Oct 2, 2026
9933479
feat(core): add truncate, parent, contains and redact
MobileMage Oct 2, 2026
0b6765d
docs(core): say why contains and parent work, and fix a test message
MobileMage Oct 2, 2026
b96e290
test(core): add property tests and run the mutation tests
MobileMage Oct 2, 2026
b1548f1
chore(repo): update the spec submodule
MobileMage Oct 2, 2026
ea094e1
test(core): make the property tests catch more mistakes
MobileMage Oct 2, 2026
53619de
docs(repo): write the README and record the PHP 8.1 floor
MobileMage Oct 2, 2026
5ed6524
docs(release): add the changelog entry for 0.1.0-alpha.0
MobileMage Oct 2, 2026
256fafc
ci(repo): add the CI workflows and check them with zizmor
MobileMage Oct 2, 2026
582e837
ci(deps): add the Renovate config
MobileMage Oct 2, 2026
2bce03d
fix(repo): install the alpha and keep the README to what CI checks
MobileMage Oct 2, 2026
963a6cd
test(repo): check the vector files, the licences and the API dump
MobileMage Oct 2, 2026
8a9f743
fix(repo): close the gaps in the generator and the repo checks
MobileMage Oct 2, 2026
be772a3
test(repo): run the doc examples, and tidy the tests and the words
MobileMage Oct 2, 2026
a7d67ae
test(repo): scan src once for the banned names
MobileMage Oct 2, 2026
ab82d9d
fix(repo): give the API dump one spelling on each PHP version
MobileMage Oct 2, 2026
e0645b7
fix(repo): refuse co-authors at the domains of AI companies
MobileMage Oct 2, 2026
9fa4c83
test(repo): ignore a common word in a string of the portability test
MobileMage Oct 2, 2026
7e14d54
fix(repo): check read access before each read in the dev tools
MobileMage Oct 2, 2026
734d974
chore(repo): point the spec submodule at spec main
MobileMage Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/0.1.0-alpha.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 0.1.0-alpha.0 - 2026-10-02

### Added

- Add the offline core of spec 0.1.0: parse, normalize, isLegacy, stateName, precisionForAccuracy, truncate, parent, contains and redact.
3 changes: 3 additions & 0 deletions .changes/header.tpl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

This file lists each notable change to `gatepost/postcode`. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Changie writes this file from the change files in `.changes/`.
Empty file added .changes/unreleased/.gitkeep
Empty file.
28 changes: 28 additions & 0 deletions .changie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
changesDir: .changes
unreleasedDir: unreleased
headerPath: header.tpl.md
changelogPath: CHANGELOG.md
versionExt: md
versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}'
changeFormat: '- {{.Body}}'
kinds:
- label: Added
auto: minor
- label: Changed
auto: major
- label: Deprecated
auto: minor
- label: Removed
auto: major
- label: Fixed
auto: patch
- label: Security
auto: patch
newlines:
afterChangelogHeader: 1
beforeKind: 1
afterKind: 1
endOfVersion: 1
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100

[*.{yml,yaml}]
indent_size = 2
27 changes: 27 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
# Packagist builds each release from a git archive. These paths stay out of it.
/.changes export-ignore
/.changie.yaml export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.gitmodules export-ignore
/.php-cs-fixer.dist.php export-ignore
/AGENTS.md export-ignore
/CLAUDE.md export-ignore
/CODING_STANDARDS.md export-ignore
/CONTEXT.md export-ignore
/REUSE.toml export-ignore
/composer.lock export-ignore
/docs export-ignore
/infection.json5 export-ignore
/phpmd.xml export-ignore
/phpstan.dist.neon export-ignore
/phpunit.xml.dist export-ignore
/renovate.json export-ignore
/scripts export-ignore
/spec export-ignore
/tests export-ignore
/tools export-ignore
106 changes: 106 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
# Each download has a SHA-256 sum. Renovate moves only the version, so a new version fails
# the sum check until a person writes the new sum.
# renovate: depName=miniscruff/changie
CHANGIE_VERSION: 1.26.0
CHANGIE_SHA256: eab168c8287a6e91912e1c02e5260911232d945bfd3c89d8a0e1ace6bb7b6161
# renovate: depName=gitleaks/gitleaks
GITLEAKS_VERSION: 8.30.1
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The API check reads the release tags, and gitleaks reads the whole history.
fetch-depth: 0
submodules: true
persist-credentials: false
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.4'
coverage: xdebug
# Composer requires ext-intl to install the BC tool. The package does not need it, as
# the tests job shows.
extensions: intl
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
# composer check starts reuse and zizmor with uvx. The tools are small, and no lock file
# keys a cache.
enable-cache: false
- name: Install changie and gitleaks
working-directory: ${{ runner.temp }}
run: |
changie_archive="changie_${CHANGIE_VERSION}_linux_amd64.tar.gz"
gitleaks_archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfLO "https://github.com/miniscruff/changie/releases/download/v${CHANGIE_VERSION}/${changie_archive}"
curl -sSfLO "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${gitleaks_archive}"
echo "$CHANGIE_SHA256 $changie_archive" | sha256sum -c -
echo "$GITLEAKS_SHA256 $gitleaks_archive" | sha256sum -c -
mkdir bin
tar -xzf "$changie_archive" -C bin changie
tar -xzf "$gitleaks_archive" -C bin gitleaks
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- run: composer install --no-interaction --no-progress
- run: composer --working-dir=tools/bc-check install --no-interaction --no-progress
- run: composer check

tests:
runs-on: ubuntu-24.04
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
# The oldest PHP that the package supports, and the newest release.
php: ['8.1', '8.5']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
persist-credentials: false
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php }}
coverage: none
# The package must run without ext-intl (PHP-9).
extensions: :intl
- name: Check that ext-intl is off
run: |
php -r 'exit(extension_loaded("intl") ? 1 : 0);'
- run: composer install --no-interaction --no-progress
- name: Use the newest PHPUnit on the newest PHP
if: matrix.php != '8.1'
run: |
composer config --unset platform.php
composer update --no-interaction --no-progress --with-all-dependencies phpunit/phpunit
# GenerateDataTest reads the UnicodeData.txt that this command downloads and checks. It also
# checks that the generated classes match the spec data on this PHP version.
- run: composer generate:check
- run: composer test
# The dev packages include polyfills of ext-intl, which could hide a call to an intl
# function. This step leaves them out of the autoloader and runs the quickstart. The job
# keeps ext-mbstring, because PHPUnit needs it, so PortabilityTest checks those names.
- name: Run the quickstart without the dev packages
run: |
composer dump-autoload --no-dev
php -r '
require "vendor/autoload.php";
echo Gatepost\Postcode\Postcode::parse("ek 01 a03 fk 01")->value->canonical;
' | grep -x 'EK-01-A03-FK-01'
31 changes: 31 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
name: codeql

on:
push:
branches: [main]
pull_request:
schedule:
- cron: '30 3 * * 1'

permissions:
contents: read

jobs:
analyse:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
# The analysis uploads its results to the code scanning page.
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
# CodeQL cannot read PHP. It checks the workflows in this repo.
languages: actions
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
31 changes: 31 additions & 0 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
name: mutation

on:
schedule:
- cron: '0 5 * * 1'
workflow_dispatch:

permissions:
contents: read

jobs:
core:
runs-on: ubuntu-24.04
# A run takes a few minutes. The limit stops a run that hangs.
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
persist-credentials: false
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.4'
coverage: xdebug
- run: composer install --no-interaction --no-progress
- run: composer --working-dir=tools/infection install --no-interaction --no-progress
# GenerateDataTest reads the UnicodeData.txt that this command downloads and checks.
- run: composer generate:check
- run: composer mutation
55 changes: 55 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
name: pull request

# A squash merge puts the title and the body of the pull request on main as the commit message.
# An edit of the title or the body does not push a commit, so it must start these jobs again.
on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read

concurrency:
group: pull-request-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
sign-off:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The check reads each commit from the base to the head.
fetch-depth: 0
submodules: true
persist-credentials: false
# Only the message that reaches main gets the other message rules, so a commit needs a
# sign-off and nothing more.
- name: Check that each commit has a sign-off
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: python3 spec/scripts/check-tells --signed-off "$BASE..$HEAD"

squash-message:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
persist-credentials: false
# The title and the body come through env, so that no text of the pull request ends up
# inside the script.
- name: Check the message that the squash merge puts on main
env:
TITLE: ${{ github.event.pull_request.title }}
NUMBER: ${{ github.event.pull_request.number }}
BODY: ${{ github.event.pull_request.body }}
run: |
printf '%s (#%s)\n\n%s\n' "$TITLE" "$NUMBER" "$BODY" > "$RUNNER_TEMP/message"
python3 spec/scripts/check-tells --squash-msg "$RUNNER_TEMP/message"
scripts/check-commit-msg "$RUNNER_TEMP/message"
33 changes: 33 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
name: scorecard

on:
push:
branches: [main]
schedule:
- cron: '30 4 * * 1'

permissions: read-all

jobs:
analysis:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
# The upload step sends the results to the code scanning page.
security-events: write
# Scorecard signs the results that it publishes, with the OIDC token of this job.
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
sarif_file: results.sarif
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2026 The Gatepost authors
# SPDX-License-Identifier: Apache-2.0
/build/
/vendor/
/tools/*/vendor/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "spec"]
path = spec
url = https://github.com/gatepost-dev/spec
36 changes: 36 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

// SPDX-FileCopyrightText: 2026 The Gatepost authors
// SPDX-License-Identifier: Apache-2.0

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = (new Finder())
->in([__DIR__ . '/scripts', __DIR__ . '/src', __DIR__ . '/tests'])
->append([__FILE__])
// The generator writes these two files. Its --check mode keeps them in step.
->notPath(['Internal/NfkcTable.php', 'Internal/SpecData.php']);

return (new Config())
->setRiskyAllowed(true)
->setCacheFile(__DIR__ . '/build/php-cs-fixer.cache')
->setFinder($finder)
->setRules([
'@PER-CS' => true,
'@PER-CS:risky' => true,
'declare_strict_types' => true,
'final_class' => true,
// On PHP 8.4, an unqualified call to a native function in a namespace compiles to two
// branches, and Xdebug counts the one that tests cannot reach. T-7 measures branches.
'native_function_invocation' => ['include' => ['@internal'], 'scope' => 'namespaced'],
'no_unused_imports' => true,
'ordered_imports' => [
'imports_order' => ['class', 'function', 'const'],
'sort_algorithm' => 'alpha',
],
'strict_comparison' => true,
'strict_param' => true,
]);
Loading