Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE:] Add opinionated creation #54

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ install_requires =
wheel>=0.37.1
click>=8.1.3
questionary>=1.10.0
pyyaml
toml

[options.extras_require]
dev =
Expand Down
13 changes: 11 additions & 2 deletions src/nester/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.")
Expand Down
14 changes: 13 additions & 1 deletion src/nester/core/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions src/nester/core/opinionated/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Original file line number Diff line number Diff line change
@@ -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"]
2 changes: 2 additions & 0 deletions src/nester/core/opinionated/configs/python/pylint.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[MASTER]
ignore-paths=docs, tests
Original file line number Diff line number Diff line change
@@ -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/<YourUserName>/$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"
11 changes: 11 additions & 0 deletions src/nester/core/opinionated/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""This module provides custom exceptions for the opinionated creation of projects."""


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"
125 changes: 125 additions & 0 deletions src/nester/core/opinionated/opinionated_creation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
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

# 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

from . import exceptions, supported_tools


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": "*",
}

# 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:
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_tools.py_build:
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


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_tools.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
6 changes: 6 additions & 0 deletions src/nester/core/opinionated/supported_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""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"]

package_managers = ["zypper", "apt", "dnf"]
23 changes: 23 additions & 0 deletions src/nester/core/opinionated/utils.py
Original file line number Diff line number Diff line change
@@ -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 ""
2 changes: 1 addition & 1 deletion src/nester/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading