Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
CSDN 技术社区
skill_tree_network
提交
27906b97
S
skill_tree_network
项目概览
CSDN 技术社区
/
skill_tree_network
通知
18
Star
5
Fork
8
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
3
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
skill_tree_network
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
3
合并请求
3
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
27906b97
编写于
12月 06, 2021
作者:
F
feilong
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
init
上级
62e748a9
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
622 addition
and
2 deletion
+622
-2
README.md
README.md
+236
-2
main.py
main.py
+5
-0
requirement.txt
requirement.txt
+1
-0
src/tree.py
src/tree.py
+380
-0
未找到文件。
README.md
浏览文件 @
27906b97
# skill_tree_
network
# skill_tree_
c
网络技能树
\ No newline at end of file
网络技能树
## 初始化
```
pip install -r requirement.txt
```
## 目录结构说明
*
技能树
`骨架文件`
:
*
位置:
`data/tree.json`
*
说明:该文件是执行
`python main.py`
生成的,请勿人工编辑
*
技能树
`根节点`
配置文件:
*
位置:
`data/config.json`
*
说明:可编辑配置关键词等字段,其中
`node_id`
字段是生成的,请勿编辑
*
技能树
`难度节点`
:
*
位置:
`data/xxx`
,例如:
`data/1.C语言初阶`
*
说明:
*
每个技能树有 3 个等级,目录前的序号是必要的,用来保持文件夹目录的顺序
*
每个目录下有一个
`config.json`
可配置关键词信息,其中
`node_id`
字段是生成的,请勿编辑
*
技能树
`章节点`
:
*
位置:
`data/xxx/xxx`
,例如:
`data/1.C语言初阶/1.C语言概述`
*
说明:
*
每个技能树的每个难度等级有 n 个章节,目录前的序号是必要的,用来保持文件夹目录的顺序
*
每个目录下有一个
`config.json`
可配置关键词信息,其中
`node_id`
字段是生成的,请勿编辑
*
技能树
`知识节点`
:
*
位置:
`data/xxx/xxx`
,例如:
`data/1.C语言初阶/1.C语言概述`
*
说明:
*
每个技能树的每章有 n 个知识节点,目录前的序号是必要的,用来保持文件夹目录的顺序
*
每个目录下有一个
`config.json`
*
其中
`node_id`
字段是生成的,请勿编辑
*
其中
`keywords`
可配置关键字字段
*
其中
`children`
可配置该
`知识节点`
下的子树结构信息,参考后面描述
*
其中
`export`
可配置该
`知识节点`
下的导出习题信息,参考后面描述
## `知识节点` 子树信息结构
例如
`data/1.C语言初阶/1.C语言概述/1.C语言发展史/config.json`
里配置对该知识节点子树信息结构:
```
json
{
//
...
"children"
:
[
{
"C语言的起源"
:
{
"keywords"
:
[
"C语言的起源"
,
"起源"
,
"C语言"
],
"children"
:
[]
}
},
{
"C语言的发展与应用"
:
{
"keywords"
:
[
"C语言的发展"
,
"C语言的应用"
,
"发展"
,
"应用"
,
"C语言"
],
"children"
:
[]
}
}
],
}
```
## `知识节点` 的导出习题编辑
例如
`data/1.C语言初阶/1.C语言概述/1.C语言发展史/config.json`
里配置对该知识节点导出的习题
```
json
{
//
...
"export"
:
[
{
"file"
:
"helloworld.c"
,
"variants"
:
"helloworld.json"
,
"depends"
:
[]
},
//
...
]
}
```
格式说明:
*
`file`
: 指定该目录下的习题源文件
*
`variants`
: 指定习题同名的json选项配置文件,参考下一节
*
`depends`
: 如果习题依赖同目录下的其他习题源代码,则在此字段里配置依赖的其他习题源文件名
## `知识节点` 的导出习题选项配置编辑
目前我们支持使用 markdown 语法直接编辑习题和各选项。
首先,编辑知识节点的配置,导出习题:
```
json
{
//
...
"export"
:
[
"hellowworld.json"
]
}
```
然后在
`data/1.C语言初阶/1.C语言概述/1.C语言发展史/`
下增加一个
`helloworld.json`
定义文件:
```
json
{
"type"
:
"code_options"
,
"author"
:
"ccat"
,
"source"
:
"helloworld.md"
,
"notebook_enable"
:
true
}
```
其中 type 字段目前都固定是
`code_options`
,notebook_enable 字段决定这个习题是否生成对应的 notebook 。根据具体情况写好其它字段,注意这里 source 的文件名,我们指定了一个 markdwon 文件。现在我们新建一个 HelloWorld.md 并编辑为:
````
mardown
# Hello World
以下 `Hello World` 程序中,能够正确输出内容的是:
## 答案
```c
#include <stdio.h>
int main(int argc, char** argv){
printf("Hello,Wrold!");
return 0;
}
```
## 选项
### main 函数定义错误
```c
#include <stdio.h>
void main(){
printf("Hello,Wrold!");
return 0;
}
```
### Print 函数调用错误
```c
#include <stdio.h>
int main(int argc, char** argv){
printf("Hello,Wrold!");
return 0;
}
```
### Java 风格的导入
```c
import stdio;
int main(int argc, char** argv){
stdio.printf("Hello,Wrold!");
return 0;
}
```
### 函数错误
```c
#include <stdio.h>
int main(int argc, char** argv){
print("Hello,Wrold!");
return 0;
}
```
````
### 增强信息
为了编写习题和生成 notebook 的需要,markdown 解释器支持两种模板能力,如果我们在答案之前,有一个名为 aop 的二级标题:
````
markdown
## aop
### before
```
c
print
(
"hello world"
);
```
### after
```
c
main
(
0
,
NULL
);
```
````
那么在创建notebook的时候,before 会插入到源代码前一个单元,after 则会插入到源代码后。aop 章节可以只包含 before 或 after 中的某一个,也可以两个都有。
另一些情况下,我们可能需要把各个选项中重复的代码提取出来,建立一个模板,此时可以在答案之前建立一个名为 template 的二级标题,例如:
````
markdwon
## template
```c
```c
#include <stdio.h>
int main(int argc, char** argv){
$code
return 0;
}
```
````
注意这里的代码中,有一个
`$code`
占位符,它在管道程序处理过程中,会替换成答案和个选项内容中的代码
## 技能树合成
在根目录下执行
`python main.py`
会合成技能树文件,合成的技能树文件:
`data/tree.json`
*
合成过程中,会自动检查每个目录下
`config.json`
里的
`node_id`
是否存在,不存在则生成
*
合成过程中,会自动检查每个知识点目录下
`config.json`
里的
`export`
里导出的习题配置,检查是否存在
`exercise_id`
字段,如果不存在则生成
main.py
0 → 100644
浏览文件 @
27906b97
from
src.tree
import
TreeWalker
if
__name__
==
'__main__'
:
walker
=
TreeWalker
(
"data"
,
"network"
,
"network"
)
walker
.
walk
()
requirement.txt
0 → 100644
浏览文件 @
27906b97
uuid==1.30
\ No newline at end of file
src/tree.py
0 → 100644
浏览文件 @
27906b97
import
json
import
logging
import
os
import
re
import
sys
import
uuid
import
re
import
git
id_set
=
set
()
logger
=
logging
.
getLogger
(
__name__
)
logger
.
setLevel
(
logging
.
INFO
)
handler
=
logging
.
StreamHandler
(
sys
.
stdout
)
formatter
=
logging
.
Formatter
(
'%(asctime)s - %(levelname)s - %(message)s'
)
handler
.
setFormatter
(
formatter
)
logger
.
addHandler
(
handler
)
repo
=
git
.
Repo
(
"."
)
def
user_name
():
return
repo
.
config_reader
().
get_value
(
"user"
,
"name"
)
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
:
logger
.
error
(
f
"
{
p
}
already exist"
)
sys
.
exit
(
0
)
with
open
(
p
,
'w+'
,
encoding
=
"utf8"
)
as
f
:
f
.
write
(
json
.
dumps
(
j
,
indent
=
2
,
ensure_ascii
=
False
))
def
ensure_config
(
path
):
config_path
=
os
.
path
.
join
(
path
,
"config.json"
)
if
not
os
.
path
.
exists
(
config_path
):
node
=
{
"keywords"
:
[]}
dump_json
(
config_path
,
node
,
exist_ok
=
True
,
override
=
False
)
return
node
else
:
return
load_json
(
config_path
)
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
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
class
TreeWalker
:
def
__init__
(
self
,
root
,
tree_name
,
title
=
None
,
log
=
None
):
self
.
name
=
tree_name
self
.
root
=
root
self
.
title
=
tree_name
if
title
is
None
else
title
self
.
tree
=
{}
self
.
logger
=
logger
if
log
is
None
else
log
def
walk
(
self
):
root
=
self
.
load_root
()
root_node
=
{
"node_id"
:
root
[
"node_id"
],
"keywords"
:
root
[
"keywords"
],
"children"
:
[]
}
self
.
tree
[
root
[
"tree_name"
]]
=
root_node
self
.
load_levels
(
root_node
)
self
.
load_chapters
(
self
.
root
,
root_node
)
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
}
"
)
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
}
"
)
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
+
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
):
if
not
os
.
path
.
isdir
(
level
):
continue
level_path
=
os
.
path
.
join
(
self
.
root
,
level
)
num
,
config
=
self
.
load_level_node
(
level_path
)
levels
.
append
((
num
,
config
))
levels
=
self
.
resort_children
(
self
.
root
,
levels
)
root_node
[
"children"
]
=
[
item
[
1
]
for
item
in
levels
]
return
root_node
def
load_level_node
(
self
,
level_path
):
config
=
self
.
ensure_level_config
(
level_path
)
num
,
name
=
self
.
extract_node_env
(
level_path
)
result
=
{
name
:
{
"node_id"
:
config
[
"node_id"
],
"keywords"
:
config
[
"keywords"
],
"children"
:
[],
}
}
return
num
,
result
def
load_chapters
(
self
,
base
,
level_node
):
chapters
=
[]
for
name
in
os
.
listdir
(
base
):
full_name
=
os
.
path
.
join
(
base
,
name
)
if
os
.
path
.
isdir
(
full_name
):
num
,
chapter
=
self
.
load_chapter_node
(
full_name
)
chapters
.
append
((
num
,
chapter
))
chapters
=
self
.
resort_children
(
base
,
chapters
)
level_node
[
"children"
]
=
[
item
[
1
]
for
item
in
chapters
]
return
level_node
def
load_sections
(
self
,
base
,
chapter_node
):
sections
=
[]
for
name
in
os
.
listdir
(
base
):
full_name
=
os
.
path
.
join
(
base
,
name
)
if
os
.
path
.
isdir
(
full_name
):
num
,
section
=
self
.
load_section_node
(
full_name
)
sections
.
append
((
num
,
section
))
sections
=
self
.
resort_children
(
base
,
sections
)
chapter_node
[
"children"
]
=
[
item
[
1
]
for
item
in
sections
]
return
chapter_node
def
resort_children
(
self
,
base
,
children
):
children
.
sort
(
key
=
lambda
item
:
item
[
0
])
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
}
"
)
if
origin
!=
posted
:
self
.
logger
.
info
(
f
"rename [
{
origin
}
] to [
{
posted
}
]"
)
os
.
rename
(
origin
,
posted
)
return
children
def
ensure_chapters
(
self
):
for
subdir
in
os
.
listdir
(
self
.
root
):
self
.
ensure_level_config
(
subdir
)
def
load_root
(
self
):
config_path
=
os
.
path
.
join
(
self
.
root
,
"config.json"
)
if
not
os
.
path
.
exists
(
config_path
):
config
=
{
"tree_name"
:
self
.
name
,
"keywords"
:
[],
"node_id"
:
self
.
gen_node_id
(),
}
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
else
:
config
=
load_json
(
config_path
)
flag
,
result
=
self
.
ensure_node_id
(
config
)
if
flag
:
dump_json
(
config_path
,
result
,
exist_ok
=
True
,
override
=
True
)
return
config
def
ensure_level_config
(
self
,
path
):
config_path
=
os
.
path
.
join
(
path
,
"config.json"
)
if
not
os
.
path
.
exists
(
config_path
):
config
=
{
"node_id"
:
self
.
gen_node_id
()
}
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
else
:
config
=
load_json
(
config_path
)
flag
,
result
=
self
.
ensure_node_id
(
config
)
if
flag
:
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
return
config
def
ensure_chapter_config
(
self
,
path
):
config_path
=
os
.
path
.
join
(
path
,
"config.json"
)
if
not
os
.
path
.
exists
(
config_path
):
config
=
{
"node_id"
:
self
.
gen_node_id
(),
"keywords"
:
[]
}
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
else
:
config
=
load_json
(
config_path
)
flag
,
result
=
self
.
ensure_node_id
(
config
)
if
flag
:
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
return
config
def
ensure_section_config
(
self
,
path
):
config_path
=
os
.
path
.
join
(
path
,
"config.json"
)
if
not
os
.
path
.
exists
(
config_path
):
config
=
{
"node_id"
:
self
.
gen_node_id
(),
"keywords"
:
[],
"children"
:
[],
"export"
:
[]
}
dump_json
(
config_path
,
config
,
exist_ok
=
True
,
override
=
True
)
else
:
config
=
load_json
(
config_path
)
flag
,
result
=
self
.
ensure_node_id
(
config
)
if
flag
:
dump_json
(
config_path
,
result
,
exist_ok
=
True
,
override
=
True
)
return
config
def
ensure_node_id
(
self
,
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
}
"
def
extract_node_env
(
self
,
path
):
try
:
_
,
dir
=
os
.
path
.
split
(
path
)
self
.
logger
.
info
(
path
)
number
,
title
=
dir
.
split
(
"."
,
1
)
return
int
(
number
),
title
except
Exception
as
error
:
self
.
logger
.
error
(
f
"目录 [
{
path
}
] 解析失败,结构不合法,可能是缺少序号"
)
# sys.exit(1)
raise
error
def
load_chapter_node
(
self
,
full_name
):
config
=
self
.
ensure_chapter_config
(
full_name
)
num
,
name
=
self
.
extract_node_env
(
full_name
)
result
=
{
name
:
{
"node_id"
:
config
[
"node_id"
],
"keywords"
:
config
[
"keywords"
],
"children"
:
[],
}
}
return
num
,
result
def
load_section_node
(
self
,
full_name
):
config
=
self
.
ensure_section_config
(
full_name
)
num
,
name
=
self
.
extract_node_env
(
full_name
)
result
=
{
name
:
{
"node_id"
:
config
[
"node_id"
],
"keywords"
:
config
[
"keywords"
],
"children"
:
config
.
get
(
"children"
,
[])
}
}
# if "children" in config:
# result["children"] = config["children"]
return
num
,
result
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
)
self
.
ensure_exercises_meta
(
meta_path
,
source
)
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
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
):
_
,
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
()
if
"type"
not
in
meta
:
meta
[
"type"
]
=
"code_options"
if
meta
is
None
:
meta
=
{
"type"
:
"code_options"
,
"author"
:
user_name
(),
"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
.
name
in
[
"python"
,
"java"
,
"c"
]:
return
True
else
:
return
False
def
check_section_keywords
(
self
,
full_path
):
config
=
self
.
ensure_section_config
(
full_path
)
if
not
config
.
get
(
"keywords"
,
[]):
self
.
logger
.
error
(
f
"节点 [
{
full_path
}
] 的关键字为空,请修改配置文件写入关键字"
)
sys
.
exit
(
1
)
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录