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