From b61043831be2bee5cb7a0150a5b31747179ad219 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Fri, 30 Jun 2023 05:14:07 +0300 Subject: [PATCH 01/35] 0.2.0-alpha --- .github/workflows/python-publish.yml | 46 ++++++++++++++++++++++ .vscode/settings.json | 6 +++ .vscode/tasks.json | 17 ++++++++ CHANGELOG.MD | 16 +++----- README.md | 10 ++++- artec/__main__.py | 6 +-- artec/argparser.py | 26 ++++++------- artec/boiler.py | 58 ++++++++++++++-------------- artec/exceptions.py | 23 ++++++++--- artec/templates.py | 28 ++++++++++++++ pyproject.toml | 4 +- src.json | 3 ++ test/test_boiler.py | 2 +- test/test_parser.py | 13 ++++--- 14 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/python-publish.yml create mode 100644 .vscode/tasks.json create mode 100644 artec/templates.py create mode 100644 src.json diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..5c43b19 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,46 @@ +name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI + +on: + push: + branches: + - 'master' + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + + - name: Build a binary wheel and a source tarball + run: >- + python3 -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API }} + repository-url: https://test.pypi.org/legacy/ + + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API }} + diff --git a/.vscode/settings.json b/.vscode/settings.json index e2d2841..2ecf267 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,11 @@ "./test", "-p", "test_*.py" + ], + "cSpell.words": [ + "Achary", + "Akhil", + "argparser", + "Narayandas" ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..96bfbab --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Lint using flake8", + "type": "shell", + "command": "flake8 artec --ignore=E101,W191" + }, + { + "label": "Format using black", + "type": "shell", + "command": "black artec" + }, + ] +} \ No newline at end of file diff --git a/CHANGELOG.MD b/CHANGELOG.MD index b4a4182..0be3ec4 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -28,47 +28,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unused normalize.css file - Identical links assigned in each translation file --> +## [0.2.0] - 2023-06-30 +### Added +- ```-t``` argument now allows Templates to be used +- Github actions can publish to Pypi & Test.Pypi + + ## [0.1.4] - 2023-06-26 ### Changed - - Improved error reporting ### Fixed - - Fixed Issue [#8](https://github.com/HushmKun/Artec/issues/8) - Fixed error building structure if the source starts with a file. ## [0.1.3] - 2023-06-20 ### Added - - ```-V ``` argument to print version of Artec. - Changelog.md to records changes. ## [0.1.2] - 2023-06-17 - ### Changed - - Tests directory to the preferred structure. ### Fixed - - Files and folders are not being created according to "structure.json" [#4](https://github.com/HushmKun/Artec/issues/4) ## [0.1.1] - 2023-06-15 ### Added - - ```-v``` argument to enforce verbosity. ## [0.1.0] - 2023-06-12 - ### Added - - Unit tests for each module. ### Changes - - Ported from Python 2.7 into Python 3 - Refactor the fork to more pip-package architecture. - Split the project into modules for better code & tests. diff --git a/README.md b/README.md index dc315f2..ae7886a 100644 --- a/README.md +++ b/README.md @@ -43,23 +43,29 @@ options: Target output path where the structure will be created -s SOURCE, --source-file SOURCE Source JSON file containing structure to be created - -i, --interactive Runs Artec in interactive mode. + -t TEMPLATE, --template TEMPLATE + Uses ready-made templates. -v, --verbose Runs Artec in verbose mode. -V, --version Display current version of Artec Examples: + artec -h artec -o dest + artec -o dest -t python artec -o dest -s structure.json + artec -o dest -s structure.json -v ``` ## Version - 0.1.4 + 0.1.5 ## Contributing * Big Thanks to [Link-](https://github.com/Link-) for jump starting the project. * Thanks for [Narayandas Akhil Achary](https://github.com/0018akhil) for various fixes & features. +Any help that can contribute to the templates will be really appreciated. + Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/artec/__main__.py b/artec/__main__.py index 56685be..7e403bc 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,15 +2,15 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.1.4" +__version__ = "0.1.5" __desc__ = "Creates a configurable python project \ template in a given directory." def main(): - args = main_args() + args = main_args(__version__) - builder = boiler_builder(args.source, args.target,args.verbose, args.template, args.tui) + builder = boiler_builder(args.source, args.target, args.verbose, args.template) builder.build() diff --git a/artec/argparser.py b/artec/argparser.py index 2ea01c4..c3aa7d9 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -5,7 +5,8 @@ class Parser(ArgumentParser): - def __init__(self): + def __init__(self, appVersion): + self.appVersion = appVersion prog = "Artec" usage = "artec [OPTIONS] -o [DEST] " description = "Artec is a simple python 3 script to create a project template in a given directory." @@ -41,16 +42,6 @@ def setup(self): required=False, ) - # ! Not Implemented Yet. - self.add_argument( - "-i", - "--interactive", - dest="tui", - help="Runs Artec in interactive mode.", - action="store_true", - required=False, - ) - self.add_argument( "-v", "--verbose", @@ -60,8 +51,17 @@ def setup(self): required=False, ) -def main_args() -> Namespace: - parser = Parser() + self.add_argument( + "-V", + "--version", + help="Display current version of Artec", + action="version", + version=f"{self.prog} {self.appVersion}", + ) + + +def main_args(appVersion) -> Namespace: + parser = Parser(appVersion) parser.setup() return parser.parse_args() diff --git a/artec/boiler.py b/artec/boiler.py index 8aab889..d9438f0 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -5,30 +5,44 @@ import json import os from pathlib import Path -from .exceptions import NotJsonFile, NotValidJson, NoSource +from .exceptions import NotJsonFile, NotValidJson, NoSource, InValidTemplate +from .templates import templates, static_list + + class boiler_builder: - #! TUI is not implemented yet. - def __init__(self, source=None, target=None, verbose=False, template=None, tui=False) -> None: + def __init__(self, source=None, target=None, verbose=False, template=None) -> None: self.verbose = verbose self.target = target - self.template = template - if self.template is None : + self.template = template + if self.template is None: self.structure = self._source(source) + else: + self.structure = self._source_temp(template.lower()) + + def _source_temp(self, template) -> list[dict[str, str]]: + try: + if template in templates: + structure = templates[template].format(self.target) + else: + raise InValidTemplate(self.verbose) + + except InValidTemplate: + structure = templates["python"].format(self.target) + return structure def _source(self, source) -> list[dict[str, str]]: try: - if os.path.isfile(source) and source.endswith('.json'): + if os.path.isfile(source) and source.endswith(".json"): with open(source, "rt", encoding="utf-8") as file_data: - structure = json.load(file_data) - else : + structure = static_list(json.load(file_data)).format(self.target) + else: raise NotJsonFile(self.verbose) - - except Exception as e : - - if not hasattr(e,"errno") : - NoSource() - structure = DEFAULT_FOLDER_STRUCTURE + except Exception as e: + if not hasattr(e, "errno"): + NoSource() + + structure = templates["python"].format(self.target) return structure def build(self): @@ -42,12 +56,12 @@ def build(self): self._make_folder(joined) elif _type == "file": self._make_file(joined) - else : + else: raise NotValidJson(self.verbose) print("Created: %s" % joined) except Exception: exit("> Fatal error - exiting...") - + def _make_file(self, path): """Create an empty file in a given directory""" path.parent.mkdir(parents=True, exist_ok=True) @@ -57,18 +71,6 @@ def _make_folder(self, path): """Create an empty directory""" os.makedirs(path, exist_ok=True) -DEFAULT_FOLDER_STRUCTURE = [ - {"folder": "src"}, - {"file": "src/__init__.py"}, - {"folder": "test"}, - {"file": "test/__init__.py"}, - {"folder": "res"}, - {"file": "README.md"}, - {"file": "setup.py"}, - {"file": "setup.cfg"}, - {"file": "pyproject.toml"}, -] - if __name__ == "__main__": pass diff --git a/artec/exceptions.py b/artec/exceptions.py index b1c25c7..2aab305 100644 --- a/artec/exceptions.py +++ b/artec/exceptions.py @@ -1,18 +1,31 @@ -class NotJsonFile (FileNotFoundError): - def __init__(self, verbose:bool) -> None: +class NotJsonFile(FileNotFoundError): + def __init__(self, verbose: bool) -> None: super().__init__("> Provided Source isn't valid JSON file") self.errno = 101 - if not verbose : return + if not verbose: + return print("> Provided Source isn't valid JSON file") print("> Reverting to default structure") + def NoSource(): print("> No Source Provided") print("> Using default structure") + class NotValidJson(KeyError): - def __init__(self, verbose:bool) -> None: + def __init__(self, verbose: bool) -> None: super().__init__("> Provided Json file format is incorrect") self.errno = 102 - if not verbose : return + if not verbose: + return print("> Provided Json file format is incorrect") + + +class InValidTemplate(KeyError): + def __init__(self, verbose: bool) -> None: + super().__init__("> Provided Template argument is invalid") + self.errno = 103 + if not verbose: + return + print("> Provided Template argument is invalid") diff --git a/artec/templates.py b/artec/templates.py new file mode 100644 index 0000000..6785ebc --- /dev/null +++ b/artec/templates.py @@ -0,0 +1,28 @@ +class static_list(list): + def __init__(self, iterable): + super().__init__(iterable) + + def format(self, name): + for _ in self: + for i, j in _.items(): + _[i] = j.format(name) + return self + + +PYTHON_PACKAGE = static_list( + [ + {"folder": "{}"}, + {"file": "{}/__init__.py"}, + {"folder": "test"}, + {"file": "test/__init__.py"}, + {"file": "README.md"}, + {"file": "LICENSE"}, + {"file": "setup.py"}, + {"file": "setup.cfg"}, + {"file": "pyproject.toml"}, + ] +) + +templates = { + "python": PYTHON_PACKAGE +} diff --git a/pyproject.toml b/pyproject.toml index 75bd197..d86e0e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.1.4" +version = "0.1.5" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, @@ -14,7 +14,7 @@ readme = "README.md" requires-python = ">=3.7" classifiers = [ "Intended Audience :: Developers", - "Development Status :: 2 - Pre-Alpha", + "Development Status :: 4 - Beta", "Environment :: Console", "Programming Language :: Python :: 3", "Topic :: Software Development :: Build Tools" diff --git a/src.json b/src.json new file mode 100644 index 0000000..0ceb975 --- /dev/null +++ b/src.json @@ -0,0 +1,3 @@ +[{"folder": "{}"}, +{"file": "{}/file1.txt"}, +{"file": "README.md"}] \ No newline at end of file diff --git a/test/test_boiler.py b/test/test_boiler.py index 19ae51f..0259c0e 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -16,10 +16,10 @@ def enablePrint(): class BoilerTest(unittest.TestCase): def setUp(self): + blockPrint() self.boiler = boiler.boiler_builder(target="fake") def test_default_structure(self): - blockPrint() self.boiler.build() enablePrint() diff --git a/test/test_parser.py b/test/test_parser.py index 023bc73..6c9a663 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -4,7 +4,7 @@ class ParserTest(unittest.TestCase): def setUp(self): - self.parser = argparser.Parser() + self.parser = argparser.Parser('appVersion') self.parser.setup() def test_arg_target(self): @@ -15,10 +15,13 @@ def test_arg_source(self): parsed = self.parser.parse_args(["-o", "/dir/r", "-s", "/dir/s"]) self.assertEqual(parsed.source, "/dir/s") - def test_arg_interactive(self): - parsed = self.parser.parse_args(["-o", "/dir/r", "-i"]) - self.assertTrue(parsed.tui) - def test_arg_verbose(self): parsed = self.parser.parse_args(["-o", "/dir/r", "-v"]) self.assertTrue(parsed.verbose) + + def test_arg_template(self): + parsed = self.parser.parse_args(["-o", "/dir/r", "-t", "python"]) + self.assertEqual(parsed.template, "python") + + def tearDown(self) -> None: + del self.parser From 5b52e9e979991fad002e2e371dcd7dbca23a3fa1 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Fri, 30 Jun 2023 05:17:31 +0300 Subject: [PATCH 02/35] 0.2.0-alpha --- .github/workflows/python-publish-test.yml | 43 +++++++++++++++++++++++ .github/workflows/python-publish.yml | 8 +---- src.json | 3 -- 3 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/python-publish-test.yml delete mode 100644 src.json diff --git a/.github/workflows/python-publish-test.yml b/.github/workflows/python-publish-test.yml new file mode 100644 index 0000000..e7b549f --- /dev/null +++ b/.github/workflows/python-publish-test.yml @@ -0,0 +1,43 @@ +name: Publish Python 🐍 distributions 📦 to TestPyPI + +on: + push: + branches: + - 'development' + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + + - name: Build a binary wheel and a source tarball + run: >- + python3 -m + build + --sdist + --wheel + --outdir dist/ + . + + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API }} + repository-url: https://test.pypi.org/legacy/ + + + diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 5c43b19..0af25c5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,4 +1,4 @@ -name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI +name: Publish Python 🐍 distributions 📦 to PyPI on: push: @@ -33,12 +33,6 @@ jobs: --outdir dist/ . - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API }} - repository-url: https://test.pypi.org/legacy/ - - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/src.json b/src.json deleted file mode 100644 index 0ceb975..0000000 --- a/src.json +++ /dev/null @@ -1,3 +0,0 @@ -[{"folder": "{}"}, -{"file": "{}/file1.txt"}, -{"file": "README.md"}] \ No newline at end of file From 2264ce7277d75d5431d7c4c50dceb2f67d9805dc Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Fri, 30 Jun 2023 05:30:13 +0300 Subject: [PATCH 03/35] 0.2.0-alpha --- .github/workflows/python-publish.yml | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 0af25c5..dfadaef 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -33,6 +33,11 @@ jobs: --outdir dist/ . + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API }} + repository-url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/pyproject.toml b/pyproject.toml index d86e0e6..00d6b92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.1.5" +version = "0.2.0-alpha" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From 58772edc807039f5308c47509992037b08e4c99e Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Fri, 30 Jun 2023 14:33:46 +0300 Subject: [PATCH 04/35] 0.2.0-rc1 --- README.md | 2 +- artec/__main__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae7886a..252306a 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Examples: ``` ## Version - 0.1.5 + 0.2.0-rc1 ## Contributing diff --git a/artec/__main__.py b/artec/__main__.py index 7e403bc..de85c8c 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.1.5" +__version__ = "0.2.0-rc1" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/pyproject.toml b/pyproject.toml index 00d6b92..1420242 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0-alpha" +version = "0.2.0-rc1" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From e71eeb4436dce1ac7e03cabf8b5153a0d0b5fa89 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Fri, 30 Jun 2023 14:36:21 +0300 Subject: [PATCH 05/35] 0.2.0-rc1 --- .github/workflows/python-publish-test.yml | 2 +- .github/workflows/python-publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-publish-test.yml b/.github/workflows/python-publish-test.yml index e7b549f..0865a82 100644 --- a/.github/workflows/python-publish-test.yml +++ b/.github/workflows/python-publish-test.yml @@ -7,7 +7,7 @@ on: jobs: build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + name: Build and publish Python 🐍 distributions 📦 to TestPyPI runs-on: ubuntu-latest steps: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index dfadaef..19ebe78 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -7,7 +7,7 @@ on: jobs: build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-latest steps: From 11d78f7657a191ab5e54dece2c76e3e936416116 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 00:27:23 +0300 Subject: [PATCH 06/35] 0.2.0-rc2 --- .vscode/launch.json | 6 +----- README.md | 4 ++-- artec/__main__.py | 2 +- artec/argparser.py | 19 ++++++++++++++++++- artec/templates.py | 10 ++++------ pyproject.toml | 2 +- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c12767b..bd17154 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,11 +7,7 @@ "request": "launch", "module": "artec", "args": [ - "-o", - "fake22", - "-s", - "src.json", - "--verbose", + "-ls" ], "justMyCode": true }, diff --git a/README.md b/README.md index 252306a..06c9829 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ _It's a maintained version of PyBoiler_ Download from pip ```bash -pip install artec +pip install -i https://test.pypi.org/simple/ Artec==0.2.0rc1 ``` or Install manually @@ -57,7 +57,7 @@ Examples: ``` ## Version - 0.2.0-rc1 + 0.2.0-rc2 ## Contributing diff --git a/artec/__main__.py b/artec/__main__.py index de85c8c..7d827d1 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.0-rc1" +__version__ = "0.2.0-rc2" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/artec/argparser.py b/artec/argparser.py index c3aa7d9..b070ae0 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -1,9 +1,20 @@ """ Parser class is a wrapper for easy access of argparse module """ -from argparse import ArgumentParser, Namespace, RawTextHelpFormatter +from argparse import ArgumentParser, Namespace, RawTextHelpFormatter, _VersionAction +from .templates import templates +import sys +class list_templates(_VersionAction): + def __call__(self, parser: ArgumentParser, namespace, argument, options) -> None: + formatter = parser._get_formatter() + formatter.add_text( + "Available templates\n\n" + '\n'.join([f"> {key.title()}\t" for key,value in templates.items()]) + ) + parser._print_message(formatter.format_help(), sys.stdout) + parser.exit() + class Parser(ArgumentParser): def __init__(self, appVersion): self.appVersion = appVersion @@ -59,6 +70,12 @@ def setup(self): version=f"{self.prog} {self.appVersion}", ) + self.add_argument( + "-ls", + "--list-template", + help="lists all ready-made templates.", + action=list_templates, + ) def main_args(appVersion) -> Namespace: parser = Parser(appVersion) diff --git a/artec/templates.py b/artec/templates.py index 6785ebc..73532aa 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -1,14 +1,10 @@ class static_list(list): - def __init__(self, iterable): - super().__init__(iterable) - def format(self, name): for _ in self: for i, j in _.items(): _[i] = j.format(name) return self - PYTHON_PACKAGE = static_list( [ {"folder": "{}"}, @@ -24,5 +20,7 @@ def format(self, name): ) templates = { - "python": PYTHON_PACKAGE -} + "python": PYTHON_PACKAGE, + "node.js": None, + "web": None, +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1420242..902f16d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0-rc1" +version = "0.2.0-rc2" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From 8d4e7b689539278d4b3d53553a0efbeb4e245be0 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 00:52:43 +0300 Subject: [PATCH 07/35] 0.2.0-rc2 --- .../{python-publish.yml => pypi-publish.yml} | 0 .github/workflows/run-tests.yml | 30 +++++++++++++++++++ ...publish-test.yml => test-pypi-publish.yml} | 0 3 files changed, 30 insertions(+) rename .github/workflows/{python-publish.yml => pypi-publish.yml} (100%) create mode 100644 .github/workflows/run-tests.yml rename .github/workflows/{python-publish-test.yml => test-pypi-publish.yml} (100%) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/pypi-publish.yml similarity index 100% rename from .github/workflows/python-publish.yml rename to .github/workflows/pypi-publish.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..a639600 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,30 @@ +name: Run Tests + +on: push + +permissions: + contents: read + +jobs: + Test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E101,W191 + - name: Test with pytest + run: | + pytest -v diff --git a/.github/workflows/python-publish-test.yml b/.github/workflows/test-pypi-publish.yml similarity index 100% rename from .github/workflows/python-publish-test.yml rename to .github/workflows/test-pypi-publish.yml From 549b4bac80ccb14c4545787bc467d9f452c85eab Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 00:55:08 +0300 Subject: [PATCH 08/35] 0.2.0-rc2 --- .github/workflows/pypi-publish.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 19ebe78..536d5ca 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -33,13 +33,11 @@ jobs: --outdir dist/ . - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API }} - repository-url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API }} + repository-url: https://test.pypi.org/legacy/ + + From 5f8ac930b895d962e3565790809a1ab05bff2f0a Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 18:14:59 +0300 Subject: [PATCH 09/35] Fixed Github action ... I think ? --- .github/workflows/pypi-publish.yml | 61 +++++++++++++++++++----- .github/workflows/run-tests.yml | 30 ------------ .github/workflows/test-pypi-publish.yml | 62 ++++++++++++++++++++----- 3 files changed, 99 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 536d5ca..d8273a3 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -1,4 +1,4 @@ -name: Publish Python 🐍 distributions 📦 to PyPI +name: Publish Artec to Pypi on: push: @@ -6,38 +6,75 @@ on: - 'master' jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-latest + Test: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E101,W191 + - name: Test with pytest + run: | + pytest -v + build: + needs: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" - name: Install pypa/build run: >- python3 -m pip install build - --user - + --user - name: Build a binary wheel and a source tarball run: >- python3 -m build --sdist --wheel - --outdir dist/ + --outdir ~/dist/ . - - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Cache Builds + uses: actions/cache@v2 with: - password: ${{ secrets.PYPI_API }} - repository-url: https://test.pypi.org/legacy/ + path: ~/dist/ + key: ${{ runner.os }}-build + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Restore Builds + id: cache-builds + uses: actions/cache@v2 + with: + path: ~/dist/ + key: ${{ runner.os }}-build + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API }} + packages-dir: ~/dist/ diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index a639600..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Run Tests - -on: push - -permissions: - contents: read - -jobs: - Test: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.x" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E101,W191 - - name: Test with pytest - run: | - pytest -v diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 0865a82..0ea7f93 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -1,4 +1,4 @@ -name: Publish Python 🐍 distributions 📦 to TestPyPI +name: Publish Artec to TestPypi on: push: @@ -6,38 +6,76 @@ on: - 'development' jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to TestPyPI - runs-on: ubuntu-latest + Test: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E101,W191 + - name: Test with pytest + run: | + pytest -v + build: + needs: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" - name: Install pypa/build run: >- python3 -m pip install build - --user - + --user - name: Build a binary wheel and a source tarball run: >- python3 -m build --sdist --wheel - --outdir dist/ + --outdir ~/dist/ . - - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Cache Builds + uses: actions/cache@v2 with: - password: ${{ secrets.TEST_PYPI_API }} - repository-url: https://test.pypi.org/legacy/ + path: ~/dist/ + key: ${{ runner.os }}-build + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Restore Builds + id: cache-builds + uses: actions/cache@v2 + with: + path: ~/dist/ + key: ${{ runner.os }}-build + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API }} + repository-url: https://test.pypi.org/legacy/ + packages-dir: ~/dist/ From de47c8f9835ea1ac0273c592824e0df9320f02c4 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 18:34:38 +0300 Subject: [PATCH 10/35] Fixed Github action ... I think ? --- .github/workflows/pypi-publish.yml | 4 ++-- .github/workflows/test-pypi-publish.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index d8273a3..4580f28 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -54,7 +54,7 @@ jobs: - name: Cache Builds uses: actions/cache@v2 with: - path: ~/dist/ + path: ~/dist key: ${{ runner.os }}-build restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- @@ -69,7 +69,7 @@ jobs: id: cache-builds uses: actions/cache@v2 with: - path: ~/dist/ + path: ~/dist key: ${{ runner.os }}-build - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 0ea7f93..340246c 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -54,7 +54,7 @@ jobs: - name: Cache Builds uses: actions/cache@v2 with: - path: ~/dist/ + path: ~/dist key: ${{ runner.os }}-build restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- @@ -69,7 +69,7 @@ jobs: id: cache-builds uses: actions/cache@v2 with: - path: ~/dist/ + path: ~/dist key: ${{ runner.os }}-build - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 From 0127a7543e55d631d5d8e0dc633deaaa9bd4af68 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 18:37:09 +0300 Subject: [PATCH 11/35] Last time I hope. --- .github/workflows/test-pypi-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 340246c..711c99c 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -76,6 +76,6 @@ jobs: with: password: ${{ secrets.TEST_PYPI_API }} repository-url: https://test.pypi.org/legacy/ - packages-dir: ~/dist/ + packages-dir: ~/dist From b9c4ae2aebb4436f9739d701567250fafcf15f2b Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 18:41:19 +0300 Subject: [PATCH 12/35] Fixed Github action ... I think ? x2 --- .github/workflows/test-pypi-publish.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 711c99c..0c663b5 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -71,11 +71,11 @@ jobs: with: path: ~/dist key: ${{ runner.os }}-build - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API }} - repository-url: https://test.pypi.org/legacy/ - packages-dir: ~/dist - + # - name: Publish distribution 📦 to Test PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # password: ${{ secrets.TEST_PYPI_API }} + # repository-url: https://test.pypi.org/legacy/ + # packages-dir: ~/dist + - run: ls -a ~/dist/ From effabc00c3faba9b9cde93caeec55761cf41437e Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 2 Jul 2023 18:44:18 +0300 Subject: [PATCH 13/35] AAAh --- .github/workflows/test-pypi-publish.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 0c663b5..2c59f2b 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -71,11 +71,11 @@ jobs: with: path: ~/dist key: ${{ runner.os }}-build - # - name: Publish distribution 📦 to Test PyPI - # uses: pypa/gh-action-pypi-publish@release/v1 - # with: - # password: ${{ secrets.TEST_PYPI_API }} - # repository-url: https://test.pypi.org/legacy/ - # packages-dir: ~/dist - - run: ls -a ~/dist/ + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + password: ${{ secrets.TEST_PYPI_API }} + repository-url: https://test.pypi.org/legacy/ + packages-dir: ~/dist From 5ae7da238b009533f8b380a38f8150a5a839aa06 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 00:33:18 +0300 Subject: [PATCH 14/35] Last Time --- .github/workflows/test-pypi-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 2c59f2b..4edf565 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -71,11 +71,11 @@ jobs: with: path: ~/dist key: ${{ runner.os }}-build + - run: ls -a ~/dist - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - verbose: true password: ${{ secrets.TEST_PYPI_API }} repository-url: https://test.pypi.org/legacy/ - packages-dir: ~/dist + packages_dir: ~/dist From e9a3943593266437b6edf3349b4d95a7340bfb28 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 00:50:04 +0300 Subject: [PATCH 15/35] Actions ... duh --- .github/workflows/test-pypi-publish.yml | 2 +- README.md | 2 +- artec/__main__.py | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 4edf565..fbe377c 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -77,5 +77,5 @@ jobs: with: password: ${{ secrets.TEST_PYPI_API }} repository-url: https://test.pypi.org/legacy/ - packages_dir: ~/dist + packages-dir: $HOME/dist diff --git a/README.md b/README.md index 06c9829..a011aa0 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Examples: ``` ## Version - 0.2.0-rc2 + 0.2.0-rc3 ## Contributing diff --git a/artec/__main__.py b/artec/__main__.py index 7d827d1..5549f64 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.0-rc2" +__version__ = "0.2.0-rc3" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/pyproject.toml b/pyproject.toml index 902f16d..5bbc10f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0-rc2" +version = "0.2.0-rc3" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From eb567058e5f6c86671f52d29839cb12b9e69b98b Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 00:57:52 +0300 Subject: [PATCH 16/35] Actions .. Once more --- .github/workflows/test-pypi-publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index fbe377c..b44e795 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -72,10 +72,11 @@ jobs: path: ~/dist key: ${{ runner.os }}-build - run: ls -a ~/dist + - run: pwd - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.TEST_PYPI_API }} repository-url: https://test.pypi.org/legacy/ - packages-dir: $HOME/dist + packages-dir: dist From 8da014bf6a3e28caed95be8ec089cdd2455a9231 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 01:10:14 +0300 Subject: [PATCH 17/35] Actions ... once more --- .github/workflows/test-pypi-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index b44e795..54394e2 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -52,7 +52,7 @@ jobs: --outdir ~/dist/ . - name: Cache Builds - uses: actions/cache@v2 + uses: actions/cache/save@v3 with: path: ~/dist key: ${{ runner.os }}-build @@ -67,7 +67,7 @@ jobs: steps: - name: Restore Builds id: cache-builds - uses: actions/cache@v2 + uses: actions/cache/restore@v3 with: path: ~/dist key: ${{ runner.os }}-build From 789a7564b65ae384f4ff3f478daf6ce5bb66f4b2 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 01:24:04 +0300 Subject: [PATCH 18/35] Last time --- .github/workflows/test-pypi-publish.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 54394e2..3304ab0 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -51,15 +51,11 @@ jobs: --wheel --outdir ~/dist/ . - - name: Cache Builds - uses: actions/cache/save@v3 + - name: Upload Builds + uses: actions/upload-artifact@v2 with: - path: ~/dist - key: ${{ runner.os }}-build - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + name: build + path: dist/ deploy: needs: build @@ -71,8 +67,9 @@ jobs: with: path: ~/dist key: ${{ runner.os }}-build - - run: ls -a ~/dist - - run: pwd + - uses: actions/download-artifact@v2 + with: + name: build - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: From 45e8ad66bd0b68cf92d187a851a014fa533551b2 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 01:29:48 +0300 Subject: [PATCH 19/35] I swear last time --- .github/workflows/test-pypi-publish.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index 3304ab0..a5c61bd 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -49,24 +49,21 @@ jobs: build --sdist --wheel - --outdir ~/dist/ + --outdir dist/ . - name: Upload Builds uses: actions/upload-artifact@v2 with: name: build - path: dist/ + path: | + dist/ + Artec.egg-info/ + if-no-files-found: error deploy: needs: build runs-on: ubuntu-latest steps: - - name: Restore Builds - id: cache-builds - uses: actions/cache/restore@v3 - with: - path: ~/dist - key: ${{ runner.os }}-build - uses: actions/download-artifact@v2 with: name: build From 63d82383cec2d77660f0966b0e90b39e6aa0f86b Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 3 Jul 2023 01:38:48 +0300 Subject: [PATCH 20/35] Finalized Github actions. --- .github/workflows/pypi-publish.yml | 25 ++++++++++--------------- .github/workflows/test-pypi-publish.yml | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 4580f28..cac50d5 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -49,32 +49,27 @@ jobs: build --sdist --wheel - --outdir ~/dist/ + --outdir dist/ . - - name: Cache Builds - uses: actions/cache@v2 + - name: Upload Builds + uses: actions/upload-artifact@v2 with: - path: ~/dist - key: ${{ runner.os }}-build - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + name: build + path: | + dist/ + Artec.egg-info/ + if-no-files-found: error deploy: needs: build runs-on: ubuntu-latest steps: - - name: Restore Builds - id: cache-builds - uses: actions/cache@v2 + - uses: actions/download-artifact@v2 with: - path: ~/dist - key: ${{ runner.os }}-build + name: build - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API }} - packages-dir: ~/dist/ diff --git a/.github/workflows/test-pypi-publish.yml b/.github/workflows/test-pypi-publish.yml index a5c61bd..09f07d8 100644 --- a/.github/workflows/test-pypi-publish.yml +++ b/.github/workflows/test-pypi-publish.yml @@ -72,5 +72,4 @@ jobs: with: password: ${{ secrets.TEST_PYPI_API }} repository-url: https://test.pypi.org/legacy/ - packages-dir: dist From 054558bf9df1c14591332f0f358371296b1d0ac0 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sat, 8 Jul 2023 17:10:00 +0300 Subject: [PATCH 21/35] 0.2.0-alpha --- .vscode/launch.json | 5 ++- .vscode/settings.json | 2 +- CHANGELOG.MD | 4 ++- README.md | 8 +++-- artec/__main__.py | 2 +- artec/argparser.py | 26 ++++++++------- artec/boiler.py | 2 +- artec/exceptions.py | 9 +++-- artec/templates.py | 77 ++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 4 ++- test/test_boiler.py | 25 +++++++++----- 11 files changed, 127 insertions(+), 37 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index bd17154..5301d8e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,10 @@ "request": "launch", "module": "artec", "args": [ - "-ls" + "-o", + "fake", + "-t", + "node.js" ], "justMyCode": true }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ecf267..3ad96d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "python.testing.unittestEnabled": true, - "python.testing.pytestEnabled": false, + "python.testing.pytestEnabled": true, "python.testing.unittestArgs": [ "-v", "-s", diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 0be3ec4..00b263e 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -30,9 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.0] - 2023-06-30 ### Added -- ```-t``` argument now allows Templates to be used +- ```-t``` argument now allows Templates to be used (Limited to Python & Node.Js) - Github actions can publish to Pypi & Test.Pypi +### Changed +- ## [0.1.4] - 2023-06-26 ### Changed diff --git a/README.md b/README.md index a011aa0..16e0f1b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ _It's a maintained version of PyBoiler_ Download from pip ```bash -pip install -i https://test.pypi.org/simple/ Artec==0.2.0rc1 +pip install -i https://test.pypi.org/simple/ Artec==0.2.0 ``` or Install manually @@ -45,19 +45,21 @@ options: Source JSON file containing structure to be created -t TEMPLATE, --template TEMPLATE Uses ready-made templates. + -ls, --list-template lists all ready-made templates. -v, --verbose Runs Artec in verbose mode. -V, --version Display current version of Artec Examples: artec -h artec -o dest - artec -o dest -t python + artec -o dest -t python artec -o dest -s structure.json artec -o dest -s structure.json -v + ``` ## Version - 0.2.0-rc3 + 0.2.0-alpha ## Contributing diff --git a/artec/__main__.py b/artec/__main__.py index 5549f64..59bfcd0 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.0-rc3" +__version__ = "0.2.0-alpha" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/artec/argparser.py b/artec/argparser.py index b070ae0..7f49980 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -3,25 +3,28 @@ """ from argparse import ArgumentParser, Namespace, RawTextHelpFormatter, _VersionAction from .templates import templates -import sys +import sys class list_templates(_VersionAction): - def __call__(self, parser: ArgumentParser, namespace, argument, options) -> None: + def __call__(self, parser: ArgumentParser, *args, **kwargs) -> None: formatter = parser._get_formatter() formatter.add_text( - "Available templates\n\n" + '\n'.join([f"> {key.title()}\t" for key,value in templates.items()]) + "Available templates\n\n" + + "\n".join([f"> {key.title()}\t" for key, value in templates.items()]) ) parser._print_message(formatter.format_help(), sys.stdout) parser.exit() + class Parser(ArgumentParser): def __init__(self, appVersion): self.appVersion = appVersion prog = "Artec" usage = "artec [OPTIONS] -o [DEST] " description = "Artec is a simple python 3 script to create a project template in a given directory." - epilog = """Examples:\n\tartec -o dest\n\tartec -o dest -s structure.json""" + epilog = "Examples:\n\tartec -h\n\tartec -o dest\ + \n\tartec -o dest -t python \n\tartec -o dest -s structure.json \n\tartec -o dest -s structure.json -v" super().__init__( prog, usage, description, epilog, formatter_class=RawTextHelpFormatter ) @@ -44,7 +47,6 @@ def setup(self): type=str, required=False, ) - self.add_argument( "-t", "--template", @@ -53,6 +55,14 @@ def setup(self): required=False, ) + self.add_argument( + "-ls", + "--list-template", + help="lists all ready-made templates.", + action=list_templates, + ) + + self.add_argument( "-v", "--verbose", @@ -70,12 +80,6 @@ def setup(self): version=f"{self.prog} {self.appVersion}", ) - self.add_argument( - "-ls", - "--list-template", - help="lists all ready-made templates.", - action=list_templates, - ) def main_args(appVersion) -> Namespace: parser = Parser(appVersion) diff --git a/artec/boiler.py b/artec/boiler.py index d9438f0..110c2c9 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -40,7 +40,7 @@ def _source(self, source) -> list[dict[str, str]]: except Exception as e: if not hasattr(e, "errno"): - NoSource() + NoSource(self.verbose) structure = templates["python"].format(self.target) return structure diff --git a/artec/exceptions.py b/artec/exceptions.py index 2aab305..1062a4b 100644 --- a/artec/exceptions.py +++ b/artec/exceptions.py @@ -8,9 +8,12 @@ def __init__(self, verbose: bool) -> None: print("> Reverting to default structure") -def NoSource(): - print("> No Source Provided") - print("> Using default structure") +class NoSource: + def __init__(self, verbose: bool) -> None: + if not verbose: + return + print("> No Source Provided") + print("> Using default structure") class NotValidJson(KeyError): diff --git a/artec/templates.py b/artec/templates.py index 73532aa..07f19ef 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -5,7 +5,8 @@ def format(self, name): _[i] = j.format(name) return self -PYTHON_PACKAGE = static_list( + +PYTHON = static_list( [ {"folder": "{}"}, {"file": "{}/__init__.py"}, @@ -19,8 +20,74 @@ def format(self, name): ] ) +NODE_JS = static_list( + [ + {"folder": "src"}, + {"folder": "src/api"}, + {"folder": "src/api/controllers"}, + {"folder": "src/api/controllers/user"}, + {"folder": "src/api/controllers/user/auth"}, + {"file": "src/api/controllers/user/auth/forgot-password.js"}, + {"file": "src/api/controllers/user/auth/login.js"}, + {"file": "src/api/controllers/user/auth/logout.js"}, + {"file": "src/api/controllers/user/auth/refresh-token.js"}, + {"file": "src/api/controllers/user/auth/register.js"}, + {"file": "src/api/controllers/user/auth/send-verification-code.js"}, + {"file": "src/api/controllers/user/auth/verify-email.js"}, + {"folder": "src/api/controllers/user/edit"}, + {"file": "src/api/controllers/user/edit/change-password.js"}, + {"file": "src/api/controllers/user/edit/edit-user.js"}, + {"file": "src/api/controllers/user/delete-user.js"}, + {"file": "src/api/controllers/user/get-user.js"}, + {"file": "src/api/controllers/user/index.js"}, + {"folder": "src/api/middlewares"}, + {"folder": "src/api/middlewares/auth"}, + {"file": "src/api/middlewares/auth/check-auth.js"}, + {"file": "src/api/middlewares/auth/check-authority.js"}, + {"file": "src/api/middlewares/image-upload.js"}, + {"file": "src/api/middlewares/index.js"}, + {"file": "src/api/middlewares/object-id-control.js"}, + {"file": "src/api/middlewares/rate-limiter.js"}, + {"folder": "src/api/routes"}, + {"file": "src/api/routes/index.js"}, + {"file": "src/api/routes/user.js"}, + {"folder": "src/api/validators"}, + {"file": "src/api/validators/index.js"}, + {"file": "src/api/validators/user.validator.js"}, + {"folder": "src/config"}, + {"file": "src/config/index.js"}, + {"folder": "src/loaders"}, + {"file": "src/loaders/index.js"}, + {"file": "src/loaders/express.js"}, + {"file": "src/loaders/mongoose.js"}, + {"folder": "src/models"}, + {"file": "src/models/index.js"}, + {"file": "src/models/log.js"}, + {"file": "src/models/token.js"}, + {"file": "src/models/user.js"}, + {"folder": "src/utils"}, + {"folder": "src/utils/helpers"}, + {"file": "src/utils/helpers/error-helper.js"}, + {"file": "src/utils/helpers/generate-random-code.js"}, + {"file": "src/utils/helpers/ip-helper.js"}, + {"file": "src/utils/helpers/jwt-token-helper.js"}, + {"file": "src/utils/helpers/local-text-helper.js"}, + {"folder": "src/utils/lang"}, + {"file": "src/utils/lang/en.json"}, + {"file": "src/utils/lang/get-text.json"}, + {"file": "src/utils/lang/tr.json"}, + {"file": "src/utils/index.js"}, + {"file": "src/utils/logger.js"}, + {"file": "src/utils/send-code-to-email.js"}, + {"file": "src/app.js"}, + {"file": ".env.sample"}, + {"file": "README.md"}, + {"file": "LICENSE"}, + {"file": "package.json"}, + {"file": "package-lock.json"}, + ] +) templates = { - "python": PYTHON_PACKAGE, - "node.js": None, - "web": None, -} \ No newline at end of file + "python": PYTHON, + "node.js": NODE_JS, +} diff --git a/pyproject.toml b/pyproject.toml index 5bbc10f..6bd7b9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0-rc3" +version = "0.2.0-alpha" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, @@ -19,6 +19,8 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Software Development :: Build Tools" ] +dependencies = [ +] [project.urls] "Homepage" = "https://github.com/HushmKun/Artec" diff --git a/test/test_boiler.py b/test/test_boiler.py index 0259c0e..9f4cc0f 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -1,28 +1,35 @@ import unittest import os import sys -from shutil import rmtree from artec import boiler +from shutil import rmtree -# Disable + +# Disable Print def blockPrint(): sys.stdout = open(os.devnull, 'w') -# Restore +# Restore Print def enablePrint(): sys.stdout.close() sys.stdout = sys.__stdout__ + class BoilerTest(unittest.TestCase): + def setUp(self): blockPrint() - self.boiler = boiler.boiler_builder(target="fake") + self.default_boiler = boiler.boiler_builder(target="default", verbose=True) + self.template_boiler = boiler.boiler_builder(target="template", template='node.js', verbose=True) def test_default_structure(self): - self.boiler.build() - enablePrint() + self.default_boiler.build() - def tearDown(self): - del self.boiler - rmtree("fake") + def test_template_structure(self): + self.template_boiler.build() + enablePrint() + + def __del__(self): + rmtree("default") + rmtree("template") \ No newline at end of file From 08851b9df7a5088f9edc45f341894e4f8d806817 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sat, 8 Jul 2023 17:19:42 +0300 Subject: [PATCH 22/35] 0.2.0 --- CHANGELOG.MD | 8 +++++--- README.md | 2 +- artec/__main__.py | 2 +- pyproject.toml | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 00b263e..f623bab 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -28,13 +28,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unused normalize.css file - Identical links assigned in each translation file --> -## [0.2.0] - 2023-06-30 +## [0.2.0] - 2023-07-08 ### Added - ```-t``` argument now allows Templates to be used (Limited to Python & Node.Js) - Github actions can publish to Pypi & Test.Pypi - +- More improvements to error handling and detection. ### Changed -- +- We can format the project name in the json by using an empty curly brackets {} +- Tests now is *little bit* better than before. + ## [0.1.4] - 2023-06-26 ### Changed diff --git a/README.md b/README.md index 16e0f1b..8f3eac8 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Examples: ``` ## Version - 0.2.0-alpha + 0.2.0 ## Contributing diff --git a/artec/__main__.py b/artec/__main__.py index 59bfcd0..c664d38 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.0-alpha" +__version__ = "0.2.0" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/pyproject.toml b/pyproject.toml index 6bd7b9a..406d800 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0-alpha" +version = "0.2.0" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From bca028dbfd23a6e9b296d72d1410934fa029cd45 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 16 Jul 2023 04:20:10 +0300 Subject: [PATCH 23/35] 0.2.1 --- CHANGELOG.MD | 14 +++++ CONTRIBUTING.md | 24 ++++----- LEARN.md | 48 ++++++++++------- README.md | 125 +++++++++++++++++++++++++++++++++++++------- artec/__main__.py | 2 +- artec/argparser.py | 28 +++++----- artec/boiler.py | 11 ++-- artec/exceptions.py | 8 +-- artec/templates.py | 2 +- pyproject.toml | 2 +- res/Artec.png | Bin 0 -> 168112 bytes 11 files changed, 190 insertions(+), 74 deletions(-) create mode 100644 res/Artec.png diff --git a/CHANGELOG.MD b/CHANGELOG.MD index f623bab..6d4d877 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -28,6 +28,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unused normalize.css file - Identical links assigned in each translation file --> +## [0.2.1] - 2023-07-16 + +### Fixed + +- ```-s``` & ```-t``` conflict. +- ('files' or 'file') and ('folders' or 'folder') can now be used in the Json structure. + +### Changed + +- Various functions to increase performance. +- Unified exceptions interface. +- Added extra data to [LEARN.md](LEARN.md) +- Added extra data to [CONTRIBUTING.md](CONTRIBUTING.md) + ## [0.2.0] - 2023-07-08 ### Added - ```-t``` argument now allows Templates to be used (Limited to Python & Node.Js) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6170a60..d9d16f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,11 +5,11 @@ First off, thanks for taking the time to contribute! ❤️ All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 -> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: -> - Star the project +> And if you like Artec, but just don't have time to contribute, that's fine. There are other easy ways to support us and show your appreciation, which we would also be very happy about: +> - Star Artec > - Tweet about it -> - Refer this project in your project's readme -> - Mention the project at local meetups and tell your friends/colleagues +> - Refer Artec in your project's readme +> - Mention Artec at local meetups and tell your friends/colleagues ## Table of Contents @@ -30,13 +30,13 @@ All types of contributions are encouraged and valued. See the [Table of Contents > If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/HushmKun/Artec/blob/master/README.md). -Before you ask a question, it is best to search for existing [Issues](https://github.com/HushmKun/Artec//issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. +Before you ask a question, it is best to search for existing [Issues](https://github.com/HushmKun/Artec/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. If you then still feel the need to ask a question and need clarification, we recommend the following: - Open an [Issue](https://github.com/HushmKun/Artec//issues/new). - Provide as much context as you can about what you're running into. -- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. +- Provide project and platform versions (pip, poetry, etc), depending on what seems relevant. We will then take care of the issue as soon as possible. @@ -44,7 +44,7 @@ We will then take care of the issue as soon as possible. ## I Want To Contribute > ### Legal Notice -> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. +> When contributing to Artec, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under Artec's license. ### Reporting Bugs @@ -59,14 +59,12 @@ A good bug report shouldn't leave others needing to chase you up for more inform - Collect information about the bug: - Stack trace (Traceback) - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) - - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. + - Version of the interpreter, package manager depending on what seems relevant. - Possibly your input and the output - Can you reliably reproduce the issue? And can you also reproduce it with older versions? #### How Do I Submit a Good Bug Report? -> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>. - We use GitHub issues to track bugs and errors. If you run into an issue with the project: - Open an [Issue](https://github.com/HushmKun/Artec//issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) @@ -78,7 +76,7 @@ Once it's filed: - We will label the issue accordingly. - I will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, I will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. -- If we are able to reproduce the issue, it will be marked `needs-fix`, and the issue will be left to be [implemented by someone](#your-first-code-contribution). +- If we are able to reproduce the issue, it will be marked `needs-fix`, and the issue will be left to be [implemented by someone](#your-first-code-contribution) (_mostly me_). @@ -91,12 +89,12 @@ This section guides you through submitting an enhancement suggestion for Artec, - Make sure that you are using the latest version. - Read the [documentation](https://github.com/HushmKun/Artec/blob/master/README.md) carefully and find out if the functionality is already covered, maybe by an individual configuration. -- Perform a [search](https://github.com/HushmKun/Artec//issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Perform a [search](https://github.com/HushmKun/Artec/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. #### How Do I Submit a Good Enhancement Suggestion? -Enhancement suggestions are tracked as [GitHub issues](https://github.com/HushmKun/Artec//issues). +Enhancement suggestions are tracked as [GitHub issues](https://github.com/HushmKun/Artec/issues). - Use a **clear and descriptive title** for the issue to identify the suggestion. - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. diff --git a/LEARN.md b/LEARN.md index 3300450..4e97b62 100644 --- a/LEARN.md +++ b/LEARN.md @@ -59,48 +59,60 @@ The first thing I focused on was parsing arguments to the program, for that I ma The main focus in that wrapper was to make it easy to modify the args whether to add or remove, Theoretically the parser should be "elegant" enough to be recycled in any other project with no hassle, In the end there's a driver function that's easy to use called (main_args). +Couple of things I had to focus on that messed up my code a little bit: +* Custom Action to list my templates. +* Mutually exclusive arguments + ### Boiler_builder Not the easiest part but not the hardest also, It had to accept arguments from our [parser](#parser) then decide some things : -1. Is the source a JSON file ? - * Yes : It's ***probably*** the structure. - * No : Use default structure. -2. Should I start the TUI ? - * Yes : **Not Implemented yet.** - * No : **Not Implemented yet.** +1. Did you use a template ? + * Yes : + * Is the template valid ? + 1. Yes : Use it. + 2. No : Use default Python structure. + + * No : + * Is Source provided ? + 1. Yes : Is valid ? + 1. Yes : Apply it. + 2. No : Use default Python structure. + 2. No : Use default Python structure. + +Also, a major key take for me was Exceptions, We kind of ignore raising them or explaining why this exceptions happens to the user and it just leaves them lost in the application. The build method is fairly simple, It just iterates over the structure dictionary and creates each folder and/or file. ### Tests -The tests was an uncharted waters for me, So trying unittest was probably a good idea. You should have a look [here](tests/) not the best but meh. +The tests was an uncharted waters for me, So trying unittest was probably a good idea. You should have a look [here](tests/) not the best but good enough for me. ### Packaging The hardest part also the one I learned in the most, let's start with some key takes : -1. ```__init__.py``` is a very **important** reserved key file. -2. Your project structure should look similar to what I did here (*Also The ```__main__.py``` part isn't 100% perfect* -): - +1. ```__init__.py``` is a very **important** reserved key file, It's main usage comes when a package provide an API. (_It isn't necessary here, except in tests_) +2. Your project structure should look similar to what I did here (*Also The ```__main__.py``` part isn't 100% perfect*): +3. I figured that keeping your entry points in ```__main__.py``` makes it easier for people to figure out where is the start of your project, how does it work, etc. ``` . ├── artec + │   ├── __main__.py │   ├── argparser.py │   ├── boiler.py - │   ├── __init__.py - │   ├── __main__.py - │   └── tui.py + │   ├── exceptions.py + │   └── templates.py ├── CONTRIBUTING.md ├── LEARN.md ├── LICENSE ├── pyproject.toml ├── README.md ├── setup.py - └── tests - ├── boiler_test.py + └── test ├── __init__.py + ├── boiler_test.py └── parser_test.py ``` -3. **Be Aware of relative and absolute imports.** They are a mess in packaging if your structure isn't "clean" enough. -4. [This](https://packaging.python.org/en/latest/overview/) is a very good reference. +4. **Be Aware of relative and absolute imports.** They are a mess in packaging if your structure isn't "clean" enough. +5. **Automate your build & deploy**, It sucks having to manage the versions and manage uploading them in order each time. +5. [This](https://packaging.python.org/en/latest/overview/) is a very good reference. ## Finishing up diff --git a/README.md b/README.md index 8f3eac8..3bd2da3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ +![Logo](res/Artec.png) # Artec - -Artec is a simple python 3 script to create a project template in a given directory.
+A python application that creates a configurable python project template in a given directory..
_It's a maintained version of PyBoiler_ ## Installation @@ -8,7 +8,7 @@ _It's a maintained version of PyBoiler_ Download from pip ```bash -pip install -i https://test.pypi.org/simple/ Artec==0.2.0 +$ pip install -i https://test.pypi.org/simple/ Artec==0.2.1 ``` or Install manually @@ -22,14 +22,17 @@ Create a JSON file to match the folder structure you desire $ vim structure.json # Paste the below into your file and modify as you desire -[{'folder': 'source'}, -{'file': 'source/file1.txt'}, # Nested file -{'folder': 'sublime'}, -{'file': 'sublime/file2.txt'}, -{'folder': 'docs'}, -{'file': 'docs/file3.txt'}, -{'file': 'docs/file4.txt'}, -{'file': 'README.md'}] +[ + {"folder": "{}"}, # Use '{}' to be replaced with project name. + {"file": "{}/__init__.py"}, + {"folder": "test"}, + {"file": "test/__init__.py"}, + {"file": "README.md"}, + {"file": "LICENSE"}, + {"file": "setup.py"}, + {"file": "setup.cfg"}, + {"file": "pyproject.toml"}, +] ``` How to execute ``` @@ -55,24 +58,110 @@ Examples: artec -o dest -t python artec -o dest -s structure.json artec -o dest -s structure.json -v +``` +## Templates +### Python +Project Named Artec +``` +artec +├── artec +│ └── __main__.py +├── test +│ └── __init__.py +├── LICENSE +├── pyproject.toml +├── README.md +├── setup.cfg +└── setup.py + + +2 directories, 7 files +``` +### Node.Js +Project Named Node +``` +Node +├── LICENSE +├── package.json +├── package-lock.json +├── README.md +└── src + ├── api + │ ├── controllers + │ │ └── user + │ │ ├── auth + │ │ │ ├── forgot-password.js + │ │ │ ├── login.js + │ │ │ ├── logout.js + │ │ │ ├── refresh-token.js + │ │ │ ├── register.js + │ │ │ ├── send-verification-code.js + │ │ │ └── verify-email.js + │ │ ├── delete-user.js + │ │ ├── edit + │ │ │ ├── change-password.js + │ │ │ └── edit-user.js + │ │ ├── get-user.js + │ │ └── index.js + │ ├── middlewares + │ │ ├── auth + │ │ │ ├── check-auth.js + │ │ │ └── check-authority.js + │ │ ├── image-upload.js + │ │ ├── index.js + │ │ ├── object-id-control.js + │ │ └── rate-limiter.js + │ ├── routes + │ │ ├── index.js + │ │ └── user.js + │ └── validators + │ ├── index.js + │ └── user.validator.js + ├── app.js + ├── config + │ └── index.js + ├── loaders + │ ├── express.js + │ ├── index.js + │ └── mongoose.js + ├── models + │ ├── index.js + │ ├── log.js + │ ├── token.js + │ └── user.js + └── utils + ├── helpers + │ ├── error-helper.js + │ ├── generate-random-code.js + │ ├── ip-helper.js + │ ├── jwt-token-helper.js + │ └── local-text-helper.js + ├── index.js + ├── lang + │ ├── en.json + │ ├── get-text.json + │ └── tr.json + ├── logger.js + └── send-code-to-email.js + +17 directories, 46 files ``` ## Version - 0.2.0 + 0.2.1 ## Contributing +Please refer to [Here](CONTRIBUTING.md) for contributing. +Any help that can contribute to the templates will be really appreciated. * Big Thanks to [Link-](https://github.com/Link-) for jump starting the project. * Thanks for [Narayandas Akhil Achary](https://github.com/0018akhil) for various fixes & features. -Any help that can contribute to the templates will be really appreciated. - -Pull requests are welcome. For major changes, please open an issue first -to discuss what you would like to change. - -Please make sure to update tests as appropriate. +## Learning +Since this project is intended as a learning project, It helps me figure out what is the best practices of X, How to use Y, etc... +If you come here to learn, Read [this](LEARN.md), I will be glad if it helped you learn something new. ## License [GNU GPLv3.0](https://choosealicense.com/licenses/gpl-3.0/) diff --git a/artec/__main__.py b/artec/__main__.py index c664d38..13028f6 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.0" +__version__ = "0.2.1" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/artec/argparser.py b/artec/argparser.py index 7f49980..c1918f2 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -11,7 +11,7 @@ def __call__(self, parser: ArgumentParser, *args, **kwargs) -> None: formatter = parser._get_formatter() formatter.add_text( "Available templates\n\n" - + "\n".join([f"> {key.title()}\t" for key, value in templates.items()]) + + "\n".join([f"> {key.title()}\t" for key in templates.keys()]) ) parser._print_message(formatter.format_help(), sys.stdout) parser.exit() @@ -30,16 +30,9 @@ def __init__(self, appVersion): ) def setup(self): - self.add_argument( - "-o", - "--output-directory", - dest="target", - help="Target output path where the structure will be created", - type=str, - required=True, - ) - - self.add_argument( + group = self.add_mutually_exclusive_group() + + group.add_argument( "-s", "--source-file", dest="source", @@ -47,7 +40,8 @@ def setup(self): type=str, required=False, ) - self.add_argument( + + group.add_argument( "-t", "--template", dest="template", @@ -55,6 +49,15 @@ def setup(self): required=False, ) + self.add_argument( + "-o", + "--output-directory", + dest="target", + help="Target output path where the structure will be created", + type=str, + required=True, + ) + self.add_argument( "-ls", "--list-template", @@ -80,7 +83,6 @@ def setup(self): version=f"{self.prog} {self.appVersion}", ) - def main_args(appVersion) -> Namespace: parser = Parser(appVersion) parser.setup() diff --git a/artec/boiler.py b/artec/boiler.py index 110c2c9..34119f2 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -13,8 +13,7 @@ class boiler_builder: def __init__(self, source=None, target=None, verbose=False, template=None) -> None: self.verbose = verbose self.target = target - self.template = template - if self.template is None: + if template is None: self.structure = self._source(source) else: self.structure = self._source_temp(template.lower()) @@ -32,6 +31,8 @@ def _source_temp(self, template) -> list[dict[str, str]]: def _source(self, source) -> list[dict[str, str]]: try: + if source is None : + raise NoSource(self.verbose) if os.path.isfile(source) and source.endswith(".json"): with open(source, "rt", encoding="utf-8") as file_data: structure = static_list(json.load(file_data)).format(self.target) @@ -39,8 +40,6 @@ def _source(self, source) -> list[dict[str, str]]: raise NotJsonFile(self.verbose) except Exception as e: - if not hasattr(e, "errno"): - NoSource(self.verbose) structure = templates["python"].format(self.target) return structure @@ -52,9 +51,9 @@ def build(self): for _type, name in entry.items(): try: joined = Path(os.path.join(self.target, name)) - if _type == "folder": + if "folder" in _type : self._make_folder(joined) - elif _type == "file": + elif "file" in _type : self._make_file(joined) else: raise NotValidJson(self.verbose) diff --git a/artec/exceptions.py b/artec/exceptions.py index 1062a4b..bf92fd0 100644 --- a/artec/exceptions.py +++ b/artec/exceptions.py @@ -8,8 +8,10 @@ def __init__(self, verbose: bool) -> None: print("> Reverting to default structure") -class NoSource: +class NoSource(ValueError): def __init__(self, verbose: bool) -> None: + super().__init__("> No Source Provided") + self.errno = 102 if not verbose: return print("> No Source Provided") @@ -19,7 +21,7 @@ def __init__(self, verbose: bool) -> None: class NotValidJson(KeyError): def __init__(self, verbose: bool) -> None: super().__init__("> Provided Json file format is incorrect") - self.errno = 102 + self.errno = 103 if not verbose: return print("> Provided Json file format is incorrect") @@ -28,7 +30,7 @@ def __init__(self, verbose: bool) -> None: class InValidTemplate(KeyError): def __init__(self, verbose: bool) -> None: super().__init__("> Provided Template argument is invalid") - self.errno = 103 + self.errno = 104 if not verbose: return print("> Provided Template argument is invalid") diff --git a/artec/templates.py b/artec/templates.py index 07f19ef..7f9a54d 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -16,7 +16,7 @@ def format(self, name): {"file": "LICENSE"}, {"file": "setup.py"}, {"file": "setup.cfg"}, - {"file": "pyproject.toml"}, + {"file": "pyproject.toml"} ] ) diff --git a/pyproject.toml b/pyproject.toml index 406d800..658f27e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Artec" -version = "0.2.0" +version = "0.2.1" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, diff --git a/res/Artec.png b/res/Artec.png new file mode 100644 index 0000000000000000000000000000000000000000..74b08ba79dd6d19152ccd117c3e86dee41f8cda5 GIT binary patch literal 168112 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 fNM?hO3Xc4I^3N!?Wb`kkkQL{ck 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

    3G5!Q1Uqn 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 V{MCj1%ljjK`n&tzY7d2_{R=v>)$jlS literal 0 HcmV?d00001 From b5ef114bfcb5492cf12c5a7ad77481f1bd05f172 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 16 Jul 2023 23:17:59 +0300 Subject: [PATCH 24/35] fixed 0.2.1 --- {res => img}/Artec.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename {res => img}/Artec.png (100%) diff --git a/res/Artec.png b/img/Artec.png similarity index 100% rename from res/Artec.png rename to img/Artec.png From 7f9dbf942cd98555dc7fff086f6eead9ccc075ee Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 16 Jul 2023 23:22:29 +0300 Subject: [PATCH 25/35] fix 0.2.1 --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 658f27e..24763a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,13 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" +[tool.setuptools] +include-package-data = false + +[tool.setuptools.packages.find] +include = ["artec*"] +exclude = ["img *"] + [project] name = "Artec" version = "0.2.1" From c47b7afe099e9ced7b76563d27d6cbe48af818cf Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 16 Jul 2023 23:22:51 +0300 Subject: [PATCH 26/35] last fix 0.2.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 24763a8..512ff94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ include-package-data = false [tool.setuptools.packages.find] include = ["artec*"] -exclude = ["img *"] +exclude = ["img*"] [project] name = "Artec" From c913e443da13c37a017c9717200876d7c5587eb0 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Tue, 18 Jul 2023 21:59:17 +0300 Subject: [PATCH 27/35] Enhanced Tests --- .gitignore | 3 ++ .vscode/launch.json | 18 -------- .vscode/settings.json | 17 -------- .vscode/tasks.json | 17 -------- artec/argparser.py | 14 ++++--- artec/boiler.py | 10 ++--- artec/exceptions.py | 16 +++++++ artec/templates.py | 2 +- test/resources/structure.json | 11 +++++ test/resources/structure.txt | 11 +++++ test/test_boiler.py | 79 ++++++++++++++++++++--------------- test/test_exceptions.py | 28 +++++++++++++ test/test_parser.py | 67 ++++++++++++++++++++--------- 13 files changed, 177 insertions(+), 116 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json create mode 100644 test/resources/structure.json create mode 100644 test/resources/structure.txt create mode 100644 test/test_exceptions.py diff --git a/.gitignore b/.gitignore index f9e57f5..7c10ff8 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ target/ # pyenv .python-version venv/ + +# vscodium +*vscode/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5301d8e..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Module", - "type": "python", - "request": "launch", - "module": "artec", - "args": [ - "-o", - "fake", - "-t", - "node.js" - ], - "justMyCode": true - }, - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3ad96d9..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "python.testing.unittestEnabled": true, - "python.testing.pytestEnabled": true, - "python.testing.unittestArgs": [ - "-v", - "-s", - "./test", - "-p", - "test_*.py" - ], - "cSpell.words": [ - "Achary", - "Akhil", - "argparser", - "Narayandas" - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 96bfbab..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Lint using flake8", - "type": "shell", - "command": "flake8 artec --ignore=E101,W191" - }, - { - "label": "Format using black", - "type": "shell", - "command": "black artec" - }, - ] -} \ No newline at end of file diff --git a/artec/argparser.py b/artec/argparser.py index c1918f2..78e86ab 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -1,5 +1,5 @@ """ - Parser class is a wrapper for easy access of argparse module + Parser class is a wrapper for easy access of argparse module v2.1.0 """ from argparse import ArgumentParser, Namespace, RawTextHelpFormatter, _VersionAction from .templates import templates @@ -22,16 +22,18 @@ def __init__(self, appVersion): self.appVersion = appVersion prog = "Artec" usage = "artec [OPTIONS] -o [DEST] " - description = "Artec is a simple python 3 script to create a project template in a given directory." + description = "Artec is a simple python 3 script to create a\ + project template in a given directory." epilog = "Examples:\n\tartec -h\n\tartec -o dest\ - \n\tartec -o dest -t python \n\tartec -o dest -s structure.json \n\tartec -o dest -s structure.json -v" + \n\tartec -o dest -t python \n\tartec -o dest -s structure.json\ + \n\tartec -o dest -s structure.json -v" super().__init__( prog, usage, description, epilog, formatter_class=RawTextHelpFormatter ) def setup(self): group = self.add_mutually_exclusive_group() - + group.add_argument( "-s", "--source-file", @@ -65,7 +67,6 @@ def setup(self): action=list_templates, ) - self.add_argument( "-v", "--verbose", @@ -83,6 +84,7 @@ def setup(self): version=f"{self.prog} {self.appVersion}", ) + def main_args(appVersion) -> Namespace: parser = Parser(appVersion) parser.setup() @@ -90,4 +92,4 @@ def main_args(appVersion) -> Namespace: if __name__ == "__main__": - pass + pass \ No newline at end of file diff --git a/artec/boiler.py b/artec/boiler.py index 34119f2..af4e427 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -31,7 +31,7 @@ def _source_temp(self, template) -> list[dict[str, str]]: def _source(self, source) -> list[dict[str, str]]: try: - if source is None : + if source is None: raise NoSource(self.verbose) if os.path.isfile(source) and source.endswith(".json"): with open(source, "rt", encoding="utf-8") as file_data: @@ -39,9 +39,9 @@ def _source(self, source) -> list[dict[str, str]]: else: raise NotJsonFile(self.verbose) - except Exception as e: - + except Exception: structure = templates["python"].format(self.target) + return structure def build(self): @@ -51,9 +51,9 @@ def build(self): for _type, name in entry.items(): try: joined = Path(os.path.join(self.target, name)) - if "folder" in _type : + if "folder" in _type: self._make_folder(joined) - elif "file" in _type : + elif "file" in _type: self._make_file(joined) else: raise NotValidJson(self.verbose) diff --git a/artec/exceptions.py b/artec/exceptions.py index bf92fd0..0504218 100644 --- a/artec/exceptions.py +++ b/artec/exceptions.py @@ -7,6 +7,9 @@ def __init__(self, verbose: bool) -> None: print("> Provided Source isn't valid JSON file") print("> Reverting to default structure") + def __str__(self): + return "> Provided Source isn't valid JSON file" + class NoSource(ValueError): def __init__(self, verbose: bool) -> None: @@ -17,6 +20,9 @@ def __init__(self, verbose: bool) -> None: print("> No Source Provided") print("> Using default structure") + def __str__(self): + return "> No Source Provided" + class NotValidJson(KeyError): def __init__(self, verbose: bool) -> None: @@ -26,6 +32,9 @@ def __init__(self, verbose: bool) -> None: return print("> Provided Json file format is incorrect") + def __str__(self): + return "> Provided Json file format is incorrect" + class InValidTemplate(KeyError): def __init__(self, verbose: bool) -> None: @@ -34,3 +43,10 @@ def __init__(self, verbose: bool) -> None: if not verbose: return print("> Provided Template argument is invalid") + + def __str__(self): + return "> Provided Template argument is invalid" + + +if __name__ == "__main__": + pass diff --git a/artec/templates.py b/artec/templates.py index 7f9a54d..07f19ef 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -16,7 +16,7 @@ def format(self, name): {"file": "LICENSE"}, {"file": "setup.py"}, {"file": "setup.cfg"}, - {"file": "pyproject.toml"} + {"file": "pyproject.toml"}, ] ) diff --git a/test/resources/structure.json b/test/resources/structure.json new file mode 100644 index 0000000..e50ee6e --- /dev/null +++ b/test/resources/structure.json @@ -0,0 +1,11 @@ +[ + {"folder": "{}"}, + {"file": "{}/__init__.py"}, + {"folder": "test"}, + {"file": "test/__init__.py"}, + {"file": "README.md"}, + {"file": "CHANGELOG.md"}, + {"file": "LICENSE"}, + {"file": "setup.py"}, + {"file": "pyproject.toml"} +] \ No newline at end of file diff --git a/test/resources/structure.txt b/test/resources/structure.txt new file mode 100644 index 0000000..e50ee6e --- /dev/null +++ b/test/resources/structure.txt @@ -0,0 +1,11 @@ +[ + {"folder": "{}"}, + {"file": "{}/__init__.py"}, + {"folder": "test"}, + {"file": "test/__init__.py"}, + {"file": "README.md"}, + {"file": "CHANGELOG.md"}, + {"file": "LICENSE"}, + {"file": "setup.py"}, + {"file": "pyproject.toml"} +] \ No newline at end of file diff --git a/test/test_boiler.py b/test/test_boiler.py index 9f4cc0f..4dc2ccb 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -1,35 +1,48 @@ import unittest -import os -import sys -from artec import boiler +import json from shutil import rmtree - - - -# Disable Print -def blockPrint(): - sys.stdout = open(os.devnull, 'w') - -# Restore Print -def enablePrint(): - sys.stdout.close() - sys.stdout = sys.__stdout__ - - -class BoilerTest(unittest.TestCase): - - def setUp(self): - blockPrint() - self.default_boiler = boiler.boiler_builder(target="default", verbose=True) - self.template_boiler = boiler.boiler_builder(target="template", template='node.js', verbose=True) - - def test_default_structure(self): - self.default_boiler.build() - - def test_template_structure(self): - self.template_boiler.build() - enablePrint() - - def __del__(self): - rmtree("default") - rmtree("template") \ No newline at end of file +from pathlib import Path +from artec.boiler import boiler_builder +from artec.templates import static_list, templates + +VAILD_JSON = "test\\resources\\structure.json" +INVAILD_JSON = "test\\resources\\structure.txt" + +class TestBoilerBuilder(unittest.TestCase): + def test_init(self): + builder = boiler_builder(source="structure.json", target="target", verbose=False) + self.assertEqual(builder.target, "target") + self.assertEqual(builder.verbose, False) + self.assertIsInstance(builder.structure, list) + + def test_source_temp(self): + builder = boiler_builder(template="python", target="target", verbose=False) + self.assertEqual(builder.structure, templates["python"].format("target")) + + def test_source(self): + with open(VAILD_JSON, "rt", encoding="utf-8") as file_data: + structure = static_list(json.load(file_data)).format("target") + builder = boiler_builder(source=VAILD_JSON, target="target", verbose=False) + self.assertEqual(builder.structure, structure) + + def test_build(self): + builder = boiler_builder(source=VAILD_JSON, target="target", verbose=False) + builder.build() + self.assertTrue(Path("target/test").is_dir()) + self.assertTrue(Path("target/README.md").is_file()) + rmtree('target') + + + def test_exception_handling(self): + builder = boiler_builder(source=INVAILD_JSON, target="target", verbose=False) + self.assertEqual(builder.structure, templates["python"].format("target")) + + builder = boiler_builder(template="invalid", target="target", verbose=False) + self.assertEqual(builder.structure, templates["python"].format("target")) + + builder = boiler_builder(target="target", verbose=False) + self.assertEqual(builder.structure, templates["python"].format("target")) + + +if __name__ == "__main__": + pass diff --git a/test/test_exceptions.py b/test/test_exceptions.py new file mode 100644 index 0000000..2e9474c --- /dev/null +++ b/test/test_exceptions.py @@ -0,0 +1,28 @@ +import unittest +from artec.exceptions import NotJsonFile, NoSource, NotValidJson, InValidTemplate + + +class TestExceptions(unittest.TestCase): + def test_not_json_file(self): + exception = NotJsonFile(False) + self.assertEqual(exception.errno, 101) + self.assertEqual(str(exception), "> Provided Source isn't valid JSON file") + + def test_no_source(self): + exception = NoSource(False) + self.assertEqual(exception.errno, 102) + self.assertEqual(str(exception), "> No Source Provided") + + def test_not_valid_json(self): + exception = NotValidJson(False) + self.assertEqual(exception.errno, 103) + self.assertEqual(str(exception), "> Provided Json file format is incorrect") + + def test_invalid_template(self): + exception = InValidTemplate(False) + self.assertEqual(exception.errno, 104) + self.assertEqual(str(exception), "> Provided Template argument is invalid") + + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/test/test_parser.py b/test/test_parser.py index 6c9a663..467c1e6 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -1,27 +1,56 @@ import unittest -from artec import argparser +from artec.argparser import Parser, Namespace +VAILD_JSON = "test\\resources\\structure.json" +INVAILD_JSON = "test\\resources\\structure.txt" -class ParserTest(unittest.TestCase): - def setUp(self): - self.parser = argparser.Parser('appVersion') - self.parser.setup() - def test_arg_target(self): - parsed = self.parser.parse_args(["-o", "/dir/r"]) - self.assertEqual(parsed.target, "/dir/r") +class TestParser(unittest.TestCase): + def test_init(self): + parser = Parser("1.0.0") + self.assertEqual(parser.prog, "Artec") + self.assertEqual(parser.appVersion, "1.0.0") - def test_arg_source(self): - parsed = self.parser.parse_args(["-o", "/dir/r", "-s", "/dir/s"]) - self.assertEqual(parsed.source, "/dir/s") + def test_setup(self): + parser = Parser("1.0.0") + parser.setup() + self.assertIn("-o", parser.__dict__['_option_string_actions']) + self.assertIn("-ls", parser.__dict__['_option_string_actions']) + self.assertIn("-v", parser.__dict__['_option_string_actions']) + self.assertIn("-V", parser.__dict__['_option_string_actions']) + self.assertIn("-s", parser.__dict__['_option_string_actions']) + self.assertIn("-t", parser.__dict__['_option_string_actions']) - def test_arg_verbose(self): - parsed = self.parser.parse_args(["-o", "/dir/r", "-v"]) - self.assertTrue(parsed.verbose) + def test_main_args(self): + parser = Parser("1.0.0") + parser.setup() + args = parser.parse_args(['-t', 'target', '-o', 'fake', '-v']) + self.assertIsInstance(args, Namespace) + self.assertIn("target", args.__dict__) + self.assertIn("verbose", args.__dict__) - def test_arg_template(self): - parsed = self.parser.parse_args(["-o", "/dir/r", "-t", "python"]) - self.assertEqual(parsed.template, "python") + def test_parse_without_args(self): + parser = Parser("1.0.0") + with self.assertRaises(SystemExit): + parser.parse_args() - def tearDown(self) -> None: - del self.parser + def test_parse_with_source(self): + parser = Parser("1.0.0") + parser.setup() + args = parser.parse_args(["-s", VAILD_JSON, "-o", "target"]) + self.assertIn("source", args.__dict__) + self.assertEqual(args.source, VAILD_JSON) + self.assertIn("target", args.__dict__) + self.assertEqual(args.target, "target") + + def test_parse_with_template(self): + parser = Parser("1.0.0") + parser.setup() + args = parser.parse_args(["-t", "python", "-o", "target"]) + self.assertIn("template", args.__dict__) + self.assertEqual(args.template, "python") + self.assertIn("target", args.__dict__) + self.assertEqual(args.target, "target") + +if __name__ == "__main__": + pass \ No newline at end of file From aca7a6d7eaa715893b0864c724fd7fed70a607fc Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Tue, 18 Jul 2023 22:06:42 +0300 Subject: [PATCH 28/35] Fixed boiler test --- test/resources/structure.json | 2 +- test/test_boiler.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/resources/structure.json b/test/resources/structure.json index e50ee6e..7569ac8 100644 --- a/test/resources/structure.json +++ b/test/resources/structure.json @@ -4,8 +4,8 @@ {"folder": "test"}, {"file": "test/__init__.py"}, {"file": "README.md"}, - {"file": "CHANGELOG.md"}, {"file": "LICENSE"}, {"file": "setup.py"}, + {"file": "setup.cfg"}, {"file": "pyproject.toml"} ] \ No newline at end of file diff --git a/test/test_boiler.py b/test/test_boiler.py index 4dc2ccb..ed721df 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -5,8 +5,8 @@ from artec.boiler import boiler_builder from artec.templates import static_list, templates -VAILD_JSON = "test\\resources\\structure.json" -INVAILD_JSON = "test\\resources\\structure.txt" +VAILD_JSON = Path("./test/resources/structure.json") +INVAILD_JSON = Path("./test/resources/structure.txt") class TestBoilerBuilder(unittest.TestCase): def test_init(self): From 24dbb49cb5ecfd9e8bf7c068318c1101d5b15bdb Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 31 Jul 2023 15:08:53 +0300 Subject: [PATCH 29/35] 0.2.2 --- README.md | 2 +- artec/templates.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bd2da3..db3b893 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](res/Artec.png) +![Logo](img/Artec.png) # Artec A python application that creates a configurable python project template in a given directory..
    _It's a maintained version of PyBoiler_ diff --git a/artec/templates.py b/artec/templates.py index 07f19ef..de7caaa 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -20,6 +20,39 @@ def format(self, name): ] ) +FLASK = static_list( + [ + {"folder": "{}"}, + {"file": "{}/__init__.py"}, + {"file": "{}/db.py"}, + {"file": "{}/schema.py"}, + {"file": "{}/auth.py"}, + {"file": "{}/blog.py"}, + {"folder": "{}/templates"}, + {"file": "{}/templates/base.html"}, + {"folder": "{}/templates/auth/"}, + {"file": "{}/templates/auth/login.html"}, + {"file": "{}/templates/auth/register.html"}, + {"folder": "{}/blog"}, + {"file": "{}/blog/create.html"}, + {"file": "{}/blog/index.html"}, + {"file": "{}/blog/update.html"}, + {"folder": "{}/static"}, + {"file": "{}/static/style.css"}, + {"folder": "test"}, + {"file": "test/__init__.py"}, + {"file": "test/conftest.py"}, + {"file": "test/data.sql"}, + {"file": "test/test_db.py"}, + {"file": "test/test_auth.py"}, + {"file": "test/test_blog.py"}, + {"file": "README.md"}, + {"file": "LICENSE"}, + {"file": "setup.py"}, + {"file": "pyproject.toml"}, + ] + ) + NODE_JS = static_list( [ {"folder": "src"}, From ba59496c68b10b42328b46993bafda3a6dc47ed9 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Mon, 31 Jul 2023 15:11:35 +0300 Subject: [PATCH 30/35] 0.2.2 --- README.md | 4 ++-- artec/__main__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db3b893..feeede8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ _It's a maintained version of PyBoiler_ Download from pip ```bash -$ pip install -i https://test.pypi.org/simple/ Artec==0.2.1 +$ pip install -i https://test.pypi.org/simple/ Artec==0.2.2 ``` or Install manually @@ -149,7 +149,7 @@ Node ``` ## Version - 0.2.1 + 0.2.2 ## Contributing Please refer to [Here](CONTRIBUTING.md) for contributing. diff --git a/artec/__main__.py b/artec/__main__.py index 13028f6..ae80cf0 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -2,7 +2,7 @@ from .boiler import boiler_builder __app_name__ = "Artec" -__version__ = "0.2.1" +__version__ = "0.2.2" __desc__ = "Creates a configurable python project \ template in a given directory." diff --git a/pyproject.toml b/pyproject.toml index 512ff94..c53ed76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ exclude = ["img*"] [project] name = "Artec" -version = "0.2.1" +version = "0.2.2" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, From 2c397c6bb59a2fe6ebb7b3b3cffd50ae6e695948 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 6 Aug 2023 12:25:22 +0300 Subject: [PATCH 31/35] 0.3.0 --- .gitignore | 6 +++- CHANGELOG.MD | 13 ++++++++ LEARN.md | 23 +++++++++---- README.md | 71 ++++++++++++++++++++++++++++++++++------- artec/__init__.py | 22 +++++++++++++ artec/__main__.py | 22 ++++++++++--- artec/argparser.py | 34 ++++++++++++++++---- artec/boiler.py | 52 ++++++++++++++++++++---------- artec/exceptions.py | 46 ++++++++++++++------------ artec/repo.py | 22 +++++++++++++ artec/templates.py | 18 ++++++++--- pyproject.toml | 16 +++++++++- test/test_boiler.py | 63 +++++++++++++++++++++++++++--------- test/test_exceptions.py | 35 ++++++++++++++------ test/test_parser.py | 19 ++++++----- 15 files changed, 357 insertions(+), 105 deletions(-) create mode 100644 artec/__init__.py create mode 100644 artec/repo.py diff --git a/.gitignore b/.gitignore index 7c10ff8..0e34524 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ htmlcov/ .coverage .coverage.* .cache +.pytest_cache/ nosetests.xml coverage.xml *,cover @@ -64,4 +65,7 @@ target/ venv/ # vscodium -*vscode/ \ No newline at end of file +*vscode/ + +# JetBrains +.idea/ \ No newline at end of file diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 6d4d877..105c547 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -28,6 +28,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unused normalize.css file - Identical links assigned in each translation file --> +## [0.3.0] - 2023-08-05 + +### Added + +- Logging to text file & stdout on `-v` flag. +- Varying Log levels. +- Initialize a git Repository & Commits the structure. +- New template : _Flask_ + +### Fixed + +- Improved error reporting. + ## [0.2.1] - 2023-07-16 ### Fixed diff --git a/LEARN.md b/LEARN.md index 4e97b62..9a3ad35 100644 --- a/LEARN.md +++ b/LEARN.md @@ -20,7 +20,7 @@ ## Introduction Hello 👋🏿!
    -I am Hussein Mukhtar, a junior student (3rd year) in Computer Science in Suez University, And I'd like to walk you through ```Artec```. +I am Hussein Mukhtar, a Senior Computer Science student in Suez University, And I'd like to walk you through ```Artec```. ## How it all started? Three years ago, I started learning Python and It was a good "hobby", Till you want to share your works with people. @@ -78,8 +78,13 @@ Not the easiest part but not the hardest also, It had to accept arguments from o 1. Yes : Apply it. 2. No : Use default Python structure. 2. No : Use default Python structure. +2. Did user choose to initialize a git repository ? + * Yes : initialize it. + * No : ignore. -Also, a major key take for me was Exceptions, We kind of ignore raising them or explaining why this exceptions happens to the user and it just leaves them lost in the application. +~~Also, a major key take for me was Exceptions, I kind of ignored raising them or explaining why this exceptions happens to the user and it just leaves them lost in the application.~~ + +Although it's a rough implementation, Now I raise specific custom-made exceptions in essential places to help the user and also log the process correctly. The build method is fairly simple, It just iterates over the structure dictionary and creates each folder and/or file. @@ -88,9 +93,8 @@ The tests was an uncharted waters for me, So trying unittest was probably a good ### Packaging The hardest part also the one I learned in the most, let's start with some key takes : -1. ```__init__.py``` is a very **important** reserved key file, It's main usage comes when a package provide an API. (_It isn't necessary here, except in tests_) -2. Your project structure should look similar to what I did here (*Also The ```__main__.py``` part isn't 100% perfect*): -3. I figured that keeping your entry points in ```__main__.py``` makes it easier for people to figure out where is the start of your project, how does it work, etc. +1. ```__init__.py``` is a very **important** reserved key file, It's main usage comes when a package provide an API (_It isn't necessary here, except in tests_), I used it as a connector of the logger module _Don't think it's correct but meh_. +2. The project structure might look similar to what I did here (*Also The ```__main__.py``` part isn't 100% perfect*): ``` . ├── artec @@ -98,6 +102,7 @@ The hardest part also the one I learned in the most, let's start with some key t │   ├── argparser.py │   ├── boiler.py │   ├── exceptions.py + │   ├── repo.py │   └── templates.py ├── CONTRIBUTING.md ├── LEARN.md @@ -107,9 +112,13 @@ The hardest part also the one I learned in the most, let's start with some key t ├── setup.py └── test ├── __init__.py - ├── boiler_test.py - └── parser_test.py + ├── test_boiler.py + ├── test_exceptions.py + └── test_parser.py ``` +* P.S.: It's not necessary to make the same, some try different structures and it works for them so don't limit yourself to mine. + +3. I figured that keeping your entry points in ```__main__.py``` makes it easier for people to figure out where is the start of your project, how does it work, etc. 4. **Be Aware of relative and absolute imports.** They are a mess in packaging if your structure isn't "clean" enough. 5. **Automate your build & deploy**, It sucks having to manage the versions and manage uploading them in order each time. 5. [This](https://packaging.python.org/en/latest/overview/) is a very good reference. diff --git a/README.md b/README.md index feeede8..795cdac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Logo](img/Artec.png) # Artec -A python application that creates a configurable python project template in a given directory..
    +A python application that creates a configurable python project template in a given directory.
    _It's a maintained version of PyBoiler_ ## Installation @@ -8,13 +8,14 @@ _It's a maintained version of PyBoiler_ Download from pip ```bash -$ pip install -i https://test.pypi.org/simple/ Artec==0.2.2 +$ pip install Artec ``` or Install manually ```bash +$ git clone https://github.com/HushmKun/Artec $ cd Artec -$ pip install . +$ pip install -U . ``` ## Usage Create a JSON file to match the folder structure you desire @@ -34,22 +35,24 @@ $ vim structure.json {"file": "pyproject.toml"}, ] ``` - How to execute +How to execute ``` +$ artec -h usage: artec [OPTIONS] -o [DEST] -Artec is a simple python 3 script to create a project template in a given directory. +Artec is a python application that creates a configurable python project template in a given directory. options: -h, --help show this help message and exit - -o TARGET, --output-directory TARGET - Target output path where the structure will be created -s SOURCE, --source-file SOURCE Source JSON file containing structure to be created -t TEMPLATE, --template TEMPLATE Uses ready-made templates. + -o TARGET, --output-directory TARGET + Target output path where the structure will be created -ls, --list-template lists all ready-made templates. -v, --verbose Runs Artec in verbose mode. + -g, --git-init Creates a git Repo for the project. -V, --version Display current version of Artec Examples: @@ -61,7 +64,9 @@ Examples: ``` ## Templates -### Python +
    + Python + Project Named Artec ``` artec @@ -75,10 +80,52 @@ artec ├── setup.cfg └── setup.py - 2 directories, 7 files ``` -### Node.Js +
    + +
    + Flask + +Project Named flaskr +``` +flaskr +. +├── flaskr +│ ├── auth.py +│ ├── blog +│ │ ├── create.html +│ │ ├── index.html +│ │ └── update.html +│ ├── blog.py +│ ├── db.py +│ ├── __init__.py +│ ├── schema.py +│ ├── static +│ │ └── style.css +│ └── templates +│ ├── auth +│ │ ├── login.html +│ │ └── register.html +│ └── base.html +├── LICENSE +├── pyproject.toml +├── README.md +├── setup.py +└── test + ├── conftest.py + ├── data.sql + ├── __init__.py + ├── test_auth.py + ├── test_blog.py + └── test_db.py + +7 directories, 22 files +``` +
    +
    + Node.Js + Project Named Node ``` Node @@ -147,9 +194,11 @@ Node 17 directories, 46 files ``` +
    + ## Version - 0.2.2 + 0.3.0 ## Contributing Please refer to [Here](CONTRIBUTING.md) for contributing. diff --git a/artec/__init__.py b/artec/__init__.py new file mode 100644 index 0000000..737ee95 --- /dev/null +++ b/artec/__init__.py @@ -0,0 +1,22 @@ +import logging + +# Root logger +logger = logging.getLogger("Artec") +logger.setLevel(logging.INFO) + +# Handlers (sub-loggers) +file_handler = logging.FileHandler("log.txt", delay=True, mode="w") +console_handler = logging.StreamHandler() + +# Formatters +file_handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +) +console_handler.setFormatter(logging.Formatter("> %(message)s.")) + +# Levels +file_handler.setLevel(logging.INFO) + +# Add the Handlers +logger.addHandler(file_handler) +logger.addHandler(console_handler) diff --git a/artec/__main__.py b/artec/__main__.py index ae80cf0..23a2f7e 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -1,16 +1,28 @@ -from .argparser import main_args -from .boiler import boiler_builder +from . import argparser as parser +from . import boiler +from . import logging, console_handler __app_name__ = "Artec" -__version__ = "0.2.2" +__version__ = "0.3.0" __desc__ = "Creates a configurable python project \ template in a given directory." +ERROR_LOGING = { + 0: logging.FATAL, + 1: logging.ERROR, + 2: logging.WARN, + 3: logging.INFO, +} + def main(): - args = main_args(__version__) + args = parser.main_args(__version__) + + console_handler.setLevel(ERROR_LOGING[args.verbose]) - builder = boiler_builder(args.source, args.target, args.verbose, args.template) + builder = boiler.boiler_builder( + args.source, args.target, args.template, args.git + ) builder.build() diff --git a/artec/argparser.py b/artec/argparser.py index 78e86ab..69b1842 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -1,7 +1,12 @@ """ Parser class is a wrapper for easy access of argparse module v2.1.0 """ -from argparse import ArgumentParser, Namespace, RawTextHelpFormatter, _VersionAction +from argparse import ( + ArgumentParser, + Namespace, + RawTextHelpFormatter, + _VersionAction, +) from .templates import templates import sys @@ -10,8 +15,10 @@ class list_templates(_VersionAction): def __call__(self, parser: ArgumentParser, *args, **kwargs) -> None: formatter = parser._get_formatter() formatter.add_text( - "Available templates\n\n" - + "\n".join([f"> {key.title()}\t" for key in templates.keys()]) + "".join( + "Available templates\n\n", + "\n".join([f"> {key.title()}\t" for key in templates.keys()]), + ) ) parser._print_message(formatter.format_help(), sys.stdout) parser.exit() @@ -22,13 +29,16 @@ def __init__(self, appVersion): self.appVersion = appVersion prog = "Artec" usage = "artec [OPTIONS] -o [DEST] " - description = "Artec is a simple python 3 script to create a\ - project template in a given directory." + description = "Artec is a python application that creates a configurable python project template in a given directory." epilog = "Examples:\n\tartec -h\n\tartec -o dest\ \n\tartec -o dest -t python \n\tartec -o dest -s structure.json\ \n\tartec -o dest -s structure.json -v" super().__init__( - prog, usage, description, epilog, formatter_class=RawTextHelpFormatter + prog, + usage, + description, + epilog, + formatter_class=RawTextHelpFormatter, ) def setup(self): @@ -72,6 +82,16 @@ def setup(self): "--verbose", dest="verbose", help="Runs Artec in verbose mode.", + action="count", + default=0, + required=False, + ) + + self.add_argument( + "-g", + "--git-init", + dest="git", + help="Creates a git Repo for the project.", action="store_true", required=False, ) @@ -92,4 +112,4 @@ def main_args(appVersion) -> Namespace: if __name__ == "__main__": - pass \ No newline at end of file + pass diff --git a/artec/boiler.py b/artec/boiler.py index af4e427..78e61ad 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -5,14 +5,23 @@ import json import os from pathlib import Path -from .exceptions import NotJsonFile, NotValidJson, NoSource, InValidTemplate -from .templates import templates, static_list - +from . import exceptions as ex +from . import templates as temps +from . import repo +from . import logger class boiler_builder: - def __init__(self, source=None, target=None, verbose=False, template=None) -> None: - self.verbose = verbose + def __init__( + self, + source: str = None, + target: str = None, + template: str = None, + git=False, + ) -> None: self.target = target + self.git = git + self.home_dir = os.path.abspath(os.path.curdir) + if template is None: self.structure = self._source(source) else: @@ -20,27 +29,29 @@ def __init__(self, source=None, target=None, verbose=False, template=None) -> No def _source_temp(self, template) -> list[dict[str, str]]: try: - if template in templates: - structure = templates[template].format(self.target) + if template in temps.templates: + structure = temps.templates[template].format(self.target) else: - raise InValidTemplate(self.verbose) + raise ex.InValidTemplate() - except InValidTemplate: - structure = templates["python"].format(self.target) + except ex.InValidTemplate: + structure = temps.templates["python"].format(self.target) return structure def _source(self, source) -> list[dict[str, str]]: try: if source is None: - raise NoSource(self.verbose) + raise ex.NoSource() if os.path.isfile(source) and source.endswith(".json"): with open(source, "rt", encoding="utf-8") as file_data: - structure = static_list(json.load(file_data)).format(self.target) + structure = temps.static_list(json.load(file_data)).format( + self.target + ) else: - raise NotJsonFile(self.verbose) + raise ex.NotJsonFile() except Exception: - structure = templates["python"].format(self.target) + structure = temps.templates["python"].format(self.target) return structure @@ -56,10 +67,19 @@ def build(self): elif "file" in _type: self._make_file(joined) else: - raise NotValidJson(self.verbose) + raise ex.NotValidJson() print("Created: %s" % joined) except Exception: - exit("> Fatal error - exiting...") + pass + print() + + if self.git: + try: + Repo = repo.repository(os.path.join(self.home_dir, self.target), self.target) + Repo.add() + + except Exception as e : + ex.GitError(e) def _make_file(self, path): """Create an empty file in a given directory""" diff --git a/artec/exceptions.py b/artec/exceptions.py index 0504218..5001d02 100644 --- a/artec/exceptions.py +++ b/artec/exceptions.py @@ -1,52 +1,58 @@ +from . import logger + + class NotJsonFile(FileNotFoundError): - def __init__(self, verbose: bool) -> None: + def __init__(self) -> None: super().__init__("> Provided Source isn't valid JSON file") self.errno = 101 - if not verbose: - return - print("> Provided Source isn't valid JSON file") - print("> Reverting to default structure") + logger.warning("Provided Source isn't valid JSON file") + logger.info("Reverting to default structure") def __str__(self): return "> Provided Source isn't valid JSON file" class NoSource(ValueError): - def __init__(self, verbose: bool) -> None: + def __init__(self) -> None: super().__init__("> No Source Provided") self.errno = 102 - if not verbose: - return - print("> No Source Provided") - print("> Using default structure") + logger.warning("No Source Provided") + logger.info("Using default structure") def __str__(self): return "> No Source Provided" class NotValidJson(KeyError): - def __init__(self, verbose: bool) -> None: + def __init__(self) -> None: super().__init__("> Provided Json file format is incorrect") self.errno = 103 - if not verbose: - return - print("> Provided Json file format is incorrect") + print(self) + logger.warning("Provided Json file format is incorrect") + logger.error("Artec shut down unexpectedly.") def __str__(self): return "> Provided Json file format is incorrect" class InValidTemplate(KeyError): - def __init__(self, verbose: bool) -> None: + def __init__(self) -> None: super().__init__("> Provided Template argument is invalid") self.errno = 104 - if not verbose: - return - print("> Provided Template argument is invalid") + logger.warning("Provided Template argument is invalid") + logger.error("Artec shut down unexpectedly.") def __str__(self): return "> Provided Template argument is invalid" -if __name__ == "__main__": - pass +class GitError(OSError): + def __init__(self , e = None) -> None: + super().__init__("> Git has encountered a problem") + self.errno = 105 + logger.warning("Git has encountered a problem") + logger.error(e) + logger.error("Artec shut down unexpectedly.") + + def __str__(self): + return "> Git has encountered a problem" diff --git a/artec/repo.py b/artec/repo.py new file mode 100644 index 0000000..8e9639f --- /dev/null +++ b/artec/repo.py @@ -0,0 +1,22 @@ +from git import Repo +from . import logger +from os import listdir + + + +class repository(Repo): + + def __init__(self, path, name) -> Repo: + self.name = name + self.repo_path = path + self.repo = Repo.init(path) + logger.info("Creating Git Repository") + + def add(self): + files = listdir(self.repo_path) + files.remove('.git') + self.repo.index.add(files) + logger.info("Adding the skeleton structure") + self.repo.index.commit(f"{self.name} : initial commit.") + logger.info("Committing the files") + diff --git a/artec/templates.py b/artec/templates.py index de7caaa..a2bdae1 100644 --- a/artec/templates.py +++ b/artec/templates.py @@ -1,9 +1,15 @@ +from . import exceptions as ex + + class static_list(list): def format(self, name): - for _ in self: - for i, j in _.items(): - _[i] = j.format(name) - return self + try: + for _ in self: + for i, j in _.items(): + _[i] = j.format(name) + return self + except AttributeError: + raise ex.NotValidJson() PYTHON = static_list( @@ -51,7 +57,7 @@ def format(self, name): {"file": "setup.py"}, {"file": "pyproject.toml"}, ] - ) +) NODE_JS = static_list( [ @@ -120,7 +126,9 @@ def format(self, name): {"file": "package-lock.json"}, ] ) + templates = { "python": PYTHON, "node.js": NODE_JS, + "flask": FLASK, } diff --git a/pyproject.toml b/pyproject.toml index c53ed76..d7062da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ exclude = ["img*"] [project] name = "Artec" -version = "0.2.2" +version = "0.3.0" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, @@ -27,8 +27,22 @@ classifiers = [ "Topic :: Software Development :: Build Tools" ] dependencies = [ + "GitPython" ] +[project.license] +file = "LICENSE" + +[project.optional-dependencies] +dev = [ + "black", + "flake8", +] +test = [ + "pytest", +] + + [project.urls] "Homepage" = "https://github.com/HushmKun/Artec" "Bug Tracker" = "https://github.com/HushmKun/Artec/issues" diff --git a/test/test_boiler.py b/test/test_boiler.py index ed721df..c0c58e7 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -1,6 +1,8 @@ import unittest -import json +import json from shutil import rmtree +from os import remove +import git from pathlib import Path from artec.boiler import boiler_builder from artec.templates import static_list, templates @@ -8,40 +10,71 @@ VAILD_JSON = Path("./test/resources/structure.json") INVAILD_JSON = Path("./test/resources/structure.txt") + class TestBoilerBuilder(unittest.TestCase): def test_init(self): - builder = boiler_builder(source="structure.json", target="target", verbose=False) + builder = boiler_builder( + source="structure.json", target="target" + ) self.assertEqual(builder.target, "target") - self.assertEqual(builder.verbose, False) self.assertIsInstance(builder.structure, list) def test_source_temp(self): - builder = boiler_builder(template="python", target="target", verbose=False) - self.assertEqual(builder.structure, templates["python"].format("target")) + builder = boiler_builder( + template="python", target="target" + ) + self.assertEqual( + builder.structure, templates["python"].format("target") + ) def test_source(self): with open(VAILD_JSON, "rt", encoding="utf-8") as file_data: structure = static_list(json.load(file_data)).format("target") - builder = boiler_builder(source=VAILD_JSON, target="target", verbose=False) + builder = boiler_builder( + source=VAILD_JSON, target="target" + ) self.assertEqual(builder.structure, structure) def test_build(self): - builder = boiler_builder(source=VAILD_JSON, target="target", verbose=False) + builder = boiler_builder( + source=VAILD_JSON, target="target" + ) builder.build() self.assertTrue(Path("target/test").is_dir()) self.assertTrue(Path("target/README.md").is_file()) - rmtree('target') + rmtree("target") + + def test_git(self): + builder = boiler_builder( + template=VAILD_JSON, target="target", git=True + ) + builder.build() + self.assertTrue(Path("target/test").is_dir()) + self.assertTrue(Path("target/README.md").is_file()) + # self.assertTrue() + git. + rmtree("target") def test_exception_handling(self): - builder = boiler_builder(source=INVAILD_JSON, target="target", verbose=False) - self.assertEqual(builder.structure, templates["python"].format("target")) + builder = boiler_builder( + source=INVAILD_JSON, target="target" + ) + self.assertEqual( + builder.structure, templates["python"].format("target") + ) - builder = boiler_builder(template="invalid", target="target", verbose=False) - self.assertEqual(builder.structure, templates["python"].format("target")) - - builder = boiler_builder(target="target", verbose=False) - self.assertEqual(builder.structure, templates["python"].format("target")) + builder = boiler_builder( + template="invalid", target="target" + ) + self.assertEqual( + builder.structure, templates["python"].format("target") + ) + + builder = boiler_builder(target="target") + self.assertEqual( + builder.structure, templates["python"].format("target") + ) if __name__ == "__main__": diff --git a/test/test_exceptions.py b/test/test_exceptions.py index 2e9474c..e6499ab 100644 --- a/test/test_exceptions.py +++ b/test/test_exceptions.py @@ -1,28 +1,45 @@ import unittest -from artec.exceptions import NotJsonFile, NoSource, NotValidJson, InValidTemplate +from artec.exceptions import ( + NotJsonFile, + NoSource, + NotValidJson, + InValidTemplate, + GitError, +) class TestExceptions(unittest.TestCase): def test_not_json_file(self): - exception = NotJsonFile(False) + exception = NotJsonFile() self.assertEqual(exception.errno, 101) - self.assertEqual(str(exception), "> Provided Source isn't valid JSON file") + self.assertEqual( + str(exception), "> Provided Source isn't valid JSON file" + ) def test_no_source(self): - exception = NoSource(False) + exception = NoSource() self.assertEqual(exception.errno, 102) self.assertEqual(str(exception), "> No Source Provided") def test_not_valid_json(self): - exception = NotValidJson(False) + exception = NotValidJson() self.assertEqual(exception.errno, 103) - self.assertEqual(str(exception), "> Provided Json file format is incorrect") + self.assertEqual( + str(exception), "> Provided Json file format is incorrect" + ) def test_invalid_template(self): - exception = InValidTemplate(False) + exception = InValidTemplate() self.assertEqual(exception.errno, 104) - self.assertEqual(str(exception), "> Provided Template argument is invalid") + self.assertEqual( + str(exception), "> Provided Template argument is invalid" + ) + + def test_git_error(self): + exception = GitError() + self.assertEqual(exception.errno, 105) + self.assertEqual(str(exception), "> Git has encountered a problem") if __name__ == "__main__": - pass \ No newline at end of file + pass diff --git a/test/test_parser.py b/test/test_parser.py index 467c1e6..fc4bd95 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -14,17 +14,19 @@ def test_init(self): def test_setup(self): parser = Parser("1.0.0") parser.setup() - self.assertIn("-o", parser.__dict__['_option_string_actions']) - self.assertIn("-ls", parser.__dict__['_option_string_actions']) - self.assertIn("-v", parser.__dict__['_option_string_actions']) - self.assertIn("-V", parser.__dict__['_option_string_actions']) - self.assertIn("-s", parser.__dict__['_option_string_actions']) - self.assertIn("-t", parser.__dict__['_option_string_actions']) + self.assertIn("-h", parser.__dict__["_option_string_actions"]) + self.assertIn("-s", parser.__dict__["_option_string_actions"]) + self.assertIn("-t", parser.__dict__["_option_string_actions"]) + self.assertIn("-o", parser.__dict__["_option_string_actions"]) + self.assertIn("-ls", parser.__dict__["_option_string_actions"]) + self.assertIn("-v", parser.__dict__["_option_string_actions"]) + self.assertIn("-g", parser.__dict__["_option_string_actions"]) + self.assertIn("-V", parser.__dict__["_option_string_actions"]) def test_main_args(self): parser = Parser("1.0.0") parser.setup() - args = parser.parse_args(['-t', 'target', '-o', 'fake', '-v']) + args = parser.parse_args(["-t", "target", "-o", "fake", "-v"]) self.assertIsInstance(args, Namespace) self.assertIn("target", args.__dict__) self.assertIn("verbose", args.__dict__) @@ -52,5 +54,6 @@ def test_parse_with_template(self): self.assertIn("target", args.__dict__) self.assertEqual(args.target, "target") + if __name__ == "__main__": - pass \ No newline at end of file + pass From 8e1d5f446219f6b7b9d18903002c7e576add9a94 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 6 Aug 2023 13:23:59 +0300 Subject: [PATCH 32/35] 0.3.0 --- test/__init__.py | 9 +++++++++ test/test_boiler.py | 12 ++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test/__init__.py b/test/__init__.py index 700b9d0..bd62150 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,4 +1,13 @@ import sys import os +from shutil import rmtree +import unittest sys.path.insert(0, os.path.abspath("..")) + + +def cleanUp(): + os.remove('log.txt') + rmtree("target") + +unittest.addModuleCleanup(cleanUp) \ No newline at end of file diff --git a/test/test_boiler.py b/test/test_boiler.py index c0c58e7..8e9cfd6 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -1,7 +1,5 @@ import unittest import json -from shutil import rmtree -from os import remove import git from pathlib import Path from artec.boiler import boiler_builder @@ -42,18 +40,15 @@ def test_build(self): builder.build() self.assertTrue(Path("target/test").is_dir()) self.assertTrue(Path("target/README.md").is_file()) - rmtree("target") def test_git(self): builder = boiler_builder( - template=VAILD_JSON, target="target", git=True + template="python", target="target", git=True ) builder.build() self.assertTrue(Path("target/test").is_dir()) self.assertTrue(Path("target/README.md").is_file()) - # self.assertTrue() - git. - rmtree("target") + self.assertIsInstance(git.Repo("target"), git.Repo) def test_exception_handling(self): @@ -75,7 +70,8 @@ def test_exception_handling(self): self.assertEqual( builder.structure, templates["python"].format("target") ) - + + if __name__ == "__main__": pass From cf148d2fdd9723daa59e8dae563f89cf52a990fa Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 6 Aug 2023 13:26:43 +0300 Subject: [PATCH 33/35] 0.3.0 --- CHANGELOG.MD | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 105c547..685bcbd 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Varying Log levels. - Initialize a git Repository & Commits the structure. - New template : _Flask_ +- Requirements file. ### Fixed diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..64b1ada --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +GitPython From ca534c277af351e975d85566f5aa0074da164440 Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 14 Apr 2024 18:37:15 +0200 Subject: [PATCH 34/35] some light refactoring & adding new template --- CHANGELOG.MD | 13 ++++ README.md | 2 +- artec/__main__.py | 5 +- artec/argparser.py | 26 +++++-- artec/boiler.py | 37 +++++----- artec/temp/__init__.py | 43 +++++++++++ artec/temp/data_science.json | 38 ++++++++++ artec/temp/flask.json | 34 +++++++++ artec/temp/nodejs.json | 69 +++++++++++++++++ artec/temp/python.json | 14 ++++ artec/templates.py | 134 ---------------------------------- pyproject.toml | 3 +- test/resources/structure.json | 25 ++++--- test/test_boiler.py | 14 ++-- 14 files changed, 274 insertions(+), 183 deletions(-) create mode 100644 artec/temp/__init__.py create mode 100644 artec/temp/data_science.json create mode 100644 artec/temp/flask.json create mode 100644 artec/temp/nodejs.json create mode 100644 artec/temp/python.json delete mode 100644 artec/templates.py diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 685bcbd..12710b5 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -28,6 +28,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unused normalize.css file - Identical links assigned in each translation file --> +## [0.3.1] - 2024-04-14 + +### Added +- Easier way to add templates. +- New templates : Data Science + +### Fixed +- Error on listing templates. + +### Changed +- Refactoring to introduce the TUI (interactive mode) + + ## [0.3.0] - 2023-08-05 ### Added diff --git a/README.md b/README.md index 795cdac..bf71f45 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ Node ## Version - 0.3.0 + 0.3.1 ## Contributing Please refer to [Here](CONTRIBUTING.md) for contributing. diff --git a/artec/__main__.py b/artec/__main__.py index 23a2f7e..f36396c 100644 --- a/artec/__main__.py +++ b/artec/__main__.py @@ -1,9 +1,10 @@ from . import argparser as parser from . import boiler from . import logging, console_handler +from .temp import * __app_name__ = "Artec" -__version__ = "0.3.0" +__version__ = "0.3.1" __desc__ = "Creates a configurable python project \ template in a given directory." @@ -19,7 +20,6 @@ def main(): args = parser.main_args(__version__) console_handler.setLevel(ERROR_LOGING[args.verbose]) - builder = boiler.boiler_builder( args.source, args.target, args.template, args.git ) @@ -29,3 +29,4 @@ def main(): if __name__ == "__main__": main() + diff --git a/artec/argparser.py b/artec/argparser.py index 69b1842..eb33625 100644 --- a/artec/argparser.py +++ b/artec/argparser.py @@ -7,7 +7,7 @@ RawTextHelpFormatter, _VersionAction, ) -from .templates import templates +from .temp import * import sys @@ -15,10 +15,7 @@ class list_templates(_VersionAction): def __call__(self, parser: ArgumentParser, *args, **kwargs) -> None: formatter = parser._get_formatter() formatter.add_text( - "".join( - "Available templates\n\n", - "\n".join([f"> {key.title()}\t" for key in templates.keys()]), - ) + list_available_templates() ) parser._print_message(formatter.format_help(), sys.stdout) parser.exit() @@ -67,7 +64,14 @@ def setup(self): dest="target", help="Target output path where the structure will be created", type=str, - required=True, + ) + + self.add_argument( + "-i", + "--interactuve", + dest="interactive", + help="Runs artec in interactive mode.", + action="store_true", ) self.add_argument( @@ -108,7 +112,15 @@ def setup(self): def main_args(appVersion) -> Namespace: parser = Parser(appVersion) parser.setup() - return parser.parse_args() + args = parser.parse_args() + + if args.interactive: + print("Running Artec in interactive mode ....") + else: + if not args.target: + parser.error("The '-o' argument is required when not in interactive mode.") + + return args if __name__ == "__main__": diff --git a/artec/boiler.py b/artec/boiler.py index 78e61ad..d5062f1 100644 --- a/artec/boiler.py +++ b/artec/boiler.py @@ -6,10 +6,11 @@ import os from pathlib import Path from . import exceptions as ex -from . import templates as temps +from .temp import * from . import repo from . import logger + class boiler_builder: def __init__( self, @@ -29,13 +30,13 @@ def __init__( def _source_temp(self, template) -> list[dict[str, str]]: try: - if template in temps.templates: - structure = temps.templates[template].format(self.target) + if template in templates: + structure = format_project_structure(templates[template], self.target) else: raise ex.InValidTemplate() except ex.InValidTemplate: - structure = temps.templates["python"].format(self.target) + structure = format_project_structure(templates["python"], self.target) return structure def _source(self, source) -> list[dict[str, str]]: @@ -44,33 +45,30 @@ def _source(self, source) -> list[dict[str, str]]: raise ex.NoSource() if os.path.isfile(source) and source.endswith(".json"): with open(source, "rt", encoding="utf-8") as file_data: - structure = temps.static_list(json.load(file_data)).format( + structure = format_project_structure(json.load(file_data), self.target ) else: raise ex.NotJsonFile() except Exception: - structure = temps.templates["python"].format(self.target) + structure = format_project_structure(templates["python"], self.target) return structure def build(self): print("> Creating folder structure: {}\n".format(self.target)) - for entry in self.structure: - for _type, name in entry.items(): - try: - joined = Path(os.path.join(self.target, name)) - if "folder" in _type: - self._make_folder(joined) - elif "file" in _type: - self._make_file(joined) - else: - raise ex.NotValidJson() - print("Created: %s" % joined) - except Exception: - pass + for folder in self.structure['folders']: + folder_path = os.path.join(self.target, folder) + self._make_folder(folder_path) + print("Created: {}".format(folder_path)) + + for file_path in self.structure['files']: + file_path = os.path.join(self.target, file_path) + self._make_file(file_path) + print("Created: {}".format(file_path)) + print() if self.git: @@ -83,7 +81,6 @@ def build(self): def _make_file(self, path): """Create an empty file in a given directory""" - path.parent.mkdir(parents=True, exist_ok=True) open(path, "a").close() def _make_folder(self, path): diff --git a/artec/temp/__init__.py b/artec/temp/__init__.py new file mode 100644 index 0000000..1cf3700 --- /dev/null +++ b/artec/temp/__init__.py @@ -0,0 +1,43 @@ +from os import listdir, path +from .. import exceptions as ex +from json import load + +DIR = path.dirname(path.abspath(__file__)) + + +def list_available_templates() -> str : + all_templates = "Available templates: \n\n" + all_templates += "\n".join( + [ + f"> {key.title()}\t" for key in templates.keys() + ] + ) + return all_templates + +def format_project_structure(template, project_name): + formatted_structure = {k: [] for k in template.keys()} + try: + for folder in template['folders']: + formatted_structure['folders'].append(folder.format(project_name)) + + for file_path in template['files']: + formatted_structure['files'].append(file_path.format(project_name)) + except Exception : + raise ex.NotValidJson() + + return formatted_structure + +def open_load(file_path): + with open(path.join(DIR, file_path), "rt") as file: + data = load(file) + + return data + + + +templates = { + filename.removesuffix(".json"): open_load(path.join(DIR, filename)) + for filename in listdir(DIR) + if filename.endswith(".json") +} + diff --git a/artec/temp/data_science.json b/artec/temp/data_science.json new file mode 100644 index 0000000..87c9349 --- /dev/null +++ b/artec/temp/data_science.json @@ -0,0 +1,38 @@ +{ + "folders": [ + "data", + "data/external", + "data/raw", + "data/processed", + "data/interim", + "docs", + "models", + "notebooks", + "refrences", + "reports", + "reports/figures", + "src", + "src/data", + "src/features", + "src/models", + "src/visualization" + ], + "files": [ + "docs/Makefile", + "docs/commands.rst", + "docs/conf.py", + "docs/getting-started.rst", + "docs/index.rst", + "docs/make.bat", + "src/__init__.py", + ".env", + ".gitignore", + "README.md", + "LICENSE", + "Makefile", + "requirements.txt", + "setup.py", + "tox.ini", + "test_environment.py" + ] +} \ No newline at end of file diff --git a/artec/temp/flask.json b/artec/temp/flask.json new file mode 100644 index 0000000..2177c66 --- /dev/null +++ b/artec/temp/flask.json @@ -0,0 +1,34 @@ +{ + "folders": [ + "{}", + "{}/templates", + "{}/templates/auth", + "{}/blog", + "{}/static", + "test" + ], + "files": [ + "{}/__init__.py", + "{}/db.py", + "{}/schema.py", + "{}/auth.py", + "{}/blog.py", + "{}/templates/base.html", + "{}/templates/auth/login.html", + "{}/templates/auth/register.html", + "{}/blog/create.html", + "{}/blog/index.html", + "{}/blog/update.html", + "{}/static/style.css", + "test/__init__.py", + "test/conftest.py", + "test/data.sql", + "test/test_db.py", + "test/test_auth.py", + "test/test_blog.py", + "README.md", + "LICENSE", + "setup.py", + "pyproject.toml" + ] +} diff --git a/artec/temp/nodejs.json b/artec/temp/nodejs.json new file mode 100644 index 0000000..af21726 --- /dev/null +++ b/artec/temp/nodejs.json @@ -0,0 +1,69 @@ +{ + "folders": [ + "src", + "src/api", + "src/api/controllers", + "src/api/controllers/user", + "src/api/controllers/user/auth", + "src/api/controllers/user/edit", + "src/api/middlewares", + "src/api/middlewares/auth", + "src/api/routes", + "src/api/validators", + "src/config", + "src/loaders", + "src/models", + "src/utils", + "src/utils/helpers", + "src/utils/lang" + ], + "files": [ + "src/api/controllers/user/auth/forgot-password.js", + "src/api/controllers/user/auth/login.js", + "src/api/controllers/user/auth/logout.js", + "src/api/controllers/user/auth/refresh-token.js", + "src/api/controllers/user/auth/register.js", + "src/api/controllers/user/auth/send-verification-code.js", + "src/api/controllers/user/auth/verify-email.js", + "src/api/controllers/user/edit/change-password.js", + "src/api/controllers/user/edit/edit-user.js", + "src/api/controllers/user/delete-user.js", + "src/api/controllers/user/get-user.js", + "src/api/controllers/user/index.js", + "src/api/middlewares/auth/check-auth.js", + "src/api/middlewares/auth/check-authority.js", + "src/api/middlewares/image-upload.js", + "src/api/middlewares/index.js", + "src/api/middlewares/object-id-control.js", + "src/api/middlewares/rate-limiter.js", + "src/api/routes/index.js", + "src/api/routes/user.js", + "src/api/validators/index.js", + "src/api/validators/user.validator.js", + "src/config/index.js", + "src/loaders/index.js", + "src/loaders/express.js", + "src/loaders/mongoose.js", + "src/models/index.js", + "src/models/log.js", + "src/models/token.js", + "src/models/user.js", + "src/utils/helpers/error-helper.js", + "src/utils/helpers/generate-random-code.js", + "src/utils/helpers/ip-helper.js", + "src/utils/helpers/jwt-token-helper.js", + "src/utils/helpers/local-text-helper.js", + "src/utils/lang/en.json", + "src/utils/lang/get-text.json", + "src/utils/lang/tr.json", + "src/utils/index.js", + "src/utils/logger.js", + "src/utils/send-code-to-email.js", + "src/app.js", + ".env.sample", + "README.md", + "LICENSE", + "package.json", + "package-lock.json" + ] +} diff --git a/artec/temp/python.json b/artec/temp/python.json new file mode 100644 index 0000000..a36d2bc --- /dev/null +++ b/artec/temp/python.json @@ -0,0 +1,14 @@ +{ + "folders": [ + "{}", + "test" + ], + "files": [ + "{}/__init__.py", + "test/__init__.py", + "README.md", + "LICENSE", + "setup.py", + "pyproject.toml" + ] +} \ No newline at end of file diff --git a/artec/templates.py b/artec/templates.py deleted file mode 100644 index a2bdae1..0000000 --- a/artec/templates.py +++ /dev/null @@ -1,134 +0,0 @@ -from . import exceptions as ex - - -class static_list(list): - def format(self, name): - try: - for _ in self: - for i, j in _.items(): - _[i] = j.format(name) - return self - except AttributeError: - raise ex.NotValidJson() - - -PYTHON = static_list( - [ - {"folder": "{}"}, - {"file": "{}/__init__.py"}, - {"folder": "test"}, - {"file": "test/__init__.py"}, - {"file": "README.md"}, - {"file": "LICENSE"}, - {"file": "setup.py"}, - {"file": "setup.cfg"}, - {"file": "pyproject.toml"}, - ] -) - -FLASK = static_list( - [ - {"folder": "{}"}, - {"file": "{}/__init__.py"}, - {"file": "{}/db.py"}, - {"file": "{}/schema.py"}, - {"file": "{}/auth.py"}, - {"file": "{}/blog.py"}, - {"folder": "{}/templates"}, - {"file": "{}/templates/base.html"}, - {"folder": "{}/templates/auth/"}, - {"file": "{}/templates/auth/login.html"}, - {"file": "{}/templates/auth/register.html"}, - {"folder": "{}/blog"}, - {"file": "{}/blog/create.html"}, - {"file": "{}/blog/index.html"}, - {"file": "{}/blog/update.html"}, - {"folder": "{}/static"}, - {"file": "{}/static/style.css"}, - {"folder": "test"}, - {"file": "test/__init__.py"}, - {"file": "test/conftest.py"}, - {"file": "test/data.sql"}, - {"file": "test/test_db.py"}, - {"file": "test/test_auth.py"}, - {"file": "test/test_blog.py"}, - {"file": "README.md"}, - {"file": "LICENSE"}, - {"file": "setup.py"}, - {"file": "pyproject.toml"}, - ] -) - -NODE_JS = static_list( - [ - {"folder": "src"}, - {"folder": "src/api"}, - {"folder": "src/api/controllers"}, - {"folder": "src/api/controllers/user"}, - {"folder": "src/api/controllers/user/auth"}, - {"file": "src/api/controllers/user/auth/forgot-password.js"}, - {"file": "src/api/controllers/user/auth/login.js"}, - {"file": "src/api/controllers/user/auth/logout.js"}, - {"file": "src/api/controllers/user/auth/refresh-token.js"}, - {"file": "src/api/controllers/user/auth/register.js"}, - {"file": "src/api/controllers/user/auth/send-verification-code.js"}, - {"file": "src/api/controllers/user/auth/verify-email.js"}, - {"folder": "src/api/controllers/user/edit"}, - {"file": "src/api/controllers/user/edit/change-password.js"}, - {"file": "src/api/controllers/user/edit/edit-user.js"}, - {"file": "src/api/controllers/user/delete-user.js"}, - {"file": "src/api/controllers/user/get-user.js"}, - {"file": "src/api/controllers/user/index.js"}, - {"folder": "src/api/middlewares"}, - {"folder": "src/api/middlewares/auth"}, - {"file": "src/api/middlewares/auth/check-auth.js"}, - {"file": "src/api/middlewares/auth/check-authority.js"}, - {"file": "src/api/middlewares/image-upload.js"}, - {"file": "src/api/middlewares/index.js"}, - {"file": "src/api/middlewares/object-id-control.js"}, - {"file": "src/api/middlewares/rate-limiter.js"}, - {"folder": "src/api/routes"}, - {"file": "src/api/routes/index.js"}, - {"file": "src/api/routes/user.js"}, - {"folder": "src/api/validators"}, - {"file": "src/api/validators/index.js"}, - {"file": "src/api/validators/user.validator.js"}, - {"folder": "src/config"}, - {"file": "src/config/index.js"}, - {"folder": "src/loaders"}, - {"file": "src/loaders/index.js"}, - {"file": "src/loaders/express.js"}, - {"file": "src/loaders/mongoose.js"}, - {"folder": "src/models"}, - {"file": "src/models/index.js"}, - {"file": "src/models/log.js"}, - {"file": "src/models/token.js"}, - {"file": "src/models/user.js"}, - {"folder": "src/utils"}, - {"folder": "src/utils/helpers"}, - {"file": "src/utils/helpers/error-helper.js"}, - {"file": "src/utils/helpers/generate-random-code.js"}, - {"file": "src/utils/helpers/ip-helper.js"}, - {"file": "src/utils/helpers/jwt-token-helper.js"}, - {"file": "src/utils/helpers/local-text-helper.js"}, - {"folder": "src/utils/lang"}, - {"file": "src/utils/lang/en.json"}, - {"file": "src/utils/lang/get-text.json"}, - {"file": "src/utils/lang/tr.json"}, - {"file": "src/utils/index.js"}, - {"file": "src/utils/logger.js"}, - {"file": "src/utils/send-code-to-email.js"}, - {"file": "src/app.js"}, - {"file": ".env.sample"}, - {"file": "README.md"}, - {"file": "LICENSE"}, - {"file": "package.json"}, - {"file": "package-lock.json"}, - ] -) - -templates = { - "python": PYTHON, - "node.js": NODE_JS, - "flask": FLASK, -} diff --git a/pyproject.toml b/pyproject.toml index d7062da..ed5d4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ exclude = ["img*"] [project] name = "Artec" -version = "0.3.0" +version = "0.3.1" authors = [ { name="HushmKun", email="HushmKun@outlook.com" }, { name="Link-", email="bsm.dgdy@gmail.com" }, @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Software Development :: Build Tools" ] + dependencies = [ "GitPython" ] diff --git a/test/resources/structure.json b/test/resources/structure.json index 7569ac8..a36d2bc 100644 --- a/test/resources/structure.json +++ b/test/resources/structure.json @@ -1,11 +1,14 @@ -[ - {"folder": "{}"}, - {"file": "{}/__init__.py"}, - {"folder": "test"}, - {"file": "test/__init__.py"}, - {"file": "README.md"}, - {"file": "LICENSE"}, - {"file": "setup.py"}, - {"file": "setup.cfg"}, - {"file": "pyproject.toml"} -] \ No newline at end of file +{ + "folders": [ + "{}", + "test" + ], + "files": [ + "{}/__init__.py", + "test/__init__.py", + "README.md", + "LICENSE", + "setup.py", + "pyproject.toml" + ] +} \ No newline at end of file diff --git a/test/test_boiler.py b/test/test_boiler.py index 8e9cfd6..0d04f30 100644 --- a/test/test_boiler.py +++ b/test/test_boiler.py @@ -3,7 +3,7 @@ import git from pathlib import Path from artec.boiler import boiler_builder -from artec.templates import static_list, templates +from artec.temp import * VAILD_JSON = Path("./test/resources/structure.json") INVAILD_JSON = Path("./test/resources/structure.txt") @@ -15,19 +15,19 @@ def test_init(self): source="structure.json", target="target" ) self.assertEqual(builder.target, "target") - self.assertIsInstance(builder.structure, list) + self.assertIsInstance(builder.structure, dict) def test_source_temp(self): builder = boiler_builder( template="python", target="target" ) self.assertEqual( - builder.structure, templates["python"].format("target") + builder.structure, format_project_structure(templates["python"], "target") ) def test_source(self): with open(VAILD_JSON, "rt", encoding="utf-8") as file_data: - structure = static_list(json.load(file_data)).format("target") + structure = format_project_structure(json.load(file_data),"target") builder = boiler_builder( source=VAILD_JSON, target="target" ) @@ -56,19 +56,19 @@ def test_exception_handling(self): source=INVAILD_JSON, target="target" ) self.assertEqual( - builder.structure, templates["python"].format("target") + builder.structure, format_project_structure(templates["python"],"target") ) builder = boiler_builder( template="invalid", target="target" ) self.assertEqual( - builder.structure, templates["python"].format("target") + builder.structure, format_project_structure(templates["python"],"target") ) builder = boiler_builder(target="target") self.assertEqual( - builder.structure, templates["python"].format("target") + builder.structure, format_project_structure(templates["python"],"target") ) From 767c4d15d0403e23e5633871ab036b4545505e4d Mon Sep 17 00:00:00 2001 From: Hussein Mukhtar Date: Sun, 14 Apr 2024 18:43:51 +0200 Subject: [PATCH 35/35] Fixed Readme. --- README.md | 160 ++++++------------------------------------------------ 1 file changed, 18 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index bf71f45..f742613 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,20 @@ Create a JSON file to match the folder structure you desire $ vim structure.json # Paste the below into your file and modify as you desire -[ - {"folder": "{}"}, # Use '{}' to be replaced with project name. - {"file": "{}/__init__.py"}, - {"folder": "test"}, - {"file": "test/__init__.py"}, - {"file": "README.md"}, - {"file": "LICENSE"}, - {"file": "setup.py"}, - {"file": "setup.cfg"}, - {"file": "pyproject.toml"}, -] +{ + "folders": [ + "{}", + "test" + ], + "files": [ + "{}/__init__.py", + "test/__init__.py", + "README.md", + "LICENSE", + "setup.py", + "pyproject.toml" + ] +} ``` How to execute ``` @@ -64,137 +67,10 @@ Examples: ``` ## Templates -
    - Python - -Project Named Artec -``` -artec -├── artec -│ └── __main__.py -├── test -│ └── __init__.py -├── LICENSE -├── pyproject.toml -├── README.md -├── setup.cfg -└── setup.py - -2 directories, 7 files -``` -
    - -
    - Flask - -Project Named flaskr -``` -flaskr -. -├── flaskr -│ ├── auth.py -│ ├── blog -│ │ ├── create.html -│ │ ├── index.html -│ │ └── update.html -│ ├── blog.py -│ ├── db.py -│ ├── __init__.py -│ ├── schema.py -│ ├── static -│ │ └── style.css -│ └── templates -│ ├── auth -│ │ ├── login.html -│ │ └── register.html -│ └── base.html -├── LICENSE -├── pyproject.toml -├── README.md -├── setup.py -└── test - ├── conftest.py - ├── data.sql - ├── __init__.py - ├── test_auth.py - ├── test_blog.py - └── test_db.py - -7 directories, 22 files -``` -
    -
    - Node.Js - -Project Named Node -``` -Node -├── LICENSE -├── package.json -├── package-lock.json -├── README.md -└── src - ├── api - │ ├── controllers - │ │ └── user - │ │ ├── auth - │ │ │ ├── forgot-password.js - │ │ │ ├── login.js - │ │ │ ├── logout.js - │ │ │ ├── refresh-token.js - │ │ │ ├── register.js - │ │ │ ├── send-verification-code.js - │ │ │ └── verify-email.js - │ │ ├── delete-user.js - │ │ ├── edit - │ │ │ ├── change-password.js - │ │ │ └── edit-user.js - │ │ ├── get-user.js - │ │ └── index.js - │ ├── middlewares - │ │ ├── auth - │ │ │ ├── check-auth.js - │ │ │ └── check-authority.js - │ │ ├── image-upload.js - │ │ ├── index.js - │ │ ├── object-id-control.js - │ │ └── rate-limiter.js - │ ├── routes - │ │ ├── index.js - │ │ └── user.js - │ └── validators - │ ├── index.js - │ └── user.validator.js - ├── app.js - ├── config - │ └── index.js - ├── loaders - │ ├── express.js - │ ├── index.js - │ └── mongoose.js - ├── models - │ ├── index.js - │ ├── log.js - │ ├── token.js - │ └── user.js - └── utils - ├── helpers - │ ├── error-helper.js - │ ├── generate-random-code.js - │ ├── ip-helper.js - │ ├── jwt-token-helper.js - │ └── local-text-helper.js - ├── index.js - ├── lang - │ ├── en.json - │ ├── get-text.json - │ └── tr.json - ├── logger.js - └── send-code-to-email.js - -17 directories, 46 files -``` -
    +- Python +- Flask +- Node.Js +- Data_Science ## Version