From a49775799eac54db95d068b7517f1f7c419baa50 Mon Sep 17 00:00:00 2001 From: luxin Date: Thu, 30 Dec 2021 18:06:19 +0800 Subject: [PATCH] add keywords_must and keywords_forbid --- .../config.json" | 79 ++++++++ .../config.json" | 8 + .../config.json" | 6 + .../config.json" | 6 + .../databases.json" | 3 +- .../config.json" | 4 +- .../config.json" | 6 + .../config.json" | 6 + data/config.json | 4 +- data/tree.json | 101 +++++++++- src/__pycache__/tree.cpython-38.pyc | Bin 8660 -> 11876 bytes src/tree.py | 177 +++++++++++++++--- 12 files changed, 365 insertions(+), 35 deletions(-) create mode 100644 "data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/1.Neo4J\347\256\200\344\273\213/config.json" create mode 100644 "data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/2.\345\256\211\350\243\205\345\222\214\345\220\257\345\212\250/config.json" create mode 100644 "data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/config.json" create mode 100644 "data/1.Neo4J\345\210\235\351\230\266/config.json" create mode 100644 "data/2.Neo4J\344\270\255\351\230\266/config.json" create mode 100644 "data/3.Neo4J\351\253\230\351\230\266/config.json" diff --git "a/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/1.Neo4J\347\256\200\344\273\213/config.json" "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/1.Neo4J\347\256\200\344\273\213/config.json" new file mode 100644 index 0000000..9b56882 --- /dev/null +++ "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/1.Neo4J\347\256\200\344\273\213/config.json" @@ -0,0 +1,79 @@ +{ + "node_id": "neo4j-0261ccb903994df281a2ec606b5d8c9e", + "keywords": [], + "children": [ + { + "什么是图数据库": { + "keywords": [ + "图数据库" + ], + "children": [ + { + "图论": { + "keywords": [ + "节点", + "边", + "关系" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-3250e8d6f7fb417c83a8f007fc842a83" + } + }, + { + "RDF": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-80467f91b8454c029ac268ec80d934ec" + } + }, + { + "属性图": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-7e028f10298f4fd5ae055a8f05e375cb" + } + }, + { + "原生图": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-c462f5300d4740148067a2250766ef90" + } + } + ], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-013ee17e238c4c51b3988ba109d1fc3d" + }, + "什么时候需要图数据库": { + "keywords": [ + "图数据库" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [] + }, + "Neo4J图数据库概览": { + "keywords": [ + "图数据库" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [] + } + } + ], + "export": [ + "helloworld.json" + ], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git "a/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/2.\345\256\211\350\243\205\345\222\214\345\220\257\345\212\250/config.json" "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/2.\345\256\211\350\243\205\345\222\214\345\220\257\345\212\250/config.json" new file mode 100644 index 0000000..1e73f81 --- /dev/null +++ "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/2.\345\256\211\350\243\205\345\222\214\345\220\257\345\212\250/config.json" @@ -0,0 +1,8 @@ +{ + "node_id": "neo4j-0ee8cb8ccd6f4a59bc20f9ccbf7d627e", + "keywords": [], + "children": [], + "export": [], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git "a/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/config.json" "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/config.json" new file mode 100644 index 0000000..eff0813 --- /dev/null +++ "b/data/1.Neo4J\345\210\235\351\230\266/1.\351\242\204\345\244\207\347\237\245\350\257\206/config.json" @@ -0,0 +1,6 @@ +{ + "node_id": "neo4j-a42252d5f8c24548bde127a385850a76", + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git "a/data/1.Neo4J\345\210\235\351\230\266/config.json" "b/data/1.Neo4J\345\210\235\351\230\266/config.json" new file mode 100644 index 0000000..249c185 --- /dev/null +++ "b/data/1.Neo4J\345\210\235\351\230\266/config.json" @@ -0,0 +1,6 @@ +{ + "node_id": "neo4j-5e171793d38e49e784f544a9f80d09cb", + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git "a/data/1.Neo4j \345\210\235\351\230\266/1.\345\233\276\346\225\260\346\215\256\345\272\223\345\237\272\346\234\254\346\246\202\345\277\265/1.\346\225\260\346\215\256\345\272\223\347\232\204\345\210\206\347\261\273\344\270\200\350\247\210/databases.json" "b/data/1.Neo4j \345\210\235\351\230\266/1.\345\233\276\346\225\260\346\215\256\345\272\223\345\237\272\346\234\254\346\246\202\345\277\265/1.\346\225\260\346\215\256\345\272\223\347\232\204\345\210\206\347\261\273\344\270\200\350\247\210/databases.json" index de9a67e..03a71cc 100644 --- "a/data/1.Neo4j \345\210\235\351\230\266/1.\345\233\276\346\225\260\346\215\256\345\272\223\345\237\272\346\234\254\346\246\202\345\277\265/1.\346\225\260\346\215\256\345\272\223\347\232\204\345\210\206\347\261\273\344\270\200\350\247\210/databases.json" +++ "b/data/1.Neo4j \345\210\235\351\230\266/1.\345\233\276\346\225\260\346\215\256\345\272\223\345\237\272\346\234\254\346\246\202\345\277\265/1.\346\225\260\346\215\256\345\272\223\347\232\204\345\210\206\347\261\273\344\270\200\350\247\210/databases.json" @@ -2,5 +2,6 @@ "type": "code_options", "author": "shiny", "source": "databases.md", - "notebook_enable": false + "notebook_enable": false, + "exercise_id": "d22f8fb9ed0d4afebe55d5b62abb7808" } \ No newline at end of file diff --git "a/data/1.Neo4j \345\210\235\351\230\266/2.Neo4j \347\211\210\346\234\254\345\222\214\345\256\211\350\243\205/config.json" "b/data/1.Neo4j \345\210\235\351\230\266/2.Neo4j \347\211\210\346\234\254\345\222\214\345\256\211\350\243\205/config.json" index b7332d8..0ca394c 100644 --- "a/data/1.Neo4j \345\210\235\351\230\266/2.Neo4j \347\211\210\346\234\254\345\222\214\345\256\211\350\243\205/config.json" +++ "b/data/1.Neo4j \345\210\235\351\230\266/2.Neo4j \347\211\210\346\234\254\345\222\214\345\256\211\350\243\205/config.json" @@ -1,4 +1,6 @@ { "node_id": "neo4j-9ec466f015f9422dab2b6b05f0581a8b", - "keywords": [] + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] } \ No newline at end of file diff --git "a/data/2.Neo4J\344\270\255\351\230\266/config.json" "b/data/2.Neo4J\344\270\255\351\230\266/config.json" new file mode 100644 index 0000000..9afd708 --- /dev/null +++ "b/data/2.Neo4J\344\270\255\351\230\266/config.json" @@ -0,0 +1,6 @@ +{ + "node_id": "neo4j-298b201de8044453a2d6e8d02e64962d", + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git "a/data/3.Neo4J\351\253\230\351\230\266/config.json" "b/data/3.Neo4J\351\253\230\351\230\266/config.json" new file mode 100644 index 0000000..23d1fc3 --- /dev/null +++ "b/data/3.Neo4J\351\253\230\351\230\266/config.json" @@ -0,0 +1,6 @@ +{ + "node_id": "neo4j-b05c040c3bfe49e29f17397e9e16c7d4", + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] +} \ No newline at end of file diff --git a/data/config.json b/data/config.json index 23f7316..3af2366 100644 --- a/data/config.json +++ b/data/config.json @@ -1,5 +1,7 @@ { "tree_name": "neo4j", "keywords": ["图数据库", "Neo4j", "图数据平台", "图机器学习", "图算法", "NoSQL", "Graph", "Graph Database", "Graph Data Science", "GDS"], - "node_id": "neo4j-50ecfa9d2d0f4012ae80a3656c0756ab" + "node_id": "neo4j-50ecfa9d2d0f4012ae80a3656c0756ab", + "keywords_must": [], + "keywords_forbid": [] } \ No newline at end of file diff --git a/data/tree.json b/data/tree.json index e7b59b0..ca6e952 100644 --- a/data/tree.json +++ b/data/tree.json @@ -17,43 +17,128 @@ "Neo4j简介": { "node_id": "neo4j-0261ccb903994df281a2ec606b5d8c9e", "keywords": [], - "children": [] + "children": [ + { + "什么是图数据库": { + "keywords": [ + "图数据库" + ], + "children": [ + { + "图论": { + "keywords": [ + "节点", + "边", + "关系" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-3250e8d6f7fb417c83a8f007fc842a83" + } + }, + { + "RDF": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-80467f91b8454c029ac268ec80d934ec" + } + }, + { + "属性图": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-7e028f10298f4fd5ae055a8f05e375cb" + } + }, + { + "原生图": { + "keywords": [], + "children": [], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-c462f5300d4740148067a2250766ef90" + } + } + ], + "keywords_must": [], + "keywords_forbid": [], + "node_id": "neo4j-013ee17e238c4c51b3988ba109d1fc3d" + }, + "什么时候需要图数据库": { + "keywords": [ + "图数据库" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [] + }, + "Neo4J图数据库概览": { + "keywords": [ + "图数据库" + ], + "children": [], + "keywords_must": [], + "keywords_forbid": [] + } + } + ], + "keywords_must": [], + "keywords_forbid": [] } }, { "安装和启动": { "node_id": "neo4j-0ee8cb8ccd6f4a59bc20f9ccbf7d627e", "keywords": [], - "children": [] + "children": [], + "keywords_must": [], + "keywords_forbid": [] } } - ] + ], + "keywords_must": [], + "keywords_forbid": [] } }, { "Cypher查询语言": { "node_id": "neo4j-9ec466f015f9422dab2b6b05f0581a8b", "keywords": [], - "children": [] + "children": [], + "keywords_must": [], + "keywords_forbid": [] } } - ] + ], + "keywords_must": [], + "keywords_forbid": [] } }, { "Neo4j中阶": { "node_id": "neo4j-298b201de8044453a2d6e8d02e64962d", "keywords": [], - "children": [] + "children": [], + "keywords_must": [], + "keywords_forbid": [] } }, { "Neo4j高阶": { "node_id": "neo4j-b05c040c3bfe49e29f17397e9e16c7d4", "keywords": [], - "children": [] + "children": [], + "keywords_must": [], + "keywords_forbid": [] } } - ] + ], + "keywords_must": [], + "keywords_forbid": [] } } \ No newline at end of file diff --git a/src/__pycache__/tree.cpython-38.pyc b/src/__pycache__/tree.cpython-38.pyc index e6ddaf0acdad3e350135a78e16c129eec5f45305..8cd64f08030a69d2be732d675b6f5e909a1e15a3 100644 GIT binary patch literal 11876 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 O#NTkNLJ7126@L%Dl6(XJ diff --git a/src/tree.py b/src/tree.py index 95e1cbc..0c58d32 100644 --- a/src/tree.py +++ b/src/tree.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- -import logging -from genericpath import exists import json +import logging import os -import uuid +import re +import subprocess import sys +import uuid import re id_set = set() @@ -16,8 +16,29 @@ handler.setFormatter(formatter) logger.addHandler(handler) +def search_author(author_dict, username): + for key in author_dict: + names = author_dict[key] + if username in names: + return key + return username + + +def user_name(md_file, author_dict): + ret = subprocess.Popen([ + "git", "log", md_file + ], stdout=subprocess.PIPE) + lines = list(map(lambda l: l.decode(), ret.stdout.readlines())) + author_lines = [] + for line in lines: + if line.startswith('Author'): + author_lines.append(line.split(' ')[1]) + author_nick_name = author_lines[-1] + return search_author(author_dict, author_nick_name) + + def load_json(p): - with open(p, 'r', encoding='utf-8') as f: + with open(p, 'r', encoding="utf-8") as f: return json.loads(f.read()) @@ -30,7 +51,7 @@ def dump_json(p, j, exist_ok=False, override=False): logger.error(f"{p} already exist") sys.exit(0) - with open(p, 'w+', encoding='utf-8') as f: + with open(p, 'w+', encoding="utf8") as f: f.write(json.dumps(j, indent=2, ensure_ascii=False)) @@ -72,7 +93,18 @@ def check_export(base, cfg): class TreeWalker: - def __init__(self, root, tree_name, title=None, log=None): + def __init__( + self, root, + tree_name, + title=None, + log=None, + authors=None, + enable_notebook=None, + ignore_keywords=False + ): + self.ignore_keywords = ignore_keywords + self.authors = authors if authors else {} + self.enable_notebook = enable_notebook self.name = tree_name self.root = root self.title = tree_name if title is None else title @@ -84,7 +116,9 @@ class TreeWalker: root_node = { "node_id": root["node_id"], "keywords": root["keywords"], - "children": [] + "children": [], + "keywords_must": root["keywords_must"], + "keywords_forbid": root["keywords_forbid"] } self.tree[root["tree_name"]] = root_node self.load_levels(root_node) @@ -92,25 +126,31 @@ class TreeWalker: for index, level in enumerate(root_node["children"]): level_title = list(level.keys())[0] level_node = list(level.values())[0] - level_path = os.path.join(self.root, f"{index+1}.{level_title}") + level_path = os.path.join(self.root, f"{index + 1}.{level_title}") self.load_chapters(level_path, level_node) for index, chapter in enumerate(level_node["children"]): chapter_title = list(chapter.keys())[0] chapter_node = list(chapter.values())[0] chapter_path = os.path.join( - level_path, f"{index+1}.{chapter_title}") + level_path, f"{index + 1}.{chapter_title}") self.load_sections(chapter_path, chapter_node) for index, section_node in enumerate(chapter_node["children"]): section_title = list(section_node.keys())[0] full_path = os.path.join( - chapter_path, f"{index}.{section_title}") + chapter_path, f"{index + 1}.{section_title}") if os.path.isdir(full_path): + self.check_section_keywords(full_path) self.ensure_exercises(full_path) tree_path = os.path.join(self.root, "tree.json") dump_json(tree_path, self.tree, exist_ok=True, override=True) return self.tree + def sort_dir_list(self, dirs): + result = [self.extract_node_env(dir) for dir in dirs] + result.sort(key=lambda item: item[0]) + return result + def load_levels(self, root_node): levels = [] for level in os.listdir(self.root): @@ -133,6 +173,8 @@ class TreeWalker: "node_id": config["node_id"], "keywords": config["keywords"], "children": [], + "keywords_must": config["keywords_must"], + "keywords_forbid": config["keywords_forbid"] } } @@ -167,7 +209,7 @@ class TreeWalker: for index, [number, element] in enumerate(children): title = list(element.keys())[0] origin = os.path.join(base, f"{number}.{title}") - posted = os.path.join(base, f"{index+1}.{title}") + posted = os.path.join(base, f"{index + 1}.{title}") if origin != posted: self.logger.info(f"rename [{origin}] to [{posted}]") os.rename(origin, posted) @@ -184,6 +226,8 @@ class TreeWalker: "tree_name": self.name, "keywords": [], "node_id": self.gen_node_id(), + "keywords_must": [], + "keywords_forbid": [] } dump_json(config_path, config, exist_ok=True, override=True) else: @@ -213,7 +257,9 @@ class TreeWalker: if not os.path.exists(config_path): config = { "node_id": self.gen_node_id(), - "keywords": [] + "keywords": [], + "keywords_must": [], + "keywords_forbid": [] } dump_json(config_path, config, exist_ok=True, override=True) else: @@ -237,15 +283,25 @@ class TreeWalker: config = load_json(config_path) flag, result = self.ensure_node_id(config) if flag: - dump_json(config_path, config, exist_ok=True, override=True) + dump_json(config_path, result, exist_ok=True, override=True) return config def ensure_node_id(self, config): - if "node_id" not in config: - config["node_id"] = self.gen_node_id() - return True, config - else: - return False, config + flag = False + if "node_id" not in config or \ + not config["node_id"].startswith(f"{self.name}-") or \ + config["node_id"] in id_set: + new_id = self.gen_node_id() + id_set.add(new_id) + config["node_id"] = new_id + flag = True + + for child in config.get("children", []): + child_node = list(child.values())[0] + f, _ = self.ensure_node_id(child_node) + flag = flag or f + + return flag, config def gen_node_id(self): return f"{self.name}-{uuid.uuid4().hex}" @@ -258,7 +314,8 @@ class TreeWalker: return int(number), title except Exception as error: self.logger.error(f"目录 [{path}] 解析失败,结构不合法,可能是缺少序号") - sys.exit(1) + # sys.exit(1) + raise error def load_chapter_node(self, full_name): config = self.ensure_chapter_config(full_name) @@ -268,6 +325,8 @@ class TreeWalker: "node_id": config["node_id"], "keywords": config["keywords"], "children": [], + "keywords_must": config["keywords_must"], + "keywords_forbid": config["keywords_forbid"] } } return num, result @@ -279,7 +338,9 @@ class TreeWalker: name: { "node_id": config["node_id"], "keywords": config["keywords"], - "children": config.get("children", []) + "children": config.get("children", []), + "keywords_must": config["keywords_must"], + "keywords_forbid": config["keywords_forbid"] } } # if "children" in config: @@ -288,9 +349,77 @@ class TreeWalker: def ensure_exercises(self, section_path): config = self.ensure_section_config(section_path) + flag = False + for e in os.listdir(section_path): + base, ext = os.path.splitext(e) + _, source = os.path.split(e) + if ext != ".md": + continue + mfile = base + ".json" + meta_path = os.path.join(section_path, mfile) + md_file = os.path.join(section_path, e) + self.ensure_exercises_meta(meta_path, source, md_file) + export = config.get("export", []) + if mfile not in export and self.name != "algorithm": + export.append(mfile) + flag = True + config["export"] = export + + if flag: + dump_json(os.path.join(section_path, "config.json"), + config, True, True) + for e in config.get("export", []): full_name = os.path.join(section_path, e) exercise = load_json(full_name) - if "exercise_id" not in exercise: - exercise["exercise_id"] = uuid.uuid4().hex - dump_json(full_name, exercise) + if "exercise_id" not in exercise or exercise.get("exercise_id") in id_set: + eid = uuid.uuid4().hex + exercise["exercise_id"] = eid + dump_json(full_name, exercise, True, True) + else: + id_set.add(exercise["exercise_id"]) + + def ensure_exercises_meta(self, meta_path, source, md_file): + _, mfile = os.path.split(meta_path) + meta = None + if os.path.exists(meta_path): + with open(meta_path) as f: + content = f.read() + if content: + meta = json.loads(content) + if "exercise_id" not in meta: + meta["exercise_id"] = uuid.uuid4().hex + if "notebook_enable" not in meta: + meta["notebook_enable"] = self.default_notebook() + if "source" not in meta: + meta["source"] = source + if "author" not in meta: + meta["author"] = user_name(md_file, self.authors) + if "type" not in meta: + meta["type"] = "code_options" + + if meta is None: + meta = { + "type": "code_options", + "author": user_name(md_file, self.authors), + "source": source, + "notebook_enable": self.default_notebook(), + "exercise_id": uuid.uuid4().hex + } + dump_json(meta_path, meta, True, True) + + def default_notebook(self): + if self.enable_notebook is not None: + return self.enable_notebook + if self.name in ["python", "java", "c"]: + return True + else: + return False + + def check_section_keywords(self, full_path): + if self.ignore_keywords: + return + config = self.ensure_section_config(full_path) + if not config.get("keywords", []): + self.logger.error(f"节点 [{full_path}] 的关键字为空,请修改配置文件写入关键字") + sys.exit(1) -- GitLab