Fix #15, don't panic on closed pipes

Logging will now use stderr instead of stdout.
This commit is contained in:
Malte Tammena 2021-10-27 15:03:04 +02:00
parent 13ed33f51f
commit 485676095e
8 changed files with 76 additions and 68 deletions

View file

@ -9,7 +9,7 @@
# If omitted, `$XDG_CONFIG_HOME/mensa/config.toml` or
# `$HOME/.config/mensa/config.toml` (if $XDG_CONFIG_HOME is unset)
# are checked.
#
#
# All options can also be specified on the command line or in the environment
# 1) CLI flags take precedence over
# 2) ENVIRONMENT VARIABLES, which overwrite

15
src/cache/dummy.rs vendored
View file

@ -38,10 +38,6 @@ impl Cache for DummyCache {
let entry = read
.get(&path)
.expect("BUG: Metadata exists, but entry does not!");
eprintln!(
"Cache read for {:?}\n-> Returning: {:#?}",
meta.integrity, entry.text
);
Ok(entry.text.clone())
}
@ -56,10 +52,6 @@ impl Cache for DummyCache {
text: text.to_owned(),
},
);
eprintln!(
"Cache write to {:?}\n-> Headers: {:#?}\n-> Content: {:#?}",
url, headers, text
);
Ok(())
}
@ -67,7 +59,6 @@ impl Cache for DummyCache {
let hash = path_from_key(url);
let read = self.content.read().expect("Reading cache failed");
let entry = read.get(&hash);
eprintln!("Cache Metadata for {:?}: {:?}", url, entry);
match entry {
Some(entry) => Ok(Some(clone_metadata(&entry.meta))),
None => Ok(None),
@ -76,7 +67,6 @@ impl Cache for DummyCache {
fn clear(&self) -> Result<()> {
self.content.write().expect("Writing cache failed").clear();
eprintln!("Cache cleared");
Ok(())
}
@ -92,9 +82,7 @@ impl Cache for DummyCache {
fn path_from_key(key: &str) -> String {
let integrity = Integrity::from(key);
let path = path_from_integrity(&integrity);
eprintln!("Hashing key {:?} -> {:#?}", key, path);
path
path_from_integrity(&integrity)
}
fn path_from_integrity(integrity: &Integrity) -> String {
@ -102,7 +90,6 @@ fn path_from_integrity(integrity: &Integrity) -> String {
let (algorithm, digest) = integrity.to_hex();
path += &algorithm.to_string();
path += &digest;
eprintln!("Hashing integrity {:?} -> {:#?}", integrity, path);
path
}

2
src/cache/tests.rs vendored
View file

@ -14,7 +14,7 @@ fn print_cache_list(header: &'static str) -> Result<()> {
CACHE.list()?.iter().for_each(|meta| {
let age_ms = meta.time;
let cache_age = chrono::Utc.timestamp((age_ms / 1000) as i64, (age_ms % 1000) as u32);
eprintln!(
println!(
"| - {}\n| SIZE: {}\n| AGE: {}",
meta.key, meta.size, cache_age
)

View file

@ -99,13 +99,12 @@ impl Canteen {
.initial_indent(ADRESS_INDENT)
.subsequent_indent(ADRESS_INDENT),
);
println!(
try_println!(
"{} {}\n{}",
color!(format!("{:>4}", self.id); bold, bright_yellow),
color!(self.meta()?.name; bold),
color!(address; bright_black),
);
Ok(())
)
}
pub fn id(&self) -> CanteenId {
@ -132,7 +131,7 @@ impl Canteen {
Self::print_all_json(canteens)
} else {
for canteen in canteens {
println!();
try_println!()?;
canteen.print()?;
}
Ok(())

View file

@ -57,7 +57,7 @@
//!
//! ### Examples
//!
//! ####
//! ####
//! <details>
//! <summary><b>Meals on monday</b> (<i>Click me!</i>)</summary>
//!
@ -70,7 +70,7 @@
//! ┊
//! ┊ ╭───╴Bohnengemüse
//! ┊ ├─╴Gemüsebeilage 🌱
//! ┊ ╰╴( 0.55€ )
//! ┊ ╰╴( 0.55€ )
//! ...
//! ```
//! </details>
@ -95,13 +95,13 @@
//!
//! ```console
//! $ mensa tags
//!
//!
//! 0 Acidifier
//! Contains artificial acidifier
//!
//!
//! 1 Alcohol
//! Contains alcohol
//!
//!
//! 2 Antioxidant
//! Contains an antioxidant
//! ...
@ -113,7 +113,7 @@
//!
//! ```console
//! $ mensa meals close --date sun
//!
//!
//! Leipzig, Cafeteria Dittrichring
//! ┊
//! ┊ ╭───╴Vegetarisch gefüllte Zucchini
@ -121,7 +121,7 @@
//! ┊ ├╴Rucola-Kartoffelpüree
//! ┊ ├╴Tomaten-Ratatouille-Soße
//! ┊ ╰╴( 2.65€ ) 2 11 12 19
//!
//!
//! Leipzig, Mensa am Park
//! ┊
//! ┊ ╭───╴Apfelrotkohl
@ -147,12 +147,14 @@
//! - `$HOME/Library/Application Support/mensa/config.toml` on **macOS**,
//! - `{FOLDERID_RoamingAppData}\mensa\config.toml` on **Windows**
use std::io;
use cache::Cache;
use chrono::Duration;
use directories_next::ProjectDirs;
use lazy_static::lazy_static;
use serde::Serialize;
use tracing::error;
use tracing::{error, info};
/// Colorizes the output.
///
@ -211,6 +213,18 @@ macro_rules! if_plain {
};
}
/// Safer `println` which doesn't panic, but errors.
macro_rules! try_println {
() => {
try_println!("\n")
};
($str:literal $(, $args:expr )* $(,)?) => ({
use std::io::Write;
writeln!(::std::io::stdout(), $str, $( $args ),* )
.map_err(|why| crate::error::Error::Io(why, "printing"))
})
}
mod cache;
mod canteen;
mod config;
@ -240,17 +254,25 @@ lazy_static! {
}
fn main() -> Result<()> {
let res = real_main();
match res {
Ok(_) => {}
Err(ref why) => error!("{}", why),
match real_main() {
Ok(_) => Ok(()),
// Ignore broken pipe errors, but log them
Err(Error::Io(err, _)) if err.kind() == io::ErrorKind::BrokenPipe => {
info!("Pipe was closed");
Ok(())
}
Err(why) => {
error!("{}", why);
Err(why)
}
}
res
}
fn real_main() -> Result<()> {
// Initialize logger
tracing_subscriber::fmt::init();
tracing_subscriber::FmtSubscriber::builder()
.with_writer(::std::io::stderr)
.init();
// Clear cache if requested
if CONF.args.clear_cache {
CACHE.clear()?;

View file

@ -5,7 +5,7 @@ use lazy_static::lazy_static;
use serde::Serialize;
use unicode_width::UnicodeWidthStr;
use crate::get_sane_terminal_dimensions;
use crate::{error::Result, get_sane_terminal_dimensions};
use super::{MealId, Meta, PRE};
@ -27,39 +27,40 @@ pub struct MealComplete<'c> {
impl<'c> MealComplete<'c> {
/// Print this [`MealComplete`] to the terminal.
pub fn print(&self, highlight: bool) {
pub fn print(&self, highlight: bool) -> Result<()> {
let (width, _height) = get_sane_terminal_dimensions();
// Print meal name
self.print_name_to_terminal(width, highlight);
self.print_name_to_terminal(width, highlight)?;
// Get notes, i.e. allergenes, descriptions, tags
self.print_category_and_primary_tags(highlight);
self.print_descriptions(width, highlight);
self.print_price_and_secondary_tags(highlight);
self.print_category_and_primary_tags(highlight)?;
self.print_descriptions(width, highlight)?;
self.print_price_and_secondary_tags(highlight)
}
fn print_name_to_terminal(&self, width: usize, highlight: bool) {
fn print_name_to_terminal(&self, width: usize, highlight: bool) -> Result<()> {
let max_name_width = width - NAME_PRE.width() - PRE.width();
let mut name_parts = textwrap::wrap(&self.meta.name, max_name_width).into_iter();
// There will always be a first part of the splitted string
let first_name_part = name_parts.next().unwrap();
println!(
try_println!(
"{}{}{}",
*PRE,
hl_if(highlight, *NAME_PRE),
color!(hl_if(highlight, first_name_part); bold),
);
)?;
for name_part in name_parts {
let name_part = hl_if(highlight, name_part);
println!(
try_println!(
"{}{}{}",
*PRE,
hl_if(highlight, *NAME_CONTINUE_PRE),
color!(name_part; bold),
);
)?;
}
Ok(())
}
fn print_category_and_primary_tags(&self, highlight: bool) {
fn print_category_and_primary_tags(&self, highlight: bool) -> Result<()> {
let mut tag_str = self
.meta
.tags
@ -69,39 +70,40 @@ impl<'c> MealComplete<'c> {
let tag_str_colored =
if_plain!(color!(tag_str.join(" "); bright_black), tag_str.join(", "));
let comma_if_plain = if_plain!("", ",");
println!(
try_println!(
"{}{}{}{} {}",
*PRE,
hl_if(highlight, *CATEGORY_PRE),
color!(self.meta.category; bright_blue),
color!(comma_if_plain; bright_black),
tag_str_colored
);
)
}
fn print_descriptions(&self, width: usize, highlight: bool) {
fn print_descriptions(&self, width: usize, highlight: bool) -> Result<()> {
let max_note_width = width - OTHER_NOTE_PRE.width() - PRE.width();
for note in &self.meta.descs {
let mut note_parts = textwrap::wrap(note, max_note_width).into_iter();
// There will always be a first part in the splitted string
println!(
try_println!(
"{}{}{}",
*PRE,
hl_if(highlight, *OTHER_NOTE_PRE),
note_parts.next().unwrap()
);
)?;
for part in note_parts {
println!(
try_println!(
"{}{}{}",
*PRE,
hl_if(highlight, *OTHER_NOTE_CONTINUE_PRE),
part
);
)?;
}
}
Ok(())
}
fn print_price_and_secondary_tags(&self, highlight: bool) {
fn print_price_and_secondary_tags(&self, highlight: bool) -> Result<()> {
let prices = self.meta.prices.to_terminal_string();
let mut secondary: Vec<_> = self
.meta
@ -111,13 +113,13 @@ impl<'c> MealComplete<'c> {
.collect();
secondary.sort_unstable();
let secondary_str = secondary.iter().map(|tag| tag.as_id()).join(" ");
println!(
try_println!(
"{}{}{} {}",
*PRE,
hl_if(highlight, *PRICES_PRE),
prices,
color!(secondary_str; bright_black),
);
)
}
}

View file

@ -96,7 +96,7 @@ impl Meal {
let day = CONF.date();
for canteen in canteens {
let name = canteen.name()?;
println!("\n {}", color!(name; bright_black));
try_println!("\n {}", color!(name; bright_black))?;
match canteen.meals_at_mut(day)? {
Some(meals) => {
let mut printed_at_least_one_meal = false;
@ -104,18 +104,16 @@ impl Meal {
let complete = meal.complete()?;
if filter.is_match(&complete) {
let is_fav = favs.is_non_empty_match(&complete);
println!("{}", *PRE);
complete.print(is_fav);
try_println!("{}", *PRE)?;
complete.print(is_fav)?;
printed_at_least_one_meal = true;
}
}
if !printed_at_least_one_meal {
println!("{} {}", *PRE, color!("no matching meals found"; dimmed));
try_println!("{} {}", *PRE, color!("no matching meals found"; dimmed))?
}
}
None => {
println!("{} {}", *PRE, color!("closed"; dimmed))
}
None => try_println!("{} {}", *PRE, color!("closed"; dimmed))?,
}
}
Ok(())

View file

@ -212,7 +212,7 @@ impl Tag {
/// Print this tag.
///
/// Does **not** respect `--json`, use [`Self::print_all`].
pub fn print(&self) {
pub fn print(&self) -> Result<()> {
let emoji = if CONF.args.plain && self.is_primary() {
format!("{:>width$}", "-", width = ID_WIDTH)
} else {
@ -231,12 +231,12 @@ impl Tag {
.initial_indent(TEXT_INDENT)
.subsequent_indent(TEXT_INDENT),
);
println!(
try_println!(
"{} {}\n{}",
color!(emoji; bright_yellow, bold),
color!(self; bold),
color!(description; bright_black),
);
)
}
/// Print all tags.
@ -245,8 +245,8 @@ impl Tag {
Self::print_all_json()
} else {
for tag in Tag::iter() {
println!();
tag.print();
try_println!()?;
tag.print()?;
}
Ok(())
}