Files
hakorune/src/config/nyash_toml_v2.rs
Moe Charm 15e0a1ab34 feat: nyash.toml自動解決とWindows/Python対応
- Windows向け自動ライブラリパス解決(lib接頭辞除去)
- Pythonプラグイン実装改善(evalR/importRメソッド追加)
- nyash.tomlに新[plugins]セクション対応開始
- プラグイン検索パスの柔軟な解決
- AOT設定Box改善とエラーハンドリング強化
- Phase 10.5bドキュメント追加(ネイティブビルド統合計画)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-30 00:08:19 +09:00

253 lines
8.4 KiB
Rust

//! nyash.toml v2 configuration parser
//!
//! Ultimate simple design: nyash.toml-centric architecture + minimal FFI
//! No Host VTable, single entry point (nyash_plugin_invoke)
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Root configuration structure
#[derive(Debug, Deserialize, Serialize)]
pub struct NyashConfigV2 {
/// Library definitions (multi-box capable)
#[serde(default)]
pub libraries: HashMap<String, LibraryDefinition>,
/// Plugin search paths
#[serde(default)]
pub plugin_paths: PluginPaths,
/// New: Plugins registry (name -> plugin root directory)
#[serde(default)]
pub plugins: HashMap<String, String>,
/// Optional central type_id mapping (box name -> type_id)
#[serde(default)]
pub box_types: HashMap<String, u32>,
}
/// Library definition (simplified)
#[derive(Debug, Deserialize, Serialize)]
pub struct LibraryDefinition {
/// Box types provided by this library
pub boxes: Vec<String>,
/// Path to the shared library
pub path: String,
}
/// Plugin search paths
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct PluginPaths {
#[serde(default)]
pub search_paths: Vec<String>,
}
/// Box type configuration (nested under library)
#[derive(Debug, Deserialize, Serialize)]
pub struct BoxTypeConfig {
/// Box type ID
pub type_id: u32,
/// ABI version (default: 1)
#[serde(default = "default_abi_version")]
pub abi_version: u32,
/// Method definitions
pub methods: HashMap<String, MethodDefinition>,
/// Singleton service flag (keep one shared instance alive in loader)
#[serde(default)]
pub singleton: bool,
}
/// Method definition (simplified - no argument info needed)
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct MethodDefinition {
/// Method ID for FFI
pub method_id: u32,
/// Optional argument declarations (v2.1+)
#[serde(default)]
pub args: Option<Vec<ArgDecl>>,
/// Optional: wrap return and errors into ResultBox when true
#[serde(default)]
pub returns_result: bool,
}
/// Method argument declaration (v2.1+)
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum ArgDecl {
/// Backward-compatible string form (treated as kind="string")
Name(String),
/// Typed declaration
Typed {
kind: String,
#[serde(default)]
category: Option<String>,
},
}
impl ArgDecl {
/// Returns a canonical kind string ("string", "box", etc.)
pub fn kind_str(&self) -> &str {
match self {
ArgDecl::Name(_) => "string",
ArgDecl::Typed { kind, .. } => kind.as_str(),
}
}
}
fn default_abi_version() -> u32 {
1
}
impl NyashConfigV2 {
/// Parse nyash.toml file
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
Self::from_str(&content)
}
/// Parse nyash.toml content from string (test/helper)
pub fn from_str(content: &str) -> Result<Self, Box<dyn std::error::Error>> {
// Parse as raw TOML first to handle nested box configs
let mut config: toml::Value = toml::from_str(content)?;
// Extract library definitions
let libraries = Self::parse_libraries(&mut config)?;
// Extract plugin paths
let plugin_paths = if let Some(paths) = config.get("plugin_paths") {
paths.clone().try_into::<PluginPaths>()?
} else {
PluginPaths::default()
};
// Extract plugins map
let plugins = if let Some(tbl) = config.get("plugins").and_then(|v| v.as_table()) {
let mut m = HashMap::new();
for (k, v) in tbl.iter() {
if let Some(s) = v.as_str() { m.insert(k.clone(), s.to_string()); }
}
m
} else { HashMap::new() };
// Extract optional box_types map
let box_types = if let Some(tbl) = config.get("box_types").and_then(|v| v.as_table()) {
let mut m = HashMap::new();
for (k, v) in tbl.iter() { if let Some(id) = v.as_integer() { m.insert(k.clone(), id as u32); } }
m
} else { HashMap::new() };
Ok(NyashConfigV2 { libraries, plugin_paths, plugins, box_types })
}
/// Parse library definitions with nested box configs
fn parse_libraries(config: &mut toml::Value) -> Result<HashMap<String, LibraryDefinition>, Box<dyn std::error::Error>> {
let mut libraries = HashMap::new();
if let Some(libs_section) = config.get("libraries").and_then(|v| v.as_table()) {
for (lib_name, lib_value) in libs_section {
if let Some(lib_table) = lib_value.as_table() {
let boxes = lib_table.get("boxes")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect()
})
.unwrap_or_default();
let path = lib_table.get("path")
.and_then(|v| v.as_str())
.unwrap_or(lib_name)
.to_string();
libraries.insert(lib_name.clone(), LibraryDefinition {
boxes,
path,
});
}
}
}
Ok(libraries)
}
/// Get box configuration from nested structure
/// e.g., [libraries."libnyash_filebox_plugin.so".FileBox]
pub fn get_box_config(&self, lib_name: &str, box_name: &str, config_value: &toml::Value) -> Option<BoxTypeConfig> {
config_value
.get("libraries")
.and_then(|v| v.get(lib_name))
.and_then(|v| v.get(box_name))
.and_then(|v| v.clone().try_into::<BoxTypeConfig>().ok())
}
/// Find library that provides a specific box type
pub fn find_library_for_box(&self, box_type: &str) -> Option<(&str, &LibraryDefinition)> {
self.libraries.iter()
.find(|(_, lib)| lib.boxes.contains(&box_type.to_string()))
.map(|(name, lib)| (name.as_str(), lib))
}
/// Resolve plugin path from search paths
pub fn resolve_plugin_path(&self, plugin_name: &str) -> Option<String> {
// Try exact path first
if std::path::Path::new(plugin_name).exists() {
return Some(plugin_name.to_string());
}
// Build effective search paths: config + ENV:NYASH_PLUGIN_PATHS (sep=';' or ':')
let mut paths: Vec<String> = Vec::new();
paths.extend(self.plugin_paths.search_paths.iter().cloned());
if let Ok(envp) = std::env::var("NYASH_PLUGIN_PATHS") {
let sep = if cfg!(target_os = "windows") { ';' } else { ':' };
for p in envp.split(sep).filter(|s| !s.is_empty()) { paths.push(p.to_string()); }
}
// Search in effective paths
for search_path in &paths {
let path = std::path::Path::new(search_path).join(plugin_name);
if path.exists() {
return Some(path.to_string_lossy().to_string());
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_v2_config() {
let toml_str = r#"
[libraries]
[libraries."libnyash_filebox_plugin.so"]
boxes = ["FileBox"]
path = "./target/release/libnyash_filebox_plugin.so"
[libraries."libnyash_filebox_plugin.so".FileBox]
type_id = 6
abi_version = 1
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
birth = { method_id = 0 }
open = { method_id = 1 }
close = { method_id = 4 }
"#;
let nyash_config = NyashConfigV2::from_str(toml_str).unwrap();
assert!(nyash_config.libraries.contains_key("libnyash_filebox_plugin.so"));
let (lib_name, lib_def) = nyash_config.find_library_for_box("FileBox").unwrap();
assert_eq!(lib_name, "libnyash_filebox_plugin.so");
assert_eq!(lib_def.boxes, vec!["FileBox".to_string()]);
// Nested box config should be retrievable
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let box_conf = nyash_config.get_box_config("libnyash_filebox_plugin.so", "FileBox", &raw).unwrap();
assert_eq!(box_conf.type_id, 6);
}
}