use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::io::Write;
use std::sync::{Arc, Mutex};
use cahier::{common, config, db, export, repl, tui};
use common::DEFAULT_MAX_OUTPUT_SIZE;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Start {
#[arg(long, default_value_t = DEFAULT_MAX_OUTPUT_SIZE)]
max_output_size: usize,
},
Export {
#[arg(short, long)]
output: Option<String>,
#[arg(long)]
only_commands: bool,
},
Edit,
}
fn main() -> Result<()> {
common::init_base_dir();
let cahier_dir = common::cahier_dir();
if !cahier_dir.exists() {
std::fs::create_dir_all(&cahier_dir).context("Failed to create cahier directory")?;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = std::fs::metadata(&cahier_dir)?;
let mut perms = metadata.permissions();
if perms.mode() & 0o777 != 0o700 {
perms.set_mode(0o700);
std::fs::set_permissions(&cahier_dir, perms)
.context("Failed to set cahier directory permissions")?;
}
}
let args = Args::parse();
let db_path = common::db_path();
let db = db::Database::init(&db_path).context("Failed to initialize database")?;
let config = config::Config::load().context("Failed to load configuration")?;
match args.command {
Some(Commands::Export {
output,
only_commands,
}) => {
let content = if only_commands {
export::generate_commands_text(&db).context("Failed to generate commands export")?
} else {
export::generate_markdown(&db).context("Failed to generate markdown export")?
};
if let Some(path) = output {
std::fs::write(path, content).context("Failed to write export output")?;
} else {
println!("{}", content);
}
Ok(())
}
Some(Commands::Edit) => {
if let Some(cmd) = tui::run(db)? {
let db =
db::Database::init(&db_path).context("Failed to re-initialize database")?;
let pty_writer = setup_signal_handler()?;
repl::run_repl(db, DEFAULT_MAX_OUTPUT_SIZE, pty_writer, config, Some(cmd))
} else {
Ok(())
}
}
Some(Commands::Start { max_output_size }) => {
let pty_writer = setup_signal_handler()?;
repl::run_repl(db, max_output_size, pty_writer, config, None)
}
None => {
let pty_writer = setup_signal_handler()?;
repl::run_repl(db, DEFAULT_MAX_OUTPUT_SIZE, pty_writer, config, None)
}
}
}
type PtyWriter = Arc<Mutex<Option<Box<dyn Write + Send>>>>;
fn setup_signal_handler() -> Result<PtyWriter> {
let pty_writer: PtyWriter = Arc::new(Mutex::new(None));
let writer_clone = Arc::clone(&pty_writer);
ctrlc::set_handler(move || {
if let Ok(mut writer_opt) = writer_clone.lock() {
if let Some(writer) = writer_opt.as_mut() {
let _ = writer.write_all(&[3]);
let _ = writer.flush();
}
}
})
.context("Failed to set Ctrl+C handler")?;
Ok(pty_writer)
}