Move tag.rs module, add basic --json

The `--json` flag is currently only respected by `mensa meals`.
Relates to #4.
This commit is contained in:
Malte Tammena 2021-10-20 10:50:02 +02:00
parent b5171922f6
commit fcf81a71db
5 changed files with 59 additions and 28 deletions

View file

@ -7,7 +7,7 @@ use std::path::PathBuf;
use crate::{
error::{Error, Result},
meal::tag::Tag,
tag::Tag,
};
use super::PriceTags;
@ -40,6 +40,10 @@ pub struct Args {
)]
pub color: ColorWhen,
/// Print plain json. Useful for shell scripts.
#[structopt(long, global = true, takes_value = false)]
pub json: bool,
#[structopt(subcommand)]
pub command: Option<Command>,
}
@ -92,7 +96,11 @@ pub struct MealsCommand {
pub canteen_id: Option<usize>,
/// Specify which price tags should be displayed
#[structopt(long, short, env = "MENSA_PRICES", possible_values = &PriceTags::variants())]
#[structopt(long,
short,
env = "MENSA_PRICES",
possible_values = &PriceTags::variants(),
case_insensitive = true)]
pub price: Option<Vec<PriceTags>>,
#[structopt(long, env = "MENSA_OVERWRITE_FILTER", takes_value = false)]

View file

@ -4,7 +4,8 @@ use std::convert::TryFrom;
use crate::{
error::{Error, Result},
meal::{tag::Tag, Meal},
meal::Meal,
tag::Tag,
};
#[derive(Debug, Clone, Default, Deserialize)]

View file

@ -109,15 +109,18 @@ mod config;
mod error;
mod geoip;
mod meal;
mod tag;
use config::{
args::{parse_human_date, Args, MealsCommand},
use crate::{
canteen::Canteen,
config::{
args::{parse_human_date, Args, Command, MealsCommand},
State,
},
error::{Error, Result, ResultExt},
meal::Meal,
tag::Tag,
};
use error::{Error, Result, ResultExt};
use meal::{tag::Tag, Meal};
use crate::{canteen::Canteen, config::args::Command};
const ENDPOINT: &str = "https://openmensa.org/api/v2";
const MIN_TERM_WIDTH: usize = 20;
@ -152,7 +155,7 @@ fn real_main() -> Result<()> {
Some(Command::Meals(cmd)) => {
let state = State::from(state, cmd);
let meals = Meal::fetch(&state)?;
Meal::print_all(&state, &meals);
Meal::print_all(&state, &meals)?;
}
Some(Command::Tomorrow(cmd)) => {
// Works like the meals command. But we replace the date with tomorrow!
@ -160,7 +163,7 @@ fn real_main() -> Result<()> {
cmd.date = parse_human_date("tomorrow").unwrap();
let state = State::from(state, &cmd);
let meals = Meal::fetch(&state)?;
Meal::print_all(&state, &meals);
Meal::print_all(&state, &meals)?;
}
Some(Command::Canteens(cmd)) => {
let state = State::from(state, cmd);
@ -172,7 +175,7 @@ fn real_main() -> Result<()> {
let cmd = MealsCommand::default();
let state = State::from(state, &cmd);
let meals = Meal::fetch(&state)?;
Meal::print_all(&state, &meals);
Meal::print_all(&state, &meals)?;
}
}
Ok(())

View file

@ -2,8 +2,7 @@ use chrono::Duration;
use core::fmt;
use itertools::Itertools;
use lazy_static::lazy_static;
use owo_colors::{OwoColorize, Stream};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthStr;
use std::collections::HashSet;
@ -11,14 +10,12 @@ use std::collections::HashSet;
use crate::{
cache::fetch_json,
config::{args::MealsCommand, MealsState, PriceTags},
error::{pass_info, Result},
get_sane_terminal_dimensions, State, ENDPOINT,
error::{pass_info, Error, Result},
get_sane_terminal_dimensions,
tag::Tag,
State, ENDPOINT,
};
pub mod tag;
use self::tag::Tag;
const NAME_PRE: &str = " ╭───╴";
const NAME_PRE_PLAIN: &str = " - ";
const NAME_CONTINUE_PRE: &str = "";
@ -36,9 +33,10 @@ lazy_static! {
static ref TTL_MEALS: Duration = Duration::hours(1);
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(from = "RawMeal")]
pub struct Meal {
#[serde(rename = "id")]
pub _id: usize,
pub name: String,
pub tags: HashSet<Tag>,
@ -57,7 +55,7 @@ struct RawMeal {
category: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(debug, serde(deny_unknown_fields))]
pub struct Prices {
students: f32,
@ -123,17 +121,24 @@ impl Meal {
/// Print the given meals.
///
/// Thi will respect passed cli arguments and the configuration.
pub fn print_all(state: &MealsState, meals: &[Self]) {
pub fn print_all(state: &MealsState, meals: &[Self]) -> Result<()> {
// Load the filter which is used to select which meals to print.
let filter = state.get_filter();
// Load the favourites which will be used for marking meals.
let favs = state.get_favs_rule();
let meals = meals.iter().filter(|meal| filter.is_match(meal));
if state.args.json {
let stdout = std::io::stdout();
let output = stdout.lock();
serde_json::to_writer_pretty(output, &meals.collect::<Vec<_>>())
.map_err(|why| Error::Serializing(why, "writing meals as json"))
} else {
for meal in meals {
if filter.is_match(meal) {
let is_fav = favs.is_match(meal);
println!();
meal.print(state, is_fav);
}
Ok(())
}
}

View file

@ -1,7 +1,7 @@
use lazy_static::lazy_static;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use regex::RegexSet;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter};
use crate::config::State;
@ -38,6 +38,9 @@ lazy_static! {
.unwrap();
}
/// A tag describing a meal.
///
/// Contains allergy information, descriptions and categories.
#[derive(
Debug,
Clone,
@ -49,6 +52,7 @@ lazy_static! {
PartialOrd,
IntoPrimitive,
TryFromPrimitive,
Serialize,
Deserialize,
EnumIter,
Display,
@ -84,6 +88,7 @@ pub enum Tag {
}
impl Tag {
/// Try deriving [`Tag`]s from the `raw` tag.
pub fn parse_str(raw: &str) -> Vec<Self> {
TAG_RE
.matches(raw)
@ -92,6 +97,9 @@ impl Tag {
.collect()
}
/// Is this a primary tag?
///
/// Primary tags have an associated emoji and are not allergy information.
pub fn is_primary(&self) -> bool {
use Tag::*;
match self {
@ -102,10 +110,15 @@ impl Tag {
}
}
/// Is this **not** a primary tag?
pub fn is_secondary(&self) -> bool {
!self.is_primary()
}
/// Describe this [`Tag`] with english words.
///
/// This should add information where the enum variant itself
/// does not suffice.
pub fn describe(&self) -> &'static str {
match self {
Self::Alcohol => "Contains alcohol",
@ -140,7 +153,8 @@ impl Tag {
/// This formats an identifier for this tag.
///
/// Will respect any settings given.
/// Will respect any settings given, i.e. emojis will be used
/// unless the output should be plain.
pub fn as_id<Cmd>(&self, state: &State<Cmd>) -> String {
match self {
Self::Vegan => if_plain!(state: "🌱".into(), "Vegan".into()),