use ankify::sync::{sync, SyncConfig};
use clap::{Arg, Command};
use std::path::PathBuf;
use std::process;
use tracing::{error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
#[tokio::main]
async fn main() {
let matches = Command::new("ankify")
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::new("file")
.help("The Typst source file to process")
.required(true)
.value_name("FILE")
.index(1),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Enable verbose output")
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("cache-file")
.long("cache-file")
.help("Custom cache file path")
.value_name("PATH"),
)
.arg(
Arg::new("ankiconnect-url")
.long("ankiconnect-url")
.help("AnkiConnect URL (default: from the document, else http://127.0.0.1:8765)")
.value_name("URL"),
)
.arg(
Arg::new("root")
.long("root")
.help("Root directory for Typst compilation")
.value_name("DIR"),
)
.arg(
Arg::new("font-path")
.long("font-path")
.help("Additional font paths for Typst")
.value_name("PATH")
.action(clap::ArgAction::Append),
)
.get_matches();
let source_file = PathBuf::from(matches.get_one::<String>("file").unwrap());
let verbose = matches.get_flag("verbose");
let initial_level = if verbose { "debug" } else { "info" };
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(initial_level));
let (filter, reload_handle) = tracing_subscriber::reload::Layer::new(env_filter);
tracing_subscriber::registry()
.with(fmt::layer())
.with(filter)
.init();
ankify::logging::register_verbose_hook(Box::new(move || {
let _ = reload_handle.modify(|f| *f = EnvFilter::new("debug"));
}));
let cache_file = matches.get_one::<String>("cache-file").map(PathBuf::from);
let ankiconnect_url = matches
.get_one::<String>("ankiconnect-url")
.map(String::from);
let mut extra_args = Vec::new();
if let Some(root) = matches.get_one::<String>("root") {
extra_args.push("--root".to_string());
extra_args.push(root.clone());
}
if let Some(font_paths) = matches.get_many::<String>("font-path") {
for path in font_paths {
extra_args.push("--font-path".to_string());
extra_args.push(path.clone());
}
}
if !source_file.exists() {
error!("Source file does not exist: {}", source_file.display());
process::exit(1);
}
let mut config = SyncConfig::new(&source_file)
.with_verbose(verbose)
.with_cli_mode(true)
.with_extra_args(extra_args);
if let Some(cache_file) = cache_file {
config = config.with_cache_file(cache_file);
}
if let Some(url) = ankiconnect_url {
config = config.with_ankiconnect_url(url);
}
match sync(config).await {
Ok(result) => {
info!("Sync completed successfully!");
info!("Notes added: {}", result.notes_added);
info!("Notes updated: {}", result.notes_updated);
info!("Notes unchanged: {}", result.notes_unchanged);
if result.decks_created > 0 {
info!("Decks created: {}", result.decks_created);
}
if !result.warnings.is_empty() {
info!("Warnings:");
for warning in &result.warnings {
info!(" - {}", warning);
}
}
}
Err(e) => {
error!("Sync failed: {}", e);
process::exit(1);
}
}
}