nichlink_run_method/authoring/operations/operations.rs
1//! File-backed add, edit, and delete operations.
2//! 文件化的新增、编辑和删除操作。
3
4use super::*;
5#[path = "create.rs"]
6mod create;
7use create::create_module;
8#[path = "delete.rs"]
9mod delete;
10pub use delete::delete_module;
11#[path = "trash.rs"]
12mod trash;
13use trash::stash_face_source;
14#[path = "face_values.rs"]
15mod face_values;
16use face_values::ModuleFaceValues;
17#[path = "face_write.rs"]
18mod face_write;
19use face_write::{FaceWrite, apply_module_face_values};
20#[path = "migration.rs"]
21mod migration;
22use migration::{migrate_kind_subtree, migrate_module_subtree};
23#[path = "paths.rs"]
24mod paths;
25use paths::generated_paths;
26
27/// The outcome of one file-backed authoring operation.
28/// 一次文件化创作操作的结果。
29pub struct AuthoringChange {
30 /// A human-readable summary of what changed.
31 /// 描述改动内容的人类可读摘要。
32 pub message: String,
33 /// The source file the change was written to.
34 /// 改动写入的源码文件。
35 pub source: PathBuf,
36}
37
38/// The complete field set for creating one module registration face.
39/// 创建一个模块注册面所需的完整字段集合。
40#[derive(Clone, Copy, Debug)]
41pub struct NewModuleFace<'a> {
42 /// New module directory and file name, and the face's identity path segment.
43 /// 新模块的目录与文件名,同时是该注册面身份路径的一段。
44 pub module: &'a str,
45 /// Face kind name, for example `Button`.
46 /// 注册面种类名,例如 `Button`。
47 pub kind: &'a str,
48 /// Preset type path that constructs this face.
49 /// 构造本注册面的 preset 类型路径。
50 pub preset: &'a str,
51 /// Parts type path that supplies this face's construction parts.
52 /// 提供本注册面构造 parts 的 parts 类型路径。
53 pub parts: &'a str,
54 /// Localized display name, Chinese half.
55 /// 本地化显示名称的中文部分。
56 pub name_zh: &'a str,
57 /// Localized display name, English half.
58 /// 本地化显示名称的英文部分。
59 pub name_en: &'a str,
60 /// Localized one-line description, Chinese half.
61 /// 本地化单行描述的中文部分。
62 pub summary_zh: &'a str,
63 /// Localized one-line description, English half.
64 /// 本地化单行描述的英文部分。
65 pub summary_en: &'a str,
66 /// Export names this face declares, as manifest text.
67 /// 本注册面声明的导出名称,以清单文本给出。
68 pub exports: &'a str,
69 /// Optional author-owned identity that survives source moves; empty means none.
70 /// 可选的作者逻辑身份,可跨源码移动保持不变;为空表示没有。
71 pub stable_name: &'a str,
72 /// Node identity of the face this one registers under.
73 /// 本注册面所挂载到的父节点身份。
74 pub parent: NodeId,
75 /// Whether this face owns a child Registry.
76 /// 本注册面是否拥有一个子注册机。
77 pub needs_registry: bool,
78 /// External registry this face is provisioned from; empty means none.
79 /// 本注册面从其获取内容的外部注册机;为空表示没有。
80 pub getting_from_other_registry: &'a str,
81 /// Rule for faces entering the Registry this face owns.
82 /// 进入本注册面所拥有 Registry 的注册规范。
83 pub registration_rule: &'a str,
84 /// External dependency gate for this face's registry.
85 /// 本注册面所属注册机对外部依赖的门禁。
86 pub admission: &'a str,
87 /// Interface names declared by the handle type.
88 /// handle 类型声明实现的接口名称。
89 pub handle_traits: &'a str,
90 /// Contract types the handle type must implement.
91 /// handle 类型必须实现的合同类型。
92 pub handle_contracts: &'a str,
93 /// Interface names declared by the parts type.
94 /// parts 类型声明实现的接口名称。
95 pub part_traits: &'a str,
96 /// Contract types the parts type must implement.
97 /// parts 类型必须实现的合同类型。
98 pub part_contracts: &'a str,
99 /// Capability requirements this face declares.
100 /// 本注册面声明的能力需求。
101 pub requires: &'a str,
102 /// Capability names this face makes available to other faces.
103 /// 本注册面向其他注册面提供的能力名称。
104 pub provides: &'a str,
105 /// Runtime value checks the host applies; empty accepts any value.
106 /// 宿主执行的运行期取值校验;为空时接受任何取值。
107 pub runtime_checks: &'a str,
108 /// Explicit flow contract for grafting; empty selects the default.
109 /// 供嫁接使用的显式数据流合同;为空时选用默认值。
110 pub flow: &'a str,
111 /// Type that supplies the compile-time flow contract, when explicit.
112 /// 显式提供编译期数据流合同的类型路径(如果显式给出)。
113 pub flow_provider: &'a str,
114}
115
116/// The complete field set for editing one module registration face in place.
117/// 原地编辑一个模块注册面所需的完整字段集合。
118#[derive(Clone, Copy, Debug)]
119pub struct ModuleFacePatch<'a> {
120 /// New module directory/file name. Changing it migrates the whole subtree.
121 /// 新模块目录和文件名;修改它会迁移整棵子树。
122 pub module: &'a str,
123 /// Face kind name, for example `Button`.
124 /// 注册面种类名,例如 `Button`。
125 pub kind: &'a str,
126 /// Preset type path that constructs this face.
127 /// 构造本注册面的 preset 类型路径。
128 pub preset: &'a str,
129 /// Parts type path that supplies this face's construction parts.
130 /// 提供本注册面构造 parts 的 parts 类型路径。
131 pub parts: &'a str,
132 /// Localized display name, Chinese half.
133 /// 本地化显示名称的中文部分。
134 pub name_zh: &'a str,
135 /// Localized display name, English half.
136 /// 本地化显示名称的英文部分。
137 pub name_en: &'a str,
138 /// Localized one-line description, Chinese half.
139 /// 本地化单行描述的中文部分。
140 pub summary_zh: &'a str,
141 /// Localized one-line description, English half.
142 /// 本地化单行描述的英文部分。
143 pub summary_en: &'a str,
144 /// Export names this face declares, as manifest text.
145 /// 本注册面声明的导出名称,以清单文本给出。
146 pub exports: &'a str,
147 /// Optional author-owned identity that survives source moves; empty means none.
148 /// 可选的作者逻辑身份,可跨源码移动保持不变;为空表示没有。
149 pub stable_name: &'a str,
150 /// Whether this face owns a child Registry.
151 /// 本注册面是否拥有一个子注册机。
152 pub needs_registry: bool,
153 /// External registry this face is provisioned from; empty means none.
154 /// 本注册面从其获取内容的外部注册机;为空表示没有。
155 pub getting_from_other_registry: &'a str,
156 /// Rule for faces entering the Registry this face owns.
157 /// 进入本注册面所拥有 Registry 的注册规范。
158 pub registration_rule: &'a str,
159 /// External dependency gate for this face's registry.
160 /// 本注册面所属注册机对外部依赖的门禁。
161 pub admission: &'a str,
162 /// Interface names declared by the handle type.
163 /// handle 类型声明实现的接口名称。
164 pub handle_traits: &'a str,
165 /// Contract types the handle type must implement.
166 /// handle 类型必须实现的合同类型。
167 pub handle_contracts: &'a str,
168 /// Interface names declared by the parts type.
169 /// parts 类型声明实现的接口名称。
170 pub part_traits: &'a str,
171 /// Contract types the parts type must implement.
172 /// parts 类型必须实现的合同类型。
173 pub part_contracts: &'a str,
174 /// Capability requirements this face declares.
175 /// 本注册面声明的能力需求。
176 pub requires: &'a str,
177 /// Capability names this face makes available to other faces.
178 /// 本注册面向其他注册面提供的能力名称。
179 pub provides: &'a str,
180 /// Runtime value checks the host applies; empty accepts any value.
181 /// 宿主执行的运行期取值校验;为空时接受任何取值。
182 pub runtime_checks: &'a str,
183 /// Explicit flow contract for grafting; empty selects the default.
184 /// 供嫁接使用的显式数据流合同;为空时选用默认值。
185 pub flow: &'a str,
186 /// Type that supplies the compile-time flow contract, when explicit.
187 /// 显式提供编译期数据流合同的类型路径(如果显式给出)。
188 pub flow_provider: &'a str,
189}
190
191/// Create the standard `<parent>/object/<name>/<name>.rs` registration face.
192/// 创建标准的 `<parent>/object/<name>/<name>.rs` 注册面。
193pub fn add_module(registry: &Registry, spec: &str) -> Result<AuthoringChange, String> {
194 add_module_with_registration(registry, spec).map(|(change, _)| change)
195}
196
197/// Create a module and return the same registration face for immediate commit.
198/// 创建模块并返回同一注册面,以便立即提交到当前 Registry。
199pub fn add_module_with_registration(
200 registry: &Registry,
201 spec: &str,
202) -> Result<(AuthoringChange, RegistrationSnapshot), String> {
203 let mut fields = spec.split_whitespace();
204 let name = fields
205 .next()
206 .ok_or_else(|| "usage: add <module-name> [parent-node]".to_owned())?;
207 validate_name(name)?;
208 let parent = fields
209 .next()
210 .map(str::parse::<NodeId>)
211 .transpose()
212 .map_err(|_| "parent must be a 32-digit node identity".to_owned())?
213 .unwrap_or(ROOT_NODE_ID);
214 if fields.next().is_some() {
215 return Err("usage: add <module-name> [parent-node]".to_owned());
216 }
217
218 create_module(registry, name, parent, None)
219}
220
221/// Validate and create a fully configured face in one filesystem transaction.
222/// 一次校验并创建完整注册面,避免逐字段编辑留下半成品。
223pub fn add_module_from_face(
224 registry: &Registry,
225 spec: &NewModuleFace<'_>,
226) -> Result<(AuthoringChange, RegistrationSnapshot), String> {
227 let values = ModuleFaceValues::from_new(spec);
228 validate_name(values.module)?;
229 create_module(registry, values.module, spec.parent, Some(&values))
230}
231
232/// Load authored faces into an owned snapshot that can be dropped after a reload.
233/// 将创作注册面加载为可随热刷新释放的拥有所有权快照。
234pub fn generated_snapshots() -> Result<Vec<RegistrationSnapshot>, String> {
235 generated_snapshots_from(&source_root())
236}
237
238/// Load generated registration faces from an explicit project root.
239pub fn generated_snapshots_from(root: &Path) -> Result<Vec<RegistrationSnapshot>, String> {
240 let mut sources = Vec::new();
241 collect_face_sources(root, &mut sources)?;
242 sources.sort();
243 sources
244 .into_iter()
245 .map(|source| {
246 FaceManifest::parse_source(&source)
247 .and_then(|face| face.to_snapshot())
248 .map_err(|error| format!("{}: {error}", source.display()))
249 })
250 .collect()
251}
252
253/// The filesystem facts the kernel's source walk asks this surface for.
254/// 内核源码遍历向本执行面索取的文件系统事实。
255struct StdSourceTree;
256
257impl nichlink::source::SourceTree for StdSourceTree {
258 fn is_directory(&self, path: &Path) -> bool {
259 path.is_dir()
260 }
261
262 fn entries(&self, path: &Path) -> Result<Vec<PathBuf>, String> {
263 fs::read_dir(path)
264 .map_err(|error| format!("cannot scan {}: {error}", path.display()))?
265 .map(|entry| {
266 entry
267 .map(|entry| entry.path())
268 .map_err(|error| format!("cannot scan {}: {error}", path.display()))
269 })
270 .collect()
271 }
272
273 fn read_text(&self, path: &Path) -> Result<String, String> {
274 fs::read_to_string(path).map_err(|error| format!("cannot read {}: {error}", path.display()))
275 }
276}
277
278fn collect_face_sources(directory: &Path, sources: &mut Vec<PathBuf>) -> Result<(), String> {
279 nichlink::source::collect_rust_sources(
280 &StdSourceTree,
281 directory,
282 nichlink::source::SourceWalk {
283 skip_target: false,
284 skip_registry_core: true,
285 skip_compile_error_demo: true,
286 },
287 |_, source| match source {
288 None => nichlink::source::Keep::NeedSource,
289 Some(text) if crate::syntax::is_face_source(text, GENERATED_MARKER) => {
290 nichlink::source::Keep::Yes
291 }
292 Some(_) => nichlink::source::Keep::No,
293 },
294 sources,
295 )
296}
297
298/// Edit all Studio-owned face fields in one filesystem transaction.
299/// 在一次文件事务中编辑 Studio 管理的全部注册面字段。
300///
301/// This is the only authored-face edit entry point. The string-parsing
302/// `edit_module` was deleted in B3a: it edited the manifest but never wrote or
303/// removed the registry-rule source, so `edit <id> needs_registry true` left a
304/// face referencing a `super::registry_rule` module it did not own. The doctest
305/// below pins the deletion at compile time.
306/// 这是唯一的注册面编辑入口。字符串解析式的 `edit_module` 已在 B3a 删除:它只改清单,
307/// 从不写入或删除注册规则源,于是 `edit <id> needs_registry true` 会留下一个引用着
308/// 自己并不拥有的 `super::registry_rule` 模块的注册面。下面的 doctest 在编译期钉住
309/// 这次删除。
310///
311/// ```compile_fail,E0433
312/// let _ = nichlink_run_method::edit_module;
313/// ```
314pub fn edit_module_face(
315 registry: &Registry,
316 id: NodeId,
317 patch: &ModuleFacePatch<'_>,
318) -> Result<AuthoringChange, String> {
319 let (_, source) = generated_paths(registry, id)?;
320 let mut face = FaceManifest::parse_source(&source)?;
321 let values = ModuleFaceValues::from_patch(patch);
322 let old_module = face.values.get("module").cloned().unwrap_or_default();
323 let requested_module = values.module.trim();
324 validate_name(requested_module)?;
325 let module_changed = requested_module != old_module;
326 let original_kind = face.values.get("kind").cloned().unwrap_or_default();
327 let kind_changed = normalize_kind_name(values.kind.trim()) != original_kind;
328 apply_module_face_values(&mut face, &values, FaceWrite::Edit)?;
329 if !values.needs_registry
330 && registry
331 .registry(id)
332 .is_some_and(|owned_registry| !owned_registry.is_empty())
333 {
334 return Err("cannot disable a Registry while it still owns child entries".to_owned());
335 }
336 if module_changed {
337 return migrate_module_subtree(registry, id, source, face, requested_module);
338 }
339 if kind_changed {
340 return migrate_kind_subtree(registry, id, source, face);
341 }
342 // Parse the complete edited face before touching either file.
343 // 在修改任一文件前先解析完整注册面,保证错误编辑不会落盘。
344 let authored = face.to_snapshot()?;
345 let existing = registry
346 .find(id)
347 .ok_or_else(|| format!("node `{id}` is not registered"))?;
348 let merged = existing.clone().merge_authored(authored);
349 registry
350 .validate_snapshot_replacement(id, merged)
351 .map_err(|error| format!("registration rejected:\n{error}"))?;
352 let old_source = fs::read_to_string(&source)
353 .map_err(|error| format!("cannot read {}: {error}", source.display()))?;
354 let rule = face.rule_source_path()?;
355 let old_rule = fs::read_to_string(&rule).ok();
356 let rendered = face.render_source()?;
357 // The rewrite rebuilds the file from the fields this surface models, so
358 // anything hand-added to it is not in `rendered`. Keep the previous text
359 // recoverable before the first byte changes: deleting a face already goes
360 // through the trash, and a rewrite deserves the same way back. Nothing is
361 // stashed when the rewrite is a no-op, so an unedited save leaves no litter.
362 // 重写会用本执行面建模的字段重建文件,因此手工加进文件的内容不在 `rendered` 里。在
363 // 第一个字节改变之前把先前的文本留成可恢复的:删除注册面本就经 trash 走,重写也应当
364 // 有同样的回退方式。重写没有实质变化时不留备份,因此未改动的保存不会留下垃圾。
365 let backup = if rendered == old_source {
366 None
367 } else {
368 Some(stash_face_source(&source, id, &old_source)?)
369 };
370 if let Err(error) = atomic_write(&source, &rendered) {
371 let _ = atomic_write(&source, &old_source);
372 return Err(error);
373 }
374 if face.owns_rule_source() {
375 let rule_source = face
376 .render_rule_source()?
377 .ok_or_else(|| "registry rule source was unexpectedly omitted".to_owned())?;
378 if let Err(error) = (|| {
379 fs::create_dir_all(rule.parent().expect("rule source has a parent"))
380 .map_err(|error| format!("cannot create registry rule directory: {error}"))?;
381 atomic_write(&rule, &rule_source)
382 })() {
383 let _ = atomic_write(&source, &old_source);
384 if let Some(old_rule) = old_rule {
385 let _ = atomic_write(&rule, &old_rule);
386 } else {
387 let _ = fs::remove_file(&rule);
388 }
389 return Err(error);
390 }
391 } else if old_rule.is_some() {
392 // Disabling a Registry also removes its generated rule source. Leaving
393 // that file behind would make the next folder scan rediscover a stale
394 // support subtree that the face no longer owns.
395 // 关闭 Registry 时同步删除生成的规则源文件。否则下一次扫描会重新发现
396 // 一个注册面已经不再拥有的陈旧 support 子树。
397 if let Err(error) = fs::remove_file(&rule) {
398 let _ = atomic_write(&source, &old_source);
399 if let Some(old_rule) = old_rule {
400 let _ = atomic_write(&rule, &old_rule);
401 }
402 return Err(format!(
403 "cannot remove registry rule {}: {error}",
404 rule.display()
405 ));
406 }
407 }
408 Ok(AuthoringChange {
409 message: match &backup {
410 Some(backup) => format!(
411 "updated registration face {} (previous text kept at {})",
412 source.display(),
413 backup.display()
414 ),
415 None => format!("updated registration face {}", source.display()),
416 },
417 source,
418 })
419}