From 5ac66472ace307d1b2cd7c04ae722f0899606f6c Mon Sep 17 00:00:00 2001 From: cyclopticnerve <46582023+cyclopticnerve@users.noreply.github.com> Date: Thu, 11 May 2023 14:10:12 -0400 Subject: [PATCH] first commit --- .gitignore | 184 ++++++++ CHANGELOG.md | 0 LICENSE.txt | 13 + MANIFEST.in | 9 + README.md | 62 +++ conf/blacklist.json | 22 + conf/metadata.json | 10 + conf/metadata.py | 958 ++++++++++++++++++++++++++++++++++++++ conf/settings.json | 11 + docs/ABOUT | 4 + misc/checklist.txt | 91 ++++ misc/empty_class.py | 104 +++++ misc/empty_cli.py | 147 ++++++ misc/empty_mod.py | 74 +++ misc/screenshot.jpg | 0 misc/snippets.txt | 30 ++ misc/style.txt | 34 ++ misc/todo.txt | 0 misc/tree.txt | 31 ++ pyproject.toml | 33 ++ requirements.txt | 34 ++ src/pkgtest/__init__.py | 19 + src/pkgtest/mod_name_1.py | 74 +++ src/pkgtest/mod_name_2.py | 74 +++ tests/ABOUT | 4 + 25 files changed, 2022 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 conf/blacklist.json create mode 100644 conf/metadata.json create mode 100755 conf/metadata.py create mode 100644 conf/settings.json create mode 100644 docs/ABOUT create mode 100644 misc/checklist.txt create mode 100644 misc/empty_class.py create mode 100755 misc/empty_cli.py create mode 100644 misc/empty_mod.py create mode 100644 misc/screenshot.jpg create mode 100644 misc/snippets.txt create mode 100644 misc/style.txt create mode 100644 misc/todo.txt create mode 100644 misc/tree.txt create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/pkgtest/__init__.py create mode 100644 src/pkgtest/mod_name_1.py create mode 100644 src/pkgtest/mod_name_2.py create mode 100644 tests/ABOUT diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2421eb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,184 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: .gitignore | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# shamelessly stolen from +# https://github.com/github/gitignore/blob/main/Python.gitignore +# (and reformatted) + +# My stuff +.VSCodeCounter + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff +instance/ +.webassets-cache + +# Scrapy stuff +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the +# code is intended to run in multiple environments; otherwise, check them in +#.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in +# version control. +# However, in case of collaboration, if having platform-specific dependencies +# or dependencies having no cross-platform support, pipenv may install +# dependencies that don't work, or not install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock +# in version control. +# This is especially recommended for binary packages to ensure +# reproducibility, and is more commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in +# version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended +# to not include it in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. +# https://github.com/David-OConnor/pyflow and +# https://github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore +# that can be found at +# https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a +# more nuclear option (not recommended) you can uncomment the following to +# ignore the entire idea folder. +#.idea/ + +# -) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8877f44 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified copies of this +license document, and changing it is allowed as long as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2afe108 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: MANIFEST.in | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# -) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6c9dd5 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ + + + + + + + + +# PkgTest + +## "It mostly works™" +[![License: WTFPL](https://img.shields.io/badge/License-WTFPL-brightgreen.svg)](http://www.wtfpl.net/about/) + + +PP_SHORT_DESC + + + + +## Requirements + +PP_PY_DEPS + + +## Installing +You can download the (hopefully stable) +[latest release](https://github.com/cyclopticnerve/PkgTest/releases/latest) +from the main branch.
+Download the 'Source Code (tar.gz)' file. + +Then install it using: +```bash +foo@bar:~$ cd ~/Downloads +foo@bar:~/Downloads$ python -m pip install PkgTest-PP_VERSION.tar.gz +``` +Or you can clone the git repo to get the latest (and often broken) code from the +dev branch: +```bash +foo@bar:~$ cd ~/Downloads +foo@bar:~/Downloads$ git clone https://github.com/cyclopticnerve/PkgTest +foo@bar:~/Downloads$ cd PkgTest +``` +Then build and install: +```bash +foo@bar:~/Downloads/PkgTest$ python -m pip install build +foo@bar:~/Downloads/PkgTest$ python -m build +foo@bar:~/Downloads/PkgTest$ python -m pip install ./dist/pkgtest-PP_VERSION.tar.gz +``` + +## Uninstalling +```bash +foo@bar:~$ python -m pip uninstall pkgtest +``` + +## Usage +blah blah blah + +## Notes +blah blah blah + +## -) + diff --git a/conf/blacklist.json b/conf/blacklist.json new file mode 100644 index 0000000..eb58bdc --- /dev/null +++ b/conf/blacklist.json @@ -0,0 +1,22 @@ +{ + "skip_all": [ + ".venv", + ".git", + "dist", + "docs", + "misc", + "tests", + "__pycache__", + "PKG-INFO" + ], + "skip_file": [ + "pkgtest.png" + ], + "skip_header": [], + "skip_text": [ + "metadata.json", + "metadata.py", + "settings.json" + ], + "skip_path": [] +} \ No newline at end of file diff --git a/conf/metadata.json b/conf/metadata.json new file mode 100644 index 0000000..1c78ae5 --- /dev/null +++ b/conf/metadata.json @@ -0,0 +1,10 @@ +{ + "PP_VERSION": "", + "PP_SHORT_DESC": "", + "PP_KEYWORDS": "", + "PP_PY_DEPS": {}, + "PP_SYS_DEPS": "", + "PP_GUI_CATEGORIES": "", + "PP_GUI_EXEC": "", + "PP_GUI_ICON": "" +} \ No newline at end of file diff --git a/conf/metadata.py b/conf/metadata.py new file mode 100755 index 0000000..76dc0e5 --- /dev/null +++ b/conf/metadata.py @@ -0,0 +1,958 @@ +#! /usr/bin/env python +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: metadata.py | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Imports +# ------------------------------------------------------------------------------ +import json +import os +import re +import shlex +import subprocess + +# ------------------------------------------------------------------------------ +# Constants +# ------------------------------------------------------------------------------ + +# this is the project dir +dir = os.path.dirname(__file__) +dir_prj = os.path.join(dir, '..') +DIR_PRJ = os.path.abspath(dir_prj) + +# load blacklist file +DICT_BLACKLIST = [] +path_blacklist = os.path.join(DIR_PRJ, 'conf', 'blacklist.json') +if os.path.exists(path_blacklist): + with open(path_blacklist, 'r', encoding='utf-8') as f: + try: + DICT_BLACKLIST = json.load(f) + except (Exception): + print(f'{f} is not a valid JSON file') + exit() + +# load metadata file +DICT_METADATA = [] +path_metadata = os.path.join(DIR_PRJ, 'conf', 'metadata.json') +if os.path.exists(path_metadata): + with open(path_metadata, 'r', encoding='utf-8') as f: + try: + DICT_METADATA = json.load(f) + except (Exception): + print(f'{f} is not a valid JSON file') + exit() + +# load settings file +DICT_SETTINGS = [] +path_settings = os.path.join(DIR_PRJ, 'conf', 'settings.json') +if os.path.exists(path_settings): + with open(path_settings, 'r', encoding='utf-8') as f: + try: + DICT_SETTINGS = json.load(f) + except (Exception): + print(f'{f} is not a valid JSON file') + exit() + +# ------------------------------------------------------------------------------ +# Globals +# ------------------------------------------------------------------------------ + +# keep track of error count +g_err_cnt = 0 + + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# The main function of the program +# ------------------------------------------------------------------------------ +def main(): + """ + The main function of the program + + This function is the main entry point for the program, initializing the + program, and performing its steps. + """ + + # do proactive replacements in specific files (replaces needed text) + + # common + fix_readme() + + # mod/pkg + fix_pyproject() + + # # pkg + fix_init() + + # # cli/gui + fix_argparse() + fix_install() + + # # gui + fix_desktop() + fix_ui() + + # # check for __PP_ /PP_ stuff left over or we missed + recurse(DIR_PRJ) + + # # do housekeeping + do_extras() + + # # print error count (__PP_/PP_ stuff found) + print(f'Errors: {g_err_cnt}') + + +# ------------------------------------------------------------------------------ +# Replace text in the README file +# ------------------------------------------------------------------------------ +def fix_readme(): + """ + Replace text in the README file + + Replace short description, dependencies, and version number in the + README file. + """ + + # check if the file exists + path_readme = os.path.join(DIR_PRJ, 'README.md') + if not os.path.exists(path_readme): + return + + # open file and get contents + with open(path_readme, 'r', encoding='utf-8') as f: + text = f.read() + + # replace short description + str_pattern = ( + r'()' + r'(.*?)' + r'()' + ) + + # get short description + pp_short_desc = DICT_METADATA['PP_SHORT_DESC'] + + # replace text + str_rep = rf'\g<1>\n{pp_short_desc}\n\g<3>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace dependencies array + str_pattern = ( + r'()' + r'(.*?)' + r'()' + ) + + # build a string from the dict (markdown link) + pp_py_deps = DICT_METADATA['PP_PY_DEPS'] + lst_py_deps = [f'[{key}]({val})' for (key, val) in pp_py_deps.items()] + str_py_deps = ','.join(lst_py_deps) + + # split the string for README + str_split = _split_quote(str_py_deps, join='
\n', tail='\n') + if str_split == '': + str_split = 'None\n' + + # replace text + str_rep = rf'\g<1>\n{str_split}\g<3>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace version + pp_version = DICT_METADATA['PP_VERSION'] + + str_pattern = ( + r'(\s*foo@bar:~/Downloads\$ python -m pip install )' + r'(.*-)' + r'(.*?)' + r'(\.tar\.gz)' + ) + str_rep = rf'\g<1>\g<2>{pp_version}\g<4>' + text = re.sub(str_pattern, str_rep, text) + + str_pattern = ( + r'(\s*foo@bar:~/Downloads/)' + r'(.*?)' + r'(\$ python -m pip install ./dist/)' + r'(.*-)' + r'(.*?)' + r'(\.tar\.gz)' + ) + str_rep = rf'\g<1>\g<2>\g<3>\g<4>{pp_version}\g<6>' + text = re.sub(str_pattern, str_rep, text) + + str_pattern = ( + r'(\s*foo@bar:~\$ cd ~/Downloads/)' + r'(.*-)' + r'(.*)' + ) + str_rep = rf'\g<1>\g<2>{pp_version}' + text = re.sub(str_pattern, str_rep, text) + + str_pattern = ( + r'(\s*foo@bar:~/Downloads/)' + r'(.*-)' + r'(.*)' + r'(\$ \./install.py)' + ) + str_rep = rf'\g<1>\g<2>{pp_version}\g<4>' + text = re.sub(str_pattern, str_rep, text) + + # save file + with open(path_readme, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Replace text in the pyproject file +# ------------------------------------------------------------------------------ +def fix_pyproject(): + """ + Replace text in the pyproject file + + Replaces things like the keywords, requirements, etc. in the toml file. + """ + + # check if the file exists + path_toml = os.path.join(DIR_PRJ, 'pyproject.toml') + if not os.path.exists(path_toml): + return + + # open file and get contents + with open(path_toml, 'r', encoding='utf-8') as f: + text = f.read() + + # NB: we do a dunder replace here because putting a dunder as the + # default name in the toml file causes the linter to choke, so we use a + # dummy name + + # replace name + str_pattern = ( + r'(^\s*\[project\]\s*$)' + r'(.*?)' + r'(^\s*name[\t ]*=[\t ]*)' + r'(.*?$)' + ) + pp_name = DICT_SETTINGS['info']['__PP_NAME_SMALL__'] + str_rep = rf'\g<1>\g<2>\g<3>"{pp_name}"' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace version + str_pattern = ( + r'(^\s*\[project\]\s*$)' + r'(.*?)' + r'(^\s*version[\t ]*=[\t ]*)' + r'(.*?$)' + ) + pp_version = DICT_METADATA['PP_VERSION'] + str_rep = rf'\g<1>\g<2>\g<3>"{pp_version}"' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace short description + str_pattern = ( + r'(^\s*\[project\]\s*$)' + r'(.*?)' + r'(^\s*description[\t ]*=[\t ]*)' + r'(.*?$)' + ) + pp_short_desc = DICT_METADATA['PP_SHORT_DESC'] + str_rep = rf'\g<1>\g<2>\g<3>"{pp_short_desc}"' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # # replace keywords array + str_pattern = ( + r'(^\s*\[project\]\s*$)' + r'(.*?)' + r'(^\s*keywords[\t ]*=[\t ]*)' + r'(.*?\])' + ) + + # convert dict to string + pp_keywords = DICT_METADATA['PP_KEYWORDS'] + str_split = _split_quote(pp_keywords, quote='"', lead='\t', + join=',\n\t', tail='\n') + + # replace string + str_rep = rf'\g<1>\g<2>\g<3>[\n{str_split}]' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace dependencies array + str_pattern = ( + r'(^\s*\[project\]\s*$)' + r'(.*?)' + r'(^\s*dependencies[\t ]*=[\t ]*)' + r'(.*?\])' + ) + + # convert dict to string (only using keys) + pp_py_deps = DICT_METADATA['PP_PY_DEPS'] + + # NB: this is not conducive to a dict (we don't need links, only names) + # so don't do what we did in README, keep it simple + str_py_deps = ','.join(pp_py_deps.keys()) + + # split the string for pyproject + str_split = _split_quote(str_py_deps, quote='"', lead='\t', + join=',\n\t', tail='\n') + + # replace text + str_rep = rf'\g<1>\g<2>\g<3>[\n{str_split}]' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_toml, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Replace text in the __init__.py file +# ------------------------------------------------------------------------------ +def fix_init(): + """ + Replace text in the __init__.py file + + Replaces the 'from __PP_NAME_SMALL__ import filename' text in the + __init__.py file. + """ + + # first check if there is a pkg dir + pp_name = DICT_SETTINGS['info']['__PP_NAME_SMALL__'] + dir_pkg = os.path.join(DIR_PRJ, 'src', pp_name) + if not os.path.exists(dir_pkg) or not os.path.isdir(dir_pkg): + return + + # the file name of the init + file_init = '__init__.py' + + # check if there is an __init__.py file + path_init = os.path.join(dir_pkg, file_init) + if not os.path.exists(path_init): + return + + # check if there are any .py files in package dir that are not init file + lst_modules = [item for item in os.listdir(dir_pkg) + if item != file_init and os.path.splitext(item)[1] == 'py'] + if len(lst_modules) == 0: + return + + # strip ext and add to list + lst_files = [os.path.splitext(item)[0] for item in lst_modules] + + # sort file list to look pretty (listdir is not sorted) + lst_files.sort() + + # format list for imports section + lst_imports = [ + f'from {pp_small} import {item} # noqa: W0611 (unused import)' + for item in lst_files + ] + str_imports = '\n'.join(lst_imports) + + # format __all__ for completeness + lst_all = [f'\'{item}\'' for item in lst_files] + str_all_join = ', '.join(lst_all) + str_all = f'__all__ = [{str_all_join}]' + + # open file and get contents + with open(path_init, 'r', encoding='utf-8') as f: + text = f.read() + + # replace imports block + str_pattern = ( + r'(^#[\t ]*__PP_IMPORTS_START__[\t ]*)' + r'(.*?)' + r'(^#[\t ]*__PP_IMPORTS_END__[\t ]*)' + ) + str_rep = rf'\g<1>\n{str_imports}\n{str_all}\n\g<3>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_init, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Replace text for argparse stuff +# ------------------------------------------------------------------------------ +def fix_argparse(): + """ + Replace text for argparse stuff + + This function replaces PP_ variables in any file that uses + argparse. + """ + + # get src dir + dir_src = os.path.join(DIR_PRJ, 'src') + + # get all paths + lst_paths = [os.path.join(dir_src, item) for item in os.listdir(dir_src)] + if len(lst_paths) == 0: + return + + # get all files + lst_files = [item for item in lst_paths if os.path.isfile(item) and + os.path.splitext(item)[1] == '.py'] + + # for each file + for item in lst_files: + + # get the whole path + path_item = os.path.join(dir_src, item) + + # check if file exists + if not os.path.exists(path_item) or os.path.isdir(path_item): + continue + + # open file and get contents + with open(path_item, 'r', encoding='utf-8') as f: + text = f.read() + + # replace short description + str_pattern = ( + r'(import argparse.*def _parse_args\(\):.*)' + r'(argparse.ArgumentParser\(\s*description=\')' + r'(.*?)' + r'(\'.*)' + ) + pp_short_desc = DICT_METADATA['PP_SHORT_DESC'] + str_rep = rf'\g<1>\g<2>{pp_short_desc}\g<4>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace version + str_pattern = ( + r'(import argparse.*def _parse_args\(\):.*)' + r'(print\(\'.* version )' + r'(.*?)' + r'(\'.*)' + ) + pp_version = DICT_METADATA['PP_VERSION'] + str_rep = rf'\g<1>\g<2>{pp_version}\g<4>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_item, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Replace text in the install file +# ------------------------------------------------------------------------------ +def fix_install(): + """ + Replace text in the install file + + Replaces the system and Python dependencies in the install file. + """ + + # check if the file exists + path_install = os.path.join(DIR_PRJ, 'install.py') + if not os.path.exists(path_install): + return + + # open file and get content + with open(path_install, 'r', encoding='utf-8') as f: + text = f.read() + + # replace python dependencies array + str_pattern = ( + r'(^\s*dict_install[\t ]*=\s*{)' + r'(.*?)' + r'(^\s*\'py_deps\'[\t ]*:)' + r'(.*?\])' + ) + + # convert dict keys to string + pp_py_deps = DICT_METADATA['PP_PY_DEPS'] + str_py_deps = ','.join(pp_py_deps.keys()) + str_split = _split_quote(str_py_deps, quote='"', lead='\t\t', + join=',\n\t\t') + + # replace text + str_rep = rf'\g<1>\g<2>\g<3> [\n{str_split}\n\t]' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace system dependencies array + str_pattern = ( + r'(^\s*dict_install[\t ]*=)' + r'(.*?)' + r'(^\s*\'sys_deps\'[\t ]*:)' + r'(.*?\])' + ) + + # convert dict to string + pp_sys_deps = DICT_METADATA['PP_SYS_DEPS'] + str_split = _split_quote(pp_sys_deps, quote='"', lead='\t\t', + join=',\n\t\t') + + # replace string + str_rep = rf'\g<1>\g<2>\g<3> [\n{str_split}\n\t]' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_install, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Replace text in the desktop file +# ------------------------------------------------------------------------------ +def fix_desktop(): + """ + Replace text in the desktop file + + Replaces the icon, executable, and category text in a .desktop file for + programs that use this. + """ + + # check if the file exists + pp_name = DICT_SETTINGS['info']['__PP_NAME_SMALL__'] + path_desk = os.path.join(DIR_PRJ, 'src', 'gui', f'{pp_name}.desktop') + if not os.path.exists(path_desk): + return + + # open file and get contents + with open(path_desk, 'r', encoding='utf-8') as f: + text = f.read() + + # replace short description + str_pattern = ( + r'(^\s*\[Desktop Entry\]\s*$)' + r'(.*?)' + r'(^\s*Comment[\t ]*=)' + r'(.*?$)' + ) + pp_short_desc = DICT_METADATA['PP_SHORT_DESC'] + str_rep = rf'\g<1>\g<2>\g<3>{pp_short_desc}' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace categories + str_pattern = ( + r'(^\s*\[Desktop Entry\]\s*$)' + r'(.*?)' + r'(^\s*Categories[\t ]*=)' + r'(.*?$)' + ) + + # convert dict to string + pp_gui_categories = DICT_METADATA['PP_GUI_CATEGORIES'] + str_split = _split_quote(pp_gui_categories, join=';', tail=';') + + # replace text + str_rep = rf'\g<1>\g<2>\g<3>{str_split}' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace exec + str_pattern = ( + r'(^\s*\[Desktop Entry\]\s*$)' + r'(.*?)' + r'(^\s*Exec[\t ]*=)' + r'(.*?$)' + ) + # TODO: look in $PATH + pp_gui_exec = DICT_METADATA['PP_GUI_EXEC'] + str_rep = rf'\g<1>\g<2>\g<3>{pp_gui_exec}' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace icon + str_pattern = ( + r'(^\s*\[Desktop Entry\]\s*$)' + r'(.*?)' + r'(^\s*Icon[\t ]*=)' + r'(.*?$)' + ) + # TODO: look in $PATH + pp_gui_icon = DICT_METADATA['PP_GUI_ICON'] + str_rep = rf'\g<1>\g<2>\g<3>{pp_gui_icon}' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_desk, 'w', encoding='utf-8') as f: + f.write(text) + +# TODO: set desktop path entry to prog dir??? + + +# ------------------------------------------------------------------------------ +# Replace text in the UI file +# ------------------------------------------------------------------------------ +def fix_ui(): + """ + Replace text in the UI file + + Replace short description, program name, and version number in the + UI file. + """ + + # check if there is a .ui file + pp_name = DICT_SETTINGS['info']['__PP_NAME_SMALL__'] + path_ui = os.path.join(DIR_PRJ, 'src', f'{pp_name}-gtk3.ui') + if not os.path.exists(path_ui): + return + + # open file and get contents + with open(path_ui, 'r', encoding='utf-8') as f: + text = f.read() + + # replace version + str_pattern = ( + r'()' + r'(.*?)' + r'(.*)' + ) + pp_version = DICT_METADATA['PP_VERSION'] + str_rep = rf'\g<1>\g<2>{pp_version}\g<4>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # replace short description + str_pattern = ( + r'()' + r'(.*?)' + r'()' + ) + pp_short_desc = DICT_METADATA['PP_SHORT_DESC'] + str_rep = rf'\g<1>\g<2>{pp_short_desc}\g<4>' + text = re.sub(str_pattern, str_rep, text, flags=re.M | re.S) + + # save file + with open(path_ui, 'w', encoding='utf-8') as f: + f.write(text) + + +# ------------------------------------------------------------------------------ +# Recurse through the folder structure looking for errors +# ------------------------------------------------------------------------------ +def recurse(dir): + """ + Recurse through the folder structure looking for errors + + Parameters: + dir [string]: the directory to start looking for errors + + This function recurses through the project directory, looking for errors + in each file's headers and content for strings that do not match their + intended contents. It checks a header's project, filename, and date + values as well as looking for dunder values that should have been + replaced. + """ + + # blacklist + # don't check everything/contents/headers/text/path names for these items + # strip trailing slashes to match path component + skip_all = [item.strip(os.sep) for item in DICT_BLACKLIST['skip_all']] + skip_file = [item.strip(os.sep) for item in DICT_BLACKLIST['skip_file']] + skip_header = [item.strip(os.sep) + for item in DICT_BLACKLIST['skip_header']] + skip_text = [item.strip(os.sep) for item in DICT_BLACKLIST['skip_text']] + skip_path = [item.strip(os.sep) for item in DICT_BLACKLIST['skip_path']] + + # get list of replaceable file names + items = [item for item in os.listdir(dir) if item not in skip_all] + for item in items: + + # put path back together + path_item = os.path.join(dir, item) + + # if it's a dir + if os.path.isdir(path_item): + + # recurse itself to find more files + recurse(path_item) + + else: + + # only open files we should be mucking in + if item not in skip_file: + + # open file and get lines + with open(path_item, 'r', encoding='utf-8') as f: + lines = f.readlines() + + # check headers of most files + if item not in skip_header: + _check_header(path_item, lines) + + # check contents of most files + if item not in skip_text: + _check_text(path_item, lines) + + # check file paths (subdirs and such) + if item not in skip_path: + _check_path(path_item) + + +# ------------------------------------------------------------------------------ +# Do extra functions to update project dir after recurse +# ------------------------------------------------------------------------------ + +def do_extras(): + """ + Do extras functions to update project dir after recurse + + Do some extra functions like add requirements, update docs, and update + the CHANGELOG file for the current project. + """ + + # get pyplate/src dir + dir_curr = os.getcwd() + + # make sure we are in project path + os.chdir(DIR_PRJ) + + # add requirements + path_req = os.path.join(DIR_PRJ, 'requirements.txt') + with open(path_req, 'w', encoding='utf-8') as f: + + cmd = 'python -m pip freeze -l --exclude-editable --require-virtualenv' + cmd_array = shlex.split(cmd) + subprocess.run(cmd_array, stdout=f) + + # update CHANGELOG + path_chg = os.path.join(DIR_PRJ, 'CHANGELOG.md') + with open(path_chg, 'w', encoding='utf-8') as f: + + cmd = 'git log --pretty="%ad - %s"' + cmd_array = shlex.split(cmd) + subprocess.run(cmd_array, stdout=f) + + # go back to old dir + os.chdir(dir_curr) + +# ------------------------------------------------------------------------------ + + # move into src dir + os.chdir(os.path.join(DIR_PRJ, 'src')) + path_docs = os.path.join('..', 'docs') + path_docs = os.path.abspath(path_docs) + + # # update docs + cmd = f'python -m pdoc --html -f -o {path_docs} .' + cmd_array = shlex.split(cmd) + subprocess.run(cmd_array) + + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Checks header values for dunders +# ------------------------------------------------------------------------------ +def _check_header(path_item, lines): + """ + Checks header values for dunders + + Parameters: + path_item [string]: the full path to file to be checked for header + lines [array]: the contents of the file to be checked + + This function checks the files headers for values that either do not + match the file's project/file name, or do not have a date set. + """ + + # global error count + global g_err_cnt + + # for each line in file + for i in range(0, len(lines)): + line = lines[i] + + # check project name + prj_name = os.path.basename(DIR_PRJ) + str_pattern = ( + r'(^\s*( + + + + + + + + + + + + diff --git a/misc/style.txt b/misc/style.txt new file mode 100644 index 0000000..d49dcaa --- /dev/null +++ b/misc/style.txt @@ -0,0 +1,34 @@ +[] update tree - $ tree --dirsfirst -a --noreport -o 'misc/tree.txt' + +[] 2 part vars: \b[a-zA-Z]+_[a-zA-Z]+\b + check for _path, _file, _dir, _str, _lst and swap + check for path_, file_, dir_, str_, lst_ + check for _proj and make _prj + check for proj_ and make prj_ + check for _prj and swap + check for prj_ + path - whole thing + file - name only + dir - up to name or if a folder +[] >2part vars: \b[a-zA-Z]+_[a-zA-Z]+_[a-zA-Z]+ + +[] all globals lowercase starting with g_ +[] all constants UPPERCASE +[] no += for strings +[] Headers/footers +[] spellcheck +[] separators ------ +[] over 80 cols +[] use list/dict comprehensions (look for for loops) +[] reduce re flags/groups + +[] cli/gui: function/method not called from main: start with underscore +[] mod/pkg: Private functions/methods start with underscore + +[] Docstrings + Check that short desc matches comment block + Check that last line of long desc ends w/ a period + docstring types have params wrapped in parens, not brackets + where do we use brackets? + 'method' in a class + 'function' not in a class diff --git a/misc/todo.txt b/misc/todo.txt new file mode 100644 index 0000000..e69de29 diff --git a/misc/tree.txt b/misc/tree.txt new file mode 100644 index 0000000..9be8446 --- /dev/null +++ b/misc/tree.txt @@ -0,0 +1,31 @@ +. +├── conf +│   ├── blacklist.json +│   ├── metadata.json +│   ├── metadata.py +│   └── settings.json +├── docs +│   └── ABOUT +├── misc +│   ├── checklist.txt +│   ├── empty_class.py +│   ├── empty_cli.py +│   ├── empty_mod.py +│   ├── screenshot.jpg +│   ├── snippets.txt +│   ├── style.txt +│   ├── todo.txt +│   └── tree.txt +├── src +│   └── pkgtest +│   ├── __init__.py +│   ├── mod_name_1.py +│   └── mod_name_2.py +├── tests +│   └── ABOUT +├── CHANGELOG.md +├── LICENSE.txt +├── MANIFEST.in +├── pyproject.toml +├── README.md +└── requirements.txt diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..abe4deb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: pyproject.toml | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +[build-system] +requires = ["setuptools>=59.6.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "NAME" +version = "0.0.0" +description = "" +authors = [{ name = "cyclopticnerve", email = "cyclopticnerve@gmail.com" }] +readme = "README.md" +license = { file = "LICENSE.txt" } +classifiers = [ + "License :: Other/Proprietary License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", +] +keywords = [] +dependencies = [] +requires-python = ">=3.10" + +[project.urls] +Homepage = "https://github.com/cyclopticnerve/PkgTest" + +# -) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..88c4955 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,34 @@ +astroid==2.14.2 +autopep8==2.0.1 +build==0.10.0 +dill==0.3.6 +duckdb==0.7.1 +isort==5.12.0 +lazy-object-proxy==1.9.0 +Mako==1.2.4 +Markdown==3.4.1 +MarkupSafe==2.1.2 +mccabe==0.7.0 +numpy==1.24.2 +packaging==23.0 +pandas==1.5.3 +pdoc3==0.10.0 +platformdirs==3.1.0 +pyarrow==11.0.0 +pycairo==1.23.0 +pycodestyle==2.10.0 +pydocstyle==6.3.0 +pyflakes==3.0.1 +PyGObject==3.44.1 +pylama==8.4.1 +pylance==0.3.10 +pylint==2.16.3 +pyproject_hooks==1.0.0 +python-dateutil==2.8.2 +pytz==2022.7.1 +six==1.16.0 +snowballstemmer==2.2.0 +tomli==2.0.1 +tomlkit==0.11.6 +typing_extensions==4.5.0 +wrapt==1.15.0 diff --git a/src/pkgtest/__init__.py b/src/pkgtest/__init__.py new file mode 100644 index 0000000..7fd8bda --- /dev/null +++ b/src/pkgtest/__init__.py @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: __init__.py | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Imports +# ------------------------------------------------------------------------------ + +# __PP_IMPORTS_START__ +from pkgtest import mod_name_1 # noqa: W0611 (unused import) +from pkgtest import mod_name_2 # noqa: W0611 (unused import) +__all__ = ['mod_name_1', 'mod_name_2'] +# __PP_IMPORTS_END__ + +# -) diff --git a/src/pkgtest/mod_name_1.py b/src/pkgtest/mod_name_1.py new file mode 100644 index 0000000..17ad80d --- /dev/null +++ b/src/pkgtest/mod_name_1.py @@ -0,0 +1,74 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: mod_name_1.py | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Imports +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Constants +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Globals +# ------------------------------------------------------------------------------ + + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Short description +# ------------------------------------------------------------------------------ +def func_1(): + """ + Short description + + Parameters: + var_name [type]: description + + Returns: + [type]: description + + Raises: + exception_type(vars): description + + Long description (including HTML). + """ + + print('this is func_1') + return _func_1() + + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Short description +# ------------------------------------------------------------------------------ +def _func_1(): + """ + Short description + + Parameters: + var_name [type]: description + + Returns: + [type]: description + + Raises: + exception_type(vars): description + + Long description (including HTML). + """ + + return ('this is _func_1') + +# -) diff --git a/src/pkgtest/mod_name_2.py b/src/pkgtest/mod_name_2.py new file mode 100644 index 0000000..9eaee1c --- /dev/null +++ b/src/pkgtest/mod_name_2.py @@ -0,0 +1,74 @@ +# ------------------------------------------------------------------------------ +# Project : PkgTest / \ +# Filename: mod_name_2.py | () | +# Date : 05/11/2023 | | +# Author : cyclopticnerve | \____/ | +# License : WTFPLv2 \ / +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Imports +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Constants +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Globals +# ------------------------------------------------------------------------------ + + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Short description +# ------------------------------------------------------------------------------ +def func_2(): + """ + Short description + + Parameters: + var_name [type]: description + + Returns: + [type]: description + + Raises: + exception_type(vars): description + + Long description (including HTML). + """ + + print(' this is func_2') + return _func_2() + + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Short description +# ------------------------------------------------------------------------------ +def _func_2(): + """ + Short description + + Parameters: + var_name [type]: description + + Returns: + [type]: description + + Raises: + exception_type(vars): description + + Long description (including HTML). + """ + + return ('this is _func_2') + +# -) diff --git a/tests/ABOUT b/tests/ABOUT new file mode 100644 index 0000000..be8de04 --- /dev/null +++ b/tests/ABOUT @@ -0,0 +1,4 @@ +This file is here to make sure git includes this directory in the repo +git does not currently allow empty directories to be synced + +This directory is used for unit test files and for 'scratchpad' files