preparse 0.1.0.dev2__tar.gz → 0.1.0.dev3__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.dev3}/PKG-INFO +1 -1
  2. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/pyproject.toml +1 -1
  3. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/_parsing/Parsing.py +56 -24
  4. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/core/PreParser.py +8 -0
  5. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/core/enums.py +7 -0
  6. preparse-0.1.0.dev3/src/preparse/tests/test_max.py +93 -0
  7. preparse-0.1.0.dev3/src/preparse/tests/test_max_1.py +51 -0
  8. preparse-0.1.0.dev3/src/preparse/tests/test_min.py +80 -0
  9. preparse-0.1.0.dev3/src/preparse/tests/test_min_1.py +103 -0
  10. preparse-0.1.0.dev3/src/preparse/tests/test_min_2.py +83 -0
  11. preparse-0.1.0.dev3/src/preparse/tests/test_min_3.py +73 -0
  12. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3/src/preparse.egg-info}/PKG-INFO +1 -1
  13. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse.egg-info/SOURCES.txt +6 -0
  14. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/LICENSE.txt +0 -0
  15. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/MANIFEST.in +0 -0
  16. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/README.rst +0 -0
  17. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/setup.cfg +0 -0
  18. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/__init__.py +0 -0
  19. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/_parsing/__init__.py +0 -0
  20. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/core/Click.py +0 -0
  21. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/core/__init__.py +0 -0
  22. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/core/warnings.py +0 -0
  23. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/__init__.py +0 -0
  24. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_expit.py +0 -0
  25. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_extra.py +0 -0
  26. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_extra_no_permutation.py +0 -0
  27. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_extra_posix.py +0 -0
  28. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_features.py +0 -0
  29. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_long_only.py +0 -0
  30. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_long_only_no_permutation.py +0 -0
  31. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_long_only_posix.py +0 -0
  32. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_parse.py +0 -0
  33. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_parse_no_permutation.py +0 -0
  34. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_parse_posix.py +0 -0
  35. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_9001.py +0 -0
  36. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_9002.py +0 -0
  37. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_9005.py +0 -0
  38. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_9006.py +0 -0
  39. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_9007.py +0 -0
  40. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_even_more.py +0 -0
  41. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse/tests/test_warn_more.py +0 -0
  42. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse.egg-info/dependency_links.txt +0 -0
  43. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/src/preparse.egg-info/requires.txt +0 -0
  44. {preparse-0.1.0.dev2 → preparse-0.1.0.dev3}/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.dev3
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.dev3"
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
 
@@ -71,26 +84,32 @@ class Parsing:
71
84
 
72
85
  def tick(self: Self, optn: str) -> str:
73
86
  if optn == "break":
87
+ # if no more options are allowed
74
88
  self.spec.extend(self.args)
75
89
  self.args.clear()
76
90
  return "break"
77
91
  arg: str = self.args.pop(0)
78
92
  if optn == "open":
93
+ # if a value for an option is already expected
79
94
  self.ans.append(arg)
80
95
  return "closed"
81
96
  if arg == "--":
97
+ # if arg is the special argument
82
98
  self.ans.append("--")
83
99
  return "break"
100
+ hasgroup: bool = optn == "group"
84
101
  if arg.startswith("-") and arg != "-":
85
- return self.tick_opt(arg)
102
+ # if arg is an option
103
+ return self.tick_opt(arg, hasgroup=hasgroup)
86
104
  else:
87
- return self.tick_pos(arg)
105
+ # if arg is positional
106
+ return self.tick_pos(arg, hasgroup=hasgroup)
88
107
 
89
- def tick_opt(self: Self, arg: str) -> str:
108
+ def tick_opt(self: Self, arg: str, /, *, hasgroup: bool) -> str:
90
109
  if arg.startswith("--") or self.islongonly:
91
110
  return self.tick_opt_long(arg)
92
111
  else:
93
- return self.tick_opt_short(arg)
112
+ return self.tick_opt_short(arg, hasgroup=hasgroup)
94
113
 
95
114
  def tick_opt_long(self: Self, arg: str) -> str:
96
115
  try:
@@ -135,26 +154,39 @@ class Parsing:
135
154
  else:
136
155
  return "closed"
137
156
 
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:
157
+ def tick_opt_short(self: Self, arg: str, /, *, hasgroup: bool) -> str:
158
+ i: int
159
+ a: str
160
+ nargs: Nargs
161
+ if self.parser.group != Group.MINIMIZE:
162
+ self.ans.append("-")
163
+ if (self.parser.group == Group.MAXIMIZE) and hasgroup:
164
+ self.ans.pop()
165
+ for i, a in enumerate(arg):
166
+ if i == 0:
167
+ continue
168
+ if self.parser.group != Group.MINIMIZE:
169
+ self.ans[-1] += a
170
+ elif a == "-":
171
+ self.ans[-1] += a
172
+ else:
173
+ self.ans.append("-" + a)
174
+ nargs = self.get_nargs_for_letter(a)
175
+ if nargs == Nargs.NO_ARGUMENT:
176
+ continue
177
+ value: str = arg[i + 1 :]
178
+ return self.tick_opt_short_nongroup(nargs=nargs, value=value)
179
+ return "group"
180
+
181
+ def tick_opt_short_nongroup(self: Self, *, nargs: Nargs, value: str) -> str:
182
+ if value:
183
+ self.ans[-1] += value
155
184
  return "closed"
185
+ if nargs == Nargs.OPTIONAL_ARGUMENT:
186
+ return "closed"
187
+ return "open"
156
188
 
157
- def tick_pos(self: Self, arg: str) -> str:
189
+ def tick_pos(self: Self, arg: str, *, hasgroup: bool) -> str:
158
190
  self.spec.append(arg)
159
191
  if self.parser.order == Order.POSIX:
160
192
  return "break"
@@ -162,7 +194,7 @@ class Parsing:
162
194
  self.dumpspec()
163
195
  return "closed"
164
196
  else:
165
- return "closed"
197
+ return "group" if hasgroup else "closed"
166
198
 
167
199
  def warn(self: Self, wrncls: type, /, **kwargs: Any) -> None:
168
200
  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."
@@ -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.dev3
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