109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
import re
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from sweagent.tools.commands import Command
|
|
|
|
|
|
def _guard_multiline_input(action: str, match_fct: Callable[[str], re.Match | None]) -> str:
|
|
"""Split action by multiline commands, then append the first line in each multiline command with "<< '{end_name}'".
|
|
Multiline commands (which are specified by an end_name) are commands that span multiple lines and are terminated by a specific end_name.
|
|
|
|
Their multi-line argument is sent using a heredoc, which is a way to send a multi-line string to a command in bash.
|
|
"""
|
|
parsed_action = []
|
|
rem_action = action
|
|
while rem_action.strip():
|
|
first_match = match_fct(rem_action)
|
|
if first_match:
|
|
pre_action = rem_action[: first_match.start()]
|
|
match_action = rem_action[first_match.start() : first_match.end()]
|
|
rem_action = rem_action[first_match.end() :]
|
|
if pre_action.strip():
|
|
parsed_action.append(pre_action)
|
|
if match_action.strip():
|
|
eof = first_match.group(3).strip()
|
|
if not match_action.split("\n")[0].strip().endswith(f"<< '{eof}'"):
|
|
guarded_command = match_action[first_match.start() :]
|
|
first_line = guarded_command.split("\n")[0]
|
|
guarded_command = guarded_command.replace(first_line, first_line + f" << '{eof}'", 1)
|
|
parsed_action.append(guarded_command)
|
|
else:
|
|
parsed_action.append(match_action)
|
|
else:
|
|
parsed_action.append(rem_action)
|
|
rem_action = ""
|
|
return "\n".join(parsed_action)
|
|
|
|
|
|
def _should_quote(value: Any, command: Command) -> bool:
|
|
"""Returns True if the value should be quoted, False otherwise."""
|
|
if command.name == "bash":
|
|
return False
|
|
return isinstance(value, str) and command.end_name is None
|
|
|
|
|
|
def get_signature(cmd):
|
|
"""Generate a command signature from its arguments.
|
|
|
|
Args:
|
|
cmd: Command object to generate signature for
|
|
|
|
Returns:
|
|
Formatted signature string
|
|
"""
|
|
signature = cmd.name
|
|
if "arguments" in cmd.__dict__ and cmd.arguments is not None:
|
|
if cmd.end_name is None:
|
|
for argument in cmd.arguments:
|
|
param = argument.name
|
|
if argument.required:
|
|
signature += f" <{param}>"
|
|
else:
|
|
signature += f" [<{param}>]"
|
|
else:
|
|
for argument in cmd.arguments[:-1]:
|
|
param = argument.name
|
|
if argument.required:
|
|
signature += f" <{param}>"
|
|
else:
|
|
signature += f" [<{param}>]"
|
|
signature += f"\n{list(cmd.arguments[-1].keys())[0]}\n{cmd.end_name}"
|
|
return signature
|
|
|
|
|
|
def generate_command_docs(
|
|
commands: list[Command],
|
|
subroutine_types,
|
|
**kwargs,
|
|
) -> str:
|
|
"""Generate detailed command documentation.
|
|
|
|
Format includes docstring, signature and argument details.
|
|
|
|
Args:
|
|
commands: List of commands to document
|
|
subroutine_types: List of subroutines to document
|
|
**kwargs: Additional format variables for docstrings
|
|
|
|
Returns:
|
|
Formatted documentation string
|
|
"""
|
|
docs = ""
|
|
for cmd in commands + subroutine_types:
|
|
docs += f"{cmd.name}:\n"
|
|
if cmd.docstring is not None:
|
|
docs += f" docstring: {cmd.docstring.format(**kwargs)}\n"
|
|
if cmd.signature is not None:
|
|
docs += f" signature: {cmd.signature}\n"
|
|
else:
|
|
docs += f" signature: {get_signature(cmd)}\n"
|
|
if cmd.arguments:
|
|
docs += " arguments:\n"
|
|
for argument in cmd.arguments:
|
|
param = argument.name
|
|
req_string = "required" if argument.required else "optional"
|
|
docs += f" - {param} ({argument.type}) [{req_string}]: {argument.description}\n"
|
|
docs += "\n"
|
|
return docs
|