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 <jeffery.to@gmail.com>
This commit is contained in:
parent
b354a7edbe
commit
5b3e4a4b3f
5 changed files with 279 additions and 0 deletions
42
lang/python/python-userpath/Makefile
Normal file
42
lang/python/python-userpath/Makefile
Normal file
|
@ -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 <jeffery.to@gmail.com>
|
||||
|
||||
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))
|
|
@ -0,0 +1,31 @@
|
|||
From 9175a0a97c7bc2eeb995e53d50a07be6a7e834f0 Mon Sep 17 00:00:00 2001
|
||||
From: Jeffery To <jeffery.to@gmail.com>
|
||||
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()]
|
|
@ -0,0 +1,89 @@
|
|||
From dffcc1c5823bcce10b420467db41e42ec41f4702 Mon Sep 17 00:00:00 2001
|
||||
From: Jeffery To <jeffery.to@gmail.com>
|
||||
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 = {
|
|
@ -0,0 +1,112 @@
|
|||
From 7823b9b39c486aedf830783329abdc3bd9664ba4 Mon Sep 17 00:00:00 2001
|
||||
From: Jeffery To <jeffery.to@gmail.com>
|
||||
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,
|
5
lang/python/python-userpath/test.sh
Normal file
5
lang/python/python-userpath/test.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
[ "$1" = python3-userpath ] || exit 0
|
||||
|
||||
userpath --version | grep -Fx "userpath, version $PKG_VERSION"
|
Loading…
Reference in a new issue