mirror of
https://github.com/MalteT/mensa.git
synced 2024-10-22 21:59:17 +02:00
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:
parent
b5171922f6
commit
fcf81a71db
|
@ -7,7 +7,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
meal::tag::Tag,
|
tag::Tag,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::PriceTags;
|
use super::PriceTags;
|
||||||
|
@ -40,6 +40,10 @@ pub struct Args {
|
||||||
)]
|
)]
|
||||||
pub color: ColorWhen,
|
pub color: ColorWhen,
|
||||||
|
|
||||||
|
/// Print plain json. Useful for shell scripts.
|
||||||
|
#[structopt(long, global = true, takes_value = false)]
|
||||||
|
pub json: bool,
|
||||||
|
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
pub command: Option<Command>,
|
pub command: Option<Command>,
|
||||||
}
|
}
|
||||||
|
@ -92,7 +96,11 @@ pub struct MealsCommand {
|
||||||
pub canteen_id: Option<usize>,
|
pub canteen_id: Option<usize>,
|
||||||
|
|
||||||
/// Specify which price tags should be displayed
|
/// 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>>,
|
pub price: Option<Vec<PriceTags>>,
|
||||||
|
|
||||||
#[structopt(long, env = "MENSA_OVERWRITE_FILTER", takes_value = false)]
|
#[structopt(long, env = "MENSA_OVERWRITE_FILTER", takes_value = false)]
|
||||||
|
|
|
@ -4,7 +4,8 @@ use std::convert::TryFrom;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
meal::{tag::Tag, Meal},
|
meal::Meal,
|
||||||
|
tag::Tag,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -109,15 +109,18 @@ mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod geoip;
|
mod geoip;
|
||||||
mod meal;
|
mod meal;
|
||||||
|
mod tag;
|
||||||
|
|
||||||
use config::{
|
use crate::{
|
||||||
args::{parse_human_date, Args, MealsCommand},
|
canteen::Canteen,
|
||||||
State,
|
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 ENDPOINT: &str = "https://openmensa.org/api/v2";
|
||||||
const MIN_TERM_WIDTH: usize = 20;
|
const MIN_TERM_WIDTH: usize = 20;
|
||||||
|
@ -152,7 +155,7 @@ fn real_main() -> Result<()> {
|
||||||
Some(Command::Meals(cmd)) => {
|
Some(Command::Meals(cmd)) => {
|
||||||
let state = State::from(state, cmd);
|
let state = State::from(state, cmd);
|
||||||
let meals = Meal::fetch(&state)?;
|
let meals = Meal::fetch(&state)?;
|
||||||
Meal::print_all(&state, &meals);
|
Meal::print_all(&state, &meals)?;
|
||||||
}
|
}
|
||||||
Some(Command::Tomorrow(cmd)) => {
|
Some(Command::Tomorrow(cmd)) => {
|
||||||
// Works like the meals command. But we replace the date with tomorrow!
|
// 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();
|
cmd.date = parse_human_date("tomorrow").unwrap();
|
||||||
let state = State::from(state, &cmd);
|
let state = State::from(state, &cmd);
|
||||||
let meals = Meal::fetch(&state)?;
|
let meals = Meal::fetch(&state)?;
|
||||||
Meal::print_all(&state, &meals);
|
Meal::print_all(&state, &meals)?;
|
||||||
}
|
}
|
||||||
Some(Command::Canteens(cmd)) => {
|
Some(Command::Canteens(cmd)) => {
|
||||||
let state = State::from(state, cmd);
|
let state = State::from(state, cmd);
|
||||||
|
@ -172,7 +175,7 @@ fn real_main() -> Result<()> {
|
||||||
let cmd = MealsCommand::default();
|
let cmd = MealsCommand::default();
|
||||||
let state = State::from(state, &cmd);
|
let state = State::from(state, &cmd);
|
||||||
let meals = Meal::fetch(&state)?;
|
let meals = Meal::fetch(&state)?;
|
||||||
Meal::print_all(&state, &meals);
|
Meal::print_all(&state, &meals)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,8 +2,7 @@ use chrono::Duration;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use owo_colors::{OwoColorize, Stream};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::Deserialize;
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
@ -11,14 +10,12 @@ 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, Result},
|
error::{pass_info, Error, Result},
|
||||||
get_sane_terminal_dimensions, State, ENDPOINT,
|
get_sane_terminal_dimensions,
|
||||||
|
tag::Tag,
|
||||||
|
State, ENDPOINT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod tag;
|
|
||||||
|
|
||||||
use self::tag::Tag;
|
|
||||||
|
|
||||||
const NAME_PRE: &str = " ╭───╴";
|
const NAME_PRE: &str = " ╭───╴";
|
||||||
const NAME_PRE_PLAIN: &str = " - ";
|
const NAME_PRE_PLAIN: &str = " - ";
|
||||||
const NAME_CONTINUE_PRE: &str = " ┊ ";
|
const NAME_CONTINUE_PRE: &str = " ┊ ";
|
||||||
|
@ -36,9 +33,10 @@ lazy_static! {
|
||||||
static ref TTL_MEALS: Duration = Duration::hours(1);
|
static ref TTL_MEALS: Duration = Duration::hours(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(from = "RawMeal")]
|
#[serde(from = "RawMeal")]
|
||||||
pub struct Meal {
|
pub struct Meal {
|
||||||
|
#[serde(rename = "id")]
|
||||||
pub _id: usize,
|
pub _id: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub tags: HashSet<Tag>,
|
pub tags: HashSet<Tag>,
|
||||||
|
@ -57,7 +55,7 @@ struct RawMeal {
|
||||||
category: String,
|
category: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(debug, serde(deny_unknown_fields))]
|
#[cfg_attr(debug, serde(deny_unknown_fields))]
|
||||||
pub struct Prices {
|
pub struct Prices {
|
||||||
students: f32,
|
students: f32,
|
||||||
|
@ -123,17 +121,24 @@ impl Meal {
|
||||||
/// Print the given meals.
|
/// Print the given meals.
|
||||||
///
|
///
|
||||||
/// Thi will respect passed cli arguments and the configuration.
|
/// 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.
|
// Load the filter which is used to select which meals to print.
|
||||||
let filter = state.get_filter();
|
let filter = state.get_filter();
|
||||||
// Load the favourites which will be used for marking meals.
|
// Load the favourites which will be used for marking meals.
|
||||||
let favs = state.get_favs_rule();
|
let favs = state.get_favs_rule();
|
||||||
for meal in meals {
|
let meals = meals.iter().filter(|meal| filter.is_match(meal));
|
||||||
if 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 {
|
||||||
let is_fav = favs.is_match(meal);
|
let is_fav = favs.is_match(meal);
|
||||||
println!();
|
println!();
|
||||||
meal.print(state, is_fav);
|
meal.print(state, is_fav);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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;
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{Display, EnumIter};
|
use strum::{Display, EnumIter};
|
||||||
|
|
||||||
use crate::config::State;
|
use crate::config::State;
|
||||||
|
@ -38,6 +38,9 @@ lazy_static! {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A tag describing a meal.
|
||||||
|
///
|
||||||
|
/// Contains allergy information, descriptions and categories.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
|
@ -49,6 +52,7 @@ lazy_static! {
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
IntoPrimitive,
|
IntoPrimitive,
|
||||||
TryFromPrimitive,
|
TryFromPrimitive,
|
||||||
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
EnumIter,
|
EnumIter,
|
||||||
Display,
|
Display,
|
||||||
|
@ -84,6 +88,7 @@ pub enum Tag {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
|
/// Try deriving [`Tag`]s from the `raw` tag.
|
||||||
pub fn parse_str(raw: &str) -> Vec<Self> {
|
pub fn parse_str(raw: &str) -> Vec<Self> {
|
||||||
TAG_RE
|
TAG_RE
|
||||||
.matches(raw)
|
.matches(raw)
|
||||||
|
@ -92,6 +97,9 @@ impl Tag {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this a primary tag?
|
||||||
|
///
|
||||||
|
/// Primary tags have an associated emoji and are not allergy information.
|
||||||
pub fn is_primary(&self) -> bool {
|
pub fn is_primary(&self) -> bool {
|
||||||
use Tag::*;
|
use Tag::*;
|
||||||
match self {
|
match self {
|
||||||
|
@ -102,10 +110,15 @@ impl Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this **not** a primary tag?
|
||||||
pub fn is_secondary(&self) -> bool {
|
pub fn is_secondary(&self) -> bool {
|
||||||
!self.is_primary()
|
!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 {
|
pub fn describe(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Alcohol => "Contains alcohol",
|
Self::Alcohol => "Contains alcohol",
|
||||||
|
@ -140,7 +153,8 @@ impl Tag {
|
||||||
|
|
||||||
/// This formats an identifier for this 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 {
|
pub fn as_id<Cmd>(&self, state: &State<Cmd>) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Vegan => if_plain!(state: "🌱".into(), "Vegan".into()),
|
Self::Vegan => if_plain!(state: "🌱".into(), "Vegan".into()),
|
Loading…
Reference in a new issue