Initial commit

This commit is contained in:
alstr 2020-03-07 15:06:00 +00:00
commit bf153fe2df
5 changed files with 210 additions and 0 deletions

17
.github/workflows/workflow.yaml vendored Normal file
View File

@ -0,0 +1,17 @@
name: "Workflow"
on: ["push"]
jobs:
build:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@master"
- name: "TODO to Issue"
uses: "alstr/todo-to-issue-action@master"
with:
REPO: ${{ github.repository }}
HEAD: ${{ github.event.head }}
BEFORE: ${{ github.event.before }}
SHA: ${{ github.sha }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
LABEL: "# TODO"
id: "todo"

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM python:3-slim AS builder
ADD . /app
WORKDIR /app
RUN pip install --target=/app requests
FROM gcr.io/distroless/python3-debian10
COPY --from=builder /app /app
WORKDIR /app
ENV PYTHONPATH /app
CMD ["/app/main.py"]

79
README.md Normal file
View File

@ -0,0 +1,79 @@
# TODO to Issue Action
This action will convert your `# TODO` comments to GitHub issues when a new commit is pushed.
It will also close an issue when a `# TODO` is removed in a pushed commit.
The `# TODO` comment is commonly used in Python, but this can be customised to whatever you want.
## Usage
Create a workflow file in your .github/workflows directory as follows:
### workflow.yaml
name: "Workflow"
on: ["push"]
jobs:
build:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@master"
- name: "TODO to Issue"
uses: "alstr/todo-to-issue-action@master"
with:
REPO: ${{ github.repository }}
BEFORE: ${{ github.event.before }}
SHA: ${{ github.sha }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
LABEL: "# TODO"
id: "todo"
### Inputs
| Input | Description |
|----------|-------------|
| `REPO` | The path to the repository where the action will be used, e.g. 'alstr/my-repo' (automatically set) |
| `BEFORE` | The SHA of the last pushed commit (automatically set) |
| `SHA` | The SHA of the latest commit (automatically set) |
| `TOKEN` | The GitHub access token to allow us to retrieve, create and update issues (automatically set) |
| `LABEL` | The label that will be used to identify TODO comments (by default this is `# TODO` for Python) |
## Examples
### Adding TODOs
def hello_world():
# TODO Come up with a more imaginative greeting
print('Hello world!')
This will create an issue called "Come up with a more imaginative greeting".
**The action expects a space to follow the `TODO` label.**
Should the title be longer than 50 characters, it will be truncated for the issue title.
The full title will be included in the issue body and a `todo` label will be attached to the issue.
### Removing TODOs
def hello_world():
print('Hello world!')
Removing the `# TODO` comment will close the issue on push.
### Updating TODOs
def hello_world():
# TODO Come up with a more imaginative greeting, like "Greetings world!"
print('Hello world!')
Should you change the `# TODO` text, this will currently create a new issue, so bear that in mind.
This may be updated in future.
## Thanks
Thanks to Jacob Tomlinson for his handy overview of GitHub Actions:
https://www.jacobtomlinson.co.uk/posts/2019/creating-github-actions-in-python/

6
action.yaml Normal file
View File

@ -0,0 +1,6 @@
name: "TODO to Issue"
description: "Converts IDE TODO comments to GitHub issues"
author: "Alastair Mooney"
runs:
using: "docker"
image: "Dockerfile"

97
main.py Normal file
View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""Convert IDE TODOs to GitHub issues."""
import os
import requests
import re
import json
from time import sleep
base_url = 'https://api.github.com/repos/'
def main():
repo = os.getenv('INPUT_REPO')
before = os.getenv('INPUT_BEFORE')
sha = os.getenv('INPUT_SHA')
label = os.getenv('INPUT_LABEL')
params = {
'access_token': os.getenv('INPUT_TOKEN')
}
# Let's compare the last two pushed commits.
diff_url = f'{base_url}{repo}/compare/{before}...{sha}'
diff_headers = {
'Accept': 'application/vnd.github.v3.diff'
}
diff_request = requests.get(url=diff_url, headers=diff_headers, params=params)
if diff_request.status_code == 200:
diff = diff_request.text
# Check for additions in the diff.
addition_pattern = re.compile(r'(?<=^\+).*', re.MULTILINE)
additions = addition_pattern.findall(diff)
new_issues = []
# Filter the additions down to newly added TODOs.
for addition in additions:
todo_pattern = re.compile(r'(?<=' + label + r'\s).*')
todos = todo_pattern.search(addition)
if todos:
new_issues.append(todos.group(0))
# Create new issues for any newly added TODOs.
issues_url = f'{base_url}{repo}/issues'
issue_headers = {
'Content-Type': 'application/json',
}
for issue in new_issues:
title = issue
# Truncate the title if it's longer than 50 chars.
if len(title) > 50:
title = issue[:50] + '...'
new_issue_body = {'title': title, 'body': issue, 'labels': ['todo']}
requests.post(url=issues_url, headers=issue_headers, params=params, data=json.dumps(new_issue_body))
# Don't add too many issues too quickly.
sleep(1)
# Check for deletions in the diff.
deletion_pattern = re.compile(r'(?<=^-).*', re.MULTILINE)
deletions = deletion_pattern.findall(diff)
closed_issues = []
# Filter the deletions down to removed TODOs.
for deletion in deletions:
todo_pattern = re.compile(r'(?<=' + label + r'\s).*')
todos = todo_pattern.search(deletion)
if todos:
closed_issues.append(todos.group(0))
if len(closed_issues) > 0:
# Get the list of current issues.
list_issues_request = requests.get(issues_url, headers=issue_headers, params=params)
if list_issues_request.status_code == 200:
current_issues = list_issues_request.json()
for closed_issue in closed_issues:
title = closed_issue
if len(title) > 50:
title = closed_issue[:50] + '...'
# Compare the title of each closed issue with each issue in the issues list.
for current_issue in current_issues:
if current_issue['title'] == title:
# The titles match, so we will try and close the issue.
issue_number = current_issue['number']
update_issue_url = f'{base_url}{repo}/issues/{issue_number}'
body = {'state': 'closed'}
requests.patch(update_issue_url, headers=issue_headers, params=params,
data=json.dumps(body))
# Don't update too many issues too quickly.
sleep(1)
if __name__ == "__main__":
main()