1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! `function_descriptions` 子命令:把 `#[duck_*]` 上声明的函数文档写成
//! community-extensions 文档页用的 CSV。
//!
//! 这是纯内存操作:读的是宏提交进 inventory 的元数据,不加载扩展、不查 catalog,
//! 因此不需要 DuckDB,也不受插件运行时影响。
//!
//! The `function_descriptions` subcommand: writes the function documentation declared on
//! `#[duck_*]` into the CSV the community-extension doc pages consume. It is a pure in-memory
//! operation over the metadata the macros submitted to inventory — no extension is loaded and the
//! catalog is never queried, so it needs no DuckDB and is unaffected by the extension runtime.
use crate::;
use Cow;
use io;
use ;
/// CSV 的表头。`duckdb/community-extensions` 的 `scripts/generate_md.sh` 用 `read_csv()` 读这份
/// 文件,`function` 是它的 JOIN 键(对应 `function_name`),另外三列覆盖同名展示列。
///
/// The CSV header. `scripts/generate_md.sh` in `duckdb/community-extensions` reads this file with
/// `read_csv()`; `function` is the JOIN key (matching `function_name`) and the other three columns
/// override the display columns of the same name.
pub const CSV_HEADER: = ;
/// 默认的输出文件名(位于项目根目录的 `target/` 下)。
///
/// The default output file name (under the project root's `target/`).
pub const FILE_NAME: &str = "function_descriptions.csv";
/// `--all` 时的输出文件名。
///
/// The output file name when `--all` is given.
pub const ALL_FILE_NAME: &str = "function_descriptions_all.csv";
/// 一次导出的结果,供 CLI 打一行摘要。
///
/// The outcome of one export, so the CLI can print a one-line summary.
/// 输出路径固定为 `<project_dir>/target/<文件名>`:固定路径便于后续自动化处理。
///
/// The output path is fixed at `<project_dir>/target/<file name>`, which keeps later automation
/// simple.
/// 收集宏声明的文档并写出 CSV,返回统计。
///
/// `all` 为 `false`(默认)时只写 [`FunctionDescription::is_documented`] 为真的函数,
/// 为 `true` 时把没写文档的也写出来(三列留空),便于当「还差哪些没写」的清单看。
///
/// # Errors
///
/// 建目录或写文件失败时返回 I/O 错误。
///
/// Collects the documentation declared by the macros, writes the CSV and reports what happened.
/// With `all == false` (the default) only functions where [`FunctionDescription::is_documented`]
/// holds are written; with `all == true` the undocumented ones are written too, with empty columns,
/// which makes the file a "still to write" checklist.
///
/// # Errors
///
/// Returns an I/O error when creating the directory or writing the file fails.
/// 用一个 `csv` crate 的 writer 写出表头 + 每一行。
///
/// 转义(字段含逗号 / 引号 / 换行时的加引号与双写)由 `csv` crate 负责,这里不手拼字符串。
/// 多条示例经 [`join_examples`] 拼成一个 `example` 字段:`generate_md.sh` 只做
/// `'[' || other.example || ']'`,不会按分隔符再拆一次,所以要在写出前拼好。
/// 字段里的换行会被压成一个空格,见 [`flatten_newlines`]。
///
/// Writes the header plus one row per function through a `csv` crate writer. Quoting and escaping
/// (for fields containing commas, quotes or newlines) are the crate's job rather than hand-rolled
/// string building. Several examples go through [`join_examples`] into the single `example` field,
/// because `generate_md.sh` only does `'[' || other.example || ']'` and never splits the field
/// again. Newlines inside a field collapse to a single space, see [`flatten_newlines`].
/// 把多条示例拼进 CSV 那一个 `example` 字段:每条去掉首尾空白与结尾的分号,再用 `"; "` 连接。
///
/// 用分号而不是逗号:真实 SQL 里逗号遍地都是(`FROM (VALUES (1, 'a'), (2, NULL)) v(i, s)`),
/// 用 `", "` 拼成一串之后根本分不清一条示例在哪结束 —— 渲染出来是 `[a, b, c]`,和示例内部的逗号混在
/// 一起。`"; "` 是 SQL 自己的语句分隔符,拼出来与「把几条语句连着写」完全一样,一眼能读。
///
/// 先把每条结尾的分号去掉,是为了避免出现 `a;; b`:有人习惯给示例收尾加分号(community-extensions
/// 里 `fakeit`、`chsql` 的 CSV 就是),有人不加(`dq` 不加),归一化之后两种写法结果一致。
///
/// Joins several examples into the CSV's single `example` field: each is stripped of surrounding
/// whitespace and of a trailing semicolon, then they are joined with `"; "`. A semicolon rather than
/// a comma, because real SQL is full of commas (`FROM (VALUES (1, 'a'), (2, NULL)) v(i, s)`) and a
/// `", "` join renders as `[a, b, c]`, indistinguishable from the commas inside the examples
/// themselves — `"; "` is SQL's own statement separator, so the result reads exactly like the
/// statements typed one after another. Dropping the trailing semicolon avoids `a;; b`: some people
/// terminate their examples (`fakeit` and `chsql` do in their community-extension CSVs) and some do
/// not (`dq` does not), and normalising makes both come out the same.
/// 把字段里的一段连续换行压成一个空格。
///
/// 两个原因,都是实测出来的:
///
/// 1. `generate_md.sh` 把这几列塞进一张 Markdown 表格,单元格里的换行会把表格行拆断;
/// 2. DuckDB 的 `read_csv()` 会把引号内字段里的 `\n` **读成 `\r\n`**
/// (实测:`length()` 多 1、`contains(x, chr(13))` 为真),所以原样写出去也会被改掉。
///
/// 与其让它变形成不可预期的样子,不如在写出时就压平。
///
/// Collapses a run of newlines inside a field into a single space, for two measured reasons:
/// `generate_md.sh` puts these columns into a Markdown table where a newline breaks the row, and
/// DuckDB's `read_csv()` reads a `\n` inside a quoted field back as `\r\n` (measured: `length()` is
/// one larger and `contains(x, chr(13))` is true). Flattening on the way out beats letting it
/// deform unpredictably on the way back in.