Skip to content

Commit 38a53ec

Browse files
feat: add utils for encoding CLI args as base64 (#64)
1 parent 64ed762 commit 38a53ec

1 file changed

Lines changed: 69 additions & 16 deletions

File tree

  • snakemake_interface_executor_plugins

snakemake_interface_executor_plugins/utils.py

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
__license__ = "MIT"
55

66
import asyncio
7+
import base64
78
from collections import UserDict
89
from pathlib import Path
910
import re
@@ -22,51 +23,66 @@
2223
TargetSpec = namedtuple("TargetSpec", ["rulename", "wildcards_dict"])
2324

2425

25-
def format_cli_arg(flag, value, quote=True, skip=False):
26+
def format_cli_arg(flag, value, quote=True, skip=False, base64_encode: bool = False):
2627
if not skip and value:
2728
if isinstance(value, bool):
2829
value = ""
2930
else:
30-
value = format_cli_pos_arg(value, quote=quote)
31+
value = format_cli_pos_arg(value, quote=quote, base64_encode=base64_encode)
3132
return f"{flag} {value}"
3233
return ""
3334

3435

35-
def format_cli_pos_arg(value, quote=True):
36+
def format_cli_pos_arg(value, quote=True, base64_encode: bool = False):
3637
if isinstance(value, (dict, UserDict)):
37-
return join_cli_args(
38-
repr(f"{key}={format_cli_value(val)}") for key, val in value.items()
39-
)
38+
39+
def fmt_item(key, value):
40+
expr = f"{key}={format_cli_value(value)}"
41+
return encode_as_base64(expr) if base64_encode else repr(expr)
42+
43+
return join_cli_args(fmt_item(key, val) for key, val in value.items())
4044
elif not_iterable(value):
41-
return format_cli_value(value)
45+
return format_cli_value(value, base64_encode=base64_encode)
4246
else:
43-
return join_cli_args(format_cli_value(v, quote=True) for v in value)
47+
return join_cli_args(
48+
format_cli_value(v, quote=True, base64_encode=base64_encode) for v in value
49+
)
4450

4551

46-
def format_cli_value(value: Any, quote: bool = False) -> str:
52+
def format_cli_value(
53+
value: Any, quote: bool = False, base64_encode: bool = False
54+
) -> str:
55+
"""Format a given value for passing it to CLI.
56+
57+
If base64_encode is True, str values are encoded and flagged as being base64 encoded.
58+
"""
59+
60+
def maybe_encode(value):
61+
return encode_as_base64(value) if base64_encode else value
62+
4763
if isinstance(value, SettingsEnumBase):
4864
return value.item_to_choice()
4965
elif isinstance(value, Path):
5066
return shlex.quote(str(value))
5167
elif isinstance(value, str):
52-
if is_quoted(value):
68+
if is_quoted(value) and not base64_encode:
5369
# the value is already quoted, do not quote again
54-
return value
55-
elif quote:
56-
return repr(value)
70+
return maybe_encode(value)
71+
elif quote and not base64_encode:
72+
return maybe_encode(repr(value))
5773
else:
58-
return value
74+
return maybe_encode(value)
5975
else:
6076
return repr(value)
6177

6278

6379
def join_cli_args(args):
6480
try:
6581
return " ".join(arg for arg in args if arg)
66-
except TypeError:
82+
except TypeError as e:
6783
raise TypeError(
6884
f"bug: join_cli_args expects iterable of strings. Given: {args}"
69-
)
85+
) from e
7086

7187

7288
def url_can_parse(url: str) -> bool:
@@ -113,3 +129,40 @@ async def async_lock(_lock: threading.Lock):
113129

114130
def is_quoted(value: str) -> bool:
115131
return _is_quoted_re.match(value) is not None
132+
133+
134+
base64_prefix = "base64//"
135+
136+
137+
def maybe_base64(parser_func):
138+
"""Parse optionally base64 encoded CLI args, applying parser_func if not None."""
139+
140+
def inner(args):
141+
def is_base64(arg):
142+
return arg.startswith(base64_prefix)
143+
144+
def decode(arg):
145+
if is_base64(arg):
146+
return base64.b64decode(arg[len(base64_prefix) :]).decode()
147+
else:
148+
return arg
149+
150+
def apply_parser(args):
151+
if parser_func is not None:
152+
return parser_func(args)
153+
else:
154+
return args
155+
156+
if isinstance(args, str):
157+
return apply_parser(decode(args))
158+
elif isinstance(args, list):
159+
decoded = [decode(arg) for arg in args]
160+
return apply_parser(decoded)
161+
else:
162+
raise NotImplementedError()
163+
164+
return inner
165+
166+
167+
def encode_as_base64(arg: str):
168+
return f"{base64_prefix}{base64.b64encode(arg.encode()).decode()}"

0 commit comments

Comments
 (0)