#!/usr/bin/env python3 import json import os from typing import Iterable class Cookbook(object): recipes: dict def __init__(self, root: str): self.recipes = dict() for dirname, dirs, files in os.walk(root): for f in files: path = os.path.join(dirname, f) name, _ = os.path.splitext(f) with open(path, 'r') as src: variations = json.load(src) self.recipes[name] = variations def __getitem__(self, item: str): return self.recipes[item] def all(self): return self.recipes.keys() def is_component(self, name): return 'component' in self.recipes[name]['type'] def components(self): found = set() for target, methods in self.recipes.items(): for variation in methods: for need, _ in variation['input'].items(): if need in found: continue found.add(need) if not self.is_component(need): continue yield need def graph(recipes: dict, targets: Iterable[str]): print("digraph G {") seen = set() remain = set(targets) while remain: output = remain.pop() for need in recipes[output][0]['input']: print(f"{need} -> {output}") if need not in seen: remain.add(need) seen.add(output) print("}") def graph_all(recipes: dict) -> None: graph(recipes, recipes.all()) def graph_one(recipes: dict, target: str) -> None: graph(recipes, [target]) if __name__ == '__main__': def main(): import argparse root = os.path.dirname(__file__) recipe_root = os.path.join(root, 'data', 'recipes') parser = argparse.ArgumentParser() parser.add_argument('--data', type=str, default=recipe_root) parser.add_argument('--target', type=str, required=False) args = parser.parse_args() recipes = Cookbook(args.data) if args.target: graph_one(recipes, args.target) else: graph(recipes, recipes.components()) main()