Skip to content

Commit 41afc05

Browse files
authored
feat(cli)!: take --wildcards as repeated key=value tokens (#13)
Switch `--wildcards` from a single comma-separated string to `nargs="*"` repeated `KEY=VALUE` tokens, matching the idiom used by snakemake's own `--config`. Parsing reuses `snakemake.common.parse_key_value_arg` and validates each key as a Python identifier. Side benefits: - Wildcard values may now contain commas, since values are no longer split on `,`. - Quote-stripping on values is delegated to the upstream helper, so shell-quoted inputs round-trip cleanly. - Help text now renders as `--wildcards [KEY=VALUE ...]`. The R preamble in the README is updated to emit one element per pair via `c(...)`, since the previous `paste(..., sep=',')` form no longer composes a valid argument. BREAKING CHANGE: `--wildcards` no longer accepts a single comma-separated string. Callers must pass each pair as its own argv token, e.g. `--wildcards k1=v1 k2=v2` instead of `--wildcards "k1=v1,k2=v2"`. A legacy comma-separated string is now silently interpreted as a single value containing commas, since the new contract allows commas in values.
1 parent 8f2ed20 commit 41afc05

2 files changed

Lines changed: 26 additions & 28 deletions

File tree

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,18 @@ if (! exists("snakemake")) {
6262
snakefile = "Snakefile"
6363
rule = "create_prediction_target"
6464
python = "/opt/anaconda/envs/snakemake/bin/python"
65-
wildcards = paste(
66-
# 'comparison=all',
67-
sep=','
65+
wildcards = c(
66+
# "comparison=all",
6867
)
6968

7069
cmd=c(
7170
"-m snakemk_util.main",
7271
"--rule", rule,
7372
"--snakefile", normalizePath(snakefile),
7473
"--root_dir", dirname(normalizePath(snakefile)),
75-
"--wildcards", paste0('"', wildcards, '"'),
7674
"--gen-preamble", "RScript",
77-
"--create_dirs"
75+
"--create_dirs",
76+
if (length(wildcards) > 0) c("--wildcards", wildcards) else NULL
7877
)
7978
eval(parse(text=system2(python, cmd, stdout=TRUE, stderr="")))
8079
}
@@ -85,7 +84,7 @@ if (! exists("snakemake")) {
8584

8685
```bash
8786
# snakemk_util --help
88-
usage: snakemk_util [-h] --rule RULE_NAME [--gen-preamble FLAVOR] [--snakefile SNAKEFILE] [--root_dir ROOT_DIR] [--wildcards WILDCARDS] [--create_dirs]
87+
usage: snakemk_util [-h] --rule RULE_NAME [--gen-preamble FLAVOR] [--snakefile SNAKEFILE] [--root_dir ROOT_DIR] [--wildcards [KEY=VALUE ...]] [--create_dirs]
8988

9089
Utility to sow Snakemake rule contents and creating script preambles without actually running Snakemake.
9190

@@ -97,8 +96,8 @@ optional arguments:
9796
--snakefile SNAKEFILE
9897
path to the Snakefile
9998
--root_dir ROOT_DIR Root directory from where you would run the `snakemake` command. By default, this is the current working directory.
100-
--wildcards WILDCARDS
101-
Comma-separated key-value list of wildcards which should be used to format the rule output. Example: 'wildcard0=x,wildcard1=y
99+
--wildcards [KEY=VALUE ...]
100+
Wildcards used to format the rule output, given as space-separated 'key=value' tokens. Example: --wildcards wildcard0=x wildcard1=y
102101
--create_dirs Create the output directories for the rule
103102
```
104103

snakemk_util/main.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ def main():
5454
)
5555
parser.add_argument(
5656
"--wildcards",
57-
action="store",
57+
nargs="*",
5858
dest="wildcards",
59-
default="",
59+
default=[],
60+
metavar="KEY=VALUE",
6061
help=(
61-
"Comma-separated key-value list of wildcards which should be used "
62-
"to format the rule output. Example: 'wildcard0=x,wildcard1=y'"
62+
"Wildcards used to format the rule output, given as "
63+
"space-separated 'key=value' tokens. "
64+
"Example: --wildcards wildcard0=x wildcard1=y"
6365
),
6466
)
6567
parser.add_argument(
@@ -76,22 +78,19 @@ def main():
7678
from snakemk_util.rule_args import load_rule_args, pretty_print_snakemake
7779

7880
wildcards: dict[str, str] = {}
79-
if args.wildcards:
80-
for entry in args.wildcards.split(","):
81-
if not entry:
82-
continue
83-
try:
84-
key, value = parse_key_value_arg(
85-
entry,
86-
errmsg="Wildcards must be specified as 'key=value' pairs",
87-
)
88-
except ValueError as exc:
89-
parser.error(str(exc))
90-
if not _VALID_WILDCARD_NAME.match(key):
91-
parser.error(f"Invalid wildcard name {key!r}: must be a valid identifier")
92-
if key in wildcards:
93-
parser.error(f"Duplicate wildcard key {key!r}")
94-
wildcards[key] = value
81+
for entry in args.wildcards:
82+
try:
83+
key, value = parse_key_value_arg(
84+
entry,
85+
errmsg="Wildcards must be specified as 'key=value' pairs",
86+
)
87+
except ValueError as exc:
88+
parser.error(str(exc))
89+
if not _VALID_WILDCARD_NAME.match(key):
90+
parser.error(f"Invalid wildcard name {key!r}: must be a valid identifier")
91+
if key in wildcards:
92+
parser.error(f"Duplicate wildcard key {key!r}")
93+
wildcards[key] = value
9594

9695
with redirect_stdout(sys.stderr):
9796
snakemake_obj = load_rule_args(

0 commit comments

Comments
 (0)