diff --git a/README.md b/README.md index 9b8bbe526dfc3f5e867086c9c99a1be90d13b9c1..e22059b8a056dfb1d29a541188af488df1d31768 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ pip install -r requirements.txt { // ... "export": [ - // TODO ... + "hello.sh" ] } ``` @@ -74,137 +74,17 @@ pip install -r requirements.txt ## `知识节点` 的导出习题选项配置编辑 -首先,在知识节点下增加一个习题代码,例如在 `data/1.toolchains初阶/1.git/4.push修改到远程创库` 下增加一个`HelloWord.sh`代码: - -```console -$ git add README.txt -$ git commit -m "first modify README.txt" -$ git push origin master -``` - -其次,增加一个同名的选项配置文件`HelloWord.json`,目前有三种配置规则 - -**单行替换规则**: - -* 配置由`one_line`字段指定的单行替换字典 -* 格式是:`"<源字符串>"`: [`"<替换字符串A>"`, `<替换字符串B>`,...], - * 其中每个 `"<源字符串>"` `/` `"<替换字符串A>"` 被生成为是一个替换选项 - * 指定的配置应该能至少生成 `3+` 个替换选项 - -```json -{ - "one_line": { - "git add": ["git put", "git in", "git checkout"] - } -} -``` - -上面的替换规则会将代码替换成 3 个变种的代码: - -```console -// 变种代码1 -$ git put README.txt -$ git commit -m "first modify README.txt" -$ git push origin master -``` - -```console -// 变种代码2 -$ git in README.txt -$ git commit -m "first modify README.txt" -$ git push origin master -``` - -```html -// 变种代码3 -$ git checkout README.txt -$ git commit -m "first modify README.txt" -$ git push origin master -``` - -这些变种代码将会作为技能树该知识点该代码选择题的选项。 - -**多行替换规则**: - -* 配置由`multiline`字段指定的多行替换数组 -* 数组的每个元素是一组替换规则,会整组被替换 - -例如: - -```json -{ - "mulitiline": [{ - "git add": "git put", - "git commit -m": "git commit -a" - }, - { - "git commit": "git base", - "git push": "git pull" - }] -} -``` - -同样,该配置将支持将源代码生成2个变种代码 - -```console -// 变种代码1 -$ git put README.txt -$ git commit -a "first modify README.txt" -$ git push origin master -``` - -```console -// 变种代码2, 注意第2组替换规则,包含了两行替换 -$ git add README.txt -$ git base -m "first modify README.txt" -$ git pull origin master -``` - -## 预制的替换规则 - - * 配置由 `prepared` 字段制定的预制文件数组 - * 数组每一个元素是一个预制的代码文件的路径文件名 - - 例如: - -```json -{ - "prepared": [ - "HelloWord.1.sh", - "HelloWord.2.sh", - "HelloWord.3.sh"] -} -``` - -同样,该配置将支持将源代码生成3个变种代码 - -```console -// HelloWord.1.sh -$ git put README.txt -$ git commit -a "first modify README.txt" -$ git push origin master -``` - -```console -$ git add README.txt -$ git base -m "first modify README.txt" -$ git pull origin master -``` - -## 使用 markdown 编写习题 - -如前内容,我们在知识节点下增加一个习题配置,例如在 `data/1.toolchains初阶/1.git/4.push修改到远程创库` 下增加一个`HelloWorld.json`代码: +如前内容,我们在知识节点下增加一个习题定义,例如在 `data/1.toolchains初阶/1.git/4.push修改到远程创库` 下增加一个`hello.json`代码: ```json { "type": "code_options", "author": "刘鑫", - "source": "HelloWorld.md", - "exercise_id":"1190bb7834904da0b1f20915960714d5", - "notebook_enable": true + "source": "hello.md", + "notebook_enable": false } ``` -其中 type 字段目前都固定是 `code_options`。exercise_id 可以不写,处理程序会自动填补这个数据。根据具体情况写好其它字段,注意这里 source 的文件名,我们指定了一个 markdwon 文件。现在我们新建一个 HelloWorld.md 并编辑为: +其中 type 字段目前都固定是 `code_options`。根据具体情况写好其它字段,注意这里 source 的文件名,我们指定了一个 markdwon 文件。现在我们新建一个 HelloWorld.md 并编辑为: ````markdown # Hello World @@ -222,7 +102,7 @@ $ git push origin master ## 选项 -### A +### add 子命令错误 ```console // HelloWord.1.sh @@ -231,7 +111,7 @@ $ git commit -a "first modify README.txt" $ git push origin master ``` -### B +### commit 子命令错误 ```console $ git add README.txt @@ -239,6 +119,13 @@ $ git base -m "first modify README.txt" $ git pull origin master ``` +### push 子命令错误 + +```console +$ git add README.txt +$ git commit -m "first modify README.txt" +$ git pull origin master +``` ```` @@ -256,13 +143,13 @@ $ git pull origin master ### before -```console +```shell echo "do something before" ``` ### after -```console +```shell echo "do something after" ``` ```` @@ -286,9 +173,6 @@ $ $code 注意这里的代码中,有一个 `$code` 占位符,它在管道程序处理过程中,会替换成答案和个选项内容中的代码。 -在后续的数据处理流程中,markdown 会被编译为 prepared 类型的习题。 - - ## 技能树合成 diff --git "a/data/1.toolchains\345\210\235\351\230\266/1.GIT\345\205\245\351\227\250/config.json" "b/data/1.toolchains\345\210\235\351\230\266/1.GIT\345\205\245\351\227\250/config.json" new file mode 100644 index 0000000000000000000000000000000000000000..89f61889832c5b4eca3923c569e4a0824e1098a5 --- /dev/null +++ "b/data/1.toolchains\345\210\235\351\230\266/1.GIT\345\205\245\351\227\250/config.json" @@ -0,0 +1,3 @@ +{ + "keywords": [] +} \ No newline at end of file diff --git "a/data/1.toolchains\345\210\235\351\230\266/config.json" "b/data/1.toolchains\345\210\235\351\230\266/config.json" new file mode 100644 index 0000000000000000000000000000000000000000..89f61889832c5b4eca3923c569e4a0824e1098a5 --- /dev/null +++ "b/data/1.toolchains\345\210\235\351\230\266/config.json" @@ -0,0 +1,3 @@ +{ + "keywords": [] +} \ No newline at end of file diff --git "a/data/2.toolchains\344\270\255\351\230\266/config.json" "b/data/2.toolchains\344\270\255\351\230\266/config.json" new file mode 100644 index 0000000000000000000000000000000000000000..89f61889832c5b4eca3923c569e4a0824e1098a5 --- /dev/null +++ "b/data/2.toolchains\344\270\255\351\230\266/config.json" @@ -0,0 +1,3 @@ +{ + "keywords": [] +} \ No newline at end of file diff --git "a/data/3.toolchains\351\253\230\351\230\266/config.json" "b/data/3.toolchains\351\253\230\351\230\266/config.json" new file mode 100644 index 0000000000000000000000000000000000000000..89f61889832c5b4eca3923c569e4a0824e1098a5 --- /dev/null +++ "b/data/3.toolchains\351\253\230\351\230\266/config.json" @@ -0,0 +1,3 @@ +{ + "keywords": [] +} \ No newline at end of file diff --git a/src/tree.py b/src/tree.py index 3eba573ac6a531272319ddf9cc9655ed85696e9f..0c64f2dd9d7e3a877c22e2dcbca26d1f6526e43d 100644 --- a/src/tree.py +++ b/src/tree.py @@ -5,6 +5,7 @@ import uuid import sys import re +id_set = set() def load_json(p): with open(p, 'r') as f: @@ -36,12 +37,26 @@ def parse_no_name(d): return no, dir_name +def check_export(base, cfg): + flag = False + exports = [] + for export in cfg.get('export', []): + ecfg_path = os.path.join(base, export) + if os.path.exists(ecfg_path): + exports.append(export) + else: + flag = True + if flag: + cfg["export"] = exports + return flag + def gen_tree(data_path): root = {} def gen_node_id(): - return ''.join(str(uuid.uuid5(uuid.NAMESPACE_URL, 'skill_tree')).split('-')) + # return ''.join(str(uuid.uuid5(uuid.NAMESPACE_URL, 'skill_tree')).split('-')) + return "c-" + uuid.uuid4().hex def list_dir(p): v = os.listdir(p) @@ -51,10 +66,42 @@ def gen_tree(data_path): 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 ensure_id_helper(node): + flag = False + + if (node.get('node_id') is None) or node.get('node_id') in id_set: + node['node_id'] = gen_node_id() + flag = True + + id_set.add(node['node_id']) + + if 'children' in node: + for c in node["children"]: + flag = flag or ensure_id_helper(list(c.values())[0]) + + return flag + + def ensure_node_id(cfg): + return ensure_id_helper(cfg) + + def ensure_title_helper(node, cfg_path, title=""): + flag = False + + if node.get('title') is None: + if cfg_path: + node['title'] = re.sub("^[0-9]{1,3}\.", "", os.path.split(os.path.dirname(cfg_path))[-1]) + else: + node['title'] = title + flag = True + + if 'children' in node: + for c in node["children"]: + flag = flag or ensure_title_helper(list(c.values())[0], None, list(c.keys())[0]) + + return flag + + def ensure_title(cfg, cfg_path): + return ensure_title_helper(cfg, cfg_path) def make_node(name, node_id, keywords, children=None): node = {} @@ -69,7 +116,12 @@ def gen_tree(data_path): # 根节点 cfg_path = os.path.join(data_path, 'config.json') cfg = load_json(cfg_path) - ensure_node_id(cfg_path, cfg) + if ensure_node_id(cfg): + dump_json(cfg_path, cfg) + + if ensure_title(cfg, cfg_path): + cfg["title"] = "C" + dump_json(cfg_path, cfg, exist_ok=True, override=True) tree_node = { "node_id": cfg['node_id'], "keywords": cfg['keywords'], @@ -81,41 +133,59 @@ def gen_tree(data_path): 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_path = os.path.join(level_no_dir, 'config.json') + level_cfg = load_json(level_path) + if ensure_node_id(level_cfg) or check_export(level_no_dir, level_cfg): + dump_json(level_path, level_cfg, exist_ok=True, override=True) + if ensure_title(level_cfg, level_path): + dump_json(level_path, level_cfg, exist_ok=True, override=True) level_node, level_node_children = make_node( - level_name, cfg['node_id'], cfg['keywords']) + level_name, level_cfg['node_id'], level_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_path = os.path.join(chapter_no_dir, 'config.json') + chapter_cfg = load_json(chapter_path) + if ensure_node_id(chapter_cfg) or check_export(chapter_no_dir, chapter_cfg): + dump_json(chapter_path, chapter_cfg, exist_ok=True, override=True) + if ensure_title(chapter_cfg, chapter_path): + dump_json(chapter_path, chapter_cfg, exist_ok=True, override=True) chapter_node, chapter_node_children = make_node( - chapter_name, cfg['node_id'], cfg['keywords']) + chapter_name, chapter_cfg['node_id'], chapter_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) + sec_path = os.path.join(section_no_dir, 'config.json') + sec_cfg = load_json(sec_path) + flag = ensure_node_id(sec_cfg) or check_export(section_no_dir, sec_cfg) section_node, section_node_children = make_node( - section_name, cfg['node_id'], cfg['keywords'], cfg['children']) + section_name, sec_cfg['node_id'], sec_cfg['keywords'], sec_cfg.get('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) + + for export in sec_cfg.get("export", []): + ecfg_path = os.path.join(section_no_dir, export) + ecfg = load_json(ecfg_path) + + if (ecfg.get('exercise_id') is None) or (ecfg.get('exercise_id') in id_set): + ecfg['exercise_id'] = uuid.uuid4().hex + dump_json(ecfg_path, ecfg, exist_ok=True, override=True) + + id_set.add(ecfg['exercise_id']) + + if flag: + dump_json(sec_path, sec_cfg, exist_ok=True, override=True) + + if ensure_title(sec_cfg, sec_path): + dump_json(sec_path, sec_cfg, exist_ok=True, override=True) # 保存技能树骨架 tree_path = os.path.join(data_path, 'tree.json')