mirror of
https://github.com/MalteT/mensa.git
synced 2024-10-22 21:59:17 +02:00
Fix #15, don't panic on closed pipes
Logging will now use stderr instead of stdout.
This commit is contained in:
parent
13ed33f51f
commit
485676095e
|
@ -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
15
src/cache/dummy.rs
vendored
|
@ -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
2
src/cache/tests.rs
vendored
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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(())
|
||||
|
|
50
src/main.rs
50
src/main.rs
|
@ -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()?;
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
10
src/tag.rs
10
src/tag.rs
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue