summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Hofstaedtler <[email protected]>2024-12-29 02:16:44 +0100
committergit-ubuntu importer <[email protected]>2024-12-29 04:35:01 +0000
commit423c2858a1c5d19d976e9fa5ea3a77275c2e8d9c (patch)
tree7a9335f42d7d5e9a845996ce704364c49f3ad364
parentb5672701f4fd6080aa0eacb988df2a277612300b (diff)
Imported using git-ubuntu import.
Notes
Notes: * Team upload * Use PyPI as source for upstream tarballs * New upstream version 2.0.0 (Closes: #1057841, #1087405) * Drop now unnecessary patches
-rw-r--r--.github/workflows/pre-commit.yml14
-rw-r--r--.github/workflows/publish.yml40
-rw-r--r--.gitignore3
-rw-r--r--.pre-commit-config.yaml23
-rw-r--r--COPYING1
-rw-r--r--Makefile49
-rw-r--r--PKG-INFO55
-rw-r--r--README.md2
-rw-r--r--configshell/__init__.py17
-rw-r--r--configshell/console.py47
-rw-r--r--configshell/log.py16
-rw-r--r--configshell/node.py439
-rw-r--r--configshell/prefs.py35
-rw-r--r--configshell/shell.py240
l---------configshell_fb1
-rw-r--r--configshell_fb.py12
-rw-r--r--debian/changelog9
-rw-r--r--debian/control30
-rw-r--r--debian/patches/replace-getargspec-with-getfullargspec.patch31
-rw-r--r--debian/patches/replace-more-occurrences-of-getargspec-with-getfullargspec.patch70
-rw-r--r--debian/patches/series2
-rw-r--r--debian/watch3
-rwxr-xr-xexamples/myshell6
-rw-r--r--pyproject.toml100
-rwxr-xr-xsetup.py54
25 files changed, 647 insertions, 652 deletions
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..7f579af
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,14 @@
+name: pre-commit
+
+on:
+ pull_request:
+ push:
+ branches: [master]
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ - uses: pre-commit/[email protected]
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..7235fce
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,40 @@
+name: pypi-release
+
+on:
+ release:
+ types:
+ - published
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: 'Branch, tag or SHA to checkout'
+ required: true
+ default: 'master'
+
+jobs:
+ pypi-publish:
+ runs-on: ubuntu-latest
+
+ permissions:
+ id-token: write # Needed for trusted publishing
+
+ environment:
+ name: pypi
+ url: https://pypi.org/p/configshell-fb
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.ref || github.ref }}
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Build a binary wheel and a source tarball
+ run: |
+ python -m pip install hatch
+ hatch build
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitignore b/.gitignore
index 93e9303..ad46a87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@ debian/tmp/
*.spec
*.pyc
rtslib-*
+venv/
+.idea
+*egg-info/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..209c747
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,23 @@
+repos:
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
+ rev: v0.6.4
+ hooks:
+ - id: ruff
+ args: [--fix]
+
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.6.0
+ hooks:
+ - id: check-case-conflict
+ - id: check-ast
+ - id: check-docstring-first
+ - id: check-case-conflict
+ - id: check-merge-conflict
+ - id: check-builtin-literals
+ - id: check-docstring-first
+ - id: check-merge-conflict
+ - id: check-toml
+ - id: debug-statements
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ args: [--markdown-linebreak-ext=md]
diff --git a/COPYING b/COPYING
index 68c771a..67db858 100644
--- a/COPYING
+++ b/COPYING
@@ -173,4 +173,3 @@
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
-
diff --git a/Makefile b/Makefile
deleted file mode 100644
index a91fdc8..0000000
--- a/Makefile
+++ /dev/null
@@ -1,49 +0,0 @@
-PKGNAME = configshell-fb
-NAME = configshell
-GIT_BRANCH = $$(git branch | grep \* | tr -d \*)
-VERSION = $$(basename $$(git describe --tags | tr - . | sed 's/^v//'))
-
-all:
- @echo "Usage:"
- @echo
- @echo " make release - Generates the release tarball."
- @echo
- @echo " make clean - Cleanup the local repository build files."
- @echo " make cleanall - Also remove dist/*"
-
-clean:
- @rm -fv ${NAME}/*.pyc ${NAME}/*.html
- @rm -frv doc
- @rm -frv ${NAME}.egg-info MANIFEST build
- @rm -frv results
- @rm -frv ${PKGNAME}-*
- @echo "Finished cleanup."
-
-cleanall: clean
- @rm -frv dist
-
-release: build/release-stamp
-build/release-stamp:
- @mkdir -p build
- @echo "Exporting the repository files..."
- @git archive ${GIT_BRANCH} --prefix ${PKGNAME}-${VERSION}/ \
- | (cd build; tar xfp -)
- @echo "Cleaning up the target tree..."
- @rm -f build/${PKGNAME}-${VERSION}/Makefile
- @rm -f build/${PKGNAME}-${VERSION}/.gitignore
- @echo "Fixing version string..."
- @sed -i "s/__version__ = .*/__version__ = '${VERSION}'/g" \
- build/${PKGNAME}-${VERSION}/${NAME}/__init__.py
- @find build/${PKGNAME}-${VERSION}/ -exec \
- touch -t $$(date -d @$$(git show -s --format="format:%at") \
- +"%Y%m%d%H%M.%S") {} \;
- @mkdir -p dist
- @cd build; tar -c --owner=0 --group=0 --numeric-owner \
- --format=gnu -b20 --quoting-style=escape \
- -f ../dist/${PKGNAME}-${VERSION}.tar \
- $$(find ${PKGNAME}-${VERSION} -type f | sort)\
- $$(find ${PKGNAME}-${VERSION} -type l | sort)
- @gzip -6 -n dist/${PKGNAME}-${VERSION}.tar
- @echo "Generated release tarball:"
- @echo " $$(ls dist/${PKGNAME}-${VERSION}.tar.gz)"
- @touch build/release-stamp
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..17ba48a
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,55 @@
+Metadata-Version: 2.3
+Name: configshell-fb
+Version: 2.0.0
+Summary: A framework to implement simple but nice CLIs.
+Project-URL: Homepage, http://github.com/open-iscsi/configshell-fb
+Author-email: Andy Grover <[email protected]>, Jerome Martin <[email protected]>
+Maintainer-email: Maurizio Lombardi <[email protected]>
+License-Expression: Apache-2.0
+License-File: COPYING
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.12
+Requires-Python: >=3.9
+Requires-Dist: pyparsing<4,>=2.4.7
+Description-Content-Type: text/markdown
+
+configshell-fb
+==============
+
+A Python library for building configuration shells
+--------------------------------------------------
+configshell-fb is a Python library that provides a framework
+for building simple but nice CLI-based applications.
+
+configshell-fb development
+--------------------------
+configshell-fb is licensed under the Apache 2.0 license. Contributions are welcome.
+
+Since configshell-fb is used most often with targetcli-fb, the
+targetcli-fb mailing should be used for configshell-fb discussion.
+
+ * Mailing list: [targetcli-fb-devel](https://lists.fedorahosted.org/mailman/listinfo/targetcli-fb-devel)
+ * Source repo: [GitHub](https://github.com/open-iscsi/configshell-fb)
+ * Bugs: [GitHub](https://github.com/open-iscsi/configshell-fb/issues) or [Trac](https://fedorahosted.org/targetcli-fb/)
+ * Tarballs: [fedorahosted](https://fedorahosted.org/releases/t/a/targetcli-fb/)
+
+Packages
+--------
+configshell-fb is packaged for a number of Linux distributions
+including RHEL,
+[Fedora](https://apps.fedoraproject.org/packages/python-configshell),
+openSUSE, Arch Linux,
+[Gentoo](https://packages.gentoo.org/packages/dev-python/configshell-fb), and
+[Debian](https://tracker.debian.org/pkg/python-configshell-fb).
+
+"fb" -- "free branch"
+---------------------
+
+configshell-fb is a fork of the "configshell" code written by
+RisingTide Systems. The "-fb" differentiates between the original and
+this version. Please ensure to use either all "fb" versions of the
+targetcli components -- targetcli, rtslib, and configshell, or stick
+with all non-fb versions, since they are no longer strictly
+compatible.
diff --git a/README.md b/README.md
index 09d6491..308bb2d 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,6 @@ A Python library for building configuration shells
configshell-fb is a Python library that provides a framework
for building simple but nice CLI-based applications.
-This runs with Python 2 and 2to3 is run by setup.py to run on Python 3.
-
configshell-fb development
--------------------------
configshell-fb is licensed under the Apache 2.0 license. Contributions are welcome.
diff --git a/configshell/__init__.py b/configshell/__init__.py
index 26c7909..9de7a25 100644
--- a/configshell/__init__.py
+++ b/configshell/__init__.py
@@ -15,18 +15,17 @@ License for the specific language governing permissions and limitations
under the License.
'''
-if __name__ == 'configshell-fb':
- from warnings import warn
- warn("'configshell' package name for configshell-fb is deprecated, please"
- + " instead import 'configshell_fb'", UserWarning, stacklevel=2)
-
from .console import Console
from .log import Log
from .node import ConfigNode, ExecutionError
from .prefs import Prefs
from .shell import ConfigShell
-__version__ = '1.1.28'
-__url__ = 'http://github.com/open-iscsi/configshell-fb'
-__description__ = 'A framework to implement simple but nice CLIs.'
-__license__ = 'Apache 2.0'
+__all__ = [
+ 'Console',
+ 'Log',
+ 'ConfigNode',
+ 'ExecutionError',
+ 'Prefs',
+ 'ConfigShell',
+]
diff --git a/configshell/console.py b/configshell/console.py
index 8f72fa0..de99a76 100644
--- a/configshell/console.py
+++ b/configshell/console.py
@@ -15,18 +15,18 @@ License for the specific language governing permissions and limitations
under the License.
'''
-from fcntl import ioctl
import re
-import six
import struct
import sys
-from termios import TIOCGWINSZ, TCSADRAIN, tcsetattr, tcgetattr
import textwrap
import tty
+from fcntl import ioctl
+from termios import TCSADRAIN, TIOCGWINSZ, tcgetattr, tcsetattr
from .prefs import Prefs
-class Console(object):
+
+class Console:
'''
Implements various utility methods providing a console UI support toolkit,
most notably an epytext-to-console text renderer using ANSI escape
@@ -36,7 +36,7 @@ class Console(object):
_escape = '\033['
_ansi_format = _escape + '%dm%s'
_ansi_reset = _escape + '0m'
- _re_ansi_seq = re.compile('(\033\[..?m)')
+ _re_ansi_seq = re.compile('(\033\\[..?m)')
_ansi_styles = {'bold': 1,
'underline': 4,
@@ -89,8 +89,8 @@ class Console(object):
tcsetattr(self._stdin, TCSADRAIN, attributes)
if reply_terminator is not None:
reply = reply[:-len(reply_terminator)]
- reply = reply.replace(self._escape, '').split(';')
- return reply
+ return reply.replace(self._escape, '').split(';')
+ return None
def get_width(self):
'''
@@ -101,11 +101,10 @@ class Console(object):
winsize = struct.pack("HHHH", 0, 0, 0, 0)
winsize = ioctl(self._stdout.fileno(), TIOCGWINSZ, winsize)
width = struct.unpack("HHHH", winsize)[1]
- except IOError:
+ except OSError:
width = self._max_width
else:
- if width > self._max_width:
- width = self._max_width
+ width = min(width, self._max_width)
return width
@@ -167,7 +166,7 @@ class Console(object):
if text[-index] == '\n':
clean_text = text[:-index]
if index != 1:
- clean_text += text[-index+1:]
+ clean_text += text[-index + 1:]
break
else:
clean_text = text
@@ -195,9 +194,7 @@ class Console(object):
break
text = text[i:]
text = textwrap.dedent(text)
- text = '\n' * i + text
-
- return text
+ return '\n' * i + text
def render_text(self, text, fgcolor=None, bgcolor=None, styles=None,
open_end=False, todefault=False):
@@ -219,9 +216,8 @@ class Console(object):
@type todefault: bool
'''
if self.prefs['color_mode'] and self._stdout.isatty():
- if fgcolor is None:
- if self.prefs['color_default']:
- fgcolor = self.prefs['color_default']
+ if fgcolor is None and self.prefs['color_default']:
+ fgcolor = self.prefs['color_default']
if fgcolor is not None:
text = self._ansi_format % (self._ansi_fgcolors[fgcolor], text)
if bgcolor is not None:
@@ -266,21 +262,20 @@ class Console(object):
'''
right = self.get_width()
if splitchars:
- chunks = re.split(r'( +|\n|[^ \n%s]*[%s])' %
- (re.escape(splitchars), re.escape(splitchars)),
+ chunks = re.split(rf'( +|\n|[^ \n{re.escape(splitchars)}]*[{re.escape(splitchars)}])',
text.expandtabs())
else:
chunks = re.split(r'( +|\n)', text.expandtabs())
- result = [' '*(indent-startindex)]
+ result = [' ' * (indent - startindex)]
charindex = max(indent, startindex)
current_style = ''
- for chunknum, chunk in enumerate(chunks):
- chunk_groups = re.split(self._re_ansi_seq, chunk)
+ for chunk in chunks:
+ chunk_groups = self._re_ansi_seq.split(chunk)
chunk_text = ''
next_style = current_style
for group in chunk_groups:
- if re.match(self._re_ansi_seq, group) is None:
+ if self._re_ansi_seq.match(group) is None:
chunk_text += group
else:
next_style += group
@@ -290,9 +285,9 @@ class Console(object):
or chunk == '\n':
result[-1] = result[-1].rstrip()
result.append(self.render_text(
- '\n' + ' '*indent + current_style, open_end=True))
+ '\n' + ' ' * indent + current_style, open_end=True))
charindex = indent
- if chunk[:1] not in ('\n', ' '):
+ if chunk[:1] not in {'\n', ' '}:
result.append(chunk)
charindex += chunk_len
else:
@@ -301,4 +296,4 @@ class Console(object):
current_style = next_style.split(self._ansi_reset)[-1]
- return ''.join(result).rstrip()+'\n'
+ return ''.join(result).rstrip() + '\n'
diff --git a/configshell/log.py b/configshell/log.py
index 003d862..fefd6bd 100644
--- a/configshell/log.py
+++ b/configshell/log.py
@@ -16,14 +16,15 @@ under the License.
'''
import inspect
-import os
import time
import traceback
+from pathlib import Path
from .console import Console
from .prefs import Prefs
-class Log(object):
+
+class Log:
'''
Implements a file and console logger using python's logging facility.
Log levels are, in raising criticality:
@@ -86,12 +87,7 @@ class Log(object):
date_fields[3], date_fields[4], date_fields[5])
if self.prefs['logfile']:
- path = os.path.expanduser(self.prefs['logfile'])
- handle = open(path, 'a')
- try:
- handle.write("[%s] %s %s\n" % (level, date, msg))
- finally:
- handle.close()
+ Path(self.prefs['logfile']).write_text(f"[{level}] {date} {msg}\n")
def _log(self, level, msg):
'''
@@ -110,7 +106,7 @@ class Log(object):
if self.prefs["color_mode"]:
msg = self.con.render_text(msg, self.colors[level])
else:
- msg = "%s: %s" % (level.capitalize(), msg)
+ msg = f"{level.capitalize()}: {msg}"
error = False
if self.levels.index(level) <= self.levels.index('error'):
error = True
@@ -136,7 +132,7 @@ class Log(object):
'''
trace = traceback.format_exc().rstrip()
if msg:
- trace += '\n%s' % msg
+ trace += f'\n{msg}'
self._log('error', trace)
def verbose(self, msg):
diff --git a/configshell/node.py b/configshell/node.py
index 6af8642..fdf4de2 100644
--- a/configshell/node.py
+++ b/configshell/node.py
@@ -14,15 +14,16 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
'''
-
+import curses
import inspect
import re
-import six
+
class ExecutionError(Exception):
pass
-class ConfigNode(object):
+
+class ConfigNode:
'''
The ConfigNode class defines a common skeleton to be used by specific
implementation. It is "purely virtual" (sorry for using non-pythonic
@@ -83,26 +84,24 @@ class ConfigNode(object):
@type shell: ConfigShell
'''
self._name = name
- self._children = set([])
+ self._children = set()
if parent is None:
if shell is None:
raise ValueError("A root ConfigNode must have a shell.")
- else:
- self._parent = None
- self._shell = shell
- shell.attach_root_node(self)
+
+ self._parent = None
+ self._shell = shell
+ shell.attach_root_node(self)
+ elif shell is None:
+ self._parent = parent
+ self._shell = None
else:
- if shell is None:
- self._parent = parent
- self._shell = None
- else:
- raise ValueError("A non-root ConfigNode can't have a shell.")
+ raise ValueError("A non-root ConfigNode can't have a shell.")
if self._parent is not None:
for sibling in self._parent._children:
if sibling.name == name:
- raise ValueError("Name '%s' already used by a sibling."
- % self._name)
+ raise ValueError(f"Name '{self._name}' already used by a sibling.")
self._parent._children.add(self)
self._configuration_groups = {}
@@ -190,8 +189,7 @@ class ConfigNode(object):
try:
value = int(value)
except ValueError:
- raise ValueError("Syntax error, '%s' is not a %s."
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not a {syntax}.")
else:
return value
@@ -230,8 +228,7 @@ class ConfigNode(object):
try:
value = str(value)
except ValueError:
- raise ValueError("Syntax error, '%s' is not a %s."
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not a {syntax}.")
else:
return value
@@ -269,8 +266,7 @@ class ConfigNode(object):
elif value.lower() == 'false':
return False
else:
- raise ValueError("Syntax error, '%s' is not %s."
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not {syntax}.")
def ui_type_loglevel(self, value=None, enum=False, reverse=False):
'''
@@ -304,8 +300,7 @@ class ConfigNode(object):
elif value in type_enum:
return value
else:
- raise ValueError("Syntax error, '%s' is not %s"
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not {syntax}")
def ui_type_color(self, value=None, enum=False, reverse=False):
'''
@@ -329,7 +324,7 @@ class ConfigNode(object):
else:
return 'default'
- type_enum = self.shell.con.colors + ['default']
+ type_enum = [*self.shell.con.colors, 'default']
syntax = '|'.join(type_enum)
if value is None:
if enum:
@@ -341,8 +336,7 @@ class ConfigNode(object):
elif value in type_enum:
return value
else:
- raise ValueError("Syntax error, '%s' is not %s"
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not {syntax}")
def ui_type_colordefault(self, value=None, enum=False, reverse=False):
'''
@@ -366,7 +360,7 @@ class ConfigNode(object):
else:
return 'none'
- type_enum = self.shell.con.colors + ['none']
+ type_enum = [*self.shell.con.colors, 'none']
syntax = '|'.join(type_enum)
if value is None:
if enum:
@@ -378,8 +372,7 @@ class ConfigNode(object):
elif value in type_enum:
return value
else:
- raise ValueError("Syntax error, '%s' is not %s"
- % (value, syntax))
+ raise ValueError(f"Syntax error, '{value}' is not {syntax}")
# User interface get/set methods
@@ -425,7 +418,7 @@ class ConfigNode(object):
'''
return self.shell.prefs[parameter]
- def ui_eval_param(self, ui_value, type, default):
+ def ui_eval_param(self, ui_value, type, default): # noqa: A002
'''
Evaluates a user-provided parameter value using a given type helper.
If the parameter value is None, the default will be returned. If the
@@ -434,8 +427,8 @@ class ConfigNode(object):
@param ui_value: The user provided parameter value.
@type ui_value: str
- @param type: The ui_type to be used
- @type type: str
+ @param type_: The ui_type to be used
+ @type type_: str
@param default: The default value to return.
@type default: any
@return: The evaluated parameter value.
@@ -453,11 +446,11 @@ class ConfigNode(object):
else:
return value
- def get_type_method(self, type):
+ def get_type_method(self, type): # noqa: A002
'''
Returns the type helper method matching the type name.
'''
- return getattr(self, "%s%s" % (self.ui_type_method_prefix, type))
+ return getattr(self, f"{self.ui_type_method_prefix}{type}")
# User interface commands
@@ -481,49 +474,46 @@ class ConfigNode(object):
self.shell.con.epy_write('''
AVAILABLE CONFIGURATION GROUPS
==============================
- %s
- ''' % ' '.join(self.list_config_groups()))
+ {}\n
+ '''.format(' '.join(self.list_config_groups())))
elif not parameter:
if group not in self.list_config_groups():
- raise ExecutionError("Unknown configuration group: %s" % group)
+ raise ExecutionError(f"Unknown configuration group: {group}")
- section = "%s CONFIG GROUP" % group.upper()
+ section = f"{group.upper()} CONFIG GROUP"
underline1 = ''.ljust(len(section), '=')
parameters = ''
for p_name in self.list_group_params(group, writable=True):
p_def = self.get_group_param(group, p_name)
type_method = self.get_type_method(p_def['type'])
- p_name = "%s=%s" % (p_def['name'], p_def['type'])
+ p_name = f"{p_def['name']}={p_def['type']}"
underline2 = ''.ljust(len(p_name), '-')
- parameters += '%s\n%s\n%s\n\n' \
- % (p_name, underline2, p_def['description'])
- self.shell.con.epy_write('''%s\n%s\n%s\n'''
- % (section, underline1, parameters))
+ parameters += f"{p_name}\n{underline2}\n{p_def['description']}\n\n"
+ self.shell.con.epy_write(f"{section}\n{underline1}\n{parameters}")
elif group not in self.list_config_groups():
- raise ExecutionError("Unknown configuration group: %s" % group)
+ raise ExecutionError(f"Unknown configuration group: {group}")
- for param, value in six.iteritems(parameter):
+ for param, value in parameter.items():
if param not in self.list_group_params(group):
- raise ExecutionError("Unknown parameter %s in group '%s'."
- % (param, group))
+ raise ExecutionError(f"Unknown parameter {param} in group '{group}'.")
p_def = self.get_group_param(group, param)
type_method = self.get_type_method(p_def['type'])
if not p_def['writable']:
- raise ExecutionError("Parameter %s is read-only." % param)
+ raise ExecutionError(f"Parameter {param} is read-only.")
try:
value = type_method(value)
except ValueError as msg:
- raise ExecutionError("Not setting %s! %s" % (param, msg))
+ raise ExecutionError(f"Not setting {param}! {msg}")
group_setter = self.get_group_setter(group)
group_setter(param, value)
group_getter = self.get_group_getter(group)
value = group_getter(param)
value = type_method(value, reverse=True)
- self.shell.con.display("Parameter %s is now '%s'." % (param, value))
+ self.shell.con.display(f"Parameter {param} is now '{value}'.")
def ui_complete_set(self, parameters, text, current_param):
'''
@@ -539,8 +529,9 @@ class ConfigNode(object):
'''
completions = []
- self.shell.log.debug("Called with params=%s, text='%s', current='%s'"
- % (str(parameters), text, current_param))
+ self.shell.log.debug(f"Called with params={parameters!s}, "
+ f"text='{text}', "
+ f"current='{current_param}'")
if current_param == 'group':
completions = [group for group in self.list_config_groups()
@@ -567,7 +558,7 @@ class ConfigNode(object):
if len(completions) == 1 and not completions[0].endswith('='):
completions = [completions[0] + ' ']
- self.shell.log.debug("Returning completions %s." % str(completions))
+ self.shell.log.debug(f"Returning completions {completions!s}.")
return completions
def ui_command_get(self, group=None, *parameter):
@@ -589,13 +580,13 @@ class ConfigNode(object):
self.shell.con.epy_write('''
AVAILABLE CONFIGURATION GROUPS
==============================
- %s
- ''' % ' '.join(self.list_config_groups()))
+ {}\n
+ '''.format(' '.join(self.list_config_groups())))
elif not parameter:
if group not in self.list_config_groups():
- raise ExecutionError("Unknown configuration group: %s" % group)
+ raise ExecutionError(f"Unknown configuration group: {group}")
- section = "%s CONFIG GROUP" % group.upper()
+ section = f"{group.upper()} CONFIG GROUP"
underline1 = ''.ljust(len(section), '=')
parameters = ''
params = [self.get_group_param(group, p_name)
@@ -605,23 +596,20 @@ class ConfigNode(object):
value = group_getter(p_def['name'])
type_method = self.get_type_method(p_def['type'])
value = type_method(value, reverse=True)
- param = "%s=%s" % (p_def['name'], value)
+ param = f"{p_def['name']}={value}"
if p_def['writable'] is False:
param += " [ro]"
underline2 = ''.ljust(len(param), '-')
- parameters += '%s\n%s\n%s\n\n' \
- % (param, underline2, p_def['description'])
+ parameters += f"{param}\n{underline2}\n{p_def['description']}\n\n"
- self.shell.con.epy_write('''%s\n%s\n%s\n'''
- % (section, underline1, parameters))
+ self.shell.con.epy_write(f"{section}\n{underline1}\n{parameters}")
elif group not in self.list_config_groups():
- raise ExecutionError("Unknown configuration group: %s" % group)
+ raise ExecutionError(f"Unknown configuration group: {group}")
for param in parameter:
if param not in self.list_group_params(group):
- raise ExecutionError("No parameter '%s' in group '%s'."
- % (param, group))
+ raise ExecutionError(f"No parameter '{param}' in group '{group}'.")
self.shell.log.debug("About to get the parameter's value.")
group_getter = self.get_group_getter(group)
@@ -629,12 +617,8 @@ class ConfigNode(object):
p_def = self.get_group_param(group, param)
type_method = self.get_type_method(p_def['type'])
value = type_method(value, reverse=True)
- if p_def['writable']:
- writable = ""
- else:
- writable = "[ro]"
- self.shell.con.display("%s=%s %s"
- % (param, value, writable))
+ writable = '' if p_def['writable'] else '[ro]'
+ self.shell.con.display(f"{param}={value} {writable}")
def ui_complete_get(self, parameters, text, current_param):
'''
@@ -650,8 +634,9 @@ class ConfigNode(object):
'''
completions = []
- self.shell.log.debug("Called with params=%s, text='%s', current='%s'"
- % (str(parameters), text, current_param))
+ self.shell.log.debug(f"Called with params={parameters!s}, "
+ f"text='{text}', "
+ f"current='{current_param}'")
if current_param == 'group':
completions = [group for group in self.list_config_groups()
@@ -669,7 +654,7 @@ class ConfigNode(object):
if len(completions) == 1 and not completions[0].endswith('='):
completions = [completions[0] + ' ']
- self.shell.log.debug("Returning completions %s." % str(completions))
+ self.shell.log.debug(f"Returning completions {completions!s}.")
return completions
def ui_command_ls(self, path=None, depth=None):
@@ -740,9 +725,7 @@ class ConfigNode(object):
else:
root_call = False
- if do_list:
- color = None
- elif not level % 3:
+ if do_list or not level % 3:
color = None
elif not (level - 1) % 3:
color = 'blue'
@@ -832,7 +815,7 @@ class ConfigNode(object):
line += name
if self.shell.prefs['tree_status_mode']:
- line += ' %s%s' % (pad, summary)
+ line += f' {pad}{summary}'
lines.append(line)
paths.append(root.path)
@@ -842,14 +825,14 @@ class ConfigNode(object):
and not do_list:
tree = ''
for child in children:
- tree = tree + self._render_tree(child, [False], depth)
+ tree += self._render_tree(child, [False], depth)
else:
tree = line + '\n'
if depth is None or depth > 0:
if depth is not None:
- depth = depth - 1
+ depth -= 1
for i in range(len(children)):
- margin.append(i<len(children)-1)
+ margin.append(i < len(children) - 1)
if do_list:
new_lines, new_paths = \
self._render_tree(children[i], margin, depth,
@@ -857,8 +840,7 @@ class ConfigNode(object):
lines.extend(new_lines)
paths.extend(new_paths)
else:
- tree = tree \
- + self._render_tree(children[i], margin, depth)
+ tree += self._render_tree(children[i], margin, depth)
margin.pop()
if root_call:
@@ -866,14 +848,13 @@ class ConfigNode(object):
return (lines, paths)
else:
return tree[:-1]
+ elif do_list:
+ return (lines, paths)
else:
- if do_list:
- return (lines, paths)
- else:
- return tree
+ return tree
- def ui_complete_ls(self, parameters, text, current_param):
+ def ui_complete_ls(self, parameters, text, current_param): # noqa: ARG002 TODO
'''
Parameter auto-completion method for user command ls.
@param parameters: Parameters on the command line.
@@ -887,7 +868,7 @@ class ConfigNode(object):
'''
if current_param == 'path':
(basedir, slash, partial_name) = text.rpartition('/')
- basedir = basedir + slash
+ basedir += slash
target = self.get_node(basedir)
names = [child.name for child in target.children]
completions = []
@@ -896,22 +877,21 @@ class ConfigNode(object):
if name.startswith(partial_name):
num_matches += 1
if num_matches == 1:
- completions.append("%s%s/" % (basedir, name))
+ completions.append(f"{basedir}{name}/")
else:
- completions.append("%s%s" % (basedir, name))
- if len(completions) == 1:
- if not self.get_node(completions[0]).children:
- completions[0] = completions[0].rstrip('/') + ' '
+ completions.append(f"{basedir}{name}")
+ if len(completions) == 1 and not self.get_node(completions[0]).children:
+ completions[0] = completions[0].rstrip('/') + ' '
# Bookmarks
bookmarks = ['@' + bookmark for bookmark
in self.shell.prefs['bookmarks']
if ('@' + bookmark).startswith(text)]
- self.shell.log.debug("Found bookmarks %s." % str(bookmarks))
+ self.shell.log.debug(f"Found bookmarks {bookmarks!s}.")
if bookmarks:
completions.extend(bookmarks)
- self.shell.log.debug("Completions are %s." % str(completions))
+ self.shell.log.debug(f"Completions are {completions!s}.")
return completions
elif current_param == 'depth':
@@ -921,9 +901,10 @@ class ConfigNode(object):
except ValueError:
self.shell.log.debug("Text is not a number.")
return []
- return [ text + number for number
+ return [text + number for number
in [str(num) for num in range(10)]
if (text + number).startswith(text)]
+ return None
def ui_command_cd(self, path=None):
'''
@@ -960,7 +941,7 @@ class ConfigNode(object):
========
ls cd
'''
- self.shell.log.debug("Changing current node to '%s'." % path)
+ self.shell.log.debug(f"Changing current node to '{path}'.")
if self.shell.prefs['path_history'] is None:
self.shell.prefs['path_history'] = [self.path]
@@ -974,8 +955,7 @@ class ConfigNode(object):
exists = False
while not exists:
if self.shell.prefs['path_history_index'] > 0:
- self.shell.prefs['path_history_index'] = \
- self.shell.prefs['path_history_index'] - 1
+ self.shell.prefs['path_history_index'] -= 1
index = self.shell.prefs['path_history_index']
path = self.shell.prefs['path_history'][index]
try:
@@ -989,7 +969,7 @@ class ConfigNode(object):
self.shell.prefs['path_history_index'] = 0
self.shell.prefs['path_history'][0] = '/'
exists = True
- self.shell.log.info('Taking you back to %s.' % path)
+ self.shell.log.info(f'Taking you back to {path}.')
return self.get_node(path)
# Go forward in history
@@ -1002,8 +982,7 @@ class ConfigNode(object):
while not exists:
if self.shell.prefs['path_history_index'] \
< len(self.shell.prefs['path_history']) - 1:
- self.shell.prefs['path_history_index'] = \
- self.shell.prefs['path_history_index'] + 1
+ self.shell.prefs['path_history_index'] += 1
index = self.shell.prefs['path_history_index']
path = self.shell.prefs['path_history'][index]
try:
@@ -1018,10 +997,9 @@ class ConfigNode(object):
= len(self.shell.prefs['path_history'])
self.shell.prefs['path_history'].append(path)
exists = True
- self.shell.log.info('Taking you back to %s.' % path)
+ self.shell.log.info(f'Taking you back to {path}.')
return self.get_node(path)
- # Use an urwid walker to select the path
if path is None:
lines, paths = self._render_tree(self.get_root(), do_list=True)
start_pos = paths.index(self.path)
@@ -1038,7 +1016,7 @@ class ConfigNode(object):
if target_node.path != self.shell.prefs['path_history'][index]:
# Truncate the hostory to retain current path as last one
self.shell.prefs['path_history'] = \
- self.shell.prefs['path_history'][:index+1]
+ self.shell.prefs['path_history'][:index + 1]
# Append the new path and update the index
self.shell.prefs['path_history'].append(target_node.path)
self.shell.prefs['path_history_index'] = index + 1
@@ -1049,7 +1027,7 @@ class ConfigNode(object):
def _lines_walker(self, lines, start_pos):
'''
- Using the curses urwid library, displays all lines passed as argument,
+ Using the curses library, displays all lines passed as argument,
and after allowing selection of one line using up, down and enter keys,
returns its index.
@param lines: The lines to display and select from.
@@ -1059,36 +1037,43 @@ class ConfigNode(object):
@return: the index of the selected line.
@rtype: int
'''
- import urwid
- palette = [('header', 'white', 'black'),
- ('reveal focus', 'black', 'yellow', 'standout')]
+ def draw_menu(stdscr, current_row):
+ stdscr.clear()
+ height, width = stdscr.getmaxyx()
+
+ for idx, row in enumerate(lines):
+ x = width // 2 - len(row) // 2
+ y = height // 2 - len(lines) // 2 + idx
+ if idx == current_row:
+ stdscr.attron(curses.color_pair(1))
+ stdscr.addstr(y, x, row)
+ stdscr.attroff(curses.color_pair(1))
+ else:
+ stdscr.addstr(y, x, row)
- content = urwid.SimpleListWalker(
- [urwid.AttrMap(w, None, 'reveal focus')
- for w in [urwid.Text(line) for line in lines]])
+ stdscr.refresh()
- listbox = urwid.ListBox(content)
- frame = urwid.Frame(listbox)
+ def main(stdscr):
+ curses.start_color()
+ curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_YELLOW)
+ current_row = start_pos
+ draw_menu(stdscr, current_row)
- def handle_input(input, raw):
- for key in input:
- widget, pos = content.get_focus()
- if key == 'up':
- if pos > 0:
- content.set_focus(pos-1)
- elif key == 'down':
- try:
- content.set_focus(pos+1)
- except IndexError:
- pass
- elif key == 'enter':
- raise urwid.ExitMainLoop()
+ while True:
+ key = stdscr.getch()
+ if key == curses.KEY_UP and current_row > 0:
+ current_row -= 1
+ elif key == curses.KEY_DOWN and current_row < len(lines) - 1:
+ current_row += 1
+ elif key == curses.KEY_ENTER or key in (10, 13):
+ break
+
+ draw_menu(stdscr, current_row)
- content.set_focus(start_pos)
- loop = urwid.MainLoop(frame, palette, input_filter=handle_input)
- loop.run()
- return listbox.focus_position
+ return current_row
+
+ return curses.wrapper(main)
def ui_complete_cd(self, parameters, text, current_param):
'''
@@ -1104,9 +1089,10 @@ class ConfigNode(object):
'''
if current_param == 'path':
completions = self.ui_complete_ls(parameters, text, current_param)
- completions.extend([nav for nav in ['<', '>']
+ completions.extend([nav for nav in ('<', '>')
if nav.startswith(text)])
return completions
+ return None
def ui_command_help(self, topic=None):
'''
@@ -1124,30 +1110,31 @@ class ConfigNode(object):
''')
for command in commands:
- msg += " - %s\n" % self.get_command_syntax(command)[0]
+ msg += f" - {self.get_command_syntax(command)[0]}\n"
+ msg += "\n"
self.shell.con.epy_write(msg)
return
if topic not in commands:
- raise ExecutionError("Cannot find help topic %s." % topic)
+ raise ExecutionError(f"Cannot find help topic {topic}.")
syntax, comments, defaults = self.get_command_syntax(topic)
- msg = self.shell.con.dedent('''
+ msg = self.shell.con.dedent(f'''
SYNTAX
======
- %s
+ {syntax}
- ''' % syntax)
+ ''')
for comment in comments:
msg += comment + '\n'
if defaults:
- msg += self.shell.con.dedent('''
+ msg += self.shell.con.dedent(f'''
DEFAULT VALUES
==============
- %s
+ {defaults}
- ''' % defaults)
+ ''')
msg += self.shell.con.dedent('''
DESCRIPTION
===========
@@ -1156,7 +1143,7 @@ class ConfigNode(object):
msg += "\n"
self.shell.con.epy_write(msg)
- def ui_complete_help(self, parameters, text, current_param):
+ def ui_complete_help(self, parameters, text, current_param): # noqa: ARG002 TODO
'''
Parameter auto-completion method for user command help.
@param parameters: Parameters on the command line.
@@ -1226,24 +1213,25 @@ class ConfigNode(object):
'''
if action == 'add' and bookmark:
if bookmark in self.shell.prefs['bookmarks']:
- raise ExecutionError("Bookmark %s already exists." % bookmark)
+ raise ExecutionError(f"Bookmark {bookmark} already exists.")
self.shell.prefs['bookmarks'][bookmark] = self.path
# No way Prefs is going to account for that :-(
self.shell.prefs.save()
- self.shell.log.info("Bookmarked %s as %s."
- % (self.path, bookmark))
+ self.shell.log.info(f"Bookmarked {self.path} as {bookmark}.")
+ return None
elif action == 'del' and bookmark:
if bookmark not in self.shell.prefs['bookmarks']:
- raise ExecutionError("No such bookmark %s." % bookmark)
+ raise ExecutionError(f"No such bookmark {bookmark}.")
del self.shell.prefs['bookmarks'][bookmark]
# No way Prefs is going to account for that deletion
self.shell.prefs.save()
- self.shell.log.info("Deleted bookmark %s." % bookmark)
+ self.shell.log.info(f"Deleted bookmark {bookmark}.")
+ return None
elif action == 'go' and bookmark:
if bookmark not in self.shell.prefs['bookmarks']:
- raise ExecutionError("No such bookmark %s." % bookmark)
+ raise ExecutionError(f"No such bookmark {bookmark}.")
return self.ui_command_cd(
self.shell.prefs['bookmarks'][bookmark])
elif action == 'show':
@@ -1256,12 +1244,13 @@ class ConfigNode(object):
bookmarks += "No bookmarks yet.\n"
else:
for (bookmark, path) \
- in six.iteritems(self.shell.prefs['bookmarks']):
+ in self.shell.prefs['bookmarks'].items():
if len(bookmark) == 1:
bookmark += '\0'
underline = ''.ljust(len(bookmark), '-')
- bookmarks += "%s\n%s\n%s\n\n" % (bookmark, underline, path)
+ bookmarks += f"{bookmark}\n{underline}\n{path}\n\n"
self.shell.con.epy_write(bookmarks)
+ return None
else:
raise ExecutionError("Syntax error, see 'help bookmarks'.")
@@ -1278,14 +1267,13 @@ class ConfigNode(object):
@rtype: list of str
'''
if current_param == 'action':
- completions = [action for action in ['add', 'del', 'go', 'show']
+ completions = [action for action in ('add', 'del', 'go', 'show')
if action.startswith(text)]
elif current_param == 'bookmark':
- if 'action' in parameters:
- if parameters['action'] not in ['show', 'add']:
- completions = [mark for mark
- in self.shell.prefs['bookmarks']
- if mark.startswith(text)]
+ if 'action' in parameters and parameters['action'] not in {'show', 'add'}:
+ completions = [mark for mark
+ in self.shell.prefs['bookmarks']
+ if mark.startswith(text)]
else:
completions = []
@@ -1375,7 +1363,7 @@ class ConfigNode(object):
'''
return ('', None)
- def execute_command(self, command, pparams=[], kparams={}):
+ def execute_command(self, command, pparams=[], kparams={}): # noqa: B006 TODO Do not use mutable data structures for argument defaults
'''
Execute a user command on the node. This works by finding out which is
the support command method, using ConfigNode naming convention:
@@ -1393,14 +1381,14 @@ class ConfigNode(object):
they are interpreted by ConfigShell.
@rtype: str or ConfigNode or None
'''
- self.shell.log.debug("Executing command %s " % command
- + "with pparams %s " % pparams
- + "and kparams %s." % kparams)
+ self.shell.log.debug(f"Executing command {command} "
+ f"with pparams {pparams} "
+ f"and kparams {kparams}.")
if command in self.list_commands():
method = self.get_command_method(command)
else:
- raise ExecutionError("Command not found %s" % command)
+ raise ExecutionError(f"Command not found {command}")
self.assert_params(method, pparams, kparams)
return method(*pparams, **kparams)
@@ -1417,10 +1405,10 @@ class ConfigNode(object):
@type kparams: dict
@raise ExecutionError: When the check fails.
'''
- spec = inspect.getargspec(method)
+ spec = inspect.getfullargspec(method)
args = spec.args[1:]
pp = spec.varargs
- kw = spec.keywords
+ kw = spec.varkw
if spec.defaults is None:
nb_opt_params = 0
@@ -1444,38 +1432,35 @@ class ConfigNode(object):
self.shell.log.debug("Min params: %d" % nb_min_params)
self.shell.log.debug("Max params: %d" % nb_max_params)
- self.shell.log.debug("Required params: %s" % ", ".join(req_params))
- self.shell.log.debug("Optional params: %s" % ", ".join(opt_params))
- self.shell.log.debug("Got %s standard params." % nb_standard_params)
- self.shell.log.debug("Got %s extended params." % nb_extended_params)
- self.shell.log.debug("Variable positional params: %s" % pp)
- self.shell.log.debug("Variable keyword params: %s" % kw)
+ self.shell.log.debug(f"Required params: {', '.join(req_params)}")
+ self.shell.log.debug(f"Optional params: {', '.join(opt_params)}")
+ self.shell.log.debug(f"Got {nb_standard_params} standard params.")
+ self.shell.log.debug(f"Got {nb_extended_params} extended params.")
+ self.shell.log.debug(f"Variable positional params: {pp}")
+ self.shell.log.debug(f"Variable keyword params: {kw}")
if len(missing_params) == 1:
raise ExecutionError(
- "Missing required parameter %s"
- % missing_params[0])
- elif missing_params:
+ f"Missing required parameter {missing_params[0]}")
+ if missing_params:
raise ExecutionError(
- "Missing required parameters %s"
- % ", ".join("'%s'" % missing for missing in missing_params))
+ "Missing required parameters {}".format(", ".join(
+ f"'{missing}'" for missing in missing_params)))
- if spec.keywords is None:
+ if kw is None:
if len(unexpected_keywords) == 1:
raise ExecutionError(
- "Unexpected keyword parameter '%s'."
- % unexpected_keywords[0])
- elif unexpected_keywords:
+ f"Unexpected keyword parameter '{unexpected_keywords[0]}'.")
+ if unexpected_keywords:
raise ExecutionError(
- "Unexpected keyword parameters %s."
- % ", ".join("'%s'" % kw for kw in unexpected_keywords))
+ "Unexpected keyword parameters {}.".format(", ".join(
+ f"'{kw}'" for kw in unexpected_keywords)))
all_params = args[:len(pparams)]
all_params.extend(kparams.keys())
for param in all_params:
if all_params.count(param) > 1:
raise ExecutionError(
- "Duplicate parameter %s."
- % param)
+ f"Duplicate parameter {param}.")
if nb_opt_params == 0 \
and nb_standard_params != nb_min_params \
@@ -1508,7 +1493,7 @@ class ConfigNode(object):
@rtype: method object
'''
prefix = self.ui_getgroup_method_prefix
- return getattr(self, '%s%s' % (prefix, group))
+ return getattr(self, f'{prefix}{group}')
def get_group_setter(self, group):
'''
@@ -1518,7 +1503,7 @@ class ConfigNode(object):
@rtype: method object
'''
prefix = self.ui_setgroup_method_prefix
- return getattr(self, '%s%s' % (prefix, group))
+ return getattr(self, f'{prefix}{group}')
def get_command_method(self, command):
'''
@@ -1530,11 +1515,10 @@ class ConfigNode(object):
'''
prefix = self.ui_command_method_prefix
if command in self.list_commands():
- return getattr(self, '%s%s' % (prefix, command))
+ return getattr(self, f'{prefix}{command}')
else:
- self.shell.log.debug('No command named %s in %s (%s)'
- % (command, self.name, self.path))
- raise ValueError('No command named "%s".' % command)
+ self.shell.log.debug(f'No command named {command} in {self.name} ({self.path})')
+ raise ValueError(f'No command named "{command}".')
def get_completion_method(self, command):
'''
@@ -1545,7 +1529,7 @@ class ConfigNode(object):
'''
prefix = self.ui_complete_method_prefix
try:
- method = getattr(self, '%s%s' % (prefix, command))
+ method = getattr(self, f'{prefix}{command}')
except AttributeError:
return None
else:
@@ -1575,12 +1559,12 @@ class ConfigNode(object):
@type command: str
'''
method = self.get_command_method(command)
- parameters, args, kwargs, default = inspect.getargspec(method)
- parameters = parameters[1:]
- if default is None:
+ spec = inspect.getfullargspec(method)
+ parameters = spec.args[1:]
+ if spec.defaults is None:
num_defaults = 0
else:
- num_defaults = len(default)
+ num_defaults = len(spec.defaults)
if num_defaults != 0:
required_parameters = parameters[:-num_defaults]
@@ -1589,32 +1573,32 @@ class ConfigNode(object):
required_parameters = parameters
optional_parameters = []
- self.shell.log.debug("Required: %s" % str(required_parameters))
- self.shell.log.debug("Optional: %s" % str(optional_parameters))
+ self.shell.log.debug(f"Required: {required_parameters!s}")
+ self.shell.log.debug(f"Optional: {optional_parameters!s}")
- syntax = "%s " % command
+ syntax = f"{command} "
required_parameters_str = ''
for param in required_parameters:
- required_parameters_str += "%s " % param
+ required_parameters_str += f"{param} "
syntax += required_parameters_str
optional_parameters_str = ''
for param in optional_parameters:
- optional_parameters_str += "[%s] " % param
+ optional_parameters_str += f"[{param}] "
syntax += optional_parameters_str
comments = []
- if args is not None:
- syntax += "[%s...] " % args
- if kwargs is not None:
- syntax += "[%s=value...] " % (kwargs)
+ if spec.varargs is not None:
+ syntax += f"[{spec.varargs}...] "
+ if spec.varkw is not None:
+ syntax += f"[{spec.varkw}=value...] "
default_values = ''
if num_defaults > 0:
for index, param in enumerate(optional_parameters):
- if default[index] is not None:
- default_values += "%s=%s " % (param, str(default[index]))
+ if spec.defaults[index] is not None:
+ default_values += f"{param}={spec.defaults[index]!s} "
return syntax, comments, default_values
@@ -1630,20 +1614,17 @@ class ConfigNode(object):
@rtype: ([str...], bool, bool)
'''
method = self.get_command_method(command)
- parameters, args, kwargs, default = inspect.getargspec(method)
- parameters = parameters[1:]
- if args is not None:
- free_pparams = args
+ spec = inspect.getfullargspec(method)
+ parameters = spec.args[1:]
+ if spec.varargs is not None:
+ free_pparams = spec.varargs
else:
free_pparams = False
- if kwargs is not None:
- free_kparams = kwargs
+ if spec.varkw is not None:
+ free_kparams = spec.varkw
else:
free_kparams = False
- self.shell.log.debug("Signature is %s, %s, %s."
- % (str(parameters),
- str(free_pparams),
- str(free_kparams)))
+ self.shell.log.debug(f"Signature is {parameters!s}, {free_pparams!s}, {free_kparams!s}.")
return parameters, free_pparams, free_kparams
def get_root(self):
@@ -1656,7 +1637,7 @@ class ConfigNode(object):
else:
return self.parent.get_root()
- def define_config_group_param(self, group, param, type,
+ def define_config_group_param(self, group, param, type, # noqa: A002
description=None, writable=True):
'''
Helper to define configuration group parameters.
@@ -1673,7 +1654,7 @@ class ConfigNode(object):
self._configuration_groups[group] = {}
if description is None:
- description = "The %s %s parameter." % (param, group)
+ description = f"The {param} {group} parameter."
# Fail early if the type and set/get helpers don't exist
self.get_type_method(type)
@@ -1703,7 +1684,7 @@ class ConfigNode(object):
return []
else:
params = []
- for p_name, p_def in six.iteritems(self._configuration_groups[group]):
+ for p_name, p_def in self._configuration_groups[group].items():
(p_type, p_description, p_writable) = p_def
if writable is not None and p_writable != writable:
continue
@@ -1724,15 +1705,14 @@ class ConfigNode(object):
@raise ValueError: If the parameter or group does not exist.
'''
if group not in self.list_config_groups():
- raise ValueError("Not such configuration group %s" % group)
+ raise ValueError(f"Not such configuration group {group}")
if param not in self.list_group_params(group):
- raise ValueError("Not such parameter %s in configuration group %s"
- % (param, group))
+ raise ValueError(f"Not such parameter {param} in configuration group {group}")
(p_type, p_description, p_writable) = \
self._configuration_groups[group][param]
- return dict(name=param, group=group, type=p_type,
- description=p_description, writable=p_writable)
+ return {'name': param, 'group': group, 'type': p_type,
+ 'description': p_description, 'writable': p_writable}
shell = property(_get_shell,
doc="Gets the shell attached to ConfigNode tree.")
@@ -1754,10 +1734,7 @@ class ConfigNode(object):
@return: Wether or not we are a root node.
@rtype: bool
'''
- if self._parent is None:
- return True
- else:
- return False
+ return self._parent is None
def get_child(self, name):
'''
@@ -1770,9 +1747,7 @@ class ConfigNode(object):
for child in self._children:
if child.name == name:
return child
- else:
- raise ValueError("No such path %s/%s"
- % (self.path.rstrip('/'), name))
+ raise ValueError(f"No such path {self.path.rstrip('/')}/{name}")
def remove_child(self, child):
'''
@@ -1816,13 +1791,13 @@ class ConfigNode(object):
if bookmark in self.shell.prefs['bookmarks']:
path = self.shell.prefs['bookmarks'][bookmark]
else:
- raise ValueError("No such bookmark %s" % bookmark)
+ raise ValueError(f"No such bookmark {bookmark}")
# More cleanup
- path = re.sub('%s+' % self._path_separator, self._path_separator, path)
+ path = re.sub(f'{self._path_separator}+', self._path_separator, path)
if len(path) > 1:
path = path.rstrip(self._path_separator)
- self.shell.log.debug("Looking for path '%s'" % path)
+ self.shell.log.debug(f"Looking for path '{path}'")
# Absolute path - make relative and pass on to root node
diff --git a/configshell/prefs.py b/configshell/prefs.py
index 7faa1fe..384e6ba 100644
--- a/configshell/prefs.py
+++ b/configshell/prefs.py
@@ -15,10 +15,12 @@ License for the specific language governing permissions and limitations
under the License.
'''
-import six
import fcntl
+import pickle
+from pathlib import Path
-class Prefs(object):
+
+class Prefs:
'''
This is a preferences backend object used to:
- Hold the ConfigShell preferences
@@ -73,10 +75,7 @@ class Prefs(object):
@param key: The preferences dictionnary key to check.
@type key: any valid dict key
'''
- if key in self._prefs:
- return True
- else:
- return False
+ return key in self._prefs
def __delitem__(self, key):
'''
@@ -115,7 +114,7 @@ class Prefs(object):
@return: Iterates on the items in preferences.
@rtype: yields items that are (key, value) pairs
'''
- return six.iteritems(self._prefs)
+ return self._prefs.items()
def save(self, filename=None):
'''
@@ -128,12 +127,10 @@ class Prefs(object):
filename = self.filename
if filename is not None:
- fsock = open(filename, 'wb')
- fcntl.lockf(fsock, fcntl.LOCK_UN)
- try:
- six.moves.cPickle.dump(self._prefs, fsock, 2)
- finally:
- fsock.close()
+ path = Path(filename)
+ with path.open('wb') as fsock:
+ fcntl.lockf(fsock, fcntl.LOCK_UN)
+ pickle.dump(self._prefs, fsock, 2)
def load(self, filename=None):
'''
@@ -143,10 +140,8 @@ class Prefs(object):
if filename is None:
filename = self.filename
- if filename is not None:
- fsock = open(filename, 'rb')
- fcntl.lockf(fsock, fcntl.LOCK_SH)
- try:
- self._prefs = six.moves.cPickle.load(fsock)
- finally:
- fsock.close()
+ if filename is not None and Path(filename).exists():
+ path = Path(filename)
+ with path.open('rb') as fsock:
+ fcntl.lockf(fsock, fcntl.LOCK_SH)
+ self._prefs = pickle.load(fsock) # noqa S301 TODO
diff --git a/configshell/shell.py b/configshell/shell.py
index 437186d..bc9b6e1 100644
--- a/configshell/shell.py
+++ b/configshell/shell.py
@@ -16,20 +16,28 @@ under the License.
'''
import os
-import six
+import signal
import sys
-from pyparsing import (alphanums, Empty, Group, locatedExpr,
- OneOrMore, Optional, ParseResults, Regex,
- Suppress, Word)
-
-from . import console
-from . import log
-from . import prefs
+from contextlib import suppress
+from pathlib import Path
+
+from pyparsing import (
+ OneOrMore,
+ Optional,
+ ParseResults,
+ Regex,
+ Suppress,
+ Word,
+ alphanums,
+ locatedExpr,
+)
+
+from . import console, log, prefs
from .node import ConfigNode, ExecutionError
+
# A fix for frozen packages
-import signal
-def handle_sigint(signum, frame):
+def handle_sigint(signum, frame): # noqa: ARG001 TODO
'''
Raise KeyboardInterrupt when we get a SIGINT.
This is normally done by python, but even after patching
@@ -38,30 +46,29 @@ def handle_sigint(signum, frame):
'''
raise KeyboardInterrupt
-try:
+
+# In a thread, this fails
+with suppress(Exception):
signal.signal(signal.SIGINT, handle_sigint)
-except Exception:
- # In a thread, this fails
- pass
if sys.stdout.isatty():
import readline
- tty=True
+ tty = True
else:
- tty=False
+ tty = False
# remember the original setting
- oldTerm = os.environ.get('TERM')
- os.environ['TERM'] = ''
-
+ old_term = os.getenv('TERM')
+ os.unsetenv('TERM')
import readline
# restore the orignal TERM setting
- if oldTerm != None:
- os.environ['TERM'] = oldTerm
- del oldTerm
+ if old_term is not None:
+ os.putenv('TERM', old_term)
+ del old_term
-class ConfigShell(object):
+
+class ConfigShell:
'''
This is a simple CLI command interpreter that can be used both in
interactive or non-interactive modes.
@@ -93,7 +100,7 @@ class ConfigShell(object):
'tree_max_depth': 0,
'tree_status_mode': True,
'tree_round_nodes': True,
- 'tree_show_root': True
+ 'tree_show_root': True,
}
_completion_help_topic = ''
@@ -113,50 +120,50 @@ class ConfigShell(object):
# Grammar of the command line
command = locatedExpr(Word(alphanums + '_'))('command')
- var = Word(alphanums + '?;&*$!#,=_\+/.<>()~@:-%[]')
+ var = Word(alphanums + r'?;&*$!#,=_\+/.<>()~@:-%[]')
value = var
- keyword = Word(alphanums + '_\-')
+ keyword = Word(alphanums + r'_\-')
kparam = locatedExpr(keyword + Suppress('=') + Optional(value, default=''))('kparams*')
pparam = locatedExpr(var)('pparams*')
parameter = kparam | pparam
parameters = OneOrMore(parameter)
bookmark = Regex('@([A-Za-z0-9:_.]|-)+')
- pathstd = Regex('([A-Za-z0-9:_.\[\]]|-)*' + '/' + '([A-Za-z0-9:_.\[\]/]|-)*') \
+ pathstd = Regex(r'([A-Za-z0-9:_.\[\]]|-)*' + '/' + r'([A-Za-z0-9:_.\[\]/]|-)*') \
| '..' | '.'
path = locatedExpr(bookmark | pathstd | '*')('path')
parser = Optional(path) + Optional(command) + Optional(parameters)
self._parser = parser
if tty:
- readline.set_completer_delims('\t\n ~!#$^&(){}\|;\'",?')
+ readline.set_completer_delims('\t\n ~!#$^&(){}\\|;\'",?')
readline.set_completion_display_matches_hook(
self._display_completions)
self.log = log.Log()
if preferences_dir is not None:
- preferences_dir = os.path.expanduser(preferences_dir)
- if not os.path.exists(preferences_dir):
- os.makedirs(preferences_dir)
- self._prefs_file = preferences_dir + '/prefs.bin'
- self.prefs = prefs.Prefs(self._prefs_file)
- self._cmd_history = preferences_dir + '/history.txt'
+ preferences_dir_path = Path(preferences_dir)
+ if not preferences_dir_path.exists():
+ preferences_dir_path.mkdir(parents=True)
+ self._prefs_file = preferences_dir_path / 'prefs.bin'
+ self.prefs = prefs.Prefs(str(self._prefs_file))
+ self._cmd_history = preferences_dir_path / '/history.txt'
self._save_history = True
- if not os.path.isfile(self._cmd_history):
+ cmd_history_path = self._cmd_history
+ if not cmd_history_path.is_file():
try:
- open(self._cmd_history, 'w').close()
- except:
- self.log.warning("Cannot create history file %s, "
- % self._cmd_history
- + "command history will not be saved.")
+ with cmd_history_path.open('w'):
+ pass
+ except OSError:
+ self.log.warning(f"Cannot create history file {self._cmd_history}, "
+ f"command history will not be saved.")
self._save_history = False
- if os.path.isfile(self._cmd_history) and tty:
+ if self._cmd_history.is_file() and tty:
try:
readline.read_history_file(self._cmd_history)
- except IOError:
- self.log.warning("Cannot read command history file %s."
- % self._cmd_history)
+ except OSError:
+ self.log.warning(f"Cannot read command history file {self._cmd_history}.")
if self.prefs['logfile'] is None:
self.prefs['logfile'] = preferences_dir + '/' + 'log.txt'
@@ -169,11 +176,10 @@ class ConfigShell(object):
try:
self.prefs.load()
- except IOError:
- self.log.warning("Could not load preferences file %s."
- % self._prefs_file)
+ except OSError:
+ self.log.warning(f"Could not load preferences file {self._prefs_file}.")
- for pref, value in six.iteritems(self.default_prefs):
+ for pref, value in self.default_prefs.items():
if pref not in self.prefs:
self.prefs[pref] = value
@@ -181,7 +187,7 @@ class ConfigShell(object):
# Private methods
- def _display_completions(self, substitution, matches, max_length):
+ def _display_completions(self, substitution, matches, max_length): # noqa: ARG002 TODO
'''
Display the completions. Invoked by readline.
@param substitution: string to complete
@@ -239,19 +245,19 @@ class ConfigShell(object):
else:
nr_cols = 1
- for i in six.moves.range(0, len(matches), nr_cols):
+ for i in range(0, len(matches), nr_cols):
self.con.raw_write(''.join(matches[i:i+nr_cols]))
self.con.raw_write('\n')
# Display the prompt and the command line
- line = "%s%s" % (self._get_prompt(), readline.get_line_buffer())
- self.con.raw_write("%s" % line)
+ line = f"{self._get_prompt()}{readline.get_line_buffer()}"
+ self.con.raw_write(line)
# Move the cursor where it should be
y_pos = self.con.get_cursor_xy()[1]
self.con.set_cursor_xy(x_orig, y_pos)
- def _complete_token_command(self, text, path, command):
+ def _complete_token_command(self, text, path, command): # TODO # noqa: ARG002
'''
Completes a partial command token, which could also be the beginning
of a path.
@@ -267,12 +273,11 @@ class ConfigShell(object):
completions = []
target = self._current_node.get_node(path)
commands = target.list_commands()
- self.log.debug("Completing command token among %s" % str(commands))
+ self.log.debug(f"Completing command token among {commands!s}")
# Start with the possible commands
- for command in commands:
- if command.startswith(text):
- completions.append(command)
+ completions = [command for command in commands if command.startswith(text)]
+
if len(completions) == 1:
completions[0] = completions[0] + ' '
@@ -303,7 +308,7 @@ class ConfigShell(object):
self.con.render_text(
'command', self.prefs['color_command'])
if len(path_completions) == 1 and \
- not path_completions[0][-1] in [' ', '*'] and \
+ path_completions[0][-1] not in (' ', '*') and \
not self._current_node.get_node(path_completions[0]).children:
path_completions[0] = path_completions[0] + ' '
completions.extend(path_completions)
@@ -314,12 +319,11 @@ class ConfigShell(object):
# Even a bookmark
bookmarks = ['@' + bookmark for bookmark in self.prefs['bookmarks']
- if bookmark.startswith("%s" % text.lstrip('@'))]
- self.log.debug("Found bookmarks %s." % str(bookmarks))
+ if bookmark.startswith(text.lstrip('@'))]
+ self.log.debug(f"Found bookmarks {bookmarks!s}.")
if bookmarks:
completions.extend(bookmarks)
-
# We are done
return completions
@@ -335,33 +339,32 @@ class ConfigShell(object):
if text.endswith('.'):
text = text + '/'
(basedir, slash, partial_name) = text.rpartition('/')
- self.log.debug("Got basedir=%s, partial_name=%s"
- % (basedir, partial_name))
+ self.log.debug(f"Got basedir={basedir}, partial_name={partial_name}")
basedir = basedir + slash
target = self._current_node.get_node(basedir)
names = [child.name for child in target.children]
# Iterall path completion
- if names and partial_name in ['', '*']:
+ if names and partial_name in ('', '*'):
# Not suggesting iterall to end a path that has only one
# child allows for fast TAB action to add the only child's
# name.
if len(names) > 1:
- completions.append("%s* " % basedir)
+ completions.append(f"{basedir}* ")
for name in names:
num_matches = 0
if name.startswith(partial_name):
num_matches += 1
if num_matches == 1:
- completions.append("%s%s/" % (basedir, name))
+ completions.append(f"{basedir}{name}/")
else:
- completions.append("%s%s" % (basedir, name))
+ completions.append(f"{basedir}{name}")
# Bookmarks
bookmarks = ['@' + bookmark for bookmark in self.prefs['bookmarks']
- if bookmark.startswith("%s" % text.lstrip('@'))]
- self.log.debug("Found bookmarks %s." % str(bookmarks))
+ if bookmark.startswith(text.lstrip('@'))]
+ self.log.debug(f"Found bookmarks {bookmarks!s}.")
if bookmarks:
completions.extend(bookmarks)
@@ -402,12 +405,11 @@ class ConfigShell(object):
for index in range(len(pparams)):
if index < len(cmd_params):
current_parameters[cmd_params[index]] = pparams[index]
- for key, value in six.iteritems(kparams):
+ for key, value in kparams.items():
current_parameters[key] = value
self._completion_help_topic = command
completion_method = target.get_completion_method(command)
- self.log.debug("Command %s accepts parameters %s."
- % (command, cmd_params))
+ self.log.debug(f"Command {command} accepts parameters {cmd_params}.")
# Do we still accept positional params ?
pparam_ok = True
@@ -436,7 +438,7 @@ class ConfigShell(object):
else:
pparam_index = len(pparams) - 1
self._current_parameter = cmd_params[pparam_index]
- self.log.debug("Completing pparam %s." % self._current_parameter)
+ self.log.debug(f"Completing pparam {self._current_parameter}.")
if completion_method:
pparam_completions = completion_method(
current_parameters, text, self._current_parameter)
@@ -453,10 +455,8 @@ class ConfigShell(object):
if param not in kparams \
if param.startswith(text)]
- self.log.debug("Possible pparam values are %s."
- % str(completions))
- self.log.debug("Possible kparam keywords are %s."
- % str(keyword_completions))
+ self.log.debug(f"Possible pparam values are {completions!s}.")
+ self.log.debug(f"Possible kparam keywords are {keyword_completions!s}.")
if keyword_completions:
if self._current_parameter:
@@ -471,14 +471,13 @@ class ConfigShell(object):
self._current_token = \
self.con.render_text(
'keyword=', self.prefs['color_keyword'])
+ elif self._current_parameter:
+ self._current_token = \
+ self.con.render_text(
+ self._current_parameter,
+ self.prefs['color_parameter'])
else:
- if self._current_parameter:
- self._current_token = \
- self.con.render_text(
- self._current_parameter,
- self.prefs['color_parameter'])
- else:
- self._current_token = ''
+ self._current_token = ''
completions.extend(keyword_completions)
@@ -506,7 +505,7 @@ class ConfigShell(object):
self._current_parameter = 'free_parameter'
if do_free_kparams:
- if not 'keyword=' in self._current_token:
+ if 'keyword=' not in self._current_token:
self._current_token = \
self.con.render_text(
'keyword=', self.prefs['color_keyword']) \
@@ -517,7 +516,7 @@ class ConfigShell(object):
completions.extend(free_completions)
- self.log.debug("Found completions %s." % str(completions))
+ self.log.debug(f"Found completions {completions!s}.")
return completions
def _complete_token_kparam(self, text, path, command, pparams, kparams):
@@ -536,21 +535,19 @@ class ConfigShell(object):
@return: Possible completions for the token.
@rtype: list of str
'''
- self.log.debug("Called for text='%s'" % text)
+ self.log.debug(f"Called for text='{text}'")
target = self._current_node.get_node(path)
cmd_params = target.get_command_signature(command)[0]
- self.log.debug("Command %s accepts parameters %s."
- % (command, cmd_params))
+ self.log.debug(f"Command {command} accepts parameters {cmd_params}.")
(keyword, sep, current_value) = text.partition('=')
- self.log.debug("Completing '%s' for kparam %s"
- % (current_value, keyword))
+ self.log.debug(f"Completing '{current_value}' for kparam {keyword}")
self._current_parameter = keyword
current_parameters = {}
for index in range(len(pparams)):
current_parameters[cmd_params[index]] = pparams[index]
- for key, value in six.iteritems(kparams):
+ for key, value in kparams.items():
current_parameters[key] = value
completion_method = target.get_completion_method(command)
if completion_method:
@@ -563,9 +560,9 @@ class ConfigShell(object):
self.con.render_text(
self._current_parameter, self.prefs['color_parameter'])
- self.log.debug("Found completions %s." % str(completions))
+ self.log.debug(f"Found completions {completions!s}.")
- return ["%s=%s" % (keyword, completion) for completion in completions]
+ return [f"{keyword}={completion}" for completion in completions]
def _complete(self, text, state):
'''
@@ -617,8 +614,7 @@ class ConfigShell(object):
pparams, kparams,
text, current_token)
- self.log.debug("Returning completions %s to readline."
- % str(self._current_completions))
+ self.log.debug(f"Returning completions {self._current_completions!s} to readline.")
if state < len(self._current_completions):
return self._current_completions[state]
@@ -653,12 +649,9 @@ class ConfigShell(object):
'''
completions = []
- self.log.debug("Dispatching completion for %s token. "
- % current_token
- + "text='%s', path='%s', command='%s', "
- % (text, path, command)
- + "pparams=%s, kparams=%s"
- % (str(pparams), str(kparams)))
+ self.log.debug(f"Dispatching completion for {current_token} token. "
+ f"text='{text}', path='{path}', command='{command}', "
+ f"pparams={pparams!s}, kparams={kparams!s}")
(path, iterall) = path.partition('*')[:2]
if iterall:
@@ -687,8 +680,7 @@ class ConfigShell(object):
self._complete_token_kparam(text, cpl_path, command,
pparams, kparams)
else:
- self.log.debug("Cannot complete unknown token %s."
- % current_token)
+ self.log.debug(f"Cannot complete unknown token {current_token}.")
return completions
@@ -701,14 +693,12 @@ class ConfigShell(object):
if prompt_length and prompt_length < len(prompt_path):
half = (prompt_length - 3) // 2
- prompt_path = "%s...%s" \
- % (prompt_path[:half], prompt_path[-half:])
+ prompt_path = f"{prompt_path[:half]}...{prompt_path[-half:]}"
if 'prompt_msg' in dir(self._current_node):
- return "%s%s> " % (self._current_node.prompt_msg(),
- prompt_path)
+ return f"{self._current_node.prompt_msg()}{prompt_path}> "
else:
- return "%s> " % prompt_path
+ return f"{prompt_path}> "
def _cli_loop(self):
'''
@@ -722,7 +712,7 @@ class ConfigShell(object):
try:
readline.parse_and_bind("tab: complete")
readline.set_completer(self._complete)
- cmdline = six.moves.input(self._get_prompt()).strip()
+ cmdline = input(self._get_prompt()).strip()
except EOFError:
self.con.raw_write('exit\n')
cmdline = "exit"
@@ -730,10 +720,9 @@ class ConfigShell(object):
if self._save_history:
try:
readline.write_history_file(self._cmd_history)
- except IOError:
+ except OSError:
self.log.warning(
- "Cannot write to command history file %s." \
- % self._cmd_history)
+ f"Cannot write to command history file {self._cmd_history}.")
self.log.warning(
"Saving command history has been disabled!")
self._save_history = False
@@ -765,9 +754,8 @@ class ConfigShell(object):
if isinstance(parse_results.kparams, ParseResults):
kparams = dict([kparam.value for kparam in parse_results.kparams])
- self.log.debug("Parse gave path='%s' command='%s' " % (path, command)
- + "pparams=%s " % str(pparams)
- + "kparams=%s" % str(kparams))
+ self.log.debug(f"Parse gave path='{path}' command='{command}' "
+ f"pparams={pparams!s} kparams={kparams!s}")
return (parse_results, path, command, pparams, kparams)
def _execute_command(self, path, command, pparams, kparams):
@@ -814,15 +802,15 @@ class ConfigShell(object):
targets = target.children
for target in targets:
if iterall:
- self.con.display("[%s]" % target.path)
+ self.con.display(f"[{target.path}]")
result = target.execute_command(command, pparams, kparams)
- self.log.debug("Command execution returned %r" % result)
+ self.log.debug(f"Command execution returned {result!r}")
if isinstance(result, ConfigNode):
self._current_node = result
elif result == 'EXIT':
self._exit = True
elif result is not None:
- raise ExecutionError("Unexpected result: %r" % result)
+ raise ExecutionError(f"Unexpected result: {result!r}")
# Public methods
@@ -838,7 +826,7 @@ class ConfigShell(object):
@type cmdline: str
'''
if cmdline:
- self.log.verbose("Running command line '%s'." % cmdline)
+ self.log.verbose(f"Running command line '{cmdline}'.")
path, command, pparams, kparams = self._parse_cmdline(cmdline)[1:]
self._execute_command(path, command, pparams, kparams)
@@ -852,12 +840,10 @@ class ConfigShell(object):
@type exit_on_error: bool
'''
try:
- script_fd = open(script_path, 'r')
- self.run_stdin(script_fd, exit_on_error)
- except IOError as msg:
- raise IOError(msg)
- finally:
- script_fd.close()
+ with Path(script_path).open() as script_fd:
+ self.run_stdin(script_fd, exit_on_error)
+ except OSError as e:
+ raise OSError(e)
def run_stdin(self, file_descriptor=sys.stdin, exit_on_error=True):
'''
@@ -872,7 +858,7 @@ class ConfigShell(object):
for cmdline in file_descriptor:
try:
self.run_cmdline(cmdline.strip())
- except Exception as msg:
+ except Exception as msg: # noqa: PERF203 `try`-`except` within a loop incurs performance overhead
self.log.error(msg)
if exit_on_error is True:
raise ExecutionError("Aborting run on error.")
diff --git a/configshell_fb b/configshell_fb
deleted file mode 120000
index 76ca383..0000000
--- a/configshell_fb
+++ /dev/null
@@ -1 +0,0 @@
-configshell \ No newline at end of file
diff --git a/configshell_fb.py b/configshell_fb.py
new file mode 100644
index 0000000..a6fd918
--- /dev/null
+++ b/configshell_fb.py
@@ -0,0 +1,12 @@
+# Providing backwards compatibility for modules importing 'configshell_fb'
+
+from configshell import ConfigNode, ConfigShell, Console, ExecutionError, Log, Prefs
+
+__all__ = [
+ 'Console',
+ 'Log',
+ 'ConfigNode',
+ 'ExecutionError',
+ 'Prefs',
+ 'ConfigShell',
+]
diff --git a/debian/changelog b/debian/changelog
index 94d3354..5a0436f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+python-configshell-fb (1:2.0.0-1) unstable; urgency=medium
+
+ * Team upload
+ * Use PyPI as source for upstream tarballs
+ * New upstream version 2.0.0 (Closes: #1057841, #1087405)
+ * Drop now unnecessary patches
+
+ -- Chris Hofstaedtler <[email protected]> Sun, 29 Dec 2024 02:16:44 +0100
+
python-configshell-fb (1:1.1.28-2.1) unstable; urgency=medium
[ Bas Couwenberg ]
diff --git a/debian/control b/debian/control
index 65b09fb..c8cd3eb 100644
--- a/debian/control
+++ b/debian/control
@@ -2,15 +2,18 @@ Source: python-configshell-fb
Section: python
Priority: optional
Maintainer: Linux Block Storage Team <[email protected]>
-Uploaders: Christophe Vu-Brugier <[email protected]>,
- Ritesh Raj Sarraf <[email protected]>,
- Christian Seiler <[email protected]>
-Build-Depends: debhelper-compat (= 13),
- dh-python,
- python3-all,
- python3-pyparsing,
- python3-setuptools,
- python3-six
+Uploaders:
+ Christophe Vu-Brugier <[email protected]>,
+ Ritesh Raj Sarraf <[email protected]>,
+ Christian Seiler <[email protected]>,
+Build-Depends:
+ debhelper-compat (= 13),
+ dh-python,
+ pybuild-plugin-pyproject,
+ python3-all,
+ python3-hatch-vcs,
+ python3-hatchling,
+ python3-pyparsing,
Standards-Version: 4.5.1
Homepage: https://github.com/open-iscsi/configshell-fb
Vcs-Git: https://salsa.debian.org/linux-blocks-team/python-configshell-fb.git
@@ -18,11 +21,10 @@ Vcs-Browser: https://salsa.debian.org/linux-blocks-team/python-configshell-fb
Package: python3-configshell-fb
Architecture: all
-Depends: ${misc:Depends},
- ${python3:Depends},
- python3-pyparsing,
- python3-six,
- python3-urwid,
+Depends:
+ python3-pyparsing,
+ ${misc:Depends},
+ ${python3:Depends},
Description: Python library for building configuration shells - Python 3
The configshell-fb package is a Python library that provides a
framework for building simple but nice CLI-based applications.
diff --git a/debian/patches/replace-getargspec-with-getfullargspec.patch b/debian/patches/replace-getargspec-with-getfullargspec.patch
deleted file mode 100644
index 8388066..0000000
--- a/debian/patches/replace-getargspec-with-getfullargspec.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-Description: replace getargspec() with getfullargspec()
- inspect.getargspec() doesn't work anymore with Python3.11
-Author: Maurizio Lombardi <[email protected]>
-Origin: https://github.com/open-iscsi/configshell-fb/commit/f3ac914861bd605e3d634aeeb5e706abdbd39259
-Forwarded: not-needed
-Bug-Debian: https://bugs.debian.org/1028996
-
---- a/configshell/node.py
-+++ b/configshell/node.py
-@@ -1417,10 +1417,10 @@ class ConfigNode(object):
- @type kparams: dict
- @raise ExecutionError: When the check fails.
- '''
-- spec = inspect.getargspec(method)
-+ spec = inspect.getfullargspec(method)
- args = spec.args[1:]
- pp = spec.varargs
-- kw = spec.keywords
-+ kw = spec.varkw
-
- if spec.defaults is None:
- nb_opt_params = 0
-@@ -1460,7 +1460,7 @@ class ConfigNode(object):
- "Missing required parameters %s"
- % ", ".join("'%s'" % missing for missing in missing_params))
-
-- if spec.keywords is None:
-+ if kw is None:
- if len(unexpected_keywords) == 1:
- raise ExecutionError(
- "Unexpected keyword parameter '%s'."
diff --git a/debian/patches/replace-more-occurrences-of-getargspec-with-getfullargspec.patch b/debian/patches/replace-more-occurrences-of-getargspec-with-getfullargspec.patch
deleted file mode 100644
index b50db4e..0000000
--- a/debian/patches/replace-more-occurrences-of-getargspec-with-getfullargspec.patch
+++ /dev/null
@@ -1,70 +0,0 @@
-Description: Replace more occurrences of getargspec() with getfullargspec()
- Follow up for f3ac914861bd605e3d634aeeb5e706abdbd39259, getargspec was used at two more places.
-Author: Vojtech Trefny <[email protected]>
-Origin: https://github.com/open-iscsi/configshell-fb/commit/343e46cbbbed339c67fe969cdf443af4c979f43e
-Forwarded: not-needed
-Bug-Debian: https://bugs.debian.org/1028996
-
---- a/configshell/node.py
-+++ b/configshell/node.py
-@@ -1575,12 +1575,12 @@ class ConfigNode(object):
- @type command: str
- '''
- method = self.get_command_method(command)
-- parameters, args, kwargs, default = inspect.getargspec(method)
-- parameters = parameters[1:]
-- if default is None:
-+ spec = inspect.getfullargspec(method)
-+ parameters = spec.args[1:]
-+ if spec.defaults is None:
- num_defaults = 0
- else:
-- num_defaults = len(default)
-+ num_defaults = len(spec.defaults)
-
- if num_defaults != 0:
- required_parameters = parameters[:-num_defaults]
-@@ -1605,16 +1605,16 @@ class ConfigNode(object):
- syntax += optional_parameters_str
-
- comments = []
-- if args is not None:
-- syntax += "[%s...] " % args
-- if kwargs is not None:
-- syntax += "[%s=value...] " % (kwargs)
-+ if spec.varargs is not None:
-+ syntax += "[%s...] " % spec.varargs
-+ if spec.varkw is not None:
-+ syntax += "[%s=value...] " % (spec.varkw)
-
- default_values = ''
- if num_defaults > 0:
- for index, param in enumerate(optional_parameters):
-- if default[index] is not None:
-- default_values += "%s=%s " % (param, str(default[index]))
-+ if spec.defaults[index] is not None:
-+ default_values += "%s=%s " % (param, str(spec.defaults[index]))
-
- return syntax, comments, default_values
-
-@@ -1630,14 +1630,14 @@ class ConfigNode(object):
- @rtype: ([str...], bool, bool)
- '''
- method = self.get_command_method(command)
-- parameters, args, kwargs, default = inspect.getargspec(method)
-- parameters = parameters[1:]
-- if args is not None:
-- free_pparams = args
-+ spec = inspect.getfullargspec(method)
-+ parameters = spec.args[1:]
-+ if spec.varargs is not None:
-+ free_pparams = spec.varargs
- else:
- free_pparams = False
-- if kwargs is not None:
-- free_kparams = kwargs
-+ if spec.varkw is not None:
-+ free_kparams = spec.varkw
- else:
- free_kparams = False
- self.shell.log.debug("Signature is %s, %s, %s."
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index 0d2ee57..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1,2 +0,0 @@
-replace-getargspec-with-getfullargspec.patch
-replace-more-occurrences-of-getargspec-with-getfullargspec.patch
diff --git a/debian/watch b/debian/watch
index bd73046..a42145d 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,3 +1,2 @@
version=4
-opts=uversionmangle=s/fb//,dversionmangle=s/fb//,filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/configshell-fb-$1\.tar\.gz/ \
- https://github.com/open-iscsi/configshell-fb/releases .*/v?(\d\S+)\.tar\.gz
+https://pypi.debian.net/configshell-fb/configshell_fb-(.+)\.(?:tar\.(?:gz|bz2|xz))
diff --git a/examples/myshell b/examples/myshell
index c932e28..93d7542 100755
--- a/examples/myshell
+++ b/examples/myshell
@@ -17,7 +17,9 @@ under the License.
'''
import os
-import configshell_fb as configshell
+
+import configshell
+
class MySystemRoot(configshell.node.ConfigNode):
def __init__(self, shell):
@@ -166,7 +168,7 @@ class Users(configshell.node.ConfigNode):
def main():
shell = configshell.shell.ConfigShell('~/.myshell')
- root_node = MySystemRoot(shell)
+ MySystemRoot(shell)
shell.run_interactive()
if __name__ == "__main__":
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..ec37959
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,100 @@
+[build-system]
+build-backend = "hatchling.build"
+requires = ["hatchling", "hatch-vcs"]
+
+[project]
+name = "configshell-fb"
+description = "A framework to implement simple but nice CLIs."
+readme = "README.md"
+license = "Apache-2.0"
+requires-python = ">=3.9"
+authors = [
+ {email = "[email protected]", name = "Andy Grover"},
+ {email = "[email protected]", name = "Jerome Martin"}
+]
+maintainers = [{email = "[email protected]", name = "Maurizio Lombardi"}]
+classifiers = [
+ "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.12",
+]
+dependencies = [
+ "pyparsing>=2.4.7, <4", # 2.4.7 required for EL9 compatibility
+]
+dynamic = ["version"]
+
+[project.license-files]
+paths = ["COPYING"]
+
+[project.urls]
+Homepage = "http://github.com/open-iscsi/configshell-fb"
+
+[tool.hatch.version]
+source = "vcs"
+
+[tool.hatch.build.targets.wheel]
+packages = [
+ "configshell",
+ "configshell_fb.py"
+]
+
+[tool.hatch.envs.default]
+dependencies = [
+ "ruff",
+ "pre-commit",
+]
+
+[tool.ruff]
+line-length = 100
+
+[tool.ruff.lint]
+select = [
+ "F", # Pyflakes
+ "E", # pycodestyle error
+ "W", # pycodestyle warning
+ "I", # isort
+ "N", # pep8-naming
+ "UP", # pyupgrade
+ "YTT", # flake8-2020
+ "S", # flake8-bandit
+ "B", # flake8-bugbear
+ "A", # flake8-builtins
+ "COM", # flake8-commas
+ "C4", # flake8-comprehensions
+ "EXE", # flake8-executable
+ "FA", # flake8-future-annotations
+ "ISC", # flake8-implicit-str-concat
+ "ICN", # flake8-import-conventions
+ "LOG", # flake8-logging
+ "PIE", # flake8-pie
+ "Q003", # flake8-quotes avoidable-escaped-quote
+ "Q004", # flake8-quotes unnecessary-escaped-quote
+ "RSE", # flake8-raise
+ "RET", # flake8-return
+ "SIM", # flake8-simplify
+ "TID", # flake8-tidy-imports
+ "INT", # flake8-gettext
+ "ARG", # flake8-unused-argument
+ "PTH", # flake8-use-pathlib
+ "PL", # Pylint
+ "FLY", # flynt
+ "PERF", # Perflint
+ "FURB", # refurb
+ "RUF", # Ruff
+]
+ignore = [
+ "RUF012", # TODO Mutable class attributes should be annotated with `typing.ClassVar`
+ "B904", # raise-without-from-inside-except
+ "PLR09", # Too many branches/statements/arguments
+ "S105", # Possible hardcoded password assigned
+ "RET505", # Unnecessary `else` after `return` statement
+ "PLW2901", # TODO `for` loop variable overwritten by assignment target
+ "UP031", # TODO Use format specifiers instead of percent format
+ "SIM102", # Use a single if statement instead of nested if statements
+ "SIM108", # Use ternary operator {contents} instead of if-else-block
+]
+
+[tool.ruff.lint.per-file-ignores]
+# Possible hardcoded password assigned to: "current_token"
+"configshell/shell" = ["S105"]
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 9b5c7d1..0000000
--- a/setup.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#! /usr/bin/env python
-'''
-This file is part of ConfigShell.
-Copyright (c) 2011-2013 by Datera, Inc
-
-Licensed under the Apache License, Version 2.0 (the "License"); you may
-not use this file except in compliance with the License. You may obtain
-a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-License for the specific language governing permissions and limitations
-under the License.
-'''
-
-import os
-import re
-from setuptools import setup
-
-# Get version without importing.
-init_file_path = os.path.join(os.path.dirname(__file__), 'configshell/__init__.py')
-
-with open(init_file_path) as f:
- for line in f:
- match = re.match(r"__version__.*'([0-9.]+)'", line)
- if match:
- version = match.group(1)
- break
- else:
- raise Exception("Couldn't find version in setup.py")
-
-setup(
- name = 'configshell-fb',
- version = '1.1.28',
- description = 'A framework to implement simple but nice CLIs.',
- license = 'Apache 2.0',
- maintainer = 'Andy Grover',
- maintainer_email = '[email protected]',
- url = 'http://github.com/open-iscsi/configshell-fb',
- packages = ['configshell', 'configshell_fb'],
- install_requires = [
- 'pyparsing >= 2.0.2',
- 'six',
- 'urwid',
- ],
- classifiers = [
- "Programming Language :: Python",
- "Programming Language :: Python :: 3",
- "License :: OSI Approved :: Apache Software License",
- ],
- )