Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue#20 Improve CLI operations #43

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added bin/__init__.py
Empty file.
113 changes: 97 additions & 16 deletions bin/fkCli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,42 @@
"close": "stop",
"connect": "start",
}
# argument should be a string
ARGS = {
"path": "/path:str",
"data": "data:str",
"version": "version:int",
}

# flag should be a set of its representations (strings)
KWARGS = {
"ephemeral": {"-e", "--ephemeral"},
"sequence": {"-s", "--sequence"},
"watch": {"-w", "--watch"},
"includeData": {"-i", "--includeData"},
}

# program can distinguish kwarg and arg by their type
CMD = {
# create /test 0x0 False False
"create": [ARGS["path"], ARGS["data"], KWARGS["ephemeral"], KWARGS["sequence"]],
"get": [ARGS["path"], KWARGS["watch"]],
"set": [ARGS["path"], ARGS["data"], ARGS["version"]],
"delete": [ARGS["path"], ARGS["version"]],
"exists": [ARGS["path"]],
"help": [],
"getChildren": [ARGS["path"], KWARGS["includeData"]],
}

INPUT_FORMAT = {
"create": "create -e -s /test 0x0",
"get": "get -w /test",
"set": "set /test 0x0 -1",
"delete": "delete /test -1",
"exists": "exists /test",
"help": "help",
"getChildren": "getChildren -i /test",
}

fkCompleter = WordCompleter(keywords, ignore_case=True)

Expand All @@ -72,35 +108,41 @@ def process_cmd(client: FaaSKeeperClient, cmd: str, args: List[str]):
function = getattr(client, clientAPIMapping[cmd])
sig = signature(function)
params_count = len(sig.parameters)
# incorrect number of parameters
if params_count != len(args):
msg = f"{cmd} arguments:"
for param in sig.parameters.values():
# "watch" requires conversion - API uses a callback
# the CLI is a boolean switch if callback should be use or not
if param.name == "watch":
msg += f" watch:bool"
else:
msg += f" {param.name}:{param.annotation.__name__}"
click.echo(msg)
return client.session_status, client.session_id
# check incorrect number of parameters
if cmd in CMD: # check parsed arguments
# in this case, number of parsed arguments is always correct, so we need to check it type
# if an argument is missing, it will be a boolean value
for idx, param in enumerate(sig.parameters.values()):
if param.annotation != bool and isinstance(args[idx], bool) and param.name != "watch":
click.echo(f"Command Example: {INPUT_FORMAT[cmd]}")
return client.session_status, client.session_id
else:
if params_count != len(args):
msg = f"{cmd} arguments:"
for param in sig.parameters.values():
# "watch" requires conversion - API uses a callback
# the CLI is a boolean switch if callback should be use or not
if param.name == "watch":
msg += f" watch:bool"
else:
msg += f" {param.name}:{param.annotation.__name__}"
click.echo(msg)
return client.session_status, client.session_id

# convert arguments
converted_arguments = []
for idx, param in enumerate(sig.parameters.values()):
# "watch" requires conversion - API uses a callback
# the CLI is a boolean switch if callback should be use or not
if param.name == "watch":
if args[idx].lower() == "true":
if args[idx]:
converted_arguments.append(watch_callback)
else:
converted_arguments.append(None)
continue

if bytes == param.annotation:
converted_arguments.append(args[idx].encode())
elif bool == param.annotation:
converted_arguments.append(bool(args[idx]))
else:
converted_arguments.append(args[idx])
try:
Expand Down Expand Up @@ -137,6 +179,40 @@ def process_cmd(client: FaaSKeeperClient, cmd: str, args: List[str]):
return client.session_status, client.session_id


# find the position of the argument in the command (function call like), return -1 if the argument is not a flag
def kwarg_pos(arg: str, kwargs_array: List[str]):
for idx, kwarg in enumerate(kwargs_array):
if isinstance(kwarg, set) and (arg in kwarg):
return idx
return -1

PARSE_SUCCESS = 1
PARSE_FAIL = 0

# parse the arguments and return the parsed arguments
def parse_args(cmd: str, args: List[str]):
if cmd not in CMD:
return PARSE_SUCCESS, args
else:
try:
assert len(args) <= len(CMD[cmd]) # check if the number of arguments is correct
parsed_args = [False] * len(CMD[cmd])
arg_idx = parse_args_idx = 0
while arg_idx < len(args):
idx = kwarg_pos(args[arg_idx], CMD[cmd])
if idx != -1:
parsed_args[idx] = True
else:
while isinstance(CMD[cmd][parse_args_idx], set): # skip the positions for flags
parse_args_idx += 1
parsed_args[parse_args_idx] = args[arg_idx]
parse_args_idx += 1
arg_idx += 1
except Exception as e:
return PARSE_FAIL, None
return PARSE_SUCCESS, parsed_args


@click.command()
@click.argument("config", type=click.File("r"))
@click.option("--port", type=int, default=-1)
Expand Down Expand Up @@ -190,7 +266,12 @@ def cli(config, port: int, verbose: str):
elif cmd not in keywords:
click.echo(f"Unknown command {text}")
else:
status, session_id = process_cmd(client, cmd, cmds[1:])
parse_status, parsed_args = parse_args(cmd, cmds[1:])
if parse_status == PARSE_FAIL:
click.echo(f"Command Example: {INPUT_FORMAT[cmd]}")
status, session_id = client.session_status, client.session_id
else:
status, session_id = process_cmd(client, cmd, parsed_args)
counter += 1

print("Closing...")
Expand Down
Empty file added tests/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions tests/cli_parser_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest

from bin.fkCli import parse_args

def test_cli():
print("test create")
assert ['/test', '0x0', True, True] == parse_args("create", ["-e", "-s", "/test", "0x0"])[1]
assert ['/test', '0x0', True, True] == parse_args("create", ["/test", "0x0", "-e", "-s"])[1]
assert ['/test', '0x0', False, False] == parse_args("create", ["/test", "0x0"])[1]
assert ['/test', '0x0', True, False] == parse_args("create", ["/test", "0x0", "-e"])[1]
assert ['/test', '0x0', False, True] == parse_args("create", ["/test", "0x0", "-s"])[1]
print("test get")
assert ['/test', True] == parse_args("get", ["/test", "-w"])[1]
assert ['/test', True] == parse_args("get", ["-w", "/test"])[1]
assert ['/test', False] == parse_args("get", ["/test"])[1]
print("test getChildren")
assert ['/test', True] == parse_args("getChildren", ["/test", "-i"])[1]
assert ['/test', True] == parse_args("getChildren", ["-i", "/test"])[1]
assert ['/test', False] == parse_args("getChildren", ["/test"])[1]