Skip to content

[auto-pr] #13: [Coding Guideline]: sdf #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/auto-pr-on-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Auto Guideline PR

on:
issues:
types:
- labeled

jobs:
auto-pr:
if: "github.event.label.name == 'status: approved'"
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 "[email protected]"
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: "[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 }}.
178 changes: 178 additions & 0 deletions scripts/auto-pr-helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import json
import re
import random
import string


def generate_id(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_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()}

lines = issue_body.splitlines()
current_key = None
current_value_lines = []

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:
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):
"""
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
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:
"""
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")
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()}-{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_example')}

.. compliant_example::
:id: {compliant_example_id}
:status: {get('status').lower()}

{get('compliant_example_prose')}

.. code-block:: rust

{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`

# 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']

fields = extract_form_fields(issue_body)
chapter = fields["chapter"]


content = guideline_template(fields)


save_guideline_file(content, chapter)


124 changes: 124 additions & 0 deletions scripts/test_issue_sample.json
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 39 additions & 0 deletions src/coding-guidelines/concurrency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,42 @@

Concurrency
===========

.. guideline:: sdf
:id: gui_dbF38EKZcPTI
:category: required
:status: draft
:release: 1.1.1-1.1.1
:fls: sdfsdfdsfsdf
:decidability: undecidable
:scope: system
:tags: 1.1.11.1.1

1.1.11.1.1

.. rationale::
:id: rat_qtFYguQZ921k
:status: draft

1.1.11.1.1

.. non_compliant_example::
:id: non_compl_ex_1x3A4wI6AWCh
:status: draft

1.1.11.1.1

.. code-block:: rust



.. compliant_example::
:id: compl_ex_licxuCLaIMiu
:status: draft

1.1.11.1.1

.. code-block:: rust

1.1.11.1.1

Loading