mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-03-29 12:59:59 +00:00
190 lines
7.2 KiB
Python
Executable File
190 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import sys
|
|
import traceback
|
|
import chameleon_com
|
|
import colorama
|
|
import chameleon_cli_unit
|
|
import chameleon_utils
|
|
import pathlib
|
|
import prompt_toolkit
|
|
from prompt_toolkit.formatted_text import ANSI
|
|
from prompt_toolkit.history import FileHistory
|
|
from chameleon_utils import CR, CG, CY, color_string
|
|
|
|
ULTRA = r"""
|
|
╦ ╦╦ ╔╦╗╦═╗╔═╗
|
|
███████ ║ ║║ ║ ╠╦╝╠═╣
|
|
╚═╝╩═╝╩ ╩╚═╩ ╩
|
|
"""
|
|
|
|
LITE = r"""
|
|
╦ ╦╔╦╗╔═╗
|
|
███████ ║ ║ ║ ║╣
|
|
╩═╝╩ ╩ ╚═╝
|
|
"""
|
|
|
|
# create by http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=Chameleon%20Ultra
|
|
BANNER = """
|
|
██████╗██╗ ██╗ █████╗ ██╗ ██╗███████╗██╗ ███████╗ █████╗ ██╗ ██╗
|
|
██╔════╝██║ ██║██╔══██╗███╗ ███║██╔════╝██║ ██╔════╝██╔══██╗███╗ ██║
|
|
██║ ███████║███████║████████║█████╗ ██║ █████╗ ██║ ██║████╗██║
|
|
██║ ██╔══██║██╔══██║██╔██╔██║██╔══╝ ██║ ██╔══╝ ██║ ██║██╔████║
|
|
╚██████╗██║ ██║██║ ██║██║╚═╝██║███████╗███████╗███████╗╚█████╔╝██║╚███║
|
|
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚════╝ ╚═╝ ╚══╝
|
|
"""
|
|
|
|
|
|
class ChameleonCLI:
|
|
"""
|
|
CLI for chameleon
|
|
"""
|
|
|
|
def __init__(self):
|
|
# new a device communication instance(only communication)
|
|
self.device_com = chameleon_com.ChameleonCom()
|
|
|
|
def get_cmd_node(self, node: chameleon_utils.CLITree,
|
|
cmdline: list[str]) -> tuple[chameleon_utils.CLITree, list[str]]:
|
|
"""
|
|
Recursively traverse the command line tree to get to the matching node
|
|
|
|
:return: last matching CLITree node, remaining tokens
|
|
"""
|
|
# No more subcommands to parse, return node
|
|
if cmdline == []:
|
|
return node, []
|
|
|
|
for child in node.children:
|
|
if cmdline[0] == child.name:
|
|
return self.get_cmd_node(child, cmdline[1:])
|
|
|
|
# No matching child node
|
|
return node, cmdline[:]
|
|
|
|
def get_prompt(self):
|
|
"""
|
|
Retrieve the cli prompt
|
|
|
|
:return: current cmd prompt
|
|
"""
|
|
if self.device_com.isOpen():
|
|
status = color_string((CG, 'USB'))
|
|
else:
|
|
status = color_string((CR, 'Offline'))
|
|
|
|
return ANSI(f"[{status}] chameleon --> ")
|
|
|
|
@staticmethod
|
|
def print_banner():
|
|
"""
|
|
print chameleon ascii banner.
|
|
|
|
:return:
|
|
"""
|
|
print(color_string((CY, BANNER)))
|
|
|
|
def exec_cmd(self, cmd_str):
|
|
if cmd_str == '':
|
|
return
|
|
|
|
# look for alternate exit
|
|
if cmd_str in ["quit", "q", "e"]:
|
|
cmd_str = 'exit'
|
|
|
|
# look for alternate comments
|
|
if cmd_str[0] in ";#%":
|
|
cmd_str = 'rem ' + cmd_str[1:].lstrip()
|
|
|
|
# parse cmd
|
|
argv = cmd_str.split()
|
|
|
|
tree_node, arg_list = self.get_cmd_node(chameleon_cli_unit.root, argv)
|
|
if not tree_node.cls:
|
|
# Found tree node is a group without an implementation, print children
|
|
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
|
for child in tree_node.children:
|
|
cmd_title = color_string((CG, child.name))
|
|
if not child.cls:
|
|
help_line = (f" - {cmd_title}".ljust(37)) + f"{{ {child.help_text}... }}"
|
|
else:
|
|
help_line = (f" - {cmd_title}".ljust(37)) + f"{child.help_text}"
|
|
print(help_line)
|
|
return
|
|
|
|
unit: chameleon_cli_unit.BaseCLIUnit = tree_node.cls()
|
|
unit.device_com = self.device_com
|
|
args_parse_result = unit.args_parser()
|
|
|
|
assert args_parse_result is not None
|
|
args: argparse.ArgumentParser = args_parse_result
|
|
args.prog = tree_node.fullname
|
|
try:
|
|
args_parse_result = args.parse_args(arg_list)
|
|
if args.help_requested:
|
|
return
|
|
except chameleon_utils.ArgsParserError as e:
|
|
args.print_help()
|
|
print(color_string((CY, str(e).strip())))
|
|
return
|
|
except chameleon_utils.ParserExitIntercept:
|
|
# don't exit process.
|
|
return
|
|
try:
|
|
# before process cmd, we need to do something...
|
|
if not unit.before_exec(args_parse_result):
|
|
return
|
|
|
|
# start process cmd, delay error to call after_exec firstly
|
|
error = None
|
|
try:
|
|
unit.on_exec(args_parse_result)
|
|
except Exception as e:
|
|
error = e
|
|
unit.after_exec(args_parse_result)
|
|
if error is not None:
|
|
raise error
|
|
|
|
except (chameleon_utils.UnexpectedResponseError, chameleon_utils.ArgsParserError) as e:
|
|
print(color_string((CR, str(e))))
|
|
except Exception:
|
|
print(f"CLI exception: {color_string((CR, traceback.format_exc()))}")
|
|
|
|
def startCLI(self):
|
|
"""
|
|
start listen input.
|
|
|
|
:return:
|
|
"""
|
|
self.completer = chameleon_utils.CustomNestedCompleter.from_clitree(chameleon_cli_unit.root)
|
|
self.session = prompt_toolkit.PromptSession(completer=self.completer,
|
|
history=FileHistory(str(pathlib.Path.home() /
|
|
".chameleon_history")))
|
|
|
|
self.print_banner()
|
|
cmd_strs = []
|
|
while True:
|
|
if cmd_strs:
|
|
cmd_str = cmd_strs.pop(0)
|
|
else:
|
|
# wait user input
|
|
try:
|
|
cmd_str = self.session.prompt(
|
|
self.get_prompt()).strip()
|
|
cmd_strs = cmd_str.replace(
|
|
"\r\n", "\n").replace("\r", "\n").split("\n")
|
|
cmd_str = cmd_strs.pop(0)
|
|
except EOFError:
|
|
cmd_str = 'exit'
|
|
except KeyboardInterrupt:
|
|
cmd_str = 'exit'
|
|
self.exec_cmd(cmd_str)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if sys.version_info < (3, 9):
|
|
raise Exception("This script requires at least Python 3.9")
|
|
colorama.init(autoreset=True)
|
|
chameleon_cli_unit.check_tools()
|
|
ChameleonCLI().startCLI()
|