From 8912bef6e56bf8a432a89b1575d5dc951990c8c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:33:25 +0000 Subject: [PATCH 1/2] Initial plan From d7df1fbbc08e5b5bb74d3b47cf7d27a8dbbd9b91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:36:20 +0000 Subject: [PATCH 2/2] Refactor Choices __new__, fix inexact type conversion, add _split_unquoted, fix typos Agent-Logs-Url: https://github.com/NetApp/recline/sessions/bda7b8f0-6028-489a-a229-ac1788166c6e Co-authored-by: RobertBlackhart <5414318+RobertBlackhart@users.noreply.github.com> --- recline/arg_types/choices.py | 24 ++++++------- recline/repl/shell.py | 65 +++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/recline/arg_types/choices.py b/recline/arg_types/choices.py index 0804511..bc0b392 100644 --- a/recline/arg_types/choices.py +++ b/recline/arg_types/choices.py @@ -48,21 +48,13 @@ def define( """ class _Choices(Choices): - def __new__(cls): - instance = super().__new__(cls) - cls.available_choices = available_choices - if isinstance(available_choices, list): - cls.metavar = f"<{'|'.join(available_choices)}>" - return instance - def validate(self, arg): - if inexact: - return arg - current_choices = self.choices(eager=True) - if arg not in current_choices: - raise ReclineTypeError( - f"\"{arg}\" must be one of {', '.join(current_choices)}." - ) + if not inexact: + current_choices = self.choices(eager=True) + if arg not in current_choices: + raise ReclineTypeError( + f"\"{arg}\" must be one of {', '.join(current_choices)}." + ) try: return data_type(arg) except Exception: # pylint: disable=broad-except @@ -89,4 +81,8 @@ def choices(self, eager=False): def completer(self, *args, **kwargs): return self.choices(eager=True) + _Choices.available_choices = available_choices + if isinstance(available_choices, list): + _Choices.metavar = f"<{'|'.join(available_choices)}>" + return _Choices diff --git a/recline/repl/shell.py b/recline/repl/shell.py index ba55519..34ad256 100644 --- a/recline/repl/shell.py +++ b/recline/repl/shell.py @@ -1,7 +1,7 @@ """ Original © NetApp 2024 -This is the main staring point for a recline application +This is the main starting point for a recline application """ import atexit @@ -56,8 +56,8 @@ def relax( repl_mode: By default, applications using recline expect to act as a REPL environment where the user will run many commands. These applications can still run a single command if the user passes a -c. But, it is - convienent for some applications to act more like a traditional CLI - command without needing to pass -c. For those applicatinos, repl_mode + convenient for some applications to act more like a traditional CLI + command without needing to pass -c. For those applications, repl_mode can be set to False. single_command: This acts a bit like non-repl mode, except in this case the user doesn't have to pass any command name as an argument to the @@ -108,25 +108,72 @@ def relax( sys.exit(0) +def _split_unquoted(current_input: str, separator: str) -> List[str]: + """Split `current_input` on `separator` only when outside quoted strings.""" + parts: List[str] = [] + start = 0 + i = 0 + in_single = False + in_double = False + escaped = False + + while i < len(current_input): + char = current_input[i] + + if escaped: + escaped = False + i += 1 + continue + + if char == "\\": + escaped = True + i += 1 + continue + + if char == "'" and not in_double: + in_single = not in_single + i += 1 + continue + + if char == '"' and not in_single: + in_double = not in_double + i += 1 + continue + + if not in_single and not in_double and current_input.startswith(separator, i): + parts.append(current_input[start:i]) + i += len(separator) + start = i + continue + + i += 1 + + parts.append(current_input[start:]) + return parts + + def execute(current_input: str) -> int: """Execute the input as a series of commands""" result = 0 - if ';' in current_input: - for command in current_input.split(';'): + semicolon_commands = _split_unquoted(current_input, ';') + if len(semicolon_commands) > 1: + for command in semicolon_commands: result = execute(command) return result - if '&&' in current_input: - for command in current_input.split('&&'): + and_commands = _split_unquoted(current_input, '&&') + if len(and_commands) > 1: + for command in and_commands: result = execute(command) if result > 0: break return result - if "||" in current_input: - for command in current_input.split('||'): + or_commands = _split_unquoted(current_input, '||') + if len(or_commands) > 1: + for command in or_commands: result = execute(command) if result == 0: break