From 5b3e4a4b3f3be3f9db6aed6a977a5fd40ca6f203 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Fri, 10 Nov 2023 14:14:48 +0800 Subject: [PATCH] python-userpath: Add new package The patches have been submitted upstream in https://github.com/ofek/userpath/pull/52 and https://github.com/ofek/userpath/pull/53. From the README: This is a tool for modifying a user's PATH. Signed-off-by: Jeffery To --- lang/python/python-userpath/Makefile | 42 +++++++ ...rors-when-running-show-path-commands.patch | 31 +++++ ...se-Sh-as-base-class-for-Bash-and-Zsh.patch | 89 ++++++++++++++ ...2-Add-support-for-ash-Almquist-shell.patch | 112 ++++++++++++++++++ lang/python/python-userpath/test.sh | 5 + 5 files changed, 279 insertions(+) create mode 100644 lang/python/python-userpath/Makefile create mode 100644 lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch create mode 100644 lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch create mode 100644 lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch create mode 100644 lang/python/python-userpath/test.sh diff --git a/lang/python/python-userpath/Makefile b/lang/python/python-userpath/Makefile new file mode 100644 index 0000000000..969a238b55 --- /dev/null +++ b/lang/python/python-userpath/Makefile @@ -0,0 +1,42 @@ +# +# Copyright (C) 2023 Jeffery To +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=python-userpath +PKG_VERSION:=1.9.1 +PKG_RELEASE:=1 + +PYPI_NAME:=userpath +PKG_HASH:=ce8176728d98c914b6401781bf3b23fccd968d1647539c8788c7010375e02796 + +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE.txt +PKG_MAINTAINER:=Jeffery To + +PKG_BUILD_DEPENDS:=python-hatchling/host + +include ../pypi.mk +include $(INCLUDE_DIR)/package.mk +include ../python3-package.mk + +define Package/python3-userpath + SECTION:=lang + CATEGORY:=Languages + SUBMENU:=Python + TITLE:=Cross-platform tool for modifying a user's PATH + URL:=https://github.com/ofek/userpath + DEPENDS:=+python3-light +python3-click +python3-psutil +endef + +define Package/python3-userpath/description +This is a tool for modifying a user's PATH. +endef + +$(eval $(call Py3Package,python3-userpath)) +$(eval $(call BuildPackage,python3-userpath)) +$(eval $(call BuildPackage,python3-userpath-src)) diff --git a/lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch b/lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch new file mode 100644 index 0000000000..3a412e62f6 --- /dev/null +++ b/lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch @@ -0,0 +1,31 @@ +From 9175a0a97c7bc2eeb995e53d50a07be6a7e834f0 Mon Sep 17 00:00:00 2001 +From: Jeffery To +Date: Thu, 9 Nov 2023 14:20:58 +0800 +Subject: [PATCH] Handle OSErrors when running show path commands + +Bash may not always be installed, for example on OpenWrt, and attempting +to call the show path commands for Bash will cause a FileNotFoundError +to be raised. + +This wraps the subprocess call with a try statement and returns the +empty string in the case of an OSError. +--- + userpath/utils.py | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/userpath/utils.py ++++ b/userpath/utils.py +@@ -30,8 +30,11 @@ def ensure_parent_dir_exists(path): + + + def get_flat_output(command, sep=os.pathsep, **kwargs): +- process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) +- output = process.communicate()[0].decode(locale.getpreferredencoding(False)).strip() ++ try: ++ process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) ++ output = process.communicate()[0].decode(locale.getpreferredencoding(False)).strip() ++ except OSError: ++ return '' + + # We do this because the output may contain new lines. + lines = [line.strip() for line in output.splitlines()] diff --git a/lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch b/lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch new file mode 100644 index 0000000000..69dfde2dd8 --- /dev/null +++ b/lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch @@ -0,0 +1,89 @@ +From dffcc1c5823bcce10b420467db41e42ec41f4702 Mon Sep 17 00:00:00 2001 +From: Jeffery To +Date: Thu, 9 Nov 2023 17:48:50 +0800 +Subject: [PATCH 1/2] Use Sh as base class for Bash and Zsh + +--- + userpath/shells.py | 41 ++++++++++++++++++++++++++--------------- + 1 file changed, 26 insertions(+), 15 deletions(-) + +--- a/userpath/shells.py ++++ b/userpath/shells.py +@@ -12,24 +12,36 @@ class Shell(object): + + + class Sh(Shell): +- def config(self, location, front=True): ++ name = 'sh' ++ ++ def _config_contents(self, location, front=True): + head, tail = (location, '$PATH') if front else ('$PATH', location) + new_path = '{}{}{}'.format(head, pathsep, tail) ++ return 'export PATH="{}"'.format(new_path) ++ ++ def config(self, location, front=True): ++ contents = self._config_contents(location, front=front) ++ return {path.join(self.home, '.profile'): contents} + +- return {path.join(self.home, '.profile'): 'PATH="{}"'.format(new_path)} ++ @classmethod ++ def _interactive_show_path_command(cls): ++ return [cls.name, '-i', '-c', 'echo $PATH'] ++ ++ @classmethod ++ def _interactive_login_show_path_command(cls): ++ return [cls.name, '-i', '-l', '-c', 'echo $PATH'] + + @classmethod + def show_path_commands(cls): + # TODO: Find out what file influences non-login shells. The issue may simply be our Docker setup. +- return [['sh', '-i', '-l', '-c', 'echo $PATH']] ++ return [cls._interactive_login_show_path_command()] + + +-class Bash(Shell): +- def config(self, location, front=True): +- head, tail = (location, '$PATH') if front else ('$PATH', location) +- new_path = '{}{}{}'.format(head, pathsep, tail) +- contents = 'export PATH="{}"'.format(new_path) ++class Bash(Sh): ++ name = 'bash' + ++ def config(self, location, front=True): ++ contents = self._config_contents(location, front=front) + configs = {path.join(self.home, '.bashrc'): contents} + + # https://github.com/ofek/userpath/issues/3#issuecomment-492491977 +@@ -50,7 +62,7 @@ class Bash(Shell): + + @classmethod + def show_path_commands(cls): +- return [['bash', '-i', '-c', 'echo $PATH'], ['bash', '-i', '-l', '-c', 'echo $PATH']] ++ return [cls._interactive_show_path_command(), cls._interactive_login_show_path_command()] + + + class Fish(Shell): +@@ -88,18 +100,17 @@ class Xonsh(Shell): + return [['xonsh', '-i', '-c', command], ['xonsh', '-i', '--login', '-c', command]] + + +-class Zsh(Shell): +- def config(self, location, front=True): +- head, tail = (location, '$PATH') if front else ('$PATH', location) +- new_path = '{}{}{}'.format(head, pathsep, tail) +- contents = 'export PATH="{}"'.format(new_path) ++class Zsh(Sh): ++ name = 'zsh' + ++ def config(self, location, front=True): ++ contents = self._config_contents(location, front=front) + zdotdir = environ.get('ZDOTDIR', self.home) + return {path.join(zdotdir, '.zshrc'): contents, path.join(zdotdir, '.zprofile'): contents} + + @classmethod + def show_path_commands(cls): +- return [['zsh', '-i', '-c', 'echo $PATH'], ['zsh', '-i', '-l', '-c', 'echo $PATH']] ++ return [cls._interactive_show_path_command(), cls._interactive_login_show_path_command()] + + + SHELLS = { diff --git a/lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch b/lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch new file mode 100644 index 0000000000..2c1132eaa2 --- /dev/null +++ b/lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch @@ -0,0 +1,112 @@ +From 7823b9b39c486aedf830783329abdc3bd9664ba4 Mon Sep 17 00:00:00 2001 +From: Jeffery To +Date: Thu, 9 Nov 2023 17:51:21 +0800 +Subject: [PATCH 2/2] Add support for ash (Almquist shell) + +--- + tests/docker/debian | 2 +- + tests/test_ash.py | 65 +++++++++++++++++++++++++++++++++++++++++++++ + userpath/shells.py | 5 ++++ + 3 files changed, 71 insertions(+), 1 deletion(-) + create mode 100644 tests/test_ash.py + +--- a/tests/docker/debian ++++ b/tests/docker/debian +@@ -2,7 +2,7 @@ ARG PYTHON_VERSION + FROM python:${PYTHON_VERSION} + + RUN apt-get update \ +- && apt-get --no-install-recommends -y install fish zsh ++ && apt-get --no-install-recommends -y install ash fish zsh + + COPY requirements.txt / + RUN pip install -r requirements.txt +--- /dev/null ++++ b/tests/test_ash.py +@@ -0,0 +1,65 @@ ++import pytest ++import userpath ++ ++from .utils import SKIP_WINDOWS_CI, get_random_path ++ ++SHELL_NAME = 'ash' ++ ++pytestmark = [SKIP_WINDOWS_CI, pytest.mark.ash] ++ ++ ++@pytest.mark.usefixtures('shell_test') ++class TestDebian(object): ++ DOCKERFILE = 'debian' ++ ++ def test_prepend(self, request, shell_test): ++ if shell_test is None: ++ location = get_random_path() ++ assert not userpath.in_current_path(location) ++ assert userpath.prepend(location, check=True) ++ assert userpath.in_new_path(location) ++ assert userpath.need_shell_restart(location) ++ else: ++ process = shell_test(request.node.name) ++ stdout, stderr = process.communicate() ++ ++ assert process.returncode == 0, (stdout + stderr).decode('utf-8') ++ ++ def test_prepend_multiple(self, request, shell_test): ++ if shell_test is None: ++ locations = [get_random_path(), get_random_path()] ++ assert not userpath.in_current_path(locations) ++ assert userpath.prepend(locations, check=True) ++ assert userpath.in_new_path(locations) ++ assert userpath.need_shell_restart(locations) ++ else: ++ process = shell_test(request.node.name) ++ stdout, stderr = process.communicate() ++ ++ assert process.returncode == 0, (stdout + stderr).decode('utf-8') ++ ++ def test_append(self, request, shell_test): ++ if shell_test is None: ++ location = get_random_path() ++ assert not userpath.in_current_path(location) ++ assert userpath.append(location, check=True) ++ assert userpath.in_new_path(location) ++ assert userpath.need_shell_restart(location) ++ else: ++ process = shell_test(request.node.name) ++ stdout, stderr = process.communicate() ++ ++ assert process.returncode == 0, (stdout + stderr).decode('utf-8') ++ ++ def test_append_multiple(self, request, shell_test): ++ if shell_test is None: ++ locations = [get_random_path(), get_random_path()] ++ assert not userpath.in_current_path(locations) ++ assert userpath.append(locations, check=True) ++ assert userpath.in_new_path(locations) ++ assert userpath.need_shell_restart(locations) ++ else: ++ process = shell_test(request.node.name) ++ stdout, stderr = process.communicate() ++ ++ assert process.returncode == 0, (stdout + stderr).decode('utf-8') +--- a/userpath/shells.py ++++ b/userpath/shells.py +@@ -37,6 +37,10 @@ class Sh(Shell): + return [cls._interactive_login_show_path_command()] + + ++class Ash(Sh): ++ name = 'ash' ++ ++ + class Bash(Sh): + name = 'bash' + +@@ -114,6 +118,7 @@ class Zsh(Sh): + + + SHELLS = { ++ 'ash': Ash, + 'bash': Bash, + 'fish': Fish, + 'sh': Sh, diff --git a/lang/python/python-userpath/test.sh b/lang/python/python-userpath/test.sh new file mode 100644 index 0000000000..e87d325ba4 --- /dev/null +++ b/lang/python/python-userpath/test.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +[ "$1" = python3-userpath ] || exit 0 + +userpath --version | grep -Fx "userpath, version $PKG_VERSION"