From bbd0b69fa046a417b0f1e6849e22b23fd268644b Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 03:35:33 +0200 Subject: [PATCH 01/19] refactor load_files --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7e1eb35..2255d2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use anyhow::anyhow; use clap::Parser; use std::fs::File; use std::io; -use std::io::{BufRead, Write}; +use std::io::BufRead; use std::path::{Path, PathBuf}; #[derive(Parser, Debug)] @@ -77,12 +77,12 @@ fn init_props(args: &Args) -> anyhow::Result { fn load_files(files: &[PathBuf]) -> anyhow::Result { let mut raw_lines = RawJsonLines::default(); - for f in files { - let path = PathBuf::from(f); - match path.extension().map(|e| e.to_str()) { - Some(Some("json")) => load_lines_from_json(&mut raw_lines, &path)?, - Some(Some("zip")) => load_lines_from_zip(&mut raw_lines, &path)?, - _ => writeln!(&mut io::stderr(), "unknown file extension: '{}'", path.to_string_lossy()).expect("failed to write to stderr"), + + for path in files { + match path.extension().and_then(|e| e.to_str()) { + Some("json") => load_lines_from_json(&mut raw_lines, path).with_context(|| format!("failed to load lines from {path:?}"))?, + Some("zip") => load_lines_from_zip(&mut raw_lines, path).with_context(|| format!("failed to load lines from {path:?}"))?, + _ => eprintln!("unknown file extension: '{}'", path.to_string_lossy()), } } From e7556328045fde3d9d8b4a1cc57e60955e9aeea0 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 04:21:52 +0200 Subject: [PATCH 02/19] refactor load_lines_from_json --- src/main.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2255d2d..f9673f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,18 +36,19 @@ struct Args { fn main() -> anyhow::Result<()> { let args = Args::parse(); - let props: Props = init_props(&args)?; + let props: Props = init_props(&args).context("failed to init props")?; - let lines = load_files(&args.files)?; + let lines = load_files(&args.files).context("failed to load files")?; terminal::install_panic_hook(); - let mut terminal = terminal::init_terminal()?; + let mut terminal = terminal::init_terminal().context("faild to initialize terminal")?; + let terminal_size = terminal.size().map_err(|e| anyhow!("{e}")).context("failed to get terminal size")?; - let mut model = Model::new(props, terminal.size().map_err(|e| anyhow!("{e}"))?, &lines); + let mut model = Model::new(props, terminal_size, &lines); while model.active_screen != Screen::Done { // Render the current view - terminal.draw(|f| terminal::view(&mut model, f)).map_err(|e| anyhow!("{e}"))?; + terminal.draw(|f| terminal::view(&mut model, f)).map_err(|e| anyhow!("{e}")).context("failed to draw to terminal")?; // Handle events and map to a Message let mut current_msg = event::handle_event(&model)?; @@ -93,9 +94,21 @@ fn load_lines_from_json( raw_lines: &mut RawJsonLines, path: &Path, ) -> anyhow::Result<()> { - for (line_nr, line) in io::BufReader::new(File::open(path)?).lines().enumerate() { - raw_lines.push(SourceName::JsonFile(path.file_name().unwrap().to_string_lossy().into()), line_nr + 1, line?); + let json_file = File::open(path).context("failed to open json")?; + let json_file = io::BufReader::new(json_file); + + for (line_nr, line) in json_file.lines().enumerate() { + let line = line.context("failed to read json line")?; + let file_name = path + .file_name() + .context("BUG: json path is missing filename")? + .to_string_lossy() + .into(); + let source_name = SourceName::JsonFile(file_name); + + raw_lines.push(source_name, line_nr + 1, line); } + Ok(()) } From 29664b7bf73c8e9e20e1e9b63696c42a78ea23fa Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 04:05:53 +0200 Subject: [PATCH 03/19] refactor load_lines_from_zip --- src/main.rs | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index f9673f5..44c6f67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ mod terminal; use crate::model::{Model, Screen}; use crate::props::Props; use crate::raw_json_lines::{RawJsonLines, SourceName}; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use clap::Parser; use std::fs::File; use std::io; @@ -116,24 +116,34 @@ fn load_lines_from_zip( raw_lines: &mut RawJsonLines, path: &Path, ) -> anyhow::Result<()> { - let zip_file = File::open(path)?; - let mut archive = zip::ZipArchive::new(zip_file)?; + let zip_file = File::open(path).context("failed to open zip")?; + let mut archive = zip::ZipArchive::new(zip_file).context("failed to parse zip")?; for i in 0..archive.len() { - let f = archive.by_index(i)?; - if f.is_file() && f.name().ends_with(".json") { - let json_file = f.name().to_string(); - for (line_nr, line) in io::BufReader::new(f).lines().enumerate() { - raw_lines.push( - SourceName::JsonInZip { - zip_file: path.file_name().unwrap().to_string_lossy().into(), - json_file: json_file.clone(), - }, - line_nr + 1, - line?, - ); - } + let f = archive + .by_index(i) + .with_context(|| format!("failed to get file with index {i} from zip"))?; + + if !f.is_file() || !f.name().ends_with(".json") { + continue; + } + + let json_file = f.name().to_string(); + let f = io::BufReader::new(f); + + for (line_nr, line) in f.lines().enumerate() { + let line = line.context("failed to read line from file in zip")?; + let zip_file = path + .file_name() + .context("BUG: zip path is missing filename")? + .to_string_lossy() + .into(); + let json_file = json_file.clone(); + let source_name = SourceName::JsonInZip { zip_file, json_file }; + + raw_lines.push(source_name, line_nr + 1, line); } } + Ok(()) } From b12207efaa74eaab5fe2bde626bf6f4ff13e2bed Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 04:29:33 +0200 Subject: [PATCH 04/19] refactor init_props --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 44c6f67..4ba07dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,13 +66,16 @@ fn main() -> anyhow::Result<()> { } fn init_props(args: &Args) -> anyhow::Result { - let mut props = Props::init()?; + let mut props = Props::init().context("failed to load props")?; + if let Some(e) = &args.field_order { props.fields_order = e.clone(); } + if let Some(e) = &args.suppressed_fields { props.fields_suppressed = e.clone(); } + Ok(props) } From 3acac3c028077d58621c8eaab5e495a729283003 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 04:45:21 +0200 Subject: [PATCH 05/19] refactor Props::init --- src/props.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/props.rs b/src/props.rs index 929a8f3..5e04a36 100644 --- a/src/props.rs +++ b/src/props.rs @@ -1,5 +1,6 @@ -use anyhow::anyhow; +use anyhow::{Context, anyhow}; use serde::{Deserialize, Serialize}; +use std::fs; use std::path::PathBuf; #[derive(Serialize, Deserialize, Default, Clone)] @@ -14,12 +15,14 @@ impl Props { } pub fn init() -> anyhow::Result { - match &Self::config_file_path() { - Some(f) if f.exists() => Ok(toml::from_str( - &std::fs::read_to_string(f).map_err(|e| anyhow!("'{}' - while reading file {}", e, f.to_string_lossy()))?, - )?), - _ => Ok(Props::default()), - } + let Some(f) = &Self::config_file_path().filter(|f| f.exists()) else { + return Ok(Props::default()); + }; + + let props = fs::read_to_string(f).with_context(|| format!("failed to read config file {f:?}"))?; + let props = toml::from_str::(&props).context("failed to parse config file as toml")?; + + Ok(props) } pub fn save(&self) -> anyhow::Result<()> { From 9a9d2b1482ad7dd143a1021c922f015fd480ad58 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 05:22:05 +0200 Subject: [PATCH 06/19] refactor main --- src/main.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4ba07dc..175ba80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ mod terminal; use crate::model::{Model, Screen}; use crate::props::Props; use crate::raw_json_lines::{RawJsonLines, SourceName}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use clap::Parser; use std::fs::File; use std::io; @@ -48,10 +48,13 @@ fn main() -> anyhow::Result<()> { while model.active_screen != Screen::Done { // Render the current view - terminal.draw(|f| terminal::view(&mut model, f)).map_err(|e| anyhow!("{e}")).context("failed to draw to terminal")?; + terminal + .draw(|f| terminal::view(&mut model, f)) + .map_err(|e| anyhow!("{e}")) + .context("failed to draw to terminal")?; // Handle events and map to a Message - let mut current_msg = event::handle_event(&model)?; + let mut current_msg = event::handle_event(&model).context("failed to handle event")?; // Process updates as long as they return a non-None message while let Some(msg) = current_msg { @@ -61,7 +64,8 @@ fn main() -> anyhow::Result<()> { } } - terminal::restore_terminal()?; + terminal::restore_terminal().context("failed to restore terminal state")?; + Ok(()) } From b401d0ce7682a8e5178b4bae758e4ffd65332f7e Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 05:36:16 +0200 Subject: [PATCH 07/19] ensure terminal state is always restored --- src/main.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 175ba80..33ad87a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ use crate::props::Props; use crate::raw_json_lines::{RawJsonLines, SourceName}; use anyhow::{Context, anyhow}; use clap::Parser; +use ratatui::prelude::Backend; +use ratatui::Terminal; use std::fs::File; use std::io; use std::io::BufRead; @@ -41,9 +43,19 @@ fn main() -> anyhow::Result<()> { let lines = load_files(&args.files).context("failed to load files")?; terminal::install_panic_hook(); - let mut terminal = terminal::init_terminal().context("faild to initialize terminal")?; - let terminal_size = terminal.size().map_err(|e| anyhow!("{e}")).context("failed to get terminal size")?; + let terminal = terminal::init_terminal().context("faild to initialize terminal")?; + + if let Err(err) = run_app(terminal, props, lines) { + eprintln!("{err:?}"); + } + + terminal::restore_terminal().context("failed to restore terminal state")?; + + Ok(()) +} +fn run_app(mut terminal: Terminal, props: Props, lines: RawJsonLines) -> Result<(), anyhow::Error> { + let terminal_size = terminal.size().map_err(|e| anyhow!("{e}")).context("failed to get terminal size")?; let mut model = Model::new(props, terminal_size, &lines); while model.active_screen != Screen::Done { @@ -64,11 +76,10 @@ fn main() -> anyhow::Result<()> { } } - terminal::restore_terminal().context("failed to restore terminal state")?; - Ok(()) } + fn init_props(args: &Args) -> anyhow::Result { let mut props = Props::init().context("failed to load props")?; From a957706cfee600f714f8a0cf63043bf986e7b74a Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 05:54:28 +0200 Subject: [PATCH 08/19] refactor handle_event --- src/event.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/event.rs b/src/event.rs index bd985bd..d0633b0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,22 +1,25 @@ use crate::model::{Message, Model}; +use anyhow::Context; use crossterm::event; use crossterm::event::{Event, KeyCode, KeyModifiers}; use ratatui::prelude::Size; use std::time::Duration; pub fn handle_event(_: &Model) -> anyhow::Result> { - if event::poll(Duration::from_millis(250))? { - match event::read()? { - Event::Key(key) if key.kind == event::KeyEventKind::Press => { - return Ok(handle_key(key)); - } - Event::Resize(cols, rows) => { - return Ok(handle_resize(cols, rows)); - } - _ => (), - } + let event_available = event::poll(Duration::from_millis(250)).context("failed to poll event")?; + + if !event_available { + return Ok(None); } - Ok(None) + + let event = event::read().context("failed to read event")?; + let message = match event { + Event::Key(key) if key.kind == event::KeyEventKind::Press => handle_key(key), + Event::Resize(cols, rows) => handle_resize(cols, rows), + _ => None, + }; + + Ok(message) } fn handle_key(key: event::KeyEvent) -> Option { From c8663f063363c7ac3a8256b1b6cd94ecaece2534 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 06:00:39 +0200 Subject: [PATCH 09/19] refactor handle_key --- src/event.rs | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/event.rs b/src/event.rs index d0633b0..8d4b40d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -23,34 +23,34 @@ pub fn handle_event(_: &Model) -> anyhow::Result> { } fn handle_key(key: event::KeyEvent) -> Option { - match key.modifiers { + Some(match key.modifiers { KeyModifiers::NONE => match key.code { - KeyCode::Home => Some(Message::First), - KeyCode::End => Some(Message::Last), - KeyCode::Up => Some(Message::ScrollUp), - KeyCode::Down => Some(Message::ScrollDown), - KeyCode::PageUp => Some(Message::PageUp), - KeyCode::PageDown => Some(Message::PageDown), - KeyCode::Left => Some(Message::ScrollLeft), - KeyCode::Right => Some(Message::ScrollRight), - KeyCode::Enter => Some(Message::Enter), - KeyCode::Esc => Some(Message::Exit), - KeyCode::Char('/') => Some(Message::OpenFindTask), - KeyCode::Backspace => Some(Message::Backspace), - KeyCode::Char(c) => Some(Message::CharacterInput(c)), - _ => None, + KeyCode::Home => Message::First, + KeyCode::End => Message::Last, + KeyCode::Up => Message::ScrollUp, + KeyCode::Down => Message::ScrollDown, + KeyCode::PageUp => Message::PageUp, + KeyCode::PageDown => Message::PageDown, + KeyCode::Left => Message::ScrollLeft, + KeyCode::Right => Message::ScrollRight, + KeyCode::Enter => Message::Enter, + KeyCode::Esc => Message::Exit, + KeyCode::Char('/') => Message::OpenFindTask, + KeyCode::Backspace => Message::Backspace, + KeyCode::Char(c) => Message::CharacterInput(c), + _ => return None, }, KeyModifiers::SHIFT => match key.code { - KeyCode::Char(c) => Some(Message::CharacterInput(c)), - _ => None - } + KeyCode::Char(c) => Message::CharacterInput(c), + _ => return None, + }, KeyModifiers::CONTROL => match key.code { - KeyCode::Char('s') => Some(Message::SaveSettings), - KeyCode::Char('f') => Some(Message::OpenFindTask), - _ => None, + KeyCode::Char('s') => Message::SaveSettings, + KeyCode::Char('f') => Message::OpenFindTask, + _ => return None, }, - _ => None, - } + _ => return None, + }) } fn handle_resize( From 7c0718030e9c20dd28d76afecca68bcf9063c9a0 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 06:23:54 +0200 Subject: [PATCH 10/19] refactor render_find_task_line_right --- src/model.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/model.rs b/src/model.rs index 14cbe5b..5a3e09d 100644 --- a/src/model.rs +++ b/src/model.rs @@ -404,15 +404,18 @@ impl<'a> Model<'a> { } pub fn render_find_task_line_right(&self) -> Line { - if let Some(t) = self.find_task.as_ref() { - if let Some(state) = t.found { - return match state { - true => "found".to_owned().into(), - false => "NOT found".to_owned().into(), - } - } + let Some(task) = &self.find_task else { + return "".into(); + }; + + let Some(found) = task.found else { + return "".into(); + }; + + match found { + true => "found".into(), + false => "NOT found".into(), } - "".into() } pub fn page_len(&self) -> u16 { From 4e25089be487f15c7d1f6253617937120c5e737a Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 06:28:32 +0200 Subject: [PATCH 11/19] refactor render_find_task_line_left --- src/model.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/model.rs b/src/model.rs index 5a3e09d..4dbcb47 100644 --- a/src/model.rs +++ b/src/model.rs @@ -386,21 +386,24 @@ impl<'a> Model<'a> { } pub fn render_find_task_line_left(&self) -> Line { - if let Some(task) = self.find_task.as_ref() { - let color = match task.found { - None => Color::default(), - Some(false) => Color::Red, - Some(true) => Color::Green - }; - " [".to_span().set_style(color) - .add("Find ".to_span()) - .add("🔍".to_span()) - .add(": ".bold()) - .add(task.search_string.to_span().bold()) - .add(" ] ".to_span().set_style(color)).to_owned() - } else { - Line::raw("").to_owned() - } + let Some(task) = &self.find_task else { + return "".into(); + }; + + let color = match task.found { + None => Color::default(), + Some(false) => Color::Red, + Some(true) => Color::Green, + }; + + " [".to_span() + .set_style(color) + .add("Find ".to_span()) + .add("🔍".to_span()) + .add(": ".bold()) + .add(task.search_string.to_span().bold()) + .add(" ] ".to_span().set_style(color)) + .to_owned() } pub fn render_find_task_line_right(&self) -> Line { From 4a8f8959b6abef69c59bd7ff125704c7aaf0db9a Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 06:42:43 +0200 Subject: [PATCH 12/19] refactor render_status_line_left --- src/model.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/model.rs b/src/model.rs index 4dbcb47..a678e27 100644 --- a/src/model.rs +++ b/src/model.rs @@ -372,15 +372,19 @@ impl<'a> Model<'a> { } pub fn render_status_line_left(&self) -> String { - match self.view_state.main_window_list_state.selected() { - Some(line_nr) if self.raw_json_lines.lines.len() > line_nr => { - let raw_line = &self.raw_json_lines.lines[line_nr]; - let source_name = self.raw_json_lines.source_name(raw_line.source_id).expect("invalid source id"); - format!("{}:{}", source_name, raw_line.line_nr) - } - _ => String::new(), - } + let Some(line_nr) = self.view_state.main_window_list_state.selected() else { + return "".into(); + }; + + let Some(raw_line) = self.raw_json_lines.lines.get(line_nr) else { + return "".into(); + }; + + let source_name = self.raw_json_lines.source_name(raw_line.source_id).expect("invalid source id"); + + format!("{}:{}", source_name, raw_line.line_nr) } + pub fn render_status_line_right(&self) -> String { self.last_action_result.clone() } From 8681dc2f059532b0d3bd205fdfb5fb7c326c87f9 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 07:00:21 +0200 Subject: [PATCH 13/19] refactor render_value_details_screen --- src/terminal.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/terminal.rs b/src/terminal.rs index d673058..064b9a2 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -11,7 +11,6 @@ use ratatui::{ Terminal, }; use serde_json::Value; -use std::str::FromStr; use std::{cmp, io::stdout, panic}; pub fn init_terminal() -> anyhow::Result> { @@ -120,15 +119,17 @@ fn render_value_details_screen( ) { let line_idx = model.view_state.main_window_list_state.selected().expect("we should find a a selected line"); let raw_line = &model.raw_json_lines.lines[line_idx].content; - let text = if let Value::Object(o) = serde_json::Value::from_str(raw_line).expect("invalid json") { - let value = o.get(model.view_state.selected_object_detail_field_name.as_ref().expect("should have a selected field")) - .expect("key should exist"); - match value { - Value::String(s) => s.clone(), - _ => format!("{value}") - } - } else { - panic!("should find a json object") + let field_name = model.view_state.selected_object_detail_field_name.as_ref().expect("should have a selected field"); + + let value = raw_line.parse::().expect("invalid json"); + let Value::Object(o) = value else { + panic!("should find a json object"); + }; + + let field_value = o.get(field_name).expect("key should exist"); + let text = match field_value { + Value::String(s) => s.clone(), + _ => format!("{field_value}") }; // correct scroll line offset – so that current text lines are always on the screen @@ -141,8 +142,10 @@ fn render_value_details_screen( .wrap(Wrap::default()) .block(block) .scroll((*vertical_scroll_offset, 0)); + if let Some(p) = cursor_position { frame.set_cursor_position(p) } + frame.render_widget(paragraph, frame.area()); } From c32c39c5d95f768e84f14a71a6efea0c8d4934b3 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 07:03:49 +0200 Subject: [PATCH 14/19] elide ModelIntoIter impl lifetime --- src/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model.rs b/src/model.rs index a678e27..fbe9d44 100644 --- a/src/model.rs +++ b/src/model.rs @@ -522,7 +522,7 @@ pub struct ModelIntoIter<'a> { index: usize, } -impl<'a> ModelIntoIter<'a> { +impl ModelIntoIter<'_> { // light version of Self::next() that simply skips the item. // returns true if the item was skipped, false if there are no more items fn skip_item(&mut self) -> bool { From 1b3566d6578a9a391e334c5f4f76dc61da65d889 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 07:23:43 +0200 Subject: [PATCH 15/19] refactor ::next --- src/model.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/model.rs b/src/model.rs index fbe9d44..f88bf94 100644 --- a/src/model.rs +++ b/src/model.rs @@ -8,7 +8,6 @@ use std::cell::Cell; use std::cmp; use std::num::NonZero; use std::ops::Add; -use std::rc::Rc; #[derive(Clone)] pub struct Model<'a> { @@ -548,19 +547,15 @@ impl<'a> Iterator for ModelIntoIter<'a> { type Item = ListItem<'a>; fn next(&mut self) -> Option { - if self.index >= self.model.raw_json_lines.lines.len() { - None - } else { - let raw_line = &self.model.raw_json_lines.lines[self.index]; - let json: Rc = Rc::new(serde_json::from_str(&raw_line.content).expect("invalid json")); - let line = match json.as_ref() { - serde_json::Value::Object(o) => self.model.render_json_line(o), - e => Line::from(format!("{e}")), - }; + let raw_line = self.model.raw_json_lines.lines.get(self.index)?; + let json = serde_json::from_str::(&raw_line.content).expect("invalid json"); + let line = match json { + serde_json::Value::Object(o) => self.model.render_json_line(&o), + e => Line::from(format!("{e}")), + }; - self.index += 1; - Some(ListItem::new(line)) - } + self.index += 1; + Some(ListItem::new(line)) } fn size_hint(&self) -> (usize, Option) { From 1757f8c961fa704a5ea43cee3ebf420671a77cd5 Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 07:48:05 +0200 Subject: [PATCH 16/19] refactor RawJsonLine::produce_rendered_fields_as_list --- src/raw_json_lines.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/raw_json_lines.rs b/src/raw_json_lines.rs index d7a6e77..2c5820a 100644 --- a/src/raw_json_lines.rs +++ b/src/raw_json_lines.rs @@ -72,21 +72,22 @@ pub struct RawJsonLine { impl RawJsonLine { /// returns JSON object lines and keys in rendered order pub fn produce_rendered_fields_as_list(&self, key_order: &[String]) -> (Vec, Vec) { - if let serde_json::Value::Object(o) = serde_json::from_str(&self.content).expect("not a json value") { + let value = serde_json::from_str(&self.content).expect("not a json value"); - let mut keys_in_rendered_order: Vec<_> = key_order.iter().filter(|&e| o.contains_key(e)).cloned().collect(); - keys_in_rendered_order.extend(o.keys().filter(|&e| !key_order.contains(e)).cloned()); + let serde_json::Value::Object(o) = value else { + panic!("line should be in json object format") + }; - let mut list_items = vec![]; + let mut keys_in_rendered_order: Vec<_> = key_order.iter().filter(|&e| o.contains_key(e)).cloned().collect(); + keys_in_rendered_order.extend(o.keys().filter(|&e| !key_order.contains(e)).cloned()); - for k in &keys_in_rendered_order { - list_items.push(Self::render_attribute(k, o.get(k).unwrap())); - } + let mut list_items = vec![]; - (list_items, keys_in_rendered_order) - } else { - panic!("line should be in json object format") + for k in &keys_in_rendered_order { + list_items.push(Self::render_attribute(k, o.get(k).unwrap())); } + + (list_items, keys_in_rendered_order) } fn render_attribute(key: &str, value: &serde_json::Value) -> String { From c8720c0df8d406b31bc780c30dc4b4c8240f613d Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 07:57:43 +0200 Subject: [PATCH 17/19] refactor Props::save --- src/props.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/props.rs b/src/props.rs index 5e04a36..9da1c11 100644 --- a/src/props.rs +++ b/src/props.rs @@ -26,12 +26,11 @@ impl Props { } pub fn save(&self) -> anyhow::Result<()> { - match Self::config_file_path() { - None => Err(anyhow!("Config dir not found")), - Some(f) => { - std::fs::write(&f, toml::to_string_pretty(self)?)?; - Ok(()) - } - } + let f = Self::config_file_path().context("Config dir not found")?; + let toml = toml::to_string_pretty(self)?; + + std::fs::write(&f, toml)?; + + Ok(()) } } From 1eff8058d0c50387592baab0bd23095eef3de83a Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 09:14:11 +0200 Subject: [PATCH 18/19] fix typo --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 33ad87a..8c845dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ fn main() -> anyhow::Result<()> { let lines = load_files(&args.files).context("failed to load files")?; terminal::install_panic_hook(); - let terminal = terminal::init_terminal().context("faild to initialize terminal")?; + let terminal = terminal::init_terminal().context("failed to initialize terminal")?; if let Err(err) = run_app(terminal, props, lines) { eprintln!("{err:?}"); From c4bac1b1c182da07b952fec88734e175082196de Mon Sep 17 00:00:00 2001 From: panicbit Date: Thu, 29 May 2025 09:14:44 +0200 Subject: [PATCH 19/19] remove unused import --- src/props.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/props.rs b/src/props.rs index 9da1c11..501c469 100644 --- a/src/props.rs +++ b/src/props.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, anyhow}; +use anyhow::Context; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf;