From e9894cc9fe56f7d9bd0a0ccca8f13043d812dd5a Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:14:11 +0200 Subject: [PATCH 01/13] Updated version fetch method, since pyproject.toml is never present in installed distributions --- rnsh/__init__.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/rnsh/__init__.py b/rnsh/__init__.py index 7c1e6b2..a89613c 100644 --- a/rnsh/__init__.py +++ b/rnsh/__init__.py @@ -26,17 +26,26 @@ # print(os.path.dirname(module_dir)) def _get_version(): + def pkg_res_version(): + import pkg_resources + return pkg_resources.get_distribution("rnsh").version + + def tomli_version(): + import tomli + return tomli.load(open(os.path.join(os.path.dirname(module_dir), "pyproject.toml"), "rb"))["tool"]["poetry"]["version"] + try: - try: - import tomli - return tomli.load(open(os.path.join(os.path.dirname(module_dir), "pyproject.toml"), "rb"))["tool"]["poetry"]["version"] - except: + if (os.path.isfile(os.path.join(os.path.dirname(module_dir), "pyproject.toml"))): try: - import pkg_resources - return pkg_resources.get_distribution("rnsh").version + return tomli_version() except: return "0.0.0" - + else: + try: + return pkg_res_version() + except: + return "0.0.0" + except: return "0.0.0" From 1fa5a42fe6238d8c1c5a22768eb59223c69028e1 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:14:38 +0200 Subject: [PATCH 02/13] Moved tomli dependency to dev dependencies, since it is never used in installed distributions --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 08ea7a1..356d128 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,6 @@ readme = "README.md" python = "^3.7" docopt = "^0.6.2" rns = ">=0.5.9" -# rns = { git = "https://github.com/acehoss/Reticulum.git", branch = "feature/channel" } -# rns = { path = "../Reticulum/", develop = true } -tomli = "^2.0.1" [tool.poetry.scripts] rnsh = 'rnsh.rnsh:rnsh_cli' @@ -22,6 +19,7 @@ pytest = "^7.2.1" setuptools = "^67.2.0" pytest-asyncio = "^0.20.3" safety = "^2.3.5" +tomli = "^2.0.1" [tool.pytest.ini_options] markers = [ From bd1c02e2562de5bb1c2592d409642b00b8032ddb Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:24:24 +0200 Subject: [PATCH 03/13] Cleanup --- rnsh/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rnsh/__init__.py b/rnsh/__init__.py index a89613c..c86cf47 100644 --- a/rnsh/__init__.py +++ b/rnsh/__init__.py @@ -23,7 +23,6 @@ import os module_abs_filename = os.path.abspath(__file__) module_dir = os.path.dirname(module_abs_filename) -# print(os.path.dirname(module_dir)) def _get_version(): def pkg_res_version(): From f18a0946c7a03eba95d96d919e0e299e9173a24a Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:25:10 +0200 Subject: [PATCH 04/13] Removed external dependency on docopt, included local version --- pyproject.toml | 1 - rnsh/docopt.py | 485 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 rnsh/docopt.py diff --git a/pyproject.toml b/pyproject.toml index 356d128..d02fa36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.7" -docopt = "^0.6.2" rns = ">=0.5.9" [tool.poetry.scripts] diff --git a/rnsh/docopt.py b/rnsh/docopt.py new file mode 100644 index 0000000..dcc39af --- /dev/null +++ b/rnsh/docopt.py @@ -0,0 +1,485 @@ +"""Pythonic command-line interface parser that will make you smile. + + * http://docopt.org + * Repository and issue-tracker: https://github.com/docopt/docopt + * Licensed under terms of MIT license (see LICENSE-MIT) + * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com + +""" +import sys +import re + +__all__ = ['docopt'] +__version__ = '0.6.2' + +class DocoptLanguageError(Exception): + + """Error in construction of usage-message by developer.""" + + +class DocoptExit(SystemExit): + + usage = '' + + def __init__(self, message=''): + SystemExit.__init__(self, (message + '\n' + self.usage).strip()) + + +class Pattern(object): + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + def fix(self): + self.fix_identities() + self.fix_repeating_arguments() + return self + + def fix_identities(self, uniq=None): + if not hasattr(self, 'children'): + return self + uniq = list(set(self.flat())) if uniq is None else uniq + for i, c in enumerate(self.children): + if not hasattr(c, 'children'): + assert c in uniq + self.children[i] = uniq[uniq.index(c)] + else: + c.fix_identities(uniq) + + def fix_repeating_arguments(self): + either = [list(c.children) for c in self.either.children] + for case in either: + for e in [c for c in case if case.count(c) > 1]: + if type(e) is Argument or type(e) is Option and e.argcount: + if e.value is None: + e.value = [] + elif type(e.value) is not list: + e.value = e.value.split() + if type(e) is Command or type(e) is Option and e.argcount == 0: + e.value = 0 + return self + + @property + def either(self): + ret = [] + groups = [[self]] + while groups: + children = groups.pop(0) + types = [type(c) for c in children] + if Either in types: + either = [c for c in children if type(c) is Either][0] + children.pop(children.index(either)) + for c in either.children: + groups.append([c] + children) + elif Required in types: + required = [c for c in children if type(c) is Required][0] + children.pop(children.index(required)) + groups.append(list(required.children) + children) + elif Optional in types: + optional = [c for c in children if type(c) is Optional][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif AnyOptions in types: + optional = [c for c in children if type(c) is AnyOptions][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif OneOrMore in types: + oneormore = [c for c in children if type(c) is OneOrMore][0] + children.pop(children.index(oneormore)) + groups.append(list(oneormore.children) * 2 + children) + else: + ret.append(children) + return Either(*[Required(*e) for e in ret]) + + +class ChildPattern(Pattern): + + def __init__(self, name, value=None): + self.name = name + self.value = value + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) + + def flat(self, *types): + return [self] if not types or type(self) in types else [] + + def match(self, left, collected=None): + collected = [] if collected is None else collected + pos, match = self.single_match(left) + if match is None: + return False, left, collected + left_ = left[:pos] + left[pos + 1:] + same_name = [a for a in collected if a.name == self.name] + if type(self.value) in (int, list): + if type(self.value) is int: + increment = 1 + else: + increment = ([match.value] if type(match.value) is str + else match.value) + if not same_name: + match.value = increment + return True, left_, collected + [match] + same_name[0].value += increment + return True, left_, collected + return True, left_, collected + [match] + + +class ParentPattern(Pattern): + + def __init__(self, *children): + self.children = list(children) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(repr(a) for a in self.children)) + + def flat(self, *types): + if type(self) in types: + return [self] + return sum([c.flat(*types) for c in self.children], []) + + +class Argument(ChildPattern): + + def single_match(self, left): + for n, p in enumerate(left): + if type(p) is Argument: + return n, Argument(self.name, p.value) + return None, None + + @classmethod + def parse(class_, source): + name = re.findall('(<\S*?>)', source)[0] + value = re.findall('\[default: (.*)\]', source, flags=re.I) + return class_(name, value[0] if value else None) + + +class Command(Argument): + + def __init__(self, name, value=False): + self.name = name + self.value = value + + def single_match(self, left): + for n, p in enumerate(left): + if type(p) is Argument: + if p.value == self.name: + return n, Command(self.name, True) + else: + break + return None, None + + +class Option(ChildPattern): + + def __init__(self, short=None, long=None, argcount=0, value=False): + assert argcount in (0, 1) + self.short, self.long = short, long + self.argcount, self.value = argcount, value + self.value = None if value is False and argcount else value + + @classmethod + def parse(class_, option_description): + short, long, argcount, value = None, None, 0, False + options, _, description = option_description.strip().partition(' ') + options = options.replace(',', ' ').replace('=', ' ') + for s in options.split(): + if s.startswith('--'): + long = s + elif s.startswith('-'): + short = s + else: + argcount = 1 + if argcount: + matched = re.findall('\[default: (.*)\]', description, flags=re.I) + value = matched[0] if matched else None + return class_(short, long, argcount, value) + + def single_match(self, left): + for n, p in enumerate(left): + if self.name == p.name: + return n, p + return None, None + + @property + def name(self): + return self.long or self.short + + def __repr__(self): + return 'Option(%r, %r, %r, %r)' % (self.short, self.long, + self.argcount, self.value) + + +class Required(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + l = left + c = collected + for p in self.children: + matched, l, c = p.match(l, c) + if not matched: + return False, left, collected + return True, l, c + + +class Optional(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + for p in self.children: + m, left, collected = p.match(left, collected) + return True, left, collected + + +class AnyOptions(Optional): + + """Marker/placeholder for [options] shortcut.""" + + +class OneOrMore(ParentPattern): + + def match(self, left, collected=None): + assert len(self.children) == 1 + collected = [] if collected is None else collected + l = left + c = collected + l_ = None + matched = True + times = 0 + while matched: + # could it be that something didn't match but changed l or c? + matched, l, c = self.children[0].match(l, c) + times += 1 if matched else 0 + if l_ == l: + break + l_ = l + if times >= 1: + return True, l, c + return False, left, collected + + +class Either(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + outcomes = [] + for p in self.children: + matched, _, _ = outcome = p.match(left, collected) + if matched: + outcomes.append(outcome) + if outcomes: + return min(outcomes, key=lambda outcome: len(outcome[1])) + return False, left, collected + + +class TokenStream(list): + + def __init__(self, source, error): + self += source.split() if hasattr(source, 'split') else source + self.error = error + + def move(self): + return self.pop(0) if len(self) else None + + def current(self): + return self[0] if len(self) else None + + +def parse_long(tokens, options): + long, eq, value = tokens.move().partition('=') + assert long.startswith('--') + value = None if eq == value == '' else value + similar = [o for o in options if o.long == long] + if tokens.error is DocoptExit and similar == []: # if no exact match + similar = [o for o in options if o.long and o.long.startswith(long)] + if len(similar) > 1: # might be simply specified ambiguously 2+ times? + raise tokens.error('%s is not a unique prefix: %s?' % + (long, ', '.join(o.long for o in similar))) + elif len(similar) < 1: + argcount = 1 if eq == '=' else 0 + o = Option(None, long, argcount) + options.append(o) + if tokens.error is DocoptExit: + o = Option(None, long, argcount, value if argcount else True) + else: + o = Option(similar[0].short, similar[0].long, + similar[0].argcount, similar[0].value) + if o.argcount == 0: + if value is not None: + raise tokens.error('%s must not have an argument' % o.long) + else: + if value is None: + if tokens.current() is None: + raise tokens.error('%s requires argument' % o.long) + value = tokens.move() + if tokens.error is DocoptExit: + o.value = value if value is not None else True + return [o] + + +def parse_shorts(tokens, options): + token = tokens.move() + assert token.startswith('-') and not token.startswith('--') + left = token.lstrip('-') + parsed = [] + while left != '': + short, left = '-' + left[0], left[1:] + similar = [o for o in options if o.short == short] + if len(similar) > 1: + raise tokens.error('%s is specified ambiguously %d times' % + (short, len(similar))) + elif len(similar) < 1: + o = Option(short, None, 0) + options.append(o) + if tokens.error is DocoptExit: + o = Option(short, None, 0, True) + else: # why copying is necessary here? + o = Option(short, similar[0].long, + similar[0].argcount, similar[0].value) + value = None + if o.argcount != 0: + if left == '': + if tokens.current() is None: + raise tokens.error('%s requires argument' % short) + value = tokens.move() + else: + value = left + left = '' + if tokens.error is DocoptExit: + o.value = value if value is not None else True + parsed.append(o) + return parsed + + +def parse_pattern(source, options): + tokens = TokenStream(re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source), + DocoptLanguageError) + result = parse_expr(tokens, options) + if tokens.current() is not None: + raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) + return Required(*result) + + +def parse_expr(tokens, options): + seq = parse_seq(tokens, options) + if tokens.current() != '|': + return seq + result = [Required(*seq)] if len(seq) > 1 else seq + while tokens.current() == '|': + tokens.move() + seq = parse_seq(tokens, options) + result += [Required(*seq)] if len(seq) > 1 else seq + return [Either(*result)] if len(result) > 1 else result + + +def parse_seq(tokens, options): + result = [] + while tokens.current() not in [None, ']', ')', '|']: + atom = parse_atom(tokens, options) + if tokens.current() == '...': + atom = [OneOrMore(*atom)] + tokens.move() + result += atom + return result + + +def parse_atom(tokens, options): + token = tokens.current() + result = [] + if token in '([': + tokens.move() + matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] + result = pattern(*parse_expr(tokens, options)) + if tokens.move() != matching: + raise tokens.error("unmatched '%s'" % token) + return [result] + elif token == 'options': + tokens.move() + return [AnyOptions()] + elif token.startswith('--') and token != '--': + return parse_long(tokens, options) + elif token.startswith('-') and token not in ('-', '--'): + return parse_shorts(tokens, options) + elif token.startswith('<') and token.endswith('>') or token.isupper(): + return [Argument(tokens.move())] + else: + return [Command(tokens.move())] + + +def parse_argv(tokens, options, options_first=False): + parsed = [] + while tokens.current() is not None: + if tokens.current() == '--': + return parsed + [Argument(None, v) for v in tokens] + elif tokens.current().startswith('--'): + parsed += parse_long(tokens, options) + elif tokens.current().startswith('-') and tokens.current() != '-': + parsed += parse_shorts(tokens, options) + elif options_first: + return parsed + [Argument(None, v) for v in tokens] + else: + parsed.append(Argument(None, tokens.move())) + return parsed + + +def parse_defaults(doc): + # in python < 2.7 you can't pass flags=re.MULTILINE + split = re.split('\n *(<\S+?>|-\S+?)', doc)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + #arguments = [Argument.parse(s) for s in split if s.startswith('<')] + #return options, arguments + return options + + +def printable_usage(doc): + # in python < 2.7 you can't pass flags=re.IGNORECASE + usage_split = re.split(r'([Uu][Ss][Aa][Gg][Ee]:)', doc) + if len(usage_split) < 3: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_split) > 3: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + return re.split(r'\n\s*\n', ''.join(usage_split[1:]))[0].strip() + + +def formal_usage(printable_usage): + pu = printable_usage.split()[1:] # split and drop "usage:" + return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' + + +def extras(help, version, options, doc): + if help and any((o.name in ('-h', '--help')) and o.value for o in options): + print(doc.strip("\n")) + sys.exit() + if version and any(o.name == '--version' and o.value for o in options): + print(version) + sys.exit() + + +class Dict(dict): + def __repr__(self): + return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) + + +def docopt(doc, argv=None, help=True, version=None, options_first=False): + if argv is None: + argv = sys.argv[1:] + DocoptExit.usage = printable_usage(doc) + options = parse_defaults(doc) + pattern = parse_pattern(formal_usage(DocoptExit.usage), options) + argv = parse_argv(TokenStream(argv, DocoptExit), list(options), + options_first) + pattern_options = set(pattern.flat(Option)) + for ao in pattern.flat(AnyOptions): + doc_options = parse_defaults(doc) + ao.children = list(set(doc_options) - pattern_options) + extras(help, version, argv, doc) + matched, left, collected = pattern.fix().match(argv) + if matched and left == []: # better error message if left? + return Dict((a.name, a.value) for a in (pattern.flat() + collected)) + raise DocoptExit() From eb4f94f57a956f654332e2b86b9ddcb6f39a464e Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:26:02 +0200 Subject: [PATCH 05/13] Updated versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d02fa36..4b39621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rnsh" -version = "0.1.2" +version = "0.1.3" description = "Shell over Reticulum" authors = ["acehoss "] license = "MIT" @@ -8,7 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.7" -rns = ">=0.5.9" +rns = ">=6.0.0" [tool.poetry.scripts] rnsh = 'rnsh.rnsh:rnsh_cli' From e03a5f34922706ecab514eaf38117c9e9774d8fe Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:28:28 +0200 Subject: [PATCH 06/13] Updated versions --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b39621..0a751d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rnsh" -version = "0.1.3" +version = "0.1.2" description = "Shell over Reticulum" authors = ["acehoss "] license = "MIT" From c9925226c9ee1ff36e71cd19e6c81219ebf374d9 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 08:29:47 +0200 Subject: [PATCH 07/13] Updated versions --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0a751d6..b7627f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.7" -rns = ">=6.0.0" +rns = ">=0.6.0" [tool.poetry.scripts] rnsh = 'rnsh.rnsh:rnsh_cli' From f5aab30d7bf41b52de329a334c4adbe3cdb30389 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 22 Sep 2023 09:55:07 +0200 Subject: [PATCH 08/13] Use local docopts --- rnsh/args.py | 2 +- tests/test_args.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rnsh/args.py b/rnsh/args.py index 3a543f7..5621694 100644 --- a/rnsh/args.py +++ b/rnsh/args.py @@ -1,8 +1,8 @@ from typing import TypeVar import RNS import rnsh -import docopt import sys +from rnsh import docopt _T = TypeVar("_T") diff --git a/tests/test_args.py b/tests/test_args.py index f9c7cc4..fec13a5 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -1,6 +1,6 @@ import rnsh.args import shlex -import docopt +from rnsh import docopt def test_program_args(): docopt_threw = False From 4da540f1895ffed6648379ef6926bca765b5eb92 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 11 May 2024 12:40:28 +0200 Subject: [PATCH 09/13] Fixed missing format string declarator --- rnsh/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rnsh/process.py b/rnsh/process.py index f583cb7..9bbe770 100644 --- a/rnsh/process.py +++ b/rnsh/process.py @@ -134,7 +134,7 @@ def tty_read_poll(fd: int) -> bytes: except EOFError: raise except Exception as ex: - module_logger.error("tty_read error: {ex}") + module_logger.error(f"tty_read error: {ex}") return result From a77451743bfda941e953565df31cbd3420b7cf70 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 11 May 2024 13:14:51 +0200 Subject: [PATCH 10/13] Fixed invalid escape sequence handling. This fixes terminal escape sequences such as Page Up/down, home/end, pasting text and others not being processed correctly. --- rnsh/initiator.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rnsh/initiator.py b/rnsh/initiator.py index 3ec6bba..ec1c3f5 100644 --- a/rnsh/initiator.py +++ b/rnsh/initiator.py @@ -283,25 +283,26 @@ def sigwinch_handler(): flush_chars = ["\x01", "\x03", "\x04", "\x05", "\x0c", "\x11", "\x13", "\x15", "\x19", "\t", "\x1A", "\x1B"] def handle_escape(b): nonlocal line_mode - if b == "~": - return "~" - elif b == "?": + if b == "?": os.write(1, "\n\r\n\rSupported rnsh escape sequences:".encode("utf-8")) os.write(1, "\n\r ~~ Send the escape character by typing it twice".encode("utf-8")) os.write(1, "\n\r ~. Terminate session and exit immediately".encode("utf-8")) os.write(1, "\n\r ~L Toggle line-interactive mode".encode("utf-8")) os.write(1, "\n\r ~? Display this quick reference\n\r".encode("utf-8")) os.write(1, "\n\r(Escape sequences are only recognized immediately after newline)\n\r".encode("utf-8")) + return None elif b == ".": _link.teardown() + return None elif b == "L": line_mode = not line_mode if line_mode: os.write(1, "\n\rLine-interactive mode enabled\n\r".encode("utf-8")) else: os.write(1, "\n\rLine-interactive mode disabled\n\r".encode("utf-8")) - - return None + return None + + return b stdin_eof = False def stdin(): @@ -318,9 +319,11 @@ def stdin(): line_flush = True data.append(b) elif line_mode and c in flush_chars: + pre_esc = False line_flush = True data.append(b) elif line_mode and (c == "\b" or c == "\x7f"): + pre_esc = False if len(line_buffer)>0: line_buffer.pop(-1) blind_write_count -= 1 @@ -331,9 +334,12 @@ def stdin(): elif esc == True: ret = handle_escape(c) if ret != None: + if ret != "~": + data.append(ord("~")) data.append(ord(ret)) esc = False else: + pre_esc = False data.append(b) if not line_mode: From 168f91908efbdd3c657aa0d284a9ca3a1dfa21a8 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 11 May 2024 13:16:18 +0200 Subject: [PATCH 11/13] Updated version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8de38a..3e227d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rnsh" -version = "0.1.3" +version = "0.1.4" description = "Shell over Reticulum" authors = ["acehoss "] license = "MIT" From cca430ca188fd9963c71ae28d4b535a7b5be2ed6 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 11 May 2024 13:17:26 +0200 Subject: [PATCH 12/13] Bumped RNS dependency to latest release version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3e227d6..1402ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.7" -rns = ">=0.6.0" +rns = ">=0.7.4" [tool.poetry.scripts] rnsh = 'rnsh.rnsh:rnsh_cli' From c28adee5ac110a849792373e28d9829216f6f912 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 11 May 2024 13:50:41 +0200 Subject: [PATCH 13/13] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bed666d..7d2c359 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ Process messages received from listener. - [X] ~~Test and fix more issues~~ - [ ] More betas - [ ] Enhancement Ideas - - [ ] `authorized_keys` mode similar to SSH to allow one listener + - [x] `authorized_keys` mode similar to SSH to allow one listener process to serve multiple users - [ ] Git over `rnsh` (git remote helper) - [ ] Sliding window acknowledgements for improved throughput