Example #1
0
    def inspect_sync_dir(self):
        """
        Examines target directory to sync, verifies that it is a git repository and ensures that there are no uncommitted changes.
        """
        if not os.path.exists(os.path.join(str(self.project_dir), '.cookietemple.yml')):
            print(f'[bold red]{self.project_dir} does not appear to contain a .cookietemple.yml file. Did you delete it?')
            sys.exit(1)
            # store .cookietemple.yml content for later reuse in the dry create run
        self.dot_cookietemple = load_yaml_file(os.path.join(str(self.project_dir), '.cookietemple.yml'))
        log.debug(f'Loaded .cookietemple.yml file content. Content is: {self.dot_cookietemple}')
        # Check that the project_dir is a git repo
        try:
            self.repo = git.Repo(self.project_dir)
        except git.exc.InvalidGitRepositoryError:
            print(f'[bold red]{self.project_dir} does not appear to be a git repository.')
            sys.exit(1)

        # get current branch so we can switch back later
        self.original_branch = self.repo.active_branch.name
        print(f'[bold blue]Original Project repository branch is {self.original_branch}')

        # Check to see if there are uncommitted changes on current branch
        if self.repo.is_dirty(untracked_files=True):
            print('[bold red]Uncommitted changes found in Project directory!\nPlease commit these before running cookietemple sync')
            sys.exit(1)
        # Check, whether a cookietemple sync PR is already open
        elif self.check_pull_request_exists():
            print('[bold blue]Open cookietemple sync PR still unmerged! No sync will happen until this PR is merged!')
            sys.exit(0)
Example #2
0
def load_github_username() -> str:
    """
    Load the username from cfg file.

    :return: The users Github account name
    """
    return load_yaml_file(ConfigCommand.CONF_FILE_PATH)["github_username"]
Example #3
0
    def list_available_templates(self) -> None:
        """
        Displays all available templates to stdout in nicely formatted yaml format.
        Omits long descriptions.
        """
        log.debug(
            f"Reading available_templates.yml at {self.TEMPLATES_PATH}/available_templates.yml"
        )
        available_templates = load_yaml_file(
            f"{self.TEMPLATES_PATH}/available_templates.yml")
        print(
            "[bold blue]Run [green]cookietemple info [blue]for long descriptions of your template of interest"
        )
        print()

        # What we want to have are lists like
        # [['name', 'handle', 'short description', 'available libraries', 'version'], ['name', 'handle', 'short description', 'available libraries', 'version']]
        log.debug("Building list table.")
        templates_to_tabulate = []
        for language in available_templates.values():
            for val in language.values():
                # has a subdomain -> traverse dictionary a level deeper
                if is_nested_dictionary(val):
                    for val_2 in val.values():
                        templates_to_tabulate.append([
                            val_2["name"],
                            val_2["handle"],
                            val_2["short description"],
                            val_2["available libraries"],
                            val_2["version"],
                        ])
                else:
                    templates_to_tabulate.append([
                        val["name"],
                        val["handle"],
                        val["short description"],
                        val["available libraries"],
                        val["version"],
                    ])

        table = Table(
            title="[bold]All available cookietemple templates",
            title_style="blue",
            header_style=Style(color="blue", bold=True),
            box=HEAVY_HEAD,
        )

        table.add_column("Name", justify="left", style="green", no_wrap=True)
        table.add_column("Handle", justify="left")
        table.add_column("Short Description", justify="left")
        table.add_column("Available Libraries", justify="left")
        table.add_column("Version", justify="left")

        for template in templates_to_tabulate:
            table.add_row(f"[bold]{template[0]}", template[1],
                          f"{template[2]}\n", template[3], template[4])

        log.debug("Printing list table.")
        console = Console()
        console.print(table)
Example #4
0
    def view_current_config() -> None:
        """
        Print the current users cookietemple configuration.
        """
        # load current settings
        try:
            log.debug(f'Fetching current cookietemple configuration at {ConfigCommand.CONF_FILE_PATH}.')
            settings = load_yaml_file(ConfigCommand.CONF_FILE_PATH)
            log.debug('Creating configuration table')
            # create the table and print
            table = Table(title="[bold]Your current configuration", title_style="blue", header_style=Style(color="blue", bold=True), box=HEAVY_HEAD)
            table.add_column('Name', style='green')
            table.add_column('Value', style='green')
            # add rows to the table consisting of the name and value of the current setting
            for (name, value) in settings.items():
                # don't print token directly, just inform it's set
                if name == 'pat':
                    table.add_row('[bold]Personal access token', 'TOKEN_IS_SET')
                else:
                    table.add_row(f'[bold]{name.capitalize().replace("_", " ")}', f'[white]{value}')
            # don't print PAT directly but inform if not set
            if 'pat' not in settings.keys():
                table.add_row('[bold]Personal access token', '[red]NO_TOKEN_SET')

            console = Console()
            console.print(table)

        except (FileNotFoundError, KeyError):
            print('[bold red]Did not found a cookietemple config file!\n'
                  'If this is your first time running cookietemple you can set them using [green]cookietemple config general')
 def __init__(self, creator_ctx: CookietempleTemplateStruct):
     self.WD = os.path.dirname(__file__)
     self.TEMPLATES_PATH = f"{self.WD}/templates"
     self.COMMON_FILES_PATH = f"{self.TEMPLATES_PATH}/common_files"
     self.AVAILABLE_TEMPLATES_PATH = f"{self.TEMPLATES_PATH}/available_templates.yml"
     self.AVAILABLE_TEMPLATES = load_yaml_file(self.AVAILABLE_TEMPLATES_PATH)
     self.CWD = Path.cwd()
     self.creator_ctx = creator_ctx
def load_available_handles() -> set:
    """
    Load all available template handles.

    :return: A set of all available handles
    """
    available_templates = load_yaml_file(f"{AVAILABLE_TEMPLATES_PATH}")
    unsplit_handles: Set[str] = set()
    all_handles: Set[str] = set()
    nested_dict_to_handle_set(available_templates, unsplit_handles)
    all_handles.update(unsplit_handles)
    split_handles(unsplit_handles, all_handles)

    return all_handles
Example #7
0
    def update_sync_token(project_name: str, gh_username: str = '') -> None:
        """
        Update the sync token secret for the repository.

        :param project_name Name of the users project
        :param gh_username The Github username (only gets passed, if the repo is an orga repo)
        """
        gh_username = load_yaml_file(ConfigCommand.CONF_FILE_PATH)['github_username'] if not gh_username else gh_username
        # get the personal access token for user authentification
        log.debug('Asking for updated sync token value.')
        updated_sync_token = cookietemple_questionary_or_dot_cookietemple(function='password',
                                                                          question='Please enter your updated sync token value')
        print(f'[bold blue]\nUpdating sync secret for project {project_name}.')
        create_sync_secret(gh_username, project_name, updated_sync_token)
        print(f'[bold blue]\nSuccessfully updated sync secret for project {project_name}.')
Example #8
0
def load_project_template_version_and_handle(project_dir: Path) -> Tuple[str, str]:
    """
    Load the template version when the user synced its project with cookietemple the last time.
    If no sync has been done so far, its the version of the cookietemple template the user created the project initially with.
    NOTE: This is NOT the projects current version (they are independent from each other)!!!

    :param project_dir: Top level directory of the users project.
    :return: The version number of the cookietemple template when the user created the project and the projects template handle.
    """
    try:
        ct_meta = load_yaml_file(f"{project_dir.__str__()}/.cookietemple.yml")
        # split the template version at first space to omit the cookietemple bump-version tag and return it and the handle
        return ct_meta["template_version"].split(" ", 1)[0], ct_meta["template_handle"]
    except FileNotFoundError:
        print(f"[bold red]No .cookietemple.yml found at {project_dir.__str__()}. Is this a cookietemple project?")
        sys.exit(1)
Example #9
0
def load_ct_template_version(handle: str, yaml_path: str) -> str:
    """
    Load the version of the template specified by the handler from the given yaml file path.

    :param handle: The template handle
    :param yaml_path: Path to the yaml file
    :return: The version number to the given handles template
    """
    available_templates = load_yaml_file(yaml_path)
    parts = handle.split("-")
    if len(parts) == 2:
        return available_templates[parts[0]][parts[1]]["version"]
    elif len(parts) == 3:

        return available_templates[parts[0]][parts[1]][parts[2]]["version"]

    return ""
Example #10
0
    def show_info(self, handle: str) -> None:
        """
        Displays detailed information of a domain/language/template

        :param handle: domain/language/template handle (examples: cli or cli-python)
        """
        # list of all templates that should be printed according to the passed handle
        templates_to_print: List[str] = []
        available_templates = load_yaml_file(
            f"{self.TEMPLATES_PATH}/available_templates.yml")
        specifiers = handle.split("-")
        domain = specifiers[0]
        template_info: List[str] = []

        # only domain OR language specified
        if len(specifiers) == 1:
            log.debug("Only domain or language was specified.")
            try:
                template_info = available_templates[domain]
            except KeyError:
                self.handle_domain_or_language_only(handle,
                                                    available_templates)
        # domain, subdomain, language
        elif len(specifiers) > 2:
            log.debug("A domain, subdomain and language was specified.")
            try:
                sub_domain = specifiers[1]
                language = specifiers[2]
                template_info = available_templates[domain][sub_domain][
                    language]
            except KeyError:
                self.handle_non_existing_command(handle, True)
        # domain, language OR domain, subdomain
        else:
            log.debug(
                "A domain and language OR domain and a subdomain was specified."
            )
            try:
                second_specifier = specifiers[1]
                template_info = available_templates[domain][second_specifier]
            except KeyError:
                self.handle_non_existing_command(handle, True)

        # Add all templates under template_info to list and output them
        self.flatten_nested_dict(template_info, templates_to_print)
        TemplateInfo.output_table(templates_to_print, handle)
Example #11
0
def decrypt_pat() -> str:
    """
    Decrypt the encrypted PAT.

    :return: The decrypted Personal Access Token for GitHub
    """
    log.debug(f'Decrypting personal access token using key saved in {ConfigCommand.KEY_PAT_FILE}.')
    # read key and encrypted PAT from files
    with open(ConfigCommand.KEY_PAT_FILE, 'rb') as f:
        key = f.readline()
    fer = Fernet(key)
    log.debug(f'Reading personal access token from {ConfigCommand.CONF_FILE_PATH}.')
    encrypted_pat = load_yaml_file(ConfigCommand.CONF_FILE_PATH)['pat']
    # decrypt the PAT and decode it to string
    print('[bold blue]Decrypting personal access token.')
    log.debug('Successfully decrypted personal access token.')
    decrypted_pat = fer.decrypt(encrypted_pat).decode('utf-8')

    return decrypted_pat
Example #12
0
def decrypt_pat() -> str:
    """
    Decrypt the encrypted PAT.

    :return: The decrypted Personal Access Token for GitHub
    """
    log.debug(
        f"Decrypting personal access token using key saved in {ConfigCommand.KEY_PAT_FILE}."
    )
    # read key and encrypted PAT from files
    with open(ConfigCommand.KEY_PAT_FILE, "rb") as f:
        key = f.readline()
    fer = Fernet(key)
    log.debug(
        f"Reading personal access token from {ConfigCommand.CONF_FILE_PATH}.")
    encrypted_pat = load_yaml_file(ConfigCommand.CONF_FILE_PATH)["pat"]
    # decrypt the PAT and decode it to string
    console.print("[bold blue]Decrypting personal access token.")
    log.debug("Successfully decrypted personal access token.")
    decrypted_pat = fer.decrypt(encrypted_pat).decode("utf-8")

    return decrypted_pat
    def prompt_general_template_configuration(self, dot_cookietemple: Optional[dict]):
        """
        Prompts the user for general options that are required by all templates.
        Options are saved in the creator context manager object.
        """
        try:
            """
            Check, if the dot_cookietemple dictionary contains the full name and email (this happens, when dry creating the template while syncing on
            TEMPLATE branch).
            If that's not the case, try to read them from the config file (created with the config command).

            If none of the approaches above succeed (no config file has been found and its not a dry create run), configure the basic credentials and proceed.
            """
            if dot_cookietemple:
                self.creator_ctx.full_name = dot_cookietemple["full_name"]
                self.creator_ctx.email = dot_cookietemple["email"]
            else:
                self.creator_ctx.full_name = load_yaml_file(ConfigCommand.CONF_FILE_PATH)["full_name"]
                self.creator_ctx.email = load_yaml_file(ConfigCommand.CONF_FILE_PATH)["email"]
        except FileNotFoundError:
            # style and automatic use config
            console.print(
                "[bold red]Cannot find a cookietemple config file. Is this your first time using cookietemple?"
            )
            # inform the user and config all settings (with PAT optional)
            console.print("[bold blue]Lets set your name, email and Github username and you´re ready to go!")
            ConfigCommand.all_settings()
            # load mail and full name
            path = Path(ConfigCommand.CONF_FILE_PATH)
            yaml = YAML(typ="safe")
            settings = yaml.load(path)
            # set full name and mail
            self.creator_ctx.full_name = settings["full_name"]
            self.creator_ctx.email = settings["email"]

        self.creator_ctx.project_name = cookietemple_questionary_or_dot_cookietemple(
            function="text",
            question="Project name",
            default="exploding-springfield",
            dot_cookietemple=dot_cookietemple,
            to_get_property="project_name",
        ).lower()  # type: ignore
        if self.creator_ctx.language == "python":
            self.check_name_available("PyPi", dot_cookietemple)
        self.check_name_available("readthedocs.io", dot_cookietemple)
        self.creator_ctx.project_slug = self.creator_ctx.project_name.replace(" ", "_")  # type: ignore
        self.creator_ctx.project_slug_no_hyphen = self.creator_ctx.project_slug.replace("-", "_")
        self.creator_ctx.project_short_description = cookietemple_questionary_or_dot_cookietemple(
            function="text",
            question="Short description of your project",
            default=f"{self.creator_ctx.project_name}" f". A cookietemple based .",
            dot_cookietemple=dot_cookietemple,
            to_get_property="project_short_description",
        )
        poss_vers = cookietemple_questionary_or_dot_cookietemple(
            function="text",
            question="Initial version of your project",
            default="0.1.0",
            dot_cookietemple=dot_cookietemple,
            to_get_property="version",
        )

        # make sure that the version has the right format
        while not re.match(r"(?<!.)\d+(?:\.\d+){2}(?:-SNAPSHOT)?(?!.)", poss_vers) and not dot_cookietemple:  # type: ignore
            console.print(
                "[bold red]The version number entered does not match semantic versioning.\n"
                + r"Please enter the version in the format \[number].\[number].\[number]!"
            )  # noqa: W605
            poss_vers = cookietemple_questionary_or_dot_cookietemple(
                function="text", question="Initial version of your project", default="0.1.0"
            )
        self.creator_ctx.version = poss_vers

        self.creator_ctx.license = cookietemple_questionary_or_dot_cookietemple(
            function="select",
            question="License",
            choices=[
                "MIT",
                "BSD",
                "ISC",
                "Apache2.0",
                "GNUv3",
                "Boost",
                "Affero",
                "CC0",
                "CCBY",
                "CCBYSA",
                "Eclipse",
                "WTFPL",
                "unlicence",
                "Not open source",
            ],
            default="MIT",
            dot_cookietemple=dot_cookietemple,
            to_get_property="license",
        )
        if dot_cookietemple:
            self.creator_ctx.github_username = dot_cookietemple["github_username"]
            self.creator_ctx.creator_github_username = dot_cookietemple["creator_github_username"]
        else:
            self.creator_ctx.github_username = load_github_username()
            self.creator_ctx.creator_github_username = self.creator_ctx.github_username
Example #14
0
def sync(project_dir, set_token, pat, username, check_update) -> None:
    """
    Sync your project with the latest template release.

    cookietemple regularly updates its templates.
    To ensure that you have the latest changes you can invoke sync, which submits a pull request to your Github repository (if existing).
    If no repository exists the TEMPLATE branch will be updated and you can merge manually.
    """
    project_dir_path = Path(project_dir).resolve()
    log.debug(f'Set project top level path to given path argument {project_dir_path}')
    # if set_token flag is set, update the sync token value and exit
    if set_token:
        log.debug('Running sync to update sync token in repo.')
        try:
            log.debug(f'Loading project information from .cookietemple.yml file located at {project_dir}')
            project_data = load_yaml_file(f'{project_dir}/.cookietemple.yml')
            # if project is an orga repo, pass orga name as username
            if project_data['is_github_repo'] and project_data['is_github_orga']:
                log.debug(f'Project is a Github orga repo. Using {project_data["github_orga"]} as username.')
                TemplateSync.update_sync_token(project_name=project_data['project_slug'], gh_username=project_data['github_orga'])
            # if not, use default username
            elif project_data['is_github_repo']:
                log.debug(f'Project is not a Github orga repo.')
                TemplateSync.update_sync_token(project_name=project_data['project_slug'])
            else:
                console.print('[bold red]Your current project does not seem to have a Github repository!')
                sys.exit(1)
        except (FileNotFoundError, KeyError):
            console.print(f'[bold red]Your token value is not a valid personal access token for your account or there exists no .cookietemple.yml file at '
                          f'{project_dir_path}. Is this a cookietemple project?')
            sys.exit(1)
        sys.exit(0)

    log.debug(f'Initializing syncer object.')
    syncer = TemplateSync(new_template_version='', project_dir=project_dir_path, gh_username=username, token=pat)
    # check for template version updates
    log.debug(f'Checking for major/minor or patch version changes in cookietemple templates.')
    major_change, minor_change, patch_change, proj_template_version, ct_template_version = TemplateSync.has_template_version_changed(project_dir_path)
    syncer.new_template_version = ct_template_version
    # check for user without actually syncing
    if check_update:
        log.debug('Running snyc to manually check whether a new template version is available.')
        # a template update has been released by cookietemple
        if any(change for change in (major_change, minor_change, patch_change)):
            console.print(f'[bold blue]Your templates version received an update from {proj_template_version} to {ct_template_version}!\n'
                          f' Use [green]cookietemple sync [blue]to sync your project')
        # no updates were found
        else:
            console.print('[bold blue]Using the latest template version. No sync required.')
        # exit without syncing
        sys.exit(0)
    # set sync flags indicating a major, minor or patch update
    syncer.major_update = major_change
    syncer.minor_update = minor_change
    syncer.patch_update = patch_change
    log.debug('Major template update found.' if major_change else 'Minor template update found.' if minor_change else 'Patch template update found.' if
              patch_change else 'No template update found.')
    # sync the project if any changes
    if any(change for change in (major_change, minor_change, patch_change)):
        if syncer.should_run_sync():
            # check if a pull request should be created according to set level constraints
            log.debug('Starting sync.')
            syncer.sync()
        else:
            console.print('[bold red]Aborting sync due to set level constraints or sync being disabled. You can set the level any time in your cookietemple.cfg'
                          ' in the sync_level section and sync again.')
    else:
        console.print('[bold blue]No changes detected. Your template is up to date.')
    def prompt_general_template_configuration(
            self, dot_cookietemple: Optional[dict]):
        """
        Prompts the user for general options that are required by all templates.
        Options are saved in the creator context manager object.
        """
        try:
            """
            Check, if the dot_cookietemple dictionary contains the full name and email (this happens, when dry creating the template while syncing on
            TEMPLATE branch).
            If that's not the case, try to read them from the config file (created with the config command).

            If none of the approaches above succeed (no config file has been found and its not a dry create run), configure the basic credentials and proceed.
            """
            if dot_cookietemple:
                self.creator_ctx.full_name = dot_cookietemple['full_name']
                self.creator_ctx.email = dot_cookietemple['email']
            else:
                self.creator_ctx.full_name = load_yaml_file(
                    ConfigCommand.CONF_FILE_PATH)['full_name']
                self.creator_ctx.email = load_yaml_file(
                    ConfigCommand.CONF_FILE_PATH)['email']
        except FileNotFoundError:
            # style and automatic use config
            console.print(
                '[bold red]Cannot find a cookietemple config file. Is this your first time using cookietemple?'
            )
            # inform the user and config all settings (with PAT optional)
            console.print(
                '[bold blue]Lets set your name, email and Github username and you´re ready to go!'
            )
            ConfigCommand.all_settings()
            # load mail and full name
            path = Path(ConfigCommand.CONF_FILE_PATH)
            yaml = YAML(typ='safe')
            settings = yaml.load(path)
            # set full name and mail
            self.creator_ctx.full_name = settings['full_name']
            self.creator_ctx.email = settings['email']

        self.creator_ctx.project_name = cookietemple_questionary_or_dot_cookietemple(
            function='text',
            question='Project name',
            default='exploding-springfield',
            dot_cookietemple=dot_cookietemple,
            to_get_property='project_name').lower()  # type: ignore
        if self.creator_ctx.language == 'python':
            self.check_name_available("PyPi", dot_cookietemple)
        self.check_name_available("readthedocs.io", dot_cookietemple)
        self.creator_ctx.project_slug = self.creator_ctx.project_name.replace(
            ' ', '_')  # type: ignore
        self.creator_ctx.project_slug_no_hyphen = self.creator_ctx.project_slug.replace(
            '-', '_')
        self.creator_ctx.project_short_description = cookietemple_questionary_or_dot_cookietemple(
            function='text',
            question='Short description of your project',
            default=f'{self.creator_ctx.project_name}'
            f'. A cookietemple based .',
            dot_cookietemple=dot_cookietemple,
            to_get_property='project_short_description')
        poss_vers = cookietemple_questionary_or_dot_cookietemple(
            function='text',
            question='Initial version of your project',
            default='0.1.0',
            dot_cookietemple=dot_cookietemple,
            to_get_property='version')

        # make sure that the version has the right format
        while not re.match(r'(?<!.)\d+(?:\.\d+){2}(?:-SNAPSHOT)?(?!.)',
                           poss_vers) and not dot_cookietemple:  # type: ignore
            console.print(
                '[bold red]The version number entered does not match semantic versioning.\n'
                +
                'Please enter the version in the format \[number].\[number].\[number]!'
            )  # noqa: W605
            poss_vers = cookietemple_questionary_or_dot_cookietemple(
                function='text',
                question='Initial version of your project',
                default='0.1.0')
        self.creator_ctx.version = poss_vers

        self.creator_ctx.license = cookietemple_questionary_or_dot_cookietemple(
            function='select',
            question='License',
            choices=[
                'MIT', 'BSD', 'ISC', 'Apache2.0', 'GNUv3', 'Boost', 'Affero',
                'CC0', 'CCBY', 'CCBYSA', 'Eclipse', 'WTFPL', 'unlicence',
                'Not open source'
            ],
            default='MIT',
            dot_cookietemple=dot_cookietemple,
            to_get_property='license')
        if dot_cookietemple:
            self.creator_ctx.github_username = dot_cookietemple[
                'github_username']
            self.creator_ctx.creator_github_username = dot_cookietemple[
                'creator_github_username']
        else:
            self.creator_ctx.github_username = load_github_username()
            self.creator_ctx.creator_github_username = self.creator_ctx.github_username