Skip to main content

asc_hap/
record.rs

1//! From a manifest source path to the `.abc` record that holds its code.
2
3use crate::module::ModuleJson;
4use crate::pkg_context::PkgContextInfo;
5use crate::profile::Route;
6
7/// Source root of a module's own package when `pkgContextInfo.json` is
8/// not at hand; every sample that has the file says the same.
9const DEFAULT_SOURCE_ROOT: &str = "src/main/";
10
11/// Extensions the compiler drops when it names a record after a file.
12const SOURCE_EXTENSIONS: &[&str] = &[".ets", ".ts", ".js", ".mjs", ".cjs"];
13
14pub(crate) fn strip_source_extension(path: &str) -> &str {
15    SOURCE_EXTENSIONS.iter().find_map(|ext| path.strip_suffix(ext)).unwrap_or(path)
16}
17
18/// The record names a manifest `srcEntry` can compile to. Which spelling
19/// a package uses is a build option (normalized OHM URLs); packages built
20/// with it carry a `pkgContextInfo.json`.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct RecordNames {
23    /// `&<packageName>/<source root><path>&<version>`, e.g.
24    /// `&entry/src/main/ets/entryability/EntryAbility&`. `None` when the
25    /// manifest names neither a package nor a module.
26    pub normalized: Option<String>,
27    /// `<bundleName>/<moduleName>/<path>`, e.g.
28    /// `com.x.y/entry/ets/entryability/EntryAbility`. `None` when the
29    /// manifest lacks the bundle or module name.
30    pub bundled: Option<String>,
31    /// Whether the package table listed the module's package, which is
32    /// what makes the normalized spelling the likely one.
33    pub package_listed: bool,
34}
35
36impl RecordNames {
37    /// The spellings, likeliest first.
38    pub fn candidates(&self) -> Vec<&str> {
39        let (first, second) = if self.package_listed { (&self.normalized, &self.bundled) } else { (&self.bundled, &self.normalized) };
40        [first, second].into_iter().flatten().map(String::as_str).collect()
41    }
42}
43
44impl ModuleJson {
45    /// Record names for a `srcEntry` of this module (an ability's, an
46    /// extension's or the module's own): `./ets/entryability/EntryAbility.ets`.
47    ///
48    /// Pass the package's `pkgContextInfo.json` when it has one: it
49    /// supplies the module's source root and version, and its presence
50    /// puts the normalized spelling first.
51    pub fn record_names(&self, src_entry: &str, packages: Option<&PkgContextInfo>) -> RecordNames {
52        let path = src_entry.strip_prefix("./").unwrap_or(src_entry).trim_start_matches('/');
53        let path = strip_source_extension(path);
54        let module = self.module.name.as_deref();
55        let package_name = self.module.package_name.as_deref().or(module);
56        let package = packages.zip(package_name).and_then(|(table, name)| table.get(name));
57        let normalized = package_name.map(|name| match package {
58            // A shared package lists its entry file, or nothing, where a hap lists its source root.
59            Some(p) if p.entry_path.ends_with('/') => format!("&{name}/{}{path}&{}", p.entry_path, p.version),
60            Some(p) => format!("&{name}/{DEFAULT_SOURCE_ROOT}{path}&{}", p.version),
61            None => format!("&{name}/{DEFAULT_SOURCE_ROOT}{path}&"),
62        });
63        let bundled = self.app.bundle_name.as_deref().zip(module).map(|(bundle, module)| format!("{bundle}/{module}/{path}"));
64        RecordNames { normalized, bundled, package_listed: package.is_some() }
65    }
66
67    /// Record names for the page of a route of this module's route table.
68    ///
69    /// The route's `ohmurl` spells the record outright; without one the
70    /// name is put together from `pageSourceFile` and the package that has
71    /// it (`packageName`, else the module's own), with the version
72    /// `packages` lists for that package. A page of the module itself has
73    /// the `<bundle>/<module>/ets/…` spelling besides.
74    pub fn route_record_names(&self, route: &Route, packages: Option<&PkgContextInfo>) -> RecordNames {
75        let module = self.module.name.as_deref();
76        let own_package = self.module.package_name.as_deref().or(module);
77        let path = route.record_path(own_package);
78        let listed = packages.zip(route.package_name.as_deref().or(own_package)).and_then(|(table, name)| table.get(name));
79        let from_source = path.as_ref().map(|path| format!("&{path}&{}", listed.map_or("", |package| package.version.as_str())));
80        let normalized = route.ohmurl_record().map(str::to_owned).or(from_source);
81        let is_own = route.package_name.as_deref().is_none_or(|package| Some(package) == own_package);
82        let source = route.page_source_file.as_deref().map(|source| source.trim_start_matches("./").trim_start_matches('/'));
83        let below_root = source.and_then(|source| source.strip_prefix(DEFAULT_SOURCE_ROOT)).filter(|_| is_own);
84        let bundled = self
85            .app
86            .bundle_name
87            .as_deref()
88            .zip(module)
89            .zip(below_root)
90            .map(|((bundle, module), path)| format!("{bundle}/{module}/{}", strip_source_extension(path)));
91        // A route with an `ohmurl` comes from a build with normalized names.
92        RecordNames { normalized, bundled, package_listed: listed.is_some() || route.ohmurl.is_some() }
93    }
94}