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 = "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}