Skip to main content

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}