preparse 0.1.0.dev2__tar.gz → 0.1.0.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {preparse-0.1.0.dev2/src/preparse.egg-info → preparse-0.1.0.dev4}/PKG-INFO +1 -1
  2. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/pyproject.toml +1 -1
  3. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/_parsing/Parsing.py +58 -29
  4. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/core/PreParser.py +9 -0
  5. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/core/enums.py +7 -0
  6. preparse-0.1.0.dev4/src/preparse/tests/test_max.py +93 -0
  7. preparse-0.1.0.dev4/src/preparse/tests/test_max_1.py +51 -0
  8. preparse-0.1.0.dev4/src/preparse/tests/test_min.py +80 -0
  9. preparse-0.1.0.dev4/src/preparse/tests/test_min_1.py +103 -0
  10. preparse-0.1.0.dev4/src/preparse/tests/test_min_2.py +83 -0
  11. preparse-0.1.0.dev4/src/preparse/tests/test_min_3.py +73 -0
  12. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4/src/preparse.egg-info}/PKG-INFO +1 -1
  13. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse.egg-info/SOURCES.txt +6 -0
  14. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/LICENSE.txt +0 -0
  15. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/MANIFEST.in +0 -0
  16. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/README.rst +0 -0
  17. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/setup.cfg +0 -0
  18. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/__init__.py +0 -0
  19. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/_parsing/__init__.py +0 -0
  20. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/core/Click.py +0 -0
  21. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/core/__init__.py +0 -0
  22. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/core/warnings.py +0 -0
  23. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/__init__.py +0 -0
  24. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_expit.py +0 -0
  25. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_extra.py +0 -0
  26. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_extra_no_permutation.py +0 -0
  27. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_extra_posix.py +0 -0
  28. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_features.py +0 -0
  29. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_long_only.py +0 -0
  30. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_long_only_no_permutation.py +0 -0
  31. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_long_only_posix.py +0 -0
  32. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_parse.py +0 -0
  33. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_parse_no_permutation.py +0 -0
  34. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_parse_posix.py +0 -0
  35. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_9001.py +0 -0
  36. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_9002.py +0 -0
  37. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_9005.py +0 -0
  38. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_9006.py +0 -0
  39. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_9007.py +0 -0
  40. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_even_more.py +0 -0
  41. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse/tests/test_warn_more.py +0 -0
  42. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse.egg-info/dependency_links.txt +0 -0
  43. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse.egg-info/requires.txt +0 -0
  44. {preparse-0.1.0.dev2 → preparse-0.1.0.dev4}/src/preparse.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: preparse
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev4
4
4
  Summary: This project preparses args for further parsing later on.
5
5
  Author-email: Johannes <johannes-programming@mailfence.com>
6
6
  License: The MIT License (MIT)
@@ -30,7 +30,7 @@ keywords = []
30
30
  name = "preparse"
31
31
  readme = "README.rst"
32
32
  requires-python = ">=3.11"
33
- version = "0.1.0.dev2"
33
+ version = "0.1.0.dev4"
34
34
 
35
35
  [project.license]
36
36
  file = "LICENSE.txt"
@@ -29,8 +29,22 @@ class Parsing:
29
29
  self.ans.extend(self.spec)
30
30
  self.spec.clear()
31
31
 
32
+ def get_nargs_for_letter(self: Self, letter: str) -> Nargs:
33
+ try:
34
+ return self.optdict["-" + letter]
35
+ except KeyError:
36
+ self.warn(
37
+ PreparseInvalidOptionWarning,
38
+ prog=self.parser.prog,
39
+ option=letter,
40
+ )
41
+ return Nargs.NO_ARGUMENT
42
+
32
43
  @functools.cached_property
33
44
  def islongonly(self: Self) -> bool:
45
+ # if a long option with a single hyphon exists
46
+ # then all options are treated as long options
47
+ # example: -foo
34
48
  for k in self.optdict.keys():
35
49
  if len(k) < 3:
36
50
  continue
@@ -38,7 +52,6 @@ class Parsing:
38
52
  continue
39
53
  if not k.startswith("-"):
40
54
  continue
41
- # example: -foo
42
55
  return True
43
56
  return False
44
57
 
@@ -51,12 +64,9 @@ class Parsing:
51
64
  option=self.ans[-1],
52
65
  )
53
66
 
54
- @functools.cached_property
67
+ @property
55
68
  def optdict(self: Self) -> Dict[str, Nargs]:
56
- ans: dict = dict()
57
- for k, v in self.parser.optdict.items():
58
- ans[str(k)] = Nargs(v)
59
- return ans
69
+ return self.parser.optdict
60
70
 
61
71
  def possibilities(self: Self, opt: str) -> list[str]:
62
72
  if opt in self.optdict.keys():
@@ -71,26 +81,32 @@ class Parsing:
71
81
 
72
82
  def tick(self: Self, optn: str) -> str:
73
83
  if optn == "break":
84
+ # if no more options are allowed
74
85
  self.spec.extend(self.args)
75
86
  self.args.clear()
76
87
  return "break"
77
88
  arg: str = self.args.pop(0)
78
89
  if optn == "open":
90
+ # if a value for an option is already expected
79
91
  self.ans.append(arg)
80
92
  return "closed"
81
93
  if arg == "--":
94
+ # if arg is the special argument
82
95
  self.ans.append("--")
83
96
  return "break"
97
+ hasgroup: bool = optn == "group"
84
98
  if arg.startswith("-") and arg != "-":
85
- return self.tick_opt(arg)
99
+ # if arg is an option
100
+ return self.tick_opt(arg, hasgroup=hasgroup)
86
101
  else:
87
- return self.tick_pos(arg)
102
+ # if arg is positional
103
+ return self.tick_pos(arg, hasgroup=hasgroup)
88
104
 
89
- def tick_opt(self: Self, arg: str) -> str:
105
+ def tick_opt(self: Self, arg: str, /, *, hasgroup: bool) -> str:
90
106
  if arg.startswith("--") or self.islongonly:
91
107
  return self.tick_opt_long(arg)
92
108
  else:
93
- return self.tick_opt_short(arg)
109
+ return self.tick_opt_short(arg, hasgroup=hasgroup)
94
110
 
95
111
  def tick_opt_long(self: Self, arg: str) -> str:
96
112
  try:
@@ -135,26 +151,39 @@ class Parsing:
135
151
  else:
136
152
  return "closed"
137
153
 
138
- def tick_opt_short(self: Self, arg: str) -> str:
139
- self.ans.append(arg)
140
- nargs = 0
141
- for i in range(1 - len(arg), 0):
142
- if nargs != 0:
143
- return "closed"
144
- nargs = self.optdict.get("-" + arg[i])
145
- if nargs is None:
146
- warning = PreparseInvalidOptionWarning(
147
- prog=self.parser.prog,
148
- option=arg[i],
149
- )
150
- self.parser.warn(warning)
151
- nargs = 0
152
- if nargs == 1:
153
- return "open"
154
- else:
154
+ def tick_opt_short(self: Self, arg: str, /, *, hasgroup: bool) -> str:
155
+ i: int
156
+ a: str
157
+ nargs: Nargs
158
+ if self.parser.group != Group.MINIMIZE:
159
+ self.ans.append("-")
160
+ if (self.parser.group == Group.MAXIMIZE) and hasgroup:
161
+ self.ans.pop()
162
+ for i, a in enumerate(arg):
163
+ if i == 0:
164
+ continue
165
+ if self.parser.group != Group.MINIMIZE:
166
+ self.ans[-1] += a
167
+ elif a == "-":
168
+ self.ans[-1] += a
169
+ else:
170
+ self.ans.append("-" + a)
171
+ nargs = self.get_nargs_for_letter(a)
172
+ if nargs == Nargs.NO_ARGUMENT:
173
+ continue
174
+ value: str = arg[i + 1 :]
175
+ return self.tick_opt_short_nongroup(nargs=nargs, value=value)
176
+ return "group"
177
+
178
+ def tick_opt_short_nongroup(self: Self, *, nargs: Nargs, value: str) -> str:
179
+ if value:
180
+ self.ans[-1] += value
155
181
  return "closed"
182
+ if nargs == Nargs.OPTIONAL_ARGUMENT:
183
+ return "closed"
184
+ return "open"
156
185
 
157
- def tick_pos(self: Self, arg: str) -> str:
186
+ def tick_pos(self: Self, arg: str, *, hasgroup: bool) -> str:
158
187
  self.spec.append(arg)
159
188
  if self.parser.order == Order.POSIX:
160
189
  return "break"
@@ -162,7 +191,7 @@ class Parsing:
162
191
  self.dumpspec()
163
192
  return "closed"
164
193
  else:
165
- return "closed"
194
+ return "group" if hasgroup else "closed"
166
195
 
167
196
  def warn(self: Self, wrncls: type, /, **kwargs: Any) -> None:
168
197
  wrn: PreparseWarning = wrncls(**kwargs)
@@ -20,6 +20,7 @@ __all__ = ["PreParser"]
20
20
  class PreParser:
21
21
  __slots__ = (
22
22
  "_abbr",
23
+ "_group",
23
24
  "_optdict",
24
25
  "_order",
25
26
  "_prog",
@@ -31,6 +32,7 @@ class PreParser:
31
32
  optdict: Any = None,
32
33
  prog: Any = None,
33
34
  abbr: Any = Abbr.COMPLETE,
35
+ group: Any = Group.MAINTAIN,
34
36
  order: Any = Order.PERMUTE,
35
37
  warn: Callable = str,
36
38
  ) -> None:
@@ -39,6 +41,7 @@ class PreParser:
39
41
  self.optdict = optdict
40
42
  self.prog = prog
41
43
  self.abbr = abbr
44
+ self.group = group
42
45
  self.order = order
43
46
  self.warn = warn
44
47
 
@@ -59,6 +62,11 @@ class PreParser:
59
62
  "This method returns a copy of the current instance."
60
63
  return type(self)(**self.todict())
61
64
 
65
+ @makeprop()
66
+ def group(self: Self, value: Any) -> dict:
67
+ "This property decides how to approach the grouping of short options."
68
+ return Group(value)
69
+
62
70
  @makeprop()
63
71
  def optdict(self: Self, value: Any) -> dict:
64
72
  "This property gives a dictionary of options."
@@ -66,6 +74,7 @@ class PreParser:
66
74
  self._optdict.clear()
67
75
  return self._optdict
68
76
  value = dict(value)
77
+ value = {str(k): Nargs(v) for k, v in value.items()}
69
78
  self._optdict.clear()
70
79
  self._optdict.update(value)
71
80
  return self._optdict
@@ -9,6 +9,7 @@ from typing import *
9
9
 
10
10
  __all__ = [
11
11
  "Abbr",
12
+ "Group",
12
13
  "Order",
13
14
  "Nargs",
14
15
  ]
@@ -26,6 +27,12 @@ class Abbr(BaseEnum):
26
27
  COMPLETE = 2
27
28
 
28
29
 
30
+ class Group(BaseEnum):
31
+ MINIMIZE = 0
32
+ MAXIMIZE = 1
33
+ MAINTAIN = 2
34
+
35
+
29
36
  class Order(BaseEnum):
30
37
  GIVEN = 0
31
38
  POSIX = 1
@@ -0,0 +1,93 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestGroupMaximize(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query, order=Order.GIVEN):
9
+ p = PreParser(order=order, group=Group.MAXIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans)))
12
+ return ans
13
+
14
+ def test_basic_maximize_grouping(self):
15
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
16
+ query = ["-a", "-b", "-c"]
17
+ solution = ["-abc"]
18
+ answer = self.parse(optdict=optdict, query=query)
19
+ self.assertEqual(solution, answer)
20
+
21
+ def test_mixed_with_non_groupable_due_to_argument(self):
22
+ optdict = {"-a": 0, "-b": 1, "-c": 0}
23
+ query = ["-a", "-b", "val", "-c"]
24
+ solution = ["-ab", "val", "-c"] # no further grouping due to -b needing arg
25
+ answer = self.parse(optdict=optdict, query=query)
26
+ self.assertEqual(solution, answer)
27
+
28
+ def test_grouping_across_permuted_positionals(self):
29
+ optdict = {"-x": 0, "-y": 0, "-z": 0}
30
+ query = ["arg1", "-x", "-y", "arg2", "-z"]
31
+ solution = ["-xyz", "arg1", "arg2"]
32
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
33
+ self.assertEqual(solution, answer)
34
+
35
+ def test_grouping_stops_due_to_optional_arg(self):
36
+ optdict = {"-a": 0, "-b": 2, "-c": 0}
37
+ query = ["-a", "-b", "-c"]
38
+ solution = ["-ab", "-c"] # b has optional arg, cannot group
39
+ answer = self.parse(optdict=optdict, query=query)
40
+ self.assertEqual(solution, answer)
41
+
42
+ def test_grouping_preserved_if_possible(self):
43
+ optdict = {"-f": 0, "-g": 0, "-h": 0}
44
+ query = ["-f", "-g"]
45
+ solution = ["-fg"] # compacted
46
+ answer = self.parse(optdict=optdict, query=query)
47
+ self.assertEqual(solution, answer)
48
+
49
+ def test_grouping_multiple_clusters(self):
50
+ optdict = {"-a": 0, "-b": 0, "-c": 0, "-x": 0, "-y": 0}
51
+ query = ["-a", "-b", "-c", "arg", "-x", "-y"]
52
+ solution = ["-abcxy", "arg"]
53
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
54
+ self.assertEqual(solution, answer)
55
+
56
+ def test_grouping_multiple_clusters_given(self):
57
+ optdict = {"-a": 0, "-b": 0, "-c": 0, "-x": 0, "-y": 0}
58
+ query = ["-a", "-b", "-c", "arg", "-x", "-y"]
59
+ solution = ["-abc", "arg", "-xy"]
60
+ answer = self.parse(optdict=optdict, query=query, order=Order.GIVEN)
61
+ self.assertEqual(solution, answer)
62
+
63
+ def test_grouping_multiple_clusters_posix(self):
64
+ optdict = {"-a": 0, "-b": 0, "-c": 0, "-x": 0, "-y": 0}
65
+ query = ["-a", "-b", "-c", "arg", "-x", "-y"]
66
+ solution = ["-abc", "arg", "-x", "-y"]
67
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
68
+ self.assertEqual(solution, answer)
69
+
70
+ def test_cannot_group_across_required_arg(self):
71
+ optdict = {"-m": 0, "-n": 1, "-o": 0}
72
+ query = ["-m", "-n", "data", "-o"]
73
+ solution = ["-mn", "data", "-o"] # -n prevents grouping
74
+ answer = self.parse(optdict=optdict, query=query)
75
+ self.assertEqual(solution, answer)
76
+
77
+ def test_grouping_with_double_dash(self):
78
+ optdict = {"-a": 0, "-b": 0}
79
+ query = ["-a", "--", "-b"]
80
+ solution = ["-a", "--", "-b"] # grouping not done past "--"
81
+ answer = self.parse(optdict=optdict, query=query)
82
+ self.assertEqual(solution, answer)
83
+
84
+ def test_preserve_original_if_only_one(self):
85
+ optdict = {"-q": 0}
86
+ query = ["-q"]
87
+ solution = ["-q"]
88
+ answer = self.parse(optdict=optdict, query=query)
89
+ self.assertEqual(solution, answer)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ unittest.main()
@@ -0,0 +1,51 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestGroupMaximizeEdgeCases(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query, order=Order.GIVEN):
9
+ p = PreParser(order=order, group=Group.MAXIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans)))
12
+ return ans
13
+
14
+ def test_combine_with_gap_due_to_argument(self):
15
+ optdict = {"-a": 0, "-b": 1, "-c": 0, "-d": 0}
16
+ query = ["-a", "-b", "value", "-cd"]
17
+ solution = ["-ab", "value", "-cd"]
18
+ answer = self.parse(optdict=optdict, query=query)
19
+ self.assertEqual(solution, answer)
20
+
21
+ def test_do_not_combine_across_double_dash(self):
22
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
23
+ query = ["-a", "--", "-b", "-c"]
24
+ solution = ["-a", "--", "-b", "-c"]
25
+ answer = self.parse(optdict=optdict, query=query)
26
+ self.assertEqual(solution, answer)
27
+
28
+ def test_combine_after_permute(self):
29
+ optdict = {"-x": 0, "-y": 0, "-z": 0}
30
+ query = ["file.txt", "-x", "-y", "-z"]
31
+ solution = ["-xyz", "file.txt"]
32
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
33
+ self.assertEqual(solution, answer)
34
+
35
+ def test_preserve_mixed_grouping_and_single(self):
36
+ optdict = {"-m": 0, "-n": 0, "-o": 0}
37
+ query = ["-m", "-no"]
38
+ solution = ["-mno"]
39
+ answer = self.parse(optdict=optdict, query=query)
40
+ self.assertEqual(solution, answer)
41
+
42
+ def test_merge_respects_optional_args(self):
43
+ optdict = {"-a": 0, "-b": 2, "-c": 0}
44
+ query = ["-a", "-bopt", "-c"]
45
+ solution = ["-abopt", "-c"] # -b consumes opt
46
+ answer = self.parse(optdict=optdict, query=query)
47
+ self.assertEqual(solution, answer)
48
+
49
+
50
+ if __name__ == "__main__":
51
+ unittest.main()
@@ -0,0 +1,80 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestMinimizeGroupParsing(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query):
9
+ p = PreParser(order=Order.GIVEN, group=Group.MINIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans))) # round-trip check
12
+ return ans
13
+
14
+ def test_simple_grouped_flags(self):
15
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
16
+ query = ["-abc"]
17
+ solution = ["-a", "-b", "-c"]
18
+ answer = self.parse(optdict=optdict, query=query)
19
+ self.assertEqual(solution, answer)
20
+
21
+ def test_grouped_with_argument_at_end(self):
22
+ optdict = {"-a": 0, "-b": 0, "-c": 1}
23
+ query = ["-abcvalue"]
24
+ solution = ["-a", "-b", "-cvalue"]
25
+ answer = self.parse(optdict=optdict, query=query)
26
+ self.assertEqual(solution, answer)
27
+
28
+ def test_grouped_with_argument_middle(self):
29
+ optdict = {"-a": 0, "-b": 1, "-c": 0}
30
+ query = ["-abvaluec"]
31
+ solution = ["-a", "-bvaluec"] # because -b consumes "valuec"
32
+ answer = self.parse(optdict=optdict, query=query)
33
+ self.assertEqual(solution, answer)
34
+
35
+ def test_multiple_grouped_chunks(self):
36
+ optdict = {"-x": 0, "-y": 0, "-z": 1}
37
+ query = ["-xy", "-zabc"]
38
+ solution = ["-x", "-y", "-zabc"]
39
+ answer = self.parse(optdict=optdict, query=query)
40
+ self.assertEqual(solution, answer)
41
+
42
+ def test_grouped_with_nonexistent_flag(self):
43
+ optdict = {"-a": 0, "-b": 0}
44
+ query = ["-abc"]
45
+ # assume unknown option "-c" is preserved as-is
46
+ solution = ["-a", "-b", "-c"]
47
+ answer = self.parse(optdict=optdict, query=query)
48
+ self.assertEqual(solution, answer)
49
+
50
+ def test_grouped_with_equals_sign(self):
51
+ optdict = {"-a": 0, "-b": 0, "-c": 1}
52
+ query = ["-abc=foo"]
53
+ solution = ["-a", "-b", "-c=foo"] # assuming `=foo` binds to last opt
54
+ answer = self.parse(optdict=optdict, query=query)
55
+ self.assertEqual(solution, answer)
56
+
57
+ def test_mixed_with_normal_args(self):
58
+ optdict = {"-v": 0, "-f": 1, "-o": 2}
59
+ query = ["-v", "-fooutput.txt", "log.txt", "--", "file1"]
60
+ solution = ["-v", "-fooutput.txt", "log.txt", "--", "file1"]
61
+ answer = self.parse(optdict=optdict, query=query)
62
+ self.assertEqual(solution, answer)
63
+
64
+ def test_double_dash_preserved(self):
65
+ optdict = {"-a": 0, "-b": 0}
66
+ query = ["-ab", "--", "-c"]
67
+ solution = ["-a", "-b", "--", "-c"]
68
+ answer = self.parse(optdict=optdict, query=query)
69
+ self.assertEqual(solution, answer)
70
+
71
+ def test_repeated_grouped_options(self):
72
+ optdict = {"-x": 0, "-y": 0}
73
+ query = ["-xy", "-yx"]
74
+ solution = ["-x", "-y", "-y", "-x"]
75
+ answer = self.parse(optdict=optdict, query=query)
76
+ self.assertEqual(solution, answer)
77
+
78
+
79
+ if __name__ == "__main__":
80
+ unittest.main()
@@ -0,0 +1,103 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestMinimizeGroupParsing(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query):
9
+ p = PreParser(order=Order.GIVEN, group=Group.MINIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans))) # round-trip check
12
+ return ans
13
+
14
+ def test_basic_grouping(self):
15
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
16
+ self.assertEqual(
17
+ self.parse(optdict=optdict, query=["-abc"]), ["-a", "-b", "-c"]
18
+ )
19
+
20
+ def test_last_option_takes_argument(self):
21
+ optdict = {"-a": 0, "-b": 0, "-c": 1}
22
+ self.assertEqual(
23
+ self.parse(optdict=optdict, query=["-abcval"]), ["-a", "-b", "-cval"]
24
+ )
25
+
26
+ def test_argument_in_middle(self):
27
+ optdict = {"-a": 0, "-b": 1, "-c": 0}
28
+ self.assertEqual(
29
+ self.parse(optdict=optdict, query=["-abvalc"]), ["-a", "-bvalc"]
30
+ )
31
+
32
+ def test_unknown_short_option_warning_behavior(self):
33
+ optdict = {"-a": 0, "-b": 0}
34
+ self.assertEqual(
35
+ self.parse(optdict=optdict, query=["-abx"]), ["-a", "-b", "-x"]
36
+ )
37
+
38
+ def test_equals_sign_treated_as_value(self):
39
+ optdict = {"-a": 0, "-b": 0, "-c": 1}
40
+ self.assertEqual(
41
+ self.parse(optdict=optdict, query=["-abc=foo"]), ["-a", "-b", "-c=foo"]
42
+ )
43
+
44
+ def test_mixed_grouped_and_separate_options(self):
45
+ optdict = {"-a": 0, "-b": 1, "-v": 0}
46
+ self.assertEqual(
47
+ self.parse(optdict=optdict, query=["-av", "-bvalue"]),
48
+ ["-a", "-v", "-bvalue"],
49
+ )
50
+
51
+ def test_long_option_untouched(self):
52
+ optdict = {"-x": 0, "--file": 1}
53
+ self.assertEqual(
54
+ self.parse(optdict=optdict, query=["-x", "--file=data.txt"]),
55
+ ["-x", "--file=data.txt"],
56
+ )
57
+
58
+ def test_option_with_dash_value(self):
59
+ optdict = {"-c": 1}
60
+ self.assertEqual(self.parse(optdict=optdict, query=["-c-value"]), ["-c-value"])
61
+
62
+ def test_double_dash_terminator(self):
63
+ optdict = {"-a": 0, "-b": 0}
64
+ self.assertEqual(
65
+ self.parse(optdict=optdict, query=["-ab", "--", "-c"]),
66
+ ["-a", "-b", "--", "-c"],
67
+ )
68
+
69
+ def test_grouped_with_space_after(self):
70
+ optdict = {"-x": 1, "-y": 0}
71
+ self.assertEqual(
72
+ self.parse(optdict=optdict, query=["-xy", "val"]), ["-xy", "val"]
73
+ )
74
+
75
+ def test_grouped_option_last_requires_arg_followed_by_space(self):
76
+ optdict = {"-a": 0, "-b": 0, "-c": 1}
77
+ self.assertEqual(
78
+ self.parse(optdict=optdict, query=["-abc", "VAL"]),
79
+ ["-a", "-b", "-c", "VAL"],
80
+ )
81
+
82
+ def test_unrecognized_grouped_mix(self):
83
+ optdict = {"-a": 0}
84
+ self.assertEqual(
85
+ self.parse(optdict=optdict, query=["-abc"]), ["-a", "-b", "-c"]
86
+ )
87
+
88
+ def test_repeated_group_flags(self):
89
+ optdict = {"-v": 0, "-d": 0}
90
+ self.assertEqual(
91
+ self.parse(optdict=optdict, query=["-vv", "-dd"]), ["-v", "-v", "-d", "-d"]
92
+ )
93
+
94
+ def test_combined_behavior_argument_and_unknown(self):
95
+ optdict = {"-a": 0, "-b": 1}
96
+ self.assertEqual(
97
+ self.parse(optdict=optdict, query=["-abvalue", "-c"]),
98
+ ["-a", "-bvalue", "-c"],
99
+ )
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
@@ -0,0 +1,83 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestPermuteAndPosixParsing(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query, order):
9
+ p = PreParser(order=order, group=Group.MINIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans)))
12
+ return ans
13
+
14
+ # --- PERMUTE ---
15
+
16
+ def test_permute_all_positionals_moved(self):
17
+ optdict = {"-a": 0, "-b": 1}
18
+ query = ["input.txt", "-a", "-bvalue", "config.json"]
19
+ solution = ["-a", "-bvalue", "input.txt", "config.json"]
20
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
21
+ self.assertEqual(solution, answer)
22
+
23
+ def test_permute_with_mixed_options(self):
24
+ optdict = {"-x": 0, "-y": 2, "-z": 0}
25
+ query = ["-x", "file1", "-yextra", "file2", "-z"]
26
+ solution = ["-x", "-yextra", "-z", "file1", "file2"]
27
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
28
+ self.assertEqual(solution, answer)
29
+
30
+ def test_permute_grouped_flags_and_positionals(self):
31
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
32
+ query = ["data.csv", "-abc"]
33
+ solution = ["-a", "-b", "-c", "data.csv"]
34
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
35
+ self.assertEqual(solution, answer)
36
+
37
+ def test_permute_with_double_dash(self):
38
+ optdict = {"-v": 0, "-o": 1}
39
+ query = ["-v", "file1", "--", "-oout.txt"]
40
+ solution = ["-v", "--", "file1", "-oout.txt"]
41
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
42
+ self.assertEqual(solution, answer)
43
+
44
+ # --- POSIX ---
45
+
46
+ def test_posix_stops_at_first_positional(self):
47
+ optdict = {"-x": 0, "-y": 0}
48
+ query = ["-x", "main.py", "-y"]
49
+ solution = ["-x", "main.py", "-y"]
50
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
51
+ self.assertEqual(solution, answer)
52
+
53
+ def test_posix_multiple_positionals(self):
54
+ optdict = {"-a": 0, "-b": 1}
55
+ query = ["-a", "input.txt", "-bval", "config.yaml"]
56
+ solution = ["-a", "input.txt", "-bval", "config.yaml"]
57
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
58
+ self.assertEqual(solution, answer)
59
+
60
+ def test_posix_double_dash_preserves_options_after(self):
61
+ optdict = {"-v": 0, "-d": 1}
62
+ query = ["file.txt", "--", "-v", "-dlog.txt"]
63
+ solution = ["file.txt", "--", "-v", "-dlog.txt"]
64
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
65
+ self.assertEqual(solution, answer)
66
+
67
+ def test_posix_with_grouped_options_then_positionals(self):
68
+ optdict = {"-a": 0, "-b": 0, "-c": 0}
69
+ query = ["-abc", "file.txt", "-d"]
70
+ solution = ["-a", "-b", "-c", "file.txt", "-d"]
71
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
72
+ self.assertEqual(solution, answer)
73
+
74
+ def test_posix_optional_arg_ignored_after_positional(self):
75
+ optdict = {"-o": 2, "-v": 0}
76
+ query = ["-o", "input.txt", "file.txt", "-v"]
77
+ solution = ["-o", "input.txt", "file.txt", "-v"]
78
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
79
+ self.assertEqual(solution, answer)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ unittest.main()
@@ -0,0 +1,73 @@
1
+ import unittest
2
+
3
+ from preparse.core import Group, Order, PreParser
4
+
5
+
6
+ class TestMixedArgScenarios(unittest.TestCase):
7
+
8
+ def parse(self, *, optdict, query, order):
9
+ p = PreParser(order=order, group=Group.MINIMIZE, optdict=optdict)
10
+ ans = p.parse_args(query)
11
+ self.assertEqual(list(ans), list(p.parse_args(ans)))
12
+ return ans
13
+
14
+ def test_mix_positional_long_short_optional_args_permute(self):
15
+ optdict = {"-a": 0, "-b": 1, "-c": 2, "--log": 1, "--debug": 0, "--config": 2}
16
+ query = ["input.txt", "--log=logfile", "-acextra", "--debug", "file.csv"]
17
+ solution = [
18
+ "--log=logfile",
19
+ "-a",
20
+ "-cextra",
21
+ "--debug",
22
+ "input.txt",
23
+ "file.csv",
24
+ ]
25
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
26
+ self.assertEqual(solution, answer)
27
+
28
+ def test_mix_positional_long_short_optional_args_posix(self):
29
+ optdict = {"-a": 0, "-b": 1, "-c": 2, "--log": 1, "--debug": 0, "--config": 2}
30
+ query = [
31
+ "--debug",
32
+ "-acextra",
33
+ "--log=logfile",
34
+ "input.txt",
35
+ "--config",
36
+ "settings",
37
+ ]
38
+ solution = [
39
+ "--debug",
40
+ "-a",
41
+ "-cextra",
42
+ "--log=logfile",
43
+ "input.txt",
44
+ "--config",
45
+ "settings",
46
+ ]
47
+ answer = self.parse(optdict=optdict, query=query, order=Order.POSIX)
48
+ self.assertEqual(solution, answer)
49
+
50
+ def test_optional_and_required_arg_mixed(self):
51
+ optdict = {"-x": 2, "-y": 1, "--mode": 2, "--file": 1}
52
+ query = ["-xyval", "--mode=fast", "--file", "out.txt", "input.dat"]
53
+ solution = ["-xyval", "--mode=fast", "--file", "out.txt", "input.dat"]
54
+ answer = self.parse(optdict=optdict, query=query, order=Order.GIVEN)
55
+ self.assertEqual(solution, answer)
56
+
57
+ def test_long_equals_and_space_combination(self):
58
+ optdict = {"--alpha": 1, "--beta": 1, "-a": 0, "-b": 1}
59
+ query = ["--alpha=one", "--beta", "two", "-a", "-bthree", "final"]
60
+ solution = ["--alpha=one", "--beta", "two", "-a", "-bthree", "final"]
61
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
62
+ self.assertEqual(solution, answer)
63
+
64
+ def test_mix_grouped_and_non_grouped(self):
65
+ optdict = {"-a": 0, "-b": 0, "-c": 1, "--set": 2}
66
+ query = ["-abcval", "--set", "x", "y", "arg1"]
67
+ solution = ["-a", "-b", "-cval", "--set", "x", "y", "arg1"]
68
+ answer = self.parse(optdict=optdict, query=query, order=Order.PERMUTE)
69
+ self.assertEqual(solution, answer)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: preparse
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev4
4
4
  Summary: This project preparses args for further parsing later on.
5
5
  Author-email: Johannes <johannes-programming@mailfence.com>
6
6
  License: The MIT License (MIT)
@@ -25,6 +25,12 @@ src/preparse/tests/test_features.py
25
25
  src/preparse/tests/test_long_only.py
26
26
  src/preparse/tests/test_long_only_no_permutation.py
27
27
  src/preparse/tests/test_long_only_posix.py
28
+ src/preparse/tests/test_max.py
29
+ src/preparse/tests/test_max_1.py
30
+ src/preparse/tests/test_min.py
31
+ src/preparse/tests/test_min_1.py
32
+ src/preparse/tests/test_min_2.py
33
+ src/preparse/tests/test_min_3.py
28
34
  src/preparse/tests/test_parse.py
29
35
  src/preparse/tests/test_parse_no_permutation.py
30
36
  src/preparse/tests/test_parse_posix.py
File without changes
File without changes
File without changes
File without changes