from genericpath import exists import json import os import uuid import sys import re def load_json(p): with open(p, 'r') as f: return json.loads(f.read()) def dump_json(p, j, exist_ok=False, override=False): if os.path.exists(p): if exist_ok: if not override: return else: print(f"{p} already exist") sys.exit(0) with open(p, 'w') as f: f.write(json.dumps(j, indent=2, ensure_ascii=False)) def parse_no_name(d): p = r'(\d+)\.(.*)' m = re.search(p, d) try: no = int(m.group(1)) dir_name = m.group(2) except: sys.exit(0) return no, dir_name def gen_tree(data_path): root = {} def gen_node_id(): return ''.join(str(uuid.uuid5(uuid.NAMESPACE_URL, 'skill_tree')).split('-')) def list_dir(p): v = os.listdir(p) v.sort() for no_name in v: no_dir = os.path.join(p, no_name) if os.path.isdir(no_dir): yield no_dir, no_name def ensure_node_id(cfg_path, cfg): if cfg.get('node_id') is None: cfg['node_id'] = gen_node_id() dump_json(cfg_path, cfg, exist_ok=True, override=True) def make_node(name, node_id, keywords, children=None): node = {} node_children = children or [] node[name] = { 'node_id': node_id, 'keywords': keywords, 'children': node_children } return node, node_children # 根节点 cfg_path = os.path.join(data_path, 'config.json') cfg = load_json(cfg_path) ensure_node_id(cfg_path, cfg) tree_node = { "node_id": cfg['node_id'], "keywords": cfg['keywords'], "children": [] } root[cfg['tree_name']] = tree_node # 难度节点 for level_no_dir, level_no_name in list_dir(data_path): print(level_no_dir) no, level_name = parse_no_name(level_no_name) cfg_path = os.path.join(level_no_dir, 'config.json') cfg = load_json(cfg_path) ensure_node_id(cfg_path, cfg) level_node, level_node_children = make_node( level_name, cfg['node_id'], cfg['keywords']) tree_node['children'].append(level_node) # 章节点 for chapter_no_dir, chapter_no_name in list_dir(level_no_dir): no, chapter_name = parse_no_name(chapter_no_name) cfg_path = os.path.join(chapter_no_dir, 'config.json') ensure_node_id(cfg_path, cfg) cfg = load_json(cfg_path) chapter_node, chapter_node_children = make_node( chapter_name, cfg['node_id'], cfg['keywords']) level_node_children.append(chapter_node) # 知识点 for section_no_dir, section_no_name in list_dir(chapter_no_dir): no, section_name = parse_no_name(section_no_name) cfg_path = os.path.join(section_no_dir, 'config.json') ensure_node_id(cfg_path, cfg) cfg = load_json(cfg_path) section_node, section_node_children = make_node( section_name, cfg['node_id'], cfg['keywords'], cfg['children']) chapter_node_children.append(section_node) # 确保习题分配了习题ID for export in cfg['export']: if export.get('exercise_id') is None: export['exercise_id'] = gen_node_id() dump_json(cfg_path, cfg, exist_ok=True, override=True) # 保存技能树骨架 tree_path = os.path.join(data_path, 'tree.json') dump_json(tree_path, root, exist_ok=True, override=True)