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 os
|
||||||
import tempfile
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from rspec_tools.checklinks import check_html_links
|
from rspec_tools.checklinks import check_html_links
|
||||||
from rspec_tools.errors import InvalidArgumenError, RuleNotFoundError, RuleValidationError
|
from rspec_tools.errors import RuleNotFoundError, RuleValidationError
|
||||||
from rspec_tools.create_rule import RuleCreator, build_github_repository_url
|
from rspec_tools.create_rule import create_new_rule
|
||||||
from rspec_tools.rules import RulesRepository
|
from rspec_tools.rules import RulesRepository
|
||||||
from rspec_tools.validation.metadata import validate_metadata
|
from rspec_tools.validation.metadata import validate_metadata
|
||||||
from rspec_tools.validation.description import validate_section_names
|
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]):
|
def create_rule(languages: str, user: Optional[str]):
|
||||||
'''Create a new rule.'''
|
'''Create a new rule.'''
|
||||||
token = os.environ.get('GITHUB_TOKEN')
|
token = os.environ.get('GITHUB_TOKEN')
|
||||||
url = build_github_repository_url(token, user)
|
create_new_rule(languages, 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)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
from rspec_tools.errors import GitError
|
from rspec_tools.errors import GitError
|
||||||
import click
|
import click
|
||||||
|
import tempfile
|
||||||
from git import Repo
|
from git import Repo
|
||||||
from git.remote import PushInfo
|
from git.remote import PushInfo
|
||||||
from github import Github
|
from github import Github
|
||||||
from github.PullRequest import PullRequest
|
from github.PullRequest import PullRequest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final, Iterable, Optional
|
from typing import Final, Iterable, Optional, Callable
|
||||||
from contextlib import contextmanager
|
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
|
from rspec_tools.utils import copy_directory_content
|
||||||
|
|
||||||
@ -21,6 +23,28 @@ def extract_repository_name(url):
|
|||||||
url_end = url.split('/')[-2:]
|
url_end = url.split('/')[-2:]
|
||||||
return '/'.join(url_end).removesuffix('.git')
|
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:
|
class RuleCreator:
|
||||||
''' Create a new Rule in a repository following the official Github 'rspec' repository structure.'''
|
''' Create a new Rule in a repository following the official Github 'rspec' repository structure.'''
|
||||||
MASTER_BRANCH: Final[str] = 'master'
|
MASTER_BRANCH: Final[str] = 'master'
|
||||||
@ -111,15 +135,12 @@ class RuleCreator:
|
|||||||
|
|
||||||
self._fill_in_the_blanks_in_the_template(rule_dir, rule_number)
|
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)
|
branch_name = self.create_new_rule_branch(rule_number, languages)
|
||||||
click.echo(f'Created rule Branch {branch_name}')
|
click.echo(f'Created rule Branch {branch_name}')
|
||||||
|
|
||||||
repository_url = extract_repository_name(self.origin_url)
|
repository_url = extract_repository_name(self.origin_url)
|
||||||
if user:
|
github = githubApi(user)
|
||||||
github = Github(user, token)
|
|
||||||
else:
|
|
||||||
github = Github(token)
|
|
||||||
github_repo = github.get_repo(repository_url)
|
github_repo = github.get_repo(repository_url)
|
||||||
first_lang = next(iter(languages))
|
first_lang = next(iter(languages))
|
||||||
pull_request = github_repo.create_pull(
|
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.
|
# 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
|
login = user if user else github.get_user().login
|
||||||
pull_request.add_to_assignees(login)
|
pull_request.add_to_assignees(login)
|
||||||
|
pull_request.add_to_labels(*labels)
|
||||||
click.echo(f'Pull request assigned to {login}')
|
click.echo(f'Pull request assigned to {login}')
|
||||||
|
|
||||||
return pull_request
|
return pull_request
|
||||||
|
@ -4,7 +4,7 @@ class RuleNotFoundError(ClickException):
|
|||||||
def __init__(self, id):
|
def __init__(self, id):
|
||||||
super().__init__(f'No rule has ID {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.'''
|
'''Exception raised when an invalid argument is given to a CLI command.'''
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
@ -1,9 +1,70 @@
|
|||||||
|
from rspec_tools.errors import InvalidArgumentError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
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):
|
def copy_directory_content(src:Path, dest:Path):
|
||||||
for item in src.iterdir():
|
for item in src.iterdir():
|
||||||
if (item.is_dir()):
|
if (item.is_dir()):
|
||||||
shutil.copytree(item, dest)
|
shutil.copytree(item, dest)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(item, dest)
|
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 git import Repo, Head
|
||||||
|
from rspec_tools.errors import InvalidArgumentError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from rspec_tools.create_rule import RuleCreator
|
from rspec_tools.create_rule import RuleCreator, create_new_rule
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def git_config():
|
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)
|
relative_path = lang_item.relative_to(lang_root)
|
||||||
actual_content = rule_dir.joinpath(lang, relative_path).read_text()
|
actual_content = rule_dir.joinpath(lang, relative_path).read_text()
|
||||||
assert actual_content == expected_content
|
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