diff --git a/README.md b/README.md index 224955c..a037f10 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,24 @@ with the syntax described earlier. most importantly edit your pull request description. * (Optional) `message_header`: You may provide a custom message to be displayed above the checklist instead of the default one found in [action.yml](action.yml). + +### Using multiple DoD checklists + +After popular demand, the action now supports using multiple/alternative DoD checklists. This is useful when you have different +DoD criteria for different types of pull requests, e.g. bug fixes, new features, etc. + +To use multiple DoD checklists, you should include a Markdown-formatted YAML block **at the end** of your pull request, e.g.: + +````` +This is the pull request description. You can write whatever here and the DoD checklist will be appended at the end. +Right below this line, you should include a YAML block with the following syntax: + +```yaml +dod_yaml: "relative/path/to/alternative-dod.yaml" +``` +````` + +Please note that the Action will _not_ replace the YAML block which specifies the alternative DoD. +Additionally, the YAML block should remain in your pull request description, +either at the end or right before the DoD checklist. +In other words, add the YAML block at the end of the pull request description and let the action do the rest. diff --git a/run_action.py b/run_action.py index b1c8ced..ad51344 100755 --- a/run_action.py +++ b/run_action.py @@ -5,6 +5,9 @@ import requests import argparse import yaml +import re + +from pathlib import Path MESSAGE_HEADER = os.environ["INPUT_MESSAGE_HEADER"] EMPTY_CHECKMARK = "- [ ]" @@ -25,6 +28,70 @@ def has_bot_comment(pull_request_description): return MESSAGE_HEADER in pull_request_description +def maybe_replace_config(config: dict, pull_request_description: str): + """ + Replace the default DoD with an alternative one if specified + in the pull request description with a YAML block at the end. + """ + if has_bot_comment(pull_request_description): + pull_request_description = pull_request_description.split(MESSAGE_HEADER)[0] + pr_description = pull_request_description.rstrip() + if not pr_description.endswith("```"): + return False + + yaml_block_pattern = r"```yaml(.*?)```" + yaml_block = re.search(yaml_block_pattern, pr_description, re.DOTALL) + if yaml_block is None: + return False + + if pr_description.endswith(yaml_block.group(0)): + try: + yaml_block_contents = yaml.safe_load(yaml_block.group(1)) + if "dod_yaml" in yaml_block_contents: + print("Alternative DoD YAML file specified") + alternative_dod_yaml_path = Path(os.environ["GITHUB_WORKSPACE"]) / Path( + yaml_block_contents["dod_yaml"] + ) + if alternative_dod_yaml_path.exists(): + with open(alternative_dod_yaml_path, "r") as stream: + try: + alternative_config = yaml.safe_load(stream) + assert_config(alternative_config) + # Replace the original config with the alternative one + print( + "Replacing the original config with the alternative one" + ) + config.clear() + config.update(alternative_config) + except yaml.YAMLError as yaml_exception: + print("Invalid YAML block") + print(yaml_exception) + return False + else: + print( + "Alternative DoD YAML file does not exist in specified path: " + + str(alternative_dod_yaml_path) + ) + return False + else: + return False + + except yaml.YAMLError as yaml_exception: + print("Invalid YAML block") + print(yaml_exception) + return False + else: + print("YAML block found but not at the end of the pull request description") + return False + + return True + + +def assert_config(config: dict): + assert "dod" in config, "No DoD section in config" + assert isinstance(config["dod"], list), "DoD section is not a list" + + def has_unsatisfied_dod(pull_request_description, dod_criteria): # Extract the bot message from the pull request description bot_message_begin = pull_request_description.find(MESSAGE_HEADER) @@ -61,9 +128,7 @@ def main(): print(yaml_exception) return 1 - if "dod" not in config: - print("No DoD section in config") - return 1 + assert_config(config) repo = os.environ.get("GITHUB_REPOSITORY") pull_request_id = args.pull_request_id @@ -90,6 +155,7 @@ def main(): if not pull_request_description: pull_request_description = "" + maybe_replace_config(config, pull_request_description) if has_bot_comment(pull_request_description): if has_unsatisfied_dod(pull_request_description, config["dod"]): print( @@ -126,7 +192,7 @@ def main(): print( "The Definition of Done for this pull request " - + "needs to fully marked as satisfied by a repository maintainer. " + + "needs to be fully marked as satisfied by a repository maintainer. " + "Please make sure a maintainer marks off the checklist " + "that was appended to the pull request description." )