From 400a0a3172e897a51a9660f5cece01337ba15f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:00:08 +0300 Subject: [PATCH 01/16] initialize new test file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_positional_arguments.py | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/test_positional_arguments.py diff --git a/tests/test_positional_arguments.py b/tests/test_positional_arguments.py new file mode 100644 index 0000000..8d99fd7 --- /dev/null +++ b/tests/test_positional_arguments.py @@ -0,0 +1,49 @@ +# +# This file is part of the crispy-parser library. +# Copyright (C) 2023 Ferit Yiğit BALABAN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA. + + +from unittest import TestCase + +from crispy.crispy import Crispy + + +class TestPositionalArguments(TestCase): + def setUp(self) -> None: + c = Crispy() + c.add_positional("name", str, 0) + c.add_positional("age", int, 1) + return super().setUp() + + def test_correct_order(self): + pass + + def test_type_mismatch(self): + pass + + def test_with_keys(self): + pass + + def test_with_subcommand(self): + pass + + def test_fail_with_keys_and_subcommand(self): + pass + + def test_pass_with_keys_and_subcommand(self): + pass \ No newline at end of file From f976427fbb26b04035b8a1c64b733ccd0c9dff9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:07:10 +0300 Subject: [PATCH 02/16] use direct Type information instead of str representation in exception context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/parsing_exception.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crispy/parsing_exception.py b/crispy/parsing_exception.py index 84dc506..3228c4d 100644 --- a/crispy/parsing_exception.py +++ b/crispy/parsing_exception.py @@ -18,8 +18,11 @@ # USA. +from typing import Type + + class ParsingException(Exception): - def __init__(self, reason: str, expected: str, at_position: int, found: str): + def __init__(self, reason: str, expected: Type, at_position: int, found: Type): super().__init__(reason) self.expected = expected self.at_position = at_position From 7323da68ce226d8dfafad92cdc8874d4c9201370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:11:10 +0300 Subject: [PATCH 03/16] + test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_positional_arguments.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/test_positional_arguments.py b/tests/test_positional_arguments.py index 8d99fd7..afd00c8 100644 --- a/tests/test_positional_arguments.py +++ b/tests/test_positional_arguments.py @@ -21,17 +21,28 @@ from unittest import TestCase from crispy.crispy import Crispy +from crispy.parsing_exception import ParsingException class TestPositionalArguments(TestCase): def setUp(self) -> None: - c = Crispy() - c.add_positional("name", str, 0) - c.add_positional("age", int, 1) + self.c = Crispy() + self.c.add_positional("name", str, 0) + self.c.add_positional("age", int, 1) + self.c.add_variable("is_student", bool) + self.c.add_variable("height", float) + self.c.add_subcommand("create", "creates the user") return super().setUp() def test_correct_order(self): - pass + expected = (None, { + "name": "Ferit", + "age": 21, + "is_student": False + }) + actual = self.c.parse_string("Ferit 21") + self.assertEqual(expected, actual) + def test_type_mismatch(self): pass From 19020166babf0881845a38964cd3a98787d88aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:11:23 +0300 Subject: [PATCH 04/16] test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_positional_arguments.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_positional_arguments.py b/tests/test_positional_arguments.py index afd00c8..dc24867 100644 --- a/tests/test_positional_arguments.py +++ b/tests/test_positional_arguments.py @@ -45,7 +45,11 @@ def test_correct_order(self): def test_type_mismatch(self): - pass + with self.assertRaises(ParsingException) as context: + self.c.parse_string("21 Ferit") + self.assertEqual(context.exception.expected, str) + self.assertEqual(context.exception.at_position, 0) + self.assertEqual(context.exception.found, int) def test_with_keys(self): pass From 741bf2da75b6f147f4c165f6f23229e63e60bdc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:11:39 +0300 Subject: [PATCH 05/16] test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_positional_arguments.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/test_positional_arguments.py b/tests/test_positional_arguments.py index dc24867..24833a2 100644 --- a/tests/test_positional_arguments.py +++ b/tests/test_positional_arguments.py @@ -52,7 +52,20 @@ def test_type_mismatch(self): self.assertEqual(context.exception.found, int) def test_with_keys(self): - pass + expected = (None, { + "name": "Ferit", + "age": 21, + "is_student": True, + "height": 1.86 + }) + actual1 = self.c.parse_string("Ferit 21 --is_student --height=1.86") + actual2 = self.c.parse_string("--is_student --height=1.86 Ferit 21") + actual3 = self.c.parse_string("--height 1.86 Ferit -i 21") + actual4 = self.c.parse_string("-h 1.86 Ferit 21 -i") + self.assertEqual(expected, actual1) + self.assertEqual(expected, actual2) + self.assertEqual(expected, actual3) + self.assertEqual(expected, actual4) def test_with_subcommand(self): pass From 90ff47cd78fe28b79602634e7f41eecf7cb6d213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:16:48 +0300 Subject: [PATCH 06/16] + method add_positional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crispy/crispy.py b/crispy/crispy.py index 260f18f..a711381 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -36,6 +36,7 @@ def __init__(self, accept_shortform=True, accept_longform=True): """ self.accepted_keys: Dict[str, str] = {} self.variables: Dict[str, Type[Union[str, bool, int, float]]] = {} + self.lookup: Dict[int, str] = {} self.subcommands: Dict[str, str] = {} if not (accept_shortform or accept_longform): @@ -43,6 +44,22 @@ def __init__(self, accept_shortform=True, accept_longform=True): self.accept_shortform = accept_shortform self.accept_longform = accept_longform + def add_positional(self, name: str, var_type: Type[Union[str, bool, int, float]], position: int): + """ + Adds a positional argument to the parser. + + :param name: Name of the positional argument + :param var_type: Type of the positional argument + :param position: Position of the positional argument + :return: None + """ + if name in self.variables: + raise DuplicateNameException(f"crispy: variable with name '{name}' is present! Choose something else.") + if position < 0 or position in self.lookup: + raise ValueError(f"crispy: invalid position '{position}'!") + self.variables[name] = var_type + self.lookup[position] = name + def add_subcommand(self, name: str, description: str): """Adds a subcommand to the parser. From a8b2f0430fe67a2b0ccdd43d074db8137f1238d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:20:39 +0300 Subject: [PATCH 07/16] subcommands can only occur at pos 0 of raw input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crispy/crispy.py b/crispy/crispy.py index a711381..d924a44 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -133,10 +133,11 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo continue if not key.startswith("-"): - if subcommand != "": - raise TooManySubcommandsException(f"crispy: too many subcommands! '{key}' is unexpected!") - subcommand = key - i += 1 + if i == 0: + if subcommand != "": + raise TooManySubcommandsException(f"crispy: too many subcommands! '{key}' is unexpected!") + subcommand = key + i += 1 continue elif "=" not in key: if (i + 1 < len_args) and (args[i + 1] not in self.accepted_keys) and ("=" not in args[i + 1]): From 6dba16f53f0f82de6ca88dfdc0c1ff39f85411be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:37:27 +0300 Subject: [PATCH 08/16] it's impossible to write flawless unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_positional_arguments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_positional_arguments.py b/tests/test_positional_arguments.py index 24833a2..d15c5ae 100644 --- a/tests/test_positional_arguments.py +++ b/tests/test_positional_arguments.py @@ -35,7 +35,7 @@ def setUp(self) -> None: return super().setUp() def test_correct_order(self): - expected = (None, { + expected = ("", { "name": "Ferit", "age": 21, "is_student": False @@ -52,7 +52,7 @@ def test_type_mismatch(self): self.assertEqual(context.exception.found, int) def test_with_keys(self): - expected = (None, { + expected = ("", { "name": "Ferit", "age": 21, "is_student": True, From 8803c11479b5f2a6a496ebc90e0a0ff34b3813cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:37:43 +0300 Subject: [PATCH 09/16] successfully parse positional args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crispy/crispy.py b/crispy/crispy.py index d924a44..5bcb5e8 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -124,7 +124,7 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo subcommand: str = "" keys: Dict[str, Union[str, bool, int, float]] = {} - i, len_args = 0, len(args) + i, j, len_args = 0, 0, len(args) while i < len_args: key = args[i] @@ -133,12 +133,16 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo continue if not key.startswith("-"): - if i == 0: + if i == 0 and key in self.subcommands: if subcommand != "": raise TooManySubcommandsException(f"crispy: too many subcommands! '{key}' is unexpected!") subcommand = key - i += 1 + else: + keys[self.lookup[j]] = self.try_parse(key, self.variables[self.lookup[j]]) + j += 1 + i += 1 continue + elif "=" not in key: if (i + 1 < len_args) and (args[i + 1] not in self.accepted_keys) and ("=" not in args[i + 1]): value = args[i + 1] From d9d0f6f9e625ed2243284cb09533d49e46353fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:50:15 +0300 Subject: [PATCH 10/16] add test cases for method deduce_type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_deduce_type.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_deduce_type.py diff --git a/tests/test_deduce_type.py b/tests/test_deduce_type.py new file mode 100644 index 0000000..d630bcd --- /dev/null +++ b/tests/test_deduce_type.py @@ -0,0 +1,46 @@ +# +# This file is part of the crispy-parser library. +# Copyright (C) 2023 Ferit Yiğit BALABAN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA. + +from unittest import TestCase +from crispy.crispy import Crispy + +class TestDeduceType(TestCase): + def setUp(self) -> None: + self.c = Crispy() + return super().setUp() + + def test_deduce_type_returns_bool_for_true(self): + result = self.c.deduce_type("true") + self.assertEqual(result, bool) + + def test_deduce_type_returns_bool_for_false(self): + result = self.c.deduce_type("false") + self.assertEqual(result, bool) + + def test_deduce_type_returns_int_for_integer(self): + result = self.c.deduce_type("123") + self.assertEqual(result, int) + + def test_deduce_type_returns_float_for_float(self): + result = self.c.deduce_type("3.14") + self.assertEqual(result, float) + + def test_deduce_type_returns_str_for_other_values(self): + result = self.c.deduce_type("hello") + self.assertEqual(result, str) From f3bc1263d54a06feaf1928df81592e22ea10cc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:50:51 +0300 Subject: [PATCH 11/16] remove subcommand position restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crispy/crispy.py b/crispy/crispy.py index 5bcb5e8..15f7c4a 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -133,7 +133,7 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo continue if not key.startswith("-"): - if i == 0 and key in self.subcommands: + if key in self.subcommands: if subcommand != "": raise TooManySubcommandsException(f"crispy: too many subcommands! '{key}' is unexpected!") subcommand = key From d6df3981604832b4777fc814a5cc63a5047c0824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:51:07 +0300 Subject: [PATCH 12/16] add method deduce_type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crispy/crispy.py b/crispy/crispy.py index 15f7c4a..564c3b5 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -199,3 +199,18 @@ def try_parse(value: str, expected_type: type) -> Union[str, bool, int, float]: if expected_type == float: return float(value) return value + + @staticmethod + def deduce_type(value: str): + """ + Deduces the type of the value. + :param value: Value in string type + :return: Returns the deduced type of value in string representation + """ + if value.lower() == "true" or value.lower() == "false": + return bool + if value.isdigit(): + return int + if value.replace(".", "", 1).isdigit(): + return float + return str From 25566b859c824768cb75d4fa2292c562a96b5378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 19:51:23 +0300 Subject: [PATCH 13/16] yet another mistake in this test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_subcommands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_subcommands.py b/tests/test_subcommands.py index a0b86da..284e3ef 100644 --- a/tests/test_subcommands.py +++ b/tests/test_subcommands.py @@ -27,6 +27,7 @@ class Test_Subcommands(TestCase): def setUp(self): self.c = Crispy() self.c.add_subcommand('add', 'adds two numbers given by keys -a and -b') + self.c.add_subcommand('test', 'to test toomanysubcommands exception') self.c.add_variable('a', int) self.c.add_variable('b', int) From e3f75577af65c5a854c59bceee54185f7404cad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 20:10:13 +0300 Subject: [PATCH 14/16] another error in the test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- tests/test_subcommands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_subcommands.py b/tests/test_subcommands.py index 284e3ef..e0f2c9e 100644 --- a/tests/test_subcommands.py +++ b/tests/test_subcommands.py @@ -32,8 +32,8 @@ def setUp(self): self.c.add_variable('b', int) def test_add_subcommand(self): - self.c.add_subcommand('test', 'description of subcommand test') - self.assertEqual(self.c.subcommands['test'], 'description of subcommand test') + self.c.add_subcommand('test2', 'description of subcommand test') + self.assertEqual(self.c.subcommands['test2'], 'description of subcommand test') def test_add_duplicate_subcommand(self): with self.assertRaises(DuplicateNameException): From 1f47a3eb6c2f9b7cfffd7868907c6baa6c01a47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 20:10:57 +0300 Subject: [PATCH 15/16] bypass pipeline-end type check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crispy/crispy.py b/crispy/crispy.py index 564c3b5..aa07adf 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -23,6 +23,7 @@ from crispy.duplicate_name_exception import DuplicateNameException from crispy.missing_value_exception import MissingValueException from crispy.no_arguments_exception import NoArgumentsException +from crispy.parsing_exception import ParsingException from crispy.unexpected_argument_exception import UnexpectedArgumentException from crispy.too_many_subcommands_exception import TooManySubcommandsException @@ -138,17 +139,25 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo raise TooManySubcommandsException(f"crispy: too many subcommands! '{key}' is unexpected!") subcommand = key else: - keys[self.lookup[j]] = self.try_parse(key, self.variables[self.lookup[j]]) + name = self.lookup[j] + expected_type, found_type = self.variables[name], self.deduce_type(key) + if expected_type != found_type: + raise ParsingException(f"crispy: type mismatch! '{key}' is not of type '{expected_type}'", expected_type, j, found_type) + + keys[name] = self.try_parse(key, expected_type) j += 1 i += 1 continue elif "=" not in key: - if (i + 1 < len_args) and (args[i + 1] not in self.accepted_keys) and ("=" not in args[i + 1]): + if ((i + 1 < len_args) and + (args[i + 1] not in self.accepted_keys) and + ("=" not in args[i + 1]) and + (self.variables[self.accepted_keys[key]] != bool)): value = args[i + 1] i += 2 else: - expected_type = self.variables.get(self.accepted_keys.get(key)) + expected_type = self.variables[self.accepted_keys[key]] if expected_type == bool: value = "True" i += 1 @@ -160,7 +169,7 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo accepted_key = self.accepted_keys.get(key) if accepted_key: - keys[accepted_key] = self.try_parse(value, self.variables.get(accepted_key)) + keys[accepted_key] = self.try_parse(value, self.variables[accepted_key]) else: raise UnexpectedArgumentException(f"crispy: unexpected argument: '{key}'") From 51b133dd28a30e650ae7faef68a05d7b4c92c09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Thu, 21 Dec 2023 20:16:42 +0300 Subject: [PATCH 16/16] check accepting status beforehand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferit Yiğit BALABAN --- crispy/crispy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crispy/crispy.py b/crispy/crispy.py index aa07adf..23559e7 100644 --- a/crispy/crispy.py +++ b/crispy/crispy.py @@ -150,6 +150,8 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo continue elif "=" not in key: + if key not in self.accepted_keys: + raise UnexpectedArgumentException(f"crispy: unexpected argument: '{key}'") if ((i + 1 < len_args) and (args[i + 1] not in self.accepted_keys) and ("=" not in args[i + 1]) and @@ -170,8 +172,6 @@ def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Union[str, bo accepted_key = self.accepted_keys.get(key) if accepted_key: keys[accepted_key] = self.try_parse(value, self.variables[accepted_key]) - else: - raise UnexpectedArgumentException(f"crispy: unexpected argument: '{key}'") for key, value in self.variables.items(): if value == bool and key not in keys: