项目文档结构

├──.github/workflows/dbook.yml
├── book.toml
├── src/
│   ├── SUMMARY.md  # 目录结构定义
│   ├── index.md
│   ├── chapter_1.md
│   ├── chapter_2.md
│   ├── images/
│   │   ├── example.png
│   ├── theme/
│   │   ├── custom.css
├── book/

简要说明:

1、book.toml中是用于设置文档/书籍、输出形式的相关设置

2、src子目录下存放源文件,包括源文档(.md),静态资源(images目录下)、模板文件(theme目录下),其中必须有一个SUMMARY.md文档,用于存储文档的目录结构,必须有一个index.md,用于指定进入页

3、.github/workflows/dbook.yml用于github进行action自动化进程,用于检查、解决打开生成的html文档时的一些问题

4、book是文档生成的一些结果信息

5、整个项目利用mdbook进行,mdbook的下载需要Rust编译环境,流程如下
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
这一步时会询问下载方式一般选择-立即安装Rust、Cargo-直接点Enter
此时rust会安装到~/.cargo/bin/ 此时还需要手动加入到PATH
export PATH="$HOME/.cargo/bin:$PATH" >> ~/.bashrc
source ~/.bashrc
此时就可以用cargo工具安装mdbook,需要注意,cargo指令是用户级管理,不要用sudo
cargo install mdbook
cargo install mdbook-pdf

book.toml

[book]
authors = ["upsetgrass"]
language = "zh"
multilingual = false
src = "docs/"
title = "note_test"

[output.html]
git-repository-url = "https://upsetgrass.github.io/note/"
mathjax-support = true

[output.pdf]
optional = true

这是一个示例,有三个部分book、output.html、output.pdf

[book] - 用于设置书籍,作者是upsetgrass,该文档的语言是中文,不支持多语言,src源文件目录,所有的markdown章节都放在docs目录下,书籍的标题是note_test,显示在index.html页面的 <\title> 标签中

[output.html] - 配置 mdBook 生成 HTML 格式文档的参数,git-repository-url,会在生成的 HTML 文档的右上角添加一个指向该 Git 仓库的链接,mathjax-support用于是否启用MathJax公式支持,Makrdown中使用LateX数学公式

对于每一个md生成的html会生成在本地的book路径下

[output.pdf] - 控制pdf的输出行为,如果 pdf 生成器(mdbook-pdf)不可用,跳过 pdf 生成,而不会报错,mdbook-pdf生成的pdf会放在book/output路径下

src源文件

SUMMARY.md是书籍的目录

# Summary

# ChiselAIA

* [😺AIA](./index.md)
* [📩IMSIC](./imsic.md)
* [🧶APLIC](./aplic.md)
* [🧭集成指南(Integration Guide)](./integration.md)

mdbook会分析SUMMARY.md中指向的.md,只有加在了SUMMARY.md中的章节,才会生成对应的html最终在书籍中才能看到对应的章节,下图框出部分就是SUMMARY.md的内容展示,右侧就是index.md的内容,index.md会作为进入页(初始页)

对于.md中需要使用到的图片,需要存放到src/images下,并在.md中以![](./images/XXX.svg)的形式引用进来
ps:[]() - 超链接,![]() - 图片插入

mdbook指令

mdbook init:对mdbook所需要的book.toml book/ .gitignore src/ SUMMARY.md进行简单设置,也可以自行mkdir touch

mdbook build:读取book.toml配置文件,解析 src/SUMMARY.md 生成目录结构;将 src/ 目录中的 .md 文件转换为 HTML(默认存放在 book/);复制 theme/、static/ 目录的内容到 book/。

mdbook serve -p 3000:运行一个本地的http服务器,默认为http://127.0.0.1:3000,-p可以指定为3000还是4000...,用于本地展示生成的书籍

github_action

workflows中的yml是自动构建工具,和makefile mill等工具类似,只不过worflows是github中action组件自动执行的,这一部分用于创建分支gh-pages,单独存储mdbook build构建出来的那些html还有一些依赖文件,以保持其他分支的洁净,并设定自动更新文档的条件,具体示例如下,基本可以直接使用

name: deploy-github-pages  # 定义workflow或步骤名称
on:                        # on:触发条件
  push:
    branches:
      - main               # 当main分支有新的push,触发该github action
jobs:
  deploy-github-pages:     # 使用最新的Ubuntu作为执行环境
    runs-on: ubuntu-latest # 运行环境
    permissions:           # 设置权限,使得action可以向gh-pages分支写入
      contents: write
    steps:                 # 以下都是各个任务
      - name: Checkout repository         # 步骤名称
        uses: actions/checkout@v4  # uses-使用github action中写好的脚本,这里是在拉取我的代码

      - name: Setup mdBook
        if: ${{ github.ref == 'refs/heads/main' }}
        uses: peaceiris/actions-mdbook@v1.2.0       # 主分支上安装mdbook
        with:
          mdbook-version: '0.4.5'

      - name: Build site
        if: ${{ github.ref == 'refs/heads/main' }}
        working-directory: .
        run: mdbook build                           # 主分支上运行mdbook build指令

      - name: Move HTML files to root 
        run: |                                      # run: | 运行多行shell脚本
          mv ./book/html/* ./book/
          rm -r ./book/html

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3  # 使用该actions用于自动部署github pages
        if: ${{ github.ref == 'refs/heads/main' }}
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./book               # 指令主分支的./book内容作为gh-pages分支的内容
          force_orphan: true                   # 删除gh-pages的历史记录  ,只保留最新版本

本质:workflows中的操作就是github帮我们在某种触发下,自动执行某些指令。比如上面就是push到主分支时,执行后面的操作,后面的操作就是github把我们的主分支的代码pull下来,然后下载mdbook执行mdbook build,然后创建gh-pages分支,将生成的/book推送到我的仓库的gh-pages分支上。

github操作

需要在github仓库的settings里面的Pages项去修改这些内容

这里your site is live at https://upsetgrass.github.io/note/ 就是github给我们的文档分配的地址

格式为:https://<用户名/组织名>.github.io/<仓库名>

我们在Readme.md中就可以以 [文档](https://upsetgrass.github.io/note/) 这种形式超链接到我们写的书籍

下面Branch中设置gh-pages/root的前提是需要先把.github/workflows/mdbook.yml git到该仓库,此时github就会自动出现gh-pages分支,用于处理Github Pages,也就是会把这个分支作为书籍的源文件

这里需要注意的是,我们之前在.github/workflows/mdbook的生成/book的操作是在主分支,但是生成的结果放在了gh-pages分支,所以是以gh-pages分支作为源文件

插图

有多种方式生成我们所需要的图,1、graphviz 2、drawio

第一种方式是以代码的形式存储,第二种方式是以图形化、交互式的方式进行,.drawio->.svg

简要针对第一种方式进行讲解
Graphviz是一个底层图形可视软件,通过命令行和编程接口来生成图表,通过解析DOT语言,自动布局绘制图,而直接写DOT语言会比较困难,所以有人将DOT接口进行优化封装,即pydot,我们通过执行python脚本,自动生成.dot,又由Graphviz将.dot解析成.svg等图片格式

所以我们只需要专注于pydot,pydot中有三个主要的类node、Subgraph、Graph
1、node是pydot最基本的元素,表示一个独立的实体,其中不能包含其他的节点,需要加入到Graph/SubGraph中,否则不会显示在最终的图中
2、Subgraph子图是一组节点的集合,其本身不是节点,可以包含多个 Node,并且可以用边连接节点
3、Graph是整个图,是最终的可视化对象。是最高级的容器,可以包含:节点、子图、边,只有Graph、DiGraph对象可以被write_png() write_svg()输出为最终图像

那么在pydot中就是创建这些类,然后将node、Subgraph、Graph连接起来就构成了图,然后根据不同的需求,用不同的布局引擎来使用不同的图形排版,如下所示
dot -Tpng example.dot -o example_dot.png
fdp -Tpng example.dot -o example_fdp.png

从理解层面,排除掉边,就可以把Graph理解为根,Subgraph理解为非叶子节点,node就是叶子节点

# Dot就是主图,Edge用于连线,Node就是节点,Subgraph就是子图
from pydot import Dot, Edge, Node, Subgraph

# 创建一个空的有向图
graph = pydot.Dot(graph_type="digraph")

# 创建节点,节点在DOT语言中的唯一ID为msi_device_0,其中文字显示为MSI Device0
msi = Node("msi_device_0", label="MSI Device0")

# 创建一个子图
subgraph = pydot.Subgraph("cluster_1")
# main.py
from arch_common import *

###############################################################################
# Graph
###############################################################################
graph = AIADot(label="Configuration Paths in an AIA System", rankdir="RL")
configure = graph.main

###############################################################################
# Nodes and Subgraphs
###############################################################################
configure.add_subgraph(aplic)
configure.add_node(bus_network)
for imsic_hart in imsic_harts:
  configure.add_subgraph(imsic_hart)

###############################################################################
# Edges
###############################################################################
for domain in aplic.domains:
  configure.add_edge(MessageEdge(bus_network, domain))
for imsic_hart in imsic_harts:
  imsic = imsic_hart.imsic
  hart = imsic_hart.hart
  for intFile in imsic.intFiles:
    configure.add_edge(WireEdge(hart, intFile))
    configure.add_edge(Edge(intFile, bus_network, color="transparent"))
  configure.add_edge(MessageEdge(hart, bus_network))

###############################################################################
# Output
###############################################################################
graph.write(__file__.replace("_dot.py", "_py.dot"))
# arch_common.py
from pydot import Dot, Edge, Node, Subgraph

###############################################################################
# Nodes and Subgraphs
###############################################################################
msi_devices = [
  Node("msi_device_0", label="MSI Device 0"),
  Node("msi_device_1", label="MSI Device 1"),
  Node("msi_device__", label="MSI Device ..."),
]

class APLIC(Subgraph):
  def __init__(self):
    Subgraph.__init__(self, "aplic", label="APLIC", cluster=True,
      style='filled', bgcolor="#F8CECC", pencolor="#B85450",
    )
    self.domains = [
      Node("m_domain", label="M Domain", height=1.5),
      Node("s_domain", label="S Domain", height=1.5),
    ]
    for domain in self.domains:
      self.add_node(domain)
aplic = APLIC()

wired_devices = [
  Node("wired_device_0", label="Wired Device 0"),
  Node("wired_device_1", label="Wired Device 1"),
  Node("wired_device__", label="Wired Device ..."),
]

bus_network = Node("bus_network", label="Bus", height=7)

class IMSICHart(Subgraph):
  def __init__(self, id, suffix):
    Subgraph.__init__(self, f"imsic_hart_{suffix}", label="", cluster=True,
      pencolor="transparent",
    )
    self.imsic = self.IMSIC(id, suffix)
    self.add_subgraph(self.imsic)
    self.hart = Node(f"hart_{suffix}", label=f"Hart {id}", height=3.2)
    self.add_node(self.hart)
    
  class IMSIC(Subgraph):
    def __init__(self, id, suffix):
      Subgraph.__init__(self, f"imsic_{suffix}", label=f"IMSIC {id}", cluster=True,
        style="filled", bgcolor="#F8CECC", pencolor="#B85450",
      )
      self.intFiles = [
        Node(f"imsic_{suffix}_mint_file", label="M IntFile"),
        Node(f"imsic_{suffix}_sint_file", label="S IntFile"),
      ]
      self.intFiles += [
        Node(f"imsic_{suffix}_vsint_file_0", label=f"VS IntFile 0"),
        Node(f"imsic_{suffix}_vsint_file__", label=f"VS IntFile ..."),
      ]
      for intFile in self.intFiles:
        self.add_node(intFile)

imsic_harts = [IMSICHart(0, 0), IMSICHart("...", "_")]

###############################################################################
# Edges
###############################################################################
class MessageEdge(Edge):
  def __init__(self, src, dst, obj_dict=None, **attrs):
    Edge.__init__(self, src, dst, obj_dict, **attrs, color='"black:invis:black"')
class WireEdge(Edge):
  def __init__(self, src, dst, obj_dict=None, **attrs):
    Edge.__init__(self, src, dst, obj_dict, **attrs)

###############################################################################
# Graph
###############################################################################
class AIADot(Dot):
  class Legend(Subgraph):
    def __init__(self):
      Subgraph.__init__(self, "legend", label="Legend", cluster=True, pencolor="gray")
    def add_edge_legend(self, EdgeClass, label):
      src = Node(f"legend_${label}_edge_src", shape="plain", label=label)
      self.add_node(src)
      dst = Node(f"legend_${label}_edge_dst", shape="plain", label=" ")
      self.add_node(dst)
      self.add_edge(EdgeClass(src, dst))

  def __init__(self, *argsl, **argsd):
    Dot.__init__(self, *argsl, **argsd,
      splines="ortho",
      bgcolor="transparent",
    )
    self.main = Subgraph("main", label="", cluster=True, pencolor="transparent")
    self.main.set_node_defaults(shape="box")
    self.add_subgraph(self.main)
    self.legend = self.Legend()
    self.add_subgraph(self.legend)
    self.legend.add_edge_legend(WireEdge, "wire")
    self.legend.add_edge_legend(MessageEdge, "message")

此时python main.py即可生成.dot,然后用前面说的布局引擎即可生成图片,上例生成的图片如下图所示