7
7
# pyre-strict
8
8
9
9
import inspect
10
+ import re
10
11
from typing import Any , Callable , Dict , List , Optional , Tuple , Type , TypeVar , Union
11
12
12
13
import typing_inspect
@@ -31,6 +32,9 @@ def to_dict(arg: str) -> Dict[str, str]:
31
32
When values are lists, the last delimiter is used as kv-pair delimiter
32
33
(e.g. ``FOO=v1,v2,BAR=v3``). Empty values of ``arg`` returns an empty map.
33
34
35
+ Values can be quoted with single or double quotes to include special characters
36
+ (``"="``, ``","``, ``";"``) without them being interpreted as separators.
37
+
34
38
Note that values that encode list literals are returned as list literals
35
39
NOT actual lists. The caller must further process each value in the returned
36
40
map, to cast/decode the value literals as specific types. In this case,
@@ -57,6 +61,7 @@ def to_dict(arg: str) -> Dict[str, str]:
57
61
to_dict("FOO=v1;v2,BAR=v3") == {"FOO": "v1;v2", "BAR": "v3"}
58
62
to_dict("FOO=v1;v2;BAR=v3") == {"FOO": "v1;v2", "BAR": "v3"}
59
63
64
+ to_dict('FOO="value with = and , and ;"') == {"FOO": "value with = and , and ;"}
60
65
"""
61
66
62
67
def parse_val_key (vk : str ) -> Tuple [str , str ]:
@@ -74,19 +79,34 @@ def parse_val_key(vk: str) -> Tuple[str, str]:
74
79
return vk [0 :idx ].strip (), vk [idx + 1 :].strip ()
75
80
76
81
def to_val (val : str ) -> str :
82
+ if (val .startswith ("'" ) and val .endswith ("'" )) or (
83
+ val .startswith ('"' ) and val .endswith ('"' )
84
+ ):
85
+ return val [1 :- 1 ]
77
86
return val if val != '""' and val != "''" else ""
78
87
79
88
arg_map : Dict [str , str ] = {}
80
89
81
90
if not arg :
82
91
return arg_map
83
92
93
+ # find quoted values
94
+ quoted_pattern = r'([\'"])((?:\\.|(?!\1).)*?)\1'
95
+ quoted_values : List [str ] = []
96
+
97
+ def replace_quoted (match ):
98
+ quoted_values .append (match .group (0 ))
99
+ return f"__QUOTED_{ len (quoted_values ) - 1 } __"
100
+
101
+ # replace quoted values with placeholders
102
+ processed_arg = re .sub (quoted_pattern , replace_quoted , arg )
103
+
84
104
# split cfgs
85
105
cfg_kv_delim = "="
86
106
87
107
# ["FOO", "v1;v2,BAR", v3, "BAZ", "v4,v5"]
88
108
split_arg = [
89
- s .strip () for s in arg .split (cfg_kv_delim ) if s .strip ()
109
+ s .strip () for s in processed_arg .split (cfg_kv_delim ) if s .strip ()
90
110
] # remove empty
91
111
split_arg_len = len (split_arg )
92
112
@@ -98,10 +118,16 @@ def to_val(val: str) -> str:
98
118
# middle elements are value_{n}<delim>key_{n+1}
99
119
for vk in split_arg [1 : split_arg_len - 1 ]: # python deals with
100
120
val , key_next = parse_val_key (vk )
121
+ for i , quoted in enumerate (quoted_values ):
122
+ val = val .replace (f"__QUOTED_{ i } __" , quoted )
101
123
arg_map [key ] = to_val (val )
102
124
key = key_next
125
+
103
126
val = split_arg [- 1 ] # last element is always a value
127
+ for i , quoted in enumerate (quoted_values ):
128
+ val = val .replace (f"__QUOTED_{ i } __" , quoted )
104
129
arg_map [key ] = to_val (val )
130
+
105
131
return arg_map
106
132
107
133
0 commit comments