use clap::Parser;
use color_eyre::Result;
use ratatui::DefaultTerminal;
use ratatui::crossterm::event::{DisableBracketedPaste, EnableBracketedPaste};
use ratatui::crossterm::execute;
use ratatui::crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use std::io::stdout;
use std::path::PathBuf;
mod app;
mod autocomplete;
mod clipboard;
mod config;
mod editor;
mod error;
mod help;
mod history;
mod input;
mod notification;
mod query;
mod results;
mod scroll;
mod search;
mod stats;
mod syntax_highlight;
#[cfg(test)]
mod test_utils;
mod tooltip;
mod widgets;
use app::{App, OutputMode};
use error::JiqError;
use input::reader::InputReader;
use query::executor::JqExecutor;
#[derive(Parser, Debug)]
#[command(
version,
about = "Interactive JSON query tool with real-time filtering using jq"
)]
struct Args {
input: Option<PathBuf>,
}
fn main() -> Result<()> {
#[cfg(debug_assertions)]
{
use std::io::Write;
let log_file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/jiq-debug.log")
.expect("Failed to open /tmp/jiq-debug.log");
env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug)
.target(env_logger::Target::Pipe(Box::new(log_file)))
.format(|buf, record| {
use std::time::SystemTime;
let datetime: chrono::DateTime<chrono::Local> = SystemTime::now().into();
writeln!(
buf,
"[{}] [{}] {}",
datetime.format("%Y-%m-%dT%H:%M:%S%.3f"),
record.level(),
record.args()
)
})
.init();
log::debug!("=== JIQ DEBUG SESSION STARTED ===");
}
color_eyre::install()?;
let config_result = config::load_config();
let args = Args::parse();
validate_jq_exists()?;
let json_input = match InputReader::read_json(args.input.as_deref()) {
Ok(json) => json,
Err(e) => {
eprintln!("Error reading JSON: {:?}", e);
return Err(e.into());
}
};
let terminal = init_terminal()?;
let app = run(terminal, json_input.clone(), config_result)?;
restore_terminal()?;
handle_output(&app, &json_input)?;
Ok(())
}
fn validate_jq_exists() -> Result<(), JiqError> {
which::which("jq").map_err(|_| JiqError::JqNotFound)?;
Ok(())
}
fn init_terminal() -> Result<DefaultTerminal> {
enable_raw_mode()?;
execute!(stdout(), EnterAlternateScreen, EnableBracketedPaste)?;
let terminal = ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?;
Ok(terminal)
}
fn restore_terminal() -> Result<()> {
execute!(stdout(), DisableBracketedPaste, LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}
fn run(
mut terminal: DefaultTerminal,
json_input: String,
config_result: config::ConfigResult,
) -> Result<App> {
let mut app = App::new(json_input, &config_result.config);
if let Some(warning) = config_result.warning {
app.notification.show_warning(&warning);
}
loop {
terminal.draw(|frame| app.render(frame))?;
app.handle_events()?;
if app.should_quit() {
break;
}
}
Ok(app)
}
fn handle_output(app: &App, json_input: &str) -> Result<()> {
match app.output_mode() {
Some(OutputMode::Results) => {
let executor = JqExecutor::new(json_input.to_string());
match executor.execute(app.query()) {
Ok(result) => println!("{}", result),
Err(e) => eprintln!("Error: {}", e),
}
}
Some(OutputMode::Query) => {
println!("{}", app.query());
}
None => {
}
}
Ok(())
}