RULEAPI-640: Add language(s) label(s) to automatically created PR
This commit is contained in:
parent
08c011b06a
commit
b0c064cfb7
@ -1,12 +1,11 @@
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
from rspec_tools.checklinks import check_html_links
|
||||
from rspec_tools.errors import InvalidArgumenError, RuleNotFoundError, RuleValidationError
|
||||
from rspec_tools.create_rule import RuleCreator, build_github_repository_url
|
||||
from rspec_tools.errors import RuleNotFoundError, RuleValidationError
|
||||
from rspec_tools.create_rule import create_new_rule
|
||||
from rspec_tools.rules import RulesRepository
|
||||
from rspec_tools.validation.metadata import validate_metadata
|
||||
from rspec_tools.validation.description import validate_section_names
|
||||
@ -36,20 +35,7 @@ def check_links(d):
|
||||
def create_rule(languages: str, user: Optional[str]):
|
||||
'''Create a new rule.'''
|
||||
token = os.environ.get('GITHUB_TOKEN')
|
||||
url = build_github_repository_url(token, user)
|
||||
config = {}
|
||||
if user:
|
||||
config['user.name'] = user
|
||||
config['user.email'] = f'{user}@users.noreply.github.com'
|
||||
lang_list = [lang.strip() for lang in languages.split(',')]
|
||||
if len(languages.strip()) == 0 or len(lang_list) == 0:
|
||||
raise InvalidArgumenError('Invalid argument for "languages". At least one language should be provided.')
|
||||
# TODO: accept only valid languages
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
rule_creator = RuleCreator(url, tmpdirname, config)
|
||||
rule_number = rule_creator.reserve_rule_number()
|
||||
pull_request = rule_creator.create_new_rule_pull_request(token, rule_number, lang_list, user=user)
|
||||
create_new_rule(languages, token, user)
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -1,12 +1,14 @@
|
||||
from rspec_tools.errors import GitError
|
||||
import click
|
||||
import tempfile
|
||||
from git import Repo
|
||||
from git.remote import PushInfo
|
||||
from github import Github
|
||||
from github.PullRequest import PullRequest
|
||||
from pathlib import Path
|
||||
from typing import Final, Iterable, Optional
|
||||
from typing import Final, Iterable, Optional, Callable
|
||||
from contextlib import contextmanager
|
||||
from rspec_tools.utils import parse_and_validate_language_list, get_labels_for_languages
|
||||
|
||||
from rspec_tools.utils import copy_directory_content
|
||||
|
||||
@ -21,6 +23,28 @@ def extract_repository_name(url):
|
||||
url_end = url.split('/')[-2:]
|
||||
return '/'.join(url_end).removesuffix('.git')
|
||||
|
||||
def authGithub(token: str) -> Callable[[Optional[str]], Github]:
|
||||
def ret(user: Optional[str]):
|
||||
if user:
|
||||
return Github(user, token)
|
||||
else:
|
||||
return Github(token)
|
||||
return ret
|
||||
|
||||
def create_new_rule(languages: str, token: str, user: Optional[str]):
|
||||
url = build_github_repository_url(token, user)
|
||||
config = {}
|
||||
if user:
|
||||
config['user.name'] = user
|
||||
config['user.email'] = f'{user}@users.noreply.github.com'
|
||||
lang_list = parse_and_validate_language_list(languages)
|
||||
label_list = get_labels_for_languages(lang_list)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
rule_creator = RuleCreator(url, tmpdirname, config)
|
||||
rule_number = rule_creator.reserve_rule_number()
|
||||
pull_request = rule_creator.create_new_rule_pull_request(authGithub(token), rule_number, lang_list, label_list, user=user)
|
||||
|
||||
class RuleCreator:
|
||||
''' Create a new Rule in a repository following the official Github 'rspec' repository structure.'''
|
||||
MASTER_BRANCH: Final[str] = 'master'
|
||||
@ -111,15 +135,12 @@ class RuleCreator:
|
||||
|
||||
self._fill_in_the_blanks_in_the_template(rule_dir, rule_number)
|
||||
|
||||
def create_new_rule_pull_request(self, token: str, rule_number: int, languages: Iterable[str], *, user: Optional[str]) -> PullRequest:
|
||||
def create_new_rule_pull_request(self, githubApi: Callable[[Optional[str]], Github], rule_number: int, languages: Iterable[str], labels: Iterable[str], *, user: Optional[str]) -> PullRequest:
|
||||
branch_name = self.create_new_rule_branch(rule_number, languages)
|
||||
click.echo(f'Created rule Branch {branch_name}')
|
||||
|
||||
repository_url = extract_repository_name(self.origin_url)
|
||||
if user:
|
||||
github = Github(user, token)
|
||||
else:
|
||||
github = Github(token)
|
||||
github = githubApi(user)
|
||||
github_repo = github.get_repo(repository_url)
|
||||
first_lang = next(iter(languages))
|
||||
pull_request = github_repo.create_pull(
|
||||
@ -133,6 +154,7 @@ class RuleCreator:
|
||||
# Note: It is not possible to get the authenticated user using get_user() from a github action.
|
||||
login = user if user else github.get_user().login
|
||||
pull_request.add_to_assignees(login)
|
||||
pull_request.add_to_labels(*labels)
|
||||
click.echo(f'Pull request assigned to {login}')
|
||||
|
||||
return pull_request
|
||||
|
@ -4,7 +4,7 @@ class RuleNotFoundError(ClickException):
|
||||
def __init__(self, id):
|
||||
super().__init__(f'No rule has ID {id}')
|
||||
|
||||
class InvalidArgumenError(ClickException):
|
||||
class InvalidArgumentError(ClickException):
|
||||
'''Exception raised when an invalid argument is given to a CLI command.'''
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
@ -17,4 +17,4 @@ class GitError(ClickException):
|
||||
class RuleValidationError(ClickException):
|
||||
'''Exception raised when a rule did not pass validation.'''
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
super().__init__(message)
|
||||
|
@ -1,9 +1,70 @@
|
||||
from rspec_tools.errors import InvalidArgumentError
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
SUPPORTED_LANGUAGES_FILENAME = '../supported_languages.adoc'
|
||||
LANG_TO_LABEL = {'abap': 'abap',
|
||||
'apex': 'slang',
|
||||
'cfamily': 'cfamily',
|
||||
'cobol': 'cobol',
|
||||
'csharp': 'dotnet',
|
||||
'css': 'css',
|
||||
'flex': 'flex',
|
||||
'go': 'slang',
|
||||
'html': 'html',
|
||||
'java': 'java',
|
||||
'javascript': 'jsts',
|
||||
'kotlin': 'kotlin',
|
||||
'php': 'php',
|
||||
'pli': 'pli',
|
||||
'plsql': 'plsql',
|
||||
'python': 'python',
|
||||
'rpg': 'rpg',
|
||||
'ruby': 'slang',
|
||||
'rust': 'rust',
|
||||
'scala': 'slang',
|
||||
'solidity': 'solidity',
|
||||
'swift': 'swift',
|
||||
'tsql': 'tsql',
|
||||
'vb6': 'vb6',
|
||||
'vbnet': 'dotnet',
|
||||
'cloudformation': 'iac',
|
||||
'terraform': 'iac',
|
||||
'xml': 'xml',
|
||||
}
|
||||
|
||||
def copy_directory_content(src:Path, dest:Path):
|
||||
for item in src.iterdir():
|
||||
if (item.is_dir()):
|
||||
shutil.copytree(item, dest)
|
||||
else:
|
||||
shutil.copy2(item, dest)
|
||||
|
||||
def load_valid_languages():
|
||||
with open(SUPPORTED_LANGUAGES_FILENAME, 'r') as supported_langs_file:
|
||||
supported_langs = supported_langs_file.read()
|
||||
supported_langs = supported_langs.replace(' or', '')
|
||||
supported_langs = supported_langs.replace('`', '')
|
||||
supported_langs = supported_langs.replace(' ', '')
|
||||
supported_langs = supported_langs.replace('\n', '')
|
||||
return supported_langs.split(',')
|
||||
|
||||
def get_mapped_languages():
|
||||
'''Get all the languages we have a label for.
|
||||
Necessary to make sure all valid languages are mapped (see test_utils.py).'''
|
||||
return LANG_TO_LABEL.keys();
|
||||
|
||||
def parse_and_validate_language_list(languages):
|
||||
lang_list = [lang.strip() for lang in languages.split(',')]
|
||||
if len(languages.strip()) == 0 or len(lang_list) == 0:
|
||||
raise InvalidArgumentError('Invalid argument for "languages". At least one language should be provided.')
|
||||
valid_langs = load_valid_languages()
|
||||
for lang in lang_list:
|
||||
if lang not in valid_langs:
|
||||
raise InvalidArgumentError(f"Unsupported language: \"{lang}\". See {SUPPORTED_LANGUAGES_FILENAME} for the list of supported languages.")
|
||||
return lang_list
|
||||
|
||||
def get_labels_for_languages(lang_list):
|
||||
labels = [LANG_TO_LABEL[lang] for lang in lang_list]
|
||||
return list(set(labels))
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
from git import Repo, Head
|
||||
from rspec_tools.errors import InvalidArgumentError
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
from rspec_tools.create_rule import RuleCreator
|
||||
from rspec_tools.create_rule import RuleCreator, create_new_rule
|
||||
|
||||
@pytest.fixture
|
||||
def git_config():
|
||||
@ -134,3 +137,41 @@ def test_create_new_single_lang_rule_branch(rule_creator: RuleCreator, mock_rspe
|
||||
relative_path = lang_item.relative_to(lang_root)
|
||||
actual_content = rule_dir.joinpath(lang, relative_path).read_text()
|
||||
assert actual_content == expected_content
|
||||
|
||||
def test_create_new_rule_pr(rule_creator: RuleCreator):
|
||||
'''Test create_new_rule_branch adds the right user and labels.'''
|
||||
rule_number = rule_creator.reserve_rule_number()
|
||||
languages = ['cfamily']
|
||||
|
||||
ghMock = Mock()
|
||||
ghRepoMock = Mock()
|
||||
pullMock = Mock()
|
||||
ghRepoMock.create_pull = Mock(return_value=pullMock)
|
||||
ghMock.get_repo = Mock(return_value=ghRepoMock)
|
||||
def mockGithub(user: Optional[str]):
|
||||
return ghMock
|
||||
|
||||
rule_creator.create_new_rule_pull_request(mockGithub, rule_number, languages, ['mylab', 'other-lab'], user='testuser')
|
||||
|
||||
ghRepoMock.create_pull.assert_called_once();
|
||||
assert ghRepoMock.create_pull.call_args.kwargs['title'].startswith('Create rule S')
|
||||
pullMock.add_to_assignees.assert_called_with('testuser');
|
||||
pullMock.add_to_labels.assert_called_with('mylab', 'other-lab');
|
||||
|
||||
@patch('rspec_tools.create_rule.RuleCreator')
|
||||
def test_create_new_rule(mockRuleCreator):
|
||||
mockRuleCreator.return_value = Mock()
|
||||
mockRuleCreator.return_value.create_new_rule_pull_request = Mock()
|
||||
prMock = mockRuleCreator.return_value.create_new_rule_pull_request
|
||||
create_new_rule('cfamily,php', 'my token', 'testuser')
|
||||
prMock.assert_called_once()
|
||||
assert set(prMock.call_args.args[2]) == set(['cfamily', 'php'])
|
||||
assert set(prMock.call_args.args[3]) == set(['cfamily', 'php'])
|
||||
|
||||
@patch('rspec_tools.create_rule.RuleCreator')
|
||||
def test_create_new_rule_unsupported_language(mockRuleCreator):
|
||||
mockRuleCreator.return_value = Mock()
|
||||
mockRuleCreator.return_value.create_new_rule_pull_request = Mock()
|
||||
prMock = mockRuleCreator.return_value.create_new_rule_pull_request
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
create_new_rule('russian,php', 'my token', 'testuser')
|
||||
|
Loading…
x
Reference in New Issue
Block a user