use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use cstats_core::prelude::*;
use tracing::Level;
mod commands;
use commands::*;
#[derive(Debug, Parser)]
#[command(name = "cstats")]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
config: Option<PathBuf>,
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
verbose: u8,
#[arg(short, long, global = true)]
quiet: bool,
#[arg(long, global = true, value_enum, default_value = "text")]
format: OutputFormat,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum OutputFormat {
Text,
Json,
Yaml,
}
#[derive(Debug, Subcommand)]
enum Commands {
Init(InitArgs),
Stats(StatsArgs),
Collect(CollectArgs),
Analyze(AnalyzeArgs),
Cache(CacheArgs),
Hook(HookArgs),
Config(ConfigArgs),
}
#[derive(Debug, Args)]
struct InitArgs {
#[arg(short, long)]
config: Option<PathBuf>,
#[arg(short, long)]
force: bool,
#[arg(long)]
api_key: Option<String>,
}
#[derive(Debug, Args)]
struct StatsArgs {
#[arg(short, long, value_enum, default_value = "daily")]
period: StatsPeriod,
#[arg(short, long)]
detailed: bool,
#[arg(short, long)]
rate_limit: bool,
#[arg(short, long)]
billing: bool,
#[arg(long)]
no_cache: bool,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum StatsPeriod {
Daily,
Weekly,
Monthly,
Summary,
}
#[derive(Debug, Args)]
struct CollectArgs {
#[arg(short, long)]
source: String,
#[arg(short, long)]
command: Option<String>,
#[arg(short, long, value_delimiter = ',')]
metrics: Vec<String>,
#[arg(long, value_delimiter = ',')]
metadata: Vec<String>,
}
#[derive(Debug, Args)]
struct AnalyzeArgs {
#[arg(short, long)]
source: Option<String>,
#[arg(long)]
start_time: Option<String>,
#[arg(long)]
end_time: Option<String>,
#[arg(short, long, value_delimiter = ',')]
metrics: Vec<String>,
#[arg(long)]
group_by_source: bool,
}
#[derive(Debug, Args)]
struct CacheArgs {
#[command(subcommand)]
action: CacheAction,
}
#[derive(Debug, Subcommand)]
enum CacheAction {
Stats,
Clear,
List,
}
#[derive(Debug, Args)]
struct HookArgs {
#[arg(value_enum)]
shell: ShellType,
#[arg(short, long)]
output: Option<PathBuf>,
}
#[derive(Debug, Clone, clap::ValueEnum)]
enum ShellType {
Bash,
Zsh,
Fish,
Powershell,
}
#[derive(Debug, Args)]
struct ConfigArgs {
#[command(subcommand)]
action: ConfigAction,
}
#[derive(Debug, Subcommand)]
enum ConfigAction {
Show,
Validate,
Default,
SetApiKey {
#[arg(long)]
api_key: Option<String>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
init_logging(cli.verbose, cli.quiet)?;
let config = load_config(cli.config.as_deref()).await?;
match cli.command {
Commands::Init(args) => init_command(args).await,
Commands::Stats(args) => stats_command(args, cli.format, config).await,
Commands::Collect(args) => collect_command(args, &config).await,
Commands::Analyze(args) => analyze_command(args, &config, cli.format).await,
Commands::Cache(args) => cache_command(args, &config).await,
Commands::Hook(args) => hook_command(args).await,
Commands::Config(args) => {
config_command(args, &config, cli.format, cli.config.as_deref()).await
}
}
}
fn init_logging(verbose: u8, quiet: bool) -> Result<()> {
if quiet {
return Ok(());
}
let level = match verbose {
0 => Level::WARN,
1 => Level::INFO,
2 => Level::DEBUG,
_ => Level::TRACE,
};
tracing_subscriber::fmt()
.with_max_level(level)
.with_target(false)
.init();
Ok(())
}
async fn load_config(config_path: Option<&std::path::Path>) -> Result<Config> {
if let Some(path) = config_path {
Config::load_from_path(path).await
} else {
Config::load().await
}
}