use anyhow::Result;
use colored::Colorize;
use std::collections::HashSet;
use std::path::PathBuf;
use super::{LogEntry, is_noise_command, strip_hostname_prefix};
use crate::config;
pub fn export_cmd(days: u32, date: Option<&str>, format: &str, output: Option<&str>) -> Result<()> {
let log_dir = config::log_dir();
let files = if let Some(raw) = date {
let parsed = super::parse_date(raw)?;
let path = log_dir.join(format!("{}.jsonl", parsed.format("%Y-%m-%d")));
if path.exists() {
vec![path]
} else {
eprintln!("Aviso: nenhum log para a data {raw}.");
return Ok(());
}
} else {
super::find_log_files(&log_dir, days)
};
if files.is_empty() {
eprintln!("Nenhum log encontrado.");
return Ok(());
}
export_raw(&files, format, output)
}
fn export_raw(files: &[PathBuf], format: &str, output: Option<&str>) -> Result<()> {
let mut rows: Vec<(String, &'static str, String)> = Vec::new();
for file in files {
let date_str = file
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("?")
.to_string();
let mut seen: HashSet<String> = HashSet::new();
for entry in super::read_log_entries(file) {
match entry {
LogEntry::Shell { commands } => {
for cmd in commands {
let cmd = cmd.trim().to_string();
if cmd.is_empty() || is_noise_command(&cmd) {
continue;
}
if seen.insert(format!("s:{cmd}")) {
rows.push((date_str.clone(), "shell", cmd));
}
}
}
LogEntry::Apps { windows } => {
for w in windows {
let w = strip_hostname_prefix(w.trim()).to_string();
if w.is_empty() {
continue;
}
if seen.insert(format!("a:{w}")) {
rows.push((date_str.clone(), "app", w));
}
}
}
LogEntry::ChromeTabs { tabs } => {
for t in tabs {
let url = t.url.trim().to_string();
if url.is_empty() {
continue;
}
if seen.insert(format!("t:{url}")) {
let content = if t.title.trim().is_empty() || t.title.trim() == url {
url
} else {
format!("{} | {}", t.title.trim(), t.url.trim())
};
rows.push((date_str.clone(), "tab", content));
}
}
}
LogEntry::Context { data } => {
for r in data.git_repos {
let repo = r.repo.clone();
let commits: Vec<String> = if !r.commits.is_empty() {
r.commits
} else if !r.last_commit.is_empty() {
vec![r.last_commit]
} else {
continue;
};
for c in commits {
let content = format!("{repo} | {c}");
if seen.insert(format!("g:{content}")) {
rows.push((date_str.clone(), "git", content));
}
}
}
}
LogEntry::Tag { label, .. } => {
if seen.insert(format!("n:{label}")) {
rows.push((date_str.clone(), "tag", label));
}
}
}
}
}
let out = match format {
"json" => {
let arr: Vec<serde_json::Value> = rows
.iter()
.map(|(d, t, c)| serde_json::json!({"date": d, "type": t, "content": c}))
.collect();
serde_json::to_string_pretty(&arr)?
}
_ => {
let mut s = String::from("date,type,content\n");
for (date, typ, content) in &rows {
let escaped = content.replace('"', "\"\"");
s.push_str(&format!("{date},{typ},\"{escaped}\"\n"));
}
s
}
};
match output {
Some(path) => {
std::fs::write(path, &out)?;
eprintln!("{} {}", "Exportado:".green(), path);
}
None => print!("{out}"),
}
Ok(())
}