use anyhow::{anyhow, Result};
use std::env;
#[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType {
Molt,
Tcl,
}
impl std::str::FromStr for RuntimeType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"molt" => Ok(RuntimeType::Molt),
"tcl" => Ok(RuntimeType::Tcl),
_ => Err(anyhow!(
"Invalid runtime type '{}'. Valid options: molt, tcl",
s
)),
}
}
}
impl RuntimeType {
pub fn as_str(&self) -> &'static str {
match self {
RuntimeType::Molt => "molt",
RuntimeType::Tcl => "tcl",
}
}
pub fn is_available(&self) -> bool {
match self {
RuntimeType::Molt => cfg!(feature = "molt"),
RuntimeType::Tcl => cfg!(feature = "tcl"),
}
}
}
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub runtime_type: Option<RuntimeType>,
pub fallback_enabled: bool,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
runtime_type: None,
fallback_enabled: true,
}
}
}
impl RuntimeConfig {
pub fn available_runtimes() -> Vec<RuntimeType> {
available_runtimes()
}
pub fn from_args_and_env(
cli_runtime: Option<&str>,
env_runtime: Option<&str>, ) -> Result<Self> {
let mut config = RuntimeConfig::default();
if let Some(env_runtime) = env_runtime {
config.runtime_type = Some(env_runtime.parse()?);
}
if let Some(cli_runtime) = cli_runtime {
config.runtime_type = Some(cli_runtime.parse()?);
}
Ok(config)
}
}
pub trait TclRuntime {
fn new() -> Self
where
Self: Sized;
fn eval(&mut self, script: &str) -> Result<String>;
fn set_var(&mut self, name: &str, value: &str) -> Result<()>;
fn get_var(&self, name: &str) -> Result<String>;
fn has_command(&self, command: &str) -> bool;
fn name(&self) -> &'static str;
fn version(&self) -> &'static str;
fn features(&self) -> Vec<String>;
fn is_safe(&self) -> bool;
}
#[cfg(feature = "molt")]
mod molt_runtime;
#[cfg(feature = "molt")]
pub use molt_runtime::MoltRuntime;
#[cfg(feature = "tcl")]
mod tcl_interpreter;
#[cfg(feature = "tcl")]
pub use tcl_interpreter::TclInterpreter;
pub fn is_runtime_available(runtime_type: RuntimeType) -> bool {
runtime_type.is_available()
}
pub fn available_runtimes() -> Vec<RuntimeType> {
let mut runtimes = Vec::new();
if cfg!(feature = "molt") {
runtimes.push(RuntimeType::Molt);
}
if cfg!(feature = "tcl") {
runtimes.push(RuntimeType::Tcl);
}
runtimes
}
pub fn create_runtime_with_config(config: RuntimeConfig) -> Result<Box<dyn TclRuntime>> {
if let Some(requested_type) = config.runtime_type {
match create_specific_runtime(requested_type.clone()) {
Ok(runtime) => {
tracing::info!("Using {} TCL runtime", requested_type.as_str());
return Ok(runtime);
}
Err(e) if config.fallback_enabled => {
tracing::warn!(
"Failed to create requested runtime {:?}: {}. Trying fallback.",
requested_type,
e
);
}
Err(e) => return Err(e),
}
}
#[cfg(feature = "tcl")]
{
tracing::info!("Auto-selecting official C TCL runtime");
return Ok(Box::new(TclInterpreter::new()));
}
#[cfg(all(feature = "molt", not(feature = "tcl")))]
{
tracing::info!("Auto-selecting Molt TCL runtime");
return Ok(Box::new(MoltRuntime::new()));
}
#[cfg(all(not(feature = "molt"), not(feature = "tcl")))]
{
return Err(anyhow!(
"No TCL runtime features enabled. Please build with --features molt or --features tcl"
));
}
}
fn create_specific_runtime(runtime_type: RuntimeType) -> Result<Box<dyn TclRuntime>> {
match runtime_type {
RuntimeType::Molt => {
#[cfg(feature = "molt")]
{
Ok(Box::new(MoltRuntime::new()))
}
#[cfg(not(feature = "molt"))]
{
Err(anyhow!(
"Molt runtime not available. Build with --features molt"
))
}
}
RuntimeType::Tcl => {
#[cfg(feature = "tcl")]
{
Ok(Box::new(TclInterpreter::new()))
}
#[cfg(not(feature = "tcl"))]
{
Err(anyhow!(
"TCL runtime not available. Build with --features tcl"
))
}
}
}
}
pub fn get_available_runtimes() -> Vec<RuntimeType> {
let mut runtimes = Vec::new();
#[cfg(feature = "molt")]
runtimes.push(RuntimeType::Molt);
#[cfg(feature = "tcl")]
runtimes.push(RuntimeType::Tcl);
runtimes
}
pub fn create_runtime_from_env(cli_runtime: Option<&str>) -> Result<Box<dyn TclRuntime>> {
let mut config = RuntimeConfig::default();
if let Ok(env_runtime) = env::var("TCL_MCP_RUNTIME") {
config.runtime_type = Some(env_runtime.parse()?);
}
if let Some(cli_runtime) = cli_runtime {
config.runtime_type = Some(cli_runtime.parse()?);
}
create_runtime_with_config(config)
}
pub fn create_runtime() -> Box<dyn TclRuntime> {
create_runtime_with_config(RuntimeConfig::default()).expect("Failed to create default runtime")
}