ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/subpack/lang/python/python-userpath/Makefile b/external/subpack/lang/python/python-userpath/Makefile
new file mode 100644
index 0000000..969a238
--- /dev/null
+++ b/external/subpack/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 <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))
diff --git a/external/subpack/lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch b/external/subpack/lang/python/python-userpath/patches/0001-Handle-OSErrors-when-running-show-path-commands.patch
new file mode 100644
index 0000000..3a412e6
--- /dev/null
+++ b/external/subpack/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 <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()]
diff --git a/external/subpack/lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch b/external/subpack/lang/python/python-userpath/patches/0001-Use-Sh-as-base-class-for-Bash-and-Zsh.patch
new file mode 100644
index 0000000..69dfde2
--- /dev/null
+++ b/external/subpack/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 <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 = {
diff --git a/external/subpack/lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch b/external/subpack/lang/python/python-userpath/patches/0002-Add-support-for-ash-Almquist-shell.patch
new file mode 100644
index 0000000..2c1132e
--- /dev/null
+++ b/external/subpack/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 <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,
diff --git a/external/subpack/lang/python/python-userpath/test.sh b/external/subpack/lang/python/python-userpath/test.sh
new file mode 100644
index 0000000..e87d325
--- /dev/null
+++ b/external/subpack/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"