From 0e86d1675909b44cd93a59f80705fd0bd69f25f4 Mon Sep 17 00:00:00 2001 From: x0rw Date: Fri, 30 May 2025 23:05:47 +0100 Subject: [PATCH 01/30] Triger workflow --- test/test.test | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/test.test diff --git a/test/test.test b/test/test.test new file mode 100644 index 0000000..e69de29 From fa8508650971b20ff71ecfc18357de8e864ab307 Mon Sep 17 00:00:00 2001 From: x0rw Date: Fri, 30 May 2025 23:12:15 +0100 Subject: [PATCH 02/30] test auto-pr template test auto-pr template 2 test auto-pr template 3 test auto-pr template 4 test auto-pr template 5 test auto-pr template 6 test auto-pr template 7 test auto-pr template 8 test auto-pr template 9 --- .github/workflows/auto-pr-on-issue.yml | 47 ++++++++ scripts/auto-pr-helper.py | 158 +++++++++++++++++++++++++ scripts/test_issue_sample.json | 124 +++++++++++++++++++ src/coding-guidelines/concurrency.rst | 74 ++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 .github/workflows/auto-pr-on-issue.yml create mode 100644 scripts/auto-pr-helper.py create mode 100644 scripts/test_issue_sample.json diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml new file mode 100644 index 0000000..44f4747 --- /dev/null +++ b/.github/workflows/auto-pr-on-issue.yml @@ -0,0 +1,47 @@ +name: Auto PR + +on: + issues: + types: + - labeled + +jobs: + auto-pr: + if: contains(github.event.label.name, 'accepted') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Git + run: | + git config --global user.email "action@github.com" + git config --global user.name "GitHub Action" + + # - name: Save issue JSON payload to file + # run: echo '${{ toJson(github.event.issue) }}' > issue.json + # pass it directly -- mayne fallback to this if an issue happened due to + # pipe-ing or encoding + + - name: Run Python script to generate guideline file + run: | + echo '${{ toJson(github.event.issue) }}' | python3 scripts/auto-pr-helper.py + + - name: Commit generated guideline files + run: | + git add src/coding-guidelines/ + git commit -m "Add guideline for issue #${{ github.event.issue.number }}" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "Add guideline for issue #${{ github.event.issue.number }}" + branch: guideline-${{ github.event.issue.number }} + title: "Add guideline from issue #${{ github.event.issue.number }}" + body: | + This PR was automatically generated from issue #${{ github.event.issue.number }}. + Closes #${{ github.event.issue.number }}. diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py new file mode 100644 index 0000000..9f26243 --- /dev/null +++ b/scripts/auto-pr-helper.py @@ -0,0 +1,158 @@ +import json +import os +import re +import random +import string + +CHARS = string.ascii_letters + string.digits +ID_LENGTH = 12 + +def generate_id(prefix): + """Generate a random ID with the given prefix.""" + random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) + return f"{prefix}_{random_part}" + +def extract_form_fields(issue_body: str) -> dict: + # Mapping from issue body headers to dict keys + header_map = { + "Chapter": "Chapter", + "Guideline Title": "Guideline Title", + "Category": "Category", + "Status": "Status", + "Release Begin": "Release Begin", + "Release End": "Release End", + "FLS Paragraph ID": "Fls Paragraph Id", + "Decidability": "Decidability", + "Scope": "Scope", + "Tags": "Tags", + "Amplification": "Amplification", + "Exception(s)": "Exception(S)", + "Rationale": "Rationale", + "Non-Compliant Example - Prose": "Non Compliant Example Prose", + "Non-Compliant Example - Code": "Non Compliant Example Code", + "Compliant Example - Prose": "Compliant Example Prose", + "Compliant Example - Code": "Compliant Example Code", + } + + fields = {v: "" for v in header_map.values()} + + lines = issue_body.splitlines() + current_key = None + current_value_lines = [] + + lines.append("### END") # Sentinel to process last field + + for line in lines: + header_match = re.match(r'^### (.+)$', line.strip()) + if header_match: + # Save previous field value if any + if current_key is not None: + value = "\n".join(current_value_lines).strip() + if value == "_No response_": + value = "" + if current_key in fields: + fields[current_key] = value + + header = header_match.group(1).strip() + current_key = header_map.get(header) # Map to dict key or None if unknown + current_value_lines = [] + else: + current_value_lines.append(line) + + return fields + +def save_guideline_file(content: str, chapter: str): + content = "\n" + content + "\n" + # os.makedirs(f"src/coding-guidelines/{chapter}", exist_ok=True) + filename = f"src/coding-guidelines/{chapter.lower()}.rst" + + print("=====CONTENT=====") + print(content) + + print("=====CONTENT=END=====") + with open(filename, 'a', encoding='utf-8') as f: + f.write(content) + print(f"Saved guideline to {filename}") + +def guideline_template(fields: dict) -> str: + + # taken from generate-guideline-templates.py + guideline_id = generate_id("gui") + rationale_id = generate_id("rat") + non_compliant_example_id = generate_id("non_compl_ex") + compliant_example_id = generate_id("compl_ex") + + def get(key): + return fields.get(key, "").strip() + + + guideline_text = f""".. guideline:: {get('Guideline Title')} + :id: {guideline_id} + :category: {get('Category').lower()} + :status: {get('Status').lower()} + :release: {get('Release Begin').lower()} + :fls: {get('Fls Paragraph Id').lower()} + :decidability: {get('Decidability').lower()} + :scope: {get('Scope').lower()} + :tags: {",".join(get('Tags').split(" "))} + + {get('Amplification')} + + .. rationale:: + :id: {rationale_id} + :status: {get('Status').lower()} + + {get('Rationale')} + + .. non_compliant_example:: + :id: {non_compliant_example_id} + :status: {get('Status').lower()} + + {get('Non Compliant Example Prose')} + + .. code-block:: rust + + {get('Non Compliant Example Code')} + + .. compliant_example:: + :id: {compliant_example_id} + :status: {get('Status').lower()} + + {get('Compliant Example Prose')} + + .. code-block:: rust + + {get('Compliant Example Code')} +""" + + return guideline_text + +import sys +if __name__ == "__main__": + # for testing purposes pull an issue in json format + # eg https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4 + # this json is how the issue is visible to the GA bot + # + # with open('scripts/issue_sample.json', 'r', encoding='utf-8') as f: + # issue = json.load(f) + # + + ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` + + issue_json = sys.stdin.read() + issue = json.loads(issue_json) + + issue_number = issue['number'] + issue_title = issue['title'] + issue_body = issue['body'] + + fields = extract_form_fields(issue_body) + chapter = fields["Chapter"] + + + content = guideline_template(fields) + + + save_guideline_file(content, chapter) + + diff --git a/scripts/test_issue_sample.json b/scripts/test_issue_sample.json new file mode 100644 index 0000000..d981e78 --- /dev/null +++ b/scripts/test_issue_sample.json @@ -0,0 +1,124 @@ +{ + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "repository_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines", + "labels_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", + "comments_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/comments", + "events_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/events", + "html_url": "https://github.com/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "id": 3104390263, + "node_id": "I_kwDOOMMjbs65CTx3", + "number": 4, + "title": "[Coding Guideline]: testtt", + "user": { + "login": "x0rw", + "id": 14003018, + "node_id": "MDQ6VXNlcjE0MDAzMDE4", + "avatar_url": "https://avatars.githubusercontent.com/u/14003018?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/x0rw", + "html_url": "https://github.com/x0rw", + "followers_url": "https://api.github.com/users/x0rw/followers", + "following_url": "https://api.github.com/users/x0rw/following{/other_user}", + "gists_url": "https://api.github.com/users/x0rw/gists{/gist_id}", + "starred_url": "https://api.github.com/users/x0rw/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/x0rw/subscriptions", + "organizations_url": "https://api.github.com/users/x0rw/orgs", + "repos_url": "https://api.github.com/users/x0rw/repos", + "events_url": "https://api.github.com/users/x0rw/events{/privacy}", + "received_events_url": "https://api.github.com/users/x0rw/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 8703664686, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiLg", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/category:%20advisory", + "name": "category: advisory", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703664688, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiMA", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/status:%20draft", + "name": "status: draft", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703664689, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiMQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", + "name": "decidability: decidable", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703686409, + "node_id": "LA_kwDOOMMjbs8AAAACBse3CQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", + "name": "chapter: concurrency", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703686412, + "node_id": "LA_kwDOOMMjbs8AAAACBse3DA", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/scope:%20crate", + "name": "scope: crate", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703732885, + "node_id": "LA_kwDOOMMjbs8AAAACBshslQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/accepted", + "name": "accepted", + "color": "6AABE8", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 0, + "created_at": "2025-05-30T22:24:07Z", + "updated_at": "2025-05-30T22:48:17Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest ga\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ntest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", + "closed_by": null, + "reactions": { + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/timeline", + "performed_via_github_app": null, + "state_reason": null +} diff --git a/src/coding-guidelines/concurrency.rst b/src/coding-guidelines/concurrency.rst index 701b2b7..0d0ce07 100644 --- a/src/coding-guidelines/concurrency.rst +++ b/src/coding-guidelines/concurrency.rst @@ -5,3 +5,77 @@ Concurrency =========== +.. guideline:: test ga + :id: gui_tWWeutonoP4C + :category: advisory + :status: draft + :release: 1.1.1 + :fls: fls_4vjbkm4ceymk + :decidability: decidable + :scope: crate + :tags: reduce-human-error + + hehehehe + + .. rationale:: + :id: rat_HPCljXjoIyli + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_uwx76Gn9AOwh + :status: draft + + test ga + + .. code-block:: rust + + test ga + + .. compliant_example:: + :id: compl_ex_JgBUmaNaaOIJ + :status: draft + + test ga + + .. code-block:: rust + + test ga +.. guideline:: test ga + :id: gui_amBpUTlR2nNJ + :category: advisory + :status: draft + :release: 1.1.1 + :fls: fls_fsdjkfslkdfj + :decidability: decidable + :scope: crate + :tags: test,gatest,ga + + hehehehe + + .. rationale:: + :id: rat_n99RxbculIiS + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_D4fbbnyDNDyx + :status: draft + + test ga + + .. code-block:: rust + + test ga + + .. compliant_example:: + :id: compl_ex_fW99UVCxBWau + :status: draft + + test ga + + .. code-block:: rust + + test ga From e97042252bc672b69eb9e8c832f19f22e0a16c32 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 31 May 2025 01:30:49 +0100 Subject: [PATCH 03/30] chores: minor stuffs --- scripts/auto-pr-helper.py | 4 +-- src/coding-guidelines/concurrency.rst | 39 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 9f26243..8372f05 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -1,5 +1,4 @@ import json -import os import re import random import string @@ -66,10 +65,11 @@ def save_guideline_file(content: str, chapter: str): # os.makedirs(f"src/coding-guidelines/{chapter}", exist_ok=True) filename = f"src/coding-guidelines/{chapter.lower()}.rst" + # for testing in the GA summary print("=====CONTENT=====") print(content) - print("=====CONTENT=END=====") + with open(filename, 'a', encoding='utf-8') as f: f.write(content) print(f"Saved guideline to {filename}") diff --git a/src/coding-guidelines/concurrency.rst b/src/coding-guidelines/concurrency.rst index 0d0ce07..2437f21 100644 --- a/src/coding-guidelines/concurrency.rst +++ b/src/coding-guidelines/concurrency.rst @@ -79,3 +79,42 @@ Concurrency .. code-block:: rust test ga + +.. guideline:: test ga + :id: gui_SiKxJC71h1FF + :category: advisory + :status: draft + :release: 1.1.1 + :fls: fls_fsdjkfslkdfj + :decidability: decidable + :scope: crate + :tags: test,gatest,ga + + hehehehe + + .. rationale:: + :id: rat_f3MX2pK3KtSM + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_1LBJwFJCQbfN + :status: draft + + test ga + + .. code-block:: rust + + test ga + + .. compliant_example:: + :id: compl_ex_EVmR76bHowPe + :status: draft + + test ga + + .. code-block:: rust + + test ga + From 5072bb6a26cbd91247769eb19739186572a9ea74 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 31 May 2025 01:42:26 +0100 Subject: [PATCH 04/30] chores: proper pr title --- .github/workflows/auto-pr-on-issue.yml | 4 +- src/coding-guidelines/concurrency.rst | 113 ------------------------- 2 files changed, 2 insertions(+), 115 deletions(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 44f4747..7d4859b 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -1,4 +1,4 @@ -name: Auto PR +name: Auto Guideline PR on: issues: @@ -41,7 +41,7 @@ jobs: with: commit-message: "Add guideline for issue #${{ github.event.issue.number }}" branch: guideline-${{ github.event.issue.number }} - title: "Add guideline from issue #${{ github.event.issue.number }}" + title: "[auto-pr] #${{ github.event.issue.number }}: ${{ github.event.issue.title }}" body: | This PR was automatically generated from issue #${{ github.event.issue.number }}. Closes #${{ github.event.issue.number }}. diff --git a/src/coding-guidelines/concurrency.rst b/src/coding-guidelines/concurrency.rst index 2437f21..701b2b7 100644 --- a/src/coding-guidelines/concurrency.rst +++ b/src/coding-guidelines/concurrency.rst @@ -5,116 +5,3 @@ Concurrency =========== -.. guideline:: test ga - :id: gui_tWWeutonoP4C - :category: advisory - :status: draft - :release: 1.1.1 - :fls: fls_4vjbkm4ceymk - :decidability: decidable - :scope: crate - :tags: reduce-human-error - - hehehehe - - .. rationale:: - :id: rat_HPCljXjoIyli - :status: draft - - test ga - - .. non_compliant_example:: - :id: non_compl_ex_uwx76Gn9AOwh - :status: draft - - test ga - - .. code-block:: rust - - test ga - - .. compliant_example:: - :id: compl_ex_JgBUmaNaaOIJ - :status: draft - - test ga - - .. code-block:: rust - - test ga -.. guideline:: test ga - :id: gui_amBpUTlR2nNJ - :category: advisory - :status: draft - :release: 1.1.1 - :fls: fls_fsdjkfslkdfj - :decidability: decidable - :scope: crate - :tags: test,gatest,ga - - hehehehe - - .. rationale:: - :id: rat_n99RxbculIiS - :status: draft - - test ga - - .. non_compliant_example:: - :id: non_compl_ex_D4fbbnyDNDyx - :status: draft - - test ga - - .. code-block:: rust - - test ga - - .. compliant_example:: - :id: compl_ex_fW99UVCxBWau - :status: draft - - test ga - - .. code-block:: rust - - test ga - -.. guideline:: test ga - :id: gui_SiKxJC71h1FF - :category: advisory - :status: draft - :release: 1.1.1 - :fls: fls_fsdjkfslkdfj - :decidability: decidable - :scope: crate - :tags: test,gatest,ga - - hehehehe - - .. rationale:: - :id: rat_f3MX2pK3KtSM - :status: draft - - test ga - - .. non_compliant_example:: - :id: non_compl_ex_1LBJwFJCQbfN - :status: draft - - test ga - - .. code-block:: rust - - test ga - - .. compliant_example:: - :id: compl_ex_EVmR76bHowPe - :status: draft - - test ga - - .. code-block:: rust - - test ga - From 53aaf2abab302058aee660ec69de7f5d7220f162 Mon Sep 17 00:00:00 2001 From: x0rw Date: Wed, 4 Jun 2025 14:15:27 +0100 Subject: [PATCH 05/30] Add comments --- scripts/auto-pr-helper.py | 96 +++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 8372f05..e36b162 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -3,34 +3,41 @@ import random import string -CHARS = string.ascii_letters + string.digits -ID_LENGTH = 12 def generate_id(prefix): - """Generate a random ID with the given prefix.""" + """ + Generate a random ID with the given prefix. + """ + + CHARS = string.ascii_letters + string.digits + ID_LENGTH = 12 random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) return f"{prefix}_{random_part}" def extract_form_fields(issue_body: str) -> dict: + """ + This function parses issues json into a dict of important fields + """ # Mapping from issue body headers to dict keys + # Changing issues fields name to snake_case (eg. 'Guideline Title' => 'guideline_title') header_map = { - "Chapter": "Chapter", - "Guideline Title": "Guideline Title", - "Category": "Category", - "Status": "Status", - "Release Begin": "Release Begin", - "Release End": "Release End", - "FLS Paragraph ID": "Fls Paragraph Id", - "Decidability": "Decidability", - "Scope": "Scope", - "Tags": "Tags", - "Amplification": "Amplification", - "Exception(s)": "Exception(S)", - "Rationale": "Rationale", - "Non-Compliant Example - Prose": "Non Compliant Example Prose", - "Non-Compliant Example - Code": "Non Compliant Example Code", - "Compliant Example - Prose": "Compliant Example Prose", - "Compliant Example - Code": "Compliant Example Code", + "Chapter": "chapter", + "Guideline Title": "guideline_title", + "Category": "category", + "Status": "status", + "Release Begin": "release_begin", + "Release End": "release_end", + "FLS Paragraph ID": "fls_id", + "Decidability": "decidability", + "Scope": "scope", + "Tags": "tags", + "Amplification": "amplification", + "Exception(s)": "exceptions", + "Rationale": "rationale", + "Non-Compliant Example - Prose": "non_compliant_ex_prose", + "Non-Compliant Example - Code": "non_compliant_ex", + "Compliant Example - Prose": "compliant_example_prose", + "Compliant Example - Code": "compliant_example", } fields = {v: "" for v in header_map.values()} @@ -41,12 +48,14 @@ def extract_form_fields(issue_body: str) -> dict: lines.append("### END") # Sentinel to process last field + # Look for '###' in every line, ### represent a sections/field in a guideline for line in lines: header_match = re.match(r'^### (.+)$', line.strip()) if header_match: # Save previous field value if any if current_key is not None: value = "\n".join(current_value_lines).strip() + # `_No response_` represents an empty field if value == "_No response_": value = "" if current_key in fields: @@ -61,6 +70,10 @@ def extract_form_fields(issue_body: str) -> dict: return fields def save_guideline_file(content: str, chapter: str): + """ + Appends a guideline str to a chapter + + """ content = "\n" + content + "\n" # os.makedirs(f"src/coding-guidelines/{chapter}", exist_ok=True) filename = f"src/coding-guidelines/{chapter.lower()}.rst" @@ -75,8 +88,14 @@ def save_guideline_file(content: str, chapter: str): print(f"Saved guideline to {filename}") def guideline_template(fields: dict) -> str: + """ + This function turns a dictionary that contains the guideline fields + into a proper .rst guideline format + """ + # taken from generate-guideline-templates.py + # to generate random ids guideline_id = generate_id("gui") rationale_id = generate_id("rat") non_compliant_example_id = generate_id("non_compl_ex") @@ -86,43 +105,43 @@ def get(key): return fields.get(key, "").strip() - guideline_text = f""".. guideline:: {get('Guideline Title')} + guideline_text = f""".. guideline:: {get('guideline_title')} :id: {guideline_id} - :category: {get('Category').lower()} - :status: {get('Status').lower()} - :release: {get('Release Begin').lower()} - :fls: {get('Fls Paragraph Id').lower()} - :decidability: {get('Decidability').lower()} - :scope: {get('Scope').lower()} - :tags: {",".join(get('Tags').split(" "))} + :category: {get('category').lower()} + :status: {get('status').lower()} + :release: {get('release_begin').lower()}-{get('release_end')} + :fls: {get('fls_id').lower()} + :decidability: {get('decidability').lower()} + :scope: {get('scope').lower()} + :tags: {",".join(get('tags').split(" "))} - {get('Amplification')} + {get('amplification')} .. rationale:: :id: {rationale_id} - :status: {get('Status').lower()} + :status: {get('status').lower()} - {get('Rationale')} + {get('rationale')} .. non_compliant_example:: :id: {non_compliant_example_id} - :status: {get('Status').lower()} + :status: {get('status').lower()} - {get('Non Compliant Example Prose')} + {get('non_compliant_ex_prose')} .. code-block:: rust - {get('Non Compliant Example Code')} + {get('non_compliant_example')} .. compliant_example:: :id: {compliant_example_id} - :status: {get('Status').lower()} + :status: {get('status').lower()} - {get('Compliant Example Prose')} + {get('compliant_example_prose')} .. code-block:: rust - {get('Compliant Example Code')} + {get('compliant_example')} """ return guideline_text @@ -139,6 +158,7 @@ def get(key): ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` + # Read json from stdin issue_json = sys.stdin.read() issue = json.loads(issue_json) @@ -147,7 +167,7 @@ def get(key): issue_body = issue['body'] fields = extract_form_fields(issue_body) - chapter = fields["Chapter"] + chapter = fields["chapter"] content = guideline_template(fields) From 14420074b6dcf7a5546a178a96af7c441f255414 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 7 Jun 2025 18:23:15 +0100 Subject: [PATCH 06/30] Update .github/workflows/auto-pr-on-issue.yml Co-authored-by: Pete LeVasseur --- .github/workflows/auto-pr-on-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 7d4859b..f2fe2f7 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -7,7 +7,7 @@ on: jobs: auto-pr: - if: contains(github.event.label.name, 'accepted') + if: contains(github.event.label.name, 'status: approved') runs-on: ubuntu-latest permissions: contents: write From 2c0a7c2d51fd7fbf15098e0f9519f9c0edb62263 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 7 Jun 2025 18:39:18 +0100 Subject: [PATCH 07/30] Fix Fix 2 Fix 3 fix : special char i yml --- .github/workflows/auto-pr-on-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index f2fe2f7..16400e9 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -7,7 +7,7 @@ on: jobs: auto-pr: - if: contains(github.event.label.name, 'status: approved') + if: "github.event.label.name == 'status: approved'" runs-on: ubuntu-latest permissions: contents: write From 14e0f8d9523f43fb4082c9dd785236e0c32c9dcf Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 12 Jul 2025 15:22:48 +0100 Subject: [PATCH 08/30] Fix non compliant ex --- scripts/auto-pr-helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index e36b162..9392fb7 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -131,7 +131,7 @@ def get(key): .. code-block:: rust - {get('non_compliant_example')} + {get('non_compliant_ex')} .. compliant_example:: :id: {compliant_example_id} From 286b9c48165f9febb3a6dee85cdefeff45c782d5 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 12 Jul 2025 23:10:48 +0100 Subject: [PATCH 09/30] refactor: address code review add snapshot testing and their runner renamed generate-guideline-templates.py to generate_guideline_templates.py general refactor --- .../auto-pr-tests/test_issue_01.json | 2 +- .github/auto-pr-tests/test_issue_01.snapshot | 41 ++++ .github/auto-pr-tests/test_issue_02.json | 124 +++++++++++++ .github/auto-pr-tests/test_issue_02.snapshot | 41 ++++ .github/auto-pr-tests/test_runner.py | 62 +++++++ .github/workflows/auto-pr-on-issue.yml | 6 +- generate-guideline-templates.py | 100 ---------- generate_guideline_templates.py | 175 ++++++++++++++++++ scripts/auto-pr-helper.py | 97 +++------- test/test.test | 0 10 files changed, 473 insertions(+), 175 deletions(-) rename scripts/test_issue_sample.json => .github/auto-pr-tests/test_issue_01.json (96%) create mode 100644 .github/auto-pr-tests/test_issue_01.snapshot create mode 100644 .github/auto-pr-tests/test_issue_02.json create mode 100644 .github/auto-pr-tests/test_issue_02.snapshot create mode 100644 .github/auto-pr-tests/test_runner.py delete mode 100755 generate-guideline-templates.py create mode 100755 generate_guideline_templates.py delete mode 100644 test/test.test diff --git a/scripts/test_issue_sample.json b/.github/auto-pr-tests/test_issue_01.json similarity index 96% rename from scripts/test_issue_sample.json rename to .github/auto-pr-tests/test_issue_01.json index d981e78..a158bed 100644 --- a/scripts/test_issue_sample.json +++ b/.github/auto-pr-tests/test_issue_01.json @@ -104,7 +104,7 @@ "completed": 0, "percent_completed": 0 }, - "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest ga\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ntest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", + "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest ga\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ndfhsdfkjshdfskdjhftest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", "closed_by": null, "reactions": { "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", diff --git a/.github/auto-pr-tests/test_issue_01.snapshot b/.github/auto-pr-tests/test_issue_01.snapshot new file mode 100644 index 0000000..c36c2ce --- /dev/null +++ b/.github/auto-pr-tests/test_issue_01.snapshot @@ -0,0 +1,41 @@ +=====CONTENT===== +.. guideline:: test ga + :id: gui_FWr4O8NFxpgr + :category: advisory + :status: draft + :release: 1.1.1-1.1.1 + :fls: fls_fsdjkfslkdfj + :decidability: decidable + :scope: crate + :tags: test,gatest,ga + + hehehehe + + .. rationale:: + :id: rat_y8xxrKQIfr7y + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_4cQT1M3psxLs + :status: draft + + test ga + + .. code-block:: rust + + dfhsdfkjshdfskdjhftest ga + + .. compliant_example:: + :id: compl_ex_zrLFcPlviRu5 + :status: draft + + test ga + + .. code-block:: rust + + test ga + +=====CONTENT=END===== +Saved guideline to src/coding-guidelines/concurrency.rst diff --git a/.github/auto-pr-tests/test_issue_02.json b/.github/auto-pr-tests/test_issue_02.json new file mode 100644 index 0000000..61a56ae --- /dev/null +++ b/.github/auto-pr-tests/test_issue_02.json @@ -0,0 +1,124 @@ +{ + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "repository_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines", + "labels_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", + "comments_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/comments", + "events_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/events", + "html_url": "https://github.com/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "id": 3104390263, + "node_id": "I_kwDOOMMjbs65CTx3", + "number": 4, + "title": "[Coding Guideline]: testtt", + "user": { + "login": "x0rw", + "id": 14003018, + "node_id": "MDQ6VXNlcjE0MDAzMDE4", + "avatar_url": "https://avatars.githubusercontent.com/u/14003018?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/x0rw", + "html_url": "https://github.com/x0rw", + "followers_url": "https://api.github.com/users/x0rw/followers", + "following_url": "https://api.github.com/users/x0rw/following{/other_user}", + "gists_url": "https://api.github.com/users/x0rw/gists{/gist_id}", + "starred_url": "https://api.github.com/users/x0rw/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/x0rw/subscriptions", + "organizations_url": "https://api.github.com/users/x0rw/orgs", + "repos_url": "https://api.github.com/users/x0rw/repos", + "events_url": "https://api.github.com/users/x0rw/events{/privacy}", + "received_events_url": "https://api.github.com/users/x0rw/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 8703664686, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiLg", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/category:%20advisory", + "name": "category: advisory", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703664688, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiMA", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/status:%20draft", + "name": "status: draft", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703664689, + "node_id": "LA_kwDOOMMjbs8AAAACBsdiMQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", + "name": "decidability: decidable", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703686409, + "node_id": "LA_kwDOOMMjbs8AAAACBse3CQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", + "name": "chapter: concurrency", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703686412, + "node_id": "LA_kwDOOMMjbs8AAAACBse3DA", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/scope:%20crate", + "name": "scope: crate", + "color": "ededed", + "default": false, + "description": null + }, + { + "id": 8703732885, + "node_id": "LA_kwDOOMMjbs8AAAACBshslQ", + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/accepted", + "name": "accepted", + "color": "6AABE8", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 0, + "created_at": "2025-05-30T22:24:07Z", + "updated_at": "2025-05-30T22:48:17Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest 2\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ndfhsdfkjshdfskdjhftest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", + "closed_by": null, + "reactions": { + "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/timeline", + "performed_via_github_app": null, + "state_reason": null +} diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot new file mode 100644 index 0000000..9fc9bab --- /dev/null +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -0,0 +1,41 @@ +=====CONTENT===== +.. guideline:: test 2 + :id: gui_10X7r6yledzw + :category: advisory + :status: draft + :release: 1.1.1-1.1.1 + :fls: fls_fsdjkfslkdfj + :decidability: decidable + :scope: crate + :tags: test,gatest,ga + + hehehehe + + .. rationale:: + :id: rat_Qpq2pZlovd7Z + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_qSOda5w4rWt4 + :status: draft + + test ga + + .. code-block:: rust + + dfhsdfkjshdfskdjhftest ga + + .. compliant_example:: + :id: compl_ex_qwzzxQs8JO27 + :status: draft + + test ga + + .. code-block:: rust + + test ga + +=====CONTENT=END===== +Saved guideline to src/coding-guidelines/concurrency.rst diff --git a/.github/auto-pr-tests/test_runner.py b/.github/auto-pr-tests/test_runner.py new file mode 100644 index 0000000..39c198f --- /dev/null +++ b/.github/auto-pr-tests/test_runner.py @@ -0,0 +1,62 @@ +import subprocess +import re +from pathlib import Path +import difflib + +def normalize_ids(text: str) -> str: + return re.sub(r'(:id:\s+[a-z_]+)_[a-zA-Z0-9]+', r'\1_IGNORED_ID', text) + +def compare(issue_json_path: Path, snapshot_path: Path) -> bool: + input_json = issue_json_path.read_text() + + result = subprocess.run( + ["uv", "run", "python", "scripts/auto-pr-helper.py"], + input=input_json.encode(), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True + ) + + # Normalize the actual output and the snapshot, this is crucial in snapshot tests to + # ignore random/volatile values. + actual_output = normalize_ids(result.stdout.decode()) + expected_output = normalize_ids(snapshot_path.read_text()) + + # Compare + if actual_output != expected_output: + diff = difflib.unified_diff( + expected_output.splitlines(), + actual_output.splitlines(), + fromfile=str(snapshot_path), + tofile="generated", + lineterm="" + ) + print(f"Difference found in {issue_json_path.name}:") + print("\n".join(diff)) + return False + else: + print(f"{issue_json_path.name} matches snapshot.") + return True + +# to generate snapshot i use this command: +# create or change the test_issue_xx file +## `cat .github/auto-pr-tests/test_issue_XX.json | python3 scripts/auto-pr-helper.py 2&>/dev/null > .github/auto-pr-tests/test_issue_0XX.snapshot` +tests = { + "test_01": ( + Path(".github/auto-pr-tests/test_issue_01.json"), + Path(".github/auto-pr-tests/test_issue_01.snapshot") + ), + "test_02": ( + Path(".github/auto-pr-tests/test_issue_02.json"), + Path(".github/auto-pr-tests/test_issue_02.snapshot") + ), +} + +# Run all tests +all_passed = True +for name, (issue_json, snapshot) in tests.items(): + if not compare(issue_json, snapshot): + all_passed = False + +if not all_passed: + exit(1) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 16400e9..be73b18 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -7,7 +7,7 @@ on: jobs: auto-pr: - if: "github.event.label.name == 'status: approved'" + if: "github.event.label.name == 'sign-off: create pr from issue'" runs-on: ubuntu-latest permissions: contents: write @@ -24,12 +24,12 @@ jobs: # - name: Save issue JSON payload to file # run: echo '${{ toJson(github.event.issue) }}' > issue.json - # pass it directly -- mayne fallback to this if an issue happened due to + # pass it directly -- maybe fallback to this if an issue happened due to # pipe-ing or encoding - name: Run Python script to generate guideline file run: | - echo '${{ toJson(github.event.issue) }}' | python3 scripts/auto-pr-helper.py + echo '${{ toJson(github.event.issue) }}' | uv run python scripts/auto-pr-helper.py - name: Commit generated guideline files run: | diff --git a/generate-guideline-templates.py b/generate-guideline-templates.py deleted file mode 100755 index 9bd77b0..0000000 --- a/generate-guideline-templates.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env -S uv run -# SPDX-License-Identifier: MIT OR Apache-2.0 -# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors - -import argparse -import string -import random - -# Configuration -CHARS = string.ascii_letters + string.digits -ID_LENGTH = 12 - -def generate_id(prefix): - """Generate a random ID with the given prefix.""" - random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) - return f"{prefix}_{random_part}" - -def generate_guideline_template(): - """Generate a complete guideline template with all required sections.""" - # Generate IDs for all sections - guideline_id = generate_id("gui") - rationale_id = generate_id("rat") - non_compliant_example_id = generate_id("non_compl_ex") - compliant_example_id = generate_id("compl_ex") - - template = f""".. guideline:: Title Here - :id: {guideline_id} - :category: - :status: draft - :release: - :fls: - :decidability: - :scope: - :tags: - - Description of the guideline goes here. - - .. rationale:: - :id: {rationale_id} - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: {non_compliant_example_id} - :status: draft - - Explanation of code example. - - .. code-block:: rust - - fn example_function() {{ - // Non-compliant implementation - }} - - .. compliant_example:: - :id: {compliant_example_id} - :status: draft - - Explanation of code example. - - .. code-block:: rust - - fn example_function() {{ - // Compliant implementation - }} -""" - return template - -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Generate guideline templates with randomly generated IDs" - ) - parser.add_argument( - "-n", - "--number-of-templates", - type=int, - default=1, - help="Number of templates to generate (default: 1)" - ) - return parser.parse_args() - -def main(): - """Generate the specified number of guideline templates.""" - args = parse_args() - num_templates = args.number_of_templates - - for i in range(num_templates): - if num_templates > 1: - print(f"=== Template {i+1} ===\n") - - template = generate_guideline_template() - print(template) - - if num_templates > 1 and i < num_templates - 1: - print("\n" + "=" * 80 + "\n") - -if __name__ == "__main__": - main() diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py new file mode 100755 index 0000000..ed787f2 --- /dev/null +++ b/generate_guideline_templates.py @@ -0,0 +1,175 @@ +#!/usr/bin/env -S uv run +# SPDX-License-Identifier: MIT OR Apache-2.0 +# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +import argparse +import string +import random + +# Configuration +CHARS = string.ascii_letters + string.digits +ID_LENGTH = 12 + +# Mapping from issue body headers to dict keys +# Changing issues fields name to snake_case (eg. 'Guideline Title' => 'guideline_title') +issue_header_map = { + "Chapter": "chapter", + "Guideline Title": "guideline_title", + "Category": "category", + "Status": "status", + "Release Begin": "release_begin", + "Release End": "release_end", + "FLS Paragraph ID": "fls_id", + "Decidability": "decidability", + "Scope": "scope", + "Tags": "tags", + "Amplification": "amplification", + "Exception(s)": "exceptions", + "Rationale": "rationale", + "Non-Compliant Example - Prose": "non_compliant_ex_prose", + "Non-Compliant Example - Code": "non_compliant_ex", + "Compliant Example - Prose": "compliant_example_prose", + "Compliant Example - Code": "compliant_example", +} + +def guideline_rst_template( + guideline_title: str, + category: str, + status: str, + release_begin: str, + release_end: str, + fls_id: str, + decidability: str, + scope: str, + tags: str, + amplification: str, + rationale: str, + non_compliant_ex_prose: str, + non_compliant_ex: str, + compliant_example_prose: str, + compliant_example: str +) -> str: + """ + Generate a .rst guideline entry from feilds values. + """ + + # Generate unique IDs + guideline_id = generate_id("gui") + rationale_id = generate_id("rat") + non_compliant_example_id = generate_id("non_compl_ex") + compliant_example_id = generate_id("compl_ex") + + # Normalize inputs + def norm(value: str) -> str: + return value.strip().lower() + + guideline_text = f""".. guideline:: {guideline_title.strip()} + :id: {guideline_id} + :category: {norm(category)} + :status: {norm(status)} + :release: {norm(release_begin)}-{release_end.strip()} + :fls: {norm(fls_id)} + :decidability: {norm(decidability)} + :scope: {norm(scope)} + :tags: {",".join(tags.strip().split())} + + {amplification.strip()} + + .. rationale:: + :id: {rationale_id} + :status: {norm(status)} + + {rationale.strip()} + + .. non_compliant_example:: + :id: {non_compliant_example_id} + :status: {norm(status)} + + {non_compliant_ex_prose.strip()} + + .. code-block:: rust + + {non_compliant_ex.strip()} + + .. compliant_example:: + :id: {compliant_example_id} + :status: {norm(status)} + + {compliant_example_prose.strip()} + + .. code-block:: rust + + {compliant_example.strip()} +""" + + return guideline_text +def generate_id(prefix): + """Generate a random ID with the given prefix.""" + random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) + return f"{prefix}_{random_part}" + +def generate_guideline_template(): + """Generate a complete guideline template with all required sections.""" + # Generate IDs for all sections + guideline_id = generate_id("gui") + rationale_id = generate_id("rat") + non_compliant_example_id = generate_id("non_compl_ex") + compliant_example_id = generate_id("compl_ex") + + template = guideline_rst_template( + guideline_title="Title Here", + category="", + status="draft", + release_begin="", + release_end="", + fls_id="", + decidability="", + scope="", + tags="", + amplification="Description of the guideline goes here.", + rationale="Explanation of why this guideline is important.", + non_compliant_ex_prose="Explanation of code example.", + non_compliant_ex=""" + fn example_function() { + // Non-compliant implementation + } + """, + compliant_example_prose="Explanation of code example.", + compliant_example=""" + fn example_function() { + // Compliant implementation + } + """) + return template + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description="Generate guideline templates with randomly generated IDs" + ) + parser.add_argument( + "-n", + "--number-of-templates", + type=int, + default=1, + help="Number of templates to generate (default: 1)" + ) + return parser.parse_args() + +def main(): + """Generate the specified number of guideline templates.""" + args = parse_args() + num_templates = args.number_of_templates + + for i in range(num_templates): + if num_templates > 1: + print(f"=== Template {i+1} ===\n") + + template = generate_guideline_template() + print(template) + + if num_templates > 1 and i < num_templates - 1: + print("\n" + "=" * 80 + "\n") + +if __name__ == "__main__": + main() diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 9392fb7..d6c7b3c 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -3,44 +3,22 @@ import random import string +import sys, os -def generate_id(prefix): - """ - Generate a random ID with the given prefix. - """ +scriptpath = "../" +script_dir = os.path.dirname(os.path.abspath(__file__)) +parent_dir = os.path.abspath(os.path.join(script_dir, "..")) +sys.path.append(parent_dir) + +from generate_guideline_templates import generate_id, guideline_rst_template, header_map, issue_header_map - CHARS = string.ascii_letters + string.digits - ID_LENGTH = 12 - random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) - return f"{prefix}_{random_part}" def extract_form_fields(issue_body: str) -> dict: """ This function parses issues json into a dict of important fields """ - # Mapping from issue body headers to dict keys - # Changing issues fields name to snake_case (eg. 'Guideline Title' => 'guideline_title') - header_map = { - "Chapter": "chapter", - "Guideline Title": "guideline_title", - "Category": "category", - "Status": "status", - "Release Begin": "release_begin", - "Release End": "release_end", - "FLS Paragraph ID": "fls_id", - "Decidability": "decidability", - "Scope": "scope", - "Tags": "tags", - "Amplification": "amplification", - "Exception(s)": "exceptions", - "Rationale": "rationale", - "Non-Compliant Example - Prose": "non_compliant_ex_prose", - "Non-Compliant Example - Code": "non_compliant_ex", - "Compliant Example - Prose": "compliant_example_prose", - "Compliant Example - Code": "compliant_example", - } - - fields = {v: "" for v in header_map.values()} + + fields = {v: "" for v in issue_header_map.values()} lines = issue_body.splitlines() current_key = None @@ -74,8 +52,6 @@ def save_guideline_file(content: str, chapter: str): Appends a guideline str to a chapter """ - content = "\n" + content + "\n" - # os.makedirs(f"src/coding-guidelines/{chapter}", exist_ok=True) filename = f"src/coding-guidelines/{chapter.lower()}.rst" # for testing in the GA summary @@ -105,44 +81,23 @@ def get(key): return fields.get(key, "").strip() - guideline_text = f""".. guideline:: {get('guideline_title')} - :id: {guideline_id} - :category: {get('category').lower()} - :status: {get('status').lower()} - :release: {get('release_begin').lower()}-{get('release_end')} - :fls: {get('fls_id').lower()} - :decidability: {get('decidability').lower()} - :scope: {get('scope').lower()} - :tags: {",".join(get('tags').split(" "))} - - {get('amplification')} - - .. rationale:: - :id: {rationale_id} - :status: {get('status').lower()} - - {get('rationale')} - - .. non_compliant_example:: - :id: {non_compliant_example_id} - :status: {get('status').lower()} - - {get('non_compliant_ex_prose')} - - .. code-block:: rust - - {get('non_compliant_ex')} - - .. compliant_example:: - :id: {compliant_example_id} - :status: {get('status').lower()} - - {get('compliant_example_prose')} - - .. code-block:: rust - - {get('compliant_example')} -""" + guideline_text = guideline_rst_template( + guideline_title=get("guideline_title"), + category=get("category"), + status=get("status"), + release_begin=get("release_begin"), + release_end=get("release_end"), + fls_id=get("fls_id"), + decidability=get("decidability"), + scope=get("scope"), + tags=get("tags"), + amplification=get("amplification"), + rationale=get("rationale"), + non_compliant_ex_prose=get("non_compliant_ex_prose"), + non_compliant_ex=get("non_compliant_ex"), + compliant_example_prose=get("compliant_example_prose"), + compliant_example=get("compliant_example") + ) return guideline_text diff --git a/test/test.test b/test/test.test deleted file mode 100644 index e69de29..0000000 From 49c32c764b061d8d455eeafcec09fdbac7a2e1b9 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sat, 12 Jul 2025 23:34:37 +0100 Subject: [PATCH 10/30] Fix workflow, use uv instead Co-authored-by: Pete LeVasseur --- .github/workflows/auto-pr-on-issue.yml | 9 +++++++++ scripts/auto-pr-helper.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index be73b18..b1fc0ca 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -14,9 +14,18 @@ jobs: pull-requests: write steps: + - name: Checkout code uses: actions/checkout@v4 + # TODO: make this step a reusable composite workflow. + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="/root/.cargo/bin:$PATH" + uv --version + + - name: Set up Git run: | git config --global user.email "action@github.com" diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index d6c7b3c..f33af73 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -10,7 +10,7 @@ parent_dir = os.path.abspath(os.path.join(script_dir, "..")) sys.path.append(parent_dir) -from generate_guideline_templates import generate_id, guideline_rst_template, header_map, issue_header_map +from generate_guideline_templates import generate_id, guideline_rst_template, issue_header_map def extract_form_fields(issue_body: str) -> dict: @@ -40,7 +40,7 @@ def extract_form_fields(issue_body: str) -> dict: fields[current_key] = value header = header_match.group(1).strip() - current_key = header_map.get(header) # Map to dict key or None if unknown + current_key = issue_header_map.get(header) # Map to dict key or None if unknown current_value_lines = [] else: current_value_lines.append(line) From 4a8ad42e5226c464ad71a82d0abdb5331c930044 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 00:03:40 +0100 Subject: [PATCH 11/30] Add snapshot tests workflow This workflow only triggers when the auto-pr script or the snapshot test directory changes --- .github/auto-pr-tests/test_runner.py | 6 +++--- .github/workflows/snapshot-ci.yml | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/snapshot-ci.yml diff --git a/.github/auto-pr-tests/test_runner.py b/.github/auto-pr-tests/test_runner.py index 39c198f..20f8b2b 100644 --- a/.github/auto-pr-tests/test_runner.py +++ b/.github/auto-pr-tests/test_runner.py @@ -38,9 +38,9 @@ def compare(issue_json_path: Path, snapshot_path: Path) -> bool: print(f"{issue_json_path.name} matches snapshot.") return True -# to generate snapshot i use this command: -# create or change the test_issue_xx file -## `cat .github/auto-pr-tests/test_issue_XX.json | python3 scripts/auto-pr-helper.py 2&>/dev/null > .github/auto-pr-tests/test_issue_0XX.snapshot` +# to generate snapshot: +# create or change the test_issue_xx file and then use this command after replacing XX with your test number: +## `cat .github/auto-pr-tests/test_issue_XX.json | uv run python scripts/auto-pr-helper.py 2&>/dev/null > .github/auto-pr-tests/test_issue_0XX.snapshot` tests = { "test_01": ( Path(".github/auto-pr-tests/test_issue_01.json"), diff --git a/.github/workflows/snapshot-ci.yml b/.github/workflows/snapshot-ci.yml new file mode 100644 index 0000000..16c46d4 --- /dev/null +++ b/.github/workflows/snapshot-ci.yml @@ -0,0 +1,25 @@ +name: Snapshot Tests for auto-pr + +on: + push: + paths: + - 'scripts/auto-pr-helper.py' + - '.github/auto-pr-tests/**' + workflow_dispatch: # also allow manual runs + +jobs: + snapshot-test: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install uv + shell: bash + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Run snapshot tests + run: | + uv run python .github/auto-pr-tests/test_runner.py From cbcfad52a617ae90fa53c93fe0e3cdf8be6bbcfc Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 16:19:47 +0100 Subject: [PATCH 12/30] Update .github/workflows/auto-pr-on-issue.yml Co-authored-by: Pete LeVasseur --- .github/workflows/auto-pr-on-issue.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index b1fc0ca..034536c 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -25,7 +25,6 @@ jobs: export PATH="/root/.cargo/bin:$PATH" uv --version - - name: Set up Git run: | git config --global user.email "action@github.com" From f17f79650d1ce5aabf08ce5307f0ab4a16674d87 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 16:37:24 +0100 Subject: [PATCH 13/30] Apply suggestion from @PLeVasseur Co-authored-by: Pete LeVasseur --- .github/workflows/snapshot-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/snapshot-ci.yml b/.github/workflows/snapshot-ci.yml index 16c46d4..f7eaa35 100644 --- a/.github/workflows/snapshot-ci.yml +++ b/.github/workflows/snapshot-ci.yml @@ -3,6 +3,7 @@ name: Snapshot Tests for auto-pr on: push: paths: + - 'generate_guideline_templates.py' - 'scripts/auto-pr-helper.py' - '.github/auto-pr-tests/**' workflow_dispatch: # also allow manual runs From f5e756d2873ad363ee59680d037c763cdebe8ea9 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 18:10:33 +0100 Subject: [PATCH 14/30] Apply suggestion from @PLeVasseur Co-authored-by: Pete LeVasseur --- generate_guideline_templates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py index ed787f2..b8c0545 100755 --- a/generate_guideline_templates.py +++ b/generate_guideline_templates.py @@ -103,6 +103,7 @@ def norm(value: str) -> str: """ return guideline_text + def generate_id(prefix): """Generate a random ID with the given prefix.""" random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) From 5b1d45bcd88c343ae1f03dc028ac382165355b9d Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 16:29:10 +0100 Subject: [PATCH 15/30] address review comments --- .github/auto-pr-tests/test_issue_02.json | 2 +- .github/auto-pr-tests/test_issue_02.snapshot | 83 +++++++++++++++----- README.md | 2 +- generate_guideline_templates.py | 2 +- scripts/auto-pr-helper.py | 47 +++++------ src/process/style-guideline.rst | 8 +- 6 files changed, 93 insertions(+), 51 deletions(-) diff --git a/.github/auto-pr-tests/test_issue_02.json b/.github/auto-pr-tests/test_issue_02.json index 61a56ae..4f1d343 100644 --- a/.github/auto-pr-tests/test_issue_02.json +++ b/.github/auto-pr-tests/test_issue_02.json @@ -104,7 +104,7 @@ "completed": 0, "percent_completed": 0 }, - "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest 2\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ndfhsdfkjshdfskdjhftest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", + "body": "### Chapter\n\nAssociated Items\n\n### Guideline Title\n\nRecursive function are not allowed\n\n### Category\n\nRequired\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.3.0\n\n### Release End\n\nlatest\n\n### FLS Paragraph ID\n\nfls_vjgkg8kfi93\n\n### Decidability\n\nUndecidable\n\n### Scope\n\nSystem\n\n### Tags\n\nreduce-human-error\n\n### Amplification\n\nAny function shall not call itself directly or indirectly\n\n### Exception(s)\n\nRecursion may be permitted under the following conditions:\n- The recursion termination condition is simple, explicit, and well-defined.\n- The function calls itself directly, or with strictly limited and clearly documented indirection.\n- The maximum recursion depth is statically bounded and justified, ensuring no risk of stack overflow.\n- The rationale for using recursion, rather than iteration, is clearly documented and reviewed.\n- The code is accompanied by tests that exercise the recursion boundary conditions.\n\n### Rationale\n\nRecursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability.\n\n### Non-Compliant Example - Prose\n\nThe below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior.\n\n### Non-Compliant Example - Code\n\n```rust\n// Recursive enum to represent a string or a list of `MyEnum`\nenum MyEnum {\n Str(String),\n List(Vec),\n}\n\n// Concatenates strings from a nested structure of `MyEnum` using recursion.\nfn concat_strings(input: &[MyEnum]) -> String {\n let mut result = String::new();\n for item in input {\n match item {\n MyEnum::Str(s) => result.push_str(s),\n MyEnum::List(list) => result.push_str(&concat_strings(list)),\n }\n }\n result\n}\n```\n\n### Compliant Example - Prose\n\nThe following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments.\n\n### Compliant Example - Code\n\n```rust\n// Recursive enum to represent a string or a list of `MyEnum`\nenum MyEnum {\n Str(String),\n List(Vec),\n}\n\n/// Concatenates strings from a nested structure of `MyEnum` without using recursion.\n/// Returns an error if the stack size exceeds `MAX_STACK_SIZE`.\nfn concat_strings_non_recursive(input: &[MyEnum]) -> Result {\n const MAX_STACK_SIZE: usize = 1000;\n let mut result = String::new();\n let mut stack = Vec::new();\n\n // Add all items to the stack\n stack.extend(input.iter());\n\n while let Some(item) = stack.pop() {\n match item {\n MyEnum::Str(s) => result.insert_str(0, s),\n MyEnum::List(list) => {\n // Add list items to the stack\n for sub_item in list.iter() {\n stack.push(sub_item);\n if stack.len() > MAX_STACK_SIZE {\n return Err(\"Too big structure\");\n }\n }\n }\n }\n }\n Ok(result)\n}\n```", "closed_by": null, "reactions": { "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index 9fc9bab..a37401a 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,41 +1,88 @@ =====CONTENT===== -.. guideline:: test 2 - :id: gui_10X7r6yledzw - :category: advisory +.. guideline:: Recursive function are not allowed + :id: gui_81NCyLnG0glk + :category: required :status: draft - :release: 1.1.1-1.1.1 - :fls: fls_fsdjkfslkdfj - :decidability: decidable - :scope: crate - :tags: test,gatest,ga + :release: 1.3.0-latest + :fls: fls_vjgkg8kfi93 + :decidability: undecidable + :scope: system + :tags: reduce-human-error - hehehehe + Any function shall not call itself directly or indirectly .. rationale:: - :id: rat_Qpq2pZlovd7Z + :id: rat_hEmkdyKGXF1a :status: draft - test ga + Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. .. non_compliant_example:: - :id: non_compl_ex_qSOda5w4rWt4 + :id: non_compl_ex_uE46RnJ7bRxg :status: draft - test ga + The below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. .. code-block:: rust - dfhsdfkjshdfskdjhftest ga + // Recursive enum to represent a string or a list of `MyEnum` + enum MyEnum { + Str(String), + List(Vec), + } + + // Concatenates strings from a nested structure of `MyEnum` using recursion. + fn concat_strings(input: &[MyEnum]) -> String { + let mut result = String::new(); + for item in input { + match item { + MyEnum::Str(s) => result.push_str(s), + MyEnum::List(list) => result.push_str(&concat_strings(list)), + } + } + result + } .. compliant_example:: - :id: compl_ex_qwzzxQs8JO27 + :id: compl_ex_OwxB9v0oUgPE :status: draft - test ga + The following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. .. code-block:: rust - test ga + // Recursive enum to represent a string or a list of `MyEnum` + enum MyEnum { + Str(String), + List(Vec), + } + + /// Concatenates strings from a nested structure of `MyEnum` without using recursion. + /// Returns an error if the stack size exceeds `MAX_STACK_SIZE`. + fn concat_strings_non_recursive(input: &[MyEnum]) -> Result { + const MAX_STACK_SIZE: usize = 1000; + let mut result = String::new(); + let mut stack = Vec::new(); + + // Add all items to the stack + stack.extend(input.iter()); + + while let Some(item) = stack.pop() { + match item { + MyEnum::Str(s) => result.insert_str(0, s), + MyEnum::List(list) => { + // Add list items to the stack + for sub_item in list.iter() { + stack.push(sub_item); + if stack.len() > MAX_STACK_SIZE { + return Err("Too big structure"); + } + } + } + } + } + Ok(result) + } =====CONTENT=END===== -Saved guideline to src/coding-guidelines/concurrency.rst +Saved guideline to src/coding-guidelines/associated items.rst diff --git a/README.md b/README.md index b7c6769..292f420 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ We have the same chapter layout as the [Ferrocene Language Specification](https: ### Guideline template -We have a script `./generate-guideline-templates.py` which which assumes you're using `uv` that can be run to generate the template for a guideline with properly randomized IDs. +We have a script `./generate_guideline_templates.py` which assumes you're using `uv` that can be run to generate the template for a guideline with properly randomized IDs. You can the copy and paste this guideline from the command line into the correct chapter. diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py index b8c0545..c704f53 100755 --- a/generate_guideline_templates.py +++ b/generate_guideline_templates.py @@ -50,7 +50,7 @@ def guideline_rst_template( compliant_example: str ) -> str: """ - Generate a .rst guideline entry from feilds values. + Generate a .rst guideline entry from field values. """ # Generate unique IDs diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index f33af73..8689da3 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -49,10 +49,9 @@ def extract_form_fields(issue_body: str) -> dict: def save_guideline_file(content: str, chapter: str): """ - Appends a guideline str to a chapter - + Appends a guideline to a chapter """ - filename = f"src/coding-guidelines/{chapter.lower()}.rst" + filename = f"src/coding-guidelines/{chapter.lower().replace(' ', '-')}.rst" # for testing in the GA summary print("=====CONTENT=====") @@ -70,8 +69,7 @@ def guideline_template(fields: dict) -> str: """ - # taken from generate-guideline-templates.py - # to generate random ids + # generate random ids guideline_id = generate_id("gui") rationale_id = generate_id("rat") non_compliant_example_id = generate_id("non_compl_ex") @@ -80,6 +78,15 @@ def guideline_template(fields: dict) -> str: def get(key): return fields.get(key, "").strip() + def format_code_block(code: str, lang: str = "rust") -> str: + lines = code.strip().splitlines() + if lines and lines[0].strip().startswith("```"): + # Strip the ```rust and ``` lines + lines = lines[1:] + if lines and lines[-1].strip() == "```": + lines = lines[:-1] + indented_code = "\n".join(f" {line}" for line in lines) # indentation + return f"\n\n{indented_code}\n" guideline_text = guideline_rst_template( guideline_title=get("guideline_title"), @@ -94,40 +101,28 @@ def get(key): amplification=get("amplification"), rationale=get("rationale"), non_compliant_ex_prose=get("non_compliant_ex_prose"), - non_compliant_ex=get("non_compliant_ex"), + non_compliant_ex=format_code_block(get("non_compliant_ex")), compliant_example_prose=get("compliant_example_prose"), - compliant_example=get("compliant_example") + compliant_example=format_code_block(get("compliant_example")) ) return guideline_text import sys if __name__ == "__main__": - # for testing purposes pull an issue in json format - # eg https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4 - # this json is how the issue is visible to the GA bot - # - # with open('scripts/issue_sample.json', 'r', encoding='utf-8') as f: - # issue = json.load(f) - # ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` + ## or use `curl https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/135 | uv run python scripts/auto-pr-helper.py` - # Read json from stdin - issue_json = sys.stdin.read() - issue = json.loads(issue_json) - issue_number = issue['number'] - issue_title = issue['title'] - issue_body = issue['body'] + # Read json from stdin + stdin_issue_json = sys.stdin.read() + json_issue = json.loads(stdin_issue_json) + issue_number = json_issue['number'] + issue_title = json_issue['title'] + issue_body = json_issue['body'] fields = extract_form_fields(issue_body) chapter = fields["chapter"] - - content = guideline_template(fields) - - save_guideline_file(content, chapter) - - diff --git a/src/process/style-guideline.rst b/src/process/style-guideline.rst index c32fd1d..90df210 100644 --- a/src/process/style-guideline.rst +++ b/src/process/style-guideline.rst @@ -105,7 +105,7 @@ A unique identifier for each guideline. Guideline identifiers **MUST** begin wit These identifiers are considered **stable** across releases and **MUST NOT** be removed. See ``status`` below for more. -**MUST** be generated using the ``generate-guideline-templates.py`` script to ensure +**MUST** be generated using the ``generate_guideline_templates.py`` script to ensure compliance. ``category`` @@ -370,7 +370,7 @@ A unique identifier for each rationale. Rationale identifiers **MUST** begin wit These identifiers are considered **stable** across releases and **MUST NOT** be removed. See ``status`` below for more. -**MUST** be generated using the ``generate-guideline-templates.py`` script to ensure +**MUST** be generated using the ``generate_guideline_templates.py`` script to ensure compliance. ``rationale`` ``status`` @@ -414,7 +414,7 @@ A unique identifier for each ``non_compliant_example``. ``non_compliant_example` These identifiers are considered **stable** across releases and **MUST NOT** be removed. See ``status`` below for more. -**MUST** be generated using the ``generate-guideline-templates.py`` script to ensure +**MUST** be generated using the ``generate_guideline_templates.py`` script to ensure compliance. ``non_compliant_example`` ``status`` @@ -487,7 +487,7 @@ A unique identifier for each ``compliant_example``. ``compliant_example`` identi These identifiers are considered **stable** across releases and **MUST NOT** be removed. See ``status`` below for more. -**MUST** be generated using the ``generate-guideline-templates.py`` script to ensure +**MUST** be generated using the ``generate_guideline_templates.py`` script to ensure compliance. ``compliant_example`` ``status`` From f4f0fe8bb5d943149894e53cc72845d0ae6a474d Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 16:34:23 +0100 Subject: [PATCH 16/30] test ci snapshot error --- .github/auto-pr-tests/test_issue_02.snapshot | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index a37401a..4f32e59 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,27 +1,28 @@ =====CONTENT===== .. guideline:: Recursive function are not allowed - :id: gui_81NCyLnG0glk + :id: gui_llfYnmAucaJQ :category: required :status: draft :release: 1.3.0-latest :fls: fls_vjgkg8kfi93 :decidability: undecidable :scope: system - :tags: reduce-human-error + :tags: increase-human-error Any function shall not call itself directly or indirectly .. rationale:: - :id: rat_hEmkdyKGXF1a + :id: rat_bbk57IUR7pjD :status: draft Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. .. non_compliant_example:: - :id: non_compl_ex_uE46RnJ7bRxg + :id: non_compl_ex_bEG9g2wgnCci :status: draft The below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. + THIS LINE WAS ADDED IN PURPOSE TO TEST SNAPSHOT TESTING??? .. code-block:: rust @@ -44,7 +45,7 @@ } .. compliant_example:: - :id: compl_ex_OwxB9v0oUgPE + :id: compl_ex_j9sv9nZWk1Eo :status: draft The following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. @@ -85,4 +86,4 @@ } =====CONTENT=END===== -Saved guideline to src/coding-guidelines/associated items.rst +Saved guideline to src/coding-guidelines/associated-items.rst From 3bd63825ee2b7242a550a92215b183f6c3919bc9 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 17:44:53 +0100 Subject: [PATCH 17/30] Fix issues, refactor dedent, update worflow to use astral actions --- .github/auto-pr-tests/test_issue_01.snapshot | 72 ++++---- .github/auto-pr-tests/test_issue_02.snapshot | 169 +++++++++---------- .github/workflows/auto-pr-on-issue.yml | 6 +- .github/workflows/build-guidelines.yml | 7 +- .github/workflows/snapshot-ci.yml | 5 +- generate_guideline_templates.py | 80 ++++----- scripts/auto-pr-helper.py | 25 +-- 7 files changed, 181 insertions(+), 183 deletions(-) diff --git a/.github/auto-pr-tests/test_issue_01.snapshot b/.github/auto-pr-tests/test_issue_01.snapshot index c36c2ce..d280270 100644 --- a/.github/auto-pr-tests/test_issue_01.snapshot +++ b/.github/auto-pr-tests/test_issue_01.snapshot @@ -1,41 +1,41 @@ =====CONTENT===== + .. guideline:: test ga - :id: gui_FWr4O8NFxpgr - :category: advisory - :status: draft - :release: 1.1.1-1.1.1 - :fls: fls_fsdjkfslkdfj - :decidability: decidable - :scope: crate - :tags: test,gatest,ga - - hehehehe - - .. rationale:: - :id: rat_y8xxrKQIfr7y - :status: draft - - test ga - - .. non_compliant_example:: - :id: non_compl_ex_4cQT1M3psxLs - :status: draft - - test ga - - .. code-block:: rust - - dfhsdfkjshdfskdjhftest ga - - .. compliant_example:: - :id: compl_ex_zrLFcPlviRu5 - :status: draft - - test ga - - .. code-block:: rust - + :id: gui_LzfV28IVG7qO + :category: advisory + :status: draft + :release: 1.1.1-1.1.1 + :fls: fls_fsdjkfslkdfj + :decidability: decidable + :scope: crate + :tags: test,gatest,ga + + hehehehe + + .. rationale:: + :id: rat_nPL2Cv7VBdqG + :status: draft + + test ga + + .. non_compliant_example:: + :id: non_compl_ex_Bv7HMs0AVNlH + :status: draft + test ga + .. code-block:: rust + + dfhsdfkjshdfskdjhftest ga + + .. compliant_example:: + :id: compl_ex_H5Z7OsxZX3Ig + :status: draft + + test ga + + .. code-block:: rust + + test ga + =====CONTENT=END===== -Saved guideline to src/coding-guidelines/concurrency.rst diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index 4f32e59..2c94606 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,89 +1,88 @@ =====CONTENT===== + .. guideline:: Recursive function are not allowed - :id: gui_llfYnmAucaJQ - :category: required - :status: draft - :release: 1.3.0-latest - :fls: fls_vjgkg8kfi93 - :decidability: undecidable - :scope: system - :tags: increase-human-error - - Any function shall not call itself directly or indirectly - - .. rationale:: - :id: rat_bbk57IUR7pjD - :status: draft - - Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. - - .. non_compliant_example:: - :id: non_compl_ex_bEG9g2wgnCci - :status: draft - - The below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. - THIS LINE WAS ADDED IN PURPOSE TO TEST SNAPSHOT TESTING??? - - .. code-block:: rust - - // Recursive enum to represent a string or a list of `MyEnum` - enum MyEnum { - Str(String), - List(Vec), - } - - // Concatenates strings from a nested structure of `MyEnum` using recursion. - fn concat_strings(input: &[MyEnum]) -> String { - let mut result = String::new(); - for item in input { - match item { - MyEnum::Str(s) => result.push_str(s), - MyEnum::List(list) => result.push_str(&concat_strings(list)), - } - } - result - } - - .. compliant_example:: - :id: compl_ex_j9sv9nZWk1Eo - :status: draft - - The following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. - - .. code-block:: rust - - // Recursive enum to represent a string or a list of `MyEnum` - enum MyEnum { - Str(String), - List(Vec), - } - - /// Concatenates strings from a nested structure of `MyEnum` without using recursion. - /// Returns an error if the stack size exceeds `MAX_STACK_SIZE`. - fn concat_strings_non_recursive(input: &[MyEnum]) -> Result { - const MAX_STACK_SIZE: usize = 1000; - let mut result = String::new(); - let mut stack = Vec::new(); - - // Add all items to the stack - stack.extend(input.iter()); - - while let Some(item) = stack.pop() { - match item { - MyEnum::Str(s) => result.insert_str(0, s), - MyEnum::List(list) => { - // Add list items to the stack - for sub_item in list.iter() { - stack.push(sub_item); - if stack.len() > MAX_STACK_SIZE { - return Err("Too big structure"); - } - } - } - } - } - Ok(result) - } + :id: gui_1EDWRNKjs6It + :category: required + :status: draft + :release: 1.3.0-latest + :fls: fls_vjgkg8kfi93 + :decidability: undecidable + :scope: system + :tags: reduce-human-error + + Any function shall not call itself directly or indirectly + + .. rationale:: + :id: rat_Hbn0yLGzVdDK + :status: draft + + Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. + + .. non_compliant_example:: + :id: non_compl_ex_eRmcumqqmMaZ + :status: draft + + The below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. + + .. code-block:: rust + + // Recursive enum to represent a string or a list of `MyEnum` + enum MyEnum { + Str(String), + List(Vec), + } + + // Concatenates strings from a nested structure of `MyEnum` using recursion. + fn concat_strings(input: &[MyEnum]) -> String { + let mut result = String::new(); + for item in input { + match item { + MyEnum::Str(s) => result.push_str(s), + MyEnum::List(list) => result.push_str(&concat_strings(list)), + } + } + result + } + + .. compliant_example:: + :id: compl_ex_t6Hy8E2YlneV + :status: draft + + The following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. + + .. code-block:: rust + + // Recursive enum to represent a string or a list of `MyEnum` + enum MyEnum { + Str(String), + List(Vec), + } + + /// Concatenates strings from a nested structure of `MyEnum` without using recursion. + /// Returns an error if the stack size exceeds `MAX_STACK_SIZE`. + fn concat_strings_non_recursive(input: &[MyEnum]) -> Result { + const MAX_STACK_SIZE: usize = 1000; + let mut result = String::new(); + let mut stack = Vec::new(); + + // Add all items to the stack + stack.extend(input.iter()); + + while let Some(item) = stack.pop() { + match item { + MyEnum::Str(s) => result.insert_str(0, s), + MyEnum::List(list) => { + // Add list items to the stack + for sub_item in list.iter() { + stack.push(sub_item); + if stack.len() > MAX_STACK_SIZE { + return Err("Too big structure"); + } + } + } + } + } + Ok(result) + } =====CONTENT=END===== -Saved guideline to src/coding-guidelines/associated-items.rst diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 034536c..cf9d4ab 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -18,12 +18,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # TODO: make this step a reusable composite workflow. - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="/root/.cargo/bin:$PATH" - uv --version + uses: astral-sh/setup-uv@v6 - name: Set up Git run: | diff --git a/.github/workflows/build-guidelines.yml b/.github/workflows/build-guidelines.yml index 3bf4c65..c5957ea 100644 --- a/.github/workflows/build-guidelines.yml +++ b/.github/workflows/build-guidelines.yml @@ -25,10 +25,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="/root/.cargo/bin:$PATH" - uv --version + uses: astral-sh/setup-uv@v6 - name: Build documentation run: | mkdir -p build @@ -86,4 +83,4 @@ jobs: run: cargo install typos-cli - name: Check for typos run: typos - \ No newline at end of file + diff --git a/.github/workflows/snapshot-ci.yml b/.github/workflows/snapshot-ci.yml index f7eaa35..0d29c12 100644 --- a/.github/workflows/snapshot-ci.yml +++ b/.github/workflows/snapshot-ci.yml @@ -16,10 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Install uv - shell: bash - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.cargo/bin" >> $GITHUB_PATH + uses: astral-sh/setup-uv@v6 - name: Run snapshot tests run: | diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py index c704f53..5dddf7f 100755 --- a/generate_guideline_templates.py +++ b/generate_guideline_templates.py @@ -5,6 +5,7 @@ import argparse import string import random +from textwrap import dedent, indent # Configuration CHARS = string.ascii_letters + string.digits @@ -63,44 +64,47 @@ def guideline_rst_template( def norm(value: str) -> str: return value.strip().lower() - guideline_text = f""".. guideline:: {guideline_title.strip()} - :id: {guideline_id} - :category: {norm(category)} - :status: {norm(status)} - :release: {norm(release_begin)}-{release_end.strip()} - :fls: {norm(fls_id)} - :decidability: {norm(decidability)} - :scope: {norm(scope)} - :tags: {",".join(tags.strip().split())} - - {amplification.strip()} - - .. rationale:: - :id: {rationale_id} - :status: {norm(status)} - - {rationale.strip()} - - .. non_compliant_example:: - :id: {non_compliant_example_id} - :status: {norm(status)} - - {non_compliant_ex_prose.strip()} - - .. code-block:: rust - - {non_compliant_ex.strip()} - - .. compliant_example:: - :id: {compliant_example_id} - :status: {norm(status)} - - {compliant_example_prose.strip()} - - .. code-block:: rust - - {compliant_example.strip()} -""" + indented_compliant_ex= indent(compliant_example.strip(), " " * 13) + indented_non_compliant_ex= indent(non_compliant_ex.strip(), " " * 13) + guideline_text = dedent(f""" + .. guideline:: {guideline_title.strip()} + :id: {guideline_id} + :category: {norm(category)} + :status: {norm(status)} + :release: {norm(release_begin)}-{release_end.strip()} + :fls: {norm(fls_id)} + :decidability: {norm(decidability)} + :scope: {norm(scope)} + :tags: {",".join(tags.strip().split())} + + {amplification.strip()} + + .. rationale:: + :id: {rationale_id} + :status: {norm(status)} + + {rationale.strip()} + + .. non_compliant_example:: + :id: {non_compliant_example_id} + :status: {norm(status)} + + {non_compliant_ex_prose.strip()} + + .. code-block:: rust + + {indented_non_compliant_ex.strip()} + + .. compliant_example:: + :id: {compliant_example_id} + :status: {norm(status)} + + {compliant_example_prose.strip()} + + .. code-block:: rust + + {indented_compliant_ex.strip()} + """) return guideline_text diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 8689da3..adbb51d 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -2,8 +2,9 @@ import re import random import string - -import sys, os +import argparse +import sys +import os scriptpath = "../" script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -52,12 +53,6 @@ def save_guideline_file(content: str, chapter: str): Appends a guideline to a chapter """ filename = f"src/coding-guidelines/{chapter.lower().replace(' ', '-')}.rst" - - # for testing in the GA summary - print("=====CONTENT=====") - print(content) - print("=====CONTENT=END=====") - with open(filename, 'a', encoding='utf-8') as f: f.write(content) print(f"Saved guideline to {filename}") @@ -108,9 +103,13 @@ def format_code_block(code: str, lang: str = "rust") -> str: return guideline_text -import sys if __name__ == "__main__": + # parse arguments + parser = argparse.ArgumentParser(description="Generate guideline from GitHub issue JSON.") + parser.add_argument("--saved", action="store_true", help="Save the generated guideline file.") + args = parser.parse_args() + ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` ## or use `curl https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/135 | uv run python scripts/auto-pr-helper.py` @@ -125,4 +124,10 @@ def format_code_block(code: str, lang: str = "rust") -> str: fields = extract_form_fields(issue_body) chapter = fields["chapter"] content = guideline_template(fields) - save_guideline_file(content, chapter) + + print("=====CONTENT=====") + print(content) + print("=====CONTENT=END=====") + + if args.saved: + save_guideline_file(content, chapter) From b2f598d3419c17abb4e0cc93e546ec3ff6352248 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 17:53:03 +0100 Subject: [PATCH 18/30] Fix ci --- .github/workflows/auto-pr-on-issue.yml | 2 +- .github/workflows/snapshot-ci.yml | 2 +- scripts/auto-pr-helper.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index cf9d4ab..da53b76 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -33,7 +33,7 @@ jobs: - name: Run Python script to generate guideline file run: | - echo '${{ toJson(github.event.issue) }}' | uv run python scripts/auto-pr-helper.py + echo '${{ toJson(github.event.issue) }}' | uv run python scripts/auto-pr-helper.py --save - name: Commit generated guideline files run: | diff --git a/.github/workflows/snapshot-ci.yml b/.github/workflows/snapshot-ci.yml index 0d29c12..8631545 100644 --- a/.github/workflows/snapshot-ci.yml +++ b/.github/workflows/snapshot-ci.yml @@ -3,7 +3,7 @@ name: Snapshot Tests for auto-pr on: push: paths: - - 'generate_guideline_templates.py' + - 'generate_guideline_templates.py' - 'scripts/auto-pr-helper.py' - '.github/auto-pr-tests/**' workflow_dispatch: # also allow manual runs diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index adbb51d..ce1fb64 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -107,7 +107,7 @@ def format_code_block(code: str, lang: str = "rust") -> str: # parse arguments parser = argparse.ArgumentParser(description="Generate guideline from GitHub issue JSON.") - parser.add_argument("--saved", action="store_true", help="Save the generated guideline file.") + parser.add_argument("--save", action="store_true", help="Save the generated guideline file.") args = parser.parse_args() ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` @@ -129,5 +129,5 @@ def format_code_block(code: str, lang: str = "rust") -> str: print(content) print("=====CONTENT=END=====") - if args.saved: + if args.save: save_guideline_file(content, chapter) From 81795ff896239f1c599424e1baa03915f45c726a Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 13 Jul 2025 18:37:10 +0100 Subject: [PATCH 19/30] Inline compliant and non-compliant-exs in guideline template --- generate_guideline_templates.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py index 5dddf7f..8837a5c 100755 --- a/generate_guideline_templates.py +++ b/generate_guideline_templates.py @@ -134,17 +134,10 @@ def generate_guideline_template(): amplification="Description of the guideline goes here.", rationale="Explanation of why this guideline is important.", non_compliant_ex_prose="Explanation of code example.", - non_compliant_ex=""" - fn example_function() { - // Non-compliant implementation - } - """, + non_compliant_ex=""" fn example_function() {\n // Non-compliant implementation\n } """, compliant_example_prose="Explanation of code example.", - compliant_example=""" - fn example_function() { - // Compliant implementation - } - """) + compliant_example=""" fn example_function() {\n // Compliant implementation\n } """, + ) return template def parse_args(): From d2d5f53b4d78383d681b6aa0e9cc1e47e5cd9046 Mon Sep 17 00:00:00 2001 From: x0rw Date: Wed, 16 Jul 2025 15:12:30 +0100 Subject: [PATCH 20/30] Address review comments and remove unused code --- .github/workflows/auto-pr-on-issue.yml | 5 ----- scripts/auto-pr-helper.py | 7 ------- 2 files changed, 12 deletions(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index da53b76..e2fe145 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -26,11 +26,6 @@ jobs: git config --global user.email "action@github.com" git config --global user.name "GitHub Action" - # - name: Save issue JSON payload to file - # run: echo '${{ toJson(github.event.issue) }}' > issue.json - # pass it directly -- maybe fallback to this if an issue happened due to - # pipe-ing or encoding - - name: Run Python script to generate guideline file run: | echo '${{ toJson(github.event.issue) }}' | uv run python scripts/auto-pr-helper.py --save diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index ce1fb64..02db383 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -62,13 +62,6 @@ def guideline_template(fields: dict) -> str: This function turns a dictionary that contains the guideline fields into a proper .rst guideline format """ - - - # generate random ids - guideline_id = generate_id("gui") - rationale_id = generate_id("rat") - non_compliant_example_id = generate_id("non_compl_ex") - compliant_example_id = generate_id("compl_ex") def get(key): return fields.get(key, "").strip() From e293ce3000904ebe8514f41a92f06aa2e8c46c34 Mon Sep 17 00:00:00 2001 From: x0rw Date: Wed, 16 Jul 2025 15:18:37 +0100 Subject: [PATCH 21/30] ci: add co-author info from issue author in auto-generated PRs --- .github/workflows/auto-pr-on-issue.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index e2fe145..17a6da2 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -38,7 +38,11 @@ jobs: - name: Create Pull Request uses: peter-evans/create-pull-request@v6 with: - commit-message: "Add guideline for issue #${{ github.event.issue.number }}" + commit-message: | + Add guideline for issue #${{ github.event.issue.number }} + + Co-authored-by: ${{ github.event.issue.user.login }} <${{ github.event.issue.user.login }}@users.noreply.github.com> + branch: guideline-${{ github.event.issue.number }} title: "[auto-pr] #${{ github.event.issue.number }}: ${{ github.event.issue.title }}" body: | From bf12b4054401c5579c3a7b5b831e79d5935ebdb6 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 27 Jul 2025 23:45:01 +0100 Subject: [PATCH 22/30] feat: md to .rst, fix ci unstability fix ci fix ci: --- .github/auto-pr-tests/test_issue_01.json | 52 +++--- .github/auto-pr-tests/test_issue_02.json | 52 +++--- .github/auto-pr-tests/test_issue_02.snapshot | 14 +- .github/auto-pr-tests/test_issue_03.json | 165 +++++++++++++++++++ .github/auto-pr-tests/test_issue_03.snapshot | 165 +++++++++++++++++++ .github/auto-pr-tests/test_runner.py | 19 ++- .github/workflows/auto-pr-on-issue.yml | 4 +- generate_guideline_templates.py | 60 ++++--- scripts/auto-pr-helper.py | 69 +++++--- 9 files changed, 486 insertions(+), 114 deletions(-) create mode 100644 .github/auto-pr-tests/test_issue_03.json create mode 100644 .github/auto-pr-tests/test_issue_03.snapshot diff --git a/.github/auto-pr-tests/test_issue_01.json b/.github/auto-pr-tests/test_issue_01.json index a158bed..bcf3e0d 100644 --- a/.github/auto-pr-tests/test_issue_01.json +++ b/.github/auto-pr-tests/test_issue_01.json @@ -1,31 +1,31 @@ { - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4", - "repository_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines", - "labels_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", - "comments_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/comments", - "events_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/events", - "html_url": "https://github.com/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4", + "repository_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines", + "labels_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", + "comments_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/comments", + "events_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/events", + "html_url": "https://github.com/rustfoundation/safety-critical-rust-coding-guidelines/issues/4", "id": 3104390263, "node_id": "I_kwDOOMMjbs65CTx3", "number": 4, "title": "[Coding Guideline]: testtt", "user": { - "login": "x0rw", + "login": "rustfoundation", "id": 14003018, "node_id": "MDQ6VXNlcjE0MDAzMDE4", "avatar_url": "https://avatars.githubusercontent.com/u/14003018?v=4", "gravatar_id": "", - "url": "https://api.github.com/users/x0rw", - "html_url": "https://github.com/x0rw", - "followers_url": "https://api.github.com/users/x0rw/followers", - "following_url": "https://api.github.com/users/x0rw/following{/other_user}", - "gists_url": "https://api.github.com/users/x0rw/gists{/gist_id}", - "starred_url": "https://api.github.com/users/x0rw/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/x0rw/subscriptions", - "organizations_url": "https://api.github.com/users/x0rw/orgs", - "repos_url": "https://api.github.com/users/x0rw/repos", - "events_url": "https://api.github.com/users/x0rw/events{/privacy}", - "received_events_url": "https://api.github.com/users/x0rw/received_events", + "url": "https://api.github.com/users/rustfoundation", + "html_url": "https://github.com/rustfoundation", + "followers_url": "https://api.github.com/users/rustfoundation/followers", + "following_url": "https://api.github.com/users/rustfoundation/following{/other_user}", + "gists_url": "https://api.github.com/users/rustfoundation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rustfoundation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rustfoundation/subscriptions", + "organizations_url": "https://api.github.com/users/rustfoundation/orgs", + "repos_url": "https://api.github.com/users/rustfoundation/repos", + "events_url": "https://api.github.com/users/rustfoundation/events{/privacy}", + "received_events_url": "https://api.github.com/users/rustfoundation/received_events", "type": "User", "user_view_type": "public", "site_admin": false @@ -34,7 +34,7 @@ { "id": 8703664686, "node_id": "LA_kwDOOMMjbs8AAAACBsdiLg", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/category:%20advisory", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/category:%20advisory", "name": "category: advisory", "color": "ededed", "default": false, @@ -43,7 +43,7 @@ { "id": 8703664688, "node_id": "LA_kwDOOMMjbs8AAAACBsdiMA", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/status:%20draft", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/status:%20draft", "name": "status: draft", "color": "ededed", "default": false, @@ -52,7 +52,7 @@ { "id": 8703664689, "node_id": "LA_kwDOOMMjbs8AAAACBsdiMQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", "name": "decidability: decidable", "color": "ededed", "default": false, @@ -61,7 +61,7 @@ { "id": 8703686409, "node_id": "LA_kwDOOMMjbs8AAAACBse3CQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", "name": "chapter: concurrency", "color": "ededed", "default": false, @@ -70,7 +70,7 @@ { "id": 8703686412, "node_id": "LA_kwDOOMMjbs8AAAACBse3DA", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/scope:%20crate", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/scope:%20crate", "name": "scope: crate", "color": "ededed", "default": false, @@ -79,7 +79,7 @@ { "id": 8703732885, "node_id": "LA_kwDOOMMjbs8AAAACBshslQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/accepted", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/accepted", "name": "accepted", "color": "6AABE8", "default": false, @@ -107,7 +107,7 @@ "body": "### Chapter\n\nConcurrency\n\n### Guideline Title\n\ntest ga\n\n### Category\n\nAdvisory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.1.1\n\n### Release End\n\n1.1.1\n\n### FLS Paragraph ID\n\nfls_fsdjkfslkdfj\n\n### Decidability\n\nDecidable\n\n### Scope\n\nCrate\n\n### Tags\n\ntest gatest ga\n\n### Amplification\n\nhehehehe\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\ntest ga\n\n### Non-Compliant Example - Prose\n\ntest ga\n\n### Non-Compliant Example - Code\n\ndfhsdfkjshdfskdjhftest ga\n\n### Compliant Example - Prose\n\ntest ga\n\n### Compliant Example - Code\n\ntest ga", "closed_by": null, "reactions": { - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/reactions", "total_count": 0, "+1": 0, "-1": 0, @@ -118,7 +118,7 @@ "rocket": 0, "eyes": 0 }, - "timeline_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/timeline", + "timeline_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/timeline", "performed_via_github_app": null, "state_reason": null } diff --git a/.github/auto-pr-tests/test_issue_02.json b/.github/auto-pr-tests/test_issue_02.json index 4f1d343..591bb10 100644 --- a/.github/auto-pr-tests/test_issue_02.json +++ b/.github/auto-pr-tests/test_issue_02.json @@ -1,31 +1,31 @@ { - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4", - "repository_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines", - "labels_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", - "comments_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/comments", - "events_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/events", - "html_url": "https://github.com/x0rw/safety-critical-rust-coding-guidelines/issues/4", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4", + "repository_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines", + "labels_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/labels{/name}", + "comments_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/comments", + "events_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/events", + "html_url": "https://github.com/rustfoundation/safety-critical-rust-coding-guidelines/issues/4", "id": 3104390263, "node_id": "I_kwDOOMMjbs65CTx3", "number": 4, "title": "[Coding Guideline]: testtt", "user": { - "login": "x0rw", + "login": "rustfoundation", "id": 14003018, "node_id": "MDQ6VXNlcjE0MDAzMDE4", "avatar_url": "https://avatars.githubusercontent.com/u/14003018?v=4", "gravatar_id": "", - "url": "https://api.github.com/users/x0rw", - "html_url": "https://github.com/x0rw", - "followers_url": "https://api.github.com/users/x0rw/followers", - "following_url": "https://api.github.com/users/x0rw/following{/other_user}", - "gists_url": "https://api.github.com/users/x0rw/gists{/gist_id}", - "starred_url": "https://api.github.com/users/x0rw/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/x0rw/subscriptions", - "organizations_url": "https://api.github.com/users/x0rw/orgs", - "repos_url": "https://api.github.com/users/x0rw/repos", - "events_url": "https://api.github.com/users/x0rw/events{/privacy}", - "received_events_url": "https://api.github.com/users/x0rw/received_events", + "url": "https://api.github.com/users/rustfoundation", + "html_url": "https://github.com/rustfoundation", + "followers_url": "https://api.github.com/users/rustfoundation/followers", + "following_url": "https://api.github.com/users/rustfoundation/following{/other_user}", + "gists_url": "https://api.github.com/users/rustfoundation/gists{/gist_id}", + "starred_url": "https://api.github.com/users/rustfoundation/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/rustfoundation/subscriptions", + "organizations_url": "https://api.github.com/users/rustfoundation/orgs", + "repos_url": "https://api.github.com/users/rustfoundation/repos", + "events_url": "https://api.github.com/users/rustfoundation/events{/privacy}", + "received_events_url": "https://api.github.com/users/rustfoundation/received_events", "type": "User", "user_view_type": "public", "site_admin": false @@ -34,7 +34,7 @@ { "id": 8703664686, "node_id": "LA_kwDOOMMjbs8AAAACBsdiLg", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/category:%20advisory", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/category:%20advisory", "name": "category: advisory", "color": "ededed", "default": false, @@ -43,7 +43,7 @@ { "id": 8703664688, "node_id": "LA_kwDOOMMjbs8AAAACBsdiMA", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/status:%20draft", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/status:%20draft", "name": "status: draft", "color": "ededed", "default": false, @@ -52,7 +52,7 @@ { "id": 8703664689, "node_id": "LA_kwDOOMMjbs8AAAACBsdiMQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", "name": "decidability: decidable", "color": "ededed", "default": false, @@ -61,7 +61,7 @@ { "id": 8703686409, "node_id": "LA_kwDOOMMjbs8AAAACBse3CQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/chapter:%20concurrency", "name": "chapter: concurrency", "color": "ededed", "default": false, @@ -70,7 +70,7 @@ { "id": 8703686412, "node_id": "LA_kwDOOMMjbs8AAAACBse3DA", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/scope:%20crate", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/scope:%20crate", "name": "scope: crate", "color": "ededed", "default": false, @@ -79,7 +79,7 @@ { "id": 8703732885, "node_id": "LA_kwDOOMMjbs8AAAACBshslQ", - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/labels/accepted", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/accepted", "name": "accepted", "color": "6AABE8", "default": false, @@ -107,7 +107,7 @@ "body": "### Chapter\n\nAssociated Items\n\n### Guideline Title\n\nRecursive function are not allowed\n\n### Category\n\nRequired\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.3.0\n\n### Release End\n\nlatest\n\n### FLS Paragraph ID\n\nfls_vjgkg8kfi93\n\n### Decidability\n\nUndecidable\n\n### Scope\n\nSystem\n\n### Tags\n\nreduce-human-error\n\n### Amplification\n\nAny function shall not call itself directly or indirectly\n\n### Exception(s)\n\nRecursion may be permitted under the following conditions:\n- The recursion termination condition is simple, explicit, and well-defined.\n- The function calls itself directly, or with strictly limited and clearly documented indirection.\n- The maximum recursion depth is statically bounded and justified, ensuring no risk of stack overflow.\n- The rationale for using recursion, rather than iteration, is clearly documented and reviewed.\n- The code is accompanied by tests that exercise the recursion boundary conditions.\n\n### Rationale\n\nRecursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability.\n\n### Non-Compliant Example - Prose\n\nThe below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior.\n\n### Non-Compliant Example - Code\n\n```rust\n// Recursive enum to represent a string or a list of `MyEnum`\nenum MyEnum {\n Str(String),\n List(Vec),\n}\n\n// Concatenates strings from a nested structure of `MyEnum` using recursion.\nfn concat_strings(input: &[MyEnum]) -> String {\n let mut result = String::new();\n for item in input {\n match item {\n MyEnum::Str(s) => result.push_str(s),\n MyEnum::List(list) => result.push_str(&concat_strings(list)),\n }\n }\n result\n}\n```\n\n### Compliant Example - Prose\n\nThe following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments.\n\n### Compliant Example - Code\n\n```rust\n// Recursive enum to represent a string or a list of `MyEnum`\nenum MyEnum {\n Str(String),\n List(Vec),\n}\n\n/// Concatenates strings from a nested structure of `MyEnum` without using recursion.\n/// Returns an error if the stack size exceeds `MAX_STACK_SIZE`.\nfn concat_strings_non_recursive(input: &[MyEnum]) -> Result {\n const MAX_STACK_SIZE: usize = 1000;\n let mut result = String::new();\n let mut stack = Vec::new();\n\n // Add all items to the stack\n stack.extend(input.iter());\n\n while let Some(item) = stack.pop() {\n match item {\n MyEnum::Str(s) => result.insert_str(0, s),\n MyEnum::List(list) => {\n // Add list items to the stack\n for sub_item in list.iter() {\n stack.push(sub_item);\n if stack.len() > MAX_STACK_SIZE {\n return Err(\"Too big structure\");\n }\n }\n }\n }\n }\n Ok(result)\n}\n```", "closed_by": null, "reactions": { - "url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/reactions", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/reactions", "total_count": 0, "+1": 0, "-1": 0, @@ -118,7 +118,7 @@ "rocket": 0, "eyes": 0 }, - "timeline_url": "https://api.github.com/repos/x0rw/safety-critical-rust-coding-guidelines/issues/4/timeline", + "timeline_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/4/timeline", "performed_via_github_app": null, "state_reason": null } diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index 2c94606..4bfa6a9 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,7 +1,7 @@ =====CONTENT===== .. guideline:: Recursive function are not allowed - :id: gui_1EDWRNKjs6It + :id: gui_VTTlQLBlJvOf :category: required :status: draft :release: 1.3.0-latest @@ -13,16 +13,16 @@ Any function shall not call itself directly or indirectly .. rationale:: - :id: rat_Hbn0yLGzVdDK + :id: rat_nN9SoDL0U2ZK :status: draft - Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports [tail call optimization](https://en.wikipedia.org/wiki/Tail_call), this optimization is not guaranteed and depends on the specific implementation and function structure. There is an [open RFC to guarantee tail call optimization in the Rust compiler](https://github.com/phi-go/rfcs/blob/guaranteed-tco/text/0000-explicit-tail-calls.md), but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. + Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports `tail call optimization `_\ , this optimization is not guaranteed and depends on the specific implementation and function structure. There is an `open RFC to guarantee tail call optimization in the Rust compiler `_\ , but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. .. non_compliant_example:: - :id: non_compl_ex_eRmcumqqmMaZ + :id: non_compl_ex_kw16P66E0GjL :status: draft - The below function `concat_strings` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. + The below function ``concat_strings`` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. .. code-block:: rust @@ -45,10 +45,10 @@ } .. compliant_example:: - :id: compl_ex_t6Hy8E2YlneV + :id: compl_ex_SIIwOv4F04bs :status: draft - The following code implements the same functionality using iteration instead of recursion. The `stack` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. + The following code implements the same functionality using iteration instead of recursion. The ``stack`` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. .. code-block:: rust diff --git a/.github/auto-pr-tests/test_issue_03.json b/.github/auto-pr-tests/test_issue_03.json new file mode 100644 index 0000000..aa52550 --- /dev/null +++ b/.github/auto-pr-tests/test_issue_03.json @@ -0,0 +1,165 @@ +{ + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156", + "repository_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines", + "labels_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156/labels{/name}", + "comments_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156/comments", + "events_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156/events", + "html_url": "https://github.com/rustfoundation/safety-critical-rust-coding-guidelines/issues/156", + "id": 3258276002, + "node_id": "I_kwDOOIv1lc7CNVii", + "number": 156, + "title": "[Coding Guideline]: Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand", + "user": { + "login": "XXX", + "id": 11747623, + "node_id": "MDQ6VXNlcjExNzQ3NjIz", + "avatar_url": "https://avatars.githubusercontent.com/u/xxx?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/XXX", + "html_url": "https://github.com/XXX", + "followers_url": "https://api.github.com/users/XXX/followers", + "following_url": "https://api.github.com/users/XXX/following{/other_user}", + "gists_url": "https://api.github.com/users/XXX/gists{/gist_id}", + "starred_url": "https://api.github.com/users/XXX/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/XXX/subscriptions", + "organizations_url": "https://api.github.com/users/XXX/orgs", + "repos_url": "https://api.github.com/users/XXX/repos", + "events_url": "https://api.github.com/users/XXX/events{/privacy}", + "received_events_url": "https://api.github.com/users/XXX/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 8397609782, + "node_id": "LA_kwDOOIv1lc8AAAAB9IlbNg", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/coding%20guideline", + "name": "coding guideline", + "color": "AD28F1", + "default": false, + "description": "An issue related to a suggestion for a coding guideline" + }, + { + "id": 8677326095, + "node_id": "LA_kwDOOIv1lc8AAAACBTV9Dw", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/category:%20mandatory", + "name": "category: mandatory", + "color": "5E1F44", + "default": false, + "description": "A coding guideline with category mandatory" + }, + { + "id": 8677332914, + "node_id": "LA_kwDOOIv1lc8AAAACBTWXsg", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/decidability:%20decidable", + "name": "decidability: decidable", + "color": "D4BCD7", + "default": false, + "description": "A coding guideline which can be checked automatically" + }, + { + "id": 8677341923, + "node_id": "LA_kwDOOIv1lc8AAAACBTW64w", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/scope:%20module", + "name": "scope: module", + "color": "AC7485", + "default": false, + "description": "A coding guideline that can be determined applied at the module level" + }, + { + "id": 8677368665, + "node_id": "LA_kwDOOIv1lc8AAAACBTYjWQ", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/chapter:%20expressions", + "name": "chapter: expressions", + "color": "D82523", + "default": false, + "description": "" + }, + { + "id": 8677484351, + "node_id": "LA_kwDOOIv1lc8AAAACBTfnPw", + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/labels/status:%20draft", + "name": "status: draft", + "color": "c5def5", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "PLeVasseur", + "id": 11622119, + "node_id": "MDQ6VXNlcjExNjIyMTE5", + "avatar_url": "https://avatars.githubusercontent.com/u/11622119?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/PLeVasseur", + "html_url": "https://github.com/PLeVasseur", + "followers_url": "https://api.github.com/users/PLeVasseur/followers", + "following_url": "https://api.github.com/users/PLeVasseur/following{/other_user}", + "gists_url": "https://api.github.com/users/PLeVasseur/gists{/gist_id}", + "starred_url": "https://api.github.com/users/PLeVasseur/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/PLeVasseur/subscriptions", + "organizations_url": "https://api.github.com/users/PLeVasseur/orgs", + "repos_url": "https://api.github.com/users/PLeVasseur/repos", + "events_url": "https://api.github.com/users/PLeVasseur/events{/privacy}", + "received_events_url": "https://api.github.com/users/PLeVasseur/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "assignees": [ + { + "login": "PLeVasseur", + "id": 11622119, + "node_id": "MDQ6VXNlcjExNjIyMTE5", + "avatar_url": "https://avatars.githubusercontent.com/u/11622119?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/PLeVasseur", + "html_url": "https://github.com/PLeVasseur", + "followers_url": "https://api.github.com/users/PLeVasseur/followers", + "following_url": "https://api.github.com/users/PLeVasseur/following{/other_user}", + "gists_url": "https://api.github.com/users/PLeVasseur/gists{/gist_id}", + "starred_url": "https://api.github.com/users/PLeVasseur/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/PLeVasseur/subscriptions", + "organizations_url": "https://api.github.com/users/PLeVasseur/orgs", + "repos_url": "https://api.github.com/users/PLeVasseur/repos", + "events_url": "https://api.github.com/users/PLeVasseur/events{/privacy}", + "received_events_url": "https://api.github.com/users/PLeVasseur/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + ], + "milestone": null, + "comments": 7, + "created_at": "2025-07-24T02:35:55Z", + "updated_at": "2025-07-26T16:04:29Z", + "closed_at": null, + "author_association": "COLLABORATOR", + "type": null, + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "body": "### Chapter\n\nExpressions\n\n### Guideline Title\n\nInteger shift shall only be performed through `checked_` APIs\n\n### Category\n\nMandatory\n\n### Status\n\nDraft\n\n### Release Begin\n\n1.7.0\n\n### Release End\n\nlatest\n\n### FLS Paragraph ID\n\nfls_sru4wi5jomoe\n\n### Decidability\n\nDecidable\n\n### Scope\n\nModule\n\n### Tags\n\nnumerics, reduce-human-error, maintainability, portability, surprising-behavior\n\n### Amplification\n\nIn particular, the user should only perform left shifts via the [checked_shl](https://doc.rust-lang.org/core/index.html?search=%22checked_shl%22) function and right shifts via the [checked_shr](https://doc.rust-lang.org/core/index.html?search=%22checked_shr%22) function. Both of these functions exist in [core](https://doc.rust-lang.org/core/index.html).\n\nThis rule applies to the following primitive types:\n* `i8`\n* `i16`\n* `i32`\n* `i64`\n* `i128`\n* `u8`\n* `u16`\n* `u32`\n* `u64`\n* `u128`\n* `usize`\n* `isize`\n\n### Exception(s)\n\n_No response_\n\n### Rationale\n\nThis is directly inspired by [INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand](https://wiki.sei.cmu.edu/confluence/display/c/INT34-C.+Do+not+shift+an+expression+by+a+negative+number+of+bits+or+by+greater+than+or+equal+to+the+number+of+bits+that+exist+in+the+operand).\n\nIn Rust these out-of-range shifts don't give rise to Undefined Behavior; however, they are still problematic in Safety Critical contexts for two reasons.\n\n**Reason 1: inconsistent behavior**\n\nThe behavior of shift operations depends on the compilation mode. Say for example, that we have a number `x` of type `uN`, and we perform the operation\n\n`x << M` \n\nThen, it will behave like this:\n\n| **Compilation Mode** | `0 <= M < N` | `M < 0` | `N <= M` |\n|:--------------------:|:----------------:|:---------------------:|:-------------------:|\n| Debug | Shifts normally | Panics | Panics |\n| Release | Shifts normally | Shifts by `M mod N` | Shifts by `M mod N` |\n\n> Note: the behavior is exactly the same for the `>>` operator.\n\nPanicking in `Debug` is an issue by itself, however, a perhaps larger issue there is that its behavior is different from that of `Release`. Such inconsistencies aren't acceptable in Safety Critical scenarios.\n\nTherefore, a consistently-behaved operation should be required for performing shifts.\n\n# Reason 2: programmer intent\n\nThere is no scenario in which it makes sense to perform a shift of negative length, or of more than `N - 1` bits. The operation itself becomes meaningless.\n\nTherefore, an API that restricts the length of the shift to the range `[0, N - 1]` should be used instead of the `<<` and `>>` operators.\n\n# The Solution\n\nThe ideal solution for this exists in `core`: `checked_shl` and `checked_shr`.\n\n`::checked_shl(M)` returns a value of type `Option`, in the following way:\n\n* If `M < 0`, the output is `None`\n* If `0 <= M < N` for `T` of `N` bits, then the output is `Some(T)`\n* If `N <= M`, the output is `None`\n\nThis API has consistent behavior across `Debug` and `Release`, and makes the programmer intent explicit, which effectively solves this issue.\n\n### Non-Compliant Example - Prose\n\nAs seen below in the `non_compliant_example()` function:\n\n* A `Debug` build **panics**, \n* Whereas a `Release` build prints the values:\n \n ```\n 61 << -1 = 2147483648\n 61 << 4 = 976\n 61 << 40 = 15616\n ```\n\nThis shows **Reason 1** prominently.\n\n**Reason 2** is not seen in the code, because it is a reason of programmer intent: shifts by less than 0 or by more than `N - 1` (`N` being the bit-length of the value being shifted) are both meaningless.\n\n### Non-Compliant Example - Code\n\n```rust\nfn non_compliant_example() {\n fn bad_shl(bits: u32, shift: i32) -> u32 {\n bits << shift\n }\n \n let bits : u32 = 61;\n let shifts = vec![-1, 4, 40];\n \n for sh in shifts {\n println!(\"{bits} << {sh} = {}\", bad_shl(bits, sh));\n }\n}\n```\n### Compliant Example - Prose\n\nAs seen below in the `compliant_example()` function:\n\n* Both `Debug` and `Release` give the same exact output, which addresses **Reason 1**.\n* Shifting by negative values is impossible due to the fact that `checked_shl` only accepts unsigned integers as shift lengths.\n* Shifting by more than `N - 1` (`N` being the bit-length of the value being shifted) returns a `None` value:\n ```\n 61 << 4 = Some(976)\n 61 << 40 = None\n ```\n\nThe last 2 observations show how this addresses **Reason 2**.\n\n### Compliant Example - Code\n\n```rust\nfn compliant_example() {\n fn good_shl(bits: u32, shift: u32) -> Option {\n bits.checked_shl(shift)\n }\n \n let bits : u32 = 61;\n // let shifts = vec![-1, 4, 40];\n // ^--- Would not typecheck, as checked_shl\n // only accepts positive shift amounts\n let shifts = vec![4, 40];\n \n for sh in shifts {\n println!(\"{bits} << {sh} = {:?}\", good_shl(bits, sh));\n }\n}\n```", + "closed_by": null, + "reactions": { + "url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156/timeline", + "performed_via_github_app": null, + "state_reason": null +} diff --git a/.github/auto-pr-tests/test_issue_03.snapshot b/.github/auto-pr-tests/test_issue_03.snapshot new file mode 100644 index 0000000..61e5a06 --- /dev/null +++ b/.github/auto-pr-tests/test_issue_03.snapshot @@ -0,0 +1,165 @@ +=====CONTENT===== + +.. guideline:: Integer shift shall only be performed through `checked_` APIs + :id: gui_U9mRD3cb9Iic + :category: mandatory + :status: draft + :release: 1.7.0-latest + :fls: fls_sru4wi5jomoe + :decidability: decidable + :scope: module + :tags: numerics,,reduce-human-error,,maintainability,,portability,,surprising-behavior + + In particular, the user should only perform left shifts via the `checked_shl `_ function and right shifts via the `checked_shr `_ function. Both of these functions exist in `core `_. + + This rule applies to the following primitive types: + + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + .. rationale:: + :id: rat_5pmnzKAN4skA + :status: draft + + This is directly inspired by `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand `_. + + In Rust these out-of-range shifts don't give rise to Undefined Behavior; however, they are still problematic in Safety Critical contexts for two reasons. + + **Reason 1: inconsistent behavior** + + The behavior of shift operations depends on the compilation mode. Say for example, that we have a number ``x`` of type ``uN``\ , and we perform the operation + + ``x << M`` + + Then, it will behave like this: + + .. list-table:: + :header-rows: 1 + + * - **Compilation Mode** + - ``0 <= M < N`` + - ``M < 0`` + - ``N <= M`` + * - Debug + - Shifts normally + - Panics + - Panics + * - Release + - Shifts normally + - Shifts by ``M mod N`` + - Shifts by ``M mod N`` + + + .. + + Note: the behavior is exactly the same for the ``>>`` operator. + + + Panicking in ``Debug`` is an issue by itself, however, a perhaps larger issue there is that its behavior is different from that of ``Release``. Such inconsistencies aren't acceptable in Safety Critical scenarios. + + Therefore, a consistently-behaved operation should be required for performing shifts. + + Reason 2: programmer intent + =========================== + + There is no scenario in which it makes sense to perform a shift of negative length, or of more than ``N - 1`` bits. The operation itself becomes meaningless. + + Therefore, an API that restricts the length of the shift to the range ``[0, N - 1]`` should be used instead of the ``<<`` and ``>>`` operators. + + The Solution + ============ + + The ideal solution for this exists in ``core``\ : ``checked_shl`` and ``checked_shr``. + + ``::checked_shl(M)`` returns a value of type ``Option``\ , in the following way: + + + * If ``M < 0``\ , the output is ``None`` + * If ``0 <= M < N`` for ``T`` of ``N`` bits, then the output is ``Some(T)`` + * If ``N <= M``\ , the output is ``None`` + + This API has consistent behavior across ``Debug`` and ``Release``\ , and makes the programmer intent explicit, which effectively solves this issue. + + .. non_compliant_example:: + :id: non_compl_ex_S5VnAJx9H8q1 + :status: draft + + As seen below in the ``non_compliant_example()`` function: + + + * A ``Debug`` build **panics**\ , + * + Whereas a ``Release`` build prints the values: + + .. code-block:: + + 61 << -1 = 2147483648 + 61 << 4 = 976 + 61 << 40 = 15616 + + This shows **Reason 1** prominently. + + **Reason 2** is not seen in the code, because it is a reason of programmer intent: shifts by less than 0 or by more than ``N - 1`` (\ ``N`` being the bit-length of the value being shifted) are both meaningless. + + .. code-block:: rust + + fn non_compliant_example() { + fn bad_shl(bits: u32, shift: i32) -> u32 { + bits << shift + } + + let bits : u32 = 61; + let shifts = vec![-1, 4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {}", bad_shl(bits, sh)); + } + } + + .. compliant_example:: + :id: compl_ex_tKfXqfcKgTvV + :status: draft + + As seen below in the ``compliant_example()`` function: + + + * Both ``Debug`` and ``Release`` give the same exact output, which addresses **Reason 1**. + * Shifting by negative values is impossible due to the fact that ``checked_shl`` only accepts unsigned integers as shift lengths. + * Shifting by more than ``N - 1`` (\ ``N`` being the bit-length of the value being shifted) returns a ``None`` value: + .. code-block:: + + 61 << 4 = Some(976) + 61 << 40 = None + + The last 2 observations show how this addresses **Reason 2**. + + .. code-block:: rust + + fn compliant_example() { + fn good_shl(bits: u32, shift: u32) -> Option { + bits.checked_shl(shift) + } + + let bits : u32 = 61; + // let shifts = vec![-1, 4, 40]; + // ^--- Would not typecheck, as checked_shl + // only accepts positive shift amounts + let shifts = vec![4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", good_shl(bits, sh)); + } + } + +=====CONTENT=END===== diff --git a/.github/auto-pr-tests/test_runner.py b/.github/auto-pr-tests/test_runner.py index 20f8b2b..37b9476 100644 --- a/.github/auto-pr-tests/test_runner.py +++ b/.github/auto-pr-tests/test_runner.py @@ -3,8 +3,10 @@ from pathlib import Path import difflib + def normalize_ids(text: str) -> str: - return re.sub(r'(:id:\s+[a-z_]+)_[a-zA-Z0-9]+', r'\1_IGNORED_ID', text) + return re.sub(r"(:id:\s+[a-z_]+)_[a-zA-Z0-9]+", r"\1_IGNORED_ID", text) + def compare(issue_json_path: Path, snapshot_path: Path) -> bool: input_json = issue_json_path.read_text() @@ -14,10 +16,10 @@ def compare(issue_json_path: Path, snapshot_path: Path) -> bool: input=input_json.encode(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, - check=True + check=True, ) - # Normalize the actual output and the snapshot, this is crucial in snapshot tests to + # Normalize the actual output and the snapshot, this is crucial in snapshot tests to # ignore random/volatile values. actual_output = normalize_ids(result.stdout.decode()) expected_output = normalize_ids(snapshot_path.read_text()) @@ -29,7 +31,7 @@ def compare(issue_json_path: Path, snapshot_path: Path) -> bool: actual_output.splitlines(), fromfile=str(snapshot_path), tofile="generated", - lineterm="" + lineterm="", ) print(f"Difference found in {issue_json_path.name}:") print("\n".join(diff)) @@ -38,17 +40,22 @@ def compare(issue_json_path: Path, snapshot_path: Path) -> bool: print(f"{issue_json_path.name} matches snapshot.") return True + # to generate snapshot: # create or change the test_issue_xx file and then use this command after replacing XX with your test number: ## `cat .github/auto-pr-tests/test_issue_XX.json | uv run python scripts/auto-pr-helper.py 2&>/dev/null > .github/auto-pr-tests/test_issue_0XX.snapshot` tests = { "test_01": ( Path(".github/auto-pr-tests/test_issue_01.json"), - Path(".github/auto-pr-tests/test_issue_01.snapshot") + Path(".github/auto-pr-tests/test_issue_01.snapshot"), ), "test_02": ( Path(".github/auto-pr-tests/test_issue_02.json"), - Path(".github/auto-pr-tests/test_issue_02.snapshot") + Path(".github/auto-pr-tests/test_issue_02.snapshot"), + ), + "test_03": ( + Path(".github/auto-pr-tests/test_issue_03.json"), + Path(".github/auto-pr-tests/test_issue_03.snapshot"), ), } diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 17a6da2..69d53e8 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -28,7 +28,9 @@ jobs: - name: Run Python script to generate guideline file run: | - echo '${{ toJson(github.event.issue) }}' | uv run python scripts/auto-pr-helper.py --save + cat < str: """ Generate a .rst guideline entry from field values. @@ -64,8 +65,8 @@ def guideline_rst_template( def norm(value: str) -> str: return value.strip().lower() - indented_compliant_ex= indent(compliant_example.strip(), " " * 13) - indented_non_compliant_ex= indent(non_compliant_ex.strip(), " " * 13) + indented_compliant_ex = indent(compliant_example.strip(), " " * 13) + indented_non_compliant_ex = indent(non_compliant_ex.strip(), " " * 13) guideline_text = dedent(f""" .. guideline:: {guideline_title.strip()} :id: {guideline_id} @@ -108,11 +109,13 @@ def norm(value: str) -> str: return guideline_text + def generate_id(prefix): """Generate a random ID with the given prefix.""" random_part = "".join(random.choice(CHARS) for _ in range(ID_LENGTH)) return f"{prefix}_{random_part}" + def generate_guideline_template(): """Generate a complete guideline template with all required sections.""" # Generate IDs for all sections @@ -120,54 +123,57 @@ def generate_guideline_template(): rationale_id = generate_id("rat") non_compliant_example_id = generate_id("non_compl_ex") compliant_example_id = generate_id("compl_ex") - + template = guideline_rst_template( - guideline_title="Title Here", - category="", - status="draft", - release_begin="", - release_end="", - fls_id="", - decidability="", - scope="", - tags="", - amplification="Description of the guideline goes here.", - rationale="Explanation of why this guideline is important.", - non_compliant_ex_prose="Explanation of code example.", - non_compliant_ex=""" fn example_function() {\n // Non-compliant implementation\n } """, - compliant_example_prose="Explanation of code example.", - compliant_example=""" fn example_function() {\n // Compliant implementation\n } """, + guideline_title="Title Here", + category="", + status="draft", + release_begin="", + release_end="", + fls_id="", + decidability="", + scope="", + tags="", + amplification="Description of the guideline goes here.", + rationale="Explanation of why this guideline is important.", + non_compliant_ex_prose="Explanation of code example.", + non_compliant_ex=""" fn example_function() {\n // Non-compliant implementation\n } """, + compliant_example_prose="Explanation of code example.", + compliant_example=""" fn example_function() {\n // Compliant implementation\n } """, ) return template + def parse_args(): """Parse command-line arguments.""" parser = argparse.ArgumentParser( description="Generate guideline templates with randomly generated IDs" ) parser.add_argument( - "-n", - "--number-of-templates", - type=int, + "-n", + "--number-of-templates", + type=int, default=1, - help="Number of templates to generate (default: 1)" + help="Number of templates to generate (default: 1)", ) return parser.parse_args() + def main(): """Generate the specified number of guideline templates.""" args = parse_args() num_templates = args.number_of_templates - + for i in range(num_templates): if num_templates > 1: - print(f"=== Template {i+1} ===\n") - + print(f"=== Template {i + 1} ===\n") + template = generate_guideline_template() print(template) - + if num_templates > 1 and i < num_templates - 1: print("\n" + "=" * 80 + "\n") + if __name__ == "__main__": main() diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 02db383..5fba5f5 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -1,7 +1,5 @@ import json import re -import random -import string import argparse import sys import os @@ -11,7 +9,18 @@ parent_dir = os.path.abspath(os.path.join(script_dir, "..")) sys.path.append(parent_dir) -from generate_guideline_templates import generate_id, guideline_rst_template, issue_header_map +from generate_guideline_templates import ( + guideline_rst_template, + issue_header_map, +) + +from textwrap import indent + +from m2r import convert + + +def md_to_rst(markdown: str) -> str: + return convert(markdown) def extract_form_fields(issue_body: str) -> dict: @@ -29,7 +38,7 @@ def extract_form_fields(issue_body: str) -> dict: # Look for '###' in every line, ### represent a sections/field in a guideline for line in lines: - header_match = re.match(r'^### (.+)$', line.strip()) + header_match = re.match(r"^### (.+)$", line.strip()) if header_match: # Save previous field value if any if current_key is not None: @@ -41,25 +50,29 @@ def extract_form_fields(issue_body: str) -> dict: fields[current_key] = value header = header_match.group(1).strip() - current_key = issue_header_map.get(header) # Map to dict key or None if unknown + current_key = issue_header_map.get( + header + ) # Map to dict key or None if unknown current_value_lines = [] else: current_value_lines.append(line) return fields + def save_guideline_file(content: str, chapter: str): """ Appends a guideline to a chapter """ filename = f"src/coding-guidelines/{chapter.lower().replace(' ', '-')}.rst" - with open(filename, 'a', encoding='utf-8') as f: + with open(filename, "a", encoding="utf-8") as f: f.write(content) print(f"Saved guideline to {filename}") + def guideline_template(fields: dict) -> str: """ - This function turns a dictionary that contains the guideline fields + This function turns a dictionary that contains the guideline fields into a proper .rst guideline format """ @@ -73,9 +86,20 @@ def format_code_block(code: str, lang: str = "rust") -> str: lines = lines[1:] if lines and lines[-1].strip() == "```": lines = lines[:-1] - indented_code = "\n".join(f" {line}" for line in lines) # indentation + indented_code = "\n".join( + f" {line}" for line in lines + ) # Adds the required indentation return f"\n\n{indented_code}\n" + amplification_text = indent(md_to_rst(get("amplification")), " " * 12) + rationale_text = indent(md_to_rst(get("rationale")), " " * 16) + non_compliant_ex_prose_text = indent( + md_to_rst(get("non_compliant_ex_prose")), " " * 16 + ) + compliant_example_prose_text = indent( + md_to_rst(get("compliant_example_prose")), " " * 16 + ) + guideline_text = guideline_rst_template( guideline_title=get("guideline_title"), category=get("category"), @@ -86,34 +110,37 @@ def format_code_block(code: str, lang: str = "rust") -> str: decidability=get("decidability"), scope=get("scope"), tags=get("tags"), - amplification=get("amplification"), - rationale=get("rationale"), - non_compliant_ex_prose=get("non_compliant_ex_prose"), + amplification=amplification_text, + rationale=rationale_text, + non_compliant_ex_prose=non_compliant_ex_prose_text, non_compliant_ex=format_code_block(get("non_compliant_ex")), - compliant_example_prose=get("compliant_example_prose"), - compliant_example=format_code_block(get("compliant_example")) + compliant_example_prose=compliant_example_prose_text, + compliant_example=format_code_block(get("compliant_example")), ) return guideline_text -if __name__ == "__main__": +if __name__ == "__main__": # parse arguments - parser = argparse.ArgumentParser(description="Generate guideline from GitHub issue JSON.") - parser.add_argument("--save", action="store_true", help="Save the generated guideline file.") + parser = argparse.ArgumentParser( + description="Generate guideline from GitHub issue JSON." + ) + parser.add_argument( + "--save", action="store_true", help="Save the generated guideline file." + ) args = parser.parse_args() ## locally test with `cat scripts/test_issue_sample.json | python3 scripts/auto-pr-helper.py` ## or use `curl https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/135 | uv run python scripts/auto-pr-helper.py` - # Read json from stdin stdin_issue_json = sys.stdin.read() json_issue = json.loads(stdin_issue_json) - issue_number = json_issue['number'] - issue_title = json_issue['title'] - issue_body = json_issue['body'] + issue_number = json_issue["number"] + issue_title = json_issue["title"] + issue_body = json_issue["body"] fields = extract_form_fields(issue_body) chapter = fields["chapter"] content = guideline_template(fields) @@ -123,4 +150,4 @@ def format_code_block(code: str, lang: str = "rust") -> str: print("=====CONTENT=END=====") if args.save: - save_guideline_file(content, chapter) + save_guideline_file(content, chapter) From fe1279b1804fdf3635ecc28444268274fb59d10a Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 27 Jul 2025 23:56:41 +0100 Subject: [PATCH 23/30] add m2r to pyproject.toml --- pyproject.toml | 1 + scripts/auto-pr-helper.py | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9e11cd0..b33da4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.12" dependencies = [ "builder", "tqdm", + "m2r", "sphinx>=8.2.3", "sphinx-autobuild>=2024.10.3", "sphinx-needs>=5.1.0", diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 5fba5f5..e4bd04b 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -3,6 +3,8 @@ import argparse import sys import os +from textwrap import indent +from m2r import convert scriptpath = "../" script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -14,10 +16,6 @@ issue_header_map, ) -from textwrap import indent - -from m2r import convert - def md_to_rst(markdown: str) -> str: return convert(markdown) From 86ab9d4e91db78da69ada6a0d19c023478832803 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 00:12:16 +0100 Subject: [PATCH 24/30] fix double commit issue --- .github/workflows/auto-pr-on-issue.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 69d53e8..68a12b3 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -32,13 +32,11 @@ jobs: ${{ toJson(github.event.issue) }} EOF - - name: Commit generated guideline files - run: | - git add src/coding-guidelines/ - git commit -m "Add guideline for issue #${{ github.event.issue.number }}" - - name: Create Pull Request uses: peter-evans/create-pull-request@v6 + with: + add-paths: | + src/coding-guidelines/ with: commit-message: | Add guideline for issue #${{ github.event.issue.number }} From 9641978e2531084a825953c87376f2c5a54d9244 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 00:14:19 +0100 Subject: [PATCH 25/30] fix ci: merge dup with --- .github/workflows/auto-pr-on-issue.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index 68a12b3..f9f45ff 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -37,7 +37,6 @@ jobs: with: add-paths: | src/coding-guidelines/ - with: commit-message: | Add guideline for issue #${{ github.event.issue.number }} From 3cc0622ad35cc9e7a4a0da467e6a8e20e10a3494 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 00:43:59 +0100 Subject: [PATCH 26/30] fix: we assume that tags are already seperated in the issue, update snapshots, update uv lock --- .github/auto-pr-tests/test_issue_01.snapshot | 13 +++++++----- .github/auto-pr-tests/test_issue_02.snapshot | 11 ++++++---- .github/auto-pr-tests/test_issue_03.snapshot | 13 +++++++----- generate_guideline_templates.py | 2 +- scripts/auto-pr-helper.py | 3 +++ uv.lock | 21 ++++++++++++++++++++ 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.github/auto-pr-tests/test_issue_01.snapshot b/.github/auto-pr-tests/test_issue_01.snapshot index d280270..c7cd3a9 100644 --- a/.github/auto-pr-tests/test_issue_01.snapshot +++ b/.github/auto-pr-tests/test_issue_01.snapshot @@ -1,25 +1,28 @@ +================ +test gatest ga +================ =====CONTENT===== .. guideline:: test ga - :id: gui_LzfV28IVG7qO + :id: gui_irRvGs6pQsDQ :category: advisory :status: draft :release: 1.1.1-1.1.1 :fls: fls_fsdjkfslkdfj :decidability: decidable :scope: crate - :tags: test,gatest,ga + :tags: test gatest ga hehehehe .. rationale:: - :id: rat_nPL2Cv7VBdqG + :id: rat_dtzzrjsA2ShY :status: draft test ga .. non_compliant_example:: - :id: non_compl_ex_Bv7HMs0AVNlH + :id: non_compl_ex_z824usgcSNr8 :status: draft test ga @@ -29,7 +32,7 @@ dfhsdfkjshdfskdjhftest ga .. compliant_example:: - :id: compl_ex_H5Z7OsxZX3Ig + :id: compl_ex_tzRbMfkM4NHX :status: draft test ga diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index 4bfa6a9..3c27d8d 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,7 +1,10 @@ +================ +reduce-human-error +================ =====CONTENT===== .. guideline:: Recursive function are not allowed - :id: gui_VTTlQLBlJvOf + :id: gui_NR0NkGkFOfEl :category: required :status: draft :release: 1.3.0-latest @@ -13,13 +16,13 @@ Any function shall not call itself directly or indirectly .. rationale:: - :id: rat_nN9SoDL0U2ZK + :id: rat_q7Vd2qfzZEpV :status: draft Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports `tail call optimization `_\ , this optimization is not guaranteed and depends on the specific implementation and function structure. There is an `open RFC to guarantee tail call optimization in the Rust compiler `_\ , but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. .. non_compliant_example:: - :id: non_compl_ex_kw16P66E0GjL + :id: non_compl_ex_MjZZmMuITSnb :status: draft The below function ``concat_strings`` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. @@ -45,7 +48,7 @@ } .. compliant_example:: - :id: compl_ex_SIIwOv4F04bs + :id: compl_ex_SdfkmboJ2Cih :status: draft The following code implements the same functionality using iteration instead of recursion. The ``stack`` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. diff --git a/.github/auto-pr-tests/test_issue_03.snapshot b/.github/auto-pr-tests/test_issue_03.snapshot index 61e5a06..2acc931 100644 --- a/.github/auto-pr-tests/test_issue_03.snapshot +++ b/.github/auto-pr-tests/test_issue_03.snapshot @@ -1,14 +1,17 @@ +================ +numerics, reduce-human-error, maintainability, portability, surprising-behavior +================ =====CONTENT===== .. guideline:: Integer shift shall only be performed through `checked_` APIs - :id: gui_U9mRD3cb9Iic + :id: gui_L1efkdhJroHY :category: mandatory :status: draft :release: 1.7.0-latest :fls: fls_sru4wi5jomoe :decidability: decidable :scope: module - :tags: numerics,,reduce-human-error,,maintainability,,portability,,surprising-behavior + :tags: numerics, reduce-human-error, maintainability, portability, surprising-behavior In particular, the user should only perform left shifts via the `checked_shl `_ function and right shifts via the `checked_shr `_ function. Both of these functions exist in `core `_. @@ -29,7 +32,7 @@ * ``isize`` .. rationale:: - :id: rat_5pmnzKAN4skA + :id: rat_eo9iSNAyUWop :status: draft This is directly inspired by `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand `_. @@ -92,7 +95,7 @@ This API has consistent behavior across ``Debug`` and ``Release``\ , and makes the programmer intent explicit, which effectively solves this issue. .. non_compliant_example:: - :id: non_compl_ex_S5VnAJx9H8q1 + :id: non_compl_ex_4RnVPjQs06uM :status: draft As seen below in the ``non_compliant_example()`` function: @@ -128,7 +131,7 @@ } .. compliant_example:: - :id: compl_ex_tKfXqfcKgTvV + :id: compl_ex_n0Is3JLe1YwT :status: draft As seen below in the ``compliant_example()`` function: diff --git a/generate_guideline_templates.py b/generate_guideline_templates.py index b4aedfc..4803427 100755 --- a/generate_guideline_templates.py +++ b/generate_guideline_templates.py @@ -76,7 +76,7 @@ def norm(value: str) -> str: :fls: {norm(fls_id)} :decidability: {norm(decidability)} :scope: {norm(scope)} - :tags: {",".join(tags.strip().split())} + :tags: {tags} {amplification.strip()} diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index e4bd04b..1d60831 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -97,6 +97,9 @@ def format_code_block(code: str, lang: str = "rust") -> str: compliant_example_prose_text = indent( md_to_rst(get("compliant_example_prose")), " " * 16 ) + print("================") + print(get("tags")) + print("================") guideline_text = guideline_rst_template( guideline_title=get("guideline_title"), diff --git a/uv.lock b/uv.lock index 98f7c01..7184d3e 100644 --- a/uv.lock +++ b/uv.lock @@ -194,6 +194,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, ] +[[package]] +name = "m2r" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "mistune" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/65/fd40fbdc608298e760affb95869c3baed237dfe5649d62da1eaa1deca8f3/m2r-0.3.1.tar.gz", hash = "sha256:aafb67fc49cfb1d89e46a3443ac747e15f4bb42df20ed04f067ad9efbee256ab", size = 16622 } + [[package]] name = "markupsafe" version = "3.0.2" @@ -232,6 +242,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] +[[package]] +name = "mistune" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", size = 58322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ec/4b43dae793655b7d8a25f76119624350b4d65eb663459eb9603d7f1f0345/mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4", size = 16220 }, +] + [[package]] name = "packaging" version = "24.2" @@ -353,6 +372,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "builder" }, + { name = "m2r" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, { name = "sphinx-needs" }, @@ -363,6 +383,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "builder", virtual = "builder" }, + { name = "m2r" }, { name = "sphinx", specifier = ">=8.2.3" }, { name = "sphinx-autobuild", specifier = ">=2024.10.3" }, { name = "sphinx-needs", specifier = ">=5.1.0" }, From e3582f47aa0e46c877135c820e406330eda8aeb5 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 00:48:29 +0100 Subject: [PATCH 27/30] Add snapshot tests readme.md --- .github/auto-pr-tests/README.md | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/auto-pr-tests/README.md diff --git a/.github/auto-pr-tests/README.md b/.github/auto-pr-tests/README.md new file mode 100644 index 0000000..7a7271d --- /dev/null +++ b/.github/auto-pr-tests/README.md @@ -0,0 +1,36 @@ +## How to Add and Document Tests in `auto-pr-tests` + +The test script `scripts/auto-pr-helper.py` transforms an issue from JSON format into our `.rst` format. + +This directory contains test issue files in JSON format along with their expected output snapshots. These tests are executed by the script `test_runner.py`. + +### Adding a New Test + +1. **Create Input JSON File** + + First, obtain the JSON data for the GitHub issue you want to use as a test case. Name the file test_issue_XX.json, where XX is a number, Instructions on how to get this JSON data are provided in the next section. + +2. **Generate Expected Output Snapshot** + Run the following command to generate the corresponding `.snapshot` file automatically: + + ```bash + cat .github/auto-pr-tests/test_issue_XX.json | uv run scripts/auto-pr-helper.py > .github/auto-pr-tests/test_issue_XX.snapshot + ``` + It is better to run this command and manually verify the output, rather than creating the snapshot manually. +3. **Add Test to the Test List** + Add your new JSON and snapshot file paths to the tests dictionary inside test_runner.py(line 47). This registers the new test so it will be run. +4. Run Tests + Execute test_runner.py to verify that the output matches the expected snapshots. + + +### How to Get Issue JSON from GitHub API + +To create the input JSON file (`test_issue_XX.json`), you can fetch the issue data directly from the GitHub API: + +1. Find the issue number and repository where the issue is located. + +2. Use a tool like `curl` or any HTTP client to request the issue JSON data: + +```bash +curl https://api.github.com/repos/OWNER/REPO/issues/ISSUE_NUMBER > test_issue_XX.json +``` From d0e216e52269b735641ec7a431cf9c94ab38cd59 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 00:52:58 +0100 Subject: [PATCH 28/30] Add scripts/auto-pr-helper.py readme.md --- scripts/README.md | 25 +++++++++++++++++++++++++ scripts/auto-pr-helper.py | 3 --- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 scripts/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..0f9b3ad --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,25 @@ +### `auto-pr-helper.py` + +This script is a utility for automating the generation of guidelines. It takes a GitHub issue's JSON data from standard input, parses its body (which is expected to follow a specific issue template), and converts it into a formatted reStructuredText (`.rst`) guideline. + +--- + +### How to Use + +The script reads a JSON payload from **standard input**. The most common way to provide this input is by using a pipe (`|`) to feed the output of another command into the script. + +#### 1. Using a Local JSON File + +For local testing, you can use `cat` to pipe the contents of a saved GitHub issue JSON file into the script. + +```bash +cat path/to/your_issue.json | uv run scripts/auto-pr-helper.py +``` + +#### 2. Fetching from the GitHub API directly +You can fetch the data for a live issue directly from the GitHub API using curl and pipe it to the script. This is useful for getting the most up-to-date content. + +```bash +curl https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/156 | uv run ./scripts/auto-pr-helper.py +``` +``` diff --git a/scripts/auto-pr-helper.py b/scripts/auto-pr-helper.py index 1d60831..e4bd04b 100644 --- a/scripts/auto-pr-helper.py +++ b/scripts/auto-pr-helper.py @@ -97,9 +97,6 @@ def format_code_block(code: str, lang: str = "rust") -> str: compliant_example_prose_text = indent( md_to_rst(get("compliant_example_prose")), " " * 16 ) - print("================") - print(get("tags")) - print("================") guideline_text = guideline_rst_template( guideline_title=get("guideline_title"), From a52f8839c297b473811a2ca16625a31f18d667d4 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 01:03:06 +0100 Subject: [PATCH 29/30] feat: tag the original author in the PR itself --- .github/workflows/auto-pr-on-issue.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/auto-pr-on-issue.yml b/.github/workflows/auto-pr-on-issue.yml index f9f45ff..7053a0d 100644 --- a/.github/workflows/auto-pr-on-issue.yml +++ b/.github/workflows/auto-pr-on-issue.yml @@ -46,4 +46,7 @@ jobs: title: "[auto-pr] #${{ github.event.issue.number }}: ${{ github.event.issue.title }}" body: | This PR was automatically generated from issue #${{ github.event.issue.number }}. + + **Authored by:** @${{ github.event.issue.user.login }} + Closes #${{ github.event.issue.number }}. From ceae2f77b2c121c59f9fb498f6aa2cb36ef69547 Mon Sep 17 00:00:00 2001 From: x0rw Date: Mon, 28 Jul 2025 01:06:17 +0100 Subject: [PATCH 30/30] update snapshots --- .github/auto-pr-tests/test_issue_01.snapshot | 11 ++++------- .github/auto-pr-tests/test_issue_02.snapshot | 11 ++++------- .github/auto-pr-tests/test_issue_03.snapshot | 11 ++++------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/auto-pr-tests/test_issue_01.snapshot b/.github/auto-pr-tests/test_issue_01.snapshot index c7cd3a9..e7d147d 100644 --- a/.github/auto-pr-tests/test_issue_01.snapshot +++ b/.github/auto-pr-tests/test_issue_01.snapshot @@ -1,10 +1,7 @@ -================ -test gatest ga -================ =====CONTENT===== .. guideline:: test ga - :id: gui_irRvGs6pQsDQ + :id: gui_3Wl9gOTto7p2 :category: advisory :status: draft :release: 1.1.1-1.1.1 @@ -16,13 +13,13 @@ test gatest ga hehehehe .. rationale:: - :id: rat_dtzzrjsA2ShY + :id: rat_PVjXjtM9G383 :status: draft test ga .. non_compliant_example:: - :id: non_compl_ex_z824usgcSNr8 + :id: non_compl_ex_ABSVsT97MwLG :status: draft test ga @@ -32,7 +29,7 @@ test gatest ga dfhsdfkjshdfskdjhftest ga .. compliant_example:: - :id: compl_ex_tzRbMfkM4NHX + :id: compl_ex_Z9TjeDsRTnwL :status: draft test ga diff --git a/.github/auto-pr-tests/test_issue_02.snapshot b/.github/auto-pr-tests/test_issue_02.snapshot index 3c27d8d..7807737 100644 --- a/.github/auto-pr-tests/test_issue_02.snapshot +++ b/.github/auto-pr-tests/test_issue_02.snapshot @@ -1,10 +1,7 @@ -================ -reduce-human-error -================ =====CONTENT===== .. guideline:: Recursive function are not allowed - :id: gui_NR0NkGkFOfEl + :id: gui_9YIhwNMWWGvW :category: required :status: draft :release: 1.3.0-latest @@ -16,13 +13,13 @@ reduce-human-error Any function shall not call itself directly or indirectly .. rationale:: - :id: rat_q7Vd2qfzZEpV + :id: rat_ZaZYjwUzZzvU :status: draft Recursive functions can easily cause stack overflows, which may result in exceptions or, in some cases, undefined behavior (typically some embedded systems). Although the Rust compiler supports `tail call optimization `_\ , this optimization is not guaranteed and depends on the specific implementation and function structure. There is an `open RFC to guarantee tail call optimization in the Rust compiler `_\ , but this feature has not yet been stabilized. Until tail call optimization is guaranteed and stabilized, developers should avoid using recursive functions to prevent potential stack overflows and ensure program reliability. .. non_compliant_example:: - :id: non_compl_ex_MjZZmMuITSnb + :id: non_compl_ex_imbh3NpZjDSh :status: draft The below function ``concat_strings`` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior. @@ -48,7 +45,7 @@ reduce-human-error } .. compliant_example:: - :id: compl_ex_SdfkmboJ2Cih + :id: compl_ex_fLIgZp7Lnwpb :status: draft The following code implements the same functionality using iteration instead of recursion. The ``stack`` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments. diff --git a/.github/auto-pr-tests/test_issue_03.snapshot b/.github/auto-pr-tests/test_issue_03.snapshot index 2acc931..c6cae41 100644 --- a/.github/auto-pr-tests/test_issue_03.snapshot +++ b/.github/auto-pr-tests/test_issue_03.snapshot @@ -1,10 +1,7 @@ -================ -numerics, reduce-human-error, maintainability, portability, surprising-behavior -================ =====CONTENT===== .. guideline:: Integer shift shall only be performed through `checked_` APIs - :id: gui_L1efkdhJroHY + :id: gui_zMVugBA2A8hz :category: mandatory :status: draft :release: 1.7.0-latest @@ -32,7 +29,7 @@ numerics, reduce-human-error, maintainability, portability, surprising-behavior * ``isize`` .. rationale:: - :id: rat_eo9iSNAyUWop + :id: rat_Ccy1VVgvSXVr :status: draft This is directly inspired by `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand `_. @@ -95,7 +92,7 @@ numerics, reduce-human-error, maintainability, portability, surprising-behavior This API has consistent behavior across ``Debug`` and ``Release``\ , and makes the programmer intent explicit, which effectively solves this issue. .. non_compliant_example:: - :id: non_compl_ex_4RnVPjQs06uM + :id: non_compl_ex_keoHBPbqHD8t :status: draft As seen below in the ``non_compliant_example()`` function: @@ -131,7 +128,7 @@ numerics, reduce-human-error, maintainability, portability, surprising-behavior } .. compliant_example:: - :id: compl_ex_n0Is3JLe1YwT + :id: compl_ex_IG96LnSjFLTt :status: draft As seen below in the ``compliant_example()`` function: