Skip to content

Commit

Permalink
Add multiple option for args
Browse files Browse the repository at this point in the history
  • Loading branch information
nat-n committed May 30, 2022
1 parent 9587a16 commit 4b4aa9f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 13 deletions.
52 changes: 44 additions & 8 deletions poethepoet/task/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"positional": (bool, str),
"required": bool,
"type": str,
"multiple": (bool, int),
}
arg_types: Dict[str, Type] = {
"string": str,
Expand Down Expand Up @@ -96,21 +97,22 @@ def get_help_content(
@classmethod
def validate_def(cls, task_name: str, args_def: ArgsDef) -> Optional[str]:
arg_names: Set[str] = set()
arg_params = []

if isinstance(args_def, list):
for item in args_def:
# can be a list of strings (just arg name) or ArgConfig dictionaries
if isinstance(item, str):
arg_name = item
elif isinstance(item, dict):
arg_name = item.get("name", "")
error = cls._validate_params(item, arg_name, task_name)
if error:
return error
arg_params.append((item, arg_name, task_name))
else:
return f"Arg {item!r} of task {task_name!r} has invlaid type"
error = cls._validate_name(arg_name, task_name, arg_names)
if error:
return error

elif isinstance(args_def, dict):
for arg_name, params in args_def.items():
error = cls._validate_name(arg_name, task_name, arg_names)
Expand All @@ -121,12 +123,26 @@ def validate_def(cls, task_name: str, args_def: ArgsDef) -> Optional[str]:
f"Unexpected 'name' option for arg {arg_name!r} of task "
f"{task_name!r}"
)
error = cls._validate_params(params, arg_name, task_name)
if error:
return error
error = cls._validate_type(params, arg_name, task_name)
if error:
return error
arg_params.append((params, arg_name, task_name))

positional_multiple = None
for params, arg_name, task_name in arg_params:
error = cls._validate_params(params, arg_name, task_name)
if error:
return error

if params.get("positional", False):
if positional_multiple:
return (
f"Only the last positional arg of task {task_name!r} may accept"
f" multiple values ({positional_multiple!r})."
)
if params.get("multiple", False):
positional_multiple = arg_name

return None

@classmethod
Expand Down Expand Up @@ -182,6 +198,17 @@ def _validate_params(
"https://docs.python.org/3/reference/lexical_analysis.html#identifiers"
)

multiple = params.get("multiple", False)
if (
not isinstance(multiple, bool)
and isinstance(multiple, int)
and multiple < 2
):
return (
f"The multiple option for arg {arg_name!r} of {task_name!r}"
" must be given a boolean or integer >= 2"
)

return None

@classmethod
Expand Down Expand Up @@ -217,10 +244,19 @@ def _get_argument_params(self, arg: ArgParams):
}

required = arg.get("required", False)
multiple = arg.get("multiple", False)
arg_type = str(arg.get("type"))

if multiple is True:
if required:
result["nargs"] = "+"
else:
result["nargs"] = "*"
elif isinstance(multiple, int):
result["nargs"] = multiple

if arg.get("positional", False):
if not required:
if not multiple and not required:
result["nargs"] = "?"
else:
result["dest"] = arg["name"]
Expand All @@ -235,7 +271,7 @@ def _get_argument_params(self, arg: ArgParams):

def parse(self, extra_args: Sequence[str]):
parsed_args = vars(self.build_parser().parse_args(extra_args))
# Ensure positional args are still exposed by name even if the were parsed with
# Ensure positional args are still exposed by name even if they were parsed with
# alternate identifiers
for arg in self._args:
if isinstance(arg.get("positional"), str):
Expand Down
15 changes: 10 additions & 5 deletions poethepoet/task/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,18 @@ def has_named_args(self):
return bool(self.named_args)

def get_named_arg_values(self) -> Mapping[str, str]:
result = {}

if not self.named_args:
return {}
return {
key: str(value)
for key, value in self.named_args.items()
if value is not None
}

for key, value in self.named_args.items():
if isinstance(value, list):
result[key] = " ".join(str(item) for item in value)
elif value is not None:
result[key] = str(value)

return result

def run(
self,
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/scripts_project/pkg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ def echo_args():
print("hello", *sys.argv[1:])


def echo_script(*args, **kwargs):
print("args", args)
print("kwargs", kwargs)


def describe_args(*args, **kwargs):
for value in args:
print(f"{type(value).__name__}: {value}")
Expand Down
23 changes: 23 additions & 0 deletions tests/fixtures/scripts_project/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,29 @@ greet = "pkg:greet"
type = "boolean"


[tool.poe.tasks.multiple-value-args]
script = "pkg:echo_script(first, second, widgets=widgets, engines=engines)"

[[tool.poe.tasks.multiple-value-args.args]]
name = "first"
positional = true

[[tool.poe.tasks.multiple-value-args.args]]
name = "second"
positional = true
multiple = true
type = "integer"

[[tool.poe.tasks.multiple-value-args.args]]
name = "widgets"
multiple = 2

[[tool.poe.tasks.multiple-value-args.args]]
name = "engines"
multiple = true
required = true


[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

0 comments on commit 4b4aa9f

Please sign in to comment.