mod cli;
mod config;
mod display;
mod error;
mod models;
mod query;
mod storage;
mod utils;
use cli::Commands;
use config::{load_config, Config};
use display::print_entry;
use error::DevbrainError;
use models::{Entry, EntryType};
use query::process_entries;
use std::fs;
use std::io::{self, Write};
use std::process;
use std::str::FromStr;
use storage::Database;
fn main() {
if let Err(error) = run() {
eprintln!("Error: {}", error);
process::exit(1);
}
}
fn run() -> Result<(), DevbrainError> {
let cli = cli::parse();
let config = load_config()?;
match cli.command {
Commands::Log { message } => log_message(&config, message)?,
Commands::LogCmd { command } => log_command(&config, command)?,
Commands::LogError { error } => log_error(&config, error)?,
Commands::Search {
query,
all,
limit,
offset,
entry_type,
} => search_entries(&config, query, all, limit, offset, entry_type)?,
Commands::Timeline {
all,
limit,
offset,
entry_type,
} => show_timeline(&config, all, limit, offset, entry_type)?,
Commands::Errors { all, limit } => {
show_recent_entries_by_type(&config, all, limit, EntryType::Error)?
}
Commands::CmdHistory { all, limit } => {
show_recent_entries_by_type(&config, all, limit, EntryType::Command)?
}
Commands::Last { all } => show_last_entry(&config, all)?,
Commands::Clear { force } => clear_entries(&config, force)?,
Commands::Export { output } => export_entries(&config, output)?,
Commands::Import { input, merge } => import_entries(&config, input, merge)?,
}
Ok(())
}
fn log_message(config: &Config, message: String) -> Result<(), DevbrainError> {
create_and_store_entry(config, EntryType::Log, message)?;
println!("Logged successfully");
Ok(())
}
fn log_command(config: &Config, command: String) -> Result<(), DevbrainError> {
create_and_store_entry(config, EntryType::Command, command)?;
println!("Command logged");
Ok(())
}
fn log_error(config: &Config, error: String) -> Result<(), DevbrainError> {
create_and_store_entry(config, EntryType::Error, error)?;
println!("Error logged");
Ok(())
}
fn create_and_store_entry(
config: &Config,
entry_type: EntryType,
content: String,
) -> Result<(), DevbrainError> {
let content = utils::normalize_input(&content)?;
let entry = Entry {
entry_type,
content,
project: utils::get_project_name(),
timestamp: utils::get_timestamp(),
};
storage::add_entry(config, entry)?;
Ok(())
}
fn search_entries(
config: &Config,
query: String,
all: bool,
limit: Option<usize>,
offset: Option<usize>,
entry_type: Option<String>,
) -> Result<(), DevbrainError> {
let entry_type = validate_entry_type(entry_type)?;
let db = storage::load_db(config)?;
let entries = process_entries(
&db.entries,
Some(query),
project_filter(all),
entry_type,
offset,
limit,
);
if entries.is_empty() {
println!("No matching entries found");
return Ok(());
}
print_entries(&entries);
Ok(())
}
fn show_timeline(
config: &Config,
all: bool,
limit: Option<usize>,
offset: Option<usize>,
entry_type: Option<String>,
) -> Result<(), DevbrainError> {
let entry_type = validate_entry_type(entry_type)?;
let db = storage::load_db(config)?;
let entries = process_entries(
&db.entries,
None,
project_filter(all),
entry_type,
offset,
limit,
);
if entries.is_empty() {
println!("No entries found");
return Ok(());
}
print_entries(&entries);
Ok(())
}
fn show_last_entry(config: &Config, all: bool) -> Result<(), DevbrainError> {
let db = storage::load_db(config)?;
let entries = process_entries(&db.entries, None, project_filter(all), None, None, Some(1));
if let Some(entry) = entries.first() {
print_entry(entry);
} else {
println!("No entries found");
}
Ok(())
}
fn show_recent_entries_by_type(
config: &Config,
all: bool,
limit: Option<usize>,
entry_type: EntryType,
) -> Result<(), DevbrainError> {
let db = storage::load_db(config)?;
let entries = process_entries(
&db.entries,
None,
project_filter(all),
Some(entry_type),
None,
limit,
);
if entries.is_empty() {
println!("No entries found");
return Ok(());
}
for entry in entries {
print_entry(entry);
}
Ok(())
}
fn clear_entries(config: &Config, force: bool) -> Result<(), DevbrainError> {
if !force && !confirm_clear()? {
println!("Operation cancelled");
return Ok(());
}
let mut db = storage::load_db(config)?;
db.entries.clear();
storage::save_db(config, &db)?;
println!("All entries cleared");
Ok(())
}
fn export_entries(config: &Config, output: String) -> Result<(), DevbrainError> {
let db = storage::load_db(config)?;
let json = serde_json::to_string_pretty(&db)
.map_err(|error| DevbrainError::ParseError(error.to_string()))?;
fs::write(&output, json).map_err(|error| DevbrainError::IoError(error.to_string()))?;
println!("Exported to {}", output);
Ok(())
}
fn import_entries(config: &Config, input: String, merge: bool) -> Result<(), DevbrainError> {
let contents =
fs::read_to_string(&input).map_err(|error| DevbrainError::IoError(error.to_string()))?;
let imported: Database = serde_json::from_str(&contents)
.map_err(|error| DevbrainError::ParseError(error.to_string()))?;
if merge {
let mut current = storage::load_db(config)?;
let mut added = 0;
for entry in imported.entries {
if !current.entries.contains(&entry) {
current.entries.push(entry);
added += 1;
}
}
storage::save_db(config, ¤t)?;
println!("Imported successfully ({} new entries added)", added);
} else {
storage::save_db(config, &imported)?;
println!("Data imported successfully (replaced existing data)");
}
Ok(())
}
fn confirm_clear() -> Result<bool, DevbrainError> {
print!("Are you sure you want to delete all entries? (y/n): ");
io::stdout()
.flush()
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
Ok(input.trim().eq_ignore_ascii_case("y"))
}
fn project_filter(all: bool) -> Option<String> {
if all {
None
} else {
Some(utils::get_project_name())
}
}
fn validate_entry_type(entry_type: Option<String>) -> Result<Option<EntryType>, DevbrainError> {
match entry_type {
Some(entry_type) => Ok(Some(EntryType::from_str(&entry_type)?)),
None => Ok(None),
}
}
fn print_entries(entries: &[&Entry]) {
for entry in entries {
print_entry(entry);
}
}