Beispiel #1
0
def check_for_update(verbose=False):
    try:
        tags = get_github_tags()
    except Exception as e:
        print(
            "An error occured whilst checking for updates. Check your network connection."
        )
        return
    latest_tag_version = tags[0][1:]
    cur_version = find_version()

    if version.parse(cur_version) < version.parse(latest_tag_version):
        Ansi.print_ok("There is an update available for conventional-commit.")
        upgrade_command = "pip install --upgrade conventional-commit"
        pyperclip.copy(upgrade_command)

        text = FormattedText([
            ("", "Version "),
            ("class:red", cur_version),
            ("", " → "),
            ("class:green", latest_tag_version),
            ("", "\nAdded to your clipboard: "),
            ("class:command", upgrade_command),
        ])
        print_formatted_text(text, style=style)
    elif verbose:
        print("Current version:", cur_version)
Beispiel #2
0
def add_scope(commit_msg):
    Ansi.print_info(
        wrap_width(
            "\nWhat is the scope / a noun describing section of repo? (try to keep under 15 characters)"
        ))
    text = Ansi.colour(Ansi.fg.bright_green, "Scope (optional): ")
    history_file_path = os.path.join(CONFIG_HOME_DIR, "scope_history")
    c_scope = prompt(ANSI(text),
                     history=FileHistory(history_file_path),
                     key_bindings=bindings).strip()

    if c_scope != "":
        commit_msg += "({})".format(c_scope)

    return commit_msg
Beispiel #3
0
def add_footer(commit_msg):
    Ansi.print_info(
        wrap_width([
            "\nThe footer MUST contain meta-information about the commit:",
            " - Related pull-requests, reviewers, breaking changes",
            " - GitHub close/fix/resolve #issue or username/repository#issue",
            " - One piece of meta-information per-line",
            " - To submit, press the Esc key before Enter",
        ]))

    text = Ansi.colour(Ansi.fg.bright_green, "Footer (optional) ┃ ")
    history_file_path = os.path.join(CONFIG_HOME_DIR, "footer_history")
    session = PromptSession(
        completer=FooterCompleter(),
        multiline=False,
        prompt_continuation=custom_prompt_continuation,
        history=FileHistory(history_file_path),
        key_bindings=bindings,
    )
    c_footer = session.prompt(ANSI(text),
                              validator=FooterValidator(session)).strip()

    if c_footer != "":
        f_lines = c_footer.split("\n")
        f_lines = [l for l in f_lines
                   if l.strip() != ""]  # remove any lines that are empty: ""

        for i, line in enumerate(f_lines):
            line = line.strip()  # clean up extraneous whitespace

            # format each line with forced line breaks to maintain maximum line length
            f_lines[i] = "\n".join(
                textwrap.wrap(
                    line,
                    width=72,
                    break_long_words=False,
                    break_on_hyphens=False,
                    subsequent_indent="  ",
                ))

        # recombine all user defined footer lines
        formatted_footer = "\n".join(f_lines)
        commit_msg += "\n\n" + formatted_footer

    return commit_msg
Beispiel #4
0
def add_description(commit_msg):
    if IS_BREAKING_CHANGE is None:
        raise ValueError(
            "Global variable `IS_BREAKING_CHANGE` has not been set.")

    if IS_BREAKING_CHANGE:
        commit_msg += "!: "
    else:
        commit_msg += ": "

    num_chars_remaining = 50 - len(commit_msg)
    Ansi.print_info(
        wrap_width(
            "\nWhat is the commit description / title. A short summary of the code changes. Use the imperative mood. No more than {} characters."
            .format(num_chars_remaining)))

    c_descr = ""

    history_file_path = os.path.join(CONFIG_HOME_DIR, "description_history")
    session = PromptSession(history=FileHistory(history_file_path),
                            key_bindings=bindings)

    length_prompt = LineLengthPrompt(num_chars_remaining, session)
    while c_descr == "":
        text = Ansi.b_green("Description: ")
        c_descr = session.prompt(
            ANSI(text),
            validator=DescriptionValidator(num_chars_remaining),
            rprompt=length_prompt.get_text,
        )

    # Sanitise
    c_descr = c_descr.strip()  # remove whitespace
    c_descr = capitaliseFirst(c_descr)  # capital first letter
    if c_descr[-1] == ".":
        c_descr = c_descr[:-1]  # remove period if last character
        c_descr = c_descr.strip()  # remove further whitespace

    commit_msg += c_descr
    return commit_msg
Beispiel #5
0
def check_if_breaking_change():
    global IS_BREAKING_CHANGE  # required to be able to write to variable
    contains_break = ""
    print()  # breakline from previous section
    while True:
        text = Ansi.b_yellow("Does commit contain breaking change? (no) ")
        contains_break = (prompt(
            ANSI(text),
            validator=YesNoValidator(answer_required=False),
            key_bindings=bindings,
        ).lower().strip())
        if contains_break == "":  # default
            IS_BREAKING_CHANGE = False
            break
        elif contains_break in ["y", "yes"]:
            IS_BREAKING_CHANGE = True
            return True
        else:
            IS_BREAKING_CHANGE = False
            return False
Beispiel #6
0
def run(args):
    # print(sys.version + "/n")

    if len(args) > 0 and args[0] in ["version", "update"]:
        check_for_update(verbose=True)
        return  # Exit early

    # Ensure the config directory exists
    os.makedirs(CONFIG_HOME_DIR, exist_ok=True)

    commits_file_path = os.path.join(CONFIG_HOME_DIR, "commit_msg_history")
    commit_msg_history = FileHistory(commits_file_path)

    if WINDOW_WIDTH < 80:
        Ansi.print_error(
            f"It is recommended you increase your window width ({WINDOW_WIDTH}) to at least 80."
        )

    commit_msg = ""
    if len(args) > 0 and args[0] == "retry":
        args = args[1:]  # consume the "retry" first arg
        cm_histories_iter = commit_msg_history.load_history_strings()
        last_commit_msg = next(cm_histories_iter, "")
        if last_commit_msg == "":
            Ansi.print_error("Could not find a previous commit message")
        commit_msg = last_commit_msg

    if commit_msg == "":
        print("Starting a conventional git commit...")
        print(
            Ansi.colour(Ansi.fg.bright_red,
                        "Tip: Press the up arrow key to recall history!"))
        commit_msg = add_type(commit_msg)
        commit_msg = add_scope(commit_msg)
        check_if_breaking_change()
        commit_msg = add_description(commit_msg)
        commit_msg = add_body(commit_msg)
        commit_msg = add_footer(commit_msg)
        print()

    Ansi.print_ok("This is your commit message:")
    print()
    print(commit_msg)
    print()

    # print("\nNOTE: This was a dry run and no commit was made.\n")

    # It is expected that all remaining args at this point are for the git
    # commit command
    args_string = " ".join(args)
    text = Ansi.colour(Ansi.fg.bright_yellow, "Extra args for git commit: ")
    extra_args_file_path = os.path.join(CONFIG_HOME_DIR, "extra_args_history")
    extra_args_str = prompt(
        ANSI(text),
        default=args_string,
        history=FileHistory(extra_args_file_path),
        key_bindings=bindings,
    )
    if extra_args_str != "":
        argv_passthrough = extra_args_str.split(" ")
    else:
        argv_passthrough = []

    # Ask for confirmation to commit
    confirmation_validator = YesNoValidator(answer_required=True)
    text = Ansi.b_yellow("Do you want to make your commit? [y/n] ")
    confirm = prompt(ANSI(text),
                     validator=confirmation_validator,
                     key_bindings=bindings).lower()

    if confirm in confirmation_validator.confirmations:
        commit_msg_history.store_string(commit_msg)
        cmds = ["git", "commit", "-m", commit_msg] + argv_passthrough
        returncode = subprocess.run(cmds).returncode
        print()
        if returncode == 0:
            Ansi.print_ok(
                "\nCommit has been made to conventional commits standards!")
        else:
            Ansi.print_error(
                "\nThere was an error whilst attempting the commit!")

    elif confirm in confirmation_validator.rejections:
        print("Aborting the commit...")

    check_for_update()
Beispiel #7
0
def add_body(commit_msg):
    if IS_BREAKING_CHANGE is None:
        raise ValueError(
            "Global variable `IS_BREAKING_CHANGE` has not been set.")

    history_file_path = os.path.join(CONFIG_HOME_DIR, "body_history")
    session = PromptSession(
        prompt_continuation=custom_prompt_continuation,
        history=FileHistory(history_file_path),
        key_bindings=bindings,
    )
    body_validator = BodyValidator(session, IS_BREAKING_CHANGE)

    if IS_BREAKING_CHANGE:
        Ansi.print_info(
            wrap_width([
                "\nExplain what has changed in this commit to cause breaking changes.",
                "Press Esc before Enter to submit.",
            ]))
        # text = Ansi.b_green("Body (required) ┃ ")
        text = Ansi.colour(Ansi.fg.bright_green, Ansi.bold, "Body ┃ ")
    else:
        Ansi.print_info(
            wrap_width([
                "\nProvide additional contextual information about the changes here.",
                "Press Esc before Enter to submit.",
            ]))
        text = Ansi.colour(Ansi.fg.bright_green, "Body (optional) ┃ ")

    c_body = session.prompt(ANSI(text), validator=body_validator)
    c_body = c_body.strip()  # remove leading/trailing whitespace
    c_body = capitaliseFirst(c_body)  # capital first letter

    if c_body != "":
        if IS_BREAKING_CHANGE:
            c_body = "BREAKING CHANGE: " + c_body

        b_lines = c_body.split("\n")
        num_blank_lines = 0  # track the number of consecutive blank lines

        condensed_b_lines = []
        for line in b_lines:
            l_stripped = line.strip()
            if l_stripped == "":
                num_blank_lines += 1
            else:
                num_blank_lines = 0

            if num_blank_lines > 1:
                continue  # ignore any blank lines after the first
            elif l_stripped == "":
                # skip any processing and add to output if blank
                condensed_b_lines.append(l_stripped)
            else:
                # check if we are dealing with a bulleted line
                bulleted_line = False
                if l_stripped[0] in ["*", "-"]:
                    bulleted_line = True
                    line_after_bullet = l_stripped[1:].strip()
                    l_stripped = " " + l_stripped[0] + " " + line_after_bullet

                # format each line with forced line breaks to maintain maximum line length
                wrapped_line = "\n".join(
                    textwrap.wrap(
                        l_stripped,
                        width=72,
                        break_long_words=False,
                        break_on_hyphens=False,
                        subsequent_indent="   " if bulleted_line else "",
                    ))
                condensed_b_lines.append(wrapped_line)

        # recombine all user defined lines
        full_body = "\n".join(condensed_b_lines)

        # append to commit message
        commit_msg += "\n\n" + full_body

    return commit_msg
Beispiel #8
0
def custom_prompt_continuation(width, line_number, is_soft_wrap):
    text_continuation = " " * (width - 2) + "┃ "
    return ANSI(Ansi.colour(Ansi.fg.bright_green, text_continuation))
Beispiel #9
0
def add_type(commit_msg):
    valid_types = {
        "feat":
        "MUST be used the commit adds/builds toward a new feature",
        "fix":
        "MUST be used when a commit represents a bug fix",
        "chore":
        "Any change to build system, dependencies, config files, scripts (no production code)",
        "docs":
        "Changes to documentation",
        "perf":
        "Changes that improves performance",
        "refactor":
        "Refactoring production code, e.g. renaming a variable/restructuring existing logic",
        "revert":
        "Any commit that explicitly reverts part/all changes introduced in a previous commit",
        "style":
        "Changes to white-space, formatting, missing semi-colons, etc.",
        "test":
        "Changes to tests e.g. adding a new/missing test or fixing/correcting existing tests",
        "wip":
        "Any code changes that are work in progress; they may not build (use these sparingly!)",
    }

    Ansi.print_info(
        wrap_width([
            "Please specify the type of this commit using one of the available keywords. Accepted types: ",
            "TAB to autocomplete...",
        ]))

    type_names = sorted(valid_types.keys())
    # create prefixes e.g. "    0  chore"
    prefixes = ["  " + str(i) + "  " + t for i, t in enumerate(type_names)]
    prefix_length = max([len(p) for p in prefixes]) + 2

    for i in range(len(type_names)):
        # type descriptions
        type_descr_width = WINDOW_WIDTH - prefix_length
        descr_lines = textwrap.wrap(
            valid_types[type_names[i]],
            width=type_descr_width,
            subsequent_indent=" " * prefix_length,
            break_long_words=False,
        )
        # ensure each line has trailing whitespace, then do join
        type_descr_str = "".join(
            map(lambda l: l.ljust(type_descr_width), descr_lines))

        # Combine type name with type description
        type_print = prefixes[i].ljust(prefix_length) + type_descr_str

        # Print the type
        Ansi.print_warning(type_print)

    valid_numeric_types = [str(n) for n in range(len(type_names))]
    valid_inputs = list(valid_types.keys()) + valid_numeric_types

    print()
    text = Ansi.b_green("Type: ")
    history_file_path = os.path.join(CONFIG_HOME_DIR, "type_history")
    c_type = prompt(
        ANSI(text),
        completer=TypeCompleter(),
        validator=TypeValidator(valid_inputs),
        history=FileHistory(history_file_path),
        key_bindings=bindings,
    )

    # Convert from number back to proper type name
    if c_type in valid_numeric_types:
        c_type = type_names[int(c_type)]

    commit_msg += c_type
    return commit_msg