Merge pull request 'Add the English-comments check: corpus, ADR-0012, detector, report and reusable workflow' (#211) from mil-013-english-comments into main
Validate workflows / validate (push) Successful in 50s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Publish wiki / publish-wiki (push) Successful in 21s
Publish own wiki / wiki (push) Successful in 21s

Reviewed-on: #211
This commit was merged in pull request #211.
This commit is contained in:
2026-10-19 06:48:53 +02:00
28 changed files with 2532 additions and 20 deletions
+116
View File
@@ -0,0 +1,116 @@
name: English comments
# Reusable workflow (MIL-013, ADR-0012). A repository calls it on its pull
# requests with
#
# on:
# pull_request:
# jobs:
# english-comments:
# uses: TirSystem/github-action/.gitea/workflows/english-comments.yml@v2
#
# It reads the comments and docstrings that the pull request adds, judges their
# language, fails the run when one is not English and writes one comment on the
# pull request that mentions the author. It changes no file. Mark the check as
# required in the branch protection of the target branch to block the merge
# (the workflow cannot do that).
on:
workflow_call:
inputs:
scope:
description: "changed: the comments on the lines the pull request adds; all: every tracked file"
type: string
default: "changed"
exclude:
description: "Comma list of file name or path patterns that are not checked (* and ** are wildcards)"
type: string
default: ""
allowed-words-file:
description: "File of the repository with words that are never judged, one per line"
type: string
default: ""
threshold:
description: "Probability, above 0 and at most 1, from which a comment counts as not English (ADR-0012)"
type: string
default: "0.6"
min-words:
description: "A comment with fewer words is not judged (ADR-0012)"
type: string
default: "3"
langid-version:
description: "Version of the langid package"
type: string
default: "1.1.6"
numpy-version:
description: "Version of numpy, which langid needs"
type: string
default: "2.1.3"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/english_comments.py is run; pin it to the exact tag together with the workflow"
type: string
default: "v2"
secrets:
REPORT_TOKEN:
description: "Optional Gitea token that may comment on pull requests; without it the token of the run is used"
required: false
jobs:
english-comments:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 15
env:
SCOPE: ${{ inputs.scope }}
EXCLUDE: ${{ inputs.exclude }}
ALLOWED_WORDS: ${{ inputs.allowed-words-file }}
THRESHOLD: ${{ inputs.threshold }}
MIN_WORDS: ${{ inputs.min-words }}
LANGID_VERSION: ${{ inputs.langid-version }}
NUMPY_VERSION: ${{ inputs.numpy-version }}
BASE_REF: ${{ gitea.base_ref }}
FINDINGS_FILE: /tmp/english-comments-findings.json
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Fetch the shared scripts
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Install the language detector
run: |
python3 -m venv /tmp/english-comments-venv
/tmp/english-comments-venv/bin/pip install --quiet "langid==${LANGID_VERSION}" "numpy==${NUMPY_VERSION}"
- name: Check the comments
id: check
continue-on-error: true
run: >-
/tmp/english-comments-venv/bin/python shared-scripts/tools/english_comments.py check
--scope "$SCOPE" --base "origin/$BASE_REF" --exclude "shared-scripts/**,$EXCLUDE"
--allowed-words "$ALLOWED_WORDS" --threshold "$THRESHOLD" --min-words "$MIN_WORDS"
--output "$FINDINGS_FILE"
- name: Tell the author
if: ${{ always() && gitea.event_name == 'pull_request' }}
continue-on-error: true
env:
GITEA_API_URL: ${{ gitea.api_url }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
PR_NUMBER: ${{ gitea.event.pull_request.number }}
PR_AUTHOR: ${{ gitea.event.pull_request.user.login }}
RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}
REPORT_TOKEN: ${{ secrets.REPORT_TOKEN || gitea.token }}
run: python3 shared-scripts/tools/english_comments_report.py
- name: Fail the run when a comment is not in English
if: ${{ steps.check.outcome == 'failure' }}
run: exit 1
+1
View File
@@ -22,6 +22,7 @@ Each release lists its changes in these groups, in this order:
### Added ### Added
- `english-comments.yml`, a reusable workflow that judges the language of the comments and docstrings a pull request adds, fails the run when one is not English and writes one comment on the pull request that mentions the author (ADR-0012), with the scripts `tools/english_comments.py` and `tools/english_comments_report.py`, the guide `guides/english-comments.md` and wiki pages in three languages. The scanner of `tools/count_loc.py` can return the text of the comments it finds.
- `actions/connect-wireguard`, a composite action that brings a WireGuard tunnel up on a Gitea Actions runner for a deployment, with the guide `guides/vpn-wireguard.md`. Experimental. - `actions/connect-wireguard`, a composite action that brings a WireGuard tunnel up on a Gitea Actions runner for a deployment, with the guide `guides/vpn-wireguard.md`. Experimental.
- `actions/connect-tailscale`, a composite action for Gitea Actions that joins a Tailscale network for a deployment (the Gitea counterpart of the marketplace action `tailscale/github-action`), with the guide `guides/vpn-tailscale.md`. Experimental. - `actions/connect-tailscale`, a composite action for Gitea Actions that joins a Tailscale network for a deployment (the Gitea counterpart of the marketplace action `tailscale/github-action`), with the guide `guides/vpn-tailscale.md`. Experimental.
+44 -2
View File
@@ -17,11 +17,11 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub. This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
It contains ten workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows). It contains eleven workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a check that comments are in English, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows).
## 🔧 Workflows ## 🔧 Workflows
Ten workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source. Eleven workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
| Workflow | File | Kind | Starts on | Purpose | | Workflow | File | Kind | Starts on | Purpose |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
@@ -31,6 +31,7 @@ Ten workflows live in this repository. A **scoped** workflow is delivered by the
| Quality checks C++ | [`quality-checks-cpp.yml`](.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing | | Quality checks C++ | [`quality-checks-cpp.yml`](.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
| Quality checks C# | [`quality-checks-csharp.yml`](.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing | | Quality checks C# | [`quality-checks-csharp.yml`](.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
| Code metrics | [`code-metrics.yml`](.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch | | Code metrics | [`code-metrics.yml`](.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch |
| English comments | [`english-comments.yml`](.gitea/workflows/english-comments.yml) | Reusable (`workflow_call`) | a call from another repository, on a pull request | Judges the language of the comments and docstrings a pull request adds, fails the run when one is not English and writes one comment on the pull request that mentions the author |
| Publish own wiki | [`publish-own-wiki.yml`](.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository | | Publish own wiki | [`publish-own-wiki.yml`](.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository |
| Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks | | Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
| Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review | | Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
@@ -282,6 +283,46 @@ deactivate Workflow
@enduml @enduml
``` ```
### 🔤 English comments
A reusable workflow that checks that the comments and docstrings a pull request adds are written in English, fails the run when one is not, and writes one comment on the pull request that mentions the author. It changes no file and sends no source text outside the instance; marking the check as required in the branch protection is what blocks the merge ([guide](guides/english-comments.md), ADR-0012).
#### 🔀 Workflow
```plantuml
@startuml
title English comments check (reusable workflow)
autonumber
actor "Pull request
uses: ...english-comments.yml@v2" as Caller
participant "Gitea Actions
english-comments" as Workflow
participant "Gitea REST API" as Gitea
Caller -> Workflow: workflow_call with scope, exclude,
allowed-words-file, threshold and min-words
activate Workflow
Workflow -> Workflow: Check out the full history and the shared scripts
Workflow -> Workflow: Install the pinned language detector
Workflow -> Workflow: Read the comments on the added lines
Workflow -> Workflow: Remove paths, options, identifiers and allowed words
Workflow -> Workflow: Judge the language of each comment block
alt a block is not English
Workflow -> Gitea: Create or update one comment that mentions the author
Workflow --> Caller: run failed (the required check blocks the merge)
else all blocks are English
Workflow -> Gitea: Mark an earlier comment resolved
Workflow --> Caller: run passed
end
deactivate Workflow
@enduml
```
### 🏷️ Release ### 🏷️ Release
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)). Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)).
@@ -546,6 +587,7 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](guides/quality-checks.md)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](guides/quality-checks.md)) |
| Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](guides/quality-checks-cpp.md), [C# guide](guides/quality-checks-csharp.md)) | | Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](guides/quality-checks-cpp.md), [C# guide](guides/quality-checks-csharp.md)) |
| Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](guides/code-metrics.md)) | | Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](guides/code-metrics.md)) |
| English comments | Implemented as the reusable workflow `.gitea/workflows/english-comments.yml` with a comment extractor, a language detector measured on a corpus (ADR-0012), a pull request comment and offline tests; not yet run on the instance or required by any repository (MIL-013) ([guide](guides/english-comments.md)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](guides/upgrading.md)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](guides/upgrading.md)) |
## 📄 License ## 📄 License
@@ -0,0 +1,88 @@
# ADR-0012: How a comment is judged not to be English
## Metadata
| Key | Value |
| --- | --- |
| ID | ADR-0012 |
| CrossReference | [MIL-013], [ADR-0005], [ADR-0010] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-19 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | pending |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
---
## Context
[MIL-013] adds a check that fails a pull request when a comment in its source code is not written in English. A comment is short (most have three to twelve words), is full of identifiers, paths and option names, and Danish and Malay words are often close to English ones. The method has to be offline (no source text leaves the instance, criterion 6 of the milestone), small enough to install on every run, deterministic (the same comment gives the same answer on every run), and measured on a corpus, not chosen by taste (criterion 1: recall at least 90 % for Danish and Malay, at most one false positive per 200 English comments).
**Corpus** (`tests/fixtures/comments/`): 300 English comments taken from this repository (`english.tsv`: comments and first docstring lines of `tools/`, `tests/`, `actions/` and the workflows, including fragments, paths and option names), and 138 comments in other languages written for the purpose (`foreign.tsv`): 45 Danish, 45 Bahasa Malaysia, and 12 each of German, Swedish, Norwegian and French. The foreign comments are written by the AI, not collected from real code (the TirSystem repositories have none) and not checked by a native reader. They are typical code comments of three to fifteen words, some with identifiers.
**Methods compared** (`tests/fixtures/comments/compare_detectors.py`; Python 3.13):
1. A rule-based one: letters outside English (`æ ø å ä ö ü ß é ...`) and word lists for English, Danish, Malay and others.
2. `langdetect` 1.0.9, a port of Google's detector (random start unless the seed is fixed).
3. `langid` 1.1.6, restricted to the candidate languages (`en da no sv de fr ms id nl`).
4. `lingua-language-detector` 2.2.0, restricted to the same candidates.
**Results** on the corpus, as the number of comments flagged as not English:
| Method | Danish | Malay | Other (48) | English flagged (of 300) | Install size | Time per 438 comments |
| --- | --- | --- | --- | --- | --- | --- |
| Rule-based (word lists written after the corpus, so optimistic) | 42/45 | 26/45 | 39/48 | 0 | none | 0.01 s |
| `langdetect`, probability at least 0.8 | 41/45 | 42/45 | 47/48 | 14 | 1 MB | 1.45 s |
| `langid`, no threshold | 45/45 | 45/45 | 48/48 | 2 | 2 MB and `numpy` | 0.02 s |
| `lingua`, confidence at least 0.5 | 29/45 | 41/45 | 47/48 | 0 | 170 MB wheel | 0.14 s |
Lowering the `lingua` threshold trades one error for the other: at 0.4 it flags 36/45 Danish and 2 English, at 0.2 it flags all Danish and Malay and 14 English. Raising the `langid` threshold on its normalised probability does the same (`0.5`: one English comment flagged; `0.6`: none).
The two English comments `langid` flagged without a threshold are lines that are mostly a script name and an option (`connect-tailscale.sh mask print ::add-mask:: for every secret input`). A pre-filter that removes tokens that are not words (paths, options, `::` tokens, tokens with digits or underscores, camelCase) and refuses to judge fewer than three words lowers the language signal of such lines, but it made the result worse at a threshold of 0.5 (three English comments flagged), so the threshold and the pre-filter go together:
| `langid` with the pre-filter | Danish | Malay | Other (48) | English flagged | English not judged (under 3 words) |
| --- | --- | --- | --- | --- | --- |
| probability at least 0.5, 3 words | 45/45 | 45/45 | 48/48 | 3 | 35 of 300 |
| probability at least 0.6, 3 words | 44/45 | 41/45 | 48/48 | 0 | 35 of 300 |
## Decision
A comment is judged **not English** when `langid` 1.1.6, restricted to `en da no sv de fr ms id nl`, with normalised probabilities, ranks a language other than English first with a probability of at least **0.6**, after this pre-filter, and only when the filter leaves **at least three words**:
- drop every token that is not a word: a token with a code character (`/ \ { } [ ] " ` = < > | $ @ # % & + ^ ~ *`), an underscore, a digit or `::` (a path, a URL, a JSON key, an identifier); a token that starts with `-` (an option), contains `.` (a file name), is written in capitals (an acronym or a constant) or is camelCase; a leading label such as `Usage:` or `Note:`; and the words of the allowed-words file. Split what is left on `-`;
- keep a token only if it is letters, with at most one inner apostrophe (letters include `æ ø å ä ö ü` and the other letters of Latin scripts);
- a comment with fewer than three words left is **not judged** (it passes), and so is a comment that holds only identifiers, URLs or numbers.
On the corpus this gives 0 false positives in 300 English comments, Danish 44/45 (97.8 %), Malay 41/45 (91.1 %), and 48/48 for the other languages, meeting criterion 1 of [MIL-013]. The threshold, the minimum of three words and the candidate languages are inputs of the workflow with these values as defaults; the rates are a test that fails when the code changes them (task 7).
The versions are pinned (`langid==1.1.6`, `numpy` at a pinned version) and installed in the job's virtual environment like the other tools ([ADR-0005]). Nothing is downloaded at run time and nothing is sent to a service.
The rules of the pre-filter came from the build, not from the first measurement. A scan of this repository with the first version of the filter found two false positives that the corpus did not contain: a docstring with a JSON example (`{"files": 3, "lines": 120, ...}`, judged French with probability 1.0) and `Usage:` lines with options and a file name (judged French with 0.6 and 0.88). The code-character, option, file-name, acronym and label rules above remove them; the corpus rates stayed the same, and the repository (31 source files) now gives no finding. Tests of the check keep both cases (the JSON docstring and the `Usage:` lines), so a change to the filter is measured against them.
## Consequences
**Positive:**
- Offline, deterministic, about 2 MB plus `numpy`, 0.06 s per 1 000 comments; no model download.
- The cases that matter most for TirSystem (Danish and Malay) are caught at or above the milestone's bar, and the rule is explainable to an author: "these words look Danish".
**Negative:**
- **The rates are an estimate, not a guarantee.** The foreign comments were written by the AI and are not checked by a native reader, the threshold and the filter were chosen on the same corpus they are measured on, and the English comments come from one repository. Real Danish and Malay comments will differ; the corpus must grow with every miss or false positive found in use (the allowed-words file and the exception marker of the workflow are the answer to a false positive in the meantime).
- Malay recall of 91.1 % leaves little margin over the bar of 90 %; short Malay comments and comments with many English technical terms are the cases it misses.
- A short comment (under three words after the filter) is never judged, so a Danish "TODO: ret" passes. This is the price of a low false positive rate.
- `langid` is not actively developed (its last release is 1.1.6). It is pinned, and `lingua` is the recorded alternative (better on long text, much larger, and weaker on short Danish at a comparable false positive rate).
- The method judges the language of words, not the quality of English: a comment in poor English passes.
## Affected Artifacts
- [MIL-013] — the decision behind tasks 3, 4 and 7, and criterion 1.
- [ADR-0005] — the tools are pinned and installed in the job's environment, like the quality-check tools.
- [ADR-0010] — the comment scanner of the code counter is reused to extract the comments.
---
[MIL-013]: ../milestones/mil-013-english-comments-check.md
[ADR-0005]: ./adr-0005-quality-check-contract.md
[ADR-0010]: ./adr-0010-code-metrics-badges-contract.md
+1 -1
View File
@@ -13,7 +13,7 @@ each document lives in *this* project and the next version to use.
| MIL | Milestone / Gateway | docs/milestones/*.md | 014 | | MIL | Milestone / Gateway | docs/milestones/*.md | 014 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 085 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 085 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0012 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0013 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
| US | User Story | docs/user-stories.md | 002 | | US | User Story | docs/user-stories.md | 002 |
| UC | Use Case | docs/uc-NNN/uc.md | 009 | | UC | Use Case | docs/uc-NNN/uc.md | 009 |
+2 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 3e3bcd9498f7 --> <!-- translation of Capabilities.md, source sha256 a8c14f722a77 -->
> Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -17,4 +17,5 @@ Rækker, der siger Implementeret eller Vejledninger er skrevet, findes i dag. Re
| Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; taget i brug i RepoFoundry ([vejledning](QualityChecksPythonShell.da)) | | Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; taget i brug i RepoFoundry ([vejledning](QualityChecksPythonShell.da)) |
| Fælles kvalitetskontrol (C og C++, C#) | Implementeret som de genbrugelige workflows `.gitea/workflows/quality-checks-cpp.yml` og `.gitea/workflows/quality-checks-csharp.yml` med præcise værktøjsversioner og offline-tests; endnu ikke brugt i et andet repository (MIL-007) ([vejledning til C og C++](QualityChecksCpp.da), [vejledning til C#](QualityChecksCSharp.da)) | | Fælles kvalitetskontrol (C og C++, C#) | Implementeret som de genbrugelige workflows `.gitea/workflows/quality-checks-cpp.yml` og `.gitea/workflows/quality-checks-csharp.yml` med præcise værktøjsversioner og offline-tests; endnu ikke brugt i et andet repository (MIL-007) ([vejledning til C og C++](QualityChecksCpp.da), [vejledning til C#](QualityChecksCSharp.da)) |
| Kodemålinger og dækningsbadges | Implementeret og udgivet i 2.1.0 som det genbrugelige workflow `.gitea/workflows/code-metrics.yml` med en tæller, en Cobertura-læser, en SVG-renderer og offline-tests; afprøvet i et kasserepository, endnu ikke brugt i et projektrepository (MIL-010) ([vejledning](CodeMetrics.da)) | | Kodemålinger og dækningsbadges | Implementeret og udgivet i 2.1.0 som det genbrugelige workflow `.gitea/workflows/code-metrics.yml` med en tæller, en Cobertura-læser, en SVG-renderer og offline-tests; afprøvet i et kasserepository, endnu ikke brugt i et projektrepository (MIL-010) ([vejledning](CodeMetrics.da)) |
| Engelske kommentarer | Implementeret som det genbrugelige workflow `.gitea/workflows/english-comments.yml` med en kommentarudtrækker, en sprogdetektor målt på et korpus (ADR-0012), en kommentar på pull requesten og offline-tests; endnu ikke kørt på instansen eller krævet af noget repository (MIL-013) ([vejledning](EnglishComments.da)) |
| Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; udgivelsesworkflowet findes, og `v1.0.0` er udgivet (MIL-004) ([vejledning](Upgrading.da)) | | Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; udgivelsesworkflowet findes, og `v1.0.0` er udgivet (MIL-004) ([vejledning](Upgrading.da)) |
+1
View File
@@ -17,4 +17,5 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](QualityChecksPythonShell)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](QualityChecksPythonShell)) |
| Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](QualityChecksCpp), [C# guide](QualityChecksCSharp)) | | Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](QualityChecksCpp), [C# guide](QualityChecksCSharp)) |
| Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](CodeMetrics)) | | Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](CodeMetrics)) |
| English comments | Implemented as the reusable workflow `.gitea/workflows/english-comments.yml` with a comment extractor, a language detector measured on a corpus (ADR-0012), a pull request comment and offline tests; not yet run on the instance or required by any repository (MIL-013) ([guide](EnglishComments)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](Upgrading)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](Upgrading)) |
+2 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 3e3bcd9498f7 --> <!-- translation of Capabilities.md, source sha256 a8c14f722a77 -->
> Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -17,4 +17,5 @@ Baris yang menyatakan Dilaksanakan atau Panduan telah ditulis wujud hari ini. Se
| Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; digunakan dalam RepoFoundry ([panduan](QualityChecksPythonShell.ms)) | | Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; digunakan dalam RepoFoundry ([panduan](QualityChecksPythonShell.ms)) |
| Semakan kualiti bersama (C dan C++, C#) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks-cpp.yml` dan `.gitea/workflows/quality-checks-csharp.yml` dengan versi alat yang tepat dan ujian luar talian; belum digunakan dalam repositori lain (MIL-007) ([panduan C dan C++](QualityChecksCpp.ms), [panduan C#](QualityChecksCSharp.ms)) | | Semakan kualiti bersama (C dan C++, C#) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks-cpp.yml` dan `.gitea/workflows/quality-checks-csharp.yml` dengan versi alat yang tepat dan ujian luar talian; belum digunakan dalam repositori lain (MIL-007) ([panduan C dan C++](QualityChecksCpp.ms), [panduan C#](QualityChecksCSharp.ms)) |
| Metrik kod dan lencana liputan | Dilaksanakan dan diterbitkan dalam 2.1.0 sebagai aliran kerja boleh guna semula `.gitea/workflows/code-metrics.yml` dengan pengira, pembaca Cobertura, pemaparan SVG dan ujian luar talian; dicuba dalam repositori sekali pakai, belum digunakan dalam repositori projek (MIL-010) ([panduan](CodeMetrics.ms)) | | Metrik kod dan lencana liputan | Dilaksanakan dan diterbitkan dalam 2.1.0 sebagai aliran kerja boleh guna semula `.gitea/workflows/code-metrics.yml` dengan pengira, pembaca Cobertura, pemaparan SVG dan ujian luar talian; dicuba dalam repositori sekali pakai, belum digunakan dalam repositori projek (MIL-010) ([panduan](CodeMetrics.ms)) |
| Komen dalam bahasa Inggeris | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/english-comments.yml` dengan pengekstrak komen, pengesan bahasa yang diukur pada korpus (ADR-0012), komen pada pull request dan ujian luar talian; belum dijalankan pada instans atau diwajibkan oleh mana-mana repositori (MIL-013) ([panduan](EnglishComments.ms)) |
| Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; aliran kerja keluaran sudah ada dan `v1.0.0` telah dikeluarkan (MIL-004) ([panduan](Upgrading.ms)) | | Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; aliran kerja keluaran sudah ada dan `v1.0.0` telah dikeluarkan (MIL-004) ([panduan](Upgrading.ms)) |
+108
View File
@@ -0,0 +1,108 @@
<!-- translation of EnglishComments.md, source sha256 866ce29fd1ea -->
> Dette er en oversættelse af [`EnglishComments`](EnglishComments). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
[English](EnglishComments) | Dansk | [Bahasa Melayu](EnglishComments.ms)
# Kommentarer i kildekoden på engelsk
Sådan kalder en softwareudvikler det fælles workflow, der kontrollerer, at
kommentarerne og docstrings i en pull request er skrevet på engelsk. Workflowet
lader kørslen fejle, når én ikke er det, og skriver én kommentar på pull
requesten, der nævner forfatteren. Det ændrer ingen fil og sender ingen
kildetekst ud af instansen. Beslutningen bag er ADR-0012 i dette repository.
## Kald af workflowet
```yaml
name: English comments
on:
pull_request:
jobs:
english-comments:
uses: TirSystem/github-action/.gitea/workflows/english-comments.yml@v2
with:
exclude: "docs/generated/**"
allowed-words-file: .english-comments-allowed-words
```
Indtil udgivelse 2.3.0 findes workflowet kun på `main`, så kald det med `@main`; `@v2` indeholder det
endnu ikke.
| Input | Standard | Betydning |
| --- | --- | --- |
| `scope` | `changed` | `changed` kontrollerer kommentarerne på de linjer, pull requesten tilføjer; `all` kontrollerer hver sporet fil |
| `exclude` | | Kommasepareret liste af fil- eller stimønstre, der ikke kontrolleres (`*` og `**` er jokertegn) |
| `allowed-words-file` | | En fil i repositoryet med ord, der aldrig bedømmes, ét pr. linje; `#` starter en kommentar |
| `threshold` | `0.6` | Sandsynligheden, over 0 og højst 1, hvorfra en kommentar regnes for ikke at være engelsk |
| `min-words` | `3` | En kommentar med færre ord bedømmes ikke |
| `langid-version`, `numpy-version` | `1.1.6`, `2.1.3` | De fastlåste pakker, detektoren bruger |
| `tools-ref` | den nuværende hovedversion | Det tag i dette repository, hvis scripts køres; fastlås det sammen med workflowet |
Den valgfri secret `REPORT_TOKEN` er et Gitea-token, der må kommentere på pull
requests. Uden den bruger workflowet kørslens eget token, så kommentaren
skrives af Actions-botten. En pull request fra en fork har et token med kun
læseadgang: kontrollen får stadig kørslen til at fejle, men der skrives ingen
kommentar.
## Hvad der kontrolleres
Kommentarerne og docstrings i filer på de sprog, kodetælleren kender
([kodemålinger](CodeMetrics)): Python, shell, PowerShell, C, C++, C#, Java,
JavaScript, TypeScript, Go, Rust og SQL. Strenge og kode læses ikke. Kommentarlinjer
efter hinanden bedømmes som én blok.
Før en blok bedømmes, fjernes alt, der ikke er et ord: stier, URL'er, tilvalg
(`--output`), filnavne, identifikatorer (`snake_case`, `camelCase`), tal,
akronymer og konstanter (`JSON`), en indledende etiket som `Usage:` og ordene
i filen med tilladte ord. En blok med færre end `min-words` ord tilbage
bedømmes ikke, så en kort kommentar slipper igennem. Kommentarer, der
konfigurerer et værktøj (`noqa`, `type:`, `pragma`, `shellcheck`), læses ikke.
## Når kontrollen fejler
Kørslen fejler, hvert fund vises som en annotation (`::error file=...,line=...`)
og i loggen som `file:line: looks Danish (0.93): "the comment"`, og én kommentar
på pull requesten opregner dem og nævner forfatteren. En ny kørsel opdaterer den
kommentar; når pull requesten består, markeres kommentaren som løst og nævner
ikke længere nogen.
Ret det ved at skrive kommentaren om på engelsk. For et citat eller et navn, der
ikke er engelsk, kan du enten skrive `english-comments: ignore` i kommentaren
(det springer hele den kommentarblok over, den står i), eller tilføje ordet til
filen med tilladte ord.
## Blokering af sammenfletningen
En fejlende kørsel blokerer ikke i sig selv en sammenfletning. Markér kontrollen
som påkrævet i grenbeskyttelsen af målgrenen: kør workflowet én gang på en pull
request og vælg dets post på listen over statuskontroller (det præcise navn
dannes af det kaldende workflow, dets job og hændelsen). At indstille
grenbeskyttelse er repositoryejerens handling; workflowet ændrer den ikke.
## Hvor præcis den er
Detektoren er `langid`, begrænset til engelsk, dansk, norsk, svensk, tysk,
fransk, malajisk, indonesisk og hollandsk. På repositoryets korpus markerer den
ingen af 300 engelske kommentarer, 44 af 45 danske og 41 af 45 malajiske
kommentarer og alle 48 tyske, svenske, norske og franske (ADR-0012). Dens
grænser:
- en kommentar på ét eller to ord, eller en der mest består af navne, bedømmes ikke;
- en kommentar på dårligt engelsk slipper igennem, og en kommentar, der blander
engelske fagudtryk med få danske eller malajiske ord, kan slippe igennem;
- korpussets danske og malajiske kommentarer er skrevet til testen og ikke samlet
fra rigtig kode, så rigtig kode vil vise andre fejl. Meld en misset kommentar
eller et falsk fund med kommentaren, så kommer den med i korpussen.
## Fejlfinding
| Besked | Årsag |
| --- | --- |
| `[configuration] give --base` | Workflowet blev startet af en hændelse, der ikke er en pull request, med `scope: changed`; kald det på `pull_request` eller brug `scope: all` |
| `[configuration] git diff failed` | Basisgrenen findes ikke i checkout; workflowet henter hele historikken, så tjek at pull requesten har en målgren |
| `[configuration] langid is not installed` | Installationstrinnet fejlede; tjek de fastlåste versioner |
| `[permissions] ... may not comment on pull requests` | Tokenet må ikke kommentere; giv `REPORT_TOKEN` skriveadgang til issues og pull requests |
| Et fund, der ikke er et fund | Tilføj ordene til filen med tilladte ord, eller `english-comments: ignore` til kommentaren |
+106
View File
@@ -0,0 +1,106 @@
<!-- Generated from guides/english-comments.md by tools/build_wiki.py. Edit the source, not this page. -->
> This page is generated from [`guides/english-comments.md`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/english-comments.md). Changes made here are overwritten the next time the wiki is published.
English | [Dansk](EnglishComments.da) | [Bahasa Melayu](EnglishComments.ms)
# Keeping source code comments in English
How a software engineer calls the shared workflow that checks that the comments
and docstrings of a pull request are written in English. The workflow fails the
run when one is not, and writes one comment on the pull request that mentions
the author. It changes no file and sends no source text outside the instance.
Its decision is ADR-0012 in this repository.
## Calling the workflow
```yaml
name: English comments
on:
pull_request:
jobs:
english-comments:
uses: TirSystem/github-action/.gitea/workflows/english-comments.yml@v2
with:
exclude: "docs/generated/**"
allowed-words-file: .english-comments-allowed-words
```
Until release 2.3.0 the workflow exists only on `main`, so call it at `@main`; `@v2` does not
contain it yet.
| Input | Default | Meaning |
| --- | --- | --- |
| `scope` | `changed` | `changed` checks the comments on the lines the pull request adds; `all` checks every tracked file |
| `exclude` | | Comma list of file name or path patterns that are not checked (`*` and `**` are wildcards) |
| `allowed-words-file` | | A file of the repository with words that are never judged, one per line, `#` starts a comment |
| `threshold` | `0.6` | The probability, above 0 and at most 1, from which a comment counts as not English |
| `min-words` | `3` | A comment with fewer words is not judged |
| `langid-version`, `numpy-version` | `1.1.6`, `2.1.3` | The pinned packages the detector needs |
| `tools-ref` | the current major | The tag of this repository whose scripts are run; pin it together with the workflow |
The optional secret `REPORT_TOKEN` is a Gitea token that may comment on pull
requests. Without it the workflow uses the token of the run, so the comment is
written by the Actions bot. A pull request from a fork has a read-only token:
the check still fails the run, but no comment is written.
## What is checked
The comments and docstrings of the files of the languages in the code counter
([code metrics](CodeMetrics)): Python, shell, PowerShell, C, C++, C#, Java,
JavaScript, TypeScript, Go, Rust and SQL. Strings and code are not read.
Consecutive comment lines are judged as one block.
Before a block is judged, everything that is not a word is removed: paths, URLs,
options (`--output`), file names, identifiers (`snake_case`, `camelCase`),
numbers, acronyms and constants (`JSON`), a leading label such as `Usage:`, and
the words of the allowed-words file. A block with fewer than `min-words` words
left is not judged, so a short comment passes. Comments that configure a tool
(`noqa`, `type:`, `pragma`, `shellcheck`) are not read.
## When the check fails
The run fails, each finding is shown as an annotation (`::error file=...,line=...`)
and in the log as `file:line: looks Danish (0.93): "the comment"`, and one comment
on the pull request lists them and mentions the author. A new run updates that
comment; when the pull request passes, the comment is marked resolved and no
longer mentions anyone.
Fix it by rewriting the comment in English. For a quotation or a name that is
not English, either put `english-comments: ignore` in the comment (it skips the
whole comment block it is in), or add the word to the allowed-words file.
## Blocking the merge
A failing run does not block a merge by itself. In the branch protection of the
target branch, mark the check as required: run the workflow once on a pull
request and choose its entry in the list of status checks (the exact name is
made from the calling workflow, its job and the event). Setting branch
protection is the repository owner's action; the workflow does not change it.
## How accurate it is
The detector is `langid`, limited to English, Danish, Norwegian, Swedish,
German, French, Malay, Indonesian and Dutch. On the corpus of the repository it
flags none of 300 English comments, 44 of 45 Danish and 41 of 45 Malay comments
and all of the 48 German, Swedish, Norwegian and French ones (ADR-0012). Its
limits:
- a comment of one or two words, or one that is mostly names, is not judged;
- a comment in poor English passes, and a comment that mixes English technical
terms with few Danish or Malay words can pass;
- the Danish and Malay comments of the corpus were written for the test, not
collected from real code, so real code will show other misses. Report a miss
or a false positive with the comment, and it joins the corpus.
## Troubleshooting
| Message | Cause |
| --- | --- |
| `[configuration] give --base` | The workflow was started by an event that is not a pull request with `scope: changed`; call it on `pull_request` or use `scope: all` |
| `[configuration] git diff failed` | The base branch is not in the checkout; the workflow fetches the full history, so check that the pull request has a target branch |
| `[configuration] langid is not installed` | The install step failed; check the pinned versions |
| `[permissions] ... may not comment on pull requests` | The token cannot comment; give `REPORT_TOKEN` write access to issues and pull requests |
| A finding that is not a finding | Add the words to the allowed-words file, or `english-comments: ignore` to the comment |
+110
View File
@@ -0,0 +1,110 @@
<!-- translation of EnglishComments.md, source sha256 866ce29fd1ea -->
> Ini ialah terjemahan [`EnglishComments`](EnglishComments). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
[English](EnglishComments) | [Dansk](EnglishComments.da) | Bahasa Melayu
# Memastikan komen kod sumber dalam bahasa Inggeris
Cara seorang jurutera perisian memanggil aliran kerja bersama yang menyemak bahawa
komen dan docstring sesuatu pull request ditulis dalam bahasa Inggeris. Aliran
kerja menggagalkan larian apabila ada yang bukan bahasa Inggeris, dan menulis satu
komen pada pull request yang menyebut penulisnya. Ia tidak mengubah sebarang fail
dan tidak menghantar teks sumber keluar daripada instans. Keputusan di
belakangnya ialah ADR-0012 dalam repositori ini.
## Memanggil aliran kerja
```yaml
name: English comments
on:
pull_request:
jobs:
english-comments:
uses: TirSystem/github-action/.gitea/workflows/english-comments.yml@v2
with:
exclude: "docs/generated/**"
allowed-words-file: .english-comments-allowed-words
```
Sehingga keluaran 2.3.0, aliran kerja hanya wujud pada `main`, jadi panggil dengan `@main`; `@v2` belum
mengandunginya.
| Input | Lalai | Maksud |
| --- | --- | --- |
| `scope` | `changed` | `changed` menyemak komen pada baris yang ditambah oleh pull request; `all` menyemak setiap fail yang dijejaki |
| `exclude` | | Senarai corak nama fail atau laluan yang dipisahkan koma dan tidak disemak (`*` dan `**` ialah kad liar) |
| `allowed-words-file` | | Fail dalam repositori dengan perkataan yang tidak pernah dinilai, satu pada setiap baris; `#` memulakan komen |
| `threshold` | `0.6` | Kebarangkalian, melebihi 0 dan paling banyak 1, yang mulai daripadanya sesuatu komen dianggap bukan bahasa Inggeris |
| `min-words` | `3` | Komen dengan perkataan yang lebih sedikit tidak dinilai |
| `langid-version`, `numpy-version` | `1.1.6`, `2.1.3` | Pakej tetap yang diperlukan oleh pengesan |
| `tools-ref` | major semasa | Tag repositori ini yang skripnya dijalankan; tetapkan bersama aliran kerja |
Secret pilihan `REPORT_TOKEN` ialah token Gitea yang boleh memberi komen pada pull
request. Tanpanya, aliran kerja menggunakan token larian itu sendiri, jadi komen
ditulis oleh bot Actions. Pull request daripada fork mempunyai token baca sahaja:
semakan masih menggagalkan larian, tetapi tiada komen ditulis.
## Apa yang disemak
Komen dan docstring dalam fail bahasa yang dikenali oleh pengira kod
([metrik kod](CodeMetrics)): Python, shell, PowerShell, C, C++, C#, Java,
JavaScript, TypeScript, Go, Rust dan SQL. String dan kod tidak dibaca. Baris komen
yang berturutan dinilai sebagai satu blok.
Sebelum sesuatu blok dinilai, segala yang bukan perkataan dibuang: laluan, URL,
pilihan (`--output`), nama fail, pengecam (`snake_case`, `camelCase`), nombor,
akronim dan pemalar (`JSON`), label di permulaan seperti `Usage:`, dan perkataan
dalam fail perkataan yang dibenarkan. Blok yang tinggal kurang daripada
`min-words` perkataan tidak dinilai, jadi komen pendek lulus. Komen yang
mengkonfigurasi alat (`noqa`, `type:`, `pragma`, `shellcheck`) tidak dibaca.
## Apabila semakan gagal
Larian gagal, setiap penemuan dipaparkan sebagai anotasi (`::error file=...,line=...`)
dan dalam log sebagai `file:line: looks Danish (0.93): "the comment"`, dan satu
komen pada pull request menyenaraikannya serta menyebut penulisnya. Larian baharu
mengemas kini komen itu; apabila pull request lulus, komen ditandakan selesai dan
tidak lagi menyebut sesiapa.
Betulkannya dengan menulis semula komen dalam bahasa Inggeris. Untuk petikan atau
nama yang bukan bahasa Inggeris, sama ada letakkan `english-comments: ignore`
dalam komen (ia melangkau seluruh blok komen tempat ia berada), atau tambahkan
perkataan itu ke dalam fail perkataan yang dibenarkan.
## Menyekat penggabungan
Larian yang gagal tidak menyekat penggabungan dengan sendirinya. Dalam
perlindungan cabang bagi cabang sasaran, tandakan semakan sebagai wajib: jalankan
aliran kerja sekali pada sebuah pull request dan pilih entrinya dalam senarai
semakan status (nama tepatnya dibentuk daripada aliran kerja pemanggil, kerjanya
dan peristiwa). Menetapkan perlindungan cabang ialah tindakan pemilik repositori;
aliran kerja tidak mengubahnya.
## Sejauh mana ia tepat
Pengesan ialah `langid`, terhad kepada bahasa Inggeris, Denmark, Norway, Sweden,
Jerman, Perancis, Melayu, Indonesia dan Belanda. Pada korpus repositori, ia
menandakan tiada satu pun daripada 300 komen bahasa Inggeris, 44 daripada 45
komen bahasa Denmark dan 41 daripada 45 komen bahasa Melayu, serta kesemua 48
komen bahasa Jerman, Sweden, Norway dan Perancis (ADR-0012). Batasannya:
- komen satu atau dua perkataan, atau yang kebanyakannya nama, tidak dinilai;
- komen dalam bahasa Inggeris yang lemah lulus, dan komen yang mencampurkan istilah
teknikal bahasa Inggeris dengan beberapa perkataan Denmark atau Melayu boleh lulus;
- komen bahasa Denmark dan Melayu dalam korpus ditulis untuk ujian, bukan dikumpulkan
daripada kod sebenar, jadi kod sebenar akan menunjukkan kesilapan lain. Laporkan
komen yang terlepas atau penemuan palsu bersama komennya, dan ia akan masuk ke
dalam korpus.
## Diagnosis
| Mesej | Punca |
| --- | --- |
| `[configuration] give --base` | Aliran kerja dimulakan oleh peristiwa yang bukan pull request dengan `scope: changed`; panggil pada `pull_request` atau gunakan `scope: all` |
| `[configuration] git diff failed` | Cabang asas tiada dalam checkout; aliran kerja mengambil seluruh sejarah, jadi semak bahawa pull request mempunyai cabang sasaran |
| `[configuration] langid is not installed` | Langkah pemasangan gagal; semak versi yang ditetapkan |
| `[permissions] ... may not comment on pull requests` | Token tidak boleh memberi komen; berikan `REPORT_TOKEN` akses tulis kepada isu dan pull request |
| Penemuan yang bukan penemuan | Tambahkan perkataan ke dalam fail perkataan yang dibenarkan, atau `english-comments: ignore` pada komen |
+44 -3
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 9b967f9e1aff --> <!-- translation of Home.md, source sha256 9b84b5cf7534 -->
> Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -13,12 +13,12 @@ En samling af genbrugelige Gitea Actions-workflows til DevOps-professionelle og
Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub. Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub.
Det indeholder ti workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki (kaldt af hvert repository), fælles kvalitetskontrol for Python og shell, for C og C++ og for C#, kodemålinger og dækningsbadges, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows). Det indeholder elleve workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki (kaldt af hvert repository), fælles kvalitetskontrol for Python og shell, for C og C++ og for C#, kodemålinger og dækningsbadges, en kontrol af, at kommentarer er på engelsk, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows).
## Workflows ## Workflows
Der er ti workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk. Der er elleve workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk.
| Workflow | Fil | Type | Starter ved | Formål | | Workflow | Fil | Type | Starter ved | Formål |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
@@ -28,6 +28,7 @@ Der er ti workflows i dette repository. Et **scoped** workflow leveres af instan
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører clang-format og clang-tidy i præcise versioner på de sporede C- og C++-filer og ændrer intet | | Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører clang-format og clang-tidy i præcise versioner på de sporede C- og C++-filer og ændrer intet |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører `dotnet format` og `dotnet build` på et præcist .NET SDK 10 eller nyere og ændrer intet | | Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører `dotnet format` og `dotnet build` på et præcist .NET SDK 10 eller nyere og ændrer intet |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Tæller kodefilerne og kodelinjerne (uden kommentar- og tomme linjer), læser en Cobertura-dækningsrapport og committer tre SVG-badges til en `badges`-gren | | Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Tæller kodefilerne og kodelinjerne (uden kommentar- og tomme linjer), læser en Cobertura-dækningsrapport og committer tre SVG-badges til en `badges`-gren |
| English comments | [`english-comments.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/english-comments.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository, på en pull request | Bedømmer sproget i de kommentarer og docstrings, en pull request tilføjer, lader kørslen fejle, når én ikke er engelsk, og skriver én kommentar på pull requesten, der nævner forfatteren |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Kun dette repository | push til `main`, der ændrer `docs/wiki/**`, manuelt | Kalder det genbrugelige workflow Publish wiki pages for dette repositorys wiki | | Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Kun dette repository | push til `main`, der ændrer `docs/wiki/**`, manuelt | Kalder det genbrugelige workflow Publish wiki pages for dette repositorys wiki |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne |
| Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang | | Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang |
@@ -279,6 +280,46 @@ deactivate Workflow
@enduml @enduml
``` ```
### Engelske kommentarer
Et genbrugeligt workflow, der kontrollerer, at de kommentarer og docstrings, en pull request tilføjer, er skrevet på engelsk, lader kørslen fejle, når én ikke er det, og skriver én kommentar på pull requesten, der nævner forfatteren. Det ændrer ingen fil og sender ingen kildetekst ud af instansen; at markere kontrollen som påkrævet i grenbeskyttelsen er det, der blokerer sammenfletningen ([vejledning](EnglishComments.da), ADR-0012).
#### Workflow
```plantuml
@startuml
title English comments check (reusable workflow)
autonumber
actor "Pull request
uses: ...english-comments.yml@v2" as Caller
participant "Gitea Actions
english-comments" as Workflow
participant "Gitea REST API" as Gitea
Caller -> Workflow: workflow_call with scope, exclude,
allowed-words-file, threshold and min-words
activate Workflow
Workflow -> Workflow: Check out the full history and the shared scripts
Workflow -> Workflow: Install the pinned language detector
Workflow -> Workflow: Read the comments on the added lines
Workflow -> Workflow: Remove paths, options, identifiers and allowed words
Workflow -> Workflow: Judge the language of each comment block
alt a block is not English
Workflow -> Gitea: Create or update one comment that mentions the author
Workflow --> Caller: run failed (the required check blocks the merge)
else all blocks are English
Workflow -> Gitea: Mark an earlier comment resolved
Workflow --> Caller: run passed
end
deactivate Workflow
@enduml
```
### Udgivelse ### Udgivelse
Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
+43 -2
View File
@@ -13,12 +13,12 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub. This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
It contains ten workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows). It contains eleven workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a check that comments are in English, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows).
## Workflows ## Workflows
Ten workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source. Eleven workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
| Workflow | File | Kind | Starts on | Purpose | | Workflow | File | Kind | Starts on | Purpose |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
@@ -28,6 +28,7 @@ Ten workflows live in this repository. A **scoped** workflow is delivered by the
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing | | Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing | | Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch | | Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch |
| English comments | [`english-comments.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/english-comments.yml) | Reusable (`workflow_call`) | a call from another repository, on a pull request | Judges the language of the comments and docstrings a pull request adds, fails the run when one is not English and writes one comment on the pull request that mentions the author |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository | | Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
| Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review | | Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
@@ -279,6 +280,46 @@ deactivate Workflow
@enduml @enduml
``` ```
### English comments
A reusable workflow that checks that the comments and docstrings a pull request adds are written in English, fails the run when one is not, and writes one comment on the pull request that mentions the author. It changes no file and sends no source text outside the instance; marking the check as required in the branch protection is what blocks the merge ([guide](EnglishComments), ADR-0012).
#### Workflow
```plantuml
@startuml
title English comments check (reusable workflow)
autonumber
actor "Pull request
uses: ...english-comments.yml@v2" as Caller
participant "Gitea Actions
english-comments" as Workflow
participant "Gitea REST API" as Gitea
Caller -> Workflow: workflow_call with scope, exclude,
allowed-words-file, threshold and min-words
activate Workflow
Workflow -> Workflow: Check out the full history and the shared scripts
Workflow -> Workflow: Install the pinned language detector
Workflow -> Workflow: Read the comments on the added lines
Workflow -> Workflow: Remove paths, options, identifiers and allowed words
Workflow -> Workflow: Judge the language of each comment block
alt a block is not English
Workflow -> Gitea: Create or update one comment that mentions the author
Workflow --> Caller: run failed (the required check blocks the merge)
else all blocks are English
Workflow -> Gitea: Mark an earlier comment resolved
Workflow --> Caller: run passed
end
deactivate Workflow
@enduml
```
### Release ### Release
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
+44 -3
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 9b967f9e1aff --> <!-- translation of Home.md, source sha256 9b84b5cf7534 -->
> Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -13,12 +13,12 @@ Koleksi aliran kerja (workflow) Gitea Actions yang boleh digunakan semula untuk
Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub. Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub.
Ia mengandungi sepuluh aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki (dipanggil oleh setiap repositori), semakan kualiti bersama untuk Python dan shell, untuk C dan C++ serta untuk C#, metrik kod dan lencana liputan, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja). Ia mengandungi sebelas aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki (dipanggil oleh setiap repositori), semakan kualiti bersama untuk Python dan shell, untuk C dan C++ serta untuk C#, metrik kod dan lencana liputan, semakan bahawa komen ditulis dalam bahasa Inggeris, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja).
## Aliran kerja ## Aliran kerja
Terdapat sepuluh aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris. Terdapat sebelas aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris.
| Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan | | Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
@@ -28,6 +28,7 @@ Terdapat sepuluh aliran kerja dalam repositori ini. Aliran kerja **scoped** diha
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan clang-format dan clang-tidy pada versi tepat pada fail C dan C++ yang dijejaki dan tidak mengubah apa-apa | | Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan clang-format dan clang-tidy pada versi tepat pada fail C dan C++ yang dijejaki dan tidak mengubah apa-apa |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan `dotnet format` dan `dotnet build` pada .NET SDK 10 atau lebih baharu yang tepat dan tidak mengubah apa-apa | | Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan `dotnet format` dan `dotnet build` pada .NET SDK 10 atau lebih baharu yang tepat dan tidak mengubah apa-apa |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Mengira fail kod dan baris kod (tanpa baris komen dan baris kosong), membaca laporan liputan Cobertura dan melakukan commit tiga lencana SVG ke cabang `badges` | | Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Mengira fail kod dan baris kod (tanpa baris komen dan baris kosong), membaca laporan liputan Cobertura dan melakukan commit tiga lencana SVG ke cabang `badges` |
| English comments | [`english-comments.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/english-comments.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain, pada pull request | Menilai bahasa komen dan docstring yang ditambah oleh pull request, menggagalkan larian apabila ada yang bukan bahasa Inggeris dan menulis satu komen pada pull request yang menyebut penulisnya |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/wiki/**`, manual | Memanggil aliran kerja boleh guna semula Publish wiki pages untuk wiki repositori ini | | Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/wiki/**`, manual | Memanggil aliran kerja boleh guna semula Publish wiki pages untuk wiki repositori ini |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan |
| Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go | | Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go |
@@ -279,6 +280,46 @@ deactivate Workflow
@enduml @enduml
``` ```
### Komen dalam bahasa Inggeris
Aliran kerja boleh guna semula yang menyemak bahawa komen dan docstring yang ditambah oleh pull request ditulis dalam bahasa Inggeris, menggagalkan larian apabila ada yang tidak, dan menulis satu komen pada pull request yang menyebut penulisnya. Ia tidak mengubah sebarang fail dan tidak menghantar teks sumber keluar daripada instans; menandakan semakan sebagai wajib dalam perlindungan cabang ialah yang menyekat penggabungan ([panduan](EnglishComments.ms), ADR-0012).
#### Aliran kerja
```plantuml
@startuml
title English comments check (reusable workflow)
autonumber
actor "Pull request
uses: ...english-comments.yml@v2" as Caller
participant "Gitea Actions
english-comments" as Workflow
participant "Gitea REST API" as Gitea
Caller -> Workflow: workflow_call with scope, exclude,
allowed-words-file, threshold and min-words
activate Workflow
Workflow -> Workflow: Check out the full history and the shared scripts
Workflow -> Workflow: Install the pinned language detector
Workflow -> Workflow: Read the comments on the added lines
Workflow -> Workflow: Remove paths, options, identifiers and allowed words
Workflow -> Workflow: Judge the language of each comment block
alt a block is not English
Workflow -> Gitea: Create or update one comment that mentions the author
Workflow --> Caller: run failed (the required check blocks the merge)
else all blocks are English
Workflow -> Gitea: Mark an earlier comment resolved
Workflow --> Caller: run passed
end
deactivate Workflow
@enduml
```
### Keluaran ### Keluaran
Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
+3
View File
@@ -12,6 +12,7 @@
- [Quality checks (C and C++)](QualityChecksCpp) - [Quality checks (C and C++)](QualityChecksCpp)
- [Quality checks (C#)](QualityChecksCSharp) - [Quality checks (C#)](QualityChecksCSharp)
- [Code metrics](CodeMetrics) - [Code metrics](CodeMetrics)
- [English comments](EnglishComments)
- [Upgrading](Upgrading) - [Upgrading](Upgrading)
- [Capabilities](Capabilities) - [Capabilities](Capabilities)
@@ -25,6 +26,7 @@
- [Kvalitetskontrol (C og C++)](QualityChecksCpp.da) - [Kvalitetskontrol (C og C++)](QualityChecksCpp.da)
- [Kvalitetskontrol (C#)](QualityChecksCSharp.da) - [Kvalitetskontrol (C#)](QualityChecksCSharp.da)
- [Kodemålinger](CodeMetrics.da) - [Kodemålinger](CodeMetrics.da)
- [Engelske kommentarer](EnglishComments.da)
- [Opgradering](Upgrading.da) - [Opgradering](Upgrading.da)
- [Kapabiliteter](Capabilities.da) - [Kapabiliteter](Capabilities.da)
@@ -38,5 +40,6 @@
- [Semakan kualiti (C dan C++)](QualityChecksCpp.ms) - [Semakan kualiti (C dan C++)](QualityChecksCpp.ms)
- [Semakan kualiti (C#)](QualityChecksCSharp.ms) - [Semakan kualiti (C#)](QualityChecksCSharp.ms)
- [Metrik kod](CodeMetrics.ms) - [Metrik kod](CodeMetrics.ms)
- [Komen dalam bahasa Inggeris](EnglishComments.ms)
- [Menaik taraf](Upgrading.ms) - [Menaik taraf](Upgrading.ms)
- [Keupayaan](Capabilities.ms) - [Keupayaan](Capabilities.ms)
+100
View File
@@ -0,0 +1,100 @@
# Keeping source code comments in English
How a software engineer calls the shared workflow that checks that the comments
and docstrings of a pull request are written in English. The workflow fails the
run when one is not, and writes one comment on the pull request that mentions
the author. It changes no file and sends no source text outside the instance.
Its decision is ADR-0012 in this repository.
## Calling the workflow
```yaml
name: English comments
on:
pull_request:
jobs:
english-comments:
uses: TirSystem/github-action/.gitea/workflows/english-comments.yml@v2
with:
exclude: "docs/generated/**"
allowed-words-file: .english-comments-allowed-words
```
Until release 2.3.0 the workflow exists only on `main`, so call it at `@main`; `@v2` does not
contain it yet.
| Input | Default | Meaning |
| --- | --- | --- |
| `scope` | `changed` | `changed` checks the comments on the lines the pull request adds; `all` checks every tracked file |
| `exclude` | | Comma list of file name or path patterns that are not checked (`*` and `**` are wildcards) |
| `allowed-words-file` | | A file of the repository with words that are never judged, one per line, `#` starts a comment |
| `threshold` | `0.6` | The probability, above 0 and at most 1, from which a comment counts as not English |
| `min-words` | `3` | A comment with fewer words is not judged |
| `langid-version`, `numpy-version` | `1.1.6`, `2.1.3` | The pinned packages the detector needs |
| `tools-ref` | the current major | The tag of this repository whose scripts are run; pin it together with the workflow |
The optional secret `REPORT_TOKEN` is a Gitea token that may comment on pull
requests. Without it the workflow uses the token of the run, so the comment is
written by the Actions bot. A pull request from a fork has a read-only token:
the check still fails the run, but no comment is written.
## What is checked
The comments and docstrings of the files of the languages in the code counter
([code metrics](code-metrics.md)): Python, shell, PowerShell, C, C++, C#, Java,
JavaScript, TypeScript, Go, Rust and SQL. Strings and code are not read.
Consecutive comment lines are judged as one block.
Before a block is judged, everything that is not a word is removed: paths, URLs,
options (`--output`), file names, identifiers (`snake_case`, `camelCase`),
numbers, acronyms and constants (`JSON`), a leading label such as `Usage:`, and
the words of the allowed-words file. A block with fewer than `min-words` words
left is not judged, so a short comment passes. Comments that configure a tool
(`noqa`, `type:`, `pragma`, `shellcheck`) are not read.
## When the check fails
The run fails, each finding is shown as an annotation (`::error file=...,line=...`)
and in the log as `file:line: looks Danish (0.93): "the comment"`, and one comment
on the pull request lists them and mentions the author. A new run updates that
comment; when the pull request passes, the comment is marked resolved and no
longer mentions anyone.
Fix it by rewriting the comment in English. For a quotation or a name that is
not English, either put `english-comments: ignore` in the comment (it skips the
whole comment block it is in), or add the word to the allowed-words file.
## Blocking the merge
A failing run does not block a merge by itself. In the branch protection of the
target branch, mark the check as required: run the workflow once on a pull
request and choose its entry in the list of status checks (the exact name is
made from the calling workflow, its job and the event). Setting branch
protection is the repository owner's action; the workflow does not change it.
## How accurate it is
The detector is `langid`, limited to English, Danish, Norwegian, Swedish,
German, French, Malay, Indonesian and Dutch. On the corpus of the repository it
flags none of 300 English comments, 44 of 45 Danish and 41 of 45 Malay comments
and all of the 48 German, Swedish, Norwegian and French ones (ADR-0012). Its
limits:
- a comment of one or two words, or one that is mostly names, is not judged;
- a comment in poor English passes, and a comment that mixes English technical
terms with few Danish or Malay words can pass;
- the Danish and Malay comments of the corpus were written for the test, not
collected from real code, so real code will show other misses. Report a miss
or a false positive with the comment, and it joins the corpus.
## Troubleshooting
| Message | Cause |
| --- | --- |
| `[configuration] give --base` | The workflow was started by an event that is not a pull request with `scope: changed`; call it on `pull_request` or use `scope: all` |
| `[configuration] git diff failed` | The base branch is not in the checkout; the workflow fetches the full history, so check that the pull request has a target branch |
| `[configuration] langid is not installed` | The install step failed; check the pinned versions |
| `[permissions] ... may not comment on pull requests` | The token cannot comment; give `REPORT_TOKEN` write access to issues and pull requests |
| A finding that is not a finding | Add the words to the allowed-words file, or `english-comments: ignore` to the comment |
+120
View File
@@ -0,0 +1,120 @@
"""MIL-013 task 2 (ADR-0012): compare detection methods on the corpus.
Needs langdetect, langid and lingua-language-detector installed (not part of the
test requirements). Usage: python compare_detectors.py [-v]
"""
import csv
import re
import sys
import time
from collections import defaultdict
from pathlib import Path
CORPUS = Path(__file__).resolve().parent
rows = []
for name in ("english.tsv", "foreign.tsv"):
with (CORPUS / name).open(encoding="utf-8", newline="") as handle:
rows += [(r["lang"], r["text"]) for r in csv.DictReader(handle, delimiter="\t", quoting=csv.QUOTE_NONE)]
# ---------- A: rule-based
NON_ENGLISH_LETTERS = re.compile(r"[æøåäöüßéèêàâçñôîûœ]", re.I)
STOP = {
"en": "the a an of to and is in it that this for on with as are be by or if not from at which can will we you "
"when then use used only each all any its into has have was were been do does done so but also than how what".split(),
"da": "og er det en at i jeg til på for med af ikke den der som vi skal kan har de om men et fra hvis her kun "
"efter før bliver være dette denne mappen filen listen brugeren serveren tjek hent gem slet læs opret".split(),
"ms": "dan yang untuk dengan ini itu di dalam pada akan atau tidak dari ke kita jika boleh adalah mesti sama "
"semak fail pelayan pengguna senarai kira baris guna supaya apabila sebelum selepas daripada".split(),
"other": "der die das und ist nicht ein eine mit von zu den dem auf für wir och att som en är inte för med "
"på av vi le la les des est une pour pas dans que qui nous sur om det jeg å ikke vi må".split(),
}
def rule_based(text: str) -> bool:
if NON_ENGLISH_LETTERS.search(text):
return True
words = re.findall(r"[a-zA-ZæøåÆØÅ']+", text.lower())
score = {k: sum(w in set(v) for w in words) for k, v in STOP.items()}
foreign = max(score["da"], score["ms"], score["other"])
return foreign >= 2 and foreign > score["en"]
# ---------- B: langdetect
def make_langdetect():
from langdetect import DetectorFactory, detect_langs
DetectorFactory.seed = 0
def run(text: str, threshold: float = 0.8) -> bool:
try:
best = detect_langs(text)[0]
except Exception:
return False
return best.lang != "en" and best.prob >= threshold
return run
# ---------- C: langid
def make_langid():
import langid
langid.set_languages(["en", "da", "no", "sv", "de", "fr", "ms", "id", "nl"])
def run(text: str, threshold: float = 0.0) -> bool:
lang, _score = langid.classify(text)
return lang != "en"
return run
# ---------- D: lingua
def make_lingua():
from lingua import Language, LanguageDetectorBuilder
names = ["ENGLISH", "DANISH", "BOKMAL", "NYNORSK", "SWEDISH", "GERMAN", "FRENCH", "MALAY", "INDONESIAN", "DUTCH"]
languages = [getattr(Language, n) for n in names if hasattr(Language, n)]
print("lingua languages:", [l.name for l in languages], file=sys.stderr)
detector = LanguageDetectorBuilder.from_languages(*languages).build()
def run(text: str, threshold: float = 0.5) -> bool:
values = detector.compute_language_confidence_values(text)
if not values:
return False
top = values[0]
return top.language != Language.ENGLISH and top.value >= threshold
return run
METHODS = {"rule": rule_based}
for name, factory in (("langdetect", make_langdetect), ("langid", make_langid), ("lingua", make_lingua)):
try:
METHODS[name] = factory()
except Exception as error: # a method that cannot be built is reported, not hidden
print("cannot build", name, error, file=sys.stderr)
def report(name: str, predict) -> None:
started = time.perf_counter()
flagged = defaultdict(lambda: [0, 0]) # lang -> [flagged, total]
wrong = []
for lang, text in rows:
is_flagged = predict(text)
flagged[lang][1] += 1
if is_flagged:
flagged[lang][0] += 1
if (lang == "en") == is_flagged:
wrong.append((lang, text))
elapsed = time.perf_counter() - started
foreign = [k for k in flagged if k != "en"]
line = f"{name:12s} " + " ".join(f"{k}={flagged[k][0]}/{flagged[k][1]}" for k in sorted(foreign))
print(f"{line} FALSE-POSITIVES en={flagged['en'][0]}/{flagged['en'][1]} {elapsed:.2f}s/{len(rows)}")
if "-v" in sys.argv:
for lang, text in wrong[:25]:
print(" ", lang, "|", text[:80])
for name, predict in METHODS.items():
report(name, predict)
+301
View File
@@ -0,0 +1,301 @@
lang text origin
en "string", "char" (a literal of one character) or "none" tools/count_loc.py
en (ADR-0008). The workflow checks out the calling repository, fetches this file tools/quality-checks-cpp.sh
en (MIL-004 task 6). The repository is public, so no token is needed. .gitea/scoped_workflows/sync-github-metadata.yml
en (compile-commands-dir) are optional. .gitea/workflows/quality-checks-cpp.yml
en (tools/publish_wiki.py), fetched at tools-ref. .gitea/workflows/publish-wiki-pages.yml
en ----------------------------------------------------------------- helpers tests/test_quality_workflows_cpp_csharp.py
en --------------------------------------------------------------------- C# tests/test_quality_workflows_cpp_csharp.py
en --------------------------------------------------------------------- C++ tests/test_quality_workflows_cpp_csharp.py
en .c files are C; everything else (including headers) is checked as C++. tools/quality-checks-cpp.sh
en .gitmodules uses ssh:// URLs and the runner has no SSH key, so the .gitea/workflows/sync-milestones.yml
en A directory with a `python3` that runs the interpreter of the tests. tests/test_code_metrics.py
en A failure with a category. tools/publish_wiki.py
en A runner must be registered for the repository (or its organisation). .gitea/workflows/sync-milestones.yml
en A script module, readable and writable by item and by attribute. tests/workflow_source.py
en A script run: the finished process and the tool calls the stubs logged. tests/test_quality_workflows_cpp_csharp.py
en A source file or section needed for a page is missing. tools/build_wiki.py
en A translation has the same code blocks, table rows and heading levels. tests/test_build_wiki.py
en A wiki that already has pages named with spaces, as Gitea stores them. tests/test_publish_wiki.py
en ADR-0009, MIL-009: the scoped publish-wiki.yml and the marker are removed. tests/test_publish_wiki.py
en An exact stable version of the .NET SDK, 10 or later (no channel, no range). tools/quality-checks-csharp.sh
en An input is invalid; the message is printed as a `[configuration]` failure. tools/badges.py
en Answers like the Gitea repository and wiki endpoints. tests/test_publish_wiki.py
en Answers the calls of the release script and records the writes. tests/test_release.py
en Black or white, whichever has the higher contrast on the background. tools/badges.py
en Build the English wiki pages in ``docs/wiki/`` from ``README.md`` and ``guides/``. tools/build_wiki.py
en CLANG_TIDY_VERSION, QC_VENV (and QC_BIN for tests). tools/quality-checks-cpp.sh
en COVERAGE_WARN, COVERAGE_GOOD, BADGES_BRANCH, BADGES_OUT (the directory of the badge files; a temporary tools/code-metrics.sh
en Copy the badge files into the worktree and stage them; return 0 when tools/code-metrics.sh
en Copy the description and topics of a Gitea repository to its GitHub push mirror. tools/sync_github_metadata.py
en Count the code files and the lines of code of a directory (ADR-0010). tools/count_loc.py
en Count the code files under directory; see the module docstring for the result. tools/count_loc.py
en Create a release of this repository: version tag, release page, major tag. tools/release.py
en Create and update the pages; delete the others only when prune is set. tools/publish_wiki.py
en Create the release page (and with it the tag), then move the major tag. tools/release.py
en Describe the shape of a token without revealing any of its characters. tools/publish_wiki.py
en Empty on a push (a real run); "true" or "false" on a manual run. .gitea/workflows/sync-milestones.yml
en Env: SHELLCHECK, SHFMT the commands to run (default: from the PATH) tools/lint-action-scripts.sh
en Every failure is printed as ::error::[category] message. tools/code-metrics.sh
en Every failure is printed as ::error::[category] message. A key is never printed. actions/connect-wireguard/connect-wireguard.sh
en Exit: 0 every script is clean (or there is none); 1 at least one finding or tools/lint-action-scripts.sh
en Gitea keeps a hyphen in a title literal (Home-da.-.md, address Home%2Dda). tests/test_publish_wiki.py
en Gitea shows subpages only through _Sidebar, so it must reach every page. tests/test_build_wiki.py
en H2 titles to keep; None keeps the file tools/build_wiki.py
en Helpers that read workflow files and the Python embedded in them. tests/workflow_source.py
en INSTALLING THIS FILE IS THE STANDING AUTHORIZATION TO PUBLISH. This project .gitea/workflows/sync-milestones.yml
en Import a script file without running its `main` (its name is not `__main__`). tests/workflow_source.py
en Import tools/changelog.py, which sits next to this file. tools/release.py
en Import tools/publish_wiki.py. tests/workflow_source.py
en Import tools/sync_github_metadata.py. tests/workflow_source.py
en It checks the C and C++ files of the repository with clang-format and .gitea/workflows/quality-checks-cpp.yml
en It checks the C# solution or project of the repository with `dotnet format` .gitea/workflows/quality-checks-csharp.yml
en It checks the Python and shell files of the repository with pinned tools, .gitea/workflows/quality-checks.yml
en It counts the code files and the lines of code of the repository (comment .gitea/workflows/code-metrics.yml
en It needs read and write on issues and milestones, and read on the framework .gitea/workflows/sync-milestones.yml
en It runs no test, needs no secret and writes only the badges branch. .gitea/workflows/code-metrics.yml
en LANGUAGES, WORKING_DIRECTORY, RUFF_VERSION, MYPY_VERSION, tools/quality-checks.sh
en LF on every platform tests/test_lint_action_scripts.py
en Leading emoji and symbols in README headings; removed so anchors stay stable. tools/build_wiki.py
en Link text of the sidebar: English, then one list per translation code. tools/build_wiki.py
en Lint the shell scripts of the composite actions (MIL-012 task 6): shellcheck, tools/lint-action-scripts.sh
en MIL-004 Go/No-Go criterion 5: every script is a file under tools/. tests/test_workflows.py
en Markdown pages of wiki_dir to the wiki of the calling repository, creating .gitea/workflows/publish-wiki-pages.yml
en Needs: bash, find, shellcheck, shfmt tools/lint-action-scripts.sh
en No translation follows an older version of its English page. tests/test_build_wiki.py
en Offline tests for the composite action actions/connect-tailscale. tests/test_connect_tailscale.py
en Offline tests for the composite action actions/connect-wireguard. tests/test_connect_wireguard.py
en Offline tests for the parts of tools/ that the flow tests do not reach. tests/test_tool_entrypoints.py
en Offline tests for the reusable C++ and C# quality-check workflows (ADR-0008). tests/test_quality_workflows_cpp_csharp.py
en Offline tests for the reusable code-metrics workflow and tools/code-metrics.sh (ADR-0010). tests/test_code_metrics.py
en Offline tests for the reusable quality-check workflow (ADR-0005). tests/test_quality_workflow.py
en Offline tests for tools/badges.py (ADR-0010): the coverage reader and the SVG renderer. tests/test_badges.py
en Offline tests for tools/build_wiki.py. tests/test_build_wiki.py
en Offline tests for tools/changelog.py and the CHANGELOG.md of this repository. tests/test_changelog.py
en Offline tests for tools/count_loc.py (ADR-0010): hand-counted fixtures per language. tests/test_count_loc.py
en Offline tests for tools/lint-action-scripts.sh (MIL-012 task 6). tests/test_lint_action_scripts.py
en Offline tests for tools/publish_wiki.py and the publish-wiki workflows. tests/test_publish_wiki.py
en Offline tests for tools/release.py and .gitea/workflows/release.yml. tests/test_release.py
en Offline tests for tools/sync_github_metadata.py. tests/test_sync_github_metadata.py
en On a push to main that changes docs/milestones/ or docs/sqa/reviews/, this .gitea/workflows/sync-milestones.yml
en One `## [x]` section of the changelog. tools/changelog.py
en One generated wiki page. tools/build_wiki.py
en Publish accepted milestones to the git host. .gitea/workflows/sync-milestones.yml
en Publish the pages of a folder to the Gitea wiki of the repository. tools/publish_wiki.py
en Publishes the wiki of this repository from docs/wiki/ by calling the .gitea/workflows/publish-own-wiki.yml
en Publishing an empty directory would delete the whole wiki. tools/publish_wiki.py
en PyYAML reads `on` as True tests/test_code_metrics.py
en QC_DOTNET_INSTALL_SCRIPT, QC_DOTNET for tests). tools/quality-checks-csharp.sh
en Read a Cobertura coverage report and write the code metrics badges (ADR-0010). tools/badges.py
en Read, check and cut release notes from CHANGELOG.md (ADR-0006). tools/changelog.py
en Records calls and answers like the Gitea and GitHub REST APIs. tests/test_sync_github_metadata.py
en Releases this repository (ADR-0006, ADR-0007, MIL-004 task 5). It is started .gitea/workflows/release.yml
en Remove the leading emoji from a heading line. tools/build_wiki.py
en Replace urllib.request.urlopen; set `.reply` to a body or an exception. tests/test_tool_entrypoints.py
en Return (code, comment, blank) line counts of the text of a file. tools/count_loc.py
en Return (exact tag, major tag, whether the major tag moves). tools/release.py
en Return ``{file name: content}`` for every English page and the sidebar. tools/build_wiki.py
en Return a flat two-part SVG badge; the width follows the number of characters. tools/badges.py
en Return a message for every page that is missing or out of date. tools/build_wiki.py
en Return a message for every translation that is missing or stale. tools/build_wiki.py
en Return every tag name of the repository. tools/release.py
en Return every workflow file in the known workflow directories. tests/workflow_source.py
en Return one message per breach of the convention. tools/changelog.py
en Return red below warn, yellow from warn to below good, green from good. tools/badges.py
en Return the Markdown of the release page for ``version``. tools/changelog.py
en Return the boolean of the WIKI_PRUNE input: true or false, nothing else. tools/publish_wiki.py
en Return the full Markdown of one wiki page. tools/build_wiki.py
en Return the identity of a page: Gitea stores a space as a hyphen. tools/publish_wiki.py
en Return the index after the string that opens at start; the end of line if unclosed. tools/count_loc.py
en Return the keys of the pages that exist in the wiki. tools/publish_wiki.py
en Return the language of a file name by its extension, or None. tools/count_loc.py
en Return the line coverage of a Cobertura XML report as a whole percent, half up. tools/badges.py
en Return the line that links a page to its other languages. tools/build_wiki.py
en Return the lines before the first H2 and the lines of each H2 section. tools/build_wiki.py
en Return the navigation shown beside every wiki page. tools/build_wiki.py
en Return the problems of one workflow's default `tools-ref`, or []. tests/test_tools_ref.py
en Return the release page text for a version, or fail as a validation error. tools/release.py
en Return the releases in file order; text before the first one is ignored. tools/changelog.py
en Return the short content hash a translation records for its source. tools/build_wiki.py
en Return the source of each `python3 - <<'PY'` heredoc in a workflow. tests/workflow_source.py
en Return the title to send to Gitea for a page key. tools/publish_wiki.py
en Return the version as a tuple, or raise a configuration error. tools/release.py
en Return the wiki form of a link target found in ``source``. tools/build_wiki.py
en Return the workflow files of this repository that a caller can use. tests/test_tools_ref.py
en Return {page key: content} for every Markdown file in wiki_dir. tools/publish_wiki.py
en Reusable workflow (ADR-0005). A repository calls it with .gitea/workflows/quality-checks.yml
en Reusable workflow (ADR-0008). A repository calls it with .gitea/workflows/quality-checks-cpp.yml
en Reusable workflow (ADR-0009, MIL-008). A repository calls it with .gitea/workflows/publish-wiki-pages.yml
en Reusable workflow (ADR-0010). A repository calls it with .gitea/workflows/code-metrics.yml
en Run a tool as `python tools/<name>` with only the given environment. tests/test_tool_entrypoints.py
en Run it once by hand from the Actions page with dry_run left on: it prints .gitea/workflows/sync-milestones.yml
en Run the generator; return the process exit code. tools/build_wiki.py
en Run the script with a stub wg-quick that records its arguments. tests/test_connect_wireguard.py
en SHELLCHECK_PY_VERSION, SHFMT_PY_VERSION, QC_VENV (and QC_BIN for tests). tools/quality-checks.sh
en Scan one line; return (has_code, has_comment) and update the state. tools/count_loc.py
en Scoped workflow: it runs in every repository of an organization. The script .gitea/scoped_workflows/sync-github-metadata.yml
en Secret: TOKEN_FOR_GITEA, the bare Gitea token of the organization (no JSON). .gitea/workflows/sync-milestones.yml
en Settings come from the environment, never from interpolated expressions: tools/code-metrics.sh
en Static validation of every workflow file in this repository. tests/test_workflows.py
en Steps of the composite action actions/connect-tailscale/action.yml. actions/connect-tailscale/connect-tailscale.sh
en Steps of the composite action actions/connect-wireguard/action.yml. actions/connect-wireguard/connect-wireguard.sh
en Steps of the reusable workflow .gitea/workflows/code-metrics.yml (ADR-0010). tools/code-metrics.sh
en Steps of the reusable workflow .gitea/workflows/quality-checks-cpp.yml tools/quality-checks-cpp.sh
en Steps of the reusable workflow .gitea/workflows/quality-checks-csharp.yml tools/quality-checks-csharp.sh
en Steps of the reusable workflow .gitea/workflows/quality-checks.yml (ADR-0005). tools/quality-checks.sh
en Strip heading emoji and rewrite links, leaving fenced code untouched. tools/build_wiki.py
en Succeeds when one of the named files exists in the working directory or above tools/quality-checks-cpp.sh
en TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }} .gitea/workflows/publish-wiki-pages.yml
en TS_ARGS is split on spaces on purpose: it holds flags, never a secret actions/connect-tailscale/connect-tailscale.sh
en TS_AUTHKEY, TS_OAUTH_SECRET, TS_TAGS, TS_HOSTNAME, TS_VERSION, TS_USERSPACE, actions/connect-tailscale/connect-tailscale.sh
en TS_SHA256, TS_ARGS, TS_TIMEOUT, TS_HEALTH_CHECK_HOST. TS_TAILSCALE, actions/connect-tailscale/connect-tailscale.sh
en TS_TAILSCALED, TS_SUDO, TS_STATE_DIR, TS_START_WAIT (tries) and actions/connect-tailscale/connect-tailscale.sh
en TS_WAIT_INTERVAL (seconds) name the commands, the work folder and the waits, actions/connect-tailscale/connect-tailscale.sh
en The Home page names each workflow file of the repository and draws it. tests/test_build_wiki.py
en The `if __name__ == "__main__"` block: exit code 1 and ERROR: on stderr. tests/test_tool_entrypoints.py
en The auth key is passed to tailscale in a file of mode 600, never on a command actions/connect-tailscale/connect-tailscale.sh
en The caller file is the opt-in; there is no marker file. It publishes the .gitea/workflows/publish-wiki-pages.yml
en The comment and string rules of one language. tools/count_loc.py
en The commit is the tip of the branch and its validation run is green. tools/release.py
en The default `tools-ref` of every reusable workflow names the current major. tests/test_tools_ref.py
en The exclude patterns of the caller; a pattern without `/` matches a name anywhere. tools/count_loc.py
en The page a link to a source file should point to. The README is the home page. tools/build_wiki.py
en The pages in wiki/ match what README.md and guides/ produce. tests/test_build_wiki.py
en The reusable workflow sets WIKI_PRUNE: the caller is the opt-in (MIL-008). tests/test_publish_wiki.py
en The wiki of this repository equals docs/wiki/ (ADR-0009, MIL-008 task 4). tests/test_publish_wiki.py
en The workflow checks out the calling repository, fetches this file into tools/code-metrics.sh
en Translate an exclude pattern: `*` stops at `/`, `**` crosses it, `?` is one character. tools/count_loc.py
en Translations (ADR-0001): `<Page>.<code>` written by hand in docs/wiki/. tools/build_wiki.py
en True when the tag holds the file; True when the tag is not in this checkout. tests/test_tools_ref.py
en Two merges in a row queue instead of racing. .gitea/workflows/sync-milestones.yml
en Usage: lint-action-scripts.sh [<directory>] (default: actions) tools/lint-action-scripts.sh
en Use the user-space implementation when the kernel has no WireGuard module, actions/connect-wireguard/connect-wireguard.sh
en WG_ALLOWED_IPS, WG_PRESHARED_KEY, WG_DNS, WG_KEEPALIVE, WG_INTERFACE, actions/connect-wireguard/connect-wireguard.sh
en WG_CONFIG, WG_PRIVATE_KEY, WG_ADDRESS, WG_PEER_PUBLIC_KEY, WG_ENDPOINT, actions/connect-wireguard/connect-wireguard.sh
en WG_HEALTH_CHECK_HOST. WG_DIR is the configuration directory (/etc/wireguard actions/connect-wireguard/connect-wireguard.sh
en WORKING_DIRECTORY, EXCLUDE, COVERAGE_REPORT, COVERAGE_ARTIFACT, tools/code-metrics.sh
en WORKING_DIRECTORY, PROJECT, DOTNET_SDK_VERSION, DOTNET_ROOT (and tools/quality-checks-csharp.sh
en WORKING_DIRECTORY, STD, COMPILE_COMMANDS_DIR, CLANG_FORMAT_VERSION, tools/quality-checks-cpp.sh
en Windows has no POSIX file modes tests/test_connect_wireguard.py
en Without a .clang-tidy, clang-tidy enables no check and refuses to run, so tools/quality-checks-cpp.sh
en Write every page; return the names of the files that changed. tools/build_wiki.py
en Write the badge files and the metrics JSON; return the paths in a fixed order. tools/badges.py
en `install` creates the venv and installs `==` versions; checked with a fake pip. tests/test_quality_workflows_cpp_csharp.py
en `ip link add` fails in the test environment, so wireguard-go must exist tests/test_connect_wireguard.py
en a breaking entry needs a new major compared with the release before it tools/changelog.py
en a called workflow has its own runner tests/test_workflows.py
en a daemon that already runs belongs to the host; this action would log it out actions/connect-tailscale/connect-tailscale.sh
en a full configuration: mask the value of every PrivateKey and PresharedKey actions/connect-wireguard/connect-wireguard.sh
en a line break would add directives (PostUp runs as root) to the file actions/connect-wireguard/connect-wireguard.sh
en a missing tool tools/lint-action-scripts.sh
en a sudo that resets the environment, as the real one does tests/test_connect_wireguard.py
en a wrapped item: the lines that continue it belong to the same item tools/changelog.py
en a.py has one code line, run.sh one tests/test_code_metrics.py
en an entry for the version. dry_run defaults to true: it checks and prints and .gitea/workflows/release.yml
en and `dotnet build` (warnings as errors) on an exact .NET SDK 10 or later, .gitea/workflows/quality-checks-csharp.yml
en and blank lines are not counted), reads an optional Cobertura XML coverage .gitea/workflows/code-metrics.yml
en and nothing when none of them changed tools/code-metrics.sh
en and shfmt in the format .editorconfig sets (two spaces, indented case labels). tools/lint-action-scripts.sh
en and updating pages, and deleting pages that are not in the folder only when .gitea/workflows/publish-wiki-pages.yml
en branches: [ main ] .gitea/workflows/publish-wiki-pages.yml
en branches: [main] .gitea/workflows/code-metrics.yml
en by default); WG_QUICK, WG_SUDO and WG_PING name the commands, for the tests. actions/connect-wireguard/connect-wireguard.sh
en by hand with the version to release; it creates the version tag, the release .gitea/workflows/release.yml
en caller a project copies from framework/templates/ci/publish-wiki.yml, with .gitea/workflows/publish-own-wiki.yml
en changes no file and needs no secret. .gitea/workflows/quality-checks.yml
en changes no file and needs no secret. The package restore needs the network. .gitea/workflows/quality-checks-csharp.yml
en clang-tidy at exact versions, changes no file and needs no secret. The .gitea/workflows/quality-checks-cpp.yml
en clang-tidy exits 0 on a warning, so every finding is made an error: a tools/quality-checks-cpp.sh
en code-metrics.sh measure count the code, read the coverage report and tools/code-metrics.sh
en code-metrics.sh publish commit the badge files to the badges branch, tools/code-metrics.sh
en code-metrics.sh validate check the inputs, before anything is counted tools/code-metrics.sh
en command line. .gitea/workflows/sync-milestones.yml
en commit is the tip of main, its validation run is green and the changelog has .gitea/workflows/release.yml
en compile-commands-dir is relative to the repository root, not to the working directory tools/quality-checks-cpp.sh
en configuration the same way. tools/quality-checks-cpp.sh
en connect-tailscale.sh down log the node out and stop tailscaled actions/connect-tailscale/connect-tailscale.sh
en connect-tailscale.sh mask print ::add-mask:: for every secret input actions/connect-tailscale/connect-tailscale.sh
en connect-tailscale.sh up install tailscale if needed, start tailscaled and join the tailnet actions/connect-tailscale/connect-tailscale.sh
en connect-wireguard.sh down take the tunnel down and delete the configuration actions/connect-wireguard/connect-wireguard.sh
en connect-wireguard.sh mask print ::add-mask:: for every secret input actions/connect-wireguard/connect-wireguard.sh
en connect-wireguard.sh up write the configuration and bring the tunnel up actions/connect-wireguard/connect-wireguard.sh
en contents: write .gitea/workflows/code-metrics.yml
en deleted. Each run updates the issues of every accepted milestone (the sync is .gitea/workflows/sync-milestones.yml
en directory under RUNNER_TEMP by default). tools/code-metrics.sh
en env keeps the userspace variable through sudo; the file holds the key, so actions/connect-wireguard/connect-wireguard.sh
en finding must fail the check, as a failing tool does for the other languages. tools/quality-checks-cpp.sh
en for the tests. actions/connect-tailscale/connect-tailscale.sh
en idempotent, but the host records the edit). .gitea/workflows/sync-milestones.yml
en installed it on 2026-10-11 at the request of S01. .gitea/workflows/sync-milestones.yml
en into shared-scripts/ and runs one command per step: tools/quality-checks-cpp.sh
en is a file of TirSystem/github-action (tools/sync_github_metadata.py), fetched .gitea/scoped_workflows/sync-github-metadata.yml
en is created or updated as a Milestone with its tasks as Issues. A Proposed .gitea/workflows/sync-milestones.yml
en it is removed when the interface does not come up actions/connect-wireguard/connect-wireguard.sh
en it, up to the top of the repository: clang-format and clang-tidy find their tools/quality-checks-cpp.sh
en latest Version History row is Accepted and whose latest review record is a Go .gitea/workflows/sync-milestones.yml
en line, and nothing else, so the rest of the log stays readable actions/connect-wireguard/connect-wireguard.sh
en line. Every failure is printed as ::error::[category] message. actions/connect-tailscale/connect-tailscale.sh
en metrics: .gitea/workflows/code-metrics.yml
en milestone, or one without a Go review, is skipped. Nothing is closed or .gitea/workflows/sync-milestones.yml
en name -> (source, code, comment, blank), counted by hand. tests/test_count_loc.py
en names that start with GITEA_ or GITHUB_. .gitea/workflows/sync-milestones.yml
en no final newline tests/test_count_loc.py
en often has no sudo. TS_SUDO overrides it (empty for none). actions/connect-tailscale/connect-tailscale.sh
en often has no sudo. WG_SUDO overrides it (empty for none). actions/connect-wireguard/connect-wireguard.sh
en on failure the captured output is the ::error:: line, which must be shown actions/connect-wireguard/connect-wireguard.sh
en only a daemon this action started is logged out and stopped actions/connect-tailscale/connect-tailscale.sh
en only the key values are masked tests/test_connect_wireguard.py
en only the probe ran: no second daemon, no up tests/test_connect_tailscale.py
en outside this repository, so it gets a copy of the repository's file tests/test_lint_action_scripts.py
en page from the changelog entry and moves the major tag, and only when the .gitea/workflows/release.yml
en parsed YAML has no fixed shape tests/test_quality_workflows_cpp_csharp.py
en paths: [ "docs/wiki/**" ] .gitea/workflows/publish-wiki-pages.yml
en permissions: .gitea/workflows/code-metrics.yml
en pretend the kernel has WireGuard and the tools are present tests/test_connect_wireguard.py
en prune is true. The script is a file of this repository .gitea/workflows/publish-wiki-pages.yml
en quality-checks-cpp.sh install install the exact clang tool versions into a venv tools/quality-checks-cpp.sh
en quality-checks-cpp.sh run run clang-format and clang-tidy on the tracked files tools/quality-checks-cpp.sh
en quality-checks-cpp.sh validate check the inputs, before anything is installed tools/quality-checks-cpp.sh
en quality-checks-csharp.sh install install the exact .NET SDK version tools/quality-checks-csharp.sh
en quality-checks-csharp.sh run run dotnet format and dotnet build on the project tools/quality-checks-csharp.sh
en quality-checks-csharp.sh validate check the inputs, before anything is installed tools/quality-checks-csharp.sh
en quality-checks.sh install install the pinned tools into a virtual environment tools/quality-checks.sh
en quality-checks.sh run run every selected tool on the tracked files tools/quality-checks.sh
en quality-checks.sh validate check the inputs, before anything is installed tools/quality-checks.sh
en quality: .gitea/workflows/quality-checks-cpp.yml
en real/a.py only, not link.py tests/test_count_loc.py
en render_config: the wg-quick configuration on stdout actions/connect-wireguard/connect-wireguard.sh
en report (an artifact of an earlier job of the caller, or a file of the .gitea/workflows/code-metrics.yml
en repository needs a .clang-format; a .clang-tidy and a compile database .gitea/workflows/quality-checks-cpp.yml
en repository) and commits three SVG badges to a branch of the calling .gitea/workflows/code-metrics.yml
en repository, which is checked out as a submodule. Gitea does not accept secret .gitea/workflows/sync-milestones.yml
en repository. .gitea/workflows/code-metrics.yml
en reusable workflow of this repository (ADR-0009, MIL-008 task 4). It is the .gitea/workflows/publish-own-wiki.yml
en secrets: .gitea/workflows/publish-wiki-pages.yml
en shared-scripts/ and runs one command per step: tools/code-metrics.sh
en shellcheck disable=SC2206 actions/connect-tailscale/connect-tailscale.sh
en shfmt reads .editorconfig from the folder upwards; a temporary folder is tests/test_lint_action_scripts.py
en something differs from the branch. tools/code-metrics.sh
en stop at the top of the repository, or at the file system root tools/quality-checks-cpp.sh
en submodules (framework, and framework/qc inside it) are fetched over HTTPS .gitea/workflows/sync-milestones.yml
en sudo is used only when the job is not root: a job container runs as root and actions/connect-tailscale/connect-tailscale.sh
en the command is required tests/test_tool_entrypoints.py
en the plan and creates nothing. Turn dry_run off only for a real run. .gitea/workflows/sync-milestones.yml
en the project gets the compiler diagnostics and the static analyzer. tools/quality-checks-cpp.sh
en the stub records its arguments and, for `up`, the content of the key file tests/test_connect_tailscale.py
en the tools are in the virtual environment of the interpreter that runs the tests tests/test_lint_action_scripts.py
en tools-ref is main so that the script that is run is the one of this commit. .gitea/workflows/publish-own-wiki.yml
en two differences: the reusable workflow is called by its local path, and .gitea/workflows/publish-own-wiki.yml
en type: ignore[index] tools/changelog.py
en type: ignore[return-value] tools/changelog.py
en uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v2 .gitea/workflows/code-metrics.yml
en uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1 .gitea/workflows/publish-wiki-pages.yml
en uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1 .gitea/workflows/quality-checks-cpp.yml
en uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1 .gitea/workflows/quality-checks-csharp.yml
en uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1 .gitea/workflows/quality-checks.yml
en validate-workflows.yml measures tools/ and fails below the .coveragerc limit. tests/test_workflows.py
en validate-workflows.yml runs it on every pull request and push to main. tools/lint-action-scripts.sh
en variables that must reach wg-quick through sudo, which resets the environment actions/connect-wireguard/connect-wireguard.sh
en wait_for_daemon: tailscaled needs a moment to create its socket actions/connect-tailscale/connect-tailscale.sh
en which is the usual case in a Gitea runner container. actions/connect-wireguard/connect-wireguard.sh
en with a checkout so that it is readable, lintable and tested as a file .gitea/scoped_workflows/sync-github-metadata.yml
Can't render this file because it contains an unexpected character in line 2 and column 11.
+139
View File
@@ -0,0 +1,139 @@
lang text origin
da hent brugeren fra databasen written
da Denne funktion beregner totalen for kurven written
da vi skal huske at lukke forbindelsen bagefter written
da TODO: ret fejlen når listen er tom written
da returnerer sandt hvis filen findes written
da Gem resultatet i en midlertidig mappe written
da her tjekker vi om brugeren har adgang written
da læs konfigurationen fra miljøvariablerne written
da hvis der ikke er noget svar, prøver vi igen written
da Opret en ny forbindelse til serveren written
da dette er en midlertidig løsning indtil videre written
da loop gennem alle filerne og tæl linjerne written
da Vi bruger en cache for at spare tid written
da fejlbesked skal vises til brugeren written
da sorter listen efter dato, nyeste først written
da ingen grund til at gentage kaldet written
da Kontroller at tokenet ikke er udløbet written
da slet de gamle filer før vi starter written
da Her håndterer vi alle undtagelser written
da afrund til nærmeste hele tal written
da venter på at serveren bliver klar written
da Parametrene er valgfrie, standardværdien er nul written
da kald funktionen to gange for at være sikker written
da skriv loggen til disken hver time written
da Det her virker kun på Linux written
da vi springer tomme linjer over written
da opdater tælleren når en ny post kommer ind written
da fjern dubletter fra resultatet written
da Brug en kø så arbejdet ikke blokerer written
da den øverste mappe i projektet written
da gennemløb træet fra roden og ned written
da Denne klasse holder styr på sessionerne written
da hvis navnet er tomt, brug standardnavnet written
da tjek om porten er optaget written
da Beregn gennemsnittet af de sidste ti målinger written
da vi skal have en bedre løsning her senere written
da Sæt tiden til midnat written
da læser hele filen ind i hukommelsen written
da Pas på, her kan der opstå en kapløbstilstand written
da flyt filen til arkivmappen written
da Dette er kun til test written
da slå op i tabellen efter navn written
da henter næste side af resultaterne written
da hvorfor gør vi det her? Fordi serveren kræver det written
da gem ændringerne og luk vinduet written
ms dapatkan pengguna daripada pangkalan data written
ms Fungsi ini mengira jumlah bagi troli written
ms kita perlu ingat untuk menutup sambungan selepas itu written
ms TODO: betulkan ralat apabila senarai kosong written
ms mengembalikan benar jika fail wujud written
ms Simpan hasil dalam folder sementara written
ms di sini kita semak sama ada pengguna mempunyai akses written
ms baca konfigurasi daripada pemboleh ubah persekitaran written
ms jika tiada jawapan, kita cuba lagi written
ms Buat sambungan baharu ke pelayan written
ms ini penyelesaian sementara buat masa ini written
ms ulang semua fail dan kira baris written
ms Kita guna cache untuk jimat masa written
ms mesej ralat mesti dipaparkan kepada pengguna written
ms susun senarai mengikut tarikh, yang terbaru dahulu written
ms tidak perlu mengulang panggilan written
ms Semak bahawa token belum tamat tempoh written
ms padam fail lama sebelum kita mulakan written
ms Di sini kita mengendalikan semua pengecualian written
ms bundarkan kepada nombor bulat yang terdekat written
ms menunggu pelayan sedia written
ms Parameter adalah pilihan, nilai lalai ialah sifar written
ms panggil fungsi dua kali untuk pastikan written
ms tulis log ke cakera setiap jam written
ms Ini hanya berfungsi pada Linux written
ms kita langkau baris kosong written
ms kemas kini pembilang apabila rekod baharu masuk written
ms buang pendua daripada hasil written
ms Guna giliran supaya kerja tidak menyekat written
ms folder teratas dalam projek written
ms lalui pokok dari akar ke bawah written
ms Kelas ini menjejaki sesi written
ms jika nama kosong, guna nama lalai written
ms semak sama ada port sedang digunakan written
ms Kira purata sepuluh ukuran terakhir written
ms kita perlukan penyelesaian yang lebih baik nanti written
ms Tetapkan masa kepada tengah malam written
ms membaca seluruh fail ke dalam memori written
ms Awas, di sini boleh berlaku keadaan perlumbaan written
ms alihkan fail ke folder arkib written
ms Ini hanya untuk ujian written
ms cari dalam jadual mengikut nama written
ms mendapatkan halaman seterusnya bagi hasil written
ms kenapa kita buat begini? Kerana pelayan memerlukannya written
ms simpan perubahan dan tutup tetingkap written
de Hole den Benutzer aus der Datenbank written
de Diese Funktion berechnet die Summe des Warenkorbs written
de wir müssen die Verbindung danach schließen written
de Gibt wahr zurück, wenn die Datei existiert written
de Speichere das Ergebnis in einem temporären Ordner written
de hier prüfen wir, ob der Benutzer Zugriff hat written
de Lies die Konfiguration aus den Umgebungsvariablen written
de wenn keine Antwort kommt, versuchen wir es erneut written
de Das ist nur eine vorläufige Lösung written
de Fehlermeldung muss dem Benutzer angezeigt werden written
de lösche die alten Dateien, bevor wir anfangen written
de Achtung, hier kann ein Wettlauf entstehen written
sv hämta användaren från databasen written
sv Den här funktionen räknar ut summan av varukorgen written
sv vi måste stänga anslutningen efteråt written
sv returnerar sant om filen finns written
sv Spara resultatet i en tillfällig mapp written
sv här kontrollerar vi om användaren har åtkomst written
sv läs konfigurationen från miljövariablerna written
sv om det inte kommer något svar försöker vi igen written
sv Det här är bara en tillfällig lösning written
sv felmeddelandet ska visas för användaren written
sv ta bort de gamla filerna innan vi börjar written
sv Se upp, här kan det uppstå en tävlingssituation written
no hent brukeren fra databasen written
no Denne funksjonen beregner summen av handlekurven written
no vi må huske å lukke forbindelsen etterpå written
no returnerer sant hvis filen finnes written
no Lagre resultatet i en midlertidig mappe written
no her sjekker vi om brukeren har tilgang written
no les konfigurasjonen fra miljøvariablene written
no hvis det ikke kommer noe svar, prøver vi igjen written
no Dette er bare en midlertidig løsning written
no feilmeldingen skal vises til brukeren written
no slett de gamle filene før vi begynner written
no Pass på, her kan det oppstå en kappløpstilstand written
fr récupère l'utilisateur depuis la base de données written
fr Cette fonction calcule le total du panier written
fr il faut penser à fermer la connexion ensuite written
fr renvoie vrai si le fichier existe written
fr Enregistre le résultat dans un dossier temporaire written
fr ici on vérifie si l'utilisateur a accès written
fr lit la configuration depuis les variables d'environnement written
fr s'il n'y a pas de réponse, on réessaie written
fr Ce n'est qu'une solution provisoire written
fr le message d'erreur doit être affiché à l'utilisateur written
fr supprime les anciens fichiers avant de commencer written
fr Attention, une condition de concurrence peut survenir ici written
1 lang text origin
2 da hent brugeren fra databasen written
3 da Denne funktion beregner totalen for kurven written
4 da vi skal huske at lukke forbindelsen bagefter written
5 da TODO: ret fejlen når listen er tom written
6 da returnerer sandt hvis filen findes written
7 da Gem resultatet i en midlertidig mappe written
8 da her tjekker vi om brugeren har adgang written
9 da læs konfigurationen fra miljøvariablerne written
10 da hvis der ikke er noget svar, prøver vi igen written
11 da Opret en ny forbindelse til serveren written
12 da dette er en midlertidig løsning indtil videre written
13 da loop gennem alle filerne og tæl linjerne written
14 da Vi bruger en cache for at spare tid written
15 da fejlbesked skal vises til brugeren written
16 da sorter listen efter dato, nyeste først written
17 da ingen grund til at gentage kaldet written
18 da Kontroller at tokenet ikke er udløbet written
19 da slet de gamle filer før vi starter written
20 da Her håndterer vi alle undtagelser written
21 da afrund til nærmeste hele tal written
22 da venter på at serveren bliver klar written
23 da Parametrene er valgfrie, standardværdien er nul written
24 da kald funktionen to gange for at være sikker written
25 da skriv loggen til disken hver time written
26 da Det her virker kun på Linux written
27 da vi springer tomme linjer over written
28 da opdater tælleren når en ny post kommer ind written
29 da fjern dubletter fra resultatet written
30 da Brug en kø så arbejdet ikke blokerer written
31 da den øverste mappe i projektet written
32 da gennemløb træet fra roden og ned written
33 da Denne klasse holder styr på sessionerne written
34 da hvis navnet er tomt, brug standardnavnet written
35 da tjek om porten er optaget written
36 da Beregn gennemsnittet af de sidste ti målinger written
37 da vi skal have en bedre løsning her senere written
38 da Sæt tiden til midnat written
39 da læser hele filen ind i hukommelsen written
40 da Pas på, her kan der opstå en kapløbstilstand written
41 da flyt filen til arkivmappen written
42 da Dette er kun til test written
43 da slå op i tabellen efter navn written
44 da henter næste side af resultaterne written
45 da hvorfor gør vi det her? Fordi serveren kræver det written
46 da gem ændringerne og luk vinduet written
47 ms dapatkan pengguna daripada pangkalan data written
48 ms Fungsi ini mengira jumlah bagi troli written
49 ms kita perlu ingat untuk menutup sambungan selepas itu written
50 ms TODO: betulkan ralat apabila senarai kosong written
51 ms mengembalikan benar jika fail wujud written
52 ms Simpan hasil dalam folder sementara written
53 ms di sini kita semak sama ada pengguna mempunyai akses written
54 ms baca konfigurasi daripada pemboleh ubah persekitaran written
55 ms jika tiada jawapan, kita cuba lagi written
56 ms Buat sambungan baharu ke pelayan written
57 ms ini penyelesaian sementara buat masa ini written
58 ms ulang semua fail dan kira baris written
59 ms Kita guna cache untuk jimat masa written
60 ms mesej ralat mesti dipaparkan kepada pengguna written
61 ms susun senarai mengikut tarikh, yang terbaru dahulu written
62 ms tidak perlu mengulang panggilan written
63 ms Semak bahawa token belum tamat tempoh written
64 ms padam fail lama sebelum kita mulakan written
65 ms Di sini kita mengendalikan semua pengecualian written
66 ms bundarkan kepada nombor bulat yang terdekat written
67 ms menunggu pelayan sedia written
68 ms Parameter adalah pilihan, nilai lalai ialah sifar written
69 ms panggil fungsi dua kali untuk pastikan written
70 ms tulis log ke cakera setiap jam written
71 ms Ini hanya berfungsi pada Linux written
72 ms kita langkau baris kosong written
73 ms kemas kini pembilang apabila rekod baharu masuk written
74 ms buang pendua daripada hasil written
75 ms Guna giliran supaya kerja tidak menyekat written
76 ms folder teratas dalam projek written
77 ms lalui pokok dari akar ke bawah written
78 ms Kelas ini menjejaki sesi written
79 ms jika nama kosong, guna nama lalai written
80 ms semak sama ada port sedang digunakan written
81 ms Kira purata sepuluh ukuran terakhir written
82 ms kita perlukan penyelesaian yang lebih baik nanti written
83 ms Tetapkan masa kepada tengah malam written
84 ms membaca seluruh fail ke dalam memori written
85 ms Awas, di sini boleh berlaku keadaan perlumbaan written
86 ms alihkan fail ke folder arkib written
87 ms Ini hanya untuk ujian written
88 ms cari dalam jadual mengikut nama written
89 ms mendapatkan halaman seterusnya bagi hasil written
90 ms kenapa kita buat begini? Kerana pelayan memerlukannya written
91 ms simpan perubahan dan tutup tetingkap written
92 de Hole den Benutzer aus der Datenbank written
93 de Diese Funktion berechnet die Summe des Warenkorbs written
94 de wir müssen die Verbindung danach schließen written
95 de Gibt wahr zurück, wenn die Datei existiert written
96 de Speichere das Ergebnis in einem temporären Ordner written
97 de hier prüfen wir, ob der Benutzer Zugriff hat written
98 de Lies die Konfiguration aus den Umgebungsvariablen written
99 de wenn keine Antwort kommt, versuchen wir es erneut written
100 de Das ist nur eine vorläufige Lösung written
101 de Fehlermeldung muss dem Benutzer angezeigt werden written
102 de lösche die alten Dateien, bevor wir anfangen written
103 de Achtung, hier kann ein Wettlauf entstehen written
104 sv hämta användaren från databasen written
105 sv Den här funktionen räknar ut summan av varukorgen written
106 sv vi måste stänga anslutningen efteråt written
107 sv returnerar sant om filen finns written
108 sv Spara resultatet i en tillfällig mapp written
109 sv här kontrollerar vi om användaren har åtkomst written
110 sv läs konfigurationen från miljövariablerna written
111 sv om det inte kommer något svar försöker vi igen written
112 sv Det här är bara en tillfällig lösning written
113 sv felmeddelandet ska visas för användaren written
114 sv ta bort de gamla filerna innan vi börjar written
115 sv Se upp, här kan det uppstå en tävlingssituation written
116 no hent brukeren fra databasen written
117 no Denne funksjonen beregner summen av handlekurven written
118 no vi må huske å lukke forbindelsen etterpå written
119 no returnerer sant hvis filen finnes written
120 no Lagre resultatet i en midlertidig mappe written
121 no her sjekker vi om brukeren har tilgang written
122 no les konfigurasjonen fra miljøvariablene written
123 no hvis det ikke kommer noe svar, prøver vi igjen written
124 no Dette er bare en midlertidig løsning written
125 no feilmeldingen skal vises til brukeren written
126 no slett de gamle filene før vi begynner written
127 no Pass på, her kan det oppstå en kappløpstilstand written
128 fr récupère l'utilisateur depuis la base de données written
129 fr Cette fonction calcule le total du panier written
130 fr il faut penser à fermer la connexion ensuite written
131 fr renvoie vrai si le fichier existe written
132 fr Enregistre le résultat dans un dossier temporaire written
133 fr ici on vérifie si l'utilisateur a accès written
134 fr lit la configuration depuis les variables d'environnement written
135 fr s'il n'y a pas de réponse, on réessaie written
136 fr Ce n'est qu'une solution provisoire written
137 fr le message d'erreur doit être affiché à l'utilisateur written
138 fr supprime les anciens fichiers avant de commencer written
139 fr Attention, une condition de concurrence peut survenir ici written
+2
View File
@@ -3,3 +3,5 @@ PyYAML>=6,<7
coverage>=7,<8 coverage>=7,<8
shellcheck-py==0.10.0.1 shellcheck-py==0.10.0.1
shfmt-py==3.11.0.2 shfmt-py==3.11.0.2
langid==1.1.6
numpy==2.1.3
+1
View File
@@ -106,6 +106,7 @@ def _write_sources(root: Path) -> None:
"quality-checks-cpp", "quality-checks-cpp",
"quality-checks-csharp", "quality-checks-csharp",
"code-metrics", "code-metrics",
"english-comments",
"upgrading", "upgrading",
) )
for name in guide_names: for name in guide_names:
+274
View File
@@ -0,0 +1,274 @@
"""Offline tests for tools/english_comments.py (MIL-013, ADR-0012).
The detector is measured on the corpus in tests/fixtures/comments/; the rates
of criterion 1 of MIL-013 are tests, so a change that lowers them fails.
"""
import csv
import json
import shutil
import subprocess
from pathlib import Path
import pytest
from workflow_source import REPO_ROOT, load_script
ec = load_script(REPO_ROOT / "tools" / "english_comments.py", "english_comments")
CORPUS = REPO_ROOT / "tests" / "fixtures" / "comments"
GIT = shutil.which("git")
def corpus() -> list[tuple[str, str]]:
rows: list[tuple[str, str]] = []
for name in ("english.tsv", "foreign.tsv"):
with (CORPUS / name).open(encoding="utf-8", newline="") as handle:
reader = csv.DictReader(handle, delimiter="\t", quoting=csv.QUOTE_NONE)
rows += [(row["lang"], row["text"]) for row in reader]
return rows
@pytest.fixture(scope="module")
def results():
found: dict[str, list[int]] = {}
for language, text in corpus():
counts = found.setdefault(language, [0, 0])
counts[1] += 1
counts[0] += ec.judge(text) is not None
return found
class TestCorpusRates:
"""Criterion 1 of MIL-013: recall of at least 90 % for Danish and Malay, and at most
one false positive per 200 English comments."""
def test_the_corpus_has_the_size_the_milestone_asks_for(self, results):
assert results["en"][1] >= 200
assert results["da"][1] >= 40
assert results["ms"][1] >= 40
assert sum(v[1] for k, v in results.items() if k not in ("en", "da", "ms")) >= 40
def test_english_comments_are_not_flagged(self, results):
flagged, total = results["en"]
assert flagged <= total // 200
@pytest.mark.parametrize("language", ["da", "ms"])
def test_danish_and_malay_are_found(self, results, language):
flagged, total = results[language]
assert flagged / total >= 0.90
def test_other_languages_are_found(self, results):
for language in ("de", "sv", "no", "fr"):
flagged, total = results[language]
assert flagged / total >= 0.80
class TestWords:
def test_paths_options_identifiers_and_numbers_are_not_words(self):
text = "Run tools/badges.py --metrics code-metrics.json with snake_case camelCase v2 DIR"
assert ec.words(text) == ["Run", "with"]
def test_json_and_code_characters_are_not_words(self):
assert ec.words('{"files": 3, "lines": 120} x = 1 <a|b>') == []
def test_a_label_and_an_acronym_are_dropped(self):
assert ec.words("Usage: lint the JSON files") == ["lint", "the", "files"]
def test_a_hyphenated_word_is_split_and_allowed_words_are_removed(self):
assert ec.words("read-only access", frozenset({"access"})) == ["read", "only"]
def test_a_hidden_file_is_not_a_word_but_an_ellipsis_is_kept(self):
assert ec.words("needs a .clang-format file...") == ["needs", "file"]
def test_quotes_inside_a_word_are_kept_once(self):
assert ec.words("don't can't won''t") == ["don't", "can't"]
class TestExtract:
def test_python_comments_and_docstrings(self):
source = '"""Module line one.\n\nSecond paragraph."""\n\n# a comment\nx = 1 # trailing\n\ndef f():\n """Doc of f."""\n'
found = dict(ec.extract("a.py", source))
assert found[1] == "Module line one."
assert found[5] == "a comment"
assert found[6] == "trailing"
assert found[9] == "Doc of f."
def test_python_that_does_not_parse_falls_back_to_the_scanner(self):
assert dict(ec.extract("a.py", "def (:\n# still a comment\n")) == {2: "still a comment"}
def test_shell_and_sql_line_comments(self):
assert ec.extract("a.sh", "#!/bin/sh\n# real comment\necho '# not'\n") == [(2, "real comment")]
assert ec.extract("a.sql", "SELECT 1; -- one row\n") == [(1, "one row")]
def test_c_block_and_line_comments_lose_their_decoration(self):
source = "/**\n * First line\n * second line\n */\nint x; // trailing\n/// doc\n"
assert ec.extract("a.c", source) == [(2, "First line"), (3, "second line"), (5, "trailing"), (6, "doc")]
def test_a_comment_inside_a_string_is_not_a_comment(self):
assert ec.extract("a.c", 'char *s = "// not a comment";\n') == []
def test_tool_pragmas_are_not_prose(self):
source = "x = 1 # noqa: E501\n# type: ignore\n# pylint: disable=x\n# a real comment\n"
assert ec.extract("a.py", source) == [(4, "a real comment")]
def test_a_file_of_an_unknown_language_has_no_comments(self):
assert ec.extract("notes.md", "# Heading\n") == []
def test_consecutive_lines_are_one_block(self):
lines = [(1, "a"), (2, "b"), (4, "c"), (5, "d"), (5, "e")]
assert ec.blocks(lines) == [(1, 2, "a b"), (4, 5, "c d e")]
class TestCheckText:
DANISH = "# Denne funktion beregner totalen for kurven og gemmer resultatet\n"
def test_a_danish_comment_is_a_finding_with_its_line(self):
findings = ec.check_text("a.py", "x = 1\n" + self.DANISH)
assert [(f.path, f.line, f.language) for f in findings] == [("a.py", 2, "da")]
def test_an_english_comment_is_not(self):
assert ec.check_text("a.py", "# This function adds the totals of the basket together\n") == []
def test_only_the_blocks_on_the_given_lines_are_checked(self):
text = self.DANISH + "x = 1\n" + self.DANISH
assert [f.line for f in ec.check_text("a.py", text, {3})] == [3]
assert ec.check_text("a.py", text, {2}) == []
def test_the_marker_skips_the_block(self):
text = self.DANISH.rstrip() + " # english-comments: ignore\n"
assert ec.check_text("a.py", text) == []
def test_allowed_words_are_not_judged(self):
allowed = frozenset(self.DANISH.lower().replace("#", "").replace(",", "").split())
assert ec.check_text("a.py", self.DANISH, allowed=allowed) == []
def test_a_short_comment_is_not_judged(self):
assert ec.check_text("a.py", "# ret fejlen\n") == []
def test_json_in_a_docstring_is_not_french(self):
source = '"""Count.\n\n {"files": 3, "lines": 120, "comment": 14, "blank": 22,\n "languages": {"Python": {"files": 2, "lines": 100}}}\n"""\n'
assert ec.check_text("a.py", source) == []
def test_usage_lines_with_options_are_not_judged(self):
text = "# Usage: lint-action-scripts.sh [<directory>] (default: actions)\n"
assert ec.check_text("a.sh", text) == []
def test_describe_names_the_language_and_cuts_a_long_text(self):
finding = ec.Finding("a.py", 1, 1, "da", 0.93, "x " * 100)
message = ec.describe(finding)
assert message.startswith('looks Danish (0.93): "') and message.endswith('..."')
class TestAllowedWords:
def test_words_one_per_line_with_comments(self, tmp_path):
file = tmp_path / "allowed.txt"
file.write_text("Kubernetes\n# a comment\nSkat # a Danish tax name\n\n", encoding="utf-8")
assert ec.read_allowed_words(str(file)) == {"kubernetes", "skat"}
def test_no_file_named_is_no_words(self):
assert ec.read_allowed_words(None) == frozenset()
def test_an_unreadable_file_is_an_error(self, tmp_path):
with pytest.raises(ec.CheckError):
ec.read_allowed_words(str(tmp_path / "missing.txt"))
def run_git(directory: Path, *args: str) -> None:
subprocess.run(["git", *args], cwd=directory, check=True, capture_output=True)
@pytest.fixture
def repo(tmp_path):
"""A repository with a base commit on main and a branch that adds comments."""
if GIT is None:
pytest.skip("git is needed")
run_git(tmp_path, "init", "-q", "-b", "main")
run_git(tmp_path, "config", "user.email", "t@example.org")
run_git(tmp_path, "config", "user.name", "t")
run_git(tmp_path, "config", "commit.gpgsign", "false")
(tmp_path / "old.py").write_bytes(b"# Denne gamle funktion beregner totalen for kurven\nx = 1\n")
run_git(tmp_path, "add", ".")
run_git(tmp_path, "commit", "-q", "-m", "base")
run_git(tmp_path, "checkout", "-q", "-b", "feature")
return tmp_path
def commit_file(repo: Path, name: str, content: bytes) -> None:
(repo / name).write_bytes(content)
run_git(repo, "add", ".")
run_git(repo, "commit", "-q", "-m", name)
class TestRepository:
def test_only_the_comments_a_branch_adds_are_checked(self, repo, capsys):
commit_file(repo, "new.py", b"# Denne funktion beregner totalen for kurven\n")
code = ec.main(["check", "--directory", str(repo), "--base", "main", "--output", str(repo / "f.json")])
out = capsys.readouterr().out
assert code == 1
assert "::error file=new.py,line=1::[english-comments] looks Danish" in out
assert "old.py" not in out # the old Danish comment is not this branch's
saved = json.loads((repo / "f.json").read_text(encoding="utf-8"))
assert [(f["path"], f["line"], f["language"]) for f in saved] == [("new.py", 1, "da")]
def test_an_added_line_in_an_old_file_is_checked_and_the_old_ones_are_not(self, repo):
commit_file(repo, "old.py", b"# Denne gamle funktion beregner totalen for kurven\nx = 1\n# Hold the lock while the file is written\n")
findings, files = ec.check_repository(repo, "main", False, [], frozenset(), 0.6, 3)
assert findings == [] and files == 1
def test_a_clean_branch_exits_zero(self, repo, capsys):
commit_file(repo, "new.py", b"# This function adds the totals of the basket together\n")
assert ec.main(["check", "--directory", str(repo), "--base", "main"]) == 0
assert "All comments are in English: 1 file(s) checked." in capsys.readouterr().out
def test_scope_all_checks_every_tracked_file(self, repo, capsys):
assert ec.main(["check", "--directory", str(repo), "--scope", "all"]) == 1
assert "old.py:1" in capsys.readouterr().out
def test_excluded_binary_unknown_and_large_files_are_skipped(self, repo, monkeypatch):
danish = b"# Denne funktion beregner totalen for kurven og gemmer resultatet\n"
(repo / "vendor").mkdir()
commit_file(repo, "vendor/skip.py", danish)
commit_file(repo, "skipme.py", danish)
commit_file(repo, "bin.py", danish + b"\x00")
commit_file(repo, "notes.md", b"# Denne funktion beregner totalen for kurven\n")
monkeypatch.setattr(ec, "MAX_FILE_BYTES", 10)
commit_file(repo, "big.py", danish)
findings, files = ec.check_repository(repo, "main", False, ["skipme.py"], frozenset(), 0.6, 3)
assert (findings, files) == ([], 0)
def test_a_deleted_file_in_the_selection_is_skipped(self, repo):
commit_file(repo, "gone.py", b"x = 1\n")
(repo / "gone.py").unlink()
findings, files = ec.check_repository(repo, "main", False, [], frozenset(), 0.6, 3)
assert findings == []
def test_changed_lines_reads_every_hunk(self, repo):
commit_file(repo, "h.py", b"a\nb\nc\nd\n")
commit_file(repo, "h.py", b"a\nB\nc\nD\nE\n")
assert ec.changed_lines(repo, "main")["h.py"] == {1, 2, 3, 4, 5}
class TestMain:
def test_a_missing_base_is_a_configuration_error(self, repo, capsys):
assert ec.main(["check", "--directory", str(repo)]) == 2
assert "::error::[configuration] give --base" in capsys.readouterr().out
def test_an_unknown_base_is_a_configuration_error(self, repo, capsys):
assert ec.main(["check", "--directory", str(repo), "--base", "nope"]) == 2
assert "failed: fatal" in capsys.readouterr().out
def test_a_missing_directory_is_a_configuration_error(self, tmp_path, capsys):
assert ec.main(["check", "--directory", str(tmp_path / "x"), "--scope", "all"]) == 2
assert "does not exist" in capsys.readouterr().out
@pytest.mark.parametrize("flags", [["--threshold", "0"], ["--threshold", "1.5"], ["--min-words", "0"]])
def test_bad_numbers_are_a_configuration_error(self, tmp_path, capsys, flags):
assert ec.main(["check", "--directory", str(tmp_path), "--scope", "all", *flags]) == 2
assert "threshold" in capsys.readouterr().out
def test_langid_missing_is_a_configuration_error(self, repo, monkeypatch, capsys):
commit_file(repo, "new.py", b"# Denne funktion beregner totalen for kurven og gemmer resultatet\n")
monkeypatch.setattr(ec, "_identifier", None)
monkeypatch.setitem(__import__("sys").modules, "langid.langid", None)
assert ec.main(["check", "--directory", str(repo), "--base", "main"]) == 2
assert "langid is not installed" in capsys.readouterr().out
+277
View File
@@ -0,0 +1,277 @@
"""Offline tests for tools/english_comments_report.py and .gitea/workflows/english-comments.yml.
The Gitea API is a fake that records the writes, so no host is contacted.
"""
import io
import json
import urllib.error
import pytest
import yaml
from workflow_source import REPO_ROOT, load_script
report = load_script(REPO_ROOT / "tools" / "english_comments_report.py", "english_comments_report")
WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "english-comments.yml"
TOKEN = "t" * 40
FINDING = {
"path": "src/a.py",
"line": 4,
"end_line": 4,
"language": "da",
"probability": 0.93,
"text": "Denne funktion beregner totalen",
}
class FakeGitea:
"""Lists the comments of one pull request and records the writes."""
def __init__(self, comments=()):
self.comments = list(comments)
self.writes = []
def __call__(self, url, method="GET", headers=None, body=None):
assert headers["Authorization"] == f"token {TOKEN}"
if method == "GET":
return self.comments
self.writes.append((method, url.split("/repos/o/r")[-1], body["body"]))
return {"id": 99}
@pytest.fixture
def env(tmp_path, monkeypatch):
findings = tmp_path / "findings.json"
findings.write_text(json.dumps([FINDING]), encoding="utf-8")
values = {
"GITEA_API_URL": "https://git.example/api/v1/",
"SOURCE_REPOSITORY": "o/r",
"PR_NUMBER": "7",
"PR_AUTHOR": "anna",
"REPORT_TOKEN": TOKEN,
"FINDINGS_FILE": str(findings),
"RUN_URL": "https://git.example/o/r/actions/runs/5",
}
for key, value in values.items():
monkeypatch.setenv(key, value)
monkeypatch.setattr("builtins.print", lambda *a, **k: None)
return findings
def write_findings(path, findings):
path.write_text(json.dumps(findings), encoding="utf-8")
def test_a_finding_writes_one_comment_that_mentions_the_author(env):
gitea = FakeGitea()
assert report.main(request=gitea) == 0
[(method, path, body)] = gitea.writes
assert (method, path) == ("POST", "/issues/7/comments")
assert report.MARKER in body and body.startswith(report.MARKER)
assert "@anna" in body and "`src/a.py`" in body and "| 4 |" in body
assert "Denne funktion beregner totalen" in body
assert "[The run](https://git.example/o/r/actions/runs/5)" in body
def test_a_second_run_updates_the_comment_instead_of_adding_one(env):
old = {"id": 11, "body": f"{report.MARKER}\nsomething older"}
gitea = FakeGitea([{"id": 3, "body": "hello"}, old])
assert report.main(request=gitea) == 0
[(method, path, _)] = gitea.writes
assert (method, path) == ("PATCH", "/issues/comments/11")
def test_an_unchanged_comment_is_not_written_again(env):
body = report.failing_body("anna", [FINDING], "https://git.example/o/r/actions/runs/5")
gitea = FakeGitea([{"id": 11, "body": body}])
assert report.main(request=gitea) == 0
assert gitea.writes == []
def test_a_passing_run_marks_the_comment_resolved_without_a_mention(env):
write_findings(env, [])
gitea = FakeGitea([{"id": 11, "body": f"{report.MARKER}\n@anna failing"}])
assert report.main(request=gitea) == 0
[(method, path, body)] = gitea.writes
assert (method, path) == ("PATCH", "/issues/comments/11")
assert "passes" in body and "@anna" not in body
def test_a_passing_run_with_no_earlier_comment_writes_nothing(env):
write_findings(env, [])
gitea = FakeGitea()
assert report.main(request=gitea) == 0
assert gitea.writes == []
def test_comments_of_other_authors_are_not_touched(env):
gitea = FakeGitea([{"id": 3, "body": "no marker here"}])
report.main(request=gitea)
assert [w[0] for w in gitea.writes] == ["POST"]
def test_all_pages_of_comments_are_searched(env):
page = [{"id": n, "body": "x"} for n in range(report.PAGE_SIZE)]
calls = []
def paged(url, method="GET", headers=None, body=None):
if method != "GET":
calls.append((method, url))
return None
number = int(url.rsplit("page=", 1)[1])
return page if number == 1 else [{"id": 900, "body": report.MARKER}]
assert report.main(request=paged) == 0
assert calls == [("PATCH", "https://git.example/api/v1/repos/o/r/issues/comments/900")]
def test_no_token_writes_nothing_and_does_not_fail(env, monkeypatch):
monkeypatch.setenv("REPORT_TOKEN", "")
gitea = FakeGitea()
assert report.main(request=gitea) == 0
assert gitea.writes == []
def test_a_missing_findings_file_means_the_check_did_not_run(env, monkeypatch):
monkeypatch.setenv("FINDINGS_FILE", str(env.parent / "absent.json"))
assert report.main(request=FakeGitea()) == 0
def test_an_unreadable_findings_file_is_a_configuration_error(env, capsys):
env.write_text("not json", encoding="utf-8")
assert report.main(request=FakeGitea()) == 1
@pytest.mark.parametrize("name", ["GITEA_API_URL", "PR_NUMBER", "PR_AUTHOR"])
def test_a_missing_setting_is_a_configuration_error(env, monkeypatch, name):
monkeypatch.delenv(name)
assert report.main(request=FakeGitea()) == 1
def test_text_in_a_comment_cannot_mention_someone_or_break_the_table():
body = report.failing_body("anna", [{**FINDING, "text": "ping @bob `x` | y\nz"}])
assert "@bob" not in body.replace("@​bob", "")
assert "\\|" in body and "\n| " in body and "`x`" not in body
def test_long_lists_are_cut(env):
many = [{**FINDING, "line": n} for n in range(report.MAX_ROWS + 5)]
body = report.failing_body("anna", many)
assert "...and 5 more" in body
assert body.count("`src/a.py`") == report.MAX_ROWS
class FakeResponse(io.BytesIO):
def __enter__(self):
return self
def __exit__(self, *args):
return False
def test_request_json_sends_the_body_and_reads_the_answer(monkeypatch):
seen = {}
def fake_urlopen(request, timeout):
seen["method"] = request.get_method()
seen["data"] = request.data
return FakeResponse(b'{"id": 5}')
monkeypatch.setattr(report.urllib.request, "urlopen", fake_urlopen)
assert report.request_json("https://h/x", "POST", {"A": "b"}, {"body": "t"}) == {"id": 5}
assert seen == {"method": "POST", "data": b'{"body": "t"}'}
def test_request_json_returns_none_for_an_empty_answer(monkeypatch):
monkeypatch.setattr(report.urllib.request, "urlopen", lambda request, timeout: FakeResponse(b""))
assert report.request_json("https://h/x") is None
@pytest.mark.parametrize(
("code", "category"), [(401, "credentials"), (403, "permissions"), (500, "api")]
)
def test_http_errors_have_a_category(monkeypatch, code, category):
def fail(request, timeout):
raise urllib.error.HTTPError(request.full_url, code, "reason", {}, None)
monkeypatch.setattr(report.urllib.request, "urlopen", fail)
with pytest.raises(report.ReportError, match=rf"\[{category}\]"):
report.request_json("https://h/x?token=secret")
def test_an_unreachable_host_is_an_api_error(monkeypatch):
def fail(request, timeout):
raise urllib.error.URLError("down")
monkeypatch.setattr(report.urllib.request, "urlopen", fail)
with pytest.raises(report.ReportError, match=r"\[api\]"):
report.request_json("https://h/x")
def test_an_api_error_is_printed_without_the_token_and_fails_the_step(env, monkeypatch, capsys):
monkeypatch.undo() # restore print
for key, value in {
"GITEA_API_URL": "https://git.example/api/v1",
"SOURCE_REPOSITORY": "o/r",
"PR_NUMBER": "7",
"PR_AUTHOR": "anna",
"REPORT_TOKEN": TOKEN,
"FINDINGS_FILE": str(env),
}.items():
monkeypatch.setenv(key, value)
def refuse(url, method="GET", headers=None, body=None):
raise report.ReportError("permissions", "the token may not comment")
assert report.main(request=refuse) == 1
out = capsys.readouterr().out
assert "::error::[permissions]" in out and TOKEN not in out
@pytest.fixture(scope="module")
def document():
return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
class TestWorkflow:
def test_is_a_reusable_workflow_with_the_contract_inputs(self, document):
call = document[True]["workflow_call"] # PyYAML reads `on` as True
assert set(call["inputs"]) == {
"scope", "exclude", "allowed-words-file", "threshold", "min-words",
"langid-version", "numpy-version", "tools-ref",
}
assert call["inputs"]["threshold"]["default"] == "0.6"
assert call["inputs"]["min-words"]["default"] == "3"
assert call["inputs"]["scope"]["default"] == "changed"
assert call["secrets"]["REPORT_TOKEN"]["required"] is False
def test_permissions_are_explicit_and_small(self, document):
permissions = document["jobs"]["english-comments"]["permissions"]
assert permissions == {"contents": "read", "issues": "write", "pull-requests": "write"}
def test_checks_out_the_full_history_for_the_merge_base(self, document):
first = document["jobs"]["english-comments"]["steps"][0]
assert first["with"]["fetch-depth"] == 0
def test_the_check_does_not_end_the_job_before_the_author_is_told(self, document):
steps = {s.get("name"): s for s in document["jobs"]["english-comments"]["steps"]}
assert steps["Check the comments"]["continue-on-error"] is True
tell = steps["Tell the author"]
assert tell["continue-on-error"] is True and "always()" in tell["if"]
assert "steps.check.outcome == 'failure'" in steps["Fail the run when a comment is not in English"]["if"]
def test_inputs_reach_the_scripts_as_environment_variables_only(self, document):
for step in document["jobs"]["english-comments"]["steps"]:
assert "inputs." not in step.get("run", "")
assert "secrets." not in step.get("run", "")
def test_the_token_falls_back_to_the_token_of_the_run(self, document):
tell = next(s for s in document["jobs"]["english-comments"]["steps"] if s.get("name") == "Tell the author")
assert "secrets.REPORT_TOKEN || gitea.token" in tell["env"]["REPORT_TOKEN"]
def test_uses_gitea_contexts_only(self):
text = WORKFLOW.read_text(encoding="utf-8")
assert "gitea.event.pull_request.user.login" in text
assert "github." not in text.replace("TirSystem/github-action", "")
+25 -2
View File
@@ -58,8 +58,31 @@ def current_major():
return MAJOR_TAG_FILE.read_text(encoding="utf-8").strip() return MAJOR_TAG_FILE.read_text(encoding="utf-8").strip()
def released_in_any_tag(path):
"""True when some version tag holds the file; False for a script not released yet."""
tags = subprocess.run(
["git", "-C", str(REPO_ROOT), "tag", "--list", "v*"],
capture_output=True,
text=True,
check=False,
).stdout.split()
return any(
subprocess.run(
["git", "-C", str(REPO_ROOT), "cat-file", "-e", f"{tag}:{path}"],
capture_output=True,
check=False,
).returncode
== 0
for tag in tags
)
def script_in_tag(tag, path): def script_in_tag(tag, path):
"""True when the tag holds the file; True when the tag is not in this checkout.""" """True when the tag holds the file; True when the tag is not in this checkout.
A script that no tag holds yet is new in the unreleased version: the release
moves the major tag to it, so it does not count as missing (MIL-013).
"""
known = subprocess.run( known = subprocess.run(
["git", "-C", str(REPO_ROOT), "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"], ["git", "-C", str(REPO_ROOT), "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"],
capture_output=True, capture_output=True,
@@ -72,7 +95,7 @@ def script_in_tag(tag, path):
capture_output=True, capture_output=True,
check=False, check=False,
) )
return holds.returncode == 0 return holds.returncode == 0 or not released_in_any_tag(path)
def test_major_tag_file_names_the_highest_major_of_the_changelog(): def test_major_tag_file_names_the_highest_major_of_the_changelog():
+5 -3
View File
@@ -47,6 +47,7 @@ PAGES: tuple[Page, ...] = (
Page("QualityChecksCpp", "guides/quality-checks-cpp.md"), Page("QualityChecksCpp", "guides/quality-checks-cpp.md"),
Page("QualityChecksCSharp", "guides/quality-checks-csharp.md"), Page("QualityChecksCSharp", "guides/quality-checks-csharp.md"),
Page("CodeMetrics", "guides/code-metrics.md"), Page("CodeMetrics", "guides/code-metrics.md"),
Page("EnglishComments", "guides/english-comments.md"),
Page("Upgrading", "guides/upgrading.md"), Page("Upgrading", "guides/upgrading.md"),
Page("Capabilities", "README.md", ("Capabilities",)), Page("Capabilities", "README.md", ("Capabilities",)),
) )
@@ -68,6 +69,7 @@ _PAGE_FOR_SOURCE = {
"guides/quality-checks-cpp.md": "QualityChecksCpp", "guides/quality-checks-cpp.md": "QualityChecksCpp",
"guides/quality-checks-csharp.md": "QualityChecksCSharp", "guides/quality-checks-csharp.md": "QualityChecksCSharp",
"guides/code-metrics.md": "CodeMetrics", "guides/code-metrics.md": "CodeMetrics",
"guides/english-comments.md": "EnglishComments",
"guides/upgrading.md": "Upgrading", "guides/upgrading.md": "Upgrading",
} }
@@ -193,9 +195,9 @@ def render_page(page: Page, repo_root: Path = REPO_ROOT) -> str:
# Link text of the sidebar: English, then one list per translation code. # Link text of the sidebar: English, then one list per translation code.
SIDEBAR_TITLE = "TirSystem GitHub Actions" SIDEBAR_TITLE = "TirSystem GitHub Actions"
SIDEBAR_LABELS: dict[str, tuple[str, ...]] = { SIDEBAR_LABELS: dict[str, tuple[str, ...]] = {
"en": ("Home", "Onboarding", "Credentials", "Troubleshooting", "Quality checks (Python, shell)", "Quality checks (C and C++)", "Quality checks (C#)", "Code metrics", "Upgrading", "Capabilities"), "en": ("Home", "Onboarding", "Credentials", "Troubleshooting", "Quality checks (Python, shell)", "Quality checks (C and C++)", "Quality checks (C#)", "Code metrics", "English comments", "Upgrading", "Capabilities"),
"da": ("Hjem", "Tilslutning", "Credentials", "Fejlfinding", "Kvalitetskontrol (Python, shell)", "Kvalitetskontrol (C og C++)", "Kvalitetskontrol (C#)", "Kodemålinger", "Opgradering", "Kapabiliteter"), "da": ("Hjem", "Tilslutning", "Credentials", "Fejlfinding", "Kvalitetskontrol (Python, shell)", "Kvalitetskontrol (C og C++)", "Kvalitetskontrol (C#)", "Kodemålinger", "Engelske kommentarer", "Opgradering", "Kapabiliteter"),
"ms": ("Utama", "Menyertakan", "Kelayakan", "Diagnosis", "Semakan kualiti (Python, shell)", "Semakan kualiti (C dan C++)", "Semakan kualiti (C#)", "Metrik kod", "Menaik taraf", "Keupayaan"), "ms": ("Utama", "Menyertakan", "Kelayakan", "Diagnosis", "Semakan kualiti (Python, shell)", "Semakan kualiti (C dan C++)", "Semakan kualiti (C#)", "Metrik kod", "Komen dalam bahasa Inggeris", "Menaik taraf", "Keupayaan"),
} }
SIDEBAR_NAME = "_Sidebar.md" SIDEBAR_NAME = "_Sidebar.md"
+16 -2
View File
@@ -161,8 +161,17 @@ def _skip_string(line: str, start: int, quote: str, escapes: bool) -> int:
return len(line) return len(line)
def _scan_line(line: str, language: Language, state: _State) -> tuple[bool, bool]: def _scan_line(
"""Scan one line; return (has_code, has_comment) and update the state.""" line: str,
language: Language,
state: _State,
comments: list[str] | None = None,
) -> tuple[bool, bool]:
"""Scan one line; return (has_code, has_comment) and update the state.
When ``comments`` is given, the text of every comment on the line is
appended to it (the English-comments check, ADR-0012, reads that text).
"""
has_code = False has_code = False
has_comment = False has_comment = False
i = 0 i = 0
@@ -174,6 +183,9 @@ def _scan_line(line: str, language: Language, state: _State) -> tuple[bool, bool
has_comment = True has_comment = True
end = line.find(block[1], i) end = line.find(block[1], i)
nested = line.find(block[0], i) if language.nested_block else -1 nested = line.find(block[0], i) if language.nested_block else -1
if comments is not None:
stop = nested if nested != -1 and (end == -1 or nested < end) else end
comments.append(line[i : size if stop == -1 else stop])
if nested != -1 and (end == -1 or nested < end): if nested != -1 and (end == -1 or nested < end):
state.block_depth += 1 state.block_depth += 1
i = nested + len(block[0]) i = nested + len(block[0])
@@ -212,6 +224,8 @@ def _scan_line(line: str, language: Language, state: _State) -> tuple[bool, bool
if comment is not None and line.startswith(comment, i): if comment is not None and line.startswith(comment, i):
word_start = i == 0 or line[i - 1] in _WORD_START word_start = i == 0 or line[i - 1] in _WORD_START
if word_start or not language.hash_at_word_start: if word_start or not language.hash_at_word_start:
if comments is not None:
comments.append(line[i + len(comment) :])
return has_code, True return has_code, True
if language.triple_quotes and ( if language.triple_quotes and (
line.startswith('"""', i) or line.startswith("'''", i) line.startswith('"""', i) or line.startswith("'''", i)
+387
View File
@@ -0,0 +1,387 @@
"""Find comments in source code that are not written in English (MIL-013, ADR-0012).
Run by .gitea/workflows/english-comments.yml. It reads the comments and docstrings
of the source files of the languages of ADR-0010, joins the lines of one comment
block, and judges the block with ``langid`` (restricted to a few candidate
languages) after removing everything that is not a word: paths, options,
identifiers, numbers and the words of the allowed-words file. A block with fewer
than ``--min-words`` words left is not judged.
Usage:
python tools/english_comments.py check [--directory DIR] [--scope changed|all]
[--base REF] [--exclude PATTERNS] [--allowed-words FILE]
[--threshold P] [--min-words N] [--output FILE]
With ``--scope changed`` (the default) only the comments on lines that the
current branch added since ``--base`` (a git ref, for example ``origin/main``)
are checked, so a pull request is not blamed for a comment it did not write;
``--scope all`` checks every tracked file. A comment block that contains ``english-comments: ignore`` is
skipped (a quotation, a name).
Every finding is printed as ``::error file=PATH,line=N::...`` and as ``PATH:N:
looks Danish (0.93): "text"``; ``--output`` writes them as JSON for the
reporting step. Exit codes: 0 no finding, 1 at least one finding, 2 a setting
or a missing tool (printed as ``::error::[configuration] ...``).
"""
import argparse
import ast
import importlib.util
import io
import json
import re
import subprocess
import sys
import tokenize
from dataclasses import asdict, dataclass
from pathlib import Path
DEFAULT_THRESHOLD = 0.6
DEFAULT_MIN_WORDS = 3
MAX_FILE_BYTES = 1_000_000
CANDIDATES = ["en", "da", "no", "sv", "de", "fr", "ms", "id", "nl"]
LANGUAGE_NAMES = {
"da": "Danish",
"no": "Norwegian",
"sv": "Swedish",
"de": "German",
"fr": "French",
"ms": "Malay",
"id": "Indonesian",
"nl": "Dutch",
}
IGNORE_MARKER = re.compile(r"english-comments:\s*ignore", re.IGNORECASE)
# comments that configure a tool are not prose
_PRAGMA = re.compile(
r"^(noqa|type:|pragma|shellcheck|fmt:|pylint|ruff:|mypy:|pyright:|eslint|prettier|"
r"-\*-|vim:|coding[:=]|nosec|isort:|nolint|clang-format|cppcheck|lint:)",
re.IGNORECASE,
)
_WORD = re.compile(r"^[^\W\d_]+(?:'[^\W\d_]+)?$")
_NOT_WORD_CHARS = re.compile(r"[\d_/\{}\[\]\"`=<>|$@#%&+^~*]")
_HUNK = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
class CheckError(RuntimeError):
"""A setting or tool problem (exit code 2)."""
def _load_sibling(name: str):
path = Path(__file__).resolve().parent / f"{name}.py"
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
count_loc = _load_sibling("count_loc")
@dataclass(frozen=True)
class Finding:
"""A comment block that is probably not English."""
path: str
line: int
end_line: int
language: str
probability: float
text: str
def _clean(text: str) -> str:
"""Remove comment decoration (``*``, ``/``, ``#``, ``-``, ``=``) from both ends."""
text = re.sub(r"^[/*!#<>=\-\s]+", "", text.strip())
return re.sub(r"[*/\s]+$", "", text).strip()
def _python_comments(source: str) -> list[tuple[int, str]]:
"""Comments (tokenizer) and docstring lines (ast) of a Python file."""
found: list[tuple[int, str]] = []
for token in tokenize.generate_tokens(io.StringIO(source).readline):
if token.type == tokenize.COMMENT:
found.append((token.start[0], _clean(token.string)))
for node in ast.walk(ast.parse(source)):
if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
first = node.body[0] if node.body else None
if (
isinstance(first, ast.Expr)
and isinstance(first.value, ast.Constant)
and isinstance(first.value.value, str)
):
for offset, line in enumerate(first.value.value.splitlines()):
found.append((first.value.lineno + offset, _clean(line)))
return found
def _scanned_comments(text: str, language) -> list[tuple[int, str]]:
"""Comments of any other language, from the scanner of the code counter."""
state = count_loc._State()
found: list[tuple[int, str]] = []
lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
for number, line in enumerate(lines, start=1):
pieces: list[str] = []
count_loc._scan_line(line, language, state, pieces)
if number == 1 and pieces and pieces[0].startswith("!"):
pieces = pieces[1:] # a shebang line is not a comment
joined = " ".join(_clean(piece) for piece in pieces).strip()
if joined:
found.append((number, joined))
return found
def extract(path: str, text: str) -> list[tuple[int, str]]:
"""Return (line, text) for every comment line of a source file, sorted by line.
A file of a language the code counter does not know has no comments here.
"""
language = count_loc.language_of(path)
if language is None:
return []
if language.name == "Python":
try:
found = _python_comments(text)
except (SyntaxError, tokenize.TokenError):
found = _scanned_comments(text, language)
else:
found = _scanned_comments(text, language)
return sorted((n, t) for n, t in found if t and not _PRAGMA.match(t))
def blocks(lines: list[tuple[int, str]]) -> list[tuple[int, int, str]]:
"""Join comment lines on consecutive line numbers: (first line, last line, text)."""
joined: list[tuple[int, int, str]] = []
for number, text in lines:
if joined and joined[-1][1] in (number, number - 1):
first, _, previous = joined[-1]
joined[-1] = (first, number, f"{previous} {text}")
else:
joined.append((number, number, text))
return joined
def words(text: str, allowed: frozenset[str] = frozenset()) -> list[str]:
"""The words of a comment: no URLs, paths, options, identifiers or allowed words."""
out: list[str] = []
tokens = text.split()
if tokens and tokens[0].endswith(":") and tokens[0][:-1].isalpha():
tokens = tokens[1:] # a label such as "Usage:" or "Note:" is not prose
for token in tokens:
if len(token) > 1 and token[0] == "." and token[1].isalpha():
continue # a hidden file such as .clang-format
token = token.strip("().,;:!?")
# a token with code characters (quotes, braces, `=`, `/`, `_`, digits) is not a word
if not token or _NOT_WORD_CHARS.search(token) or "::" in token:
continue
if token.startswith("-") or "." in token or (len(token) > 1 and token.isupper()):
continue # an option, a file name, an acronym or a constant
if re.search(r"[a-z][A-Z]", token):
continue
for part in token.split("-"):
if len(part) > 1 and _WORD.match(part) and part.lower() not in allowed:
out.append(part)
return out
_identifier = None
def _identifier_model():
global _identifier
if _identifier is None:
try:
from langid.langid import LanguageIdentifier, model
except ImportError:
raise CheckError("langid is not installed (pip install langid==1.1.6).") from None
_identifier = LanguageIdentifier.from_modelstring(model, norm_probs=True)
_identifier.set_languages(CANDIDATES)
return _identifier
def judge(
text: str,
allowed: frozenset[str] = frozenset(),
threshold: float = DEFAULT_THRESHOLD,
min_words: int = DEFAULT_MIN_WORDS,
) -> tuple[str, float] | None:
"""Return (language code, probability) when the text is not English, else None."""
kept = words(text, allowed)
if len(kept) < min_words:
return None
code, probability = _identifier_model().classify(" ".join(kept))
if code != "en" and probability >= threshold:
return code, float(probability)
return None
def check_text(
path: str,
text: str,
lines: set[int] | None = None,
allowed: frozenset[str] = frozenset(),
threshold: float = DEFAULT_THRESHOLD,
min_words: int = DEFAULT_MIN_WORDS,
) -> list[Finding]:
"""Findings of one file; with ``lines`` only the blocks that touch those line numbers."""
findings: list[Finding] = []
for first, last, body in blocks(extract(path, text)):
if IGNORE_MARKER.search(body):
continue
if lines is not None and not any(first <= n <= last for n in lines):
continue
verdict = judge(body, allowed, threshold, min_words)
if verdict:
findings.append(Finding(path, first, last, verdict[0], round(verdict[1], 2), body))
return findings
def read_allowed_words(path: str | None) -> frozenset[str]:
"""Words, one per line, ``#`` starts a comment; the file may be missing only when not named."""
if not path:
return frozenset()
try:
content = Path(path).read_text(encoding="utf-8")
except OSError:
raise CheckError(f"allowed-words file '{path}' cannot be read.") from None
return frozenset(
line.split("#", 1)[0].strip().lower()
for line in content.splitlines()
if line.split("#", 1)[0].strip()
)
def _git(directory: Path, *args: str) -> str:
result = subprocess.run(
["git", "-c", "core.quotepath=off", *args],
cwd=directory,
capture_output=True,
text=True,
encoding="utf-8",
check=False,
)
if result.returncode != 0:
raise CheckError(f"git {' '.join(args[:2])} failed: {result.stderr.strip()[:200]}")
return result.stdout
def changed_lines(directory: Path, base: str) -> dict[str, set[int]]:
"""Added or changed line numbers per file since the merge base with ``base``."""
out = _git(directory, "diff", "-U0", "--no-color", "--diff-filter=ACMR", f"{base}...HEAD")
result: dict[str, set[int]] = {}
current: set[int] | None = None
for line in out.splitlines():
if line.startswith("+++ "):
name = line[4:]
current = result.setdefault(name[2:], set()) if name.startswith("b/") else None
continue
hunk = _HUNK.match(line)
if hunk and current is not None:
start = int(hunk.group(1))
size = int(hunk.group(2)) if hunk.group(2) is not None else 1
current.update(range(start, start + size))
return result
def tracked_files(directory: Path) -> list[str]:
return [name for name in _git(directory, "ls-files").splitlines() if name]
def _excluded(path: str, excludes) -> bool:
parts = path.split("/")
if any(part in count_loc.DEFAULT_EXCLUDED_DIRS for part in parts[:-1]):
return True
return excludes.matches(parts[-1], path)
def check_repository(
directory: Path,
base: str | None,
check_all: bool,
patterns: list[str],
allowed: frozenset[str],
threshold: float,
min_words: int,
) -> tuple[list[Finding], int]:
"""Return (findings, number of files read)."""
excludes = count_loc.Excludes(patterns)
if check_all:
selection: dict[str, set[int] | None] = {name: None for name in tracked_files(directory)}
else:
if not base:
raise CheckError("give --base (a git ref such as origin/main) or --all.")
selection = dict(changed_lines(directory, base))
findings: list[Finding] = []
read = 0
for name in sorted(selection):
if count_loc.language_of(name) is None or _excluded(name, excludes):
continue
target = directory / name
if not target.is_file() or target.stat().st_size > MAX_FILE_BYTES:
continue
data = target.read_bytes()
if not count_loc._is_text(data):
continue
read += 1
text = data.decode("utf-8", errors="replace")
findings += check_text(name, text, selection[name], allowed, threshold, min_words)
return findings, read
def describe(finding: Finding) -> str:
name = LANGUAGE_NAMES.get(finding.language, finding.language)
shown = finding.text if len(finding.text) <= 120 else finding.text[:117] + "..."
return f'looks {name} ({finding.probability}): "{shown}"'
def _split(value: str) -> list[str]:
return [item.strip() for item in re.split(r"[,\n]", value) if item.strip()]
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Check that source code comments are in English.")
parser.add_argument("command", choices=["check"])
parser.add_argument("--directory", default=".")
parser.add_argument("--base", default="")
parser.add_argument("--scope", choices=["changed", "all"], default="changed")
parser.add_argument("--exclude", default="")
parser.add_argument("--allowed-words", default="")
parser.add_argument("--threshold", type=float, default=DEFAULT_THRESHOLD)
parser.add_argument("--min-words", type=int, default=DEFAULT_MIN_WORDS)
parser.add_argument("--output", default="")
args = parser.parse_args(argv)
try:
if not 0.0 < args.threshold <= 1.0 or args.min_words < 1:
raise CheckError("threshold must be above 0 and at most 1, and min-words at least 1.")
directory = Path(args.directory)
if not directory.is_dir():
raise CheckError(f"directory '{args.directory}' does not exist.")
allowed = read_allowed_words(args.allowed_words or None)
findings, files = check_repository(
directory,
args.base or None,
args.scope == "all",
_split(args.exclude),
allowed,
args.threshold,
args.min_words,
)
except CheckError as error:
print(f"::error::[configuration] {error}")
return 2
for finding in findings:
message = describe(finding)
print(f"::error file={finding.path},line={finding.line}::[english-comments] {message}")
print(f"{finding.path}:{finding.line}: {message}")
if args.output:
Path(args.output).write_text(
json.dumps([asdict(f) for f in findings], indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
summary = f"{len(findings)} comment(s) not in English in {files} file(s) checked."
print(summary if findings else f"All comments are in English: {files} file(s) checked.")
return 1 if findings else 0
if __name__ == "__main__":
sys.exit(main())
+172
View File
@@ -0,0 +1,172 @@
"""Tell the author of a pull request which comments are not in English (MIL-013).
Run by .gitea/workflows/english-comments.yml after the check. It reads the
findings file of tools/english_comments.py and keeps ONE comment on the pull
request:
- findings: the comment mentions the author and lists each finding; an
existing comment of this check is updated, never a second one added;
- no findings: an existing comment of this check is marked resolved (and no
longer mentions the author); with none nothing is written.
Settings come from the environment:
GITEA_API_URL Gitea API base, for example https://host/api/v1
SOURCE_REPOSITORY owner/repo of the pull request
PR_NUMBER the number of the pull request
PR_AUTHOR the login of the author, mentioned in the comment
REPORT_TOKEN a token that may comment on pull requests (a CI secret);
without it nothing is written and the step ends with a
warning (the check run itself still fails)
FINDINGS_FILE the JSON file written by english_comments.py --output
RUN_URL optional, linked in the comment
Every failure is printed as ``::error::[category] message`` with the categories
configuration, credentials, permissions and api; the token is never printed.
"""
import json
import os
import sys
import urllib.error
import urllib.request
MARKER = "<!-- english-comments-check -->"
REQUEST_TIMEOUT_SECONDS = 30
PAGE_SIZE = 50
MAX_ROWS = 30
class ReportError(RuntimeError):
"""A failure with a category: configuration, credentials, permissions or api."""
def __init__(self, category: str, message: str) -> None:
super().__init__(f"[{category}] {message}")
def request_json(url, method="GET", headers=None, body=None):
request = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8") if body is not None else None,
headers=headers or {},
method=method,
)
try:
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response:
content = response.read()
return json.loads(content) if content else None
except urllib.error.HTTPError as error:
target = url.split("?")[0]
detail = f"Gitea API request failed with HTTP {error.code} ({error.reason}) for {method} {target}"
if error.code == 401:
raise ReportError("credentials", f"{detail}. The token was rejected.") from None
if error.code == 403:
raise ReportError(
"permissions", f"{detail}. The token may not comment on pull requests."
) from None
raise ReportError("api", detail) from None
except (urllib.error.URLError, TimeoutError) as error:
raise ReportError("api", f"Gitea API is unreachable ({error}).") from None
def _cell(text: str, limit: int = 100) -> str:
"""Text safe for a table cell: no pipes, line breaks, mentions or backticks."""
text = " ".join(text.split()).replace("|", "\\|").replace("`", "'").replace("@", "@​")
return text if len(text) <= limit else text[: limit - 3] + "..."
def failing_body(author: str, findings: list[dict], run_url: str = "") -> str:
rows = [
f"| `{_cell(f['path'], 80)}` | {f['line']} | {_cell(str(f['language']))} {f['probability']} | {_cell(f['text'])} |"
for f in findings[:MAX_ROWS]
]
more = len(findings) - MAX_ROWS
lines = [
MARKER,
f"@{author}, the **English comments** check found {len(findings)} comment(s) that do not look "
"like English. Source code comments are written in English only.",
"",
"| File | Line | Looks like | Comment |",
"| --- | --- | --- | --- |",
*rows,
]
if more > 0:
lines.append(f"\n...and {more} more; see the run.")
lines += [
"",
"Rewrite the comment in English. For a quotation or a name, add `english-comments: ignore` "
"to the comment, or add the word to the allowed-words file.",
]
if run_url:
lines.append(f"\n[The run]({run_url})")
return "\n".join(lines)
def resolved_body() -> str:
return f"{MARKER}\nThe **English comments** check passes: every checked comment is in English."
def find_comment(api, headers, request, repository, number):
"""Return the newest comment of this check on the pull request, or None."""
found = None
page = 1
while True:
url = f"{api}/repos/{repository}/issues/{number}/comments?limit={PAGE_SIZE}&page={page}"
comments = request(url, "GET", headers, None) or []
for comment in comments:
if MARKER in (comment.get("body") or ""):
found = comment
if len(comments) < PAGE_SIZE:
return found
page += 1
def main(request=request_json) -> int:
env = os.environ
token = env.get("REPORT_TOKEN", "")
try:
missing = [
name
for name in ("GITEA_API_URL", "SOURCE_REPOSITORY", "PR_NUMBER", "PR_AUTHOR", "FINDINGS_FILE")
if not env.get(name)
]
if missing:
raise ReportError("configuration", f"{', '.join(missing)} not set.")
if not os.path.exists(env["FINDINGS_FILE"]):
print("::warning::The check wrote no findings file (it did not run); nothing to report.")
return 0
try:
with open(env["FINDINGS_FILE"], encoding="utf-8") as handle:
findings = json.load(handle)
except (OSError, ValueError):
raise ReportError("configuration", f"findings file '{env['FINDINGS_FILE']}' cannot be read.") from None
if not token:
print("::warning::No REPORT_TOKEN: the pull request is not commented. The check itself still decides.")
return 0
api = env["GITEA_API_URL"].rstrip("/")
repository = env["SOURCE_REPOSITORY"]
number = env["PR_NUMBER"]
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
existing = find_comment(api, headers, request, repository, number)
if findings:
body = failing_body(env["PR_AUTHOR"], findings, env.get("RUN_URL", ""))
elif existing:
body = resolved_body()
else:
print("No findings and no earlier comment: nothing to write.")
return 0
if existing:
if existing.get("body") != body:
request(f"{api}/repos/{repository}/issues/comments/{existing['id']}", "PATCH", headers, {"body": body})
print("Updated the comment of the check.")
else:
request(f"{api}/repos/{repository}/issues/{number}/comments", "POST", headers, {"body": body})
print("Wrote a comment on the pull request.")
return 0
except ReportError as error:
print(f"::error::{error}")
return 1
if __name__ == "__main__":
sys.exit(main())