├── .github └── workflows │ └── doc.yml ├── .gitignore ├── Makefile ├── README.md ├── docs ├── faq.md ├── framework.md ├── index.md ├── javascripts │ └── mathjax.js ├── lwip.md ├── requirement.md ├── setup.md └── submission.md ├── mkdocs.yml ├── pandoc ├── docx.yml ├── pdf.yml └── remove_duplicate_title.sh ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── theme-override └── main.html /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Build documentation with mkdocs 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 3.12 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: 3.12 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install -r requirements.txt 19 | - name: Build with mkdocs 20 | run: mkdocs build 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | .vscode/ 3 | generated/ 4 | deploy.sh 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: combine pdf docx clean 2 | 3 | project_name := tcp_lab 4 | 5 | combine: generated/$(project_name).md 6 | 7 | pdf: generated/$(project_name).pdf 8 | 9 | docx: generated/$(project_name).docx 10 | 11 | generated/$(project_name).md: docs mkdocs.yml 12 | mkdir -p generated 13 | mkdocscombine -d -o $@ 14 | bash ./pandoc/remove_duplicate_title.sh $@ 15 | 16 | generated/$(project_name).docx: generated/$(project_name).md pandoc/docx.yml 17 | pandoc --defaults pandoc/docx.yml -s -o $@ $< 18 | 19 | generated/$(project_name).pdf: generated/$(project_name).md pandoc/pdf.yml 20 | pandoc --defaults pandoc/pdf.yml -o $@ $< 21 | 22 | clean: 23 | rm -rf generated 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TCP-Lab Documentation 2 | 3 | ![Build documentation with mkdocs](https://github.com/thu-cs-lab/TCP-Lab-Docs/workflows/Build%20documentation%20with%20mkdocs/badge.svg) 4 | 5 | 本项目为《计算机网络专题训练》课程的 TCP 实验的实验文档,采用 `mkdocs` 编写。 6 | 7 | 本站点的自动编译版本在 [这里](https://lab.cs.tsinghua.edu.cn/tcp/doc/) 发布。 8 | 9 | ## 撰写 10 | 11 | 本站点内容使用 Markdown 进行编写。具体可查看 [mkdocs](https://www.mkdocs.org/) 和 [mkdocs-material](https://squidfunk.github.io/mkdocs-material/extensions/pymdown/) 文档。 12 | 13 | 如果创建了新页面,需要插入到 `mkdocs.yml` 的 `nav` 部分,否则将不会出现在编译结果中。 14 | 15 | ## 编译 16 | 17 | 首先安装依赖,而后编译即可: 18 | 19 | ```bash 20 | python3 -m pip install --user -r requirements.txt # 安装 Python 依赖包 21 | mkdocs serve # 直接在本地 serve,或者: 22 | mkdocs build --clean # 生成于 site/ 文件夹中 23 | ``` 24 | 25 | ## 生成离线版 26 | 27 | 生成 PDF 和 DOCX 版本需要(经过修改的) [`mkdocs-combine`](https://github.com/Harry-Chen/mkdocs-combine) 插件的支持。首先安装插件: 28 | 29 | ```bash 30 | pip3 install git+https://github.com/Harry-Chen/mkdocs-combine.git 31 | ``` 32 | 33 | 而后运行: 34 | 35 | ```bash 36 | make docx 37 | make pdf 38 | ``` 39 | 40 | 即可在 `generated` 目录下获得相应格式的文件。 41 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | !!! question "RFC 793 中 TCP 状态机写得很复杂,我需要都实现吗?" 4 | 5 | 不需要,其中一些比较关键的部分已经在代码框架中给出,对于一些可选功能,可能会实现更完整的状态机。我们的建议是,对于没有实现的代码,使用 `UNIMPLEMENTED` 宏表示尚未实现,那么代码在运行到此处的时候,就会显示错误,此时再去实现对应的处理即可,没有必要先把完整的 TCP 状态机翻译成代码,那样代码量太大,并且会很枯燥。 6 | 7 | !!! question "Nagle 算法是什么呢?" 8 | 9 | 简单来说,Nagle 算法要解决的是小包问题:如果用户 `write` 调用的数据都很小,并且每次 `write` 都要发送一个 TCP 包给对端,就会产生大量的 TCP 包。一个简单解决的思路是,如果 `write` 的数据太小,那就等一小段时间,期望用户在这个时间里继续写入一些数据,直到超时或者积攒到足够大的数据可以发送。由于超时实现比较麻烦(需要用到 Timer,引入了额外的状态,实现不方便),一个更优雅的方式是,当所有发送过的数据都已经 ACK 了(类似于拿 rtt 做超时,在更新 `snd_una` 的时候判断),此时没有 unACKed 的数据,就把缓存的数据都发送出去。详细介绍见 [RFC896](https://tools.ietf.org/html/rfc896) 和 [Wikipedia 页面](https://en.wikipedia.org/wiki/Nagle%27s_algorithm)。 10 | 11 | !!! question "TCP Urgent 怎么实现?" 12 | 13 | 其实不是同学们的问题,而是 RFC 793 写得并不清晰。在 [RFC 6093](https://tools.ietf.org/html/rfc6093) 中,作者提到了历史上对 TCP Urgent 不同的处理和带来的一系列问题,并最后建议应用程序不要用 TCP Urgent 功能,TCP 协议栈实现它也只是为了兼容一些已有的程序。虽然我们把它列入了一个可选功能中,但并不建议大家实现。 14 | 15 | !!! question "在 WSL 中运行程序的时候,出现了 local datagram socket: Operation not supported 的报错,这是为什么呢?" 16 | 17 | WSL 的 `/mnt` 路径下是 mount 的 NTFS,不支持 unix domain socket,请换到 WSL 内部的路径(比如 home 目录)再尝试。 18 | 19 | !!! question "make 的时候报错:Division works only with integers" 20 | 21 | meson 版本太老,请用 `pip3 install -U meson` 升级。 22 | 23 | !!! question "make 的时候报错:meson: No such file or directory" 24 | 25 | 首先检查 meson 是否安装了,并且可以在命令行中执行;如果安装了还是显示失败,可能是因为更新了 meson,导致它的路径变了,可以删除整个 `builddir` 目录再重新 `make`。 26 | 27 | !!! question "设置了丢包以后,lwip 服务器一段时间没有收到 TCP 包就给客户端发送 FIN 了,能不能延长一下这个超时?" 28 | 29 | lwip 的 HTTPD 实现中,如果发现客户端一段时间(默认是 2s)都没有发送任何数据,就会终止连接。可以在 `lwipopt.h` 头文件中设置 `#define HTTPD_MAX_RETRIES 10`(默认值是 4),这样 HTTPD 会等待更长的时间。详细的设置可以阅读 lwip 的 `httpd_opts.h` 头文件。 -------------------------------------------------------------------------------- /docs/framework.md: -------------------------------------------------------------------------------- 1 | # 实验框架 2 | 3 | 实验框架代码在 [https://git.tsinghua.edu.cn/tcp-lab/tcp-lab](https://git.tsinghua.edu.cn/tcp-lab/tcp-lab) ,请同学克隆下来进行开发。 4 | 5 | ## 目录结构 6 | 7 | 目录结构大体如下: 8 | 9 | 1. include: 头文件目录 10 | 2. src: 源代码目录 11 | 3. thirdparty: 第三方库,目前只有 lwIP 12 | 4. Makefile: 用于 Make 命令构建 13 | 5. meson.build: 实际的构建描述文件 14 | 15 | 需要同学们修改的,主要是 `include` `src` 和 `meson.build`。一般不需要修改 lwIP 的源代码,关于如何配置 lwIP,请阅读 [lwIP 库](/tcp/doc/lwip/)。 16 | 17 | 在源代码目录 `src` 下,又有三个子目录: 18 | 19 | 1. lwip: 基于 lwIP 编写的 TCP 客户端和服务端。 20 | 2. lab: 同学需要补全的 TCP 协议栈,也包括了一个简单的 HTTP 客户端和服务端。 21 | 3. test: 用于测试 TCP 协议栈的测试代码和脚本 22 | 23 | 同学们主要需要编写的就是 `lab` 目录下的代码。 24 | 25 | ## 客户端与服务端通信 26 | 27 | 在 `src/common.cpp` 中,提供了进程间通信的代码。在本次实验中,客户端和服务端分别运行在一个进程中,为了发送 IP 分组给对方,采用的是 unix socket 的方法。如果出现创建 unix socket 失败的错误,请检查文件系统是否支持。 28 | 29 | 编译后,会生成四个可执行程序: 30 | 31 | - lab-client, lab-server:采用同学编写的 TCP 协议栈的客户端和服务端 32 | - lwip-client, lwip-server:采用 lwIP 协议栈的客户端和服务端 33 | 34 | ## 如何运行程序 35 | 36 | 程序所使用的 unix socket 路径通过命令行参数给出,比如,如果用 `s` 表示服务端,`c` 表示客户端,那么应该这样编译并运行 `lwip-server`: 37 | 38 | ```shell 39 | $ make && ./builddir/lwip-server -l s -r c -p lwip-server.pcap 40 | ``` 41 | 42 | 这表示 `lwip-server` 的本地(监听)unix socket 是 `s`,远端(目的)unix socket 是 `c`,并且会把收发的 IP 分组写入到 `lwip-server.pcap` 文件里。 43 | 44 | 类似地,可以运行 `lwip-client`: 45 | 46 | ```shell 47 | $ make && ./builddir/lwip-client -l c -r s -p lwip-client.pcap 48 | ``` 49 | 50 | 注意要保持这里参数的 `-l` `-r` 应该和 `lwip-server` 的次序正好颠倒,这样就可以保证两个进程可以正常通信。同样地,它会把收发的 IP 分组写入到 `lwip-client.pcap` 文件里。 51 | 52 | 如果同时运行 `lwip-server` 和 `lwip-client` 并且路径正确,应该可以看到 HTTP 获取到的结果: 53 | 54 | ```text 55 | tcp_recved: received 130 bytes, wnd 1738 (406). 56 | HTTP Got Body: 57 | 58 | lwIP - A Lightweight TCP/IP Stack 59 | 60 | 61 | 62 | 114 |
63 | SICS logo 65 | 66 |

lwIP - A Lightweight TCP/IP Stack

67 |

68 | The web page you are watchitcp_output: nothing to send (0x0) 69 | RX: 45 00 02 40 00 02 00 00 FF 06 A5 B3 0A 00 00 01 0A 00 00 02 00 50 C0 01 00 00 1B 87 00 00 19 FD 50 18 07 D1 35 FA 00 00 6E 67 20 77 61 73 20 73 65 72 76 65 64 20 62 79 20 61 20 73 69 6D 70 6C 65 20 77 65 62 0D 0A 09 20 20 20 20 73 65 72 76 65 72 20 72 75 6E 6E 69 6E 67 20 6F 6E 20 74 6F 70 20 6F 66 20 74 68 65 20 6C 69 67 68 74 77 65 69 67 68 74 20 54 43 50 2F 49 50 20 73 74 61 63 6B 20 3C 61 0D 0A 09 20 20 20 20 68 72 65 66 3D 22 68 74 74 70 3A 2F 2F 77 77 77 2E 73 69 63 73 2E 73 65 2F 7E 61 64 61 6D 2F 6C 77 69 70 2F 22 3E 6C 77 49 50 3C 2F 61 3E 2E 0D 0A 09 20 20 3C 2F 70 3E 0D 0A 09 20 20 3C 70 3E 0D 0A 09 20 20 20 20 6C 77 49 50 20 69 73 20 61 6E 20 6F 70 65 6E 20 73 6F 75 72 63 65 20 69 6D 70 6C 65 6D 65 6E 74 61 74 69 6F 6E 20 6F 66 20 74 68 65 20 54 43 50 2F 49 50 0D 0A 09 20 20 20 20 70 72 6F 74 6F 63 6F 6C 20 73 75 69 74 65 20 74 68 61 74 20 77 61 73 20 6F 72 69 67 69 6E 61 6C 6C 79 20 77 72 69 74 74 65 6E 20 62 79 20 3C 61 0D 0A 09 20 20 20 20 68 72 65 66 3D 22 68 74 74 70 3A 2F 2F 77 77 77 2E 73 69 63 73 2E 73 65 2F 7E 61 64 61 6D 2F 6C 77 69 70 2F 22 3E 41 64 61 6D 20 44 75 6E 6B 65 6C 73 0D 0A 09 20 20 20 20 6F 66 20 74 68 65 20 53 77 65 64 69 73 68 20 49 6E 73 74 69 74 75 74 65 20 6F 66 20 43 6F 6D 70 75 74 65 72 20 53 63 69 65 6E 63 65 3C 2F 61 3E 20 62 75 74 20 6E 6F 77 20 69 73 0D 0A 09 20 20 20 20 62 65 69 6E 67 20 61 63 74 69 76 65 6C 79 20 64 65 76 65 6C 6F 70 65 64 20 62 79 20 61 20 74 65 61 6D 20 6F 66 20 64 65 76 65 6C 6F 70 65 72 73 0D 0A 09 20 20 20 20 64 69 73 74 72 69 62 75 74 65 64 20 77 6F 72 6C 64 2D 77 69 64 65 2E 20 53 69 6E 63 65 20 69 74 27 73 20 72 65 6C 65 61 73 65 2C 20 6C 77 49 50 20 68 61 73 0D 0A 09 20 20 20 20 73 70 75 72 72 65 64 20 61 20 6C 6F 74 20 6F 66 70 | tcp_receive: window update 2001 71 | HTTP Got Body: 72 | ng was served by a simple web 73 | server running on top of the lightweight TCP/IP stack lwIP. 75 |

76 |

77 | lwIP is an open source implementation of the TCP/IP 78 | protocol suite that was originally written by Adam Dunkels 80 | of the Swedish Institute of Computer Science but now is 81 | being actively developed by a team of developers 82 | distributed world-wide. Since it's release, lwIP has 83 | spurred a lot oftcp_output: nothing to send (0x0) 84 | tcp_output: sending ACK for 7583 85 | TX: 45 00 00 28 00 02 00 00 FF 06 A7 CB 0A 00 00 02 0A 00 00 01 C0 01 00 50 00 00 19 FD 00 00 1D 9F 50 10 04 30 9F B4 00 00 86 | RX: 45 00 02 40 00 03 00 00 FF 06 A5 B2 0A 00 00 01 0A 00 00 02 00 50 C0 01 00 00 1D 9F 00 00 19 FD 50 10 07 D1 AF 50 00 00 20 69 6E 74 65 72 65 73 74 20 61 6E 64 20 68 61 73 20 62 65 65 6E 20 70 6F 72 74 65 64 20 74 6F 20 73 65 76 65 72 61 6C 0D 0A 09 20 20 20 20 70 6C 61 74 66 6F 72 6D 73 20 61 6E 64 20 6F 70 65 72 61 74 69 6E 67 20 73 79 73 74 65 6D 73 2E 20 6C 77 49 50 20 63 61 6E 20 62 65 20 75 73 65 64 20 65 69 74 68 65 72 0D 0A 09 20 20 20 20 77 69 74 68 20 6F 72 20 77 69 74 68 6F 75 74 20 61 6E 20 75 6E 64 65 72 6C 79 69 6E 67 20 4F 53 2E 0D 0A 09 20 20 3C 2F 70 3E 0D 0A 09 20 20 3C 70 3E 0D 0A 09 20 20 20 20 54 68 65 20 66 6F 63 75 73 20 6F 66 20 74 68 65 20 6C 77 49 50 20 54 43 50 2F 49 50 20 69 6D 70 6C 65 6D 65 6E 74 61 74 69 6F 6E 20 69 73 20 74 6F 20 72 65 64 75 63 65 0D 0A 09 20 20 20 20 74 68 65 20 52 41 4D 20 75 73 61 67 65 20 77 68 69 6C 65 20 73 74 69 6C 6C 20 68 61 76 69 6E 67 20 61 20 66 75 6C 6C 20 73 63 61 6C 65 20 54 43 50 2E 20 54 68 69 73 0D 0A 09 20 20 20 20 6D 61 6B 65 73 20 6C 77 49 50 20 73 75 69 74 61 62 6C 65 20 66 6F 72 20 75 73 65 20 69 6E 20 65 6D 62 65 64 64 65 64 20 73 79 73 74 65 6D 73 20 77 69 74 68 20 74 65 6E 73 0D 0A 09 20 20 20 20 6F 66 20 6B 69 6C 6F 62 79 74 65 73 20 6F 66 20 66 72 65 65 20 52 41 4D 20 61 6E 64 20 72 6F 6F 6D 20 66 6F 72 20 61 72 6F 75 6E 64 20 34 30 20 6B 69 6C 6F 62 79 74 65 73 0D 0A 09 20 20 20 20 6F 66 20 63 6F 64 65 20 52 4F 4D 2E 0D 0A 09 20 20 3C 2F 70 3E 0D 0A 09 20 20 3C 70 3E 0D 0A 09 20 20 20 20 4D 6F 72 65 20 69 6E 66 6F 72 6D 61 74 69 6F 6E 20 61 62 6F 75 74 20 6C 77 49 50 20 63 61 6E 20 62 65 20 66 6F 75 6E 64 20 61 74 20 74 68 65 20 6C 77 49 50 0D 0A 09 20 20 20 20 68 6F 6D 65 70 61 67 65 20 61 74 20 3C 61 0D 0A 09 20 20 20 20 87 | tcp_receive: window update 2001 88 | HTTP Got Body: 89 | interest and has been ported to several 90 | platforms and operating systems. lwIP can be used either 91 | with or without an underlying OS. 92 |

93 |

94 | The focus of the lwIP TCP/IP implementation is to reduce 95 | the RAM usage while still having a full scale TCP. This 96 | makes lwIP suitable for use in embedded systems with tens 97 | of kilobytes of free RAM and room for around 40 kilobytes 98 | of code ROM. 99 |

100 |

101 | More information about lwIP can be found at the lwIP 102 | homepage at http://savannah.nongnu.org/projects/lwip/ 108 | or at the lwIP wiki at http://lwip.wikia.com/. 110 |

111 |
112 |   113 |
115 | 116 | 117 | 118 | HTTP Result: 0 119 | ``` 120 | 121 | 接下来,用类似的方法运行同学编写的实验程序 `lab-client`: 122 | 123 | ```shell 124 | $ make && ./builddir/lab-client -l c -r s -p lab-client.pcap 125 | ``` 126 | 127 | 如果工作正常,应该也可以看到类似上面的输出。 128 | 129 | 为了测试同学编写的协议栈作为客户端的功能,可以运行 lwip-server,再运行 lab-client 进行测试;如果要测试同学编写的协议栈作为服务端的功能,可以运行 lab-server,再运行 lwip-client 进行测试。最后,也可以自己与自己测试:运行 lab-server 和 lab-client。 130 | 131 | ## 模拟丢包 132 | 133 | 在代码 `common.h` 和 `common.cpp` 中,提供了伪随机模拟丢包的逻辑,可以通过命令行参数指定: 134 | 135 | - -R 0.1: 表示设置接收时的丢包率 136 | - -S 0.1: 表示设置发送时的丢包率 137 | 138 | 默认设置下为不丢包。你可以任意设置丢包的比例,然后观察协议栈是否还可以正常工作。 139 | 140 | 除了模拟丢包以外,还可以模拟乱序:比如在收发包的时候,先保存到缓冲区,然后按照一定的策略(定时 + 随机)选择缓冲区中的 IP 分组发出去。 141 | 142 | ## 如何调试 143 | 144 | 利用程序生成的 pcap 文件,可以用 wireshark 打开并查看各个 TCP segment 的信息以找到问题。 145 | 146 | 此外,也可以在代码中添加调试信息,也可以打开 lwIP 的更多调试信息,详见 [lwIP 库](/tcp/doc/lwip/)。 147 | 148 | 最后,别忘了可以用调试器来单步调试。 149 | 150 | ## 如何执行自动化测试 151 | 152 | 框架里为各项任务提供了自动化的测例,在 `src/test` 下,同学们可以自行阅读并修改其源代码。不建议直接执行这些 Python 文件,而是通过 `make test` 命令调用,这会自动编译源代码并执行相应的测试。 153 | 154 | 如果你只想执行部分测试,可以修改 `meson.build` 的内容。`meson.build`的语法其实并不复杂,注释掉不想运行的测试即可。 155 | 156 | 如果测试不通过,可以通过 `builddir` 目录下的日志文件来排查问题。 157 | 158 | ## 定时器设计 159 | 160 | 框架代码是一个单线程并发的模式,有些类似 Node.JS,即不会进行阻塞的操作。如果进行的操作可能阻塞,注册一个回调函数,定期轮询是否可以继续。以 `lab-client.cpp` 的发送逻辑为例子: 161 | 162 | ```cpp 163 | // write HTTP request line by line every 1s 164 | const char *data[] = { 165 | "GET /index.html HTTP/1.1\r\n", 166 | "Accept: */*\r\n", 167 | "Host: 10.0.0.1\r\n", 168 | "Connection: Close\r\n", 169 | "\r\n", 170 | }; 171 | int index = 0; 172 | size_t offset = 0; 173 | timer_fn write_fn = [&] { 174 | if (tcp_state(tcp_fd) == TCPState::CLOSED) { 175 | printf("Connection closed\n"); 176 | return -1; 177 | } 178 | if (tcp_state(tcp_fd) != TCPState::ESTABLISHED) { 179 | printf("Waiting for connection establishment\n"); 180 | return 1000; 181 | } 182 | 183 | const char *p = data[index]; 184 | size_t len = strlen(p); 185 | ssize_t res = tcp_write(tcp_fd, (const uint8_t *)p + offset, len - offset); 186 | if (res > 0) { 187 | printf("Write '%s' to tcp\n", p); 188 | offset += res; 189 | 190 | // write completed 191 | if (offset == len) { 192 | index++; 193 | offset = 0; 194 | } 195 | } 196 | 197 | // next data 198 | if (index < 5) { 199 | return 100; 200 | } else { 201 | return -1; 202 | } 203 | }; 204 | TIMERS.schedule_job(write_fn, 1000); 205 | ``` 206 | 207 | 这段代码向计时器类 TIMER 注册了一个回调函数,然后这个函数内部有一个状态机,记录当前发送到哪一段数据的哪个偏移。如果发送不完全(比如因为 buffer 满等原因),就再注册一个回调函数,等下一次超时再尝试发送。 208 | 209 | 接受端也是类似的,只不过更复杂一些,在 `struct read_http_response` 中实现。它实现了一个状态机,保存了当前的状态(是否读入了 http header,读了多少 http body),并且不断尝试读取;类似地,如果发现 TCP 读取缓冲区为空,就注册一个回调函数(恰好也是结构体自己),等下一次超时再次尝试接收。 210 | 211 | 那么,上面的机制,很重要的一个基础设施就是计时器,在代码中就是 TIMERS 结构体。它维护了一个优先队列,根据事件发生时间从小到大排序。注册回调的时候,会往优先队列中插入一个新的事件。在 main 函数调用 `TIMERS.trigger` 函数的时候,它就会检查优先队列,然后执行那些已经到了时间的事件,并移出队列。注意,这里的事件不是重复进行的,如果要重复执行,需要重新注册(类似 JavaScript 的 setTimeout,而不是 setInterval)。 212 | 213 | TIMERS 的用法也很简单: 214 | 215 | - `TIMERS.add_job(timer_fn fn, uint64_t ts_msec)`: 注册一个回调,会在 `ts_msec` 这个时间戳之后的时间内尽快被调用 216 | - `TIMERS.schedule_job(timer_fn fn, uint64_t delay_msec)`: 注册一个回调,会在距离现在 `delay_msec` 毫秒之后不远的时间内尽快被调用 217 | 218 | 需要注意的是,在这种单线程并发的模式里,如果代码中写了死循环,TIMERS 也不会触发,因为代码不会进入 `TIMERS.trigger` 函数中。另外,事件的触发也是不准时的:如果之前的事件跑的时间太久,后面的事件可能很晚才能开始执行。同学们可以回忆一下《操作系统》课程中对调度器和实时性操作系统的讲解。和操作系统不同的是,这里没有时钟中断,无法打断正在执行的程序,只能程序主动交出执行权限(类似 coroutine/green thread,同学们可以回忆一下《操作系统》课程中讲的用户线程,内核线程和用户线程 N:M 的理论知识)。 -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 总述 2 | 3 | !!! danger "版权声明" 4 | 5 | 本项目为 2021 年春季学期起清华大学计算机系开设的《计算机网络专题训练》课程的 TCP 实验的实验框架。 6 | **所有内容(包括文档、代码等)未经作者授权,禁止用作任何其他用途,包括且不限于在其他课程或者其他学校中使用。** 7 | 8 | 如需使用授权,可通过 shengqi dot chen at tuna dot tsinghua dot edu dot cn 联系作者。作者保留一切追究侵权责任的权利。 9 | 10 | !!! tip "推荐查看在线版" 11 | 12 | 推荐查看本文档的在线版(),而非 PDF 或 Word 版本。 13 | 离线版本的显示效果可能不佳,并且无法获得实时的更新。 14 | -------------------------------------------------------------------------------- /docs/javascripts/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [["\\(", "\\)"]], 4 | displayMath: [["\\[", "\\]"]], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: ".*|", 10 | processHtmlClass: "arithmatex" 11 | } 12 | }; 13 | 14 | document$.subscribe(() => { 15 | MathJax.typesetPromise() 16 | }) 17 | -------------------------------------------------------------------------------- /docs/lwip.md: -------------------------------------------------------------------------------- 1 | # lwIP 库 2 | 3 | 为了方便实验的开发,在代码中包含了一份 lwIP 2.1.2 的源代码(去掉了文档目录)。lwIP 是一个开源的 TCP/IP 网络栈,常用于各种嵌入式场景。 4 | 5 | 对于 lwIP 的详细文档,可以访问[官方文档](https://www.nongnu.org/lwip/2_1_x/index.html)。 6 | 7 | ## 配置 8 | 9 | lwIP 提供了许多可以配置的选项,也包括了许多应用。主要的配置文件是 `include\lwipopts.h`,文件中已经打开了一些常用的选项。你可以修改其中的定义,以打开/关闭 lwIP 的功能。如果你开发的功能需要 lwIP 的配合,请一定记得打开 lwIP 的功能。完整的 lwIP 选项列表可以在 `thirdparty/lwip-2.1.2/src/include/lwip/opt.h` 中找到。 10 | 11 | 建议同学在选择自选功能实现的时候,先查看一下 lwIP 是否需要做相应的设置,如果发现有不兼容的地方,可能需要自行实现客户端 + 服务端,实现起来难度就比较大。 12 | 13 | 另外,部分 lwIP 的功能打开可能需要额外的文件编译进来,此时需要编辑 `meson.build` 文件,添加所需要的文件。 14 | -------------------------------------------------------------------------------- /docs/requirement.md: -------------------------------------------------------------------------------- 1 | # 实验要求 2 | 3 | 本实验的目的是实现一个 TCP 网络栈,单人完成。 4 | 5 | 实验分数由两部分组成,一部分是必选功能共 60 分,一部分是限选功能共 40 分。 6 | 7 | 各项功能见下表,其中打 `*` 号的为必选功能,其余为限选功能。同学们需要实现所有的必选功能,并在剩余的限选功能中选择若干个功能使得这部分分数累计达到或超过 40 分。 8 | 9 | 部分自选功能依赖必选功能的实现。你可以放弃实现一部分必选功能,但扣除的必选部分分数不会由溢出的自选功能分数弥补。 10 | 11 | 评分方法: 12 | 13 | 1. 必选部分(60 分):`sum(scores)` 14 | 2. 自选部分(40 分):`min(sum(scores),40)` 15 | 16 | 在实验报告中,对于所实现的每个功能,请编写一个章节,包括下列内容: 17 | 18 | 1. 标注这是属于下面哪个类别里的哪一项功能 19 | 2. 为了实现这个功能,做了哪些代码改动 20 | 3. 可以展示功能的 Wireshark 截图展示或者命令行输出 21 | 22 | 在选择实现的自选功能的时候,请研究一下如何实现和触发该功能。有的功能可能容易实现,但是不容易触发。另外,请注意,部分功能由 lwip 支持,可以在 `lwipopts.h` 中打开;如果有不支持的功能,可能需要自己实现客户端 + 服务端,难度较大。为了展示实现的客户端功能,可以用自己的协议栈或者 lwip 作为服务端进行测试;同样地,为了展示实现的服务端功能,可以用自己的协议栈或者 lwip 作为客户端进行测试。 23 | 24 | 框架里为各项任务提供了自动化的测例,可以通过 `make test` 命令调用。你也可以适当修改 `src/test` 下的测例,以便于更好地展示你的实验结果,但是修改之处请在最终提交的`实验报告`中注明。 25 | 26 | 主要参考文档: 27 | 28 | - [RFC 793](https://www.rfc-editor.org/rfc/rfc793.html) 和 [RFC 793 勘误](https://www.rfc-editor.org/errata/rfc0793) 29 | - [RFC 1122 Section 4.2 TCP](https://www.rfc-editor.org/rfc/rfc1122#page-82) 30 | 31 | 教学目的: 32 | 33 | 1. 本实验从简易到困难,逐步完善一个 TCP 协议栈的功能,可以让同学理解 TCP 协议设计上的一些思路,并在写代码的过程中理解从设计到实现的区别,并考虑实现上的一些细节。 34 | 2. 锻炼同学对于 RFC 全英文文档的阅读能力,这个能力对于未来的学习和研究是很有必要的,因此在本文档中,不会很细致地用中文把要实现的内容复述一遍,目的是让同学去阅读 RFC 文档进行自学。如果实在不想自学,或者因为时间问题,可以在网上搜索中文博客,但这样做也缺少了一次很重要的锻炼机会。相比论文,RFC 的文字比较详细和朴实,是比较好的英文技术类阅读资料。 35 | 3. 希望同学在未来参加公司面试等场合,被问到 TCP 三次握手的时候,可以自信地说:我做过 TCP 实验,你问的这些,我都写过,我都会。 36 | 4. 希望同学可以从 TCP 协议解决问题的过程中,学习一些设计思路,并借鉴到自己所研究的领域当中。 37 | 38 | ??? question "我英语水平不够怎么办?" 39 | 40 | ??? question "看到大段英文我就头晕怎么办?" 41 | 42 | ??? question "我真的没法读 RFC,助教你饶了我吧!" 43 | 44 | 那助教推荐一个中文 TCP 教程:[小林 x 图解计算机基础](https://xiaolincoding.com/network/3_tcp/tcp_feature.html) 45 | 46 | ## 必选功能(总分 60 分) 47 | ### Step 1. TCP 序列号的对比与生成(sequence number comparison and generation) 48 | 49 | - 实现序列号的对比函数(共四个),注意序列号在可能溢出时大小比较的特殊情况。 50 | - 实现序列号的生成函数,序列号可以从目前的时间戳计算得到。 51 | - 分数:5 52 | - 参考文档:[RFC 793 Section 3.3](https://www.rfc-editor.org/rfc/rfc793.html#section-3.3) 和 [RFC 793 Page 27](https://www.rfc-editor.org/rfc/rfc793.html#page-27) 53 | - 测试 1:给定一些例子,测试对比函数结果是否正确 54 | - 测试 2:每隔一段时间生成一系列的序列号,这些序列号应该不重复 55 | - 代码量:~10 行 56 | - 教学目的:了解序列号的大小关系,如何在整数溢出的情况下,实现保证相对顺序的比较 57 | - 验收要求:通过自动化测试 58 | 59 | ### Step 2. TCP 三次握手连接的建立(3-way handshake) 60 | 61 | - 对于客户端,首先发送 SYN,进入 SYN_SENT 状态;接收到 SYN+ACK 时,发送 ACK 并进入 ESTABLISHED 状态。 62 | - 对于服务端,在 LISTEN 状态时,如果接收到 SYN,新建一个 TCP socket,发送 SYN+ACK 并进入 SYN_RCVD 状态;接收到 ACK 时,进入 ESTABLISHED 状态。 63 | - 分数:10 64 | - 参考文档:[RFC 793 Figure 7](https://www.rfc-editor.org/rfc/rfc793.html#page-23) 和 [RFC 793 Section 3.4](https://www.rfc-editor.org/rfc/rfc793.html#section-3.4) 65 | - 测试 3a:测试客户端逻辑,启动 lab-client 和 lwip-server,在输出日志中检查 TCP 状态机的转移 66 | - 测试 3b:测试服务端逻辑,启动 lwip-client 和 lab-server,在输出日志中检查 TCP 状态机的转移 67 | - 代码量:~50 行 68 | - 教学目的:学习 TCP 建立连接的三次握手过程,初步接触框架中发送 TCP 分组的方法和 TCP 状态机的实现,强化 SYN 占用序列号的记忆 69 | - 验收要求:通过自动化测试 70 | 71 | ### Step 3. 简易的发送和接收逻辑(send & receive) 72 | 73 | - 简易的发送和接收:不考虑丢包和乱序 74 | - 发送:将用户调用 tcp_write 传入的数据写入缓冲区,综合 MSS、待发送的数据大小和对方窗口大小,发送数据 75 | - 接收:将对方发送的数据写入缓冲区,当用户调用 tcp_read 时,从缓冲区复制数据到用户数组 76 | - 分数:5 77 | - 参考文档:[RFC 793 Page 56](https://www.rfc-editor.org/rfc/rfc793.html#page-56) 和 [RFC 793 Page 58](https://www.rfc-editor.org/rfc/rfc793.html#page-58) 和 [RFC 793 Send Sequence Space](https://www.rfc-editor.org/rfc/rfc793.html#page-20) 78 | - 测试 4a:测试客户端逻辑,启动 lab-client 和 lwip-server,在输出日志中检查是否正确完成了 HTTP 请求 79 | - 测试 4b:测试服务端逻辑,启动 lwip-client 和 lab-server,在输出日志中检查是否正确完成了 HTTP 请求 80 | - 代码量:~50 行 81 | - 注意:代码中的`TODO`只是提示这些地方需要添加代码,但是`TODO`之外也有一些地方根据 TCP 协议的标准来需要补充实现 82 | - 注意:如果完成了 Step 3,但还没完成 Step 4,且本测试无法通过,请尝试把 TODO(step4) 的`UNIMPLEMENTED`注释掉再运行测试 83 | - 教学目的:理解 TCP 栈与用户程序交互的方式,TCP 栈内发送缓存和接收缓存的意义,如何维护发送和接收的序列号空间(即 `SND.UNA`,`SND.NXT`,`SND.WND`,`RCV.NXT` 和 `RCV.WND`) 84 | - 验收要求:通过自动化测试 85 | 86 | ### Step 4. TCP 连接的终止(connection termination) 87 | 88 | - 实现 tcp_close 函数:发送 FIN 89 | - 实现接收 FIN 的逻辑 90 | - 分数:10 91 | - 参考文档:[RFC 793 Page 60](https://www.rfc-editor.org/rfc/rfc793.html#page-60) 和 [RFC 793 Page 75](https://www.rfc-editor.org/rfc/rfc793.html#page-75) 92 | - 测试 5a:测试客户端逻辑,启动 lab-client 和 lwip-server,在输出日志中检查是否出现了正确的状态机转移 93 | - 测试 5b:测试服务端逻辑,启动 lwip-client 和 lab-server,在输出日志中检查是否出现了正确的状态机转移 94 | - 教学目的:理解 TCP 连接终止的四次挥手过程,强化 FIN 占用序列号的记忆 95 | - 验收要求:通过自动化测试 96 | 97 | ### Step 5. 访问百度主页(visit baidu homepage) 98 | 99 | - 如果前面四步实现得当,这一步应该直接可以通过 100 | - 给做实验的你一个有成就感的阶段性成果 101 | - 分数:5 102 | - 测试 6:在本地运行 socat 命令(命令:`sudo socat TCP-LISTEN:80,reuseaddr,fork TCP:www.baidu.com:80`),监听 80 端口并转发百度主页;然后运行 lab-client 并设置为 TUN 模式(命令:`make run-lab-client-tun`),那么 lab-client 会通过本机 80 端口访问百度主页 80 端口,并下载到本地 103 | - 注意:部分发行版可能没有自带 socat命令, 需要额外安装 socat 软件包 104 | - 注意:在关闭连接的过程中,当百度的服务器处于`FIN_WAIT_2`状态时,它只能接受`ACK=1, FIN=1`的包,而不会接受`ACK=0, FIN=1`的包,收到`ACK=0, FIN=1`的包时,服务器既不会返回`ACK`,也不会进入`TIME_WAIT`状态。如果你的实现里只能发送`ACK=0, FIN=1`的包,那么你的客户端可能会卡在`LAST_ACK`状态,无法进入`CLOSE`状态。(cr. 尤梓锐) 105 | - 注意:如果采用虚拟机环境,发现无法访问百度,但是前面的 step 均正常,请尝试关闭防火墙(cr. 王楚怡) 106 | - 教学目的:了解一个最小的 TCP 实现所需要的功能,并获得阶段性的成就 107 | - 验收要求:通过自动化测试,并展示下载的百度网页 108 | 109 | ### Step 6. 重传和乱序重排(retransmission and out of order handling) 110 | 111 | - 重传的实现思路是,对于发出去的 TCP 分组,记录在一个列表中,并且启动一个计时器,每一段时间检查一下列表中的 TCP 分组,如果发现有已经被对端 ACK 的,就删掉;否则就再次发送。 112 | - 乱序重排的实现思路是,如果发现 TCP 分组的序列号不等于 RCV.NXT,此时出现了乱序,先把数据保存到列表中,当之后接受到序列号等于 RCV.NXT 的分组时,再将列表中已有的数据拼接起来,写入到接收缓冲区中。 113 | - 分数:10 114 | - 测试 7a:启动 lwip-client 和 lab-server,模拟接收端的丢包,在输出日志中检查是否出现了正确的状态机转移 115 | - 测试 7b:启动 lab-client 和 lab-server,lab-server模拟 HTTP response 的延迟、乱序发送,在输出日志中检查是否出现了正确的状态机转移 116 | - 测例的具体实现机理可以参见源代码。欢迎对这两个测例进行改进,通过其他方式来展现重传和乱序重排的效果 117 | - 注意:有同学反映,有一定概率出现 HTTP response 不完整的情况。经分析可能是 `src/lab/lab-client.cpp` 中 95 行对 `close_and_exit` 函数的执行时间安排得太紧。现在已经做出了修改(cr. 郑皓之) 118 | - 注意:在处理对面回应的 ACK 包时,不仅要排出重传队列中对应的包,也要排出 SendRingBuffer 中对应的包,否则会导致 SendRingBuffer 溢出 119 | - 注意:不仅是普通的数据包可能遭遇乱序的情况,FIN 包也可能会遭遇乱序,比如 FIN 包到达对端的时机过早,之前发送的数据包还没有到达对端,此时不能贸然处理 FIN 包,在乱序重排的时候也需要考虑这类特殊情况(cr. 游文汛) 120 | - 教学目的:理解在网络中出现丢包或者乱序的时候,如何在协议上保持不重不漏、顺序正确地实现数据传输 121 | - 验收要求:通过抓包、日志等方式展示重传和重排,展示场景可以参考自动化测试脚本 122 | 123 | ### Step 7. 实现 Nagle 算法(Nagle's algorithm) 124 | 125 | - 分数:5 126 | - 参考文档:[RFC 896](https://datatracker.ietf.org/doc/html/rfc896) 127 | - 测试 8a:启动 lwip-client 和 lab-server,启用 Nagle 算法,在输出日志中检查是否出现了正确的状态机转移 128 | - 测试 8b:启动 lwip-client 和 lab-server,禁用 Nagle 算法,在输出日志中检查是否出现了正确的状态机转移 129 | - lab-server 以每次发送 13 字节的 `Hello World!\n` 的方式,向客户端返回共计 1300 字节的 Content ,共调用 `tcp_write` 100 余次,产生较多的小数据包 130 | - 请比较开启 Nagle 算法与关闭 Nagle 算法时,TCP 连接的传输效率 131 | - 注意:编译时,我们用 `ENABLE_NAGLE` 这个宏来区分 Nagle 算法是否启用。如果存在 `ENABLE_NAGLE` 这个宏,你的 TCP 实现就应该启用 Nagle 算法 132 | - 教学目的:学习并实现一个经典的 TCP 上的优化 133 | - 验收要求:展示开启 Nagle 算法时 TCP 连接的传输效率相比关闭 Nagle 算法时有所提高,展示场景可以参考自动化测试脚本 134 | 135 | ### Step 8. 实现慢启动,冲突避免,快速重传和快速恢复(slow start, congestion avoidance and fast retransmit/recovery) 136 | 137 | - 添加 cwnd 变量,表示拥塞窗口大小。还需要定义 ssthresh,表示慢启动的阈值。 138 | - cwnd 初始状态下设为 MSS 的一个倍数,在慢驱动阶段(cwnd < ssthresh),每次接收到 ACK,都会增加 cwnd。 139 | - 当 cwnd > ssthresh 时,进入冲突避免阶段,此时采用新的方式增加 cwnd。 140 | - 当发现重复的 ACK 时,采用快速重传算法,更新 cwnd 和 ssthresh。 141 | - 分数:10 142 | - 参考文档:[RFC 5681 Section 3.1](https://datatracker.ietf.org/doc/html/rfc5681#section-3.1) 和 [RFC 5681 Section 3.2](https://datatracker.ietf.org/doc/html/rfc5681#section-3.2) 143 | - 测试 9a:启动 lwip-client 和 lab-server,在输出日志中检查是否出现了正确的状态机转移 144 | - 测试 9b:启动 lwip-client 和 lab-server,在输出日志中检查是否出现了正确的状态机转移 145 | - 在测试中在模拟的网络层中实现一个类似漏桶算法的限速方法,当客户端平均接收带宽超过 10KiB/s 时,通过丢包(在客户端接收时丢弃)模拟网络拥塞 146 | - 测试 9a 模拟极端的网络拥塞情况,带宽超过上限时,丢弃所有到达的包;测试 9b 模拟偶然的网络拥塞情况,带宽超过上限时,以一定概率丢弃到达的包 147 | - 请比较测试 9a 和 9b 下,TCP 连接的传输效率 148 | - 注:为了便于显示拥塞控制算法的效果,你可以修改 MTU 以便观察 149 | - 教学目的:学习并实现经典的拥塞控制协议,并理解 TCP 协议栈是如何通过丢包和重复的 ACK 来判断链路上是否拥挤的,拥挤时要如何让出带宽来缓解网络的拥挤程度 150 | - 验收要求:通过抓包、日志等方式展示慢启动和冲突避免阶段 cwnd 的变化模式,以及在丢包发生后算法的行为,展示场景可以参考自动化测试脚本 151 | 152 | ## 限选功能(总分 40 分) 153 | 154 | ### Feature 1. 实现 TCP BBR 拥塞控制算法 155 | 156 | - 分数:40 157 | - 参考文档:[RFC BBR draft](https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-00) 158 | - 由于 lwIP 的一些实现细节,不建议在本部分实验中使用 lwIP 库,客户端和服务器都使用本实验实现的程序即可 159 | - 教学目的:学习并实现一个较为复杂和现代的拥塞控制算法 160 | - 验收要求:能介绍 TCP BBR 拥塞控制算法的核心思想 161 | 162 | #### Step 1. 实现 pacing 功能 163 | 164 | - 分数:5 165 | - BBR 算法中使用了 pacing 功能,通过控制发送时间间隔的方式控制发送速率。 166 | - 在这一步中,你很可能需要使用定时器。你可以在“实验框架”一节中获得一些提示。 167 | - 完成这一步后,你应能使发送端按照给定速率发送。 168 | - 验收要求:能通过控制 pacing 速率,使相同流以不同时间完成传输。 169 | 170 | #### Step 2. 构建 rate sample 171 | 172 | - 分数:10 173 | - 参考文档:[RFC Delivery Rate Estimation draft](https://datatracker.ietf.org/doc/html/draft-cheng-iccrg-delivery-rate-estimation) 174 | - BBR 算法中需要根据收到的 ACK 还原网络情况。因此,需要针对每一个 ACK 都构建一个 rate sample,并将其作为参数传入拥塞控制算法对于 ACK 的响应函数中。 175 | - rate sample 中应当至少包含如下信息:RTT、delivery rate。 176 | - 为了构建 rate sample,可能需要维护一些额外的数据结构。 177 | - 完成这一步后,你应能在收到每一个 ACK 后输出一组统计信息。 178 | - 验收要求:能展示收到每个 ACK 后的统计信息;展示的 RTT 与配置的链路延迟相匹配;展示的 delivery rate 与 pacing rate 设置的速率相匹配 179 | 180 | #### Step 3. 实现 BBR 算法 181 | 182 | - 分数:25 183 | - 你已经完成了必要的准备工作。现在,将 BBR 算法实现出来吧! 184 | - 提示:BBR 算法同时使用 cwnd 和 pacing rate 来控制发送,你只需理解在算法的几个状态以及核心状态的探测循环中 cwnd 和 pacing rate 应当如何设置,并正确计算和设置即可。 185 | - 验收要求:能展示 BBR 算法的工作循环;能展示 BBR 算法在遇到丢包时的行为。如果能展示出比必选功能 Step 8 更高的带宽和更低的延迟就更好了。 186 | - 验收提示:可以参考必选功能 Step 8 的自动化测试脚本模拟网络环境;推荐使用统计量随时间变化的图表来展示 BBR 的行为。 187 | 188 | ### Feature 2. 实现 SACK 189 | 190 | - 由于 lwIP 不支持 SACK,此处需要用 TUN 模式进行测试。 191 | - 分数:20 192 | - 参考文档:[RFC 2018](https://datatracker.ietf.org/doc/html/rfc2018) [RFC 2018 勘误](https://www.rfc-editor.org/errata_search.php?rfc=2018&rec_status=0) 193 | - 教学目的:学习并实现一个比较复杂一些的 TCP Option,理解如何通过添加简单的数据结构和扩展的方式来解决原有协议的不足 194 | - 验收要求:能展示一个发出 SACK 的场景,并展示发出的 SACK 包;展示 SACK 接收方利用 SACK 实现的功能,通过对比不使用 SACK 的行为展示 SACK 的作用 195 | 196 | !!! tip "BBRv2" 197 | 198 | 目前 BBR 已经经过改进发布了 BBRv2 版本([BBR draft](https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-02)),这个版本为 BBR 添加了 199 | 对丢包的响应,使得 BBR 能和其他基于丢包的算法共存,且稳定性有所提升。如果有 SACK 支持,BBRv2 能更精准地处理丢包事件。如果你对 BBR 和 SACK 都很感兴趣,不妨在实现 SACK 后继续实现 200 | BBRv2,进一步挑战自己。如果你只计划实现 BBR,也不妨了解一下 BBRv2 进行了怎样的改进。 -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # 配置环境 2 | 3 | 该实验代码由 `git` 管理,构建软件需要 `meson` 和 `ninja`,还需要编译器 `gcc` 或者 `clang`,请用系统的包管理器进行安装。 4 | 5 | 以 Debian(Ubuntu)为例,可以用 APT 安装依赖: 6 | 7 | ```shell 8 | $ sudo apt install -y git meson ninja-build gcc g++ 9 | ``` 10 | 11 | 如果使用的是 Ubuntu 18.04、Debian 9 或更老的版本,请用 pip3 安装最新 meson: 12 | 13 | ```shell 14 | $ pip3 install meson 15 | ``` 16 | 17 | 因为官方 APT 源中 meson 的版本不够新。如果 meson 版本还是不对,检查一下运行的 meson 是否是 pip3 安装的 meson(比如 `~/.local/bin/meson`)。 18 | 19 | ## 支持的操作系统 20 | 21 | 实验框架保证支持下列的操作系统(发行版): 22 | 23 | 1. Ubuntu 18.04 及更新 24 | 2. Debian 9 及更新 25 | 3. macOS High Sierra 及更新 26 | 27 | 在其他的一些环境(Windows w/o Visual Studio,WSL,OpenBSD 等)中代码可能也可以编译和运行,欢迎同学进行测试;但不保证可以正常工作和运行。 28 | -------------------------------------------------------------------------------- /docs/submission.md: -------------------------------------------------------------------------------- 1 | # 作业提交 2 | 3 | 作业应该提交一个压缩包,包含如下内容: 4 | 5 | 1. report 目录:里面存放了实验报告,如果有报告所需要的一些图片和 pcap 文件,也放在这个目录。 6 | 2. code 目录:即同学们修改后的 TCP-Lab 仓库。 7 | 8 | 压缩包格式建议为 .zip 或者 .tar.gz。 9 | 10 | ## 实验报告 11 | 12 | 实验报告需要有如下几个部分: 13 | 14 | 1. 开头:姓名,学号和班级。 15 | 2. 正文:对于实现的各个功能,编写一个小节。每个小节应该描述:代码修改,效果截图。 16 | 3. 结尾:留下你对本实验的吐槽和建议。 17 | 18 | 推荐采用 Markdown 或者 LaTeX 编写实验报告。用 Markdown 时请注意图片要用相对路径。 19 | 20 | 对于需要抓包体现的功能,尽量在抓到想要的包以后就停止,让 pcap 文件小一些。 21 | 22 | ## 代码目录 23 | 24 | 原样复制同学修改后的代码即可。 25 | 26 | 如果在开发时用 Git 做版本管理,推荐把 .git 目录也提交到压缩包中。 -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: TCP 实验文档 2 | site_description: 'Documentation for Labs of Network Training' 3 | site_author: 'TCP Lab Developers' 4 | copyright: 'Copyright © 2021-2022 Department of Computer Science and Technology, Tsinghua University. All Rights Reserved.' 5 | 6 | theme: 7 | name: 'material' 8 | language: 'zh' 9 | custom_dir: 'theme-override/' 10 | icon: 11 | logo: material/memory 12 | repo: fontawesome/brands/github 13 | features: 14 | # - navigation.tabs 15 | # - navigation.instant 16 | 17 | repo_name: 'thu-cs-lab/TCP-Lab-Docs' 18 | repo_url: 'https://github.com/thu-cs-lab/TCP-Lab-Docs' 19 | 20 | extra: 21 | pagetime: 'on' 22 | 23 | nav: 24 | - 计算机网络专题训练 TCP 实验: 25 | - 总述: index.md 26 | - 实验要求: requirement.md 27 | - 配置环境: setup.md 28 | - 实验框架: framework.md 29 | - lwIP 库: lwip.md 30 | - 作业提交: submission.md 31 | - FAQ: faq.md 32 | 33 | extra_javascript: 34 | - javascripts/mathjax.js 35 | - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 36 | 37 | plugins: 38 | - search 39 | - git-revision-date-localized: 40 | fallback_to_build_date: true 41 | locale: zh 42 | - git-authors: 43 | show_contribution: true 44 | show_line_count: true 45 | count_empty_lines: true 46 | fallback_to_empty: false 47 | 48 | markdown_extensions: 49 | - admonition 50 | - codehilite: 51 | guess_lang: false 52 | linenums: true 53 | - footnotes 54 | - def_list 55 | - meta 56 | - toc: 57 | permalink: true 58 | - pymdownx.arithmatex: 59 | generic: true 60 | - pymdownx.betterem: 61 | smart_enable: all 62 | - pymdownx.caret 63 | - pymdownx.critic 64 | - pymdownx.details 65 | - pymdownx.emoji: 66 | emoji_generator: !!python/name:pymdownx.emoji.to_svg 67 | - pymdownx.inlinehilite 68 | - pymdownx.magiclink 69 | - pymdownx.mark 70 | - pymdownx.smartsymbols 71 | - pymdownx.superfences 72 | - pymdownx.tasklist: 73 | custom_checkbox: true 74 | - pymdownx.tilde 75 | 76 | extra: 77 | analytics: 78 | provider: google 79 | property: G-69YPS2LP7R 80 | -------------------------------------------------------------------------------- /pandoc/docx.yml: -------------------------------------------------------------------------------- 1 | from: markdown+grid_tables+table_captions 2 | verbosity: WARNING 3 | 4 | standalone: true 5 | table-of-contents: true 6 | -------------------------------------------------------------------------------- /pandoc/pdf.yml: -------------------------------------------------------------------------------- 1 | from: markdown+grid_tables+table_captions 2 | verbosity: WARNING 3 | 4 | variables: 5 | documentclass: ctexart 6 | classoption: 7 | - fontset=ubuntu 8 | geometry: 9 | - a4paper 10 | - top=2.54cm 11 | - bottom=2.54cm 12 | - left=3.18cm 13 | - right=3.18cm 14 | indent: yes 15 | numbersections: yes 16 | title: 计算机网络原理实验指导书 17 | author: 计算机网络原理实验教学团队 18 | date: '\today' 19 | header-includes: | 20 | \usepackage{enumitem} 21 | \setlistdepth{9} 22 | \newlist{deepenum}{enumerate}{9} 23 | \setlist[deepenum,1]{label=(\arabic*)} 24 | \setlist[deepenum,2]{label=(\Roman*)} 25 | \setlist[deepenum,3]{label=(\Alph*)} 26 | \setlist[deepenum,4]{label=(\roman*)} 27 | \setlist[deepenum,5]{label=(\alph*)} 28 | \setlist[deepenum,6]{label=(\arabic*)} 29 | \setlist[deepenum,7]{label=(\Roman*)} 30 | \setlist[deepenum,8]{label=(\Alph*)} 31 | \setlist[deepenum,9]{label=(\roman*)} 32 | \renewenvironment{enumerate}{\begin{deepenum}}{\end{deepenum}} 33 | 34 | \definecolor{codegreen}{rgb}{0,0.6,0} 35 | \definecolor{codegray}{rgb}{0.5,0.5,0.5} 36 | \definecolor{codepurple}{rgb}{0.58,0,0.82} 37 | 38 | \lstset{ 39 | commentstyle=\color{codegreen}, 40 | keywordstyle=\color{magenta}, 41 | numberstyle=\tiny\color{codegray}, 42 | stringstyle=\color{codepurple}, 43 | basicstyle=\ttfamily, 44 | prebreak=\raisebox{0ex}[0ex][0ex]{\ensuremath{\hookleftarrow}}, 45 | columns=fullflexible, 46 | keepspaces=true, 47 | showspaces=false, 48 | showtabs=false, 49 | breaklines=true, 50 | showstringspaces=false, 51 | breakatwhitespace=true, 52 | frame=single, 53 | framesep=3pt, 54 | xleftmargin=12pt, 55 | tabsize=4, 56 | captionpos=b, 57 | numbers=left 58 | } 59 | 60 | pdf-engine: xelatex 61 | standalone: true 62 | table-of-contents: true 63 | listings: true 64 | 65 | -------------------------------------------------------------------------------- /pandoc/remove_duplicate_title.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i -r 's/^(# .*)\{: .page-title\}/\1/' $1 4 | sed -i -r '/.*\{: .page-title\}/d' $1 5 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "babel" 5 | version = "2.17.0" 6 | description = "Internationalization utilities" 7 | optional = false 8 | python-versions = ">=3.8" 9 | groups = ["dev"] 10 | files = [ 11 | {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, 12 | {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, 13 | ] 14 | 15 | [package.dependencies] 16 | pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} 17 | 18 | [package.extras] 19 | dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] 20 | 21 | [[package]] 22 | name = "certifi" 23 | version = "2025.1.31" 24 | description = "Python package for providing Mozilla's CA Bundle." 25 | optional = false 26 | python-versions = ">=3.6" 27 | groups = ["dev"] 28 | files = [ 29 | {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, 30 | {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, 31 | ] 32 | 33 | [[package]] 34 | name = "charset-normalizer" 35 | version = "3.4.1" 36 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 37 | optional = false 38 | python-versions = ">=3.7" 39 | groups = ["dev"] 40 | files = [ 41 | {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, 42 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, 43 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, 44 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, 45 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, 46 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, 47 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, 48 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, 49 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, 50 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, 51 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, 52 | {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, 53 | {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, 54 | {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, 55 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, 56 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, 57 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, 58 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, 59 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, 60 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, 61 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, 62 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, 63 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, 64 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, 65 | {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, 66 | {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, 67 | {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, 68 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, 69 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, 70 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, 71 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, 72 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, 73 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, 74 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, 75 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, 76 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, 77 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, 78 | {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, 79 | {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, 80 | {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, 81 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, 82 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, 83 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, 84 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, 85 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, 86 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, 87 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, 88 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, 89 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, 90 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, 91 | {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, 92 | {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, 93 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, 94 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, 95 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, 96 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, 97 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, 98 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, 99 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, 100 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, 101 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, 102 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, 103 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, 104 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, 105 | {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, 106 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, 107 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, 108 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, 109 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, 110 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, 111 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, 112 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, 113 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, 114 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, 115 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, 116 | {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, 117 | {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, 118 | {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, 119 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, 120 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, 121 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, 122 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, 123 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, 124 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, 125 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, 126 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, 127 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, 128 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, 129 | {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, 130 | {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, 131 | {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, 132 | {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, 133 | ] 134 | 135 | [[package]] 136 | name = "click" 137 | version = "8.1.8" 138 | description = "Composable command line interface toolkit" 139 | optional = false 140 | python-versions = ">=3.7" 141 | groups = ["dev"] 142 | files = [ 143 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 144 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 145 | ] 146 | 147 | [package.dependencies] 148 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 149 | 150 | [[package]] 151 | name = "colorama" 152 | version = "0.4.6" 153 | description = "Cross-platform colored terminal text." 154 | optional = false 155 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 156 | groups = ["dev"] 157 | files = [ 158 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 159 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 160 | ] 161 | 162 | [[package]] 163 | name = "ghp-import" 164 | version = "2.1.0" 165 | description = "Copy your docs directly to the gh-pages branch." 166 | optional = false 167 | python-versions = "*" 168 | groups = ["dev"] 169 | files = [ 170 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 171 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 172 | ] 173 | 174 | [package.dependencies] 175 | python-dateutil = ">=2.8.1" 176 | 177 | [package.extras] 178 | dev = ["flake8", "markdown", "twine", "wheel"] 179 | 180 | [[package]] 181 | name = "gitdb" 182 | version = "4.0.12" 183 | description = "Git Object Database" 184 | optional = false 185 | python-versions = ">=3.7" 186 | groups = ["dev"] 187 | files = [ 188 | {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, 189 | {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, 190 | ] 191 | 192 | [package.dependencies] 193 | smmap = ">=3.0.1,<6" 194 | 195 | [[package]] 196 | name = "gitpython" 197 | version = "3.1.44" 198 | description = "GitPython is a Python library used to interact with Git repositories" 199 | optional = false 200 | python-versions = ">=3.7" 201 | groups = ["dev"] 202 | files = [ 203 | {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, 204 | {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, 205 | ] 206 | 207 | [package.dependencies] 208 | gitdb = ">=4.0.1,<5" 209 | 210 | [package.extras] 211 | doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] 212 | test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] 213 | 214 | [[package]] 215 | name = "idna" 216 | version = "3.10" 217 | description = "Internationalized Domain Names in Applications (IDNA)" 218 | optional = false 219 | python-versions = ">=3.6" 220 | groups = ["dev"] 221 | files = [ 222 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 223 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 224 | ] 225 | 226 | [package.extras] 227 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 228 | 229 | [[package]] 230 | name = "importlib-metadata" 231 | version = "8.5.0" 232 | description = "Read metadata from Python packages" 233 | optional = false 234 | python-versions = ">=3.8" 235 | groups = ["dev"] 236 | markers = "python_version < \"3.10\"" 237 | files = [ 238 | {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, 239 | {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, 240 | ] 241 | 242 | [package.dependencies] 243 | zipp = ">=3.20" 244 | 245 | [package.extras] 246 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 247 | cover = ["pytest-cov"] 248 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 249 | enabler = ["pytest-enabler (>=2.2)"] 250 | perf = ["ipython"] 251 | test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] 252 | type = ["pytest-mypy"] 253 | 254 | [[package]] 255 | name = "jinja2" 256 | version = "3.1.5" 257 | description = "A very fast and expressive template engine." 258 | optional = false 259 | python-versions = ">=3.7" 260 | groups = ["dev"] 261 | files = [ 262 | {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, 263 | {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, 264 | ] 265 | 266 | [package.dependencies] 267 | MarkupSafe = ">=2.0" 268 | 269 | [package.extras] 270 | i18n = ["Babel (>=2.7)"] 271 | 272 | [[package]] 273 | name = "markdown" 274 | version = "3.7" 275 | description = "Python implementation of John Gruber's Markdown." 276 | optional = false 277 | python-versions = ">=3.8" 278 | groups = ["dev"] 279 | files = [ 280 | {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, 281 | {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, 282 | ] 283 | 284 | [package.dependencies] 285 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 286 | 287 | [package.extras] 288 | docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] 289 | testing = ["coverage", "pyyaml"] 290 | 291 | [[package]] 292 | name = "markupsafe" 293 | version = "2.1.5" 294 | description = "Safely add untrusted strings to HTML/XML markup." 295 | optional = false 296 | python-versions = ">=3.7" 297 | groups = ["dev"] 298 | files = [ 299 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 300 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 301 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 302 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 303 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 304 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 305 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 306 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 307 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 308 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 309 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 310 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 311 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 312 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 313 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 314 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 315 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 316 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 317 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 318 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 319 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 320 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 321 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 322 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 323 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 324 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 325 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 326 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 327 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 328 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 329 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 330 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 331 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 332 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 333 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 334 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 335 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 336 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 337 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 338 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 339 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 340 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 341 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 342 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 343 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 344 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 345 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 346 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 347 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 348 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 349 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 350 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 351 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 352 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 353 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 354 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 355 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 356 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 357 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 358 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 359 | ] 360 | 361 | [[package]] 362 | name = "mergedeep" 363 | version = "1.3.4" 364 | description = "A deep merge function for 🐍." 365 | optional = false 366 | python-versions = ">=3.6" 367 | groups = ["dev"] 368 | files = [ 369 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 370 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 371 | ] 372 | 373 | [[package]] 374 | name = "mkdocs" 375 | version = "1.6.1" 376 | description = "Project documentation with Markdown." 377 | optional = false 378 | python-versions = ">=3.8" 379 | groups = ["dev"] 380 | files = [ 381 | {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, 382 | {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, 383 | ] 384 | 385 | [package.dependencies] 386 | click = ">=7.0" 387 | colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} 388 | ghp-import = ">=1.0" 389 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 390 | jinja2 = ">=2.11.1" 391 | markdown = ">=3.3.6" 392 | markupsafe = ">=2.0.1" 393 | mergedeep = ">=1.3.4" 394 | mkdocs-get-deps = ">=0.2.0" 395 | packaging = ">=20.5" 396 | pathspec = ">=0.11.1" 397 | pyyaml = ">=5.1" 398 | pyyaml-env-tag = ">=0.1" 399 | watchdog = ">=2.0" 400 | 401 | [package.extras] 402 | i18n = ["babel (>=2.9.0)"] 403 | min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] 404 | 405 | [[package]] 406 | name = "mkdocs-get-deps" 407 | version = "0.2.0" 408 | description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" 409 | optional = false 410 | python-versions = ">=3.8" 411 | groups = ["dev"] 412 | files = [ 413 | {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, 414 | {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, 415 | ] 416 | 417 | [package.dependencies] 418 | importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} 419 | mergedeep = ">=1.3.4" 420 | platformdirs = ">=2.2.0" 421 | pyyaml = ">=5.1" 422 | 423 | [[package]] 424 | name = "mkdocs-git-authors-plugin" 425 | version = "0.7.2" 426 | description = "Mkdocs plugin to display git authors of a page" 427 | optional = false 428 | python-versions = ">=3.7" 429 | groups = ["dev"] 430 | files = [ 431 | {file = "mkdocs-git-authors-plugin-0.7.2.tar.gz", hash = "sha256:f541730e4cabdafa0ac758c94d28ba5e8ddca4c859e5de4c89f1226cb6ccd0ad"}, 432 | {file = "mkdocs_git_authors_plugin-0.7.2-py3-none-any.whl", hash = "sha256:c8a2784a867db79ad3b477a96ee96875d17b09192b6d3be71f08df25afff76c4"}, 433 | ] 434 | 435 | [package.dependencies] 436 | mkdocs = ">=1.0" 437 | 438 | [[package]] 439 | name = "mkdocs-git-revision-date-localized-plugin" 440 | version = "1.3.0" 441 | description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." 442 | optional = false 443 | python-versions = ">=3.8" 444 | groups = ["dev"] 445 | files = [ 446 | {file = "mkdocs_git_revision_date_localized_plugin-1.3.0-py3-none-any.whl", hash = "sha256:c99377ee119372d57a9e47cff4e68f04cce634a74831c06bc89b33e456e840a1"}, 447 | {file = "mkdocs_git_revision_date_localized_plugin-1.3.0.tar.gz", hash = "sha256:439e2f14582204050a664c258861c325064d97cdc848c541e48bb034a6c4d0cb"}, 448 | ] 449 | 450 | [package.dependencies] 451 | babel = ">=2.7.0" 452 | GitPython = "*" 453 | mkdocs = ">=1.0" 454 | pytz = "*" 455 | 456 | [package.extras] 457 | all = ["GitPython", "babel (>=2.7.0)", "click", "codecov", "mkdocs (>=1.0)", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkdocs-material", "mkdocs-static-i18n", "pytest", "pytest-cov", "pytz"] 458 | base = ["GitPython", "babel (>=2.7.0)", "mkdocs (>=1.0)", "pytz"] 459 | dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkdocs-material", "mkdocs-static-i18n", "pytest", "pytest-cov"] 460 | 461 | [[package]] 462 | name = "mkdocs-material" 463 | version = "9.6.3" 464 | description = "Documentation that simply works" 465 | optional = false 466 | python-versions = ">=3.8" 467 | groups = ["dev"] 468 | files = [ 469 | {file = "mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf"}, 470 | {file = "mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832"}, 471 | ] 472 | 473 | [package.dependencies] 474 | babel = ">=2.10,<3.0" 475 | colorama = ">=0.4,<1.0" 476 | jinja2 = ">=3.0,<4.0" 477 | markdown = ">=3.2,<4.0" 478 | mkdocs = ">=1.6,<2.0" 479 | mkdocs-material-extensions = ">=1.3,<2.0" 480 | paginate = ">=0.5,<1.0" 481 | pygments = ">=2.16,<3.0" 482 | pymdown-extensions = ">=10.2,<11.0" 483 | regex = ">=2022.4" 484 | requests = ">=2.26,<3.0" 485 | 486 | [package.extras] 487 | git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] 488 | imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] 489 | recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] 490 | 491 | [[package]] 492 | name = "mkdocs-material-extensions" 493 | version = "1.3.1" 494 | description = "Extension pack for Python Markdown and MkDocs Material." 495 | optional = false 496 | python-versions = ">=3.8" 497 | groups = ["dev"] 498 | files = [ 499 | {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, 500 | {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, 501 | ] 502 | 503 | [[package]] 504 | name = "packaging" 505 | version = "24.2" 506 | description = "Core utilities for Python packages" 507 | optional = false 508 | python-versions = ">=3.8" 509 | groups = ["dev"] 510 | files = [ 511 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 512 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 513 | ] 514 | 515 | [[package]] 516 | name = "paginate" 517 | version = "0.5.7" 518 | description = "Divides large result sets into pages for easier browsing" 519 | optional = false 520 | python-versions = "*" 521 | groups = ["dev"] 522 | files = [ 523 | {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, 524 | {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, 525 | ] 526 | 527 | [package.extras] 528 | dev = ["pytest", "tox"] 529 | lint = ["black"] 530 | 531 | [[package]] 532 | name = "pathspec" 533 | version = "0.12.1" 534 | description = "Utility library for gitignore style pattern matching of file paths." 535 | optional = false 536 | python-versions = ">=3.8" 537 | groups = ["dev"] 538 | files = [ 539 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 540 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 541 | ] 542 | 543 | [[package]] 544 | name = "platformdirs" 545 | version = "4.3.6" 546 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 547 | optional = false 548 | python-versions = ">=3.8" 549 | groups = ["dev"] 550 | files = [ 551 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 552 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 553 | ] 554 | 555 | [package.extras] 556 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 557 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 558 | type = ["mypy (>=1.11.2)"] 559 | 560 | [[package]] 561 | name = "pygments" 562 | version = "2.19.1" 563 | description = "Pygments is a syntax highlighting package written in Python." 564 | optional = false 565 | python-versions = ">=3.8" 566 | groups = ["dev"] 567 | files = [ 568 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 569 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 570 | ] 571 | 572 | [package.extras] 573 | windows-terminal = ["colorama (>=0.4.6)"] 574 | 575 | [[package]] 576 | name = "pymdown-extensions" 577 | version = "10.14.3" 578 | description = "Extension pack for Python Markdown." 579 | optional = false 580 | python-versions = ">=3.8" 581 | groups = ["dev"] 582 | files = [ 583 | {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, 584 | {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, 585 | ] 586 | 587 | [package.dependencies] 588 | markdown = ">=3.6" 589 | pyyaml = "*" 590 | 591 | [package.extras] 592 | extra = ["pygments (>=2.19.1)"] 593 | 594 | [[package]] 595 | name = "python-dateutil" 596 | version = "2.9.0.post0" 597 | description = "Extensions to the standard Python datetime module" 598 | optional = false 599 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 600 | groups = ["dev"] 601 | files = [ 602 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 603 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 604 | ] 605 | 606 | [package.dependencies] 607 | six = ">=1.5" 608 | 609 | [[package]] 610 | name = "pytz" 611 | version = "2025.1" 612 | description = "World timezone definitions, modern and historical" 613 | optional = false 614 | python-versions = "*" 615 | groups = ["dev"] 616 | files = [ 617 | {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, 618 | {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, 619 | ] 620 | 621 | [[package]] 622 | name = "pyyaml" 623 | version = "6.0.2" 624 | description = "YAML parser and emitter for Python" 625 | optional = false 626 | python-versions = ">=3.8" 627 | groups = ["dev"] 628 | files = [ 629 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 630 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 631 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 632 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 633 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 634 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 635 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 636 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 637 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 638 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 639 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 640 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 641 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 642 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 643 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 644 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 645 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 646 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 647 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 648 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 649 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 650 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 651 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 652 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 653 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 654 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 655 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 656 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 657 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 658 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 659 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 660 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 661 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 662 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 663 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 664 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 665 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 666 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 667 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 668 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 669 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 670 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 671 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 672 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 673 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 674 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 675 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 676 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 677 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 678 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 679 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 680 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 681 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 682 | ] 683 | 684 | [[package]] 685 | name = "pyyaml-env-tag" 686 | version = "0.1" 687 | description = "A custom YAML tag for referencing environment variables in YAML files. " 688 | optional = false 689 | python-versions = ">=3.6" 690 | groups = ["dev"] 691 | files = [ 692 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 693 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 694 | ] 695 | 696 | [package.dependencies] 697 | pyyaml = "*" 698 | 699 | [[package]] 700 | name = "regex" 701 | version = "2024.11.6" 702 | description = "Alternative regular expression module, to replace re." 703 | optional = false 704 | python-versions = ">=3.8" 705 | groups = ["dev"] 706 | files = [ 707 | {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, 708 | {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, 709 | {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, 710 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, 711 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, 712 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, 713 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, 714 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, 715 | {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, 716 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, 717 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, 718 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, 719 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, 720 | {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, 721 | {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, 722 | {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, 723 | {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, 724 | {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, 725 | {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, 726 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, 727 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, 728 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, 729 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, 730 | {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, 731 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, 732 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, 733 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, 734 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, 735 | {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, 736 | {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, 737 | {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, 738 | {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, 739 | {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, 740 | {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, 741 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, 742 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, 743 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, 744 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, 745 | {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, 746 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, 747 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, 748 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, 749 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, 750 | {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, 751 | {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, 752 | {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, 753 | {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, 754 | {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, 755 | {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, 756 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, 757 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, 758 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, 759 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, 760 | {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, 761 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, 762 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, 763 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, 764 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, 765 | {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, 766 | {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, 767 | {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, 768 | {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, 769 | {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, 770 | {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, 771 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, 772 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, 773 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, 774 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, 775 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, 776 | {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, 777 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, 778 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, 779 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, 780 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, 781 | {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, 782 | {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, 783 | {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, 784 | {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, 785 | {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, 786 | {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, 787 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, 788 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, 789 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, 790 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, 791 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, 792 | {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, 793 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, 794 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, 795 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, 796 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, 797 | {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, 798 | {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, 799 | {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, 800 | {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, 801 | ] 802 | 803 | [[package]] 804 | name = "requests" 805 | version = "2.32.3" 806 | description = "Python HTTP for Humans." 807 | optional = false 808 | python-versions = ">=3.8" 809 | groups = ["dev"] 810 | files = [ 811 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 812 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 813 | ] 814 | 815 | [package.dependencies] 816 | certifi = ">=2017.4.17" 817 | charset-normalizer = ">=2,<4" 818 | idna = ">=2.5,<4" 819 | urllib3 = ">=1.21.1,<3" 820 | 821 | [package.extras] 822 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 823 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 824 | 825 | [[package]] 826 | name = "six" 827 | version = "1.17.0" 828 | description = "Python 2 and 3 compatibility utilities" 829 | optional = false 830 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 831 | groups = ["dev"] 832 | files = [ 833 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 834 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 835 | ] 836 | 837 | [[package]] 838 | name = "smmap" 839 | version = "5.0.2" 840 | description = "A pure Python implementation of a sliding window memory map manager" 841 | optional = false 842 | python-versions = ">=3.7" 843 | groups = ["dev"] 844 | files = [ 845 | {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, 846 | {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, 847 | ] 848 | 849 | [[package]] 850 | name = "urllib3" 851 | version = "2.2.3" 852 | description = "HTTP library with thread-safe connection pooling, file post, and more." 853 | optional = false 854 | python-versions = ">=3.8" 855 | groups = ["dev"] 856 | files = [ 857 | {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, 858 | {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, 859 | ] 860 | 861 | [package.extras] 862 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 863 | h2 = ["h2 (>=4,<5)"] 864 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 865 | zstd = ["zstandard (>=0.18.0)"] 866 | 867 | [[package]] 868 | name = "watchdog" 869 | version = "4.0.2" 870 | description = "Filesystem events monitoring" 871 | optional = false 872 | python-versions = ">=3.8" 873 | groups = ["dev"] 874 | files = [ 875 | {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, 876 | {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, 877 | {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, 878 | {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, 879 | {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, 880 | {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, 881 | {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, 882 | {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, 883 | {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, 884 | {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, 885 | {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, 886 | {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, 887 | {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, 888 | {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, 889 | {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, 890 | {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, 891 | {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, 892 | {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, 893 | {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, 894 | {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, 895 | {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, 896 | {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, 897 | {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, 898 | {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, 899 | {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, 900 | {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, 901 | {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, 902 | {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, 903 | {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, 904 | {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, 905 | {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, 906 | {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, 907 | {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, 908 | {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, 909 | {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, 910 | ] 911 | 912 | [package.extras] 913 | watchmedo = ["PyYAML (>=3.10)"] 914 | 915 | [[package]] 916 | name = "zipp" 917 | version = "3.20.2" 918 | description = "Backport of pathlib-compatible object wrapper for zip files" 919 | optional = false 920 | python-versions = ">=3.8" 921 | groups = ["dev"] 922 | markers = "python_version < \"3.10\"" 923 | files = [ 924 | {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, 925 | {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, 926 | ] 927 | 928 | [package.extras] 929 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] 930 | cover = ["pytest-cov"] 931 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 932 | enabler = ["pytest-enabler (>=2.2)"] 933 | test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] 934 | type = ["pytest-mypy"] 935 | 936 | [metadata] 937 | lock-version = "2.1" 938 | python-versions = "^3.8" 939 | content-hash = "9a4223d870e292e44a732ef30e691e39ea646a4de2d8f5a3abd754f69b4e229f" 940 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "tcp-lab-docs" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Jiajie Chen "] 6 | package-mode = false 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | 11 | [tool.poetry.dev-dependencies] 12 | mkdocs = "^1.3.1" 13 | mkdocs-material = "^9.4.8" 14 | mkdocs-git-authors-plugin = "^0.7.2" 15 | mkdocs-git-revision-date-localized-plugin = "^1.1.0" 16 | 17 | [build-system] 18 | requires = ["poetry-core>=1.0.0"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.2.4 2 | mkdocs-material==8.2.1 3 | mkdocs-git-revision-date-localized-plugin==1.0.0 4 | mkdocs-git-authors-plugin==0.6.4 5 | nltk>=3.7 6 | -------------------------------------------------------------------------------- /theme-override/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {{ super() }} 4 | 5 | {% if git_page_authors %} 6 |
7 | 8 | 作者:{{ git_page_authors | default('enable mkdocs-git-authors-plugin') }} 9 | 10 |
11 | {% endif %} 12 | 13 |

{{ lang.t("meta.comments") }}

14 | 28 | {% endblock %} --------------------------------------------------------------------------------