use anyhow::Result;
use clap::{Parser, Subcommand};
use fact_tools::{Fact, FactConfig};
use fact_tools::templates::TemplateBuilder;
use serde_json::json;
use std::path::PathBuf;
use std::time::Instant;
use tracing::{error, info};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(name = "fact")]
#[command(author, version, about = "FACT (Fast Augmented Context Tools) - High-performance context processing", long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true)]
config: Option<PathBuf>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Process {
#[arg(short, long)]
template: String,
#[arg(short, long)]
input: String,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
no_cache: bool,
},
Templates {
#[arg(short, long)]
tag: Option<String>,
#[arg(short, long)]
detailed: bool,
},
Cache {
#[arg(long)]
clear: bool,
},
Benchmark {
#[arg(short, long, default_value = "100")]
iterations: usize,
#[arg(short, long)]
template: Option<String>,
},
Init {
#[arg(short, long)]
force: bool,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let filter = if cli.verbose {
EnvFilter::new("debug")
} else {
EnvFilter::new("info")
};
tracing_subscriber::fmt()
.with_env_filter(filter)
.init();
let config = if let Some(config_path) = cli.config {
info!("Loading configuration from: {:?}", config_path);
let config_str = std::fs::read_to_string(config_path)?;
serde_json::from_str(&config_str)?
} else {
FactConfig::default()
};
let fact = Fact::with_config(config);
match cli.command {
Commands::Process {
template,
input,
output,
no_cache,
} => {
process_command(fact, template, input, output, no_cache).await?;
}
Commands::Templates { tag, detailed } => {
templates_command(tag, detailed)?;
}
Commands::Cache { clear } => {
cache_command(fact, clear)?;
}
Commands::Benchmark {
iterations,
template,
} => {
benchmark_command(fact, iterations, template).await?;
}
Commands::Init { force } => {
init_command(force)?;
}
}
Ok(())
}
async fn process_command(
fact: Fact,
template: String,
input: String,
output: Option<PathBuf>,
no_cache: bool,
) -> Result<()> {
info!("Processing with template: {}", template);
let context = if input.starts_with('{') || input.starts_with('[') {
serde_json::from_str(&input)?
} else if PathBuf::from(&input).exists() {
let content = std::fs::read_to_string(&input)?;
serde_json::from_str(&content)?
} else {
json!({ "data": input })
};
let start = Instant::now();
let result = fact.process(&template, context).await?;
let duration = start.elapsed();
info!("Processing completed in {:?}", duration);
let formatted = serde_json::to_string_pretty(&result)?;
if let Some(output_path) = output {
std::fs::write(output_path, &formatted)?;
info!("Result written to file");
} else {
println!("{}", formatted);
}
if !no_cache {
let stats = fact.cache_stats();
info!(
"Cache stats - Hit rate: {:.2}%, Entries: {}, Size: {} bytes",
stats.hit_rate * 100.0,
stats.entries,
stats.size_bytes
);
}
Ok(())
}
fn templates_command(tag: Option<String>, detailed: bool) -> Result<()> {
let registry = fact_tools::templates::TemplateRegistry::new();
let templates = if let Some(tag) = tag {
registry.search_by_tags(&[tag])
} else {
registry.list()
.into_iter()
.filter_map(|id| registry.get(&id))
.collect()
};
if detailed {
for template in templates {
println!("Template: {} ({})", template.name, template.id);
println!(" Description: {}", template.description);
println!(" Version: {}", template.metadata.version);
println!(" Tags: {}", template.metadata.tags.join(", "));
println!(" Steps: {}", template.steps.len());
println!(
" Performance: {:.0}ms avg, {}KB memory, complexity {}",
template.metadata.performance.avg_execution_time_ms,
template.metadata.performance.memory_usage_bytes / 1024,
template.metadata.performance.complexity
);
println!();
}
} else {
println!("Available templates:");
for template in templates {
println!(" {} - {}", template.id, template.name);
}
}
Ok(())
}
fn cache_command(fact: Fact, clear: bool) -> Result<()> {
if clear {
fact.clear_cache();
info!("Cache cleared");
}
let stats = fact.cache_stats();
println!("Cache Statistics:");
println!(" Entries: {}", stats.entries);
println!(" Size: {} KB", stats.size_bytes / 1024);
println!(" Hits: {}", stats.hits);
println!(" Misses: {}", stats.misses);
println!(" Hit Rate: {:.2}%", stats.hit_rate * 100.0);
println!(" Evictions: {}", stats.evictions);
Ok(())
}
async fn benchmark_command(
fact: Fact,
iterations: usize,
template: Option<String>,
) -> Result<()> {
let template_id = template.unwrap_or_else(|| "quick-transform".to_string());
info!("Running benchmark with template: {}", template_id);
info!("Iterations: {}", iterations);
let test_data = json!({
"data": (0..100).collect::<Vec<_>>(),
"metadata": {
"source": "benchmark",
"timestamp": chrono::Utc::now().to_rfc3339(),
}
});
let mut total_duration = std::time::Duration::ZERO;
let mut min_duration = std::time::Duration::MAX;
let mut max_duration = std::time::Duration::ZERO;
for _ in 0..10 {
let _ = fact.process(&template_id, test_data.clone()).await?;
}
fact.clear_cache();
for i in 0..iterations {
let start = Instant::now();
let _ = fact.process(&template_id, test_data.clone()).await?;
let duration = start.elapsed();
total_duration += duration;
min_duration = min_duration.min(duration);
max_duration = max_duration.max(duration);
if (i + 1) % 10 == 0 {
info!("Progress: {}/{}", i + 1, iterations);
}
}
let avg_duration = total_duration / iterations as u32;
let stats = fact.cache_stats();
println!("\nBenchmark Results:");
println!(" Template: {}", template_id);
println!(" Iterations: {}", iterations);
println!(" Average: {:?}", avg_duration);
println!(" Min: {:?}", min_duration);
println!(" Max: {:?}", max_duration);
println!(" Cache Hit Rate: {:.2}%", stats.hit_rate * 100.0);
println!(" Throughput: {:.2} ops/sec", 1000.0 / avg_duration.as_millis() as f64);
Ok(())
}
fn init_command(force: bool) -> Result<()> {
let config_path = PathBuf::from("fact.json");
if config_path.exists() && !force {
error!("Configuration file already exists. Use --force to overwrite.");
return Ok(());
}
let default_config = FactConfig::default();
let config_str = serde_json::to_string_pretty(&default_config)?;
std::fs::write(&config_path, config_str)?;
info!("Created configuration file: {:?}", config_path);
let template_path = PathBuf::from("custom_template.json");
if !template_path.exists() || force {
let example_template = TemplateBuilder::new("custom-example")
.name("Custom Example Template")
.description("An example template for custom processing")
.add_tag("custom")
.add_tag("example")
.build();
let template_str = serde_json::to_string_pretty(&example_template)?;
std::fs::write(&template_path, template_str)?;
info!("Created example template: {:?}", template_path);
}
println!("\nFACT initialized successfully!");
println!("Configuration file: fact.json");
println!("Example template: custom_template.json");
println!("\nGet started with:");
println!(" fact process --template analysis-basic --input '{{\"data\": [1,2,3,4,5]}}'");
Ok(())
}