# skill_tree_python

`Python 技能树`是[技能森林](https://gitcode.net/csdn/skill_tree)的一部分。

## 初始化

```
pip install -r requirements.txt
```

## 目录结构说明

* 技能树`骨架文件`：
    * 位置：`data/tree.json` 
    * 说明：该文件是执行 `python main.py` 生成的，请勿人工编辑
* 技能树`根节点`配置文件：
    * 位置：`data/config.json`
    * 说明：可编辑配置关键词等字段，其中 `node_id` 字段是生成的，请勿编辑
* 技能树`难度节点`：
    * 位置：`data/xxx`，例如: `data/1.python初阶`
    * 说明：
        * 每个技能树有 3 个等级，目录前的序号是必要的，用来保持文件夹目录的顺序
        * 每个目录下有一个 `config.json` 可配置关键词信息，其中 `node_id` 字段是生成的，请勿编辑
* 技能树`章节点`：
    * 位置：`data/xxx/xxx`，例如：`data/1.python初阶/1.预备知识`
    * 说明：
        * 每个技能树的每个难度等级有 n 个章节，目录前的序号是必要的，用来保持文件夹目录的顺序
        * 每个目录下有一个 `config.json` 可配置关键词信息，其中 `node_id` 字段是生成的，请勿编辑
* 技能树`知识节点`：
    * 位置：`data/xxx/xxx`，例如：`data/1.python初阶/1.预备知识`
    * 说明：
        * 每个技能树的每章有 n 个知识节点，目录前的序号是必要的，用来保持文件夹目录的顺序
        * 每个目录下有一个 `config.json`
            * 其中 `node_id` 字段是生成的，请勿编辑
            * 其中 `keywords` 可配置关键字字段
            * 其中 `children` 可配置该`知识节点`下的子树结构信息，参考后面描述
            * 其中 `export` 可配置该`知识节点`下的导出习题信息，参考后面描述


## `知识节点` 子树信息结构

例如 `data/1.python初阶/1.预备知识/1.Python简介/config.json` 里配置对该知识节点子树信息结构：
```json
{
    // ...

    "children": [
    {
      "Python都能做什么": {
        "keywords": [],
        "children": [],
        "node_id": "python-4-0"
      }
    },
    {
      "python起源和发展": {
        "keywords": [],
        "children": [],
        "node_id": "python-insert-2"
      }
    },
    {
      "python语言特点": {
        "keywords": [],
        "children": [],
        "node_id": "python-insert-3"
      }
    }
  ],
}
```



## `知识节点` 的习题配置规则(一)

例如 `data/1.python初阶/1.预备知识/1.Python简介/config.json` 里配置对该知识节点导出的习题

```json
{
    // ...
    "export": [
        "helloworld.json"
        // ...
    ]
}
```

其中`"helloworld.json"`是习题的配置文件，文件如下：
```json
{
  "exercise_id": 33,
  "source": "helloworld.py",
  "depends": [],
  "multiline": [
    {
      "str1 = \"Hello,\"": "",
      "str2 = \"World!\"": "str = \"Hello,World!\"",
      "print('str1'+'str2')": "print(str)"
    },
    {
      "str1 = \"Hello,\"": "",
      "str2 = \"World!\"": "",
      "print('str1'+'str2')": "print(\"Hello\" +\",\" +\"World!\")"
    },
    {
      "print('str1'+'str2')": "print(str1+str2)"
    }
  ]
}
```

其中：

* `"exercise_id"` 是脚本填充的字段，编辑的时候不用填写
* `"source"` 指定了习题的来源文件
* `"depends"`: 指定了习题依赖的同知识点下的其他源文件
* `"multiline"`: 是习题选项替换的规则之一


#### 习题源代码

`"helloworld.py"`的源代码如下：

```python
# -*- coding: UTF-8 -*-
# 作者：幻灰龙
# 标题：Hello World
# 描述：输出 "Hello,World!" 字符串，找出错的那项。

if __name__ == '__main__':
    str1 = "Hello,"
    str2 = "World!"
    print('str1'+'str2')

```

源代码的头部注释固定格式，配置`作者`,`标题`,`描述`，其中描述可以是MarkDown格式

#### 习题源代码替换规则

对于习题代码 `"helloworld.py"` 的代码变种替换规则配置在`"helloworld.json"` 里面。有两种替换规则：

**单行替换规则**：

* 配置由`one_line`字段指定的单行替换字典
* 格式是：`"<源字符串>"`: [`"<替换字符串A>"`, `<替换字符串B>`,...],
    * 其中每个 `"<源字符串>"` `/` `"<替换字符串A>"` 被生成为是一个替换选项
    * 指定的配置应该能至少生成 `3+` 个替换选项

```json
{
    "one_line": {
        "printf": ["print"],
        "return 0;": ["return 0"],
        "(\"Hello,Wrold!\")": [" \"Hello,Wrold!\""]
    }
}
```

上面的替换规则会将代码替换成 3 个变种的代码：

```c
// 变种代码1
#include <stdio.h>
int main(int argc, char** argv){
    print("Hello,Wrold!");
    return 0;
}
```

```c
// 变种代码2
#include <stdio.h>
int main(int argc, char** argv){
    print("Hello,Wrold!");
    return 0
}
```

```c
// 变种代码3
#include <stdio.h>
int main(int argc, char** argv){
    print "Hello,Wrold!";
    return 0
}
```

这些变种代码将会作为技能树该知识点该代码选择题的选项。

**多行替换规则**：

* 配置由`multiline`字段指定的多行替换数组
* 数组的每个元素是一组替换规则，会整组被替换

例如：

```json
{
    "multiline": [
        {
            "printf": "print"
        },
        {
            "int main(int argc, char** argv){" : "int main(char** argv){",
            "return 0;" : "return 0",
        },
        {
            "#include <stdio.h>": ""
        }
    ]
}
```

同样，该配置将支持将源代码生成3个变种代码

```c
// 变种代码1
#include <stdio.h>
int main(int argc, char** argv){
    print("Hello,Wrold!");
    return 0;
}
```

```c
// 变种代码2, 注意第2组替换规则，包含了两行替换
#include <stdio.h>
int main(char** argv){
    print("Hello,Wrold!");
    return 0
}
```

```c
// 变种代码3
int main(int argc, char** argv){
    print("Hello,Wrold!");
    return 0;
}
```

## `知识节点` 的习题配置规则(二)

另外一种更便利的配置习题的方式是直接基于 MarkDown 格式配置。还是以`"helloworld.json"` 为例子展开描述。

此时，`"helloworld.json"` 简化为：

```json
{
    "type": "code_options",
    "author": "幻灰龙",
    "source": "helloworld.md",
}
```

其中 type 字段目前都固定是 `code_options`。根据具体情况写好其它字段，注意这里 `source` 的文件名，我们指定了一个 Markdown 文件。现在我们新建一个 `helloworld.md` 并编辑为：

````markdown
# Hello World

输出 "Hello,World!" 字符串，找出错的那项。

## template

```python
# this part is used to be displayed in jupyter notebook
if ___name__ == '__main__':
    str1 = "Hello,"
    str2 = "World!"
    print('str1' + 'str2')
```

## 答案

```python
if __name__ == '__main__':
    str1 = "Hello,"
    str2 = "World!"
    print('str1' + 'str2')
```

## 选项

### 两字符串变量拼接

```python
if __name__ == '__main__':
    str1 = "Hello,"
    str2 = "World!"
    print(str1+str2)
```

### 两字符串字面量拼接

```python
if __name__ == '__main__':
    print("Hello,"+"World!")
```

### 两字符串字面量拼接成字符串变量

```python
if __name__ == '__main__':
    str = "Hello," + "World!"
    print(str)
```

````

这是一个最基本的习题结构，它包含标题、答案、选项，注意这几个一级和二级标题必须填写正确，解释器会读取这几个标题。而选项的标题会被直接忽略掉，在
最终生成的习题中不包含选项的三级标题，所以这个标题可以用来标注一些编辑信息，例如“此选项没有关闭文件连接”，“类型错误”等等。

## 习题配置规则的选择

我们介绍了两种习题编辑规则，目前Python技能树这里大量使用了规则(一)，这是历史编辑遗留。新增习题建议使用规则（二），更简洁方便。

## 技能树合成

在根目录下执行 `python main.py` 会合成技能树文件，合成的技能树文件: `data/tree.json`

* 合成过程中，会自动检查每个目录下 `config.json` 里的 `node_id` 是否存在，不存在则生成
* 合成过程中，会自动检查每个知识点目录下 `config.json` 里的 `export` 里导出的习题配置，检查是否存在`exercise_id` 字段，如果不存在则生成
