history : historique structure par session + am history (issue #7) - migration one-shot de history.txt, filtres kind/cwd/search/failed/session, enregistrement REPL avec duree et exit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
//! history: search and reuse the structured command history.
|
||||
|
||||
use super::*;
|
||||
use crate::history::{self, HistoryEntry};
|
||||
use crate::output::print_json;
|
||||
|
||||
/// Filter history entries; returns them most recent first.
|
||||
pub fn filter(
|
||||
app: &App,
|
||||
kind: Option<&str>,
|
||||
cwd: Option<&str>,
|
||||
search: Option<&str>,
|
||||
failed: bool,
|
||||
session: Option<&str>,
|
||||
limit: usize,
|
||||
) -> Vec<(String, HistoryEntry)> {
|
||||
let _ = session; // session filtering happens by reading one file directly
|
||||
let mut out: Vec<(String, HistoryEntry)> = Vec::new();
|
||||
let dir = history::dir(app);
|
||||
for f in history::files(&dir).iter().rev() {
|
||||
let sid = history::session_of_file(f).unwrap_or_default();
|
||||
if let Some(s) = session {
|
||||
if sid != s {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let Ok(text) = std::fs::read_to_string(f) else {
|
||||
continue;
|
||||
};
|
||||
let mut entries: Vec<HistoryEntry> = text
|
||||
.lines()
|
||||
.filter(|l| !l.trim().is_empty())
|
||||
.filter_map(|l| serde_json::from_str::<HistoryEntry>(l).ok())
|
||||
.collect();
|
||||
entries.reverse();
|
||||
for e in entries {
|
||||
if kind.map_or(true, |k| e.kind == k)
|
||||
&& cwd.map_or(true, |c| e.cwd.as_deref() == Some(c))
|
||||
&& search.map_or(true, |s| e.cmd.to_lowercase().contains(&s.to_lowercase()))
|
||||
&& (!failed || e.exit_code.is_some())
|
||||
{
|
||||
out.push((sid.clone(), e));
|
||||
if limit > 0 && out.len() >= limit {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
app: &App,
|
||||
kind: Option<&str>,
|
||||
cwd: Option<&str>,
|
||||
search: Option<&str>,
|
||||
failed: bool,
|
||||
session: Option<&str>,
|
||||
limit: usize,
|
||||
) -> Result<i32> {
|
||||
history::migrate(app)?;
|
||||
let rows = filter(app, kind, cwd, search, failed, session, limit);
|
||||
if app.json() {
|
||||
let payload: Vec<serde_json::Value> = rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (sid, e))| {
|
||||
serde_json::json!({
|
||||
"n": rows.len() - i,
|
||||
"ts": e.ts,
|
||||
"cmd": e.cmd,
|
||||
"kind": e.kind,
|
||||
"cwd": e.cwd,
|
||||
"exit_code": e.exit_code,
|
||||
"duration_ms": e.duration_ms,
|
||||
"session": sid,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
print_json(&payload);
|
||||
return Ok(0);
|
||||
}
|
||||
for (i, (_sid, e)) in rows.iter().enumerate() {
|
||||
let n = rows.len() - i;
|
||||
let ts = e.ts.get(0..16).unwrap_or(&e.ts);
|
||||
let flag = match e.exit_code {
|
||||
Some(_) => "✗".to_string(),
|
||||
None => " ".to_string(),
|
||||
};
|
||||
println!("{n:>4} {flag} {ts} {}", e.cmd);
|
||||
}
|
||||
if rows.is_empty() {
|
||||
app.log.info("no matching history — run 'am' and type some commands");
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub mod config_cmd;
|
||||
pub mod doctor_cmd;
|
||||
pub mod export_import;
|
||||
pub mod help_cmd;
|
||||
pub mod history_cmd;
|
||||
pub mod info_cmd;
|
||||
pub mod install_cmd;
|
||||
pub mod list_cmd;
|
||||
@@ -78,6 +79,22 @@ pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
|
||||
Command::Uninstall { agent, purge } => uninstall_cmd::run(app, agent, *purge),
|
||||
Command::Update { agent, all } => update_cmd::run(app, agent.as_deref(), *all),
|
||||
Command::Search { keyword, category } => search_cmd::run(app, keyword, category.as_deref()),
|
||||
Command::History {
|
||||
kind,
|
||||
cwd,
|
||||
search,
|
||||
failed,
|
||||
session,
|
||||
limit,
|
||||
} => history_cmd::run(
|
||||
app,
|
||||
kind.as_deref(),
|
||||
cwd.as_deref(),
|
||||
search.as_deref(),
|
||||
*failed,
|
||||
session.as_deref(),
|
||||
*limit,
|
||||
),
|
||||
Command::Info { agent } => info_cmd::run(app, agent),
|
||||
Command::Help { command, find } => help_cmd::run(app, command.as_deref(), find.as_deref()),
|
||||
Command::Version => version_cmd::run(app),
|
||||
|
||||
Reference in New Issue
Block a user