2022-02-23 13:39:58 +01:00
import json
from contextlib import contextmanager
from pathlib import Path
from typing import Callable , Final , Iterable , Optional
import click
2021-02-16 21:21:46 +01:00
from github . PullRequest import PullRequest
2022-02-23 13:39:58 +01:00
from rspec_tools . errors import InvalidArgumentError
2022-02-24 17:09:07 +01:00
from rspec_tools . repo import RspecRepo , tmp_rspec_repo
2022-02-23 13:39:58 +01:00
from rspec_tools . utils import ( LANG_TO_SOURCE , copy_directory_content ,
get_label_for_language ,
get_labels_for_languages , is_empty_metadata ,
parse_and_validate_language_list , resolve_rule ,
2022-02-24 17:09:07 +01:00
swap_metadata_files )
2022-02-23 13:39:58 +01:00
2021-02-16 21:21:46 +01:00
2022-02-23 13:39:58 +01:00
@contextmanager
def _rule_creator ( token : str , user : Optional [ str ] ) :
2022-02-24 17:09:07 +01:00
with tmp_rspec_repo ( token , user ) as repo :
yield RuleCreator ( repo )
2022-02-23 13:39:58 +01:00
def create_new_rule ( languages : str , token : str , user : Optional [ str ] ) :
lang_list = parse_and_validate_language_list ( languages )
label_list = get_labels_for_languages ( lang_list )
with _rule_creator ( token , user ) as rule_creator :
2022-02-24 17:09:07 +01:00
rule_number = rule_creator . rspec_repo . reserve_rule_number ( )
rule_creator . create_new_rule_pull_request ( token , rule_number , lang_list , label_list , user )
2021-06-10 11:03:05 +02:00
2021-09-30 17:47:25 +02:00
def add_language_to_rule ( language : str , rule : str , token : str , user : Optional [ str ] ) :
2022-02-24 17:09:07 +01:00
label = get_label_for_language ( language )
2021-09-30 17:47:25 +02:00
rule_number = resolve_rule ( rule )
2022-02-23 13:39:58 +01:00
with _rule_creator ( token , user ) as rule_creator :
2022-02-24 17:09:07 +01:00
rule_creator . add_language_pull_request ( token , rule_number , language , label , user )
2021-09-30 17:47:25 +02:00
2022-02-23 13:39:58 +01:00
2021-02-16 21:21:46 +01:00
class RuleCreator :
2022-02-24 17:09:07 +01:00
''' Create a new Rule in a repository following the official Github ' rspec ' repository structure. '''
2021-02-16 21:21:46 +01:00
TEMPLATE_PATH : Final [ Path ] = Path ( __file__ ) . parent . parent . joinpath ( ' rspec_template ' )
2023-06-06 17:04:39 +02:00
PR_TEMPLATE_PATH : Final [ Path ] = Path ( __file__ ) . parent . parent . parent . joinpath ( ' .github/pull_request_template.md ' )
2021-02-16 21:21:46 +01:00
2022-02-24 17:09:07 +01:00
def __init__ ( self , rspec_repo : RspecRepo ) :
self . rspec_repo = rspec_repo
self . repo_dir = Path ( self . rspec_repo . repository . working_dir )
2021-02-16 21:21:46 +01:00
2021-09-30 17:47:25 +02:00
def add_language_branch ( self , rule_number : int , language : str ) - > str :
''' Create and move files to add a new language to an existing rule. '''
branch_name = f ' rule/S { rule_number } -add- { language } '
2022-02-24 17:09:07 +01:00
with self . rspec_repo . checkout_branch ( self . rspec_repo . MASTER_BRANCH , branch_name ) :
rule_dir = self . repo_dir / ' rules ' / f ' S { rule_number } '
2021-09-30 17:47:25 +02:00
if not rule_dir . is_dir ( ) :
raise InvalidArgumentError ( f " Rule \" S { rule_number } \" does not exist. " )
2022-02-24 17:09:07 +01:00
lang_dir = rule_dir / language
2021-09-30 17:47:25 +02:00
if lang_dir . is_dir ( ) :
lang_url = f " https://github.com/SonarSource/rspec/tree/master/rules/S { rule_number } / { language } "
2021-10-01 10:25:35 +02:00
raise InvalidArgumentError ( f " Rule \" S { rule_number } \" is already defined for language { language } . Modify the definition here: { lang_url } " )
lang_dirs = [ d for d in rule_dir . glob ( ' */ ' ) if d . is_dir ( ) ]
if 1 == len ( list ( lang_dirs ) ) and is_empty_metadata ( rule_dir ) :
swap_metadata_files ( rule_dir , lang_dirs [ 0 ] )
2021-09-30 17:47:25 +02:00
lang_dir . mkdir ( )
2022-02-24 17:09:07 +01:00
lang_specific_template = self . TEMPLATE_PATH / ' multi_language ' / ' language_specific '
2021-09-30 17:47:25 +02:00
copy_directory_content ( lang_specific_template , lang_dir )
self . _fill_in_the_blanks_in_the_template ( lang_dir , rule_number )
2022-02-08 17:34:53 +01:00
self . _fill_language_name_in_the_template ( lang_dir , language )
2022-02-24 17:09:07 +01:00
self . rspec_repo . commit_all_and_push ( f ' Add { language } to rule S { rule_number } ' )
2021-09-30 17:47:25 +02:00
return branch_name
2021-02-16 21:21:46 +01:00
def create_new_rule_branch ( self , rule_number : int , languages : Iterable [ str ] ) - > str :
''' Create all the files required for a new rule. '''
2021-06-01 17:41:02 +02:00
branch_name = f ' rule/add-RSPEC-S { rule_number } '
2022-02-24 17:09:07 +01:00
with self . rspec_repo . checkout_branch ( self . rspec_repo . MASTER_BRANCH , branch_name ) :
rule_dir = self . repo_dir / ' rules ' / f ' S { rule_number } '
2021-02-16 21:21:46 +01:00
rule_dir . mkdir ( )
2022-01-13 09:25:17 +01:00
lang_count = sum ( 1 for _ in languages )
2021-05-03 09:24:46 +02:00
if lang_count > 1 :
self . _fill_multi_lang_template_files ( rule_dir , rule_number , languages )
else :
self . _fill_single_lang_template_files ( rule_dir , rule_number , next ( iter ( languages ) ) )
2022-02-24 17:09:07 +01:00
self . rspec_repo . commit_all_and_push ( f ' Create rule S { rule_number } ' )
2021-05-03 09:24:46 +02:00
2021-02-16 21:21:46 +01:00
return branch_name
2021-05-03 09:24:46 +02:00
def _fill_in_the_blanks_in_the_template ( self , rule_dir : Path , rule_number : int ) :
for rule_item in rule_dir . glob ( ' **/* ' ) :
if rule_item . is_file ( ) :
template_content = rule_item . read_text ( )
final_content = template_content . replace ( ' $ {RSPEC_ID} ' , str ( rule_number ) )
rule_item . write_text ( final_content )
2022-02-08 17:34:53 +01:00
def _fill_language_name_in_the_template ( self , lang_dir : Path , language : str ) :
for rule_item in lang_dir . glob ( ' *.adoc ' ) :
if rule_item . is_file ( ) :
template_content = rule_item . read_text ( )
lang = LANG_TO_SOURCE [ language ]
final_content = template_content . replace ( ' [source,text] ' , f ' [source, { lang } ] ' )
rule_item . write_text ( final_content )
2021-05-03 09:24:46 +02:00
def _fill_multi_lang_template_files ( self , rule_dir : Path , rule_number : int , languages : Iterable [ str ] ) :
2022-02-24 17:09:07 +01:00
common_template = self . TEMPLATE_PATH / ' multi_language ' / ' common '
lang_specific_template = self . TEMPLATE_PATH / ' multi_language ' / ' language_specific '
2021-05-03 09:24:46 +02:00
copy_directory_content ( common_template , rule_dir )
for lang in languages :
2022-02-24 17:09:07 +01:00
lang_dir = rule_dir / lang
2021-05-03 09:24:46 +02:00
lang_dir . mkdir ( )
copy_directory_content ( lang_specific_template , lang_dir )
2022-02-08 17:34:53 +01:00
self . _fill_language_name_in_the_template ( lang_dir , lang )
2021-05-03 09:24:46 +02:00
self . _fill_in_the_blanks_in_the_template ( rule_dir , rule_number )
def _fill_single_lang_template_files ( self , rule_dir : Path , rule_number : int , language : str ) :
2022-02-24 17:09:07 +01:00
common_template = self . TEMPLATE_PATH / ' single_language ' / ' common '
lang_specific_template = self . TEMPLATE_PATH / ' single_language ' / ' language_specific '
2021-05-03 09:24:46 +02:00
copy_directory_content ( common_template , rule_dir )
2022-02-24 17:09:07 +01:00
lang_dir = rule_dir / language
2021-05-03 09:24:46 +02:00
lang_dir . mkdir ( )
copy_directory_content ( lang_specific_template , lang_dir )
self . _fill_in_the_blanks_in_the_template ( rule_dir , rule_number )
2022-02-08 17:34:53 +01:00
self . _fill_language_name_in_the_template ( lang_dir , language )
2021-05-03 09:24:46 +02:00
2022-02-24 17:09:07 +01:00
def add_language_pull_request ( self , token : str , rule_number : int , language : str , label : str , user : Optional [ str ] ) :
2021-09-30 17:47:25 +02:00
branch_name = self . add_language_branch ( rule_number , language )
click . echo ( f ' Created rule branch { branch_name } ' )
2022-02-24 17:09:07 +01:00
return self . rspec_repo . create_pull_request (
token ,
2021-09-30 17:47:25 +02:00
branch_name ,
2022-01-13 17:03:40 +01:00
f ' Create rule S { rule_number } ' ,
2023-06-06 17:04:39 +02:00
f ' You can preview this rule [here](https://sonarsource.github.io/rspec/#/rspec/S { rule_number } / { language } ) (updated a few minutes after each push). \n \n { self . PR_TEMPLATE_PATH . read_text ( ) } ' ,
2021-09-30 17:47:25 +02:00
[ label ] ,
user
)
2022-02-24 17:09:07 +01:00
def create_new_rule_pull_request ( self , token : str , rule_number : int , languages : Iterable [ str ] , labels : Iterable [ str ] , user : Optional [ str ] ) - > PullRequest :
2021-09-30 17:47:25 +02:00
branch_name = self . create_new_rule_branch ( rule_number , languages )
click . echo ( f ' Created rule branch { branch_name } ' )
first_lang = next ( iter ( languages ) )
2022-02-24 17:09:07 +01:00
return self . rspec_repo . create_pull_request (
token ,
2021-09-30 17:47:25 +02:00
branch_name ,
f ' Create rule S { rule_number } ' ,
2023-06-06 17:04:39 +02:00
f ' You can preview this rule [here](https://sonarsource.github.io/rspec/#/rspec/S { rule_number } / { first_lang } ) (updated a few minutes after each push). \n \n { self . PR_TEMPLATE_PATH . read_text ( ) } ' ,
2021-09-30 17:47:25 +02:00
labels ,
user
)