use anyhow::Context as _;
use rusqlite::Connection;
mod config;
mod queries;
mod types;
pub use config::{
HistoryConfig, OutputConfig, ShimsConfig, SyncConfig, TokfHistorySection, TokfOutputSection,
TokfProjectConfig, TokfShimsSection, TokfSyncSection, current_project, global_config_path,
load_project_config, local_config_path, project_root_for, save_project_config,
save_upload_stats, save_upload_stats_to_path,
};
pub use queries::{
clear_history, get_history_entry, get_latest_entry, list_history, record_history,
search_history,
};
pub use types::{HistoryEntry, HistoryRecord};
pub fn try_was_recently_run(command: &str) -> bool {
let cwd = std::env::current_dir().unwrap_or_default();
let project_root = project_root_for(&cwd);
let project = project_root.to_string_lossy().into_owned();
let Some(path) = crate::tracking::db_path() else {
return false;
};
let Ok(conn) = open_db(&path) else {
return false;
};
matches!(
queries::most_recent_command(&conn, &project),
Ok(Some(last)) if last == command
)
}
pub fn open_db(path: &std::path::Path) -> anyhow::Result<Connection> {
let conn = crate::tracking::open_db(path)?;
init_history_table(&conn)?;
Ok(conn)
}
pub fn init_history_table(conn: &Connection) -> anyhow::Result<()> {
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
project TEXT NOT NULL DEFAULT '',
command TEXT NOT NULL,
filter_name TEXT,
raw_output TEXT NOT NULL,
filtered_output TEXT NOT NULL,
exit_code INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_history_timestamp ON history(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_history_command ON history(command);",
)
.context("create history table")?;
let has_project: i64 = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('history') WHERE name='project'",
[],
|r| r.get(0),
)
.unwrap_or(0);
if has_project == 0 {
conn.execute_batch("ALTER TABLE history ADD COLUMN project TEXT NOT NULL DEFAULT '';")
.context("migrate history table: add project column")?;
}
conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_history_project ON history(project);")
.context("create project index")?;
Ok(())
}
pub fn try_record(
command: &str,
filter_name: &str,
raw_output: &str,
filtered_output: &str,
exit_code: i32,
) -> Option<i64> {
let cwd = std::env::current_dir().unwrap_or_default();
let project_root = project_root_for(&cwd);
let project = project_root.to_string_lossy().into_owned();
let config = HistoryConfig::load(Some(&project_root));
let path = crate::tracking::db_path()?;
let conn = match open_db(&path) {
Ok(c) => c,
Err(e) => {
if crate::paths::debug_enabled() {
eprintln!("[tokf] history error (db open): {e:#}");
}
return None;
}
};
let record = HistoryRecord {
project,
command: command.to_owned(),
filter_name: Some(filter_name.to_owned()),
raw_output: raw_output.to_owned(),
filtered_output: filtered_output.to_owned(),
exit_code,
};
match record_history(&conn, &record, &config) {
Ok(id) => Some(id),
Err(e) => {
if crate::paths::debug_enabled() {
eprintln!("[tokf] history error (record): {e:#}");
}
None
}
}
}
#[cfg(test)]
mod config_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_clear;
#[cfg(test)]
mod tests_search;