Skip to content

Commit a6900a3

Browse files
committed
script engine and basic tests
1 parent b108c84 commit a6900a3

4 files changed

Lines changed: 781 additions & 0 deletions

File tree

software/script/chameleon_cli_unit.py

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import chameleon_com
2525
import chameleon_cmd
26+
import chameleon_script
2627
from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError, execute_tool, \
2728
tqdm_if_exists, print_key_table
2829
from chameleon_utils import CLITree
@@ -4724,3 +4725,251 @@ def on_exec(self, args: argparse.Namespace):
47244725
)
47254726
else:
47264727
print(f" [*] {color_string((CY, 'No response'))}")
4728+
4729+
4730+
# Scripting commands
4731+
script = root.subgroup('script', 'Script management and execution')
4732+
4733+
@script.command('run')
4734+
class ScriptRun(BaseCLIUnit):
4735+
"""Run a Python script"""
4736+
4737+
def args_parser(self) -> ArgumentParserNoExit:
4738+
parser = ArgumentParserNoExit()
4739+
parser.description = 'Run a Python script'
4740+
parser.add_argument('filename', help='Script filename')
4741+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
4742+
return parser
4743+
4744+
def on_exec(self, args):
4745+
try:
4746+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4747+
script_content = script_engine.load_script(args.filename)
4748+
4749+
# Show the actual path being used
4750+
script_path = Path(args.filename)
4751+
if not script_path.is_absolute():
4752+
actual_path = script_engine.scripts_dir / args.filename
4753+
if actual_path.exists():
4754+
script_path = actual_path
4755+
else:
4756+
script_path = Path.cwd() / args.filename
4757+
4758+
print(f" [*] Running script: {color_string((CG, str(script_path)))}")
4759+
4760+
result = script_engine.execute_script(script_content, args.filename)
4761+
4762+
if result['success']:
4763+
print(f" [+] {color_string((CG, 'Script executed successfully'))}")
4764+
if result['output'].strip():
4765+
print(" [*] Output:")
4766+
print(result['output'])
4767+
if args.verbose and result['variables']:
4768+
print(" [*] Variables:")
4769+
for key, value in result['variables'].items():
4770+
print(f" {key}: {value}")
4771+
else:
4772+
print(f" [!] {color_string((CR, 'Script execution failed'))}")
4773+
if result['error']:
4774+
print(f" [!] Error: {result['error']}")
4775+
if args.verbose and 'traceback' in result:
4776+
print(" [*] Traceback:")
4777+
print(result['traceback'])
4778+
4779+
except FileNotFoundError:
4780+
print(f" [!] {color_string((CR, f'Script file not found: {args.filename}'))}")
4781+
print(f" [*] Tried paths:")
4782+
print(f" - {chameleon_script.ScriptEngine(self.device_com).scripts_dir / args.filename}")
4783+
print(f" - {Path.cwd() / args.filename}")
4784+
if Path(args.filename).is_absolute():
4785+
print(f" - {args.filename}")
4786+
except Exception as e:
4787+
print(f" [!] {color_string((CR, f'Error running script: {e}'))}")
4788+
4789+
4790+
@script.command('exec')
4791+
class ScriptExec(BaseCLIUnit):
4792+
"""Execute Python code directly"""
4793+
4794+
def args_parser(self) -> ArgumentParserNoExit:
4795+
parser = ArgumentParserNoExit()
4796+
parser.description = 'Execute Python code directly'
4797+
parser.add_argument('code', help='Python code to execute')
4798+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
4799+
return parser
4800+
4801+
def on_exec(self, args):
4802+
try:
4803+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4804+
4805+
print(f" [*] Executing code...")
4806+
4807+
result = script_engine.execute_script(args.code)
4808+
4809+
if result['success']:
4810+
print(f" [+] {color_string((CG, 'Code executed successfully'))}")
4811+
if result['output'].strip():
4812+
print(" [*] Output:")
4813+
print(result['output'])
4814+
if args.verbose and result['variables']:
4815+
print(" [*] Variables:")
4816+
for key, value in result['variables'].items():
4817+
print(f" {key}: {value}")
4818+
else:
4819+
print(f" [!] {color_string((CR, 'Code execution failed'))}")
4820+
if result['error']:
4821+
print(f" [!] Error: {result['error']}")
4822+
if args.verbose and 'traceback' in result:
4823+
print(" [*] Traceback:")
4824+
print(result['traceback'])
4825+
4826+
except Exception as e:
4827+
print(f" [!] {color_string((CR, f'Error executing code: {e}'))}")
4828+
4829+
4830+
@script.command('list')
4831+
class ScriptList(BaseCLIUnit):
4832+
"""List available scripts"""
4833+
4834+
def args_parser(self) -> ArgumentParserNoExit:
4835+
parser = ArgumentParserNoExit()
4836+
parser.description = 'List available scripts'
4837+
return parser
4838+
4839+
def on_exec(self, args):
4840+
try:
4841+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4842+
scripts = script_engine.list_scripts()
4843+
4844+
if scripts:
4845+
print(f" [*] Available scripts:")
4846+
for script in scripts:
4847+
print(f" - {color_string((CG, script))}")
4848+
else:
4849+
print(f" [*] {color_string((CY, 'No scripts found'))}")
4850+
print(f" [*] Scripts directory: {script_engine.scripts_dir}")
4851+
4852+
except Exception as e:
4853+
print(f" [!] {color_string((CR, f'Error listing scripts: {e}'))}")
4854+
4855+
4856+
@script.command('save')
4857+
class ScriptSave(BaseCLIUnit):
4858+
"""Save a script to file"""
4859+
4860+
def args_parser(self) -> ArgumentParserNoExit:
4861+
parser = ArgumentParserNoExit()
4862+
parser.description = 'Save a script to file'
4863+
parser.add_argument('filename', help='Script filename')
4864+
parser.add_argument('--code', '-c', help='Script code content')
4865+
return parser
4866+
4867+
def on_exec(self, args):
4868+
try:
4869+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4870+
4871+
if args.code:
4872+
script_content = args.code
4873+
else:
4874+
print(" [*] Enter script content (Ctrl+D to finish):")
4875+
script_content = []
4876+
try:
4877+
while True:
4878+
line = input()
4879+
script_content.append(line)
4880+
except EOFError:
4881+
pass
4882+
script_content = '\n'.join(script_content)
4883+
4884+
script_engine.save_script(args.filename, script_content)
4885+
print(f" [+] {color_string((CG, f'Script saved: {args.filename}'))}")
4886+
4887+
except Exception as e:
4888+
print(f" [!] {color_string((CR, f'Error saving script: {e}'))}")
4889+
4890+
4891+
@script.command('show')
4892+
class ScriptShow(BaseCLIUnit):
4893+
"""Show script content"""
4894+
4895+
def args_parser(self) -> ArgumentParserNoExit:
4896+
parser = ArgumentParserNoExit()
4897+
parser.description = 'Show script content'
4898+
parser.add_argument('filename', help='Script filename')
4899+
return parser
4900+
4901+
def on_exec(self, args):
4902+
try:
4903+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4904+
script_content = script_engine.load_script(args.filename)
4905+
4906+
print(f" [*] Content of {color_string((CG, args.filename))}:")
4907+
print("-" * 50)
4908+
print(script_content)
4909+
print("-" * 50)
4910+
4911+
except FileNotFoundError:
4912+
print(f" [!] {color_string((CR, f'Script file not found: {args.filename}'))}")
4913+
except Exception as e:
4914+
print(f" [!] {color_string((CR, f'Error showing script: {e}'))}")
4915+
4916+
4917+
@script.command('vars')
4918+
class ScriptVars(BaseCLIUnit):
4919+
"""Show current script variables"""
4920+
4921+
def args_parser(self) -> ArgumentParserNoExit:
4922+
parser = ArgumentParserNoExit()
4923+
parser.description = 'Show current script variables'
4924+
return parser
4925+
4926+
def on_exec(self, args):
4927+
try:
4928+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4929+
4930+
if script_engine.env.variables:
4931+
print(f" [*] Current script variables:")
4932+
for key, value in script_engine.env.variables.items():
4933+
print(f" {key}: {value}")
4934+
else:
4935+
print(f" [*] {color_string((CY, 'No variables set'))}")
4936+
4937+
except Exception as e:
4938+
print(f" [!] {color_string((CR, f'Error showing variables: {e}'))}")
4939+
4940+
4941+
@script.command('clear')
4942+
class ScriptClear(BaseCLIUnit):
4943+
"""Clear script variables"""
4944+
4945+
def args_parser(self) -> ArgumentParserNoExit:
4946+
parser = ArgumentParserNoExit()
4947+
parser.description = 'Clear script variables'
4948+
return parser
4949+
4950+
def on_exec(self, args):
4951+
try:
4952+
script_engine = chameleon_script.ScriptEngine(self.device_com)
4953+
script_engine.env.clear_variables()
4954+
print(f" [+] {color_string((CG, 'Script variables cleared'))}")
4955+
4956+
except Exception as e:
4957+
print(f" [!] {color_string((CR, f'Error clearing variables: {e}'))}")
4958+
4959+
4960+
# Example script command
4961+
@root.command('py')
4962+
class PyCommand(BaseCLIUnit):
4963+
"""Execute Python code (alias for script exec)"""
4964+
4965+
def args_parser(self) -> ArgumentParserNoExit:
4966+
parser = ArgumentParserNoExit()
4967+
parser.description = 'Execute Python code (alias for script exec)'
4968+
parser.add_argument('code', help='Python code to execute')
4969+
return parser
4970+
4971+
def on_exec(self, args):
4972+
# Reuse ScriptExec functionality
4973+
script_exec = ScriptExec()
4974+
script_exec.device_com = self.device_com
4975+
script_exec.on_exec(args)

0 commit comments

Comments
 (0)