Respect --json in mensa tags

See #4.
This commit is contained in:
Malte Tammena 2021-10-20 20:35:08 +02:00
parent fcf81a71db
commit ab9bd9801c
3 changed files with 87 additions and 43 deletions

View file

@ -63,10 +63,9 @@
use chrono::Duration; use chrono::Duration;
use directories_next::ProjectDirs; use directories_next::ProjectDirs;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::Serialize;
use structopt::StructOpt; use structopt::StructOpt;
use strum::IntoEnumIterator;
use tracing::error; use tracing::error;
use unicode_width::UnicodeWidthStr;
/// Colorizes the output. /// Colorizes the output.
/// ///
@ -170,7 +169,9 @@ fn real_main() -> Result<()> {
let canteens = Canteen::fetch(&state)?; let canteens = Canteen::fetch(&state)?;
Canteen::print_all(&state, &canteens); Canteen::print_all(&state, &canteens);
} }
Some(Command::Tags) => print_tags(&state), Some(Command::Tags) => {
Tag::print_all(&state)?;
}
None => { None => {
let cmd = MealsCommand::default(); let cmd = MealsCommand::default();
let state = State::from(state, &cmd); let state = State::from(state, &cmd);
@ -181,38 +182,6 @@ fn real_main() -> Result<()> {
Ok(()) Ok(())
} }
fn print_tags<Cmd>(state: &State<Cmd>) {
for tag in Tag::iter() {
println!();
const ID_WIDTH: usize = 4;
const TEXT_INDENT: &str = " ";
let emoji = if state.args.plain && tag.is_primary() {
format!("{:>width$}", "-", width = ID_WIDTH)
} else {
let emoji = tag.as_id(state);
let emoji_len = emoji.width();
format!(
"{}{}",
" ".repeat(ID_WIDTH.saturating_sub(emoji_len)),
emoji
)
};
let description_width = get_sane_terminal_dimensions().0;
let description = textwrap::fill(
tag.describe(),
textwrap::Options::new(description_width)
.initial_indent(TEXT_INDENT)
.subsequent_indent(TEXT_INDENT),
);
println!(
"{} {}\n{}",
color!(state: emoji; bright_yellow, bold),
color!(state: tag; bold),
color!(state: description; bright_black),
);
}
}
fn get_sane_terminal_dimensions() -> (usize, usize) { fn get_sane_terminal_dimensions() -> (usize, usize) {
terminal_size::terminal_size() terminal_size::terminal_size()
.map(|(w, h)| (w.0 as usize, h.0 as usize)) .map(|(w, h)| (w.0 as usize, h.0 as usize))
@ -221,3 +190,10 @@ fn get_sane_terminal_dimensions() -> (usize, usize) {
.log_warn() .log_warn()
.unwrap_or((80, 80)) .unwrap_or((80, 80))
} }
fn print_json<T: Serialize>(value: &T) -> Result<()> {
let stdout = std::io::stdout();
let output = stdout.lock();
serde_json::to_writer_pretty(output, value)
.map_err(|why| Error::Serializing(why, "writing meals as json"))
}

View file

@ -10,8 +10,8 @@ use std::collections::HashSet;
use crate::{ use crate::{
cache::fetch_json, cache::fetch_json,
config::{args::MealsCommand, MealsState, PriceTags}, config::{args::MealsCommand, MealsState, PriceTags},
error::{pass_info, Error, Result}, error::{pass_info, Result},
get_sane_terminal_dimensions, get_sane_terminal_dimensions, print_json,
tag::Tag, tag::Tag,
State, ENDPOINT, State, ENDPOINT,
}; };
@ -128,10 +128,7 @@ impl Meal {
let favs = state.get_favs_rule(); let favs = state.get_favs_rule();
let meals = meals.iter().filter(|meal| filter.is_match(meal)); let meals = meals.iter().filter(|meal| filter.is_match(meal));
if state.args.json { if state.args.json {
let stdout = std::io::stdout(); print_json(&meals.collect::<Vec<_>>())
let output = stdout.lock();
serde_json::to_writer_pretty(output, &meals.collect::<Vec<_>>())
.map_err(|why| Error::Serializing(why, "writing meals as json"))
} else { } else {
for meal in meals { for meal in meals {
let is_fav = favs.is_match(meal); let is_fav = favs.is_match(meal);

View file

@ -1,10 +1,16 @@
use std::collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use regex::RegexSet; use regex::RegexSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter}; use strum::{Display, EnumIter, IntoEnumIterator};
use unicode_width::UnicodeWidthStr;
use crate::config::State; use crate::{config::State, error::Result, get_sane_terminal_dimensions, print_json};
const ID_WIDTH: usize = 4;
const TEXT_INDENT: &str = " ";
lazy_static! { lazy_static! {
/// These must have the same order as the variants in the [`Tag`] enum. /// These must have the same order as the variants in the [`Tag`] enum.
@ -170,4 +176,69 @@ impl Tag {
} }
} }
} }
/// Print this tag.
///
/// Does **not** respect `--json`, use [`Self::print_all`].
pub fn print<Cmd>(&self, state: &State<Cmd>) {
let emoji = if state.args.plain && self.is_primary() {
format!("{:>width$}", "-", width = ID_WIDTH)
} else {
let emoji = self.as_id(state);
let emoji_len = emoji.width();
format!(
"{}{}",
" ".repeat(ID_WIDTH.saturating_sub(emoji_len)),
emoji
)
};
let description_width = get_sane_terminal_dimensions().0;
let description = textwrap::fill(
self.describe(),
textwrap::Options::new(description_width)
.initial_indent(TEXT_INDENT)
.subsequent_indent(TEXT_INDENT),
);
println!(
"{} {}\n{}",
color!(state: emoji; bright_yellow, bold),
color!(state: self; bold),
color!(state: description; bright_black),
);
}
/// Print all tags.
pub fn print_all<Cmd>(state: &State<Cmd>) -> Result<()> {
if state.args.json {
Self::print_all_json(state)
} else {
for tag in Tag::iter() {
println!();
tag.print(state);
}
Ok(())
}
}
/// Print all tags as json.
///
/// This will result in a list of objects containing the following keys:
/// - id: An identifier, like 'Vegan' or '22'
/// - name: The name of the tag.
/// - desc: A simple description.
///
fn print_all_json<Cmd>(state: &State<Cmd>) -> Result<()> {
let tags: Vec<HashMap<&str, String>> = Tag::iter()
.map(|tag| {
vec![
("id", tag.as_id(state)),
("name", tag.to_string()),
("desc", tag.describe().to_owned()),
]
.into_iter()
.collect()
})
.collect();
print_json(&tags)
}
} }