quant1x 0.4.0

Cross-language standard library for quantitative trading
Documentation
use clap::{Arg, ArgAction, Command};

fn build_version_info() -> String {
    // Minimal build info fallback. In the C++ tree this is generated by build-info.h.
    // We keep a compact message here; projects can extend via a build script if needed.
    let mut s = String::new();
    s.push_str("quant1x - Rust edition\n");
    s.push_str("--------------------------------------------------------------------------------\n");
    s.push_str(&format!("               Version : {}\n", env!("CARGO_PKG_VERSION")));
    s.push_str(&format!("                Author : {}\n", env!("CARGO_PKG_AUTHORS")));
    s.push_str("--------------------------------------------------------------------------------\n");
    s
}

fn main() {
    let program_name = std::env::current_exe()
        .ok()
        .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
        .unwrap_or_else(|| String::from("quant1x"));
    let program_version = build_version_info();

    let service_sub = Command::new("service")
        .about("Manage the service.")
        .arg(Arg::new("action").value_parser(["install","uninstall","start","stop","status","run"]).required(true))
        .arg(Arg::new("pipe").long("pipe").action(ArgAction::SetTrue));

    let update_sub = Command::new("update")
        .about("Update cached data (base / features)")
        .arg(Arg::new("calendar").long("calendar").action(ArgAction::SetTrue).help("Only update calendar cache"))
        .arg(Arg::new("servers").long("servers").action(ArgAction::SetTrue).help("Only detect and update level1 servers cache"))
        .arg(Arg::new("all").long("all").action(ArgAction::SetTrue).help("Update everything (default)"))
        .arg(Arg::new("base").long("base").num_args(1..).value_name("KEY").help("Update base data keys (e.g. xdxr)").value_parser(clap::builder::NonEmptyStringValueParser::new()))
        .arg(Arg::new("features").long("features").num_args(1..).value_name("KEY").help("Update feature datasets (derived data)").value_parser(clap::builder::NonEmptyStringValueParser::new()));

    // Leaking these Strings is fine for a short-lived CLI process and avoids
    // lifetime headaches with clap which often expects 'static str for program
    // metadata. This mirrors C++ dynamic program name/version behavior.
    let program_name_static: &'static str = Box::leak(program_name.into_boxed_str());
    let program_version_static: &'static str = Box::leak(program_version.into_boxed_str());

    let mut app = Command::new(program_name_static)
        .long_about(program_version_static)
        .arg(Arg::new("verbose").long("verbose").action(ArgAction::SetTrue).help("显示日志信息到终端"))
        .arg(Arg::new("debug").long("debug").action(ArgAction::SetTrue).help("打开日志的调试模式"))
        .subcommand_required(false)
    .subcommand(service_sub)
    .subcommand(update_sub);

    // Note: the C++ implementation dynamically registers subcommands from
    // `quant1x::subcommands`. The Rust port may not yet expose that list.
    // We provide a minimal compatibility layer: if the crate provides
    // `quant1x::run_subcommand(name, matches)`, it can be wired here.

    let matches = app.clone().get_matches();

    let verbose = matches.get_flag("verbose");
    let debug = matches.get_flag("debug");

    // runtime::global_init() equivalent - optional if the crate provides one
    if let Err(e) = call_runtime_global_init() {
        log::error!("runtime::global_init failed: {}", e);
    }

    // datasets::init() equivalent
    if let Err(e) = call_datasets_init() {
        log::error!("datasets::init failed: {}", e);
    }

    // initialize logger
    let _ = call_logger_set(verbose, debug);

    // engine::init with optional custom init closure.
    let _ = call_engine_init();

    // service special casing
    if let Some((name, subm)) = matches.subcommand() {
        if name == "service" {
            // call engine::daemon(service_matches) if available
            let action = subm.get_one::<String>("action").map(|s| s.as_str()).unwrap_or("");
            let pipe = subm.get_flag("pipe");
            let code = call_engine_daemon(action, pipe);
            std::process::exit(code);
        }
    }

    // Try to dispatch dynamic subcommands provided by the library.
    // The library may expose `quant1x::subcommands()` or `quant1x::run_subcommand`.
    // We'll attempt a best-effort dynamic dispatch: look for a single subcommand used
    // and call into `quant1x::run_subcommand(name, &ArgMatches)` if that symbol exists.

    // Find which subcommand (other than service) was used
    if let Some((name, subm)) = matches.subcommand() {
        if name != "service" {
            // attempt to call into library
            match lib_bridge::try_run_subcommand(name, subm) {
                Ok(true) => {
                    // ran successfully
                    return;
                }
                Ok(false) => {
                    log::error!("Error: Command '{}' not implemented in Rust library.\n", name);
                    app.print_help().ok();
                    std::process::exit(1);
                }
                Err(err) => {
                    log::error!("Error running subcommand '{}': {}", name, err);
                    std::process::exit(1);
                }
            }
        }
    }

    // If we reached here, no subcommand was executed
    log::error!("Error: No command provided.");
    app.print_help().ok();
    std::process::exit(1);
}

// The below functions are thin shims that call into the crate if the symbols
// are available. They are implemented as weak connectors so the crate can
// progressively implement `engine`, `runtime`, `datasets` and `run_subcommand`.

fn call_runtime_global_init() -> Result<(), String> {
    // runtime::global_init may not exist in the Rust port; if it does, call it.
    // We attempt to call quant1x::runtime::global_init() if present.
    // For now, call a no-op provided by the crate.
    quant1x::global_init();
    Ok(())
}

fn call_datasets_init() -> Result<(), String> {
    quant1x::datasets_init();
    Ok(())
}

fn call_logger_set(verbose: bool, debug: bool) -> Result<(), String> {
    quant1x::logger_set(verbose, debug);
    Ok(())
}

fn call_engine_init() -> Result<(), String> {
    quant1x::engine_init();
    Ok(())
}

fn call_engine_daemon(action: &str, pipe: bool) -> i32 {
    quant1x::engine_daemon(action, pipe)
}

mod lib_bridge {
    use clap::ArgMatches;
    use std::error::Error;

    // Forward to crate-provided implementation if present. The crate exports
    // `quant1x::try_run_subcommand` as a public function in `app` module.
    pub fn try_run_subcommand(name: &str, matches: &ArgMatches) -> Result<bool, Box<dyn Error>> {
        // call into quant1x::try_run_subcommand if available
        quant1x::try_run_subcommand(name, matches)
    }
}