aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2023-07-04 01:55:04 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2023-07-04 01:55:04 +0200
commitd481985ce83d1e3f18c55f261d2fed2d344d4a29 (patch)
tree95465253f4cf3b60bf17bf189d54bf2cbf2f2133
parentForward puppet strings messages to logger. (diff)
downloadmuppet-strings-d481985ce83d1e3f18c55f261d2fed2d344d4a29.tar.gz
muppet-strings-d481985ce83d1e3f18c55f261d2fed2d344d4a29.tar.xz
Split puppet parse major match into multiple functions.
-rw-r--r--muppet/format.py1200
-rw-r--r--mypy.ini3
2 files changed, 598 insertions, 605 deletions
diff --git a/muppet/format.py b/muppet/format.py
index 48d692c..734f8bd 100644
--- a/muppet/format.py
+++ b/muppet/format.py
@@ -171,459 +171,383 @@ def handle_case_body(forms: list[dict[str, Any]],
# - qr
# - var (except when it's the var declaration)
-LineFragment: TypeAlias = str | Tag
+LineFragment: TypeAlias = str | Markup
Line: TypeAlias = list[LineFragment]
-def parse(form: Any, indent: int, context: list[str]) -> Markup:
- """
- Print everything from a puppet parse tree.
+def parse_access(how: Any, args: list[Any], *, indent: int, context: list[str]) -> Tag:
+ """Parse access form."""
+ # TODO newlines?
+ items = []
+ items += [parse(how, indent, context), '[']
+ for sublist in intersperse([',', ' '],
+ [[parse(arg, indent, context)]
+ for arg in args]):
+ items += sublist
+ items += [']']
+ return tag(items, 'access')
+
+
+def parse_array(items: list[Any], *, indent: int, context: list[str]) -> Tag:
+ """Parse array form."""
+ out: list[Markup]
+ out = ['[', '\n']
+ for item in items:
+ out += [
+ ind(indent+2),
+ parse(item, indent+1, context),
+ ',',
+ '\n',
+ ]
+ out += [ind(indent), ']']
+ return tag(out, 'array')
+
+
+def parse_call(func: Any, args: list[Any], *, indent: int, context: list[str]) -> Tag:
+ """Parse call form."""
+ items = []
+ items += [parse(func, indent, context), '(']
+ for sublist in intersperse([',', ' '],
+ [[parse(arg, indent, context)]
+ for arg in args]):
+ items += sublist
+ items += [')']
+ return tag(items, 'call')
+
+
+def parse_call_method(func: Any, *, indent: int, context: list[str]) -> Tag:
+ """Parse call method form."""
+ items = [parse(func['functor'], indent, context)]
+
+ if not ('block' in func and func['args'] == []):
+ items += ['(']
+ for sublist in intersperse([',', ' '],
+ [[parse(x, indent, context)]
+ for x in func['args']]):
+ items += sublist
+ items += [')']
- :param from:
- A puppet AST.
- :param indent:
- How many levels deep in indentation the code is.
- Will get multiplied by the indentation width.
- """
- items: list[Markup]
- # Sorted per `sort -V`
- match form:
- case None:
- return tag('undef', 'literal', 'undef')
+ if 'block' in func:
+ items += [parse(func['block'], indent+1, context)]
- case True:
- return tag('true', 'literal', 'true')
+ return tag(items, 'call-method')
- case False:
- return tag('false', 'literal', 'false')
- case ['access', how, *args]:
- # TODO newlines?
- items = []
- items += [parse(how, indent, context), '[']
- for sublist in intersperse([',', ' '],
- [[parse(arg, indent, context)]
- for arg in args]):
- items += sublist
- items += [']']
- return tag(items, 'access')
+def parse_case(test: Any, forms: Any, *, indent: int, context: list[str]) -> Tag:
+ """Parse case form."""
+ items: list[Markup] = [
+ keyword('case'),
+ ' ',
+ parse(test, indent, context),
+ ' ', '{', '\n',
+ handle_case_body(forms, indent, context),
+ ind(indent),
+ '}',
+ ]
- case ['and', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', keyword('and'), ' ',
- parse(b, indent, context),
- ])
+ return tag(items)
- case ['array']:
- return tag('[]', 'array')
- case ['array', *items]:
- out = ['[', '\n']
- for item in items:
- out += [
- ind(indent+2),
- parse(item, indent+1, context),
- ',',
- '\n',
+def parse_class(name: Any, rest: dict[str, Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse class form."""
+ items: list[Markup] = []
+ items += [
+ keyword('class'),
+ ' ',
+ tag(name, 'name'),
+ ' ',
+ ]
+
+ if 'params' in rest:
+ items += ['(', '\n']
+ for name, data in rest['params'].items():
+ decls: list[Markup] = []
+ decls += [ind(indent+1)]
+ if 'type' in data:
+ tt = parse(data['type'], indent+1, context)
+ decls += [tag(tt, 'type'),
+ ' ']
+ decls += [declare_var(name)]
+ if 'value' in data:
+ decls += [
+ ' ', operator('='), ' ',
+ # TODO this is a declaration
+ parse(data.get('value'), indent+1, context),
]
- out += [ind(indent), ']']
- return tag(out, 'array')
-
- case ['call', {'functor': func,
- 'args': args}]:
- items = []
- items += [parse(func, indent, context), '(']
- for sublist in intersperse([',', ' '],
- [[parse(arg, indent, context)]
- for arg in args]):
- items += sublist
- items += [')']
- return tag(items, 'call')
-
- case ['call-method', func]:
- items = [parse(func['functor'], indent, context)]
-
- if not ('block' in func and func['args'] == []):
- items += ['(']
- for sublist in intersperse([',', ' '],
- [[parse(x, indent, context)]
- for x in func['args']]):
- items += sublist
- items += [')']
-
- if 'block' in func:
- items += [parse(func['block'], indent+1, context)]
+ items += [declaration(decls, 'declaration', variable=name)]
+ items += [',', '\n']
+ items += [ind(indent), ')', ' ', '{', '\n']
+ else:
+ items += ['{', '\n']
+
+ if 'body' in rest:
+ for entry in rest['body']:
+ items += [ind(indent+1),
+ parse(entry, indent+1, context),
+ '\n']
+ items += [ind(indent), '}']
+ return tag(items)
- return tag(items, 'call-method')
- case ['case', test, forms]:
- items = [
- keyword('case'),
- ' ',
- parse(test, indent, context),
- ' ', '{', '\n',
- handle_case_body(forms, indent, context),
- ind(indent),
- '}',
- ]
-
- return tag(items)
+def parse_concat(args: list[Any], *, indent: int, context: list[str]) -> Tag:
+ """Parse concat form."""
+ items = ['"']
+ for item in args:
+ match item:
+ case ['str', ['var', x]]:
+ items += [tag(['${', print_var(x, False), '}'], 'str-var')]
+ case ['str', thingy]:
+ content = parse(thingy, indent, ['str'] + context)
+ items += [tag(['${', content, '}'], 'str-var')]
+ case s:
+ items += [s
+ .replace('"', '\\"')
+ .replace('\n', '\\n')]
+ items += '"'
+ return tag(items, 'string')
+
+
+def parse_define(name: Any, rest: dict[str, Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse define form."""
+ items: list[Markup] = []
+ items += [keyword('define'),
+ ' ',
+ tag(name, 'name'),
+ ' ']
+
+ if params := rest.get('params'):
+ items += ['(', '\n']
+ for name, data in params.items():
+ decl: list[Markup] = []
+ decl += [ind(indent+1)]
+ if 'type' in data:
+ decl += [tag(parse(data['type'], indent, context),
+ 'type'),
+ ' ']
+ # print(f'<span class="var">${name}</span>', end='')
+ decl += [declare_var(name)]
+ if 'value' in data:
+ decl += [
+ ' ', '=', ' ',
+ parse(data.get('value'), indent, context),
+ ]
+ items += [declaration(decl, 'declaration', variable=name)]
+ items += [',', '\n']
- case ['class', {'name': name,
- **rest}]:
- items = []
- items += [
- keyword('class'),
- ' ',
- tag(name, 'name'),
- ' ',
- ]
+ items += [ind(indent), ')', ' ']
- if 'params' in rest:
- items += ['(', '\n']
- for name, data in rest['params'].items():
- decls: list[Markup] = []
- decls += [ind(indent+1)]
- if 'type' in data:
- tt = parse(data['type'], indent+1, context)
- decls += [tag(tt, 'type'),
- ' ']
- decls += [declare_var(name)]
- if 'value' in data:
- decls += [
- ' ', operator('='), ' ',
- # TODO this is a declaration
- parse(data.get('value'), indent+1, context),
- ]
- items += [declaration(decls, 'declaration', variable=name)]
- items += [',', '\n']
- items += [ind(indent), ')', ' ', '{', '\n']
- else:
- items += ['{', '\n']
-
- if 'body' in rest:
- for entry in rest['body']:
- items += [ind(indent+1),
- parse(entry, indent+1, context),
- '\n']
- items += [ind(indent), '}']
- return tag(items)
+ items += ['{', '\n']
- case ['concat', *args]:
- items = ['"']
- for item in args:
- match item:
- case ['str', ['var', x]]:
- items += [tag(['${', print_var(x, False), '}'], 'str-var')]
- case ['str', thingy]:
- content = parse(thingy, indent, ['str'] + context)
- items += [tag(['${', content, '}'], 'str-var')]
- case s:
- items += [s
- .replace('"', '\\"')
- .replace('\n', '\\n')]
- items += '"'
- return tag(items, 'string')
-
- case ['collect', {'type': t,
- 'query': q}]:
- return tag([parse(t, indent, context),
- ' ',
- parse(q, indent, context)])
+ if 'body' in rest:
+ for entry in rest['body']:
+ items += [ind(indent+1),
+ parse(entry, indent+1, context),
+ '\n']
- case ['default']:
- return keyword('default')
+ items += [ind(indent), '}']
- case ['define', {'name': name,
- **rest}]:
- items = [keyword('define'),
- ' ',
- tag(name, 'name'),
- ' ']
-
- if params := rest.get('params'):
- items += ['(', '\n']
- for name, data in params.items():
- decl: list[Markup] = []
- decl += [ind(indent+1)]
- if 'type' in data:
- decl += [tag(parse(data['type'], indent, context),
- 'type'),
- ' ']
- # print(f'<span class="var">${name}</span>', end='')
- decl += [declare_var(name)]
- if 'value' in data:
- decl += [
- ' ', '=', ' ',
- parse(data.get('value'), indent, context),
- ]
- items += [declaration(decl, 'declaration', variable=name)]
- items += [',', '\n']
+ return tag(items)
- items += [ind(indent), ')', ' ']
- items += ['{', '\n']
+def parse_function(name: Any, rest: dict[str, Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse function form."""
+ items = []
+ items += [keyword('function'),
+ ' ', name]
+ if 'params' in rest:
+ items += [' ', '(', '\n']
+ for name, attributes in rest['params'].items():
+ items += [ind(indent+1)]
+ if 'type' in attributes:
+ items += [parse(attributes['type'], indent, context),
+ ' ']
+ items += [f'${name}']
+ if 'value' in attributes:
+ items += [
+ ' ', '=', ' ',
+ parse(attributes['value'], indent, context),
+ ]
+ items += [',', '\n']
+ items += [ind(indent), ')']
- if 'body' in rest:
- for entry in rest['body']:
- items += [ind(indent+1),
- parse(entry, indent+1, context),
- '\n']
+ if 'returns' in rest:
+ items += [' ', '>>', ' ',
+ parse(rest['returns'], indent, context)]
- items += [ind(indent), '}']
+ items += [' ', '{']
+ if 'body' in rest:
+ items += ['\n']
+ for item in rest['body']:
+ items += [
+ ind(indent+1),
+ parse(item, indent+1, context),
+ '\n',
+ ]
+ items += [ind(indent)]
+ items += ['}']
+ return tag(items)
- return tag(items)
- case ['exported-query']:
- return tag(['<<|', ' ', '|>>'])
+def parse_heredoc_concat(parts: list[Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse heredoc form containing concatenation."""
+ items: list[Markup] = ['@("EOF")']
- case ['exported-query', arg]:
- return tag(['<<|', ' ',
- parse(arg, indent, context),
- ' ', '|>>'])
+ lines: list[Line] = [[]]
- case ['function', {'name': name,
- **rest}]:
- items = []
- items += [keyword('function'),
- ' ', name]
- if 'params' in rest:
- items += [' ', '(', '\n']
- for name, attributes in rest['params'].items():
- items += [ind(indent+1)]
- if 'type' in attributes:
- items += [parse(attributes['type'], indent, context),
- ' ']
- items += [f'${name}']
- if 'value' in attributes:
- items += [
- ' ', '=', ' ',
- parse(attributes['value'], indent, context),
- ]
- items += [',', '\n']
- items += [ind(indent), ')']
+ for part in parts:
+ match part:
+ case ['str', ['var', x]]:
+ lines[-1] += [tag(['${', print_var(x, False), '}'])]
+ case ['str', form]:
+ lines[-1] += [tag(['${', parse(form, indent, context), '}'])]
+ case s:
+ if not isinstance(s, str):
+ raise ValueError('Unexpected value in heredoc', s)
- if 'returns' in rest:
- items += [' ', '>>', ' ',
- parse(rest['returns'], indent, context)]
+ first, *rest = s.split('\n')
+ lines[-1] += [first]
+ # lines += [[]]
- items += [' ', '{']
- if 'body' in rest:
- items += ['\n']
- for item in rest['body']:
- items += [
- ind(indent+1),
- parse(item, indent+1, context),
- '\n',
- ]
- items += [ind(indent)]
- items += ['}']
- return tag(items)
+ for item1 in rest:
+ lines += [[item1]]
- case ['hash']:
- return tag('{}', 'hash')
+ for line in lines:
+ items += ['\n']
+ if line != ['']:
+ items += [ind(indent)]
+ for item2 in line:
+ if item2:
+ items += [item2]
- case ['hash', *hash]:
- return tag([
- '{', '\n',
- print_hash(hash, indent+1, context),
- ind(indent),
- '}',
- ], 'hash')
+ match lines:
+ case [*_, ['']]:
+ # We have a trailing newline
+ items += [ind(indent), '|']
+ case _:
+ # We don't have a trailing newline
+ # Print the graphical one, but add the dash to the pipe
+ items += ['\n', ind(indent), '|-']
- # TODO a safe string to use?
- # TODO extra options?
- # Are all these already removed by the parser, requiring
- # us to reverse parse the text?
+ items += [' ', 'EOF']
+ return tag(items, 'heredoc', 'literal')
- # Parts can NEVER be empty, since that case wouldn't generate
- # a concat element, but a "plain" text element
- case ['heredoc', {'text': ['concat', *parts]}]:
- items = ['@("EOF")']
-
- lines: list[Line] = [[]]
-
- for part in parts:
- match part:
- case ['str', ['var', x]]:
- lines[-1] += [tag(['${', print_var(x, False), '}'])]
- case ['str', form]:
- lines[-1] += [tag(['${', parse(form, indent, context), '}'])]
- case s:
- if not isinstance(s, str):
- raise ValueError('Unexpected value in heredoc', s)
-
- first, *rest = s.split('\n')
- lines[-1] += [first]
- # lines += [[]]
-
- for item in rest:
- lines += [[item]]
-
- for line in lines:
- items += ['\n']
- if line != ['']:
- items += [ind(indent)]
- for item in line:
- if item:
- items += [item]
-
- match lines:
- case [*_, ['']]:
- # We have a trailing newline
- items += [ind(indent), '|']
- case _:
- # We don't have a trailing newline
- # Print the graphical one, but add the dash to the pipe
- items += ['\n', ind(indent), '|-']
-
- items += [' ', 'EOF']
- return tag(items, 'heredoc', 'literal')
- case ['heredoc', {'text': ''}]:
- return tag(['@(EOF)', '\n', ind(indent), '|', ' ', 'EOF'],
- 'heredoc', 'literal')
+def parse_heredoc_text(text: str, *, indent: int, context: list[str]) -> Tag:
+ """Parse heredoc form only containing text."""
+ items: list[Markup] = []
+ items += ['@(EOF)', '\n']
+ lines = text.split('\n')
- case ['heredoc', {'text': text}]:
- items = []
- items += ['@(EOF)', '\n']
- lines = text.split('\n')
+ no_eol: bool = True
- no_eol: bool = True
+ if lines[-1] == '':
+ lines = lines[:-1]
+ no_eol = False
- if lines[-1] == '':
- lines = lines[:-1]
- no_eol = False
+ for line in lines:
+ if line:
+ items += [ind(indent), line]
+ items += ['\n']
+ items += [ind(indent)]
- for line in lines:
- if line:
- items += [ind(indent), line]
- items += ['\n']
- items += [ind(indent)]
+ if no_eol:
+ items += ['|-']
+ else:
+ items += ['|']
+ items += [' ', 'EOF']
- if no_eol:
- items += ['|-']
- else:
- items += ['|']
- items += [' ', 'EOF']
+ return tag(items, 'heredoc', 'literal')
- return tag(items, 'heredoc', 'literal')
- case ['if', {'test': test,
- **rest}]:
- items = []
+def parse_if(test: Any, rest: dict[str, Any], *, indent: int, context: list[str]) -> Tag:
+ """Parse if form."""
+ items: list[Markup] = []
+ items += [
+ keyword('if'),
+ ' ',
+ parse(test, indent, context),
+ ' ', '{', '\n',
+ ]
+ if 'then' in rest:
+ for item in rest['then']:
items += [
- keyword('if'),
- ' ',
- parse(test, indent, context),
- ' ', '{', '\n',
+ ind(indent+1),
+ parse(item, indent+1, context),
+ '\n',
]
- if 'then' in rest:
- for item in rest['then']:
+ items += [ind(indent), '}']
+
+ if 'else' in rest:
+ items += [' ']
+ match rest['else']:
+ case [['if', *rest]]:
+ # TODO propper tagging
+ items += ['els',
+ parse(['if', *rest], indent, context)]
+ case el:
+ items += [keyword('else'),
+ ' ', '{', '\n']
+ for item in el:
items += [
ind(indent+1),
parse(item, indent+1, context),
'\n',
]
- items += [ind(indent), '}']
-
- if 'else' in rest:
- items += [' ']
- match rest['else']:
- case [['if', *rest]]:
- # TODO propper tagging
- items += ['els',
- parse(['if', *rest], indent, context)]
- case el:
- items += [keyword('else'),
- ' ', '{', '\n']
- for item in el:
- items += [
- ind(indent+1),
- parse(item, indent+1, context),
- '\n',
- ]
- items += [
- ind(indent),
- '}',
- ]
- return tag(items)
-
- case ['in', needle, stack]:
- return tag([
- parse(needle, indent, context),
- ' ', keyword('in'), ' ',
- parse(stack, indent, context),
- ])
-
- case ['invoke', {'functor': func,
- 'args': args}]:
- items = [
- parse(func, indent, context),
- ' ',
- ]
- if len(args) == 1:
- items += [parse(args[0], indent+1, context)]
- else:
- items += ['(']
- for sublist in intersperse([',', ' '],
- [[parse(arg, indent+1, context)]
- for arg in args]):
- items += sublist
- items += [')']
- return tag(items, 'invoke')
-
- case ['nop']:
- return tag('', 'nop')
-
- case ['lambda', {'params': params,
- 'body': body}]:
- items = []
- # TODO note these are declarations
- items += ['|']
- for sublist in intersperse([',', ' '],
- [[f'${x}'] for x in params.keys()]):
- items += sublist
- items += ['|', ' ', '{', '\n']
- for entry in body:
items += [
ind(indent),
- parse(entry, indent, context),
- '\n',
+ '}',
]
- items += [ind(indent-1), '}']
- return tag(items, 'lambda')
-
- case ['or', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', keyword('or'), ' ',
- parse(b, indent, context),
- ])
-
- case ['paren', *forms]:
- return tag([
- '(',
- *(parse(form, indent+1, context)
- for form in forms),
- ')',
- ], 'paren')
+ return tag(items)
- # Qualified name?
- case ['qn', x]:
- return tag(x, 'qn')
- # Qualified resource?
- case ['qr', x]:
- return tag(x, 'qr')
+def parse_invoke(func: Any, args: list[Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse invoke form."""
+ items = [
+ parse(func, indent, context),
+ ' ',
+ ]
+ if len(args) == 1:
+ items += [parse(args[0], indent+1, context)]
+ else:
+ items += ['(']
+ for sublist in intersperse([',', ' '],
+ [[parse(arg, indent+1, context)]
+ for arg in args]):
+ items += sublist
+ items += [')']
+ return tag(items, 'invoke')
- case ['regexp', s]:
- return tag(['/', tag(s, 'regex-body'), '/'], 'regex')
- # Resource instansiation with exactly one instance
- case ['resource', {'type': t,
- 'bodies': [body]}]:
+def parse_lambda(params: dict[str, Any], body: Any,
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse lambda form."""
+ items: list[Markup] = []
+ # TODO note these are declarations
+ items += ['|']
+ for sublist in intersperse([',', ' '],
+ [[f'${x}'] for x in params.keys()]):
+ items += sublist
+ items += ['|', ' ', '{', '\n']
+ for entry in body:
+ items += [
+ ind(indent),
+ parse(entry, indent, context),
+ '\n',
+ ]
+ items += [ind(indent-1), '}']
+ return tag(items, 'lambda')
+
+
+def parse_resource(t: str, bodies: list[Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse resource form."""
+ match bodies:
+ case [body]:
items = [
parse(t, indent, context),
' ', '{', ' ',
@@ -665,10 +589,7 @@ def parse(form: Any, indent: int, context: list[str]) -> Markup:
]
return tag(items)
-
- # Resource instansiation with any number of instances
- case ['resource', {'type': t,
- 'bodies': bodies}]:
+ case bodies:
items = []
items += [
parse(t, indent, context),
@@ -714,227 +635,330 @@ def parse(form: Any, indent: int, context: list[str]) -> Markup:
items += ['\n', ind(indent), '}']
return tag(items)
- case ['resource-defaults', {'type': t,
- 'ops': ops}]:
- items = [
- parse(t, indent, context),
- ' ', '{', '\n',
- ]
- namelen = ops_namelen(ops)
- for op in ops:
- match op:
- case ['=>', key, value]:
- pad = namelen - len(key)
- items += [
- ind(indent+1),
- tag(key, 'parameter'),
- ' '*pad,
- ' ', operator('=>'), ' ',
- parse(value, indent+3, context),
- ',', '\n',
- ]
- case ['splat-hash', value]:
- pad = namelen - 1
- items += [
- ind(indent+1),
- tag('*', 'parameter', 'splat'),
- ' '*pad,
- ' ', operator('=>'), ' ',
- parse(value, indent+2, context),
- ',', '\n',
- ]
+def parse_resource_defaults(t: str, ops: Any,
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse resource defaults form."""
+ items = [
+ parse(t, indent, context),
+ ' ', '{', '\n',
+ ]
+ namelen = ops_namelen(ops)
+ for op in ops:
+ match op:
+ case ['=>', key, value]:
+ pad = namelen - len(key)
+ items += [
+ ind(indent+1),
+ tag(key, 'parameter'),
+ ' '*pad,
+ ' ', operator('=>'), ' ',
+ parse(value, indent+3, context),
+ ',', '\n',
+ ]
- case x:
- raise Exception('Unexpected item in resource defaults:', x)
+ case ['splat-hash', value]:
+ pad = namelen - 1
+ items += [
+ ind(indent+1),
+ tag('*', 'parameter', 'splat'),
+ ' '*pad,
+ ' ', operator('=>'), ' ',
+ parse(value, indent+2, context),
+ ',', '\n',
+ ]
- items += [ind(indent),
- '}']
+ case x:
+ raise Exception('Unexpected item in resource defaults:', x)
- return tag(items)
+ items += [ind(indent),
+ '}']
- case ['resource-override', {'resources': resources,
- 'ops': ops}]:
- items = [
- parse(resources, indent, context),
- ' ', '{', '\n',
- ]
+ return tag(items)
- namelen = ops_namelen(ops)
- for op in ops:
- match op:
- case ['=>', key, value]:
- pad = namelen - len(key)
- items += [
- ind(indent+1),
- tag(key, 'parameter'),
- ' '*pad,
- ' ', operator('=>'), ' ',
- parse(value, indent+3, context),
- ',', '\n',
- ]
- case ['+>', key, value]:
- pad = namelen - len(key)
- items += [
- ind(indent+1),
- tag(key, 'parameter'),
- ' '*pad,
- ' ', operator('+>'), ' ',
- parse(value, indent+2, context),
- ',', '\n',
- ]
+def parse_resource_override(resources: Any, ops: Any,
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse resoruce override form."""
+ items = [
+ parse(resources, indent, context),
+ ' ', '{', '\n',
+ ]
- case ['splat-hash', value]:
- pad = namelen - 1
- items += [
- ind(indent+1),
- tag('*', 'parameter', 'splat'),
- ' '*pad,
- ' ', operator('=>'), ' ',
- parse(value, indent+2, context),
- ',', '\n',
- ]
+ namelen = ops_namelen(ops)
+ for op in ops:
+ match op:
+ case ['=>', key, value]:
+ pad = namelen - len(key)
+ items += [
+ ind(indent+1),
+ tag(key, 'parameter'),
+ ' '*pad,
+ ' ', operator('=>'), ' ',
+ parse(value, indent+3, context),
+ ',', '\n',
+ ]
- case _:
- raise Exception('Unexpected item in resource override:',
- op)
+ case ['+>', key, value]:
+ pad = namelen - len(key)
+ items += [
+ ind(indent+1),
+ tag(key, 'parameter'),
+ ' '*pad,
+ ' ', operator('+>'), ' ',
+ parse(value, indent+2, context),
+ ',', '\n',
+ ]
- items += [
- ind(indent),
- '}',
- ]
+ case ['splat-hash', value]:
+ pad = namelen - 1
+ items += [
+ ind(indent+1),
+ tag('*', 'parameter', 'splat'),
+ ' '*pad,
+ ' ', operator('=>'), ' ',
+ parse(value, indent+2, context),
+ ',', '\n',
+ ]
- return tag(items)
+ case _:
+ raise Exception('Unexpected item in resource override:',
+ op)
- case ['unless', {'test': test,
- **rest}]:
- items = [
- keyword('unless'),
- ' ',
- parse(test, indent, context),
- ' ', '{', '\n',
- ]
+ items += [
+ ind(indent),
+ '}',
+ ]
- if 'then' in rest:
- for item in rest['then']:
- items += [
- ind(indent+1),
- parse(item, indent+1, context),
- '\n',
- ]
+ return tag(items)
+
+
+def parse_unless(test: Any, rest: dict[str, Any],
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse unless form."""
+ items: list[Markup] = [
+ keyword('unless'),
+ ' ',
+ parse(test, indent, context),
+ ' ', '{', '\n',
+ ]
+ if 'then' in rest:
+ for item in rest['then']:
items += [
- ind(indent),
- '}',
+ ind(indent+1),
+ parse(item, indent+1, context),
+ '\n',
]
- return tag(items)
- case ['var', x]:
- if context[0] == 'declaration':
- return declare_var(x)
- else:
- return print_var(x, True)
+ items += [
+ ind(indent),
+ '}',
+ ]
+ return tag(items)
- case ['virtual-query', q]:
- return tag([
- '<|', ' ',
- parse(q, indent, context),
- ' ', '|>',
- ])
- case ['virtual-query']:
- return tag(['<|', ' ', '|>'])
+def parse_operator(op: str, lhs: Any, rhs: Any,
+ *, indent: int, context: list[str]) -> Tag:
+ """Parse binary generic operator form."""
+ return tag([
+ parse(lhs, indent, context),
+ ' ', operator(op), ' ',
+ parse(rhs, indent, context),
+ ])
- case ['!', x]:
- return tag([
- operator('!'), ' ',
- parse(x, indent, context),
- ])
- case ['!=', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('!='), ' ',
- parse(b, indent, context),
- ])
+def parse(form: Any, indent: int, context: list[str]) -> Markup:
+ """
+ Print everything from a puppet parse tree.
- case ['+', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('+'), ' ',
- parse(b, indent, context),
- ])
+ :param from:
+ A puppet AST.
+ :param indent:
+ How many levels deep in indentation the code is.
+ Will get multiplied by the indentation width.
+ """
+ items: list[Markup]
+ # Sorted per `sort -V`
+ match form:
+ case None:
+ return tag('undef', 'literal', 'undef')
- case ['-', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('-'), ' ',
- parse(b, indent, context),
- ])
+ case True:
+ return tag('true', 'literal', 'true')
- case ['-', a]:
- return tag([
- operator('-'), ' ',
- parse(a, indent, context),
- ])
+ case False:
+ return tag('false', 'literal', 'false')
- case ['*', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('*'), ' ',
- parse(b, indent, context),
- ])
+ case ['access', how, *args]:
+ return parse_access(how, args, indent=indent, context=context)
- case ['%', a, b]:
+ case ['and', a, b]:
return tag([
parse(a, indent, context),
- ' ', operator('%'), ' ',
+ ' ', keyword('and'), ' ',
parse(b, indent, context),
])
- case ['<<', a, b]:
+ case ['array']:
+ return tag('[]', 'array')
+
+ case ['array', *items]:
+ return parse_array(items, indent=indent, context=context)
+
+ case ['call', {'functor': func, 'args': args}]:
+ return parse_call(func, args, indent=indent, context=context)
+
+ case ['call-method', func]:
+ return parse_call_method(func, indent=indent, context=context)
+
+ case ['case', test, forms]:
+ return parse_case(test, forms, indent=indent, context=context)
+
+ case ['class', {'name': name, **rest}]:
+ return parse_class(name, rest, indent=indent, context=context)
+
+ case ['concat', *args]:
+ return parse_concat(args, indent=indent, context=context)
+
+ case ['collect', {'type': t, 'query': q}]:
+ return tag([parse(t, indent, context),
+ ' ',
+ parse(q, indent, context)])
+
+ case ['default']:
+ return keyword('default')
+
+ case ['define', {'name': name, **rest}]:
+ return parse_define(name, rest, indent=indent, context=context)
+
+ case ['exported-query']:
+ return tag(['<<|', ' ', '|>>'])
+
+ case ['exported-query', arg]:
+ return tag(['<<|', ' ', parse(arg, indent, context), ' ', '|>>'])
+
+ case ['function', {'name': name, **rest}]:
+ return parse_function(name, rest, indent=indent, context=context)
+
+ case ['hash']:
+ return tag('{}', 'hash')
+
+ case ['hash', *hash]:
return tag([
- parse(a, indent, context),
- ' ', operator('<<'), ' ',
- parse(b, indent, context),
- ])
+ '{', '\n',
+ print_hash(hash, indent+1, context),
+ ind(indent),
+ '}',
+ ], 'hash')
+
+ # TODO a safe string to use?
+ # TODO extra options?
+ # Are all these already removed by the parser, requiring
+ # us to reverse parse the text?
+
+ # Parts can NEVER be empty, since that case wouldn't generate
+ # a concat element, but a "plain" text element
+ case ['heredoc', {'text': ['concat', *parts]}]:
+ return parse_heredoc_concat(parts, indent=indent, context=context)
+
+ case ['heredoc', {'text': ''}]:
+ return tag(['@(EOF)', '\n', ind(indent), '|', ' ', 'EOF'],
+ 'heredoc', 'literal')
+
+ case ['heredoc', {'text': text}]:
+ return parse_heredoc_text(text, indent=indent, context=context)
+
+ case ['if', {'test': test, **rest}]:
+ return parse_if(test, rest, indent=indent, context=context)
- case ['>>', a, b]:
+ case ['in', needle, stack]:
return tag([
- parse(a, indent, context),
- ' ', operator('>>'), ' ',
- parse(b, indent, context),
+ parse(needle, indent, context),
+ ' ', keyword('in'), ' ',
+ parse(stack, indent, context),
])
- case ['>=', a, b]:
+ case ['invoke', {'functor': func, 'args': args}]:
+ return parse_invoke(func, args, indent=indent, context=context)
+
+ case ['nop']:
+ return tag('', 'nop')
+
+ case ['lambda', {'params': params, 'body': body}]:
+ return parse_lambda(params, body, indent=indent, context=context)
+
+ case ['or', a, b]:
return tag([
parse(a, indent, context),
- ' ', operator('>='), ' ',
+ ' ', keyword('or'), ' ',
parse(b, indent, context),
])
- case ['<=', a, b]:
+ case ['paren', *forms]:
return tag([
- parse(a, indent, context),
- ' ', operator('<='), ' ',
- parse(b, indent, context),
- ])
+ '(',
+ *(parse(form, indent+1, context)
+ for form in forms),
+ ')',
+ ], 'paren')
+
+ # Qualified name?
+ case ['qn', x]:
+ return tag(x, 'qn')
- case ['>', a, b]:
+ # Qualified resource?
+ case ['qr', x]:
+ return tag(x, 'qr')
+
+ case ['regexp', s]:
+ return tag(['/', tag(s, 'regex-body'), '/'], 'regex')
+
+ # Resource instansiation with exactly one instance
+ case ['resource', {'type': t, 'bodies': [body]}]:
+ return parse_resource(t, [body], indent=indent, context=context)
+
+ # Resource instansiation with any number of instances
+ case ['resource', {'type': t, 'bodies': bodies}]:
+ return parse_resource(t, bodies, indent=indent, context=context)
+
+ case ['resource-defaults', {'type': t, 'ops': ops}]:
+ return parse_resource_defaults(t, ops, indent=indent, context=context)
+
+ case ['resource-override', {'resources': resources, 'ops': ops}]:
+ return parse_resource_override(resources, ops, indent=indent, context=context)
+
+ case ['unless', {'test': test, **rest}]:
+ return parse_unless(test, rest, indent=indent, context=context)
+
+ case ['var', x]:
+ if context[0] == 'declaration':
+ return declare_var(x)
+ else:
+ return print_var(x, True)
+
+ case ['virtual-query', q]:
+ return tag(['<|', ' ', parse(q, indent, context), ' ', '|>', ])
+
+ case ['virtual-query']:
+ return tag(['<|', ' ', '|>'])
+
+ case ['!', x]:
return tag([
- parse(a, indent, context),
- ' ', operator('>'), ' ',
- parse(b, indent, context),
+ operator('!'), ' ',
+ parse(x, indent, context),
])
- case ['<', a, b]:
+ case ['-', a]:
return tag([
+ operator('-'), ' ',
parse(a, indent, context),
- ' ', operator('<'), ' ',
- parse(b, indent, context),
])
+ case [('!=' | '+' | '-' | '*' | '%' | '<<' | '>>' | '>=' | '<=' | '>' | '<' | '/' | '==' | '=~' | '!~') as op, # noqa: E501
+ a, b]:
+ return parse_operator(op, a, b, indent=indent, context=context)
+
case ['~>', left, right]:
return tag([
parse(left, indent, context),
@@ -962,13 +986,6 @@ def parse(form: Any, indent: int, context: list[str]) -> Markup:
parse(right, indent+1, context),
])
- case ['/', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('/'), ' ',
- parse(b, indent, context),
- ])
-
case ['=', field, value]:
return tag([
parse(field, indent, ['declaration'] + context),
@@ -976,27 +993,6 @@ def parse(form: Any, indent: int, context: list[str]) -> Markup:
parse(value, indent, context),
], 'declaration')
- case ['==', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('=='), ' ',
- parse(b, indent, context),
- ])
-
- case ['=~', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('=~'), ' ',
- parse(b, indent, context),
- ])
-
- case ['!~', a, b]:
- return tag([
- parse(a, indent, context),
- ' ', operator('!~'), ' ',
- parse(b, indent, context),
- ])
-
case ['?', condition, cases]:
return tag([
parse(condition, indent, context),
diff --git a/mypy.ini b/mypy.ini
index 7c7a251..0d2369b 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,7 +1,4 @@
[mypy]
-# Disabled since `match` breaks it.
-disable_error_code = used-before-def
-
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True