Files
hakorune/src/using/resolver.rs

186 lines
7.6 KiB
Rust

use crate::using::errors::UsingError;
use crate::using::policy::UsingPolicy;
use crate::using::spec::{PackageKind, UsingPackage};
use std::collections::HashMap;
/// Populate using context vectors from nyash.toml (if present).
/// Keeps behavior aligned with existing runner pipeline:
/// - Adds [using.paths] entries to `using_paths`
/// - Flattens [modules] into (name, path) pairs appended to `pending_modules`
/// - Reads optional [aliases] table (k -> v)
pub fn populate_from_toml(
using_paths: &mut Vec<String>,
pending_modules: &mut Vec<(String, String)>,
aliases: &mut HashMap<String, String>,
packages: &mut HashMap<String, UsingPackage>,
) -> Result<UsingPolicy, UsingError> {
let mut policy = UsingPolicy::default();
// Prefer CWD nyash.toml; if missing, honor NYASH_ROOT/nyash.toml for tools that run from subdirs
let (text, toml_path) = {
let path = std::path::Path::new("nyash.toml");
if path.exists() {
(
std::fs::read_to_string(path)
.map_err(|e| UsingError::ReadToml(e.to_string()))?,
path.to_path_buf(),
)
} else if let Ok(root) = std::env::var("NYASH_ROOT") {
let alt = std::path::Path::new(&root).join("nyash.toml");
if alt.exists() {
(
std::fs::read_to_string(&alt)
.map_err(|e| UsingError::ReadToml(e.to_string()))?,
alt,
)
} else {
return Ok(policy);
}
} else {
return Ok(policy);
}
};
let doc = toml::from_str::<toml::Value>(&text)
.map_err(|e| UsingError::ParseToml(e.to_string()))?;
let toml_dir = toml_path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::path::PathBuf::from("."));
// [modules] table flatten: supports nested namespaces (a.b.c = "path")
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) {
for (k, v) in tbl.iter() {
let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) };
if let Some(s) = v.as_str() {
out.push((name, s.to_string()));
} else if let Some(t) = v.as_table() {
visit(&name, t, out);
}
}
}
visit("", mods, pending_modules);
if let Some(workspace_tbl) = mods.get("workspace").and_then(|v| v.as_table()) {
load_workspace_modules(&toml_dir, workspace_tbl, pending_modules, aliases)?;
}
}
// [using.paths] array
if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) {
// paths
if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) {
for p in paths_arr {
if let Some(s) = p.as_str() {
let s = s.trim();
if !s.is_empty() {
using_paths.push(s.to_string());
policy.search_paths.push(s.to_string());
}
}
}
}
// aliases
if let Some(alias_tbl) = using_tbl.get("aliases").and_then(|v| v.as_table()) {
for (k, v) in alias_tbl.iter() {
if let Some(target) = v.as_str() {
aliases.insert(k.to_string(), target.to_string());
}
}
}
// named packages: any subtable not paths/aliases is a package
for (k, v) in using_tbl.iter() {
if k == "paths" || k == "aliases" { continue; }
if let Some(tbl) = v.as_table() {
let kind = tbl.get("kind").and_then(|x| x.as_str()).map(PackageKind::from_str).unwrap_or(PackageKind::Package);
// path is required
if let Some(path_s) = tbl.get("path").and_then(|x| x.as_str()) {
let path = path_s.to_string();
let main = tbl.get("main").and_then(|x| x.as_str()).map(|s| s.to_string());
let bid = tbl.get("bid").and_then(|x| x.as_str()).map(|s| s.to_string());
packages.insert(k.to_string(), UsingPackage { kind, path, main, bid });
}
}
}
}
// legacy top-level [aliases] also accepted (migration)
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
for (k, v) in alias_tbl.iter() {
if let Some(target) = v.as_str() {
aliases.insert(k.to_string(), target.to_string());
}
}
}
Ok(policy)
}
fn load_workspace_modules(
nyash_dir: &std::path::Path,
workspace_tbl: &toml::value::Table,
pending_modules: &mut Vec<(String, String)>,
aliases: &mut HashMap<String, String>,
) -> Result<(), UsingError> {
let members = workspace_tbl
.get("members")
.and_then(|v| v.as_array())
.ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "expected members array".into()))?;
for entry in members {
let raw_path = entry
.as_str()
.ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "members must be string paths".into()))?;
let module_path = if std::path::Path::new(raw_path).is_absolute() {
std::path::PathBuf::from(raw_path)
} else {
nyash_dir.join(raw_path)
};
let module_dir = module_path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| nyash_dir.to_path_buf());
let module_text = std::fs::read_to_string(&module_path).map_err(|e| {
UsingError::ReadWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string())
})?;
let module_doc = toml::from_str::<toml::Value>(&module_text).map_err(|e| {
UsingError::ParseWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string())
})?;
let module_name = module_doc
.get("module")
.and_then(|v| v.get("name"))
.and_then(|v| v.as_str())
.ok_or_else(|| {
UsingError::WorkspaceModuleMissingName(module_path.to_string_lossy().to_string())
})?;
if let Some(exports_tbl) = module_doc.get("exports").and_then(|v| v.as_table()) {
for (export_key, export_value) in exports_tbl {
if let Some(rel_path) = export_value.as_str() {
let mut full_name = module_name.to_string();
if !export_key.is_empty() {
full_name.push('.');
full_name.push_str(export_key);
}
if pending_modules.iter().any(|(name, _)| name == &full_name) {
continue;
}
let resolved_path = module_dir.join(rel_path);
let resolved_str = resolved_path
.canonicalize()
.unwrap_or(resolved_path)
.to_string_lossy()
.to_string();
pending_modules.push((full_name, resolved_str));
}
}
}
if let Some(alias_tbl) = module_doc.get("aliases").and_then(|v| v.as_table()) {
for (alias, target) in alias_tbl {
if let Some(target_str) = target.as_str() {
aliases.insert(alias.to_string(), target_str.to_string());
}
}
}
}
Ok(())
}