Skip to main content

str_format/
bundle.rs

1//! bundle 遍历与索引。
2//!
3//! 「分支」的唯一判据是**该目录是否含 `._meta`**(规范 3.4 / 4.6);
4//! 层级关系由「目录结构 + 父级 `entries`」唯一决定(规范 5.1)。
5
6use std::collections::{HashMap, VecDeque};
7use std::path::{Path, PathBuf};
8
9use crate::error::{Error, Issue, Result, code};
10use crate::meta::{Meta, MetaLoad, load};
11use crate::util::{self, META_FILE};
12
13/// 下钻硬上限(防止病态目录结构导致遍历失控;策略级 `max_depth` 由校验器判定)。
14pub const HARD_MAX_DEPTH: usize = 128;
15
16/// 一处被访问到的分支目录。
17pub struct Visit {
18    /// 目录绝对/原始路径。
19    pub dir: PathBuf,
20    /// bundle 内相对路径(ROOT 为 `.`)。
21    pub rel: String,
22    /// 深度(ROOT = 0)。
23    pub depth: usize,
24    /// 解析结果;`None` 表示 `._meta` 解析失败。
25    pub meta: Option<Box<Meta>>,
26    /// 父分支在 `visits` 中的下标。
27    pub parent: Option<usize>,
28    /// 原始 `._meta` 文本是否读取成功。
29    pub readable: bool,
30}
31
32/// 一次完整扫描的结果。
33pub struct Scan {
34    /// bundle 根目录。
35    pub root: PathBuf,
36    /// 全部分支目录(下标 0 为 ROOT,若存在)。
37    pub visits: Vec<Visit>,
38    /// 扫描阶段产生的问题(解析层)。
39    pub issues: Vec<Issue>,
40    /// `id` → `visits` 下标列表(用于重复检测与引用解析)。
41    pub by_id: HashMap<String, Vec<usize>>,
42    /// ROOT 是否成功解析。
43    pub root_index: Option<usize>,
44    /// 实测最大深度。
45    pub max_depth: usize,
46}
47
48impl Scan {
49    /// 按 `id` 解析分支下标(首次命中)。
50    pub fn resolve(&self, id: &str) -> Option<usize> {
51        self.by_id.get(id).and_then(|v| v.first().copied())
52    }
53
54    /// 分支的完整链条(从 ROOT 到自身)。
55    pub fn ancestors(&self, mut idx: usize) -> Vec<usize> {
56        let mut out = vec![idx];
57        while let Some(p) = self.visits[idx].parent {
58            out.push(p);
59            idx = p;
60        }
61        out.reverse();
62        out
63    }
64}
65
66/// 一个 `.str` bundle。
67#[derive(Debug, Clone)]
68pub struct Bundle {
69    /// 根目录。
70    pub root: PathBuf,
71}
72
73impl Bundle {
74    /// 构造并确认路径存在且为目录。
75    pub fn new(root: impl Into<PathBuf>) -> Result<Self> {
76        let root = root.into();
77        if !root.exists() {
78            return Err(Error::NotFound(root.display().to_string()));
79        }
80        if !root.is_dir() {
81            return Err(Error::BadArg(format!(
82                "{} 不是目录(`.str` 是目录 bundle)",
83                root.display()
84            )));
85        }
86        // 归一化为绝对路径:否则以 `.` / `..` 形式传入时取不到目录名,
87        // 会误报 `W_BUNDLE_SUFFIX`(`str validate .` 的实际场景)。
88        let root = std::fs::canonicalize(&root).map_err(|e| Error::io(&root, e))?;
89        Ok(Self { root })
90    }
91
92    /// bundle 目录名(不含路径)。
93    pub fn name(&self) -> String {
94        self.root
95            .file_name()
96            .map(|s| s.to_string_lossy().to_string())
97            .unwrap_or_else(|| self.root.display().to_string())
98    }
99
100    /// 某目录下 `._meta` 的路径。
101    pub fn meta_path(&self, dir: &Path) -> PathBuf {
102        dir.join(META_FILE)
103    }
104
105    /// 某目录是否含 `._meta`(即是否为分支)。
106    pub fn has_meta(&self, dir: &Path) -> bool {
107        self.meta_path(dir).is_file()
108    }
109
110    /// 相对路径显示。
111    pub fn rel(&self, path: &Path) -> String {
112        util::rel_display(&self.root, path)
113    }
114
115    /// 读取并解析某目录的 `._meta`。
116    pub fn read_meta(&self, dir: &Path) -> Result<MetaLoad> {
117        let p = self.meta_path(dir);
118        let rel = self.rel(&p);
119        load(&p, &rel)
120    }
121
122    /// 列出目录内全部条目 `(名字, 是否目录)`,按名字排序。
123    pub fn list_names(&self, dir: &Path) -> Result<Vec<(String, bool)>> {
124        let mut out = Vec::new();
125        let rd = std::fs::read_dir(dir).map_err(|e| Error::io(dir, e))?;
126        for ent in rd {
127            let ent = ent.map_err(|e| Error::io(dir, e))?;
128            let name = ent.file_name().to_string_lossy().to_string();
129            let is_dir = ent
130                .file_type()
131                .map(|t| t.is_dir())
132                .unwrap_or(false);
133            out.push((name, is_dir));
134        }
135        out.sort();
136        Ok(out)
137    }
138
139    /// 仅列出子目录。
140    pub fn child_dirs(&self, dir: &Path) -> Result<Vec<PathBuf>> {
141        Ok(self
142            .list_names(dir)?
143            .into_iter()
144            .filter(|(_, is_dir)| *is_dir)
145            .map(|(n, _)| dir.join(n))
146            .collect())
147    }
148
149    /// 在**非分支**目录中查找是否藏有 `._meta`(层级异常,规范 4.6)。
150    fn contains_meta_deeper(&self, dir: &Path) -> bool {
151        for entry in walkdir::WalkDir::new(dir)
152            .max_depth(8)
153            .into_iter()
154            .filter_entry(|e| {
155                if e.depth() == 0 {
156                    return true;
157                }
158                let name = e.file_name().to_string_lossy().to_string();
159                // 不进入独立子 bundle(`.str` 目录是硬边界,规范 3.5)
160                !util::is_os_noise(&name) && !util::is_sub_bundle(&name)
161            })
162            .flatten()
163        {
164            if entry.depth() == 0 || !entry.file_type().is_dir() {
165                continue;
166            }
167            if entry.path().join(META_FILE).is_file() {
168                return true;
169            }
170        }
171        false
172    }
173
174    /// 扫描整棵分支树。
175    pub fn scan(&self) -> Result<Scan> {
176        let mut visits: Vec<Visit> = Vec::new();
177        let mut issues: Vec<Issue> = Vec::new();
178        let mut by_id: HashMap<String, Vec<usize>> = HashMap::new();
179
180        let root_meta = self.meta_path(&self.root);
181        if !root_meta.is_file() {
182            issues.push(Issue::error(
183                code::META_MISSING,
184                ".",
185                "bundle 根目录缺少 `._meta`",
186            ));
187            return Ok(Scan {
188                root: self.root.clone(),
189                visits,
190                issues,
191                by_id,
192                root_index: None,
193                max_depth: 0,
194            });
195        }
196
197        let (root_parsed, mut root_issues) = match self.read_meta(&self.root)? {
198            MetaLoad::Ok(m, i) => (Some(m), i),
199            MetaLoad::Failed(i) => (None, i),
200        };
201        issues.append(&mut root_issues);
202        if let Some(m) = &root_parsed {
203            if let Some(id) = &m.id {
204                by_id.entry(id.clone()).or_default().push(0);
205            }
206        }
207        visits.push(Visit {
208            dir: self.root.clone(),
209            rel: ".".to_string(),
210            depth: 0,
211            meta: root_parsed,
212            parent: None,
213            readable: true,
214        });
215
216        let mut queue: VecDeque<usize> = VecDeque::from([0usize]);
217        let mut max_depth = 0usize;
218
219        while let Some(cur) = queue.pop_front() {
220            let dir = visits[cur].dir.clone();
221            let depth = visits[cur].depth;
222            if depth >= HARD_MAX_DEPTH {
223                continue;
224            }
225            for child in self.child_dirs(&dir)? {
226                let name = child
227                    .file_name()
228                    .map(|s| s.to_string_lossy().to_string())
229                    .unwrap_or_default();
230                if util::is_os_noise(&name) {
231                    continue;
232                }
233                if util::is_sub_bundle(&name) {
234                    // 独立子 bundle:硬边界,父 bundle 不进入、不参与分支树
235                    continue;
236                }
237                let rel = self.rel(&child);
238                if !self.has_meta(&child) {
239                    // 普通内容容器;若其中藏有分支则层级无法建立
240                    if !util::is_reserved_name(&name) && self.contains_meta_deeper(&child) {
241                        issues.push(Issue::error(
242                            code::META_MISSING,
243                            rel,
244                            "父目录不是分支(缺少 `._meta`),其内出现 `._meta`,无法建立分支层级",
245                        ));
246                    }
247                    continue;
248                }
249                let d = depth + 1;
250                let (parsed, mut iss) = match self.read_meta(&child)? {
251                    MetaLoad::Ok(m, i) => (Some(m), i),
252                    MetaLoad::Failed(i) => (None, i),
253                };
254                issues.append(&mut iss);
255                let idx = visits.len();
256                if let Some(m) = &parsed {
257                    if let Some(id) = &m.id {
258                        by_id.entry(id.clone()).or_default().push(idx);
259                    }
260                }
261                visits.push(Visit {
262                    dir: child.clone(),
263                    rel,
264                    depth: d,
265                    meta: parsed,
266                    parent: Some(cur),
267                    readable: true,
268                });
269                max_depth = max_depth.max(d);
270                queue.push_back(idx);
271            }
272        }
273
274        Ok(Scan {
275            root: self.root.clone(),
276            visits,
277            issues,
278            by_id,
279            root_index: Some(0),
280            max_depth,
281        })
282    }
283}