-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added cli_serializer & from_cli function (#311)
* Added cli_serializer & from_cli function * Removed cli serializer exit_on_error option * Invalid arg test, static CLISerializer func & vars
- Loading branch information
1 parent
83e4853
commit 1feb11c
Showing
4 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from argparse import ArgumentError, ArgumentParser | ||
from collections import Counter | ||
from re import finditer | ||
|
||
from benedict.serializers.abstract import AbstractSerializer | ||
from benedict.utils import type_util | ||
|
||
|
||
class CLISerializer(AbstractSerializer): | ||
""" | ||
This class describes a CLI serializer. | ||
""" | ||
|
||
regex_keys_with_values = r"-+\w+(?=\s[^\s-])" | ||
""" | ||
Regex string. | ||
Used to search for keys (e.g. -STRING or --STRING) | ||
that *aren't* followed by another key | ||
Example input: script.py --username example --verbose -d -e email@example.com | ||
- Matches: --username, -e | ||
- Doesn't match: script.py, example, --verbose, -d, email@example.com | ||
""" | ||
|
||
regex_all_keys = r"-+\w+" | ||
""" | ||
Regex string. | ||
Used to search for keys (e.g. -STRING or --STRING) | ||
no matter if they are followed by another key | ||
Example input: script.py --username example --verbose -d -e email@example.com | ||
- Matches: --username, --verbose, -d, -e | ||
- Doesn't match: script.py, example, email@example.com | ||
""" | ||
|
||
def __init__(self): | ||
super().__init__( | ||
extensions=["cli"], | ||
) | ||
|
||
@staticmethod | ||
def parse_keys(regex, string): | ||
# For some reason findall didn't work | ||
results = [match.group(0) for match in finditer(regex, string)] | ||
return results | ||
|
||
"""Helper method, returns a list of --keys based on the regex used""" | ||
|
||
@staticmethod | ||
def _get_parser(options): | ||
parser = ArgumentParser(**options) | ||
return parser | ||
|
||
def decode(self, s=None, **kwargs): | ||
parser = self._get_parser(options=kwargs) | ||
|
||
keys_with_values = set(self.parse_keys(self.regex_keys_with_values, s)) | ||
all_keys = Counter(self.parse_keys(self.regex_all_keys, s)) | ||
for key in all_keys: | ||
count = all_keys[key] | ||
|
||
try: | ||
# If the key has a value... | ||
if key in keys_with_values: | ||
# and is defined once, collect the values | ||
if count == 1: | ||
parser.add_argument( | ||
key, | ||
nargs="*", | ||
# This puts multiple values in a list | ||
# even though this won't always be wanted | ||
# This is adressed after the dict is generated | ||
required=False, | ||
) | ||
# and is defined multiple times, collect the values | ||
else: | ||
parser.add_argument(key, action="append", required=False) | ||
|
||
# If the key doesn't have a value... | ||
else: | ||
# and is defined once, store as bool | ||
if count <= 1: | ||
parser.add_argument(key, action="store_true", required=False) | ||
# and is defined multiple times, count how many times | ||
else: | ||
parser.add_argument(key, action="count", required=False) | ||
|
||
except ArgumentError as error: | ||
raise ValueError from error | ||
|
||
try: | ||
args = parser.parse_args(s.split()) | ||
except BaseException as error: | ||
raise ValueError from error | ||
|
||
dict = vars(args) | ||
for key in dict: | ||
value = dict[key] | ||
# If only one value was written, | ||
# return that value instead of a list | ||
if type_util.is_list(value) and len(value) == 1: | ||
dict[key] = value[0] | ||
|
||
return dict |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from benedict.dicts.io import IODict | ||
|
||
from .test_io_dict import io_dict_test_case | ||
|
||
|
||
class io_dict_cli_test_case(io_dict_test_case): | ||
""" | ||
This class describes an IODict / cli test case. | ||
""" | ||
|
||
def test_from_cli_with_valid_data(self): | ||
s = """--url "https://github.com" --usernames another handle --languages Python --languages JavaScript -v --count --count --count""" | ||
# static method | ||
r = { | ||
"url": '"https://github.com"', | ||
"usernames": ["another", "handle"], | ||
"languages": ["Python", "JavaScript"], | ||
"v": True, | ||
"count": 3, | ||
} | ||
|
||
d = IODict.from_cli(s) | ||
self.assertTrue(isinstance(d, dict)) | ||
self.assertEqual(d, r) | ||
# constructor | ||
d = IODict(s, format="cli") | ||
self.assertTrue(isinstance(d, dict)) | ||
self.assertEqual(d, r) | ||
|
||
def test_from_cli_with_invalid_arguments(self): | ||
s = """--help -h""" | ||
|
||
# static method | ||
with self.assertRaises(ValueError): | ||
IODict.from_cli(s) | ||
# constructor | ||
with self.assertRaises(ValueError): | ||
IODict(s, format="cli") | ||
|
||
def test_from_cli_with_invalid_data(self): | ||
s = "Lorem ipsum est in ea occaecat nisi officia." | ||
# static method | ||
with self.assertRaises(ValueError): | ||
IODict.from_cli(s) | ||
# constructor | ||
with self.assertRaises(ValueError): | ||
IODict(s, format="cli") |