From a615f6cc4901a05f7fe72a55d005280119cb792e Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 21 Aug 2022 13:14:00 -0400 Subject: refactor and (try to) follow PEP518 --- .flake8 | 4 ++ .gitignore | 160 +++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 14 ----- README.md | 7 +-- bin/hiccup | 160 ------------------------------------------------- default-config.json | 6 +- hiccup.py | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 31 ++++++++++ setup.py | 2 + 9 files changed, 371 insertions(+), 183 deletions(-) create mode 100644 .flake8 create mode 100644 .gitignore delete mode 100644 Makefile delete mode 100755 bin/hiccup create mode 100755 hiccup.py create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..0a419e2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 88 +extend-ignore = + E203 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# 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. github.com/David-OConnor/pyflow and 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/Makefile b/Makefile deleted file mode 100644 index 4eb5369..0000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -shell = /usr/bin/env bash -PREFIX ?= $(HOME)/.local -CONFIG = $(HOME)/.config -ifeq ($(XDG_CONFIG_HOME),) - CONFIG = $(XDG_CONFIG_HOME) -endif - -install: - install -Dm755 bin/hiccup $(DESTDIR)$(PREFIX)/bin/hiccup - install -Dm644 default-config.json $(DESTDIR)$(CONFIG)/hiccup/config.json - -uninstall: - rm $(DESTDIR)$(PREFIX)/bin/hiccup - rm $(DESTDIR)$(CONFIG)/hiccup/config.json diff --git a/README.md b/README.md index b9433a0..ca377e4 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,5 @@ see `default-config.json` for example ## how to install hiccup only needs one command to install :) ```sh -make install -``` - -and to uninstall: -```sh -make uninstall +python setup.py install --user ``` diff --git a/bin/hiccup b/bin/hiccup deleted file mode 100755 index d65b650..0000000 --- a/bin/hiccup +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import json -import subprocess -import os - -CONFIG_FILE = os.path.join(os.environ['XDG_CONFIG_HOME'], 'hiccup/config.json') -OS_RELEASE_PATH = '/etc/os-release' - - -class DistroNotSupportedError(Exception): - def __init__(self, name): - self.message = '{} isn\'t supported yet'.format(name) - super().__init__(self.message) - - -class CurrentDistro: - __silent = ' > /dev/null 2>&1' - - def __init__(self, id: str): - try: - with open(CONFIG_FILE) as file: - data = json.load(file) - self.__system_update_cmds = data['system_update_cmds'] - self.__extra_cmds = data['extra_cmds'] - self.__clean_cmds = data['clean_cmds'] - self.__shell_plugin_cmds = data['shell_plugin_cmds'] - self.__other_cmds = data['other_cmds'] - except Exception: - pass - - self.id = id - - if self.is_supported(): - self.update_cmd = self.get_update_cmd() - if self.has_clean_cmd(): - self.clean_cmd = self.get_clean_cmd() - if self.has_extra_cmd(): - self.extra_cmd = self.get_extra_cmd() - else: - raise DistroNotSupportedError(self.id) - - def __get_cmd(self, dct: dict): - return dct[self.id] - - def __sys_cmd( - self, - cmd: str, - executable='bash', - append='', - prepend='', - sudo=False): - - cmd = '{}{}{}'.format(prepend, cmd, append) - if sudo: - cmd = 'sudo bash -c \'{}\''.format(cmd) - - return subprocess.run( - cmd, - check=True, - executable=executable, - shell=True) - - def __run_items(self, msg: str, dct: dict, name_as_cmd=False): - executable = 'sh' - for name, cmd in dct.items(): - print(msg.format(name)) - - if name_as_cmd: - executable = name - self.__sys_cmd(cmd, executable=executable, append=self.__silent) - - def is_supported(self): - return self.id in self.__system_update_cmds - - def has_clean_cmd(self): - return self.id in self.__clean_cmds - - def has_extra_cmd(self): - return self.id in self.__extra_cmds - - def get_update_cmd(self): - return self.__get_cmd(self.__system_update_cmds) - - def get_extra_cmd(self): - return self.__get_cmd(self.__extra_cmds) - - def get_clean_cmd(self): - return self.__get_cmd(self.__clean_cmds) - - def update_system(self): - self.__sys_cmd(self.update_cmd, sudo=True) - if self.has_extra_cmd(): - self.__sys_cmd(self.extra_cmd) - - def cleanup_system(self): - if self.has_clean_cmd(): - print('cleaning up system...') - return self.__sys_cmd(self.clean_cmd, sudo=True) - print('no cleanup command found for {}'.format(self.id)) - - def update_shell_plugins(self): - msg = 'updating {} plugins...' - return self.__run_items(msg, self.__shell_plugin_cmds, - name_as_cmd=True) - - def update_other(self): - msg = 'updating {}...' - return self.__run_items(msg, self.__other_cmds) - - def update_all(self): - self.update_system() - self.update_shell_plugins() - self.update_other() - self.cleanup_system() - - -def get_os_release(): - cmd = 'source {}; echo -n $ID'.format(OS_RELEASE_PATH) - p: subprocess.Popen = subprocess.Popen( - cmd, - shell=True, - executable='bash', - stdout=subprocess.PIPE) - - return str(p.communicate()[0], 'UTF-8').strip() - - -def run(): - current_distro = get_os_release() - distro = CurrentDistro(current_distro) - - parser = argparse.ArgumentParser( - description='a python script to help keep you up to date') - parser.add_argument("--cleanonly", "-c", action="store_true", - default=False, dest="cleanonly", - help='cleanup unneeded dependencies') - parser.add_argument("--systemonly", "-s", action="store_true", - default=False, dest="systemonly", - help='only update through the system\'s package manager') # noqa: E501 - args = parser.parse_args() - - if args.cleanonly: - return distro.cleanup_system() - if args.systemonly: - return distro.update_system() - - return distro.update_all() - - -if __name__ == '__main__': - if os.geteuid() == 0: - print('please don\'t run this as root :(') - exit(1) - try: - run() - print('done!') - except Exception as e: - print(repr(e)) - exit(2) diff --git a/default-config.json b/default-config.json index 4616521..f4a1573 100644 --- a/default-config.json +++ b/default-config.json @@ -5,16 +5,16 @@ "fedora": "dnf upgrade" }, "extra_cmds": { - "arch": "aur sync --upgrades --rmdeps --sign --remove --verify" + "arch": "paru -Sua" }, "clean_cmds": { - "arch": "pacman -Rns $(pacman -Qdtq); exit 0", + "arch": "paru -c", "debian": "apt-get --purge autoremove", "fedora": "dnf autoremove" }, "shell_plugin_cmds": { "fish": "fisher update", - "zsh": "source $HOME/.config/zsh/.antidote/antidote.zsh && antidote update" + "zsh": "source $ZDOTDIR/.antidote/antidote.zsh && antidote update" }, "other_cmds": { "neovim": "nvim --headless -c 'autocmd User PackerComplete quitall' -c 'PackerSync'" diff --git a/hiccup.py b/hiccup.py new file mode 100755 index 0000000..b3f2120 --- /dev/null +++ b/hiccup.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess # nosec:b404 +import os + +CONFIG_FILE = os.path.join(os.environ["XDG_CONFIG_HOME"], "hiccup/config.json") +OS_RELEASE_PATH = "/etc/os-release" + + +class DistroNotSupportedError(Exception): + def __init__(self, name): + self.message = "{} isn't supported yet".format(name) + super().__init__(self.message) + + +class Distro: + def __init__(self, id: str, config_file: os.path): + try: + # read and store commands from config file + with open(config_file) as file: + data = json.load(file) + self.__system_update_cmds = data["system_update_cmds"] + self.__extra_cmds = data["extra_cmds"] + self.__clean_cmds = data["clean_cmds"] + self.__shell_plugin_cmds = data["shell_plugin_cmds"] + self.__other_cmds = data["other_cmds"] + except OSError: + raise OSError("no config file found!") + except json.JSONDecodeError: + raise json.JSONDecodeError("unable to parse json") + + self.id = id + + # get commands specific to current distro + if self.is_supported(): + self.update_cmd = self.get_update_cmd() + if self.has_clean_cmd(): + self.clean_cmd = self.get_clean_cmd() + if self.has_extra_cmd(): + self.extra_cmd = self.get_extra_cmd() + else: + raise DistroNotSupportedError(self.id) + + def __get_cmd(self, dct: dict): + return dct[self.id] + + # wrapper for subprocess.run that allows for easy privlage escalation, + # silencing, and variable shells + def __sys_cmd(self, cmd: str, shell="bash", silent=False, sudo=False): + args = list() + if silent: + cmd += " > /dev/null 2>&1" + if sudo: + args += ["/usr/bin/sudo"] + + args += [shell, "-c", cmd] + + return subprocess.run(args, check=True) # nosec:B603 + + # iterate through dict of commands, optionally allow for keys to + # determine the shell the command is run though + def __run_items(self, msg: str, dct: dict, name_as_shell=False): + shell = "bash" + for name, cmd in dct.items(): + print(msg.format(name)) + + if name_as_shell: + shell = name + self.__sys_cmd(cmd, shell=shell, silent=True) # nosec:B604 + + def is_supported(self): + return self.id in self.__system_update_cmds + + def has_clean_cmd(self): + return self.id in self.__clean_cmds + + def has_extra_cmd(self): + return self.id in self.__extra_cmds + + def get_update_cmd(self): + return self.__get_cmd(self.__system_update_cmds) + + def get_extra_cmd(self): + return self.__get_cmd(self.__extra_cmds) + + def get_clean_cmd(self): + return self.__get_cmd(self.__clean_cmds) + + def update_system(self): + self.__sys_cmd(self.update_cmd, sudo=True) + if self.has_extra_cmd(): + self.__sys_cmd(self.extra_cmd) + + def cleanup_system(self): + if self.has_clean_cmd(): + print("cleaning up system...") + return self.__sys_cmd(self.clean_cmd, sudo=True) + print("no cleanup command found for {}".format(self.id)) + + def update_shell_plugins(self): + msg = "updating {} plugins..." + return self.__run_items(msg, self.__shell_plugin_cmds, name_as_shell=True) + + def update_other(self): + msg = "updating {}..." + return self.__run_items(msg, self.__other_cmds) + + def update_all(self): + self.update_system() + self.update_shell_plugins() + self.update_other() + self.cleanup_system() + + +# reads id from an os-release file +def get_distro_id(filename: os.path): + with open(filename) as file: + for line in file.readlines(): + k, v = line.strip().split("=") + if k == "ID": + return v + + +def run(): + try: + current_distro = get_distro_id(OS_RELEASE_PATH) + except DistroNotSupportedError: + pass + distro = Distro(current_distro, CONFIG_FILE) + + parser = argparse.ArgumentParser( + description="a python script to help keep you up to date" + ) + parser.add_argument( + "--cleanonly", + "-c", + action="store_true", + default=False, + dest="clean_only", + help="cleanup unneeded dependencies", + ) + parser.add_argument( + "--systemonly", + "-s", + action="store_true", + default=False, + dest="system_only", + help="only update through the system's package manager", + ) + args = parser.parse_args() + + if args.clean_only: + return distro.cleanup_system() + if args.system_only: + return distro.update_system() + + return distro.update_all() + + +if __name__ == "__main__": + if os.geteuid() == 0: + print("please don't run this as root :(") + exit(1) + try: + run() + print("done!") + except Exception as e: + print(repr(e)) + exit(2) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2e79d63 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "hiccup" +version = "0.0.1" +authors = [ + { name = "getchoo", email="getchoo@tuta.io"}, +] +description = "a python script to help keep you up to date" +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", +] + +[project.urls] +"Homepage" = "https://github.com/getchoo/hiccup" +"Bug Tracker" = "https://github.com/getchoo/hiccup/issues" + +[project.scripts] +hiccup = "hiccup:run" + +[tool.hatch.build] +include = [ + "hiccup.py" +] \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a4f49f9 --- /dev/null +++ b/setup.py @@ -0,0 +1,2 @@ +import setuptools +setuptools.setup() -- cgit v1.2.3