use std::borrow::Cow;
use std::{env, process};
use std::path::Path;
use clap::{ArgAction, Args, Command, FromArgMatches};
use tracing::info;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::EnvFilter;
use crate::name::{Trl, TrlObject};
use crate::testing::TestContext;
use super::ServerBuilder;
#[derive(Debug, Clone, Args)]
#[command(about = None, long_about = None)]
pub struct ServerArgs {
pub inst: String,
#[arg(short, long, action = ArgAction::Count)]
pub verbose: u8,
#[arg(long)]
pub host: Option<String>,
#[arg(long)]
pub port: Option<u16>,
#[arg(long = "dev", value_name = "[CLASS::]NAME", value_parser = parse_dev)]
pub devs: Vec<(Option<String>, String)>,
#[arg(long = "prop", value_name = "DEV->NAME=VALUE", value_parser = parse_prop, requires = "devs")]
pub props: Vec<(String, String, String)>,
#[arg(long, requires = "devs")]
pub db_port: Option<u16>,
}
impl ServerBuilder {
pub fn from_args() -> Self {
Self::from_raw_args(None, None)
}
pub fn from_raw_args(exec: Option<&str>, args: Option<&ServerArgs>) -> Self {
let exec = exec.map(Cow::Borrowed).unwrap_or_else(|| Cow::Owned(exe()));
let args = match args {
Some(args) => Cow::Borrowed(args),
None => {
let matches = ServerArgs::augment_args(Command::new(exec.to_string())).get_matches();
let args = ServerArgs::from_arg_matches(&matches).unwrap_or_else(|err| err.exit());
Cow::Owned(args)
}
};
let level = match args.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
};
let _ = tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(EnvFilter::builder()
.with_default_directive(level.into())
.from_env_lossy())
.try_init();
let mut builder = Self::new(&*exec, &args.inst);
if let Some(host) = &args.host {
builder = builder.host(host);
}
if let Some(port) = args.port {
builder = builder.port(port);
}
if !args.devs.is_empty() {
let ctx = test_context(&exec, &args).unwrap_or_else(|err| {
eprintln!("Error: {err:#}");
process::exit(1)
});
builder = builder.database(ctx.database().clone());
builder.test_context = Some(ctx);
}
builder
}
}
fn exe() -> String {
let arg0 = env::args_os().next().unwrap_or_default();
let name = Path::new(&arg0).file_name().unwrap_or_default().to_string_lossy();
name.strip_suffix(env::consts::EXE_SUFFIX).unwrap_or(&name).to_string()
}
fn test_context(exec: &str, args: &ServerArgs) -> crate::error::Result<TestContext> {
let ctx = TestContext::with_port(args.db_port.unwrap_or(0))?;
let devs = args.devs.iter()
.map(|(class, name)| (name.as_str(), class.as_deref().unwrap_or(exec)))
.collect::<Vec<_>>();
ctx.database().add_server(&format!("{exec}/{}", args.inst), &devs)?;
for (dev, name, value) in &args.props {
ctx.database().set_dev_prop(dev, (name.as_str(), value.as_str()))?;
}
info!(tango_host = %format_args!("{}:{}", ctx.host(), ctx.port()), "Serving the test database");
Ok(ctx)
}
fn parse_dev(arg: &str) -> Result<(Option<String>, String), String> {
match arg.split_once("::") {
Some((class, name)) => Ok((Some(class.to_string()), name.to_string())),
None => Ok((None, arg.to_string())),
}
}
fn parse_prop(arg: &str) -> Result<(String, String, String), String> {
let (trl, value) = arg.split_once('=')
.ok_or("expected DEV->NAME=VALUE")?;
let trl = Trl::parse(trl)
.map_err(|err| format!("{trl} is not a property of a device: {err:?}"))?;
match (trl.object(), trl.property()) {
(TrlObject::Dev(dev), Some(name)) => Ok((dev.to_string(), name.to_string(), value.to_string())),
_ => Err(format!("{trl} is not a property of a device")),
}
}