summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/tables/system_prerequisites.csv2
-rw-r--r--lib/spack/spack/main.py26
-rw-r--r--lib/spack/spack/test/util/unparse/unparse.py74
-rw-r--r--lib/spack/spack/util/unparse/unparser.py92
4 files changed, 182 insertions, 12 deletions
diff --git a/lib/spack/docs/tables/system_prerequisites.csv b/lib/spack/docs/tables/system_prerequisites.csv
index 5f661883d3..af8dcafffa 100644
--- a/lib/spack/docs/tables/system_prerequisites.csv
+++ b/lib/spack/docs/tables/system_prerequisites.csv
@@ -1,5 +1,5 @@
Name, Supported Versions, Notes, Requirement Reason
-Python, 2.7/3.6-3.10, , Interpreter for Spack
+Python, 2.7/3.6-3.11, , Interpreter for Spack
C/C++ Compilers, , , Building software
make, , , Build software
patch, , , Build software
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
index 4c3f19d75d..a9a9d20df7 100644
--- a/lib/spack/spack/main.py
+++ b/lib/spack/spack/main.py
@@ -343,17 +343,21 @@ class SpackArgumentParser(argparse.ArgumentParser):
self._remove_action(self._actions[-1])
self.subparsers = self.add_subparsers(metavar="COMMAND", dest="command")
- # each command module implements a parser() function, to which we
- # pass its subparser for setup.
- module = spack.cmd.get_module(cmd_name)
-
- # build a list of aliases
- alias_list = [k for k, v in aliases.items() if v == cmd_name]
-
- subparser = self.subparsers.add_parser(
- cmd_name, aliases=alias_list, help=module.description, description=module.description
- )
- module.setup_parser(subparser)
+ if cmd_name not in self.subparsers._name_parser_map:
+ # each command module implements a parser() function, to which we
+ # pass its subparser for setup.
+ module = spack.cmd.get_module(cmd_name)
+
+ # build a list of aliases
+ alias_list = [k for k, v in aliases.items() if v == cmd_name]
+
+ subparser = self.subparsers.add_parser(
+ cmd_name,
+ aliases=alias_list,
+ help=module.description,
+ description=module.description,
+ )
+ module.setup_parser(subparser)
# return the callable function for the command
return spack.cmd.get_command(cmd_name)
diff --git a/lib/spack/spack/test/util/unparse/unparse.py b/lib/spack/spack/test/util/unparse/unparse.py
index 49009ae98e..82148c9dc8 100644
--- a/lib/spack/spack/test/util/unparse/unparse.py
+++ b/lib/spack/spack/test/util/unparse/unparse.py
@@ -178,6 +178,71 @@ async def f():
"""
+match_literal = """\
+match status:
+ case 400:
+ return "Bad request"
+ case 404 | 418:
+ return "Not found"
+ case _:
+ return "Something's wrong with the internet"
+"""
+
+match_with_noop = """\
+match status:
+ case 400:
+ return "Bad request"
+"""
+
+match_literal_and_variable = """\
+match point:
+ case (0, 0):
+ print("Origin")
+ case (0, y):
+ print(f"Y={y}")
+ case (x, 0):
+ print(f"X={x}")
+ case (x, y):
+ print(f"X={x}, Y={y}")
+ case _:
+ raise ValueError("Not a point")
+"""
+
+
+match_classes = """\
+class Point:
+ x: int
+ y: int
+
+def location(point):
+ match point:
+ case Point(x=0, y=0):
+ print("Origin is the point's location.")
+ case Point(x=0, y=y):
+ print(f"Y={y} and the point is on the y-axis.")
+ case Point(x=x, y=0):
+ print(f"X={x} and the point is on the x-axis.")
+ case Point():
+ print("The point is located somewhere else on the plane.")
+ case _:
+ print("Not a point")
+"""
+
+match_nested = """\
+match points:
+ case []:
+ print("No points in the list.")
+ case [Point(0, 0)]:
+ print("The origin is the only point in the list.")
+ case [Point(x, y)]:
+ print(f"A single point {x}, {y} is in the list.")
+ case [Point(0, y1), Point(0, y2)]:
+ print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
+ case _:
+ print("Something else is found in the list.")
+"""
+
+
def check_ast_roundtrip(code1, filename="internal", mode="exec"):
ast1 = compile(str(code1), filename, mode, ast.PyCF_ONLY_AST)
code2 = spack.util.unparse.unparse(ast1)
@@ -512,3 +577,12 @@ def test_async_with():
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Not supported < 3.5")
def test_async_with_as():
check_ast_roundtrip(async_with_as)
+
+
+@pytest.mark.skipif(sys.version_info < (3, 10), reason="Not supported < 3.10")
+@pytest.mark.parametrize(
+ "literal",
+ [match_literal, match_with_noop, match_literal_and_variable, match_classes, match_nested],
+)
+def test_match_literal(literal):
+ check_ast_roundtrip(literal)
diff --git a/lib/spack/spack/util/unparse/unparser.py b/lib/spack/spack/util/unparse/unparser.py
index a46d19fa76..c204aea25a 100644
--- a/lib/spack/spack/util/unparse/unparser.py
+++ b/lib/spack/spack/util/unparse/unparser.py
@@ -1243,3 +1243,95 @@ class Unparser:
if node.optional_vars:
self.write(" as ")
self.dispatch(node.optional_vars)
+
+ def visit_Match(self, node):
+ self.fill("match ")
+ self.dispatch(node.subject)
+ with self.block():
+ for case in node.cases:
+ self.dispatch(case)
+
+ def visit_match_case(self, node):
+ self.fill("case ")
+ self.dispatch(node.pattern)
+ if node.guard:
+ self.write(" if ")
+ self.dispatch(node.guard)
+ with self.block():
+ self.dispatch(node.body)
+
+ def visit_MatchValue(self, node):
+ self.dispatch(node.value)
+
+ def visit_MatchSingleton(self, node):
+ self._write_constant(node.value)
+
+ def visit_MatchSequence(self, node):
+ with self.delimit("[", "]"):
+ interleave(lambda: self.write(", "), self.dispatch, node.patterns)
+
+ def visit_MatchStar(self, node):
+ name = node.name
+ if name is None:
+ name = "_"
+ self.write("*{}".format(name))
+
+ def visit_MatchMapping(self, node):
+ def write_key_pattern_pair(pair):
+ k, p = pair
+ self.dispatch(k)
+ self.write(": ")
+ self.dispatch(p)
+
+ with self.delimit("{", "}"):
+ keys = node.keys
+ interleave(
+ lambda: self.write(", "),
+ write_key_pattern_pair,
+ zip(keys, node.patterns),
+ )
+ rest = node.rest
+ if rest is not None:
+ if keys:
+ self.write(", ")
+ self.write("**{}".format(rest))
+
+ def visit_MatchClass(self, node):
+ self.set_precedence(_Precedence.ATOM, node.cls)
+ self.dispatch(node.cls)
+ with self.delimit("(", ")"):
+ patterns = node.patterns
+ interleave(lambda: self.write(", "), self.dispatch, patterns)
+ attrs = node.kwd_attrs
+ if attrs:
+
+ def write_attr_pattern(pair):
+ attr, pattern = pair
+ self.write("{}=".format(attr))
+ self.dispatch(pattern)
+
+ if patterns:
+ self.write(", ")
+ interleave(
+ lambda: self.write(", "),
+ write_attr_pattern,
+ zip(attrs, node.kwd_patterns),
+ )
+
+ def visit_MatchAs(self, node):
+ name = node.name
+ pattern = node.pattern
+ if name is None:
+ self.write("_")
+ elif pattern is None:
+ self.write(node.name)
+ else:
+ with self.require_parens(_Precedence.TEST, node):
+ self.set_precedence(_Precedence.BOR, node.pattern)
+ self.dispatch(node.pattern)
+ self.write(" as {}".format(node.name))
+
+ def visit_MatchOr(self, node):
+ with self.require_parens(_Precedence.BOR, node):
+ self.set_precedence(pnext(_Precedence.BOR), *node.patterns)
+ interleave(lambda: self.write(" | "), self.dispatch, node.patterns)