认识 Notist
Notist 是一种面向知识库的结构化文本语言。普通文字可以直接书写;Module 引用把多篇文档连接起来;Function 构造标题、引用、代码等内容;Scope 和 Attributes 为内容附加可查询的元数据。
一个 .not 文件既是可读的文本,也是一个有类型的文档程序。Notist 会把它求值为 Content,再用于检查、预览或构建静态页面。
本文从写作者的角度介绍 Notist 的核心机制。精确的字符、空白和 delimiter 规则见 vault::grammar。
一篇 Notist 文档
下面的示例包含标题、Module 引用、元数据、引用块和代码:
#heading(level=1)[Project Notes]
The language reference is [[vault::grammar]].
#heading(level=2)[Status]
#[The documentation build passes.]@status,#verified,owner="Alice"
#quote[
Run `cargo test` before publishing.
]
#heading(level=2)[Example]
```rust
fn main() {
println!("hello");
}
```这仍然是一篇以普通文字为主体的文档。#heading 和 #quote 是产生内容的 Function;inline backtick 与 fenced code 是内置 Raw 语法;[[vault::grammar]] 是跨文档引用;#[...]@... 为一段内容附加元数据。
Vault 与 Module
一个目录就是一个 Vault。Vault 中的每个 .not 文件对应一个逻辑 Module:
docs/README.not -> vault
docs/intro.not -> vault::intro
docs/guide/setup.not -> vault::guide::setup
docs/guide/README.not -> vault::guide普通文件名成为 ModulePath 的最后一段。README.not 表示它所在的目录,因此 guide.not 与 guide/README.not 都会映射到 vault::guide,不能同时存在。
只有子 Module、没有 README.not 的目录仍然可以出现在 Module 树中。例如存在 guide/setup.not 时,vault::guide 可以作为承载 vault::guide::setup 的目录 Module。
ModulePath 是文档的稳定逻辑名称。引用和工具使用 vault::guide::setup,而不是依赖操作系统路径写法。
引用其他 Module
使用 [[...]] 引用 Module。假设当前文档是 vault::guide:
[[setup]]
[[self::setup]]
[[super::intro]]
[[vault::grammar]][[setup]]从当前 Module 查找 child,即vault::guide::setup。[[self::setup]]同样从当前 Module 开始,但把起点写得更明确。[[super::intro]]先回到父 Module,再找到vault::intro。[[vault::grammar]]从 Vault 根开始,不受当前文档位置影响。
路径使用 :: 分段。连续的 super 可以继续向上,例如 [[super::super::shared]]。引用还可以带 label:[[vault::guide#install]] 指向目标 Module 内的 install。
Function 调用
Function 通过普通参数接收值并产生 Content。圆括号中的 arguments 可以独立构成完整调用:
#pagebreak()
#raw(text="cargo test")
#raw(text=r#"fn main() {}"#, lang="rust")Function 还可以接收一个 trailing Content:
#quote[Quoted content.]
#heading(level=2)[Title]
#callout(
kind="warning"
)[
Read [[vault::grammar]] before editing the syntax.
Run `notist check docs` after editing.
]#name(args)[Content] 中的 [...] 先递归求值为结构化 Content,再绑定到 Function signature 指定的 trailing_content 参数。它不是普通位置参数,因此可以跟在 named arguments 之后。#name[Content] 是省略圆括号的常用形式。#name(args) 不要求 trailing Content;是否缺少必需参数由类型检查决定,而不是 parser 决定。
例如签名可以写成:
heading(level: Int = 1, body: Content) -> Content
quote(attribution: Content? = none, body: Content) -> Content
callout(kind: String = "note", title: Content? = none, body: Content) -> Content
details(summary: Content? = none, open: Bool = false, body: Content) -> Content
raw(text: String, lang: String? = none) -> Content因此 #quote("text") 是类型错误,因为 String 不能替代 Content;#raw[parsed content] 也是类型错误,因为 trailing Content 不能绑定到 text: String。源码不再通过特殊 Raw body 传递,旧的 #name![...]! 已从语法中删除。
Function 名称可以使用 :: 分段。插件可以提供普通调用,例如:
#diagram::mermaid(
source=r#"""
graph TD
A --> B
"""#,
theme="neutral"
)其中 source 与其他参数一样是 String;宿主 parser 不根据 Function 名称切换 body grammar。
String Literal
Notist 提供四种 String literal,四者都产生同一个 String 类型:
形式 | 用途 |
|---|---|
| 单行 escaped String |
| 多行 escaped String |
| 单行 raw String |
| 多行 raw String |
Escaped String 支持 \"、\\、\n、\r、\t。Raw String 不处理反斜杠;至少使用一个 #,closing delimiter 必须使用完全相同数量的 #。正文可能包含 closing-like 文本时提高 hash level:
#raw(text=r##"contains "# without closing"##)
#raw(
text=r##"""
contains """# without closing
and keeps every backslash \ unchanged
"""##,
lang="text",
)Inline 与 Multiline 是 literal 的源码形态,不是不同的 String 类型。只有 triple quote 后立即出现 LF 或 CRLF 时才进入 Multiline;这个 required opening framing newline,以及 closing delimiter 前紧邻的一个 framing newline,会从 value 中裁掉。除此之外不裁剪空白,也不执行 dedent。
要求 opening line break 是为了消除 raw form 的歧义。r#"""# 是值为 " 的 Inline Raw String;r#"""text"""# 也是 Inline,值为 ""text""。Multiline 必须写成实际多行:
r#"""
text
"""#Raw 内容
日常文档中的代码不需要显式调用 raw。一对相同长度的 backtick 直接产生 inline Raw Element:
Run `cargo test` before publishing.
Use ``two ` delimiters`` when the payload contains a backtick.三个或更多 backtick 构成 fenced Raw。opening line 上的 tag 成为 language,fence 产生 block Raw Element:
```rust
fn main() {}
```Backtick 和 fence 是核心 grammar 的内置语法糖。它们直接归一到 Raw Element 构造逻辑,不查找名为 raw 的 Function,也不能出现在 Function arguments 中充当值。
需要以普通函数方式构造 Raw 时,把源码作为 String 参数传给 raw:
#raw(text="cargo test")
#raw(
text=r#"""
fn main() {
println!("hello");
}
"""#,
lang="rust",
)直接传入的 Inline String literal 产生 inline Raw,Multiline String literal 产生 block Raw。Raw String 只改变 escape 行为;一对 quote 是 Inline,triple quote 只有紧跟 opening line break 时才是 Multiline。裸 fence 的 info tag 直接属于语法糖产生的 Raw Element,因此不存在它与显式 raw(..., lang=...) 参数竞争的问题。
类型系统
Function 的参数、trailing Content 和返回值由同一套类型系统描述。Notist 的核心类型包括:
类型 | 常见值或来源 | 含义 |
|---|---|---|
|
| 没有值 |
|
| 布尔值 |
|
| 整数 |
|
| 浮点数 |
|
| UTF-8 字符串;literal 保留 form/style provenance |
| Module 求值结果、trailing Content 或 Function 返回值 | 结构化的 Notist 文档内容 |
|
| 可选值,例如 |
Content 是文档的基础类型。一篇 Module 求值后得到 Content;普通文字、Reference、Raw syntax 和 Function 结果按顺序组成它。Content 不是源码字符串,而是已经具有文档意义、可以继续组合和渲染的内容。
源码参数使用 String,不再引入仅能出现在 body position 的 RawSource 类型。Raw String 与 escaped String 的差别仅在词法阶段;求值后都参与同一套参数绑定和 String 类型检查。
参数与绑定
参数写在 Function 名称后的圆括号中:
#heading(2)[Positional argument]
#heading(level=2)[Named argument]
#raw(text=r#"fn main() {}"#, lang="rust")位置参数按签名中的顺序绑定,命名参数按名称绑定。命名参数之后不能再出现位置参数,但 signature 指定的 trailing Content 可以继续跟在 named arguments 之后。Function 可以为参数提供默认值,因此 #heading[Title] 会使用默认的 level=1,#raw(text="text") 会使用默认的 lang=none。
参数同样经过类型检查。未知参数、重复参数、缺少必需参数或类型不匹配都会指向对应的调用位置。Int 可以传给要求 Float 的参数,Optional(T) 接受 none 或 T。
Scope 与 Attributes
Function 用来产生内容;Transparent Scope #[...] 用来标注文档中的一段范围。Scope 不改变内部 Content,只为它附加 Attributes:
#[The documentation build passes.]@status,#verified,.highlight,owner="Alice"上例包含四种元数据:
status是 ID,用于稳定标识这段内容。#verified是 tag,用于分类和查询。.highlight是 class,可供 renderer 选择展示样式。owner="Alice"是 key-value property。
Attributes 也可以跟在 Function 结果后:
#quote[Keep references stable.]@principle,#design,owner="Alice"
#raw(text="cargo test")@command,#verified圆括号中的 arguments 控制 Function 如何产生内容;结尾的 attributes 描述这段内容是什么。level=2 是 heading 的参数,而 #design 是输出内容的 tag。
段落与块
普通文字和 inline content 会组成段落。单个换行仍属于同一段,空行结束当前段落:
first line
second line
new paragraphHeading、Quote、fenced Raw 和显式 multiline Raw 等 block content 会结束当前段落并独立展示。这样文档结构由 Content 中各元素的意义决定,而不只是由最终 HTML 决定。
检查、构建与预览
Notist CLI 以一个 Vault 目录为输入:
notist check docs
notist build docs -o dist
notist preview docscheck 检查 Module、引用、调用和类型问题。build 把整个 Vault 构建为静态站点。preview 在本地展示相同的页面,并在文档变化后刷新。
日常写作主要围绕四个概念:用 Module 组织知识,用 Reference 连接文档,用 Function 从有类型的参数与 trailing Content 产生 Content,用 Scope 和 Attributes 描述内容。需要确认某一语法形式的严格定义时,查阅 vault::grammar。