Skip to content

Commit

Permalink
Use thiserror for error type
Browse files Browse the repository at this point in the history
  • Loading branch information
YS-L committed Jul 21, 2024
1 parent b8e8633 commit 67c1afd
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 63 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tui-input = { version = "0.8", features = ["crossterm"] }
arrow = {version = "50.0.0", default-features = false, features = ["csv"]}
sorted-vec = "0.8.3"
arboard = { version = "3.3.2", features = ["wayland-data-control"], optional = true }
thiserror = "1.0.63"

[target.'cfg(windows)'.dependencies]
crossterm = "0.27.0"
Expand Down
35 changes: 16 additions & 19 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ extern crate csv_sniffer;

use crate::csv;
use crate::delimiter::{sniff_delimiter, Delimiter};
use crate::errors::{CsvlensError, CsvlensResult};
use crate::find;
use crate::help;
use crate::input::{Control, InputHandler};
use crate::sort::{self, SortOrder, SorterStatus};
use crate::ui::{CsvTable, CsvTableState, FilterColumnsState, FinderState};
use crate::view;

use anyhow::ensure;
#[cfg(feature = "clipboard")]
use arboard::Clipboard;
use ratatui::backend::Backend;
use ratatui::{Frame, Terminal};

use anyhow::{Context, Result};
use anyhow::Result;
use regex::Regex;
use std::cmp::min;
use std::sync::Arc;
Expand Down Expand Up @@ -160,7 +160,7 @@ impl App {
columns_regex: Option<String>,
filter_regex: Option<String>,
find_regex: Option<String>,
) -> Result<Self> {
) -> CsvlensResult<Self> {
let input_handler = InputHandler::new();

// Some lines are reserved for plotting headers (3 lines for headers + 2 lines for status bar)
Expand All @@ -177,15 +177,11 @@ impl App {
let config = csv::CsvConfig::new(filename, delimiter, no_headers);
let shared_config = Arc::new(config);

let csvlens_reader = csv::CsvLensReader::new(shared_config.clone())
.context(format!("Failed to open file: {filename}"))?;
let csvlens_reader = csv::CsvLensReader::new(shared_config.clone())?;
let rows_view = view::RowsView::new(csvlens_reader, num_rows as u64)?;

if let Some(column_name) = &echo_column {
ensure!(
rows_view.headers().iter().any(|h| h.name == *column_name),
format!("Column name not found: {column_name}"),
);
return Err(CsvlensError::ColumnNameNotFound(column_name.clone()));
}

let csv_table_state = CsvTableState::new(
Expand Down Expand Up @@ -244,7 +240,10 @@ impl App {
Ok(app)
}

pub fn main_loop<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<Option<String>> {
pub fn main_loop<B: Backend>(
&mut self,
terminal: &mut Terminal<B>,
) -> CsvlensResult<Option<String>> {
loop {
let control = self.input_handler.next();
if matches!(control, Control::Quit) {
Expand Down Expand Up @@ -273,7 +272,7 @@ impl App {
}
}

fn step_help(&mut self, control: &Control) -> Result<()> {
fn step_help(&mut self, control: &Control) -> CsvlensResult<()> {
match &control {
Control::ScrollDown => {
self.help_page_state.scroll_down();
Expand All @@ -286,7 +285,7 @@ impl App {
Ok(())
}

fn step(&mut self, control: &Control) -> Result<()> {
fn step(&mut self, control: &Control) -> CsvlensResult<()> {
if self.help_page_state.is_active() {
return self.step_help(control);
}
Expand Down Expand Up @@ -805,12 +804,10 @@ impl App {
}
}

fn draw<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
terminal
.draw(|f| {
self.render_frame(f);
})
.unwrap();
fn draw<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> CsvlensResult<()> {
terminal.draw(|f| {
self.render_frame(f);
})?;

Ok(())
}
Expand Down Expand Up @@ -853,7 +850,7 @@ mod tests {
}
}

fn build(self) -> Result<App> {
fn build(self) -> CsvlensResult<App> {
App::new(
self.filename.as_str(),
self.delimiter,
Expand Down
22 changes: 15 additions & 7 deletions src/csv.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
extern crate csv;

use anyhow::Result;
use csv::{Position, Reader, ReaderBuilder};
use std::cmp::max;
use std::fs::File;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time;

use crate::errors::CsvlensResult;

fn string_record_to_vec(record: &csv::StringRecord) -> Vec<String> {
let mut string_vec = Vec::new();
for field in record.iter() {
Expand All @@ -31,7 +32,7 @@ impl CsvConfig {
}
}

pub fn new_reader(&self) -> Result<Reader<File>> {
pub fn new_reader(&self) -> CsvlensResult<Reader<File>> {
let reader = ReaderBuilder::new()
.flexible(true)
.delimiter(self.delimiter)
Expand Down Expand Up @@ -118,7 +119,7 @@ struct GetRowIndex {
}

impl CsvLensReader {
pub fn new(config: Arc<CsvConfig>) -> Result<Self> {
pub fn new(config: Arc<CsvConfig>) -> CsvlensResult<Self> {
let mut reader = config.new_reader()?;

let headers_record = if config.no_headers() {
Expand All @@ -143,16 +144,23 @@ impl CsvLensReader {
Ok(reader)
}

pub fn get_rows(&mut self, rows_from: u64, num_rows: u64) -> Result<(Vec<Row>, GetRowsStats)> {
pub fn get_rows(
&mut self,
rows_from: u64,
num_rows: u64,
) -> CsvlensResult<(Vec<Row>, GetRowsStats)> {
let indices: Vec<u64> = (rows_from..rows_from + num_rows).collect();
self.get_rows_impl(&indices)
}

pub fn get_rows_for_indices(&mut self, indices: &[u64]) -> Result<(Vec<Row>, GetRowsStats)> {
pub fn get_rows_for_indices(
&mut self,
indices: &[u64],
) -> CsvlensResult<(Vec<Row>, GetRowsStats)> {
self.get_rows_impl(indices)
}

fn get_rows_impl(&mut self, indices: &[u64]) -> Result<(Vec<Row>, GetRowsStats)> {
fn get_rows_impl(&mut self, indices: &[u64]) -> CsvlensResult<(Vec<Row>, GetRowsStats)> {
let mut get_row_indices = indices
.iter()
.enumerate()
Expand All @@ -168,7 +176,7 @@ impl CsvLensReader {
fn _get_rows_impl_sorted(
&mut self,
indices: &[GetRowIndex],
) -> Result<(Vec<Row>, GetRowsStats)> {
) -> CsvlensResult<(Vec<Row>, GetRowsStats)> {
// stats for debugging and testing
let mut stats = GetRowsStats::new();

Expand Down
18 changes: 7 additions & 11 deletions src/delimiter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use crate::errors::{CsvlensError, CsvlensResult};

/// Delimiter behaviour as specified in the command line
pub enum Delimiter {
Expand All @@ -14,7 +14,7 @@ pub enum Delimiter {

impl Delimiter {
/// Create a Delimiter by parsing the command line argument for the delimiter
pub fn from_arg(delimiter_arg: &Option<String>, tab_separation: bool) -> Result<Self> {
pub fn from_arg(delimiter_arg: &Option<String>, tab_separation: bool) -> CsvlensResult<Self> {
if tab_separation {
return Ok(Delimiter::Character('\t'.try_into()?));
}
Expand All @@ -27,18 +27,14 @@ impl Delimiter {
return Ok(Delimiter::Character(b'\t'));
}
let mut chars = s.chars();
let c = chars.next().context("Delimiter should not be empty")?;
let c = chars
.next()
.ok_or_else(|| CsvlensError::DelimiterEmptyError)?;
if !c.is_ascii() {
bail!(
"Delimiter should be within the ASCII range: {} is too fancy",
c
);
return Err(CsvlensError::DelimiterNotAsciiError(c));
}
if chars.next().is_some() {
bail!(
"Delimiter should be exactly one character (or \\t), got '{}'",
s
);
return Err(CsvlensError::DelimiterMultipleCharactersError(s.clone()));
}
Ok(Delimiter::Character(c.try_into()?))
} else {
Expand Down
31 changes: 31 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use thiserror::Error;

pub type CsvlensResult<T> = std::result::Result<T, CsvlensError>;

/// Errors csvlens can have
#[derive(Debug, Error)]
pub enum CsvlensError {
#[error("Failed to read file: {0}")]
FileReadError(String),

#[error("Column name not found: {0}")]
ColumnNameNotFound(String),

#[error("Delimiter should not be empty")]
DelimiterEmptyError,

#[error("Delimiter should be within the ASCII range: {0} is too fancy")]
DelimiterNotAsciiError(char),

#[error("Delimiter should be exactly one character (or \\t), got '{0}'")]
DelimiterMultipleCharactersError(String),

#[error(transparent)]
DelimiterParseError(#[from] std::char::TryFromCharError),

#[error(transparent)]
CsvError(#[from] csv::Error),

#[error(transparent)]
Io(#[from] std::io::Error),
}
11 changes: 6 additions & 5 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use anyhow::{Context, Result};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use tempfile::NamedTempFile;

use crate::errors::{CsvlensError, CsvlensResult};

pub struct SeekableFile {
filename: Option<String>,
inner_file: Option<NamedTempFile>,
}

impl SeekableFile {
pub fn new(maybe_filename: &Option<String>) -> Result<SeekableFile> {
pub fn new(maybe_filename: &Option<String>) -> CsvlensResult<SeekableFile> {
let mut inner_file = NamedTempFile::new()?;
let inner_file_res;

if let Some(filename) = maybe_filename {
let err = format!("Failed to open file: {filename}");
let mut f = File::open(filename).context(err)?;
let mut f =
File::open(filename).map_err(|_| CsvlensError::FileReadError(filename.clone()))?;
// If not seekable, it most likely is due to process substitution using
// pipe - write out to a temp file to make it seekable
if f.seek(SeekFrom::Start(0)).is_err() {
Expand Down Expand Up @@ -46,7 +47,7 @@ impl SeekableFile {
}
}

fn chunked_copy<R: Read, W: Write>(source: &mut R, dest: &mut W) -> Result<usize> {
fn chunked_copy<R: Read, W: Write>(source: &mut R, dest: &mut W) -> CsvlensResult<usize> {
let mut total_copied = 0;
let mut buffer = vec![0; 1_000_000];
loop {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod app;
mod common;
mod csv;
mod delimiter;
pub mod errors;
mod find;
mod help;
mod history;
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fn main() {
let args_itr = std::env::args_os().skip(1);
match run_csvlens(args_itr) {
Err(e) => {
println!("{e:?}");
println!("{e:#}");
std::process::exit(1);
}
Ok(Some(selection)) => {
Expand Down
6 changes: 3 additions & 3 deletions src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::app::App;
use crate::delimiter::Delimiter;
use crate::errors::CsvlensResult;
use crate::io::SeekableFile;

use anyhow::Result;
use clap::{command, Parser};
use crossterm::execute;
use crossterm::terminal::{
Expand Down Expand Up @@ -76,7 +76,7 @@ impl AppRunner {
AppRunner { app }
}

fn run(&mut self) -> Result<Option<String>> {
fn run(&mut self) -> CsvlensResult<Option<String>> {
enable_raw_mode()?;
let mut output = std::io::stderr();
execute!(output, EnterAlternateScreen)?;
Expand Down Expand Up @@ -117,7 +117,7 @@ impl Drop for AppRunner {
/// Err(e) => eprintln!("Error: {:?}", e),
/// }
/// ```
pub fn run_csvlens<I, T>(args: I) -> Result<Option<String>>
pub fn run_csvlens<I, T>(args: I) -> CsvlensResult<Option<String>>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
Expand Down
Loading

0 comments on commit 67c1afd

Please sign in to comment.