diff --git a/README.md b/README.md index 9728343..16b06b7 100644 --- a/README.md +++ b/README.md @@ -169,10 +169,12 @@ orca: range: from: 9026 to: 9042 - template: LocalForward ${port} localhost:${port} + property: LocalForward + template: ${port} localhost:${port} - variable: env range: ["FOO=bar", "BAR=foo"] - template: SendEnv ${env} + property: SendEnv + template: ${env} ``` Expands to: diff --git a/src/main.py b/src/main.py index 73e1fea..121204c 100644 --- a/src/main.py +++ b/src/main.py @@ -6,7 +6,7 @@ from pathlib import Path parser = argparse.ArgumentParser(description="Process some integers.") parser.add_argument("file", type=Path, help="yaml config") -parser.add_argument("--json", action="store_true", help="output as json") +parser.add_argument("--json", action="store_true", help="output parse as json") parser.add_argument("--out", type=Path, help="write into file") args = parser.parse_args() @@ -66,23 +66,57 @@ def expand_for_loop(props): return expand -def get_ssh_props(data, hosts): - props = list() - - if data.get('template'): - props = list(hosts[data.get('template')]) +def get_ssh_props(data): + props = dict() for k, v in data["ssh_props"].items(): - if isinstance(v, list): - for x in v: - props.append(f"{k} {x}") - else: - props.append(f"{k} {v}") + props[k] = v if isinstance(v, list) else [v] return props -def remove_templates(expanded, data): +def parse_props(host, props, hosts): + parsed = dict() + + # Standard ssh properties + for k, v in props.get("ssh_props", {}).items(): + parsed[k] = v if isinstance(v, list) else [v] + + # For-loops in config + for loop in props.get("for", []): + prop = loop["property"] + r = loop["range"] + + if isinstance(r, dict): + iterations = range(r["from"], r["to"]) + elif isinstance(r, list): + iterations = r + else: + raise TypeError("Expected list or dict in `for` loop `range`") + + if not parsed.get(prop): + parsed[prop] = list() + + pattern = re.compile(rf'\$\{{{loop["variable"]}\}}') + + for i in iterations: + sub = pattern.sub(str(i), loop["template"]) + parsed[prop].append(sub) + + # Inherit from template + template = props.get("template") + + if template and template not in hosts: + raise SyntaxError(f"Template `{template}` required by `{host}` is not declared above `{host}` in the yaml") + elif template: + for k, v in hosts[props.get("template")].items(): + if k not in parsed: + parsed[k] = v + + return parsed + + +def remove_templates(expanded): hosts = dict(expanded) for host, props in data.items(): @@ -109,21 +143,14 @@ def stringify_booleans(props): def parse_yaml(data): - expanded = dict() + hosts = dict() check_duplicate_hosts(data) for host, props in data.items(): - parsed = list() - parsed.extend(get_ssh_props(props, expanded)) - parsed.extend(expand_for_loop(props)) + hosts[host] = parse_props(host, props, hosts) - parsed = stringify_booleans(parsed) - run_assertions(host, parsed) - - expanded[host] = sorted(parsed) - - expanded = remove_templates(expanded, data) - return expanded + hosts = remove_templates(hosts) + return hosts # ╔───────────────────────────────────────────────────────────────────────────╗ @@ -134,8 +161,12 @@ def to_ssh_config_string(parse): for host, keys in parse.items(): s += f"Host {host}\n" - for key in keys: - s += f"\t{key}\n" + for key, values in keys.items(): + for value in values: + if isinstance(value, bool): + s += f"\t{key} {'true' if value else 'no'}\n" + else: + s += f"\t{key} {value}\n" s += "\n" return s