From 5208b18dd12cd90a2cba923691a2e4902667444e Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Tue, 24 Oct 2023 17:28:15 +0200 Subject: [PATCH 1/6] Update TUI behaviour to correctly check for empty choice list --- src/nester/tui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nester/tui.py b/src/nester/tui.py index e4aaf39..b58329d 100644 --- a/src/nester/tui.py +++ b/src/nester/tui.py @@ -76,7 +76,7 @@ def interactive_mode() -> None: choices: List[str] = ["---Abort---"] projects = nester_log.find_all_projects() - if projects is not None: + if projects != []: for project in projects: choices.append(project) else: From 51ef95879e98b29b404e639c224faf79abd88f1a Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Tue, 1 Aug 2023 21:39:14 +0200 Subject: [PATCH 2/6] Add opinionated creation for python --- setup.cfg | 2 + src/nester/core/opinionated/__init__.py | 6 + .../python/pre-commit-config.yaml.template | 16 +++ .../configs/python/pylint.template | 2 + .../configs/python/pyproject.toml.template | 36 ++++++ src/nester/core/opinionated/exceptions.py | 6 + .../core/opinionated/opinionated_creation.py | 118 ++++++++++++++++++ .../core/opinionated/supported_linters.py | 4 + 8 files changed, 190 insertions(+) create mode 100644 src/nester/core/opinionated/__init__.py create mode 100644 src/nester/core/opinionated/configs/python/pre-commit-config.yaml.template create mode 100644 src/nester/core/opinionated/configs/python/pylint.template create mode 100644 src/nester/core/opinionated/configs/python/pyproject.toml.template create mode 100644 src/nester/core/opinionated/exceptions.py create mode 100644 src/nester/core/opinionated/opinionated_creation.py create mode 100644 src/nester/core/opinionated/supported_linters.py diff --git a/setup.cfg b/setup.cfg index 16e6625..8e78711 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,8 @@ install_requires = wheel>=0.37.1 click>=8.1.3 questionary>=1.10.0 + pyyaml + toml; python_version<"3.11" [options.extras_require] dev = diff --git a/src/nester/core/opinionated/__init__.py b/src/nester/core/opinionated/__init__.py new file mode 100644 index 0000000..89b4a24 --- /dev/null +++ b/src/nester/core/opinionated/__init__.py @@ -0,0 +1,6 @@ +""" +This module implements the opinionated option for project creation. + +When creating a project, the user can choose to use various options for setting up the project with Nester providing +various default configurations for linters, formatters, build tools and workflows. +""" diff --git a/src/nester/core/opinionated/configs/python/pre-commit-config.yaml.template b/src/nester/core/opinionated/configs/python/pre-commit-config.yaml.template new file mode 100644 index 0000000..8c8df4f --- /dev/null +++ b/src/nester/core/opinionated/configs/python/pre-commit-config.yaml.template @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + args: ["--verbose", "--safe"] + # It is recommended to specify the latest version of Python + # supported by your project here, or alternatively use + # pre-commit's default_language_version, see + # https://pre-commit.com/#top_level-default_language_version + language_version: python3.10 + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black", "--filter-files"] diff --git a/src/nester/core/opinionated/configs/python/pylint.template b/src/nester/core/opinionated/configs/python/pylint.template new file mode 100644 index 0000000..befefb4 --- /dev/null +++ b/src/nester/core/opinionated/configs/python/pylint.template @@ -0,0 +1,2 @@ +[MASTER] +ignore-paths=docs, tests diff --git a/src/nester/core/opinionated/configs/python/pyproject.toml.template b/src/nester/core/opinionated/configs/python/pyproject.toml.template new file mode 100644 index 0000000..dfdcc08 --- /dev/null +++ b/src/nester/core/opinionated/configs/python/pyproject.toml.template @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools", "setuptools-scm", "wheel"] +build-backend = "setuptools.build_meta" + + +[project] +name = "$projectname" +version = "0.0.1" +description = "" +authors = [ + { name = "", email = "" } +] +license = "" +readme = "README.md" +homepage = "" +repository = "https://github.com//$projectname" +classifiers = [ + "Programming Language :: Python :: 3", +] + +[project.scripts] +$projectname = "$projectname.$projectname:main" + + +[project.dependencies] + + +[project.dev-dependencies] + + +[tool.setuptools_scm] +root = "." +relative_to = "src/nester/__init__.py" + +[tool.isort] +profile = "black" diff --git a/src/nester/core/opinionated/exceptions.py b/src/nester/core/opinionated/exceptions.py new file mode 100644 index 0000000..05cf072 --- /dev/null +++ b/src/nester/core/opinionated/exceptions.py @@ -0,0 +1,6 @@ +"""This module provides custom exceptions for the opinionated creation of projects.""" + + +class UnsupportedLanguageException(Exception): + def __str__(self) -> str: + return "\033[91mError: The chosen language is not supported for opinionated creation!\033[0m" diff --git a/src/nester/core/opinionated/opinionated_creation.py b/src/nester/core/opinionated/opinionated_creation.py new file mode 100644 index 0000000..5ac659d --- /dev/null +++ b/src/nester/core/opinionated/opinionated_creation.py @@ -0,0 +1,118 @@ +""" +This module takes care of reading the stock configuration files located in the ```configs``` directory and dumps +their content into the already created empty config files. +""" + +import subprocess +from typing import Any + +import toml +import yaml + +from . import exceptions, supported_linters + + +def set_config_files_for_project( + language: str, project_name: str, add_linters: bool +) -> None: + """ + Will set config files for project. + + This function takes the project's language and name at creation time and will look for a folder containing the + templates for various config files such as build files. + + If ```add_linters``` is true, the linter configuration files are added and the linters are installed when possible. + + If language is not supported, it will raise an exception and inform the user. + + :param language: The programming language the project is written in. + :type language: str + :param project_name: The name of the project that is being created. + :type project_name: str + """ + if add_linters: + install_linters(language) + + match language: + case "py": + with open("configs/python/pyproject.toml.template", "r") as file: + template_content: dict[str, Any] = toml.load(file) + + with open("pyproject.toml", "w") as file: + toml.dump(template_content, file) + + if add_linters: + # Add pylintrc + with open("configs/python/pylint.template", "r") as file: + rc_template_content: str = file.read() + + with open(".pylintrc", "w") as file: + file.write(rc_template_content) + + # Add pre-commit-config + with open("config/pre-commit-config.yaml.template") as file: + pre_commit_template_content = yaml.safe_load(file) + + with open(".pre-commit-config.yaml", "w") as file: + yaml.safe_dump(pre_commit_template_content) + + # Add linters to pyproject.toml + with open("pyproject.toml", "r") as project_toml: + pyproject_config: dict[str, Any] = toml.load(project_toml) + + pyproject_config["project"]["dev-dependencies"] = { + "pylint": "*", + "black": "*", + "isort": "*", + "pre-commit": "*", + } + + pyproject_config["tool"]["isort"] = {"profile": "black"} + + with open("pyproject.toml", "w") as project_toml: + toml.dump(pyproject_config, project_toml) + + case _: + # If language is not supported, an error is raised. + raise exceptions.UnsupportedLanguageException() + + # Install all specified build tools + install_build_system(language) + + +def install_build_system(language: str) -> None: + """ + Will attempt to install a project's build system. + + For various languages some build tools are supported, check the :py:mod:`supported_linters` module to see which. + + :param language: The programming language the project is written in. Used to install the list of supported tools. + :type language: str + """ + match language: + case "py": + for tool in supported_linters.py_build: + subprocess.run(["pip", "install", tool]) + case _: + raise exceptions.UnsupportedLanguageException + + +def install_linters(language: str) -> None: + """ + Will install the linters and other dependencies for a given language. You can find this in the + :py:mod:`supported_linters` module. + + :param language: The programming language the project is written in. Used to install the list of supported tools. + :type language: str + """ + match language: + case "py": + try: + for linter in supported_linters.py_linters: + subprocess.run(["pip", "install", linter]) + except subprocess.CalledProcessError as exc: + print( + "\033[91mError: Pip does not seem to be installed on your system. Install it and try again.\033[0m" + ) + case _: + raise exceptions.UnsupportedLanguageException diff --git a/src/nester/core/opinionated/supported_linters.py b/src/nester/core/opinionated/supported_linters.py new file mode 100644 index 0000000..41d6b8f --- /dev/null +++ b/src/nester/core/opinionated/supported_linters.py @@ -0,0 +1,4 @@ +"""This module holds lists of supported linters and tools for projects. For these tools config files are ready to be used.""" + +py_build = ["setuptools", "setuptools-scm", "wheel"] +py_linters = ["black", "isort", "pylint"] From 345c43bfad291fba357c815c73f7a09dd1cc1802 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Tue, 1 Aug 2023 21:58:46 +0200 Subject: [PATCH 3/6] Fix unresolvable toml import --- src/nester/core/opinionated/opinionated_creation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nester/core/opinionated/opinionated_creation.py b/src/nester/core/opinionated/opinionated_creation.py index 5ac659d..e9a3851 100644 --- a/src/nester/core/opinionated/opinionated_creation.py +++ b/src/nester/core/opinionated/opinionated_creation.py @@ -6,9 +6,13 @@ import subprocess from typing import Any -import toml import yaml +try: + import tomllib as toml +except ImportError: + import toml + from . import exceptions, supported_linters From 50368d68d103044e5406cf98245ead19c7c260ea Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Tue, 1 Aug 2023 22:50:45 +0200 Subject: [PATCH 4/6] remove call to native tomllib because of missing features --- setup.cfg | 2 +- src/nester/core/opinionated/opinionated_creation.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8e78711..16265ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ install_requires = click>=8.1.3 questionary>=1.10.0 pyyaml - toml; python_version<"3.11" + toml [options.extras_require] dev = diff --git a/src/nester/core/opinionated/opinionated_creation.py b/src/nester/core/opinionated/opinionated_creation.py index e9a3851..eb18ce1 100644 --- a/src/nester/core/opinionated/opinionated_creation.py +++ b/src/nester/core/opinionated/opinionated_creation.py @@ -6,13 +6,10 @@ import subprocess from typing import Any +# If a version of the dump method comes available in python 3.11's native tomllib, add a import try except here. +import toml import yaml -try: - import tomllib as toml -except ImportError: - import toml - from . import exceptions, supported_linters From f810fee81ee8895188a005ad9198aeaee4ed78f5 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Fri, 4 Aug 2023 13:24:46 +0200 Subject: [PATCH 5/6] link opinionated creation to cli frontend --- src/nester/cli.py | 13 +++++++++++-- src/nester/core/commands.py | 14 +++++++++++++- .../core/opinionated/opinionated_creation.py | 7 ++++--- .../{supported_linters.py => supported_tools.py} | 0 4 files changed, 28 insertions(+), 6 deletions(-) rename src/nester/core/opinionated/{supported_linters.py => supported_tools.py} (100%) diff --git a/src/nester/cli.py b/src/nester/cli.py index 405567e..73ddb52 100644 --- a/src/nester/cli.py +++ b/src/nester/cli.py @@ -39,7 +39,16 @@ def cli(ctx): "--git", "-g", is_flag=True, default=False, help="Set up git repository as well." ) @click.option("--no-log", is_flag=True, default=False, help="Do not log this project.") -def create(language: str, project_name: str, git: bool, no_log: bool) -> None: +@click.option( + "--opinionated", + "-o", + is_flag=True, + default=False, + help="Configure supported linters and build tools for your project.", +) +def create( + language: str, project_name: str, git: bool, no_log: bool, opinionated: bool +) -> None: """ Create new project structure within current directory. @@ -53,7 +62,7 @@ def create(language: str, project_name: str, git: bool, no_log: bool) -> None: "Starting Nester.\nCopyright (c) 2023 ByteOtter.(github.com/ByteOtter)\nLicensed under the terms of GPL-3.0. Check github.com/ByteOtter/nester/LICENSE for more information.\nNo warranty or liability are included with the use of this software." ) - commands.create_project(language, project_name, git, no_log) + commands.create_project(language, project_name, git, no_log, opinionated) @click.command(help="Validate current structure against Nester's JSON schemas.") diff --git a/src/nester/core/commands.py b/src/nester/core/commands.py index 67ebf33..78b421f 100644 --- a/src/nester/core/commands.py +++ b/src/nester/core/commands.py @@ -8,9 +8,12 @@ from pathlib import Path from . import nester_log, utils +from .opinionated import opinionated_creation -def create_project(language: str, project_name: str, git: bool, no_log: bool) -> None: +def create_project( + language: str, project_name: str, git: bool, no_log: bool, opinionated: bool +) -> None: """ Create a new project. @@ -34,6 +37,15 @@ def create_project(language: str, project_name: str, git: bool, no_log: bool) -> print(f"Creating file structure for your {language} project '{project_name}'...") + if opinionated: + if language is not "py": + print( + "\033[31mSorry, Nester currently only supports opinionated creation for Python projects.\033[0m\nStay tuned on updates for your language." + ) + else: + opinionated_creation.install_build_system(language) + opinionated_creation.install_linters(language) + if git: utils.initialize_git_repository(project_dir) diff --git a/src/nester/core/opinionated/opinionated_creation.py b/src/nester/core/opinionated/opinionated_creation.py index eb18ce1..94b3b93 100644 --- a/src/nester/core/opinionated/opinionated_creation.py +++ b/src/nester/core/opinionated/opinionated_creation.py @@ -10,7 +10,7 @@ import toml import yaml -from . import exceptions, supported_linters +from . import exceptions, supported_tools def set_config_files_for_project( @@ -68,6 +68,7 @@ def set_config_files_for_project( "pre-commit": "*", } + # Add isort profile config to pyproject toml to avoid conflicts between isort and black pyproject_config["tool"]["isort"] = {"profile": "black"} with open("pyproject.toml", "w") as project_toml: @@ -92,7 +93,7 @@ def install_build_system(language: str) -> None: """ match language: case "py": - for tool in supported_linters.py_build: + for tool in supported_tools.py_build: subprocess.run(["pip", "install", tool]) case _: raise exceptions.UnsupportedLanguageException @@ -109,7 +110,7 @@ def install_linters(language: str) -> None: match language: case "py": try: - for linter in supported_linters.py_linters: + for linter in supported_tools.py_linters: subprocess.run(["pip", "install", linter]) except subprocess.CalledProcessError as exc: print( diff --git a/src/nester/core/opinionated/supported_linters.py b/src/nester/core/opinionated/supported_tools.py similarity index 100% rename from src/nester/core/opinionated/supported_linters.py rename to src/nester/core/opinionated/supported_tools.py From 846007ff781b0ff66956b1d7660949ef5bc2c40c Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Mon, 9 Oct 2023 15:14:18 +0200 Subject: [PATCH 6/6] Add logic to identify system pm --- src/nester/core/opinionated/exceptions.py | 5 ++++ .../core/opinionated/opinionated_creation.py | 7 +++++- .../core/opinionated/supported_tools.py | 2 ++ src/nester/core/opinionated/utils.py | 23 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/nester/core/opinionated/utils.py diff --git a/src/nester/core/opinionated/exceptions.py b/src/nester/core/opinionated/exceptions.py index 05cf072..cabc82d 100644 --- a/src/nester/core/opinionated/exceptions.py +++ b/src/nester/core/opinionated/exceptions.py @@ -2,5 +2,10 @@ class UnsupportedLanguageException(Exception): + """ + This exception is raised, whenever the user tries to use opinionated creation for a language that does not + support this feature. + """ + def __str__(self) -> str: return "\033[91mError: The chosen language is not supported for opinionated creation!\033[0m" diff --git a/src/nester/core/opinionated/opinionated_creation.py b/src/nester/core/opinionated/opinionated_creation.py index 94b3b93..d2541e0 100644 --- a/src/nester/core/opinionated/opinionated_creation.py +++ b/src/nester/core/opinionated/opinionated_creation.py @@ -94,7 +94,12 @@ def install_build_system(language: str) -> None: match language: case "py": for tool in supported_tools.py_build: - subprocess.run(["pip", "install", tool]) + try: + subprocess.run(["pip", "install", tool]) + except subprocess.CalledProcessError: + print( + "\033[91mError: Pip does not seem to be installed on your system. Install it and try again.\033[0m" + ) case _: raise exceptions.UnsupportedLanguageException diff --git a/src/nester/core/opinionated/supported_tools.py b/src/nester/core/opinionated/supported_tools.py index 41d6b8f..f55b3ac 100644 --- a/src/nester/core/opinionated/supported_tools.py +++ b/src/nester/core/opinionated/supported_tools.py @@ -2,3 +2,5 @@ py_build = ["setuptools", "setuptools-scm", "wheel"] py_linters = ["black", "isort", "pylint"] + +package_managers = ["zypper", "apt", "dnf"] diff --git a/src/nester/core/opinionated/utils.py b/src/nester/core/opinionated/utils.py new file mode 100644 index 0000000..57f7b66 --- /dev/null +++ b/src/nester/core/opinionated/utils.py @@ -0,0 +1,23 @@ +""" +This module provides support functionality for the opinionated creation module. +""" + +import subprocess + +from . import supported_tools + + +def identify_package_manager() -> str: + """ + Identify which distribution's package manager is installed via trial and error. + + :return: The name of the package manager to be used to install dependencies. + :rtype: str + """ + for pm in supported_tools.package_managers: + try: + subprocess.run(pm) + except subprocess.CalledProcessError: + continue + return pm + return ""