use std::env::current_exe;
use std::ffi::{OsStr, OsString};
use std::io::{self, IsTerminal};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use clap::Parser;
use log::*;
use crate::error::ExitCode;
use crate::{Interp, JimObject, JimResult};
use crate::{JimError, sys};
pub enum TclInput<'cli> {
Stdin,
Interactive,
Script(&'cli str),
File(&'cli OsStr),
}
#[derive(Parser, Debug)]
#[command(disable_version_flag = true)]
pub struct Jimsh {
#[arg(long = "version")]
pub print_version: bool,
#[arg(short = 'e')]
pub expression: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub script_and_args: Vec<OsString>,
}
impl Jimsh {
pub fn script_input(&self) -> TclInput<'_> {
if let Some(e) = &self.expression {
TclInput::Script(e)
} else if self.script_and_args.is_empty() {
if io::stdin().is_terminal() {
TclInput::Interactive
} else {
TclInput::Stdin
}
} else if self.script_and_args[0].as_bytes() == b"-" {
TclInput::Stdin
} else {
TclInput::File(&self.script_and_args[0])
}
}
pub fn script_file(&self) -> Option<&OsStr> {
if let TclInput::File(path) = self.script_input() {
Some(path)
} else {
None
}
}
pub fn script_args(&self) -> &[OsString] {
if !self.script_and_args.is_empty() && self.expression.is_none() {
&self.script_and_args[1..]
} else {
&self.script_and_args
}
}
pub fn setup_env(&self, interp: &Interp, argv0: Option<OsString>) -> JimResult<()> {
debug!("initializing interpreter");
interp.eval_source("initjimsh.tcl", 1, sys::tclexts::initjimsh)?;
if let TclInput::File(path) = self.script_input() {
interp.set_variable("argv0", path.as_bytes())?;
}
let argv = JimObject::empty(interp);
for arg in self.script_args() {
argv.list_append(arg.as_bytes());
}
interp.set_variable("argv", argv)?;
interp.set_variable("jim::lineedit", 0)?;
let exe = current_exe()?;
debug!("setting executable from {}", exe.display());
interp.set_variable("jim::exe", exe.as_os_str().as_bytes())?;
if let Some(arg) = argv0 {
debug!("initializing argv0 to {:?}", arg);
interp.set_variable("jim::argv0", arg.as_bytes())?;
} else {
interp.set_variable("jim::argv0", exe.as_os_str().as_bytes())?;
}
Ok(())
}
pub fn run(&self, interp: &Interp) -> JimResult<i32> {
let result = match self.script_input() {
TclInput::Interactive => {
info!("running interactive prompt");
interp.interactive_prompt()
}
TclInput::Stdin => {
info!("evaluating standard input");
interp
.eval("eval [info source [stdin read] stdin 1]")
.map(|_| ())
}
TclInput::Script(c) => {
info!("evaluating command");
interp.eval(c).map(|_| ())
}
TclInput::File(f) => {
let path = PathBuf::from(f);
info!("evaluating file {}", path.display());
interp.eval_file(&path)
}
};
if let Err(JimError::Error(msg)) = &result {
eprintln!("{}", msg);
let stack = interp.stack_trace();
let obj = interp.new_string("stackdump");
obj.list_append(stack);
let result = interp.eval_object(&obj)?;
eprintln!("{}", result);
Ok(1)
} else if let Err(JimError::OtherCode(ExitCode::Break)) = &result {
Ok(0)
} else {
interp.resolve_exit(result)
}
}
}