Skip to content

Commit

Permalink
Reference solution of modular
Browse files Browse the repository at this point in the history
  • Loading branch information
MarekSuchanek committed Nov 6, 2019
1 parent e905ab5 commit 0182d61
Show file tree
Hide file tree
Showing 13 changed files with 784 additions and 0 deletions.
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include LICENSE
include config/auth.example.cfg
include config/rules.example.cfg
78 changes: 78 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
ghia
====

GitHub Issue Assigner

Installation and usage
----------------------

::

$ python setup.py install
$ ghia --help

Or for the web app (GitHub webhook service):

::

$ python setup.py install
$ export FLASK_APP=ghia
$ flask run

Then visit the web application on displayed address.

Strategies
~~~~~~~~~~

- ``append`` = add additional matching assignees
- ``set`` = set matching assignees only if issue is not assigned yet
- ``change`` = keep only matching assignees (remove existing
non-matching assignees)

Configuration
~~~~~~~~~~~~~

Authentication
^^^^^^^^^^^^^^

You need a GitHub `personal access token`_ to run this application. It
has to be specified in the configuration file as follows:

.. code:: ini
[github]
token=<YOUR_PERSONAL_ACCESS_TOKEN>
Rules
^^^^^

Rules configuration consists of two parts:

- **Patterns** which define what username will be matched against what
regex pattern. Each pattern starts with information with what part of
issue it will be matched (``title``, ``text``, ``label``, or
``any``).
- (optional) **Fallback** part describes just a label to be set for
issue that has no assignee after running the assigner.

.. code:: ini
[patterns]
MarekSuchanek=
title:network
text:protocol
text:http[s]{0,1}://localhost:[0-9]{2,5}
label:^(network|networking)$
hroncok=any:Python
[fallback]
label=Need assignment
License
-------

This project is licensed under the MIT License - see the `LICENSE`_ file
for more details.

.. _personal access token: https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line
.. _LICENSE: LICENSE
2 changes: 2 additions & 0 deletions config/auth.example.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[github]
token=TOKEN
10 changes: 10 additions & 0 deletions config/rules.example.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[patterns]
MarekSuchanek=
title:network
text:protocol
text:http[s]{0,1}://localhost:[0-9]{2,5}
label:^(network|networking)$
hroncok=any:Python

[fallback]
label=Need assignment
4 changes: 4 additions & 0 deletions ghia/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from ghia.cli import cli
from ghia.web import create_app

__all__ = ['cli', 'create_app']
3 changes: 3 additions & 0 deletions ghia/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ghia.cli import cli

cli(prog_name='ghia')
100 changes: 100 additions & 0 deletions ghia/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import click
import configparser

from ghia.logic import GHIA, parse_rules


class PrinterObserver:

@staticmethod
def issue(owner, repo, issue):
number, url = issue['number'], issue['html_url']
identifier = click.style(f'{owner}/{repo}#{number}', bold=True)
click.echo(f'-> {identifier} ({url})')

@staticmethod
def assignees(old, new):
mi = click.style('-', fg='red', bold=True)
pl = click.style('+', fg='green', bold=True)
eq = click.style('=', fg='blue', bold=True)
assignees = list(set(old).union(set(new)))
assignees.sort(key=lambda a: a.lower())
for assignee in assignees:
sign = eq
if assignee not in old:
sign = pl
elif assignee not in new:
sign = mi
click.echo(f' {sign} {assignee}')

@staticmethod
def fallbacked(label, added=True):
prefix = click.style('FALLBACK', fg='yellow', bold=True)
click.echo(' ', nl=False)
message = 'added label' if added else 'already has label'
click.echo(f'{prefix}: {message} "{label}"')

@staticmethod
def error(message, of_issue=False):
prefix = click.style('ERROR', bold=True, fg='red')
if of_issue:
click.echo(' ', nl=False, err=True)
click.echo(f'{prefix}: {message}', err=True)



def get_rules(ctx, param, config_rules):
"""
Extract labels from labels config and do the checks
config_rules: ConfigParser with loaded configuration of labels
"""
try:
cfg_rules = configparser.ConfigParser()
cfg_rules.optionxform = str
cfg_rules.read_file(config_rules)
return parse_rules(cfg_rules)
except Exception:
raise click.BadParameter('incorrect configuration format')


def get_token(ctx, param, config_auth):
"""
Extract token from auth config and do the checks
config_auth: ConfigParser with loaded configuration of auth
"""
try:
cfg_auth = configparser.ConfigParser()
cfg_auth.read_file(config_auth)
return cfg_auth.get('github', 'token')
except Exception:
raise click.BadParameter('incorrect configuration format')


def parse_reposlug(ctx, param, reposlug):
try:
owner, repo = reposlug.split('/')
return owner, repo
except ValueError:
raise click.BadParameter('not in owner/repository format')


@click.command(name='ghia')
@click.argument('reposlug', type=click.STRING, callback=parse_reposlug)
@click.option('-s', '--strategy', default=GHIA.DEFAULT_STRATEGY,
show_default=True, type=click.Choice(GHIA.STRATEGIES.keys()),
envvar=GHIA.ENVVAR_STRATEGY,
help='How to handle assignment collisions.')
@click.option('--dry-run', '-d', is_flag=True, envvar=GHIA.ENVVAR_DRYRUN,
help='Run without making any changes.')
@click.option('-a', '--config-auth', type=click.File('r'), callback=get_token,
help='File with authorization configuration.', required=True)
@click.option('-r', '--config-rules', type=click.File('r'), callback=get_rules,
help='File with assignment rules configuration.', required=True)
def cli(reposlug, strategy, dry_run, config_auth, config_rules):
"""CLI tool for automatic issue assigning of GitHub issues"""
token = config_auth
rules, fallback_label = config_rules
owner, repo = reposlug
ghia = GHIA(token, rules, fallback_label, dry_run, strategy)
ghia.add_observer('printer', PrinterObserver)
ghia.run(owner, repo)
Loading

0 comments on commit 0182d61

Please sign in to comment.