From 73f9faa536c22addd73d960c58a66b9d8a25726e Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:58:56 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20:green=5Fheart:=20=E9=94=81=E5=AE=9A?= =?UTF-8?q?=E6=9C=80=E4=BD=8EJulia=E7=89=88=E6=9C=AC=E4=B8=BA1.7=EF=BC=9B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0README=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 - Project.toml | 4 +- README.md | 9 +- src/IpynbCompile.ipynb | 277 ++++++++++++++++++++++----------------- src/IpynbCompile.jl | 213 ++++++++++++++++-------------- test/runtests.jl | 10 +- 6 files changed, 289 insertions(+), 228 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ea6514..5cedc16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,11 +16,7 @@ jobs: matrix: version: # Versions below 1.5 cause of compat@`include`: - - "1.5" - - "1.6" - "1.7" - - "1.8" - - "1.9" - "1" # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. - "nightly" os: diff --git a/Project.toml b/Project.toml index 4371899..5e8f1b7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "IpynbCompile" uuid = "4eb781bf-a71e-403a-9d46-9d48649f04b2" authors = ["ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com>"] -version = "1.5.1" +version = "1.5.3" [deps] JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" @@ -11,7 +11,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -julia = "1.5" +julia = "1.7" [targets] test = ["Test", "Dates"] diff --git a/README.md b/README.md index eb4ef0b..24fb93b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ - + # IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具 +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) +[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/) + +[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml) + +该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 + ## 主要功能 ### 简介 diff --git a/src/IpynbCompile.ipynb b/src/IpynbCompile.ipynb index 84b7097..2d3f807 100644 --- a/src/IpynbCompile.ipynb +++ b/src/IpynbCompile.ipynb @@ -15,6 +15,18 @@ "(✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\n", + "[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\n", + "\n", + "[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\n", + "\n", + "该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -487,12 +499,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- 📌兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用\n", - "- 📌兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出\n", + "- ✅兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用\n", + " - 🔒锁定最低Julia版本为**1.5**\n", + "- ❌兼容 @ Julia **1.6⁻**:「多行字符串」自Julia **1.7**方可使用\"\\【换行】\"取消换行\n", + " - 📄错误信息:`LoadError: syntax: invalid escape sequence`\n", + " - 🔒2024-02-04:锁定Julia版本为**1.7⁺**\n", + "- ✅兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出\n", + " - 📄错误信息:`LoadError: UndefVarError: @kwdef not defined`\n", " - ⚠️禁止不引入直接使用`Base.@kwdef`\n", - "- 📌兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型\n", - " - ⚠️禁止在`const`定义的变量中标注类型(Julia运行时会自动推导)\n", - " - 📄错误信息:`ERROR: LoadError: syntax: type declarations on global variables are not yet supported`" + "- ✅兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型\n", + " - 📄错误信息:`LoadError: syntax: type declarations on global variables are not yet supported`\n", + " - ⚠️禁止在`const`定义的变量中标注类型(Julia运行时会自动推导)" ] }, { @@ -739,7 +756,7 @@ "output_type": "stream", "text": [ "\u001b[36m\u001b[1m┌ \u001b[22m\u001b[39m\u001b[36m\u001b[1mInfo: \u001b[22m\u001b[39mJSON转译结构化成功!\n", - "\u001b[36m\u001b[1m│ \u001b[22m\u001b[39m notebook_raw_cell = IpynbNotebook{Any}(Any[Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"(✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!)\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"## 主要功能\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"### 简介\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性\\n\", \"\\n\", \"- 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义\\n\", \" - 笔记本 `IpynbNotebook{单元格类型}`\\n\", \" - 元数据 `IpynbNotebookMetadata`\\n\", \" - 单元格 `IpynbCell`\\n\", \"- 📌可将Jupyter笔记本(`.ipynb`文件)【转换】成可直接执行的 [***Julia***](https://julialang.org/) 代码\\n\", \" - 编译单元格 `compile_cell`\\n\", \" - 编译笔记本 `compile_notebook`\\n\", \" - 方法1:`compile_notebook(笔记本::IpynbNotebook)`\\n\" … \" - `nothing`(若为其它类型)\\n\", \" - 解析笔记本 `parse_notebook`\\n\", \" - 等效于「编译笔记本的**所有单元格**」\\n\", \" - 执行单元格 `eval_cell`\\n\", \" - 等效于「【解析】并【执行】单元格」\\n\", \" - 执行笔记本 `eval_notebook`\\n\", \" - 等效于「【解析】并【执行】笔记本」\\n\", \" - 逐单元格版本:`eval_notebook_by_cell`\\n\", \" - 引入笔记本 `include_notebook`\\n\", \" - 逐单元格版本:`include_notebook_by_cell`\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"✨创新点:**使用多样的「特殊注释」机制,让使用者能更灵活、更便捷地编译Jupyter笔记本,并能将其【交互式】优势用于库的编写之中**\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"### 重要机制:单元格「特殊注释」\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"简介:单元格的主要「特殊注释」及其作用(以`# 单行注释` `#= 块注释 =#`为例)\\n\", \"\\n\", \"- `# %ignore-line` 忽略下一行\\n\", \"- `# %ignore-below` 忽略下面所有行\\n\", \"- `# %ignore-cell` 忽略整个单元格\\n\", \"- `# %ignore-begin` 块忽略开始\\n\", \"- `# %ignore-end` 块忽略结束\\n\", \"- `#= %only-compiled` 仅编译后可用(头)\\n\", \"- `%only-compiled =#` 仅编译后可用(尾)\\n\", \"- `# %include <路径>` 引入指定路径的文件内容,替代一整行注释\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"✨**该笔记本自身**,就是一个好的用法参考来源\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"#### 各个「特殊注释」的用法\"], \"metadata\" => Dict{String, Any}()) … Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"## 自动构建\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"构建过程主要包括:\\n\", \"\\n\", \"- **自举**构建主模块,生成库文件\\n\", \"- 扫描`src`目录下基本所有Jupyter笔记本(`.ipynb`),编译生成`.jl`源码\\n\", \"- 提取该文件开头Markdown笔记,在**项目根目录**下**生成自述文件**(`README.md`)\\n\", \" - 因此`README.md`暂且只有一种语言(常更新的语言)\\n\", \"\\n\", \"⚠️不应该在编译后的库文件中看到任何代码\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 自举生成源码\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[93m\\e[1m✅Jupyter笔记本「主模块」自编译成功!\\e[22m\\e[39m\\n\", \"\\e[93m\\e[1m(共写入 nothing 个字节)\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" # * 测试Pair编译\\n\", \" write_bytes = compile_notebook(SELF_PATH => joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 63), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 文件夹下其它笔记本的编译\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[92m\\e[1mCompiled: .\\\\compiler.ipynb => .\\\\compiler.jl\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell\\n\", \"# * 扫描`src`目录,自动构建主模块\\n\", \"# * - 📝Julia 文件夹遍历:`walkdir`迭代器\\n\", \"# * - 🔗参考:参考:https://stackoverflow.com/questions/58258101/how-to-loop-through-a-folder-of-sub-folders-in-julia\\n\", \"PATH_SRC = \\\".\\\"\\n\", \"let root_folder = PATH_SRC\\n\", \"\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\" … \" path => new_path # * 测试Pair\\n\", \" # ! 根目录后续会由`path`自行指定\\n\", \" )\\n\", \" # 输出编译结果\\n\", \" printstyled(\\n\", \" \\\"Compiled: \\$path => \\$new_path\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \" end\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 64), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 编译生成测试文件`runtests.jl`\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[92m\\e[1m✅测试文件编译成功!\\e[22m\\e[39m\\n\", \"\\e[92m\\e[1m(共写入 40869 个字节)\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 自举构建主模块\\n\", \"if !contains(@__DIR__, \\\"test\\\") # 不能在测试代码中被重复调用\\n\", \" OUT_TEST_JL = joinpath(ROOT_PATH, \\\"test\\\", \\\"runtests.jl\\\") # 直接作为库的主文件\\n\", \" # 直接拼接所有代码单元格\\n\", \" code_tests = join((\\n\", \" join(cell.source)\\n\", \" for cell in notebook.cells\\n\", \" if cell.cell_type == \\\"code\\\"\\n\", \" ), \\\"\\\\n\\\\n\\\")\\n\", \" # 开头使用Test库,并添加测试上下文\\n\" … \" code_tests *= \\\"\\\"\\\"\\n\", \" end\\n\", \" \\\"\\\"\\\" =#\\n\", \" # 最终写入\\n\", \" write_bytes = write(OUT_TEST_JL, code_tests)\\n\", \" printstyled(\\n\", \" \\\"✅测试文件编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 65), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 编译生成自述文件`README.md`(非库功能)\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\n\", \"# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\\n\", \"\\n\", \"## 主要功能\\n\", \"\\n\", \"### 简介\\n\", \"\\n\", \"📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性\\n\", \"\\n\", \"- 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义\\n\" … \"# ↑ 上面一行会被替换成数据\\n\", \"```\\n\", \"\\n\", \"📝Julia的「空白符无关性」允许在等号后边大范围附带注释的空白\\n\", \"\\n\", \"## 参考\\n\", \"\\n\", \"- 本Julia库的灵感来源:[Promises.jl/src/notebook.jl](https://github.com/fonsp/Promises.jl/blob/main/src/notebook.jl)\\n\", \" - 源库使用了 [**Pluto.jl**](https://github.com/fonsp/Pluto.jl) 的「笔记本导出」功能\\n\", \"- **Jupyter Notebook** 文件格式(JSON):[🔗nbformat.readthedocs.io](https://nbformat.readthedocs.io/en/latest/format_description.html#notebook-file-format)\\n\"]), Dict{String, Any}(\"output_type\" => \"execute_result\", \"data\" => Dict{String, Any}(\"text/plain\" => Any[\"7338\"]), \"metadata\" => Dict{String, Any}(), \"execution_count\" => 66)], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 扫描自身Markdown单元格,自动生成`README.md`\\n\", \"\\\"决定「单元格采集结束」的标识\\\" # ! 不包括结束处单元格\\n\", \"FLAG_END = \\\"\\n\", \"\\$README_markdown_TEXT\\\\\\n\", \"\\\"\\\"\\\"\\n\", \"print(README_markdown_TEXT)\\n\", \"\\n\", \"README_FILE = \\\"README.md\\\"\\n\", \"write(joinpath(ROOT_PATH, README_FILE), README_markdown_TEXT)\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 66)], IpynbNotebookMetadata(Dict{String, Any}(\"file_extension\" => \".jl\", \"mimetype\" => \"application/julia\", \"name\" => \"julia\", \"version\" => \"1.10.0\"), Dict{String, Any}(\"name\" => \"julia-1.10\", \"display_name\" => \"Julia 1.10.0\", \"language\" => \"julia\")), 4, 2)\n", + "\u001b[36m\u001b[1m│ \u001b[22m\u001b[39m notebook_raw_cell = IpynbNotebook{Any}(Any[Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"(✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!)\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\\n\", \"[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\\n\", \"\\n\", \"[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\\n\", \"\\n\", \"该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"## 主要功能\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"### 简介\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性\\n\", \"\\n\", \"- 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义\\n\", \" - 笔记本 `IpynbNotebook{单元格类型}`\\n\", \" - 元数据 `IpynbNotebookMetadata`\\n\", \" - 单元格 `IpynbCell`\\n\", \"- 📌可将Jupyter笔记本(`.ipynb`文件)【转换】成可直接执行的 [***Julia***](https://julialang.org/) 代码\\n\", \" - 编译单元格 `compile_cell`\\n\", \" - 编译笔记本 `compile_notebook`\\n\", \" - 方法1:`compile_notebook(笔记本::IpynbNotebook)`\\n\" … \" - `nothing`(若为其它类型)\\n\", \" - 解析笔记本 `parse_notebook`\\n\", \" - 等效于「编译笔记本的**所有单元格**」\\n\", \" - 执行单元格 `eval_cell`\\n\", \" - 等效于「【解析】并【执行】单元格」\\n\", \" - 执行笔记本 `eval_notebook`\\n\", \" - 等效于「【解析】并【执行】笔记本」\\n\", \" - 逐单元格版本:`eval_notebook_by_cell`\\n\", \" - 引入笔记本 `include_notebook`\\n\", \" - 逐单元格版本:`include_notebook_by_cell`\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"✨创新点:**使用多样的「特殊注释」机制,让使用者能更灵活、更便捷地编译Jupyter笔记本,并能将其【交互式】优势用于库的编写之中**\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"### 重要机制:单元格「特殊注释」\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"简介:单元格的主要「特殊注释」及其作用(以`# 单行注释` `#= 块注释 =#`为例)\\n\", \"\\n\", \"- `# %ignore-line` 忽略下一行\\n\", \"- `# %ignore-below` 忽略下面所有行\\n\", \"- `# %ignore-cell` 忽略整个单元格\\n\", \"- `# %ignore-begin` 块忽略开始\\n\", \"- `# %ignore-end` 块忽略结束\\n\", \"- `#= %only-compiled` 仅编译后可用(头)\\n\", \"- `%only-compiled =#` 仅编译后可用(尾)\\n\", \"- `# %include <路径>` 引入指定路径的文件内容,替代一整行注释\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"✨**该笔记本自身**,就是一个好的用法参考来源\"], \"metadata\" => Dict{String, Any}()) … Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"## 自动构建\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"构建过程主要包括:\\n\", \"\\n\", \"- **自举**构建主模块,生成库文件\\n\", \"- 扫描`src`目录下基本所有Jupyter笔记本(`.ipynb`),编译生成`.jl`源码\\n\", \"- 提取该文件开头Markdown笔记,在**项目根目录**下**生成自述文件**(`README.md`)\\n\", \" - 因此`README.md`暂且只有一种语言(常更新的语言)\\n\", \"\\n\", \"⚠️不应该在编译后的库文件中看到任何代码\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 自举生成源码\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[93m\\e[1m✅Jupyter笔记本「主模块」自编译成功!\\e[22m\\e[39m\\n\", \"\\e[93m\\e[1m(共写入 nothing 个字节)\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" write_bytes = compile_notebook(SELF_PATH, joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 30), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 文件夹下其它笔记本的编译\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[92m\\e[1mCompiled: .\\\\compiler.ipynb => .\\\\compiler.jl\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell\\n\", \"# * 扫描`src`目录,自动构建主模块\\n\", \"# * - 📝Julia 文件夹遍历:`walkdir`迭代器\\n\", \"# * - 🔗参考:参考:https://stackoverflow.com/questions/58258101/how-to-loop-through-a-folder-of-sub-folders-in-julia\\n\", \"PATH_SRC = \\\".\\\"\\n\", \"let root_folder = PATH_SRC\\n\", \"\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\" … \" path => new_path # * 测试Pair\\n\", \" # ! 根目录后续会由`path`自行指定\\n\", \" )\\n\", \" # 输出编译结果\\n\", \" printstyled(\\n\", \" \\\"Compiled: \\$path => \\$new_path\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \" end\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 31), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 编译生成测试文件`runtests.jl`\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\e[92m\\e[1m✅测试文件编译成功!\\e[22m\\e[39m\\n\", \"\\e[92m\\e[1m(共写入 41020 个字节)\\e[22m\\e[39m\\n\"])], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 自举构建主模块\\n\", \"if !contains(@__DIR__, \\\"test\\\") # 不能在测试代码中被重复调用\\n\", \" OUT_TEST_JL = joinpath(ROOT_PATH, \\\"test\\\", \\\"runtests.jl\\\") # 直接作为库的主文件\\n\", \" # 直接拼接所有代码单元格\\n\", \" code_tests = join((\\n\", \" join(cell.source)\\n\", \" for cell in notebook.cells\\n\", \" if cell.cell_type == \\\"code\\\"\\n\", \" ), \\\"\\\\n\\\\n\\\")\\n\", \" # 开头使用Test库,并添加测试上下文\\n\" … \" code_tests *= \\\"\\\"\\\"\\n\", \" end\\n\", \" \\\"\\\"\\\" =#\\n\", \" # 最终写入\\n\", \" write_bytes = write(OUT_TEST_JL, code_tests)\\n\", \" printstyled(\\n\", \" \\\"✅测试文件编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \"end\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 32), Dict{String, Any}(\"cell_type\" => \"markdown\", \"source\" => Any[\"\\n\", \"### 编译生成自述文件`README.md`(非库功能)\"], \"metadata\" => Dict{String, Any}()), Dict{String, Any}(\"outputs\" => Any[Dict{String, Any}(\"name\" => \"stdout\", \"output_type\" => \"stream\", \"text\" => Any[\"\\n\", \"# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\\n\", \"\\n\", \"[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\\n\", \"[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\\n\", \"\\n\", \"[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\\n\", \"\\n\", \"该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。\\n\", \"\\n\" … \"# ↑ 上面一行会被替换成数据\\n\", \"```\\n\", \"\\n\", \"📝Julia的「空白符无关性」允许在等号后边大范围附带注释的空白\\n\", \"\\n\", \"## 参考\\n\", \"\\n\", \"- 本Julia库的灵感来源:[Promises.jl/src/notebook.jl](https://github.com/fonsp/Promises.jl/blob/main/src/notebook.jl)\\n\", \" - 源库使用了 [**Pluto.jl**](https://github.com/fonsp/Pluto.jl) 的「笔记本导出」功能\\n\", \"- **Jupyter Notebook** 文件格式(JSON):[🔗nbformat.readthedocs.io](https://nbformat.readthedocs.io/en/latest/format_description.html#notebook-file-format)\\n\"]), Dict{String, Any}(\"output_type\" => \"execute_result\", \"data\" => Dict{String, Any}(\"text/plain\" => Any[\"7861\"]), \"metadata\" => Dict{String, Any}(), \"execution_count\" => 33)], \"cell_type\" => \"code\", \"source\" => Any[\"# %ignore-cell # * 扫描自身Markdown单元格,自动生成`README.md`\\n\", \"\\\"决定「单元格采集结束」的标识\\\" # ! 不包括结束处单元格\\n\", \"FLAG_END = \\\"\\n\", \"\\$README_markdown_TEXT\\\\\\n\", \"\\\"\\\"\\\"\\n\", \"print(README_markdown_TEXT)\\n\", \"\\n\", \"README_FILE = \\\"README.md\\\"\\n\", \"write(joinpath(ROOT_PATH, README_FILE), README_markdown_TEXT)\"], \"metadata\" => Dict{String, Any}(), \"execution_count\" => 33)], IpynbNotebookMetadata(Dict{String, Any}(\"file_extension\" => \".jl\", \"mimetype\" => \"application/julia\", \"name\" => \"julia\", \"version\" => \"1.10.0\"), Dict{String, Any}(\"name\" => \"julia-1.10\", \"display_name\" => \"Julia 1.10.0\", \"language\" => \"julia\")), 4, 2)\n", "\u001b[36m\u001b[1m└ \u001b[22m\u001b[39m notebook_metadata = IpynbNotebookMetadata(Dict{String, Any}(\"file_extension\" => \".jl\", \"mimetype\" => \"application/julia\", \"name\" => \"julia\", \"version\" => \"1.10.0\"), Dict{String, Any}(\"name\" => \"julia-1.10\", \"display_name\" => \"Julia 1.10.0\", \"language\" => \"julia\"))\n" ] } @@ -1412,9 +1429,10 @@ { "data": { "text/plain": [ - "113-element Vector{IpynbCell}:\n", + "114-element Vector{IpynbCell}:\n", " IpynbCell(\"markdown\", [\"# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"(✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!)\"], Dict{String, Any}(), nothing)\n", + " IpynbCell(\"markdown\", [\"[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\\n\", \"[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\\n\", \"\\n\", \"[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\\n\", \"\\n\", \"该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"## 主要功能\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"### 简介\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性\\n\", \"\\n\", \"- 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义\\n\", \" - 笔记本 `IpynbNotebook{单元格类型}`\\n\", \" - 元数据 `IpynbNotebookMetadata`\\n\", \" - 单元格 `IpynbCell`\\n\", \"- 📌可将Jupyter笔记本(`.ipynb`文件)【转换】成可直接执行的 [***Julia***](https://julialang.org/) 代码\\n\", \" - 编译单元格 `compile_cell`\\n\", \" - 编译笔记本 `compile_notebook`\\n\", \" - 方法1:`compile_notebook(笔记本::IpynbNotebook)`\\n\" … \" - `nothing`(若为其它类型)\\n\", \" - 解析笔记本 `parse_notebook`\\n\", \" - 等效于「编译笔记本的**所有单元格**」\\n\", \" - 执行单元格 `eval_cell`\\n\", \" - 等效于「【解析】并【执行】单元格」\\n\", \" - 执行笔记本 `eval_notebook`\\n\", \" - 等效于「【解析】并【执行】笔记本」\\n\", \" - 逐单元格版本:`eval_notebook_by_cell`\\n\", \" - 引入笔记本 `include_notebook`\\n\", \" - 逐单元格版本:`include_notebook_by_cell`\"], Dict{String, Any}(), nothing)\n", @@ -1425,14 +1443,13 @@ " IpynbCell(\"markdown\", [\"#### 各个「特殊注释」的用法\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"##### 忽略单行\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"📌简要用途:忽略**下一行**代码\"], Dict{String, Any}(), nothing)\n", - " IpynbCell(\"markdown\", [\"编译前@笔记本单元格:\\n\", \"\\n\", \"```julia\\n\", \"[[\\\"上边还会被编译\\\"]]\\n\", \"# %ignore-line # 忽略单行(可直接在此注释后另加字符,会被一并忽略)\\n\", \"[\\\"这是一行被忽略的代码\\\"]\\n\", \"[[\\\"下边也要被编译\\\"]]\\n\", \"```\\n\", \"\\n\", \"编译后:\\n\", \"\\n\", \"```julia\\n\", \"[[\\\"上边还会被编译\\\"]]\\n\", \"[[\\\"下边也要被编译\\\"]]\\n\", \"```\"], Dict{String, Any}(), nothing)\n", " ⋮\n", " IpynbCell(\"markdown\", [\"## 关闭模块上下文\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# ! ↓这后边注释的代码只有在编译后才会被执行\\n\", \"# ! 仍然使用多行注释语法,以便统一格式\\n\", \"#= %only-compiled\\n\", \"end # module\\n\", \"%only-compiled =#\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"## 自动构建\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"构建过程主要包括:\\n\", \"\\n\", \"- **自举**构建主模块,生成库文件\\n\", \"- 扫描`src`目录下基本所有Jupyter笔记本(`.ipynb`),编译生成`.jl`源码\\n\", \"- 提取该文件开头Markdown笔记,在**项目根目录**下**生成自述文件**(`README.md`)\\n\", \" - 因此`README.md`暂且只有一种语言(常更新的语言)\\n\", \"\\n\", \"⚠️不应该在编译后的库文件中看到任何代码\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"### 自举生成源码\"], Dict{String, Any}(), nothing)\n", - " IpynbCell(\"code\", [\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" # * 测试Pair编译\\n\", \" write_bytes = compile_notebook(SELF_PATH => joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], Dict{String, Any}(), nothing)\n", + " IpynbCell(\"code\", [\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" write_bytes = compile_notebook(SELF_PATH, joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"### 文件夹下其它笔记本的编译\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# %ignore-cell\\n\", \"# * 扫描`src`目录,自动构建主模块\\n\", \"# * - 📝Julia 文件夹遍历:`walkdir`迭代器\\n\", \"# * - 🔗参考:参考:https://stackoverflow.com/questions/58258101/how-to-loop-through-a-folder-of-sub-folders-in-julia\\n\", \"PATH_SRC = \\\".\\\"\\n\", \"let root_folder = PATH_SRC\\n\", \"\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\" … \" path => new_path # * 测试Pair\\n\", \" # ! 根目录后续会由`path`自行指定\\n\", \" )\\n\", \" # 输出编译结果\\n\", \" printstyled(\\n\", \" \\\"Compiled: \\$path => \\$new_path\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \" end\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"markdown\", [\"\\n\", \"### 编译生成测试文件`runtests.jl`\"], Dict{String, Any}(), nothing)\n", @@ -1593,14 +1610,14 @@ " IpynbCell(\"code\", [\"# %ignore-cell\\n\", \"let path_examples(path) = joinpath(ROOT_PATH, \\\"examples\\\", path),\\n\", \" notebooks = [\\n\", \" path_examples(\\\"c.ipynb\\\") # C\\n\", \" path_examples(\\\"java.ipynb\\\") # Java\\n\", \" SELF_PATH # Julia # * 直接使用自身\\n\", \" path_examples(\\\"python.ipynb\\\") # Python\\n\", \" path_examples(\\\"typescript.ipynb\\\") # TypeScript\\n\", \" ] .|> read_ipynb_json .|> IpynbNotebook\\n\", \"\\n\" … \" ])\\n\", \"\\n\", \" langs = identify_lang.(notebooks)\\n\", \" @info \\\"识别到的所有语言\\\" langs\\n\", \"\\n\", \" table_comments = [langs generate_comment_inline.(langs) generate_comment_multiline_head.(langs) generate_comment_multiline_tail.(langs)]\\n\", \" @info \\\"生成的所有注释 [语言 单行 多行开头 多行结尾]\\\" table_comments\\n\", \"\\n\", \" @info \\\"生成的常见扩展名 [语言 扩展名]\\\" [langs get_extension.(langs)]\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " ⋮\n", " IpynbCell(\"code\", [\"\\\"\\\"\\\"\\n\", \"对Markdown的编译\\n\", \"- 📌主要方法:转换成多个单行注释\\n\", \"- ✨不对Markdown单元格作过于特殊的处理\\n\", \" - 仅将其视作语言为 `markdown` 的源码\\n\", \" - 仅在编译后作为程序语言注释\\n\", \"\\n\", \"@example IpynbCell(\\\"markdown\\\", [\\\"# IpynbCompile.jl: 一个通用的Jupyter笔记本集成编译工具\\\"], Dict{String, Any}(), nothing)\\n\", \"(行号为1)将被转换为\\n\", \"```julia\\n\" … \" 全部被忽略!\\n\", \" \\\"\\\"\\\"markdown\\n\", \" local compiled::String = compile_cell(cell; lang=:julia, line_num=1)\\n\", \" @assert compiled == \\\"\\\"\\\"\\\\\\n\", \" # %% [1] markdown\\n\", \" # 这些内容不会被忽略\\n\", \" # \\n\", \" \\\"\\\"\\\"\\n\", \" compiled |> println\\n\", \"end\"], Dict{String, Any}(), nothing)\n", - " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export parse_cell, tryparse_cell, eval_cell\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"解析一个单元格\\n\", \"- 🎯将单元格解析成Julia表达式\\n\", \"- 📌使用`Meta.parseall`解析代码\\n\", \" - `Meta.parse`只能解析一个Julia表达式\\n\", \" - 可能会附加上不必要的「:toplevel」表达式\\n\" … \"# %ignore-below\\n\", \"\\n\", \"# 执行其中一个代码单元格 # * 参考「预置语法糖」\\n\", \"eval_cell(codes[3]; lang=:julia)::Base.Docs.Binding\\n\", \"\\n\", \"# 尝试对每个单元格进行解析\\n\", \"[\\n\", \" tryparse_cell(cell; lang=:julia, line_num=i)\\n\", \" for (i, cell) in enumerate(codes)\\n\", \"]\"], Dict{String, Any}(), nothing)\n", + " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export parse_cell, tryparse_cell, eval_cell\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"解析一个单元格\\n\", \"- 🎯将单元格解析成Julia表达式\\n\", \"- 📌使用`Meta.parseall`解析代码\\n\", \" - `Meta.parse`只能解析一个Julia表达式\\n\", \" - 可能会附加上不必要的「:toplevel」表达式\\n\" … \" contains(cell.source[end], \\\"const\\\")\\n\", \" end]\\n\", \" eval_cell(cell_const; lang=:julia)::Base.Docs.Binding\\n\", \"end\\n\", \"\\n\", \"# 尝试对每个单元格进行解析\\n\", \"[\\n\", \" tryparse_cell(cell; lang=:julia, line_num=i)\\n\", \" for (i, cell) in enumerate(codes)\\n\", \"]\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# %ignore-cell # * ↓ 不提供语言,会报错但静默失败\\n\", \"tryparse_cell(codes#= ; lang=:julia =#)\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export compile_notebook\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"编译整个笔记本\\n\", \"- 🎯编译整个笔记本对象,形成相应Julia代码\\n\", \"- 📌整体文本:头部注释+各单元格编译(逐个join(_, '\\\\\\\\n'))\\n\", \"- ⚠️末尾不会附加换行符\\n\", \"- @param notebook 要编译的笔记本对象\\n\" … \" # 根据语言自动追加扩展名,作为目标路径\\n\", \" \\\"\\$path.\\$(get_extension(identify_lang(notebook)))\\\";\\n\", \" # 其它附加参数 #\\n\", \" # 自动从`path`构造编译根目录\\n\", \" root_path=dirname(path),\\n\", \" )\\n\", \"end\\n\", \"\\n\", \"# %ignore-below\\n\", \"compile_notebook(notebook) |> print\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export parse_notebook, tryparse_notebook\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"解析笔记本\\n\", \"@param notebook 笔记本\\n\", \"@param parse_function 解析函数(替代原先`Meta.parseall`的位置)\\n\", \"@param kwargs 附加参数\\n\", \"@return 解析后的Julia表达式(可能含有错误的表达式`:error`)\\n\" … \" try\\n\", \" parse_notebook(args...; kwargs...)\\n\", \" catch e\\n\", \" @warn e\\n\", \" showerror(stderr, e, Base.stacktrace(Base.catch_backtrace()))\\n\", \" nothing\\n\", \" end\\n\", \"\\n\", \"# %ignore-below\\n\", \"@assert tryparse_notebook(notebook) isa Expr\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export eval_notebook, eval_notebook_by_cell\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"【整个】解释并执行Jupyter笔记本\\n\", \"- 📌先解析整个笔记本,然后一次性执行所有代码\\n\", \" - 可以实现一些「编译后可用」的「上下文相关代码」\\n\", \" - 如「将全笔记本代码打包成一个模块」\\n\", \"\\\"\\\"\\\"\\n\" … \" local result::Any = nothing\\n\", \" # 逐个执行\\n\", \" for cell in notebook.cells\\n\", \" result = eval_cell(cell; kwargs...)\\n\", \" end\\n\", \" # 返回最后一个结果\\n\", \" return result\\n\", \"end\\n\", \"\\n\", \"# ! 测试代码放在最后边\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"#= %only-compiled # ! 模块上下文:导出元素\\n\", \"export include_notebook, include_notebook_by_cell\\n\", \"%only-compiled =#\\n\", \"\\n\", \"\\\"\\\"\\\"\\n\", \"从【字符串】路径解析并【整个】编译执行整个笔记本的代码\\n\", \"- 📌「执行笔记本」已经有`eval_notebook`支持了\\n\", \"- 🎯直接解析并执行`.ipynb`文件\\n\", \"- 📌先加载并编译Jupyter笔记本,再【整个】执行其所有单元格\\n\", \"- 会像`include`一样返回「最后一个执行的单元格的返回值」\\n\" … \"@assert @isdefined IpynbCompile # ! 模块上下文生效:所有代码现在都在模块之中\\n\", \"printstyled(\\\"✅Jupyter笔记本文件引入完成,模块导入成功!\\\\n\\\"; color=:light_green, bold=true)\\n\", \"@show IpynbCompile\\n\", \"println()\\n\", \"\\n\", \"# * 打印导出的所有符号\\n\", \"printstyled(\\\"📜以下为IpynbCompile模块导出的所有\\$(length(names(IpynbCompile)))个符号:\\\\n\\\"; color=:light_blue, bold=true)\\n\", \"for name in names(IpynbCompile)\\n\", \" println(name)\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# ! ↓这后边注释的代码只有在编译后才会被执行\\n\", \"# ! 仍然使用多行注释语法,以便统一格式\\n\", \"#= %only-compiled\\n\", \"end # module\\n\", \"%only-compiled =#\"], Dict{String, Any}(), nothing)\n", - " IpynbCell(\"code\", [\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" # * 测试Pair编译\\n\", \" write_bytes = compile_notebook(SELF_PATH => joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], Dict{String, Any}(), nothing)\n", + " IpynbCell(\"code\", [\"# %ignore-cell # * 自举构建主模块\\n\", \"# * 自编译生成`.jl`源码\\n\", \"let OUT_LIB_FILE = \\\"IpynbCompile.jl\\\" # 直接作为库的主文件\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\", \" write_bytes = compile_notebook(SELF_PATH, joinpath(ROOT_PATH, \\\"src\\\", OUT_LIB_FILE))\\n\", \" printstyled(\\n\", \" \\\"✅Jupyter笔记本「主模块」自编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_yellow, bold=true\\n\", \" )\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# %ignore-cell\\n\", \"# * 扫描`src`目录,自动构建主模块\\n\", \"# * - 📝Julia 文件夹遍历:`walkdir`迭代器\\n\", \"# * - 🔗参考:参考:https://stackoverflow.com/questions/58258101/how-to-loop-through-a-folder-of-sub-folders-in-julia\\n\", \"PATH_SRC = \\\".\\\"\\n\", \"let root_folder = PATH_SRC\\n\", \"\\n\", \" # !不能在`runtests.jl`中运行\\n\", \" contains(@__DIR__, \\\"test\\\") && return\\n\", \"\\n\" … \" path => new_path # * 测试Pair\\n\", \" # ! 根目录后续会由`path`自行指定\\n\", \" )\\n\", \" # 输出编译结果\\n\", \" printstyled(\\n\", \" \\\"Compiled: \\$path => \\$new_path\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \" end\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# %ignore-cell # * 自举构建主模块\\n\", \"if !contains(@__DIR__, \\\"test\\\") # 不能在测试代码中被重复调用\\n\", \" OUT_TEST_JL = joinpath(ROOT_PATH, \\\"test\\\", \\\"runtests.jl\\\") # 直接作为库的主文件\\n\", \" # 直接拼接所有代码单元格\\n\", \" code_tests = join((\\n\", \" join(cell.source)\\n\", \" for cell in notebook.cells\\n\", \" if cell.cell_type == \\\"code\\\"\\n\", \" ), \\\"\\\\n\\\\n\\\")\\n\", \" # 开头使用Test库,并添加测试上下文\\n\" … \" code_tests *= \\\"\\\"\\\"\\n\", \" end\\n\", \" \\\"\\\"\\\" =#\\n\", \" # 最终写入\\n\", \" write_bytes = write(OUT_TEST_JL, code_tests)\\n\", \" printstyled(\\n\", \" \\\"✅测试文件编译成功!\\\\n(共写入 \\$write_bytes 个字节)\\\\n\\\";\\n\", \" color=:light_green, bold=true\\n\", \" )\\n\", \"end\"], Dict{String, Any}(), nothing)\n", " IpynbCell(\"code\", [\"# %ignore-cell # * 扫描自身Markdown单元格,自动生成`README.md`\\n\", \"\\\"决定「单元格采集结束」的标识\\\" # ! 不包括结束处单元格\\n\", \"FLAG_END = \\\"\\n\", \"\\$README_markdown_TEXT\\\\\\n\", \"\\\"\\\"\\\"\\n\", \"print(README_markdown_TEXT)\\n\", \"\\n\", \"README_FILE = \\\"README.md\\\"\\n\", \"write(joinpath(ROOT_PATH, README_FILE), README_markdown_TEXT)\"], Dict{String, Any}(), nothing)" @@ -3171,7 +3188,12 @@ "# %ignore-below\n", "\n", "# 执行其中一个代码单元格 # * 参考「预置语法糖」\n", - "eval_cell(codes[3]; lang=:julia)::Base.Docs.Binding\n", + "let cell_const = codes[findfirst(codes) do cell\n", + " cell.cell_type == \"code\" &&\n", + " contains(cell.source[end], \"const\")\n", + " end]\n", + " eval_cell(cell_const; lang=:julia)::Base.Docs.Binding\n", + "end\n", "\n", "# 尝试对每个单元格进行解析\n", "[\n", @@ -3286,12 +3308,20 @@ "# (✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!)\n", "\n", "# %% [3] markdown\n", - "# ## 主要功能\n", + "# [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\n", + "# [![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\n", + "# \n", + "# [![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\n", + "# \n", + "# 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。\n", "\n", "# %% [4] markdown\n", - "# ### 简介\n", + "# ## 主要功能\n", "\n", "# %% [5] markdown\n", + "# ### 简介\n", + "\n", + "# %% [6] markdown\n", "# 📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性\n", "# \n", "# - 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义\n", @@ -3326,13 +3356,13 @@ "# - 引入笔记本 `include_notebook`\n", "# - 逐单元格版本:`include_notebook_by_cell`\n", "\n", - "# %% [6] markdown\n", + "# %% [7] markdown\n", "# ✨创新点:**使用多样的「特殊注释」机制,让使用者能更灵活、更便捷地编译Jupyter笔记本,并能将其【交互式】优势用于库的编写之中**\n", "\n", - "# %% [7] markdown\n", + "# %% [8] markdown\n", "# ### 重要机制:单元格「特殊注释」\n", "\n", - "# %% [8] markdown\n", + "# %% [9] markdown\n", "# 简介:单元格的主要「特殊注释」及其作用(以`# 单行注释` `#= 块注释 =#`为例)\n", "# \n", "# - `# %ignore-line` 忽略下一行\n", @@ -3344,19 +3374,19 @@ "# - `%only-compiled =#` 仅编译后可用(尾)\n", "# - `# %include <路径>` 引入指定路径的文件内容,替代一整行注释\n", "\n", - "# %% [9] markdown\n", + "# %% [10] markdown\n", "# ✨**该笔记本自身**,就是一个好的用法参考来源\n", "\n", - "# %% [10] markdown\n", + "# %% [11] markdown\n", "# #### 各个「特殊注释」的用法\n", "\n", - "# %% [11] markdown\n", + "# %% [12] markdown\n", "# ##### 忽略单行\n", "\n", - "# %% [12] markdown\n", + "# %% [13] markdown\n", "# 📌简要用途:忽略**下一行**代码\n", "\n", - "# %% [13] markdown\n", + "# %% [14] markdown\n", "# 编译前@笔记本单元格:\n", "# \n", "# ```julia\n", @@ -3373,10 +3403,10 @@ "# [[\"下边也要被编译\"]]\n", "# ```\n", "\n", - "# %% [14] markdown\n", + "# %% [15] markdown\n", "# ##### 忽略下面所有行\n", "\n", - "# %% [15] markdown\n", + "# %% [16] markdown\n", "# 编译前@笔记本单元格\n", "# \n", "# ```julia\n", @@ -3395,10 +3425,10 @@ "# [[\"上边的代码正常编译\"]]\n", "# ```\n", "\n", - "# %% [16] markdown\n", + "# %% [17] markdown\n", "# ##### 忽略整个单元格\n", "\n", - "# %% [17] markdown\n", + "# %% [18] markdown\n", "# 编译前@笔记本单元格:\n", "# \n", "# ```julia\n", @@ -3415,16 +3445,16 @@ "# \n", "# ↑空字串\n", "\n", - "# %% [18] markdown\n", + "# %% [19] markdown\n", "# 📌一般习惯将 `# %ignore-cell` 放在第一行\n", "\n", - "# %% [19] markdown\n", + "# %% [20] markdown\n", "# ##### 忽略代码块\n", "\n", - "# %% [20] markdown\n", + "# %% [21] markdown\n", "# 📝即「块忽略」\n", "\n", - "# %% [21] markdown\n", + "# %% [22] markdown\n", "# 编译前@笔记本单元格:\n", "# \n", "# ```julia\n", @@ -3443,15 +3473,15 @@ "# [[\"下面的代码都会被编译\"]]\n", "# ```\n", "\n", - "# %% [22] markdown\n", + "# %% [23] markdown\n", "# ##### 仅编译后可用\n", "\n", - "# %% [23] markdown\n", + "# %% [24] markdown\n", "# 主要用途:包装 `module` 等代码,实现编译后模块上下文\n", "# \n", "# - ⚠️对于 **Python** 等【依赖缩进定义上下文】的语言,难以进行此类编译\n", "\n", - "# %% [24] markdown\n", + "# %% [25] markdown\n", "# 编译前@笔记本单元格:\n", "# \n", "# ```julia\n", @@ -3480,15 +3510,15 @@ "# [[\"下面的代码正常编译,并且会随着笔记本一起执行\"]]\n", "# ```\n", "\n", - "# %% [25] markdown\n", + "# %% [26] markdown\n", "# ##### 文件引入\n", "\n", - "# %% [26] markdown\n", + "# %% [27] markdown\n", "# 主要用途:结合「仅编译后可用」实现「外部代码内联」\n", "# \n", "# - 如:集成某些**中小型映射表**,整合零散源码文件……\n", "\n", - "# %% [27] markdown\n", + "# %% [28] markdown\n", "# 编译前@笔记本单元格:\n", "# \n", "# ```julia\n", @@ -3526,26 +3556,26 @@ "# \n", "# 📝Julia的「空白符无关性」允许在等号后边大范围附带注释的空白\n", "\n", - "# %% [28] markdown\n", + "# %% [29] markdown\n", "# ## 参考\n", "\n", - "# %% [29] markdown\n", + "# %% [30] markdown\n", "# - 本Julia库的灵感来源:[Promises.jl/src/notebook.jl](https://github.com/fonsp/Promises.jl/blob/main/src/notebook.jl)\n", "# - 源库使用了 [**Pluto.jl**](https://github.com/fonsp/Pluto.jl) 的「笔记本导出」功能\n", "# - **Jupyter Notebook** 文件格式(JSON):[🔗nbformat.readthedocs.io](https://nbformat.readthedocs.io/en/latest/format_description.html#notebook-file-format)\n", "\n", - "# %% [30] markdown\n", + "# %% [31] markdown\n", "# \n", "# \n", "# ⚠️该单元格首行注释用于截止生成`README.md`(包括自身)\n", "\n", - "# %% [31] markdown\n", + "# %% [32] markdown\n", "# ## 建立模块上下文\n", "\n", - "# %% [32] markdown\n", + "# %% [33] markdown\n", "# 📌使用 `# %only-compiled` 控制 `module` 代码,生成模块上下文\n", "\n", - "# %% [33] code\n", + "# %% [34] code\n", "# ! ↓这后边注释的代码只有在编译后才会被执行\n", "# ! 使用多行注释/块注释的语法,\n", "# ! 以`#= %only-compiled`行*开头*\n", @@ -3556,46 +3586,51 @@ "module IpynbCompile # 后续编译后会变为模块上下文\n", "\n", "\n", - "# %% [34] markdown\n", + "# %% [35] markdown\n", "# ## 前置模块\n", "\n", - "# %% [35] markdown\n", + "# %% [36] markdown\n", "# ### 导入库\n", "\n", - "# %% [36] code\n", + "# %% [37] code\n", "import JSON\n", "\n", - "# %% [37] markdown\n", + "# %% [38] markdown\n", "# ### 预置语法糖\n", "\n", - "# %% [38] code\n", + "# %% [39] code\n", "\"JSON常用的字典\"\n", "const JSONDict{ValueType} = Dict{String,ValueType} where ValueType\n", "\n", "\"默认解析出来的JSON字典(与`JSONDict`有本质不同,会影响到后续方法分派,并可能导致歧义)\"\n", "const JSONDictAny = JSONDict{Any}\n", "\n", - "# %% [39] markdown\n", + "# %% [40] markdown\n", "# ### 兼容+注意事项\n", "\n", - "# %% [40] code\n", + "# %% [41] code\n", "import Base: @kwdef # 兼容Julia 1.8⁻\n", "\n", - "# %% [41] markdown\n", - "# - 📌兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用\n", - "# - 📌兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出\n", + "# %% [42] markdown\n", + "# - ✅兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用\n", + "# - 🔒锁定最低Julia版本为**1.5**\n", + "# - ❌兼容 @ Julia **1.6⁻**:「多行字符串」自Julia **1.7**方可使用\"\\【换行】\"取消换行\n", + "# - 📄错误信息:`LoadError: syntax: invalid escape sequence`\n", + "# - 🔒2024-02-04:锁定Julia版本为**1.7⁺**\n", + "# - ✅兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出\n", + "# - 📄错误信息:`LoadError: UndefVarError: @kwdef not defined`\n", "# - ⚠️禁止不引入直接使用`Base.@kwdef`\n", - "# - 📌兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型\n", + "# - ✅兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型\n", + "# - 📄错误信息:`LoadError: syntax: type declarations on global variables are not yet supported`\n", "# - ⚠️禁止在`const`定义的变量中标注类型(Julia运行时会自动推导)\n", - "# - 📄错误信息:`ERROR: LoadError: syntax: type declarations on global variables are not yet supported`\n", "\n", - "# %% [42] markdown\n", + "# %% [43] markdown\n", "# ## 读取解析Jupyter笔记本(`.ipynb`文件)\n", "\n", - "# %% [43] markdown\n", + "# %% [44] markdown\n", "# ### 读取文件(JSON)\n", "\n", - "# %% [44] code\n", + "# %% [45] code\n", "export read_ipynb_json\n", "\n", "\"\"\"\n", @@ -3611,10 +3646,10 @@ "# ! ↓使用`# %ignore-line`让 编译器/解释器 忽略下一行\n", "\n", "\n", - "# %% [45] markdown\n", + "# %% [46] markdown\n", "# ### 解析文件元信息\n", "\n", - "# %% [46] markdown\n", + "# %% [47] markdown\n", "# Jupyter Notebook元数据 格式参考\n", "# \n", "# ```yaml\n", @@ -3639,7 +3674,7 @@ "# }\n", "# ```\n", "\n", - "# %% [47] markdown\n", + "# %% [48] markdown\n", "# Jupyter Notebook Cell 格式参考\n", "# \n", "# 共有:\n", @@ -3683,7 +3718,7 @@ "# }\n", "# ```\n", "\n", - "# %% [48] markdown\n", + "# %% [49] markdown\n", "# 当前Julia笔记本 元数据:\n", "# \n", "# ```json\n", @@ -3705,13 +3740,13 @@ "# (截止至2024-01-16)\n", "\n", "\n", - "# %% [50] markdown\n", + "# %% [51] markdown\n", "# ## 解析Jupyter笔记本(Julia `struct`)\n", "\n", - "# %% [51] markdown\n", + "# %% [52] markdown\n", "# ### 定义「笔记本」结构\n", "\n", - "# %% [52] code\n", + "# %% [53] code\n", "export IpynbNotebook, IpynbNotebookMetadata\n", "\n", "\"\"\"\n", @@ -3774,22 +3809,22 @@ "# ! ↓使用`# %ignore-below`让 编译器/解释器 忽略后续内容\n", "\n", "\n", - "# %% [53] markdown\n", + "# %% [54] markdown\n", "# ### 读取笔记本 总函数\n", "\n", - "# %% [54] markdown\n", + "# %% [55] markdown\n", "# 根据路径读取笔记本\n", "\n", - "# %% [55] code\n", + "# %% [56] code\n", "export read_notebook\n", "\n", "\"从路径读取Jupyter笔记本(`struct IpynbNotebook`)\"\n", "read_notebook(path::AbstractString)::IpynbNotebook = IpynbNotebook(read_ipynb_json(path))\n", "\n", - "# %% [56] markdown\n", + "# %% [57] markdown\n", "# 方便引入笔记本的字符串宏\n", "\n", - "# %% [57] code\n", + "# %% [58] code\n", "export @notebook_str\n", "\n", "macro notebook_str(path::AbstractString)\n", @@ -3797,13 +3832,13 @@ "end\n", "\n", "\n", - "# %% [58] markdown\n", + "# %% [59] markdown\n", "# ### 解析/生成 笔记本信息\n", "\n", - "# %% [59] markdown\n", + "# %% [60] markdown\n", "# #### 识别编程语言\n", "\n", - "# %% [60] code\n", + "# %% [61] code\n", "\"【内部】编程语言⇒正则表达式 识别字典\"\n", "const LANG_IDENTIFY_DICT = Dict{Symbol,Regex}(\n", " lang => Regex(\"^(?:$regex_str)\\$\") # ! ←必须头尾精确匹配(不然就会把`JavaScript`认成`r`)\n", @@ -3897,14 +3932,14 @@ " end # ! 默认返回`nothing`\n", "\n", "\n", - "# %% [61] markdown\n", + "# %% [62] markdown\n", "# #### 根据编程语言生成注释\n", "# \n", "# - 生成的注释会用于「行开头」识别\n", "# - 如:`// %ignore-cell` (C系列)\n", "# - 如:`# %ignore-cell` (Python/Julia)\n", "\n", - "# %% [62] code\n", + "# %% [63] code\n", "\"【内部】编程语言⇒单行注释\"\n", "const LANG_COMMENT_DICT_INLINE = Dict{Symbol,String}()\n", "\n", @@ -3993,10 +4028,10 @@ "\n", "\n", "\n", - "# %% [63] markdown\n", + "# %% [64] markdown\n", "# #### 生成常用扩展名\n", "\n", - "# %% [64] code\n", + "# %% [65] code\n", "\"【内部】编程语言⇒常用扩展名(不带`.`)\"\n", "const LANG_EXTENSION_DICT = Dict{Symbol,String}(\n", " # ! 以下「特殊注释」需要在行首\n", @@ -4072,17 +4107,17 @@ "\n", "\n", "\n", - "# %% [65] markdown\n", + "# %% [66] markdown\n", "# #### 解析/生成 测试\n", "\n", "\n", - "# %% [67] markdown\n", + "# %% [68] markdown\n", "# ### Notebook编译/头部注释\n", "# \n", "# - 🎯标注 版本信息\n", "# - 🎯标注 各类元数据\n", "\n", - "# %% [68] code\n", + "# %% [69] code\n", "\"\"\"\n", "【内部】从Notebook生成头部注释\n", "- ⚠️末尾有换行\n", @@ -4108,16 +4143,16 @@ "\n", "\n", "\n", - "# %% [69] markdown\n", + "# %% [70] markdown\n", "# ## 解析处理单元格\n", "\n", - "# %% [70] markdown\n", + "# %% [71] markdown\n", "# ### 定义「单元格」\n", "\n", - "# %% [71] markdown\n", + "# %% [72] markdown\n", "# 定义结构类型\n", "\n", - "# %% [72] code\n", + "# %% [73] code\n", "export IpynbCell\n", "\n", "\"\"\"\n", @@ -4155,10 +4190,10 @@ " )...)\n", "end\n", "\n", - "# %% [73] markdown\n", + "# %% [74] markdown\n", "# 定义快捷字符串宏\n", "\n", - "# %% [74] code\n", + "# %% [75] code\n", "export @cell_str\n", "\n", "\"🎯将字符串拆分成单元格各行(区分末尾换行)\"\n", @@ -4195,23 +4230,23 @@ "\n", "\n", "\n", - "# %% [75] markdown\n", + "# %% [76] markdown\n", "# 结合笔记本,重定向&调用测试处理\n", "\n", - "# %% [76] code\n", + "# %% [77] code\n", "# ! 在此重定向,以便后续外部调用\n", "\"重定向「笔记本」的默认「单元格」类型\"\n", "IpynbNotebook(json) = IpynbNotebook{IpynbCell}(json)\n", "\n", "\n", "\n", - "# %% [77] markdown\n", + "# %% [78] markdown\n", "# ## 编译单元格\n", "\n", - "# %% [78] markdown\n", + "# %% [79] markdown\n", "# ### 编译/入口\n", "\n", - "# %% [79] code\n", + "# %% [80] code\n", "export compile_cell\n", "\n", "\"\"\"\n", @@ -4246,10 +4281,10 @@ " for (line_num, cell) in enumerate(cells) # ! ←一定是顺序遍历\n", " ), '\\n')\n", "\n", - "# %% [80] markdown\n", + "# %% [81] markdown\n", "# ### 编译/单元格标头\n", "\n", - "# %% [81] code\n", + "# %% [82] code\n", "\"\"\"\n", "【内部】对整个单元格的「类型标头」编译\n", "- 🎯生成一行注释,标识单元格\n", @@ -4272,14 +4307,14 @@ "\n", "\n", "\n", - "# %% [82] markdown\n", + "# %% [83] markdown\n", "# ### 编译/代码\n", "\n", "\n", - "# %% [84] markdown\n", + "# %% [85] markdown\n", "# 主编译方法\n", "\n", - "# %% [85] code\n", + "# %% [86] code\n", "\"\"\"\n", "对代码的编译\n", "- @param cell 所需编译的单元格\n", @@ -4398,10 +4433,10 @@ "\n", "\n", "\n", - "# %% [86] markdown\n", + "# %% [87] markdown\n", "# ### 编译/Markdown\n", "\n", - "# %% [87] code\n", + "# %% [88] code\n", "\"\"\"\n", "对Markdown的编译\n", "- 📌主要方法:转换成多个单行注释\n", @@ -4436,10 +4471,10 @@ "\n", "\n", "\n", - "# %% [88] markdown\n", + "# %% [89] markdown\n", "# ## 解析执行单元格\n", "\n", - "# %% [89] markdown\n", + "# %% [90] markdown\n", "# 🎯将单元格解析**编译**成Julia表达式,并可直接作为代码执行\n", "# - 【核心】解释:`parse_cell`\n", "# - 📌基本是`compile_cell` ∘ `Meta.parse`的复合\n", @@ -4451,7 +4486,7 @@ "# - 📌基本是`parse_cell` ∘ `eval`的复合\n", "# - ⚙️可任意指定其中的`eval`函数\n", "\n", - "# %% [90] code\n", + "# %% [91] code\n", "export parse_cell, tryparse_cell, eval_cell\n", "\n", "\"\"\"\n", @@ -4527,13 +4562,13 @@ "\n", "\n", "\n", - "# %% [92] markdown\n", + "# %% [93] markdown\n", "# ## 编译解析笔记本\n", "\n", - "# %% [93] markdown\n", + "# %% [94] markdown\n", "# 编译笔记本\n", "\n", - "# %% [94] code\n", + "# %% [95] code\n", "export compile_notebook\n", "\n", "\"\"\"\n", @@ -4614,10 +4649,10 @@ "\n", "\n", "\n", - "# %% [95] markdown\n", + "# %% [96] markdown\n", "# 解析笔记本\n", "\n", - "# %% [96] code\n", + "# %% [97] code\n", "export parse_notebook, tryparse_notebook\n", "\n", "\"\"\"\n", @@ -4652,13 +4687,13 @@ "\n", "\n", "\n", - "# %% [97] markdown\n", + "# %% [98] markdown\n", "# ## 执行笔记本\n", "\n", - "# %% [98] markdown\n", + "# %% [99] markdown\n", "# 执行笔记本\n", "\n", - "# %% [99] code\n", + "# %% [100] code\n", "export eval_notebook, eval_notebook_by_cell\n", "\n", "\"\"\"\n", @@ -4691,10 +4726,10 @@ "\n", "# ! 测试代码放在最后边\n", "\n", - "# %% [100] markdown\n", + "# %% [101] markdown\n", "# 引入笔记本\n", "\n", - "# %% [101] code\n", + "# %% [102] code\n", "export include_notebook, include_notebook_by_cell\n", "\n", "\"\"\"\n", @@ -4730,10 +4765,10 @@ "\n", "\n", "\n", - "# %% [102] markdown\n", + "# %% [103] markdown\n", "# ## 关闭模块上下文\n", "\n", - "# %% [103] code\n", + "# %% [104] code\n", "# ! ↓这后边注释的代码只有在编译后才会被执行\n", "# ! 仍然使用多行注释语法,以便统一格式\n", "end # module\n", @@ -5111,7 +5146,7 @@ "output_type": "stream", "text": [ "\u001b[93m\u001b[1m✅Jupyter笔记本「主模块」自编译成功!\u001b[22m\u001b[39m\n", - "\u001b[93m\u001b[1m(共写入 nothing 个字节)\u001b[22m\u001b[39m\n" + "\u001b[93m\u001b[1m(共写入 50148 个字节)\u001b[22m\u001b[39m\n" ] } ], @@ -5122,8 +5157,7 @@ " # !不能在`runtests.jl`中运行\n", " contains(@__DIR__, \"test\") && return\n", "\n", - " # * 测试Pair编译\n", - " write_bytes = compile_notebook(SELF_PATH => joinpath(ROOT_PATH, \"src\", OUT_LIB_FILE))\n", + " write_bytes = compile_notebook(SELF_PATH, joinpath(ROOT_PATH, \"src\", OUT_LIB_FILE))\n", " printstyled(\n", " \"✅Jupyter笔记本「主模块」自编译成功!\\n(共写入 $write_bytes 个字节)\\n\";\n", " color=:light_yellow, bold=true\n", @@ -5208,7 +5242,7 @@ "output_type": "stream", "text": [ "\u001b[92m\u001b[1m✅测试文件编译成功!\u001b[22m\u001b[39m\n", - "\u001b[92m\u001b[1m(共写入 40869 个字节)\u001b[22m\u001b[39m\n" + "\u001b[92m\u001b[1m(共写入 40993 个字节)\u001b[22m\u001b[39m\n" ] } ], @@ -5267,9 +5301,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "\n", + "\n", "# IpynbCompile.jl: 一个实用的Jupyter笔记本构建工具\n", "\n", + "[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)\n", + "[![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/)\n", + "\n", + "[![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml)\n", + "\n", + "该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。\n", + "\n", "## 主要功能\n", "\n", "### 简介\n", @@ -5496,7 +5537,7 @@ { "data": { "text/plain": [ - "7338" + "7861" ] }, "execution_count": 33, diff --git a/src/IpynbCompile.jl b/src/IpynbCompile.jl index 928411a..8371346 100644 --- a/src/IpynbCompile.jl +++ b/src/IpynbCompile.jl @@ -12,12 +12,20 @@ # (✨执行其中所有单元格,可自动构建、测试并生成相应`.jl`源码、测试文件与README!) # %% [3] markdown -# ## 主要功能 +# [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) +# [![Static Badge](https://img.shields.io/badge/julia-package?logo=julia&label=1.7%2B)](https://julialang.org/) +# +# [![CI status](https://github.com/ARCJ137442/IpynbCompile.jl/workflows/CI/badge.svg)](https://github.com/ARCJ137442/IpynbCompile.jl/actions/workflows/ci.yml) +# +# 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 # %% [4] markdown -# ### 简介 +# ## 主要功能 # %% [5] markdown +# ### 简介 + +# %% [6] markdown # 📍主要功能:为 [***Jupyter***](https://jupyter.org/) 笔记本(`.ipynb`文件)提供一套特定的注释语法,以支持 **编译转换**&**解释执行** 功能,扩展其应用的可能性 # # - 📌可【打开】并【解析】Jupyter笔记本:提供基本的「Jupyter笔记本」「Jupyter笔记本元数据」「Jupyter笔记本单元格」数据结构定义 @@ -52,13 +60,13 @@ # - 引入笔记本 `include_notebook` # - 逐单元格版本:`include_notebook_by_cell` -# %% [6] markdown +# %% [7] markdown # ✨创新点:**使用多样的「特殊注释」机制,让使用者能更灵活、更便捷地编译Jupyter笔记本,并能将其【交互式】优势用于库的编写之中** -# %% [7] markdown +# %% [8] markdown # ### 重要机制:单元格「特殊注释」 -# %% [8] markdown +# %% [9] markdown # 简介:单元格的主要「特殊注释」及其作用(以`# 单行注释` `#= 块注释 =#`为例) # # - `# %ignore-line` 忽略下一行 @@ -70,19 +78,19 @@ # - `%only-compiled =#` 仅编译后可用(尾) # - `# %include <路径>` 引入指定路径的文件内容,替代一整行注释 -# %% [9] markdown +# %% [10] markdown # ✨**该笔记本自身**,就是一个好的用法参考来源 -# %% [10] markdown +# %% [11] markdown # #### 各个「特殊注释」的用法 -# %% [11] markdown +# %% [12] markdown # ##### 忽略单行 -# %% [12] markdown +# %% [13] markdown # 📌简要用途:忽略**下一行**代码 -# %% [13] markdown +# %% [14] markdown # 编译前@笔记本单元格: # # ```julia @@ -99,10 +107,10 @@ # [["下边也要被编译"]] # ``` -# %% [14] markdown +# %% [15] markdown # ##### 忽略下面所有行 -# %% [15] markdown +# %% [16] markdown # 编译前@笔记本单元格 # # ```julia @@ -121,10 +129,10 @@ # [["上边的代码正常编译"]] # ``` -# %% [16] markdown +# %% [17] markdown # ##### 忽略整个单元格 -# %% [17] markdown +# %% [18] markdown # 编译前@笔记本单元格: # # ```julia @@ -141,16 +149,16 @@ # # ↑空字串 -# %% [18] markdown +# %% [19] markdown # 📌一般习惯将 `# %ignore-cell` 放在第一行 -# %% [19] markdown +# %% [20] markdown # ##### 忽略代码块 -# %% [20] markdown +# %% [21] markdown # 📝即「块忽略」 -# %% [21] markdown +# %% [22] markdown # 编译前@笔记本单元格: # # ```julia @@ -169,15 +177,15 @@ # [["下面的代码都会被编译"]] # ``` -# %% [22] markdown +# %% [23] markdown # ##### 仅编译后可用 -# %% [23] markdown +# %% [24] markdown # 主要用途:包装 `module` 等代码,实现编译后模块上下文 # # - ⚠️对于 **Python** 等【依赖缩进定义上下文】的语言,难以进行此类编译 -# %% [24] markdown +# %% [25] markdown # 编译前@笔记本单元格: # # ```julia @@ -206,15 +214,15 @@ # [["下面的代码正常编译,并且会随着笔记本一起执行"]] # ``` -# %% [25] markdown +# %% [26] markdown # ##### 文件引入 -# %% [26] markdown +# %% [27] markdown # 主要用途:结合「仅编译后可用」实现「外部代码内联」 # # - 如:集成某些**中小型映射表**,整合零散源码文件…… -# %% [27] markdown +# %% [28] markdown # 编译前@笔记本单元格: # # ```julia @@ -252,26 +260,26 @@ # # 📝Julia的「空白符无关性」允许在等号后边大范围附带注释的空白 -# %% [28] markdown +# %% [29] markdown # ## 参考 -# %% [29] markdown +# %% [30] markdown # - 本Julia库的灵感来源:[Promises.jl/src/notebook.jl](https://github.com/fonsp/Promises.jl/blob/main/src/notebook.jl) # - 源库使用了 [**Pluto.jl**](https://github.com/fonsp/Pluto.jl) 的「笔记本导出」功能 # - **Jupyter Notebook** 文件格式(JSON):[🔗nbformat.readthedocs.io](https://nbformat.readthedocs.io/en/latest/format_description.html#notebook-file-format) -# %% [30] markdown +# %% [31] markdown # # # ⚠️该单元格首行注释用于截止生成`README.md`(包括自身) -# %% [31] markdown +# %% [32] markdown # ## 建立模块上下文 -# %% [32] markdown +# %% [33] markdown # 📌使用 `# %only-compiled` 控制 `module` 代码,生成模块上下文 -# %% [33] code +# %% [34] code # ! ↓这后边注释的代码只有在编译后才会被执行 # ! 使用多行注释/块注释的语法, # ! 以`#= %only-compiled`行*开头* @@ -282,46 +290,51 @@ IpynbCompile 主模块 module IpynbCompile # 后续编译后会变为模块上下文 -# %% [34] markdown +# %% [35] markdown # ## 前置模块 -# %% [35] markdown +# %% [36] markdown # ### 导入库 -# %% [36] code +# %% [37] code import JSON -# %% [37] markdown +# %% [38] markdown # ### 预置语法糖 -# %% [38] code +# %% [39] code "JSON常用的字典" const JSONDict{ValueType} = Dict{String,ValueType} where ValueType "默认解析出来的JSON字典(与`JSONDict`有本质不同,会影响到后续方法分派,并可能导致歧义)" const JSONDictAny = JSONDict{Any} -# %% [39] markdown +# %% [40] markdown # ### 兼容+注意事项 -# %% [40] code +# %% [41] code import Base: @kwdef # 兼容Julia 1.8⁻ -# %% [41] markdown -# - 📌兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用 -# - 📌兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出 +# %% [42] markdown +# - ✅兼容 @ Julia **1.5⁺**:`include`自Julia **1.5**方可用 +# - 🔒锁定最低Julia版本为**1.5** +# - ❌兼容 @ Julia **1.6⁻**:「多行字符串」自Julia **1.7**方可使用"\【换行】"取消换行 +# - 📄错误信息:`LoadError: syntax: invalid escape sequence` +# - 🔒2024-02-04:锁定Julia版本为**1.7⁺** +# - ✅兼容 @ Julia **1.8⁻**:`Base.@kwdef`自Julia **1.9**方被导出 +# - 📄错误信息:`LoadError: UndefVarError: @kwdef not defined` # - ⚠️禁止不引入直接使用`Base.@kwdef` -# - 📌兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型 +# - ✅兼容 @ Julia **1.7⁻**:全局`const`自Julia **1.8**方能附带类型 +# - 📄错误信息:`LoadError: syntax: type declarations on global variables are not yet supported` # - ⚠️禁止在`const`定义的变量中标注类型(Julia运行时会自动推导) -# - 📄错误信息:`ERROR: LoadError: syntax: type declarations on global variables are not yet supported` -# %% [42] markdown +# %% [43] markdown # ## 读取解析Jupyter笔记本(`.ipynb`文件) -# %% [43] markdown +# %% [44] markdown # ### 读取文件(JSON) -# %% [44] code +# %% [45] code export read_ipynb_json """ @@ -337,10 +350,10 @@ read_ipynb_json(path) = # ! ↓使用`# %ignore-line`让 编译器/解释器 忽略下一行 -# %% [45] markdown +# %% [46] markdown # ### 解析文件元信息 -# %% [46] markdown +# %% [47] markdown # Jupyter Notebook元数据 格式参考 # # ```yaml @@ -365,7 +378,7 @@ read_ipynb_json(path) = # } # ``` -# %% [47] markdown +# %% [48] markdown # Jupyter Notebook Cell 格式参考 # # 共有: @@ -409,7 +422,7 @@ read_ipynb_json(path) = # } # ``` -# %% [48] markdown +# %% [49] markdown # 当前Julia笔记本 元数据: # # ```json @@ -431,13 +444,13 @@ read_ipynb_json(path) = # (截止至2024-01-16) -# %% [50] markdown +# %% [51] markdown # ## 解析Jupyter笔记本(Julia `struct`) -# %% [51] markdown +# %% [52] markdown # ### 定义「笔记本」结构 -# %% [52] code +# %% [53] code export IpynbNotebook, IpynbNotebookMetadata """ @@ -500,22 +513,22 @@ IpynbNotebookMetadata(json::JSONDict) = IpynbNotebookMetadata(; # ! ↓使用`# %ignore-below`让 编译器/解释器 忽略后续内容 -# %% [53] markdown +# %% [54] markdown # ### 读取笔记本 总函数 -# %% [54] markdown +# %% [55] markdown # 根据路径读取笔记本 -# %% [55] code +# %% [56] code export read_notebook "从路径读取Jupyter笔记本(`struct IpynbNotebook`)" read_notebook(path::AbstractString)::IpynbNotebook = IpynbNotebook(read_ipynb_json(path)) -# %% [56] markdown +# %% [57] markdown # 方便引入笔记本的字符串宏 -# %% [57] code +# %% [58] code export @notebook_str macro notebook_str(path::AbstractString) @@ -523,13 +536,13 @@ macro notebook_str(path::AbstractString) end -# %% [58] markdown +# %% [59] markdown # ### 解析/生成 笔记本信息 -# %% [59] markdown +# %% [60] markdown # #### 识别编程语言 -# %% [60] code +# %% [61] code "【内部】编程语言⇒正则表达式 识别字典" const LANG_IDENTIFY_DICT = Dict{Symbol,Regex}( lang => Regex("^(?:$regex_str)\$") # ! ←必须头尾精确匹配(不然就会把`JavaScript`认成`r`) @@ -623,14 +636,14 @@ identify_lang(language_text::AbstractString) = end # ! 默认返回`nothing` -# %% [61] markdown +# %% [62] markdown # #### 根据编程语言生成注释 # # - 生成的注释会用于「行开头」识别 # - 如:`// %ignore-cell` (C系列) # - 如:`# %ignore-cell` (Python/Julia) -# %% [62] code +# %% [63] code "【内部】编程语言⇒单行注释" const LANG_COMMENT_DICT_INLINE = Dict{Symbol,String}() @@ -719,10 +732,10 @@ generate_comment_multiline_tail(lang::Symbol) = LANG_COMMENT_DICT_MULTILINE_TAIL -# %% [63] markdown +# %% [64] markdown # #### 生成常用扩展名 -# %% [64] code +# %% [65] code "【内部】编程语言⇒常用扩展名(不带`.`)" const LANG_EXTENSION_DICT = Dict{Symbol,String}( # ! 以下「特殊注释」需要在行首 @@ -798,17 +811,17 @@ get_extension(lang::Symbol) = get( -# %% [65] markdown +# %% [66] markdown # #### 解析/生成 测试 -# %% [67] markdown +# %% [68] markdown # ### Notebook编译/头部注释 # # - 🎯标注 版本信息 # - 🎯标注 各类元数据 -# %% [68] code +# %% [69] code """ 【内部】从Notebook生成头部注释 - ⚠️末尾有换行 @@ -834,16 +847,16 @@ $(generate_comment_inline(lang)) % nbformat_minor: $(notebook.nbformat_minor) -# %% [69] markdown +# %% [70] markdown # ## 解析处理单元格 -# %% [70] markdown +# %% [71] markdown # ### 定义「单元格」 -# %% [71] markdown +# %% [72] markdown # 定义结构类型 -# %% [72] code +# %% [73] code export IpynbCell """ @@ -881,10 +894,10 @@ struct IpynbCell )...) end -# %% [73] markdown +# %% [74] markdown # 定义快捷字符串宏 -# %% [74] code +# %% [75] code export @cell_str "🎯将字符串拆分成单元格各行(区分末尾换行)" @@ -921,23 +934,23 @@ end -# %% [75] markdown +# %% [76] markdown # 结合笔记本,重定向&调用测试处理 -# %% [76] code +# %% [77] code # ! 在此重定向,以便后续外部调用 "重定向「笔记本」的默认「单元格」类型" IpynbNotebook(json) = IpynbNotebook{IpynbCell}(json) -# %% [77] markdown +# %% [78] markdown # ## 编译单元格 -# %% [78] markdown +# %% [79] markdown # ### 编译/入口 -# %% [79] code +# %% [80] code export compile_cell """ @@ -972,10 +985,10 @@ compile_cell(cells::Vector{IpynbCell}; kwargs...)::String = join(( for (line_num, cell) in enumerate(cells) # ! ←一定是顺序遍历 ), '\n') -# %% [80] markdown +# %% [81] markdown # ### 编译/单元格标头 -# %% [81] code +# %% [82] code """ 【内部】对整个单元格的「类型标头」编译 - 🎯生成一行注释,标识单元格 @@ -998,14 +1011,14 @@ $(cell.cell_type) -# %% [82] markdown +# %% [83] markdown # ### 编译/代码 -# %% [84] markdown +# %% [85] markdown # 主编译方法 -# %% [85] code +# %% [86] code """ 对代码的编译 - @param cell 所需编译的单元格 @@ -1124,10 +1137,10 @@ end -# %% [86] markdown +# %% [87] markdown # ### 编译/Markdown -# %% [87] code +# %% [88] code """ 对Markdown的编译 - 📌主要方法:转换成多个单行注释 @@ -1162,10 +1175,10 @@ end -# %% [88] markdown +# %% [89] markdown # ## 解析执行单元格 -# %% [89] markdown +# %% [90] markdown # 🎯将单元格解析**编译**成Julia表达式,并可直接作为代码执行 # - 【核心】解释:`parse_cell` # - 📌基本是`compile_cell` ∘ `Meta.parse`的复合 @@ -1177,7 +1190,7 @@ end # - 📌基本是`parse_cell` ∘ `eval`的复合 # - ⚙️可任意指定其中的`eval`函数 -# %% [90] code +# %% [91] code export parse_cell, tryparse_cell, eval_cell """ @@ -1253,13 +1266,13 @@ eval_cell(code_or_codes; eval_function=Main.eval, kwargs...) = eval_function( -# %% [92] markdown +# %% [93] markdown # ## 编译解析笔记本 -# %% [93] markdown +# %% [94] markdown # 编译笔记本 -# %% [94] code +# %% [95] code export compile_notebook """ @@ -1340,10 +1353,10 @@ end -# %% [95] markdown +# %% [96] markdown # 解析笔记本 -# %% [96] code +# %% [97] code export parse_notebook, tryparse_notebook """ @@ -1378,13 +1391,13 @@ tryparse_notebook(args...; kwargs...) = -# %% [97] markdown +# %% [98] markdown # ## 执行笔记本 -# %% [98] markdown +# %% [99] markdown # 执行笔记本 -# %% [99] code +# %% [100] code export eval_notebook, eval_notebook_by_cell """ @@ -1417,10 +1430,10 @@ end # ! 测试代码放在最后边 -# %% [100] markdown +# %% [101] markdown # 引入笔记本 -# %% [101] code +# %% [102] code export include_notebook, include_notebook_by_cell """ @@ -1456,10 +1469,10 @@ include_notebook_by_cell(path::AbstractString; kwargs...) = eval_notebook_by_cel -# %% [102] markdown +# %% [103] markdown # ## 关闭模块上下文 -# %% [103] code +# %% [104] code # ! ↓这后边注释的代码只有在编译后才会被执行 # ! 仍然使用多行注释语法,以便统一格式 end # module diff --git a/test/runtests.jl b/test/runtests.jl index 13ec35a..4ba7918 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -763,7 +763,12 @@ eval_cell(code_or_codes; eval_function=Main.eval, kwargs...) = eval_function( # %ignore-below # 执行其中一个代码单元格 # * 参考「预置语法糖」 -eval_cell(codes[3]; lang=:julia)::Base.Docs.Binding +let cell_const = codes[findfirst(codes) do cell + cell.cell_type == "code" && + contains(cell.source[end], "const") + end] + eval_cell(cell_const; lang=:julia)::Base.Docs.Binding +end # 尝试对每个单元格进行解析 [ @@ -992,8 +997,7 @@ let OUT_LIB_FILE = "IpynbCompile.jl" # 直接作为库的主文件 # !不能在`runtests.jl`中运行 contains(@__DIR__, "test") && return - # * 测试Pair编译 - write_bytes = compile_notebook(SELF_PATH => joinpath(ROOT_PATH, "src", OUT_LIB_FILE)) + write_bytes = compile_notebook(SELF_PATH, joinpath(ROOT_PATH, "src", OUT_LIB_FILE)) printstyled( "✅Jupyter笔记本「主模块」自编译成功!\n(共写入 $write_bytes 个字节)\n"; color=:light_yellow, bold=true