MVP achieved.
This commit is contained in:
parent
25b64a6ca0
commit
390193db14
|
@ -80,6 +80,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
|
@ -372,6 +373,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
|
@ -420,6 +427,7 @@ dependencies = [
|
|||
"clap",
|
||||
"dirs",
|
||||
"html-escape",
|
||||
"lazy_static",
|
||||
"owo-colors",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -5,12 +5,20 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
chrono = "0.4.23"
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
clap = { version = "4.1.2", features = ["derive"] }
|
||||
dirs = "4.0.0"
|
||||
html-escape = "0.2.13"
|
||||
lazy_static = "1.4.0"
|
||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
|
|
234
src/main.rs
234
src/main.rs
|
@ -1,8 +1,22 @@
|
|||
use std::string::ToString;
|
||||
use owo_colors::{AnsiColors, OwoColorize, Stream::Stdout};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::PathBuf, io::Write};
|
||||
|
||||
use dirs;
|
||||
|
||||
use chrono::{serde::ts_seconds, Datelike, Utc};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use chrono::Datelike;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
macro_rules! cond {
|
||||
($cond:expr => $true:expr; $false:expr) => {
|
||||
if $cond {
|
||||
$true
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
|
@ -17,9 +31,11 @@ struct Cli {
|
|||
|
||||
/// Colorise output. No effect when --json is set.
|
||||
#[arg(short, long, default_value_t = ColoriseOutput::Auto)]
|
||||
pub color: ColoriseOutput
|
||||
|
||||
pub color: ColoriseOutput,
|
||||
|
||||
/// Output today only. Incompatible with --color
|
||||
#[arg(short, long)]
|
||||
pub today: bool,
|
||||
}
|
||||
|
||||
#[derive(strum::Display, Debug, ValueEnum, Clone, Copy)]
|
||||
|
@ -30,26 +46,161 @@ pub enum ColoriseOutput {
|
|||
False,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()>{
|
||||
let _args = Cli::parse();
|
||||
const MENU_URL: &'static str =
|
||||
"http://kantinemeny.azurewebsites.net/ukesmeny?lokasjon=toro@albatross-as.no&dato=";
|
||||
const SOUP_URL: &'static str =
|
||||
"http://kantinemeny.azurewebsites.net/ukesmenysuppe?lokasjon=toro@albatross-as.no&dato=";
|
||||
|
||||
let resp = ureq::get("http://kantinemeny.azurewebsites.net/ukesmeny?lokasjon=toro@albatross-as.no&dato=")
|
||||
.call()?;
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
let body = resp.into_string()?;
|
||||
let now = chrono::offset::Utc::now();
|
||||
let weekday_offset = now
|
||||
.weekday()
|
||||
.num_days_from_monday();
|
||||
|
||||
let items = body.html_string_to_vec()?;
|
||||
let cache = match get_cached_data() {
|
||||
Ok(data) => {
|
||||
if (now - data.timestamp).num_minutes() > 60 {
|
||||
let data = CachedData {
|
||||
timestamp: now.clone(),
|
||||
items: ureq::get(MENU_URL).call()?
|
||||
.into_string()?
|
||||
.html_string_to_vec()?,
|
||||
soup_items: ureq::get(SOUP_URL).call()?
|
||||
.into_string()?
|
||||
.html_string_to_vec()?,
|
||||
};
|
||||
|
||||
for item in items {
|
||||
println!("{} - {}", item.title.unwrap_or("".to_string()), item.additional.unwrap_or("".to_string()));
|
||||
set_cached_data(&data)?;
|
||||
data
|
||||
} else {
|
||||
data
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let data = CachedData {
|
||||
timestamp: now.clone(),
|
||||
items: ureq::get(MENU_URL).call()?
|
||||
.into_string()?
|
||||
.html_string_to_vec()?,
|
||||
soup_items: ureq::get(SOUP_URL).call()?
|
||||
.into_string()?
|
||||
.html_string_to_vec()?,
|
||||
};
|
||||
set_cached_data(&data)?;
|
||||
data
|
||||
}
|
||||
};
|
||||
|
||||
let items = cond!(args.soup => cache.soup_items; cache.items);
|
||||
|
||||
match args.color {
|
||||
ColoriseOutput::True => {
|
||||
std::env::set_var("FORCE_COLOR", "true");
|
||||
}
|
||||
ColoriseOutput::False => {
|
||||
std::env::set_var("NO_COLOR", "false");
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if args.today {
|
||||
if weekday_offset as usize >= (&items).len() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let item = &items[weekday_offset as usize];
|
||||
|
||||
let title: String = match item.title.clone() {
|
||||
Some(t) => t,
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
let additional = item.additional.clone().unwrap_or_default();
|
||||
|
||||
let mut final_s = String::new();
|
||||
final_s.push_str(&title);
|
||||
if !additional.is_empty() {
|
||||
let f = format!(" - {}", additional);
|
||||
final_s.push_str(&f);
|
||||
}
|
||||
|
||||
println!("{}", final_s);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let now = chrono::offset::Local::now();
|
||||
println!("{}", now.date_naive().weekday().number_from_monday());
|
||||
if args.json {
|
||||
let json =serde_json::to_string_pretty(&items)?;
|
||||
println!("{}", json);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
display_week(items, weekday_offset);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_data() -> anyhow::Result<CachedData> {
|
||||
let mut cache = dirs::cache_dir().unwrap_or(PathBuf::from("."));
|
||||
cache.push("n58-kantine");
|
||||
cache.push("data.json");
|
||||
|
||||
let s = std::fs::read_to_string(cache)?;
|
||||
let cached_data: CachedData = serde_json::from_str(&s)?;
|
||||
|
||||
Ok(cached_data)
|
||||
}
|
||||
|
||||
fn set_cached_data(data: &CachedData) -> anyhow::Result<()> {
|
||||
let mut cache = dirs::cache_dir().unwrap_or(PathBuf::from("."));
|
||||
cache.push("n58-kantine");
|
||||
let dir = cache.clone();
|
||||
cache.push("data.json");
|
||||
|
||||
let json = serde_json::to_string_pretty(data)?;
|
||||
|
||||
std::fs::create_dir_all(dir)?;
|
||||
|
||||
let mut f = std::fs::File::create(cache)?;
|
||||
f.write_all(json.as_bytes())?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref WEEKDAYS: Vec<&'static str> =
|
||||
vec!["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
|
||||
}
|
||||
|
||||
fn display_week(items: Vec<MenuItem>, day: u32) {
|
||||
for (i, item) in items.into_iter().enumerate() {
|
||||
if i >= WEEKDAYS.len() {
|
||||
break;
|
||||
};
|
||||
|
||||
let is_today = day == (i as u32);
|
||||
|
||||
println!(
|
||||
"{day}\n {title}\n {additional}",
|
||||
day = format!(
|
||||
"{}{}{}",
|
||||
"[".if_supports_color(Stdout, |s| s.color(cond!(is_today => AnsiColors::BrightWhite; AnsiColors::BrightBlack))),
|
||||
WEEKDAYS[i].if_supports_color(Stdout, |s| s.color(AnsiColors::Green)),
|
||||
"]".if_supports_color(Stdout, |s| s.color(cond!(is_today => AnsiColors::BrightWhite; AnsiColors::BrightBlack))),
|
||||
),
|
||||
title = item.title.unwrap_or("".to_owned()),
|
||||
additional = match item.additional {
|
||||
Some(text) => format!("{}\n", text),
|
||||
None => format!(""),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
trait DeocdeHTMLEnts {
|
||||
fn decode_html_ents(&self) -> Self;
|
||||
}
|
||||
|
@ -68,25 +219,25 @@ trait HTMLStringToVec {
|
|||
|
||||
fn get_item(p: &tl::Parser, tag: &tl::HTMLTag) -> Option<String> {
|
||||
Some(
|
||||
tag
|
||||
.query_selector(p, ".dagsrett")?
|
||||
tag.query_selector(p, ".dagsrett")?
|
||||
.last()?
|
||||
.get(p)?
|
||||
.as_tag()?
|
||||
.inner_text(p).to_string()
|
||||
.decode_html_ents()
|
||||
.inner_text(p)
|
||||
.to_string()
|
||||
.decode_html_ents(),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
fn get_item_additional(p: &tl::Parser, tag: &tl::HTMLTag) -> Option<String> {
|
||||
Some(
|
||||
tag
|
||||
.query_selector(p, ".dagsrettgarnityr")?
|
||||
tag.query_selector(p, ".dagsrettgarnityr")?
|
||||
.last()?
|
||||
.get(p)?
|
||||
.as_tag()?.inner_text(p).to_string()
|
||||
.decode_html_ents()
|
||||
.as_tag()?
|
||||
.inner_text(p)
|
||||
.to_string()
|
||||
.decode_html_ents(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -95,26 +246,41 @@ impl HTMLStringToVec for String {
|
|||
let dom = tl::parse(self, tl::ParserOptions::default())?;
|
||||
let p = dom.parser();
|
||||
|
||||
let nodes: Vec<&tl::Node> = Vec::from_iter(dom.query_selector(".dagsinfo").unwrap().filter_map(|x| x.get(p)));
|
||||
let nodes: Vec<&tl::Node> = Vec::from_iter(
|
||||
dom.query_selector(".dagsinfo")
|
||||
.unwrap()
|
||||
.filter_map(|x| x.get(p)),
|
||||
);
|
||||
|
||||
let items = nodes.into_iter().map(|i| {
|
||||
let tag = i.as_tag().unwrap();
|
||||
let text = get_item(p, tag);
|
||||
let items = nodes
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let tag = i.as_tag().unwrap();
|
||||
let title = get_item(p, tag);
|
||||
|
||||
let additional = get_item_additional(p, tag);
|
||||
let additional = get_item_additional(p, tag);
|
||||
|
||||
MenuItem {
|
||||
title: text,
|
||||
additional: additional,
|
||||
}
|
||||
}).collect::<Vec<MenuItem>>();
|
||||
MenuItem {
|
||||
title,
|
||||
additional,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<MenuItem>>();
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MenuItem {
|
||||
pub title: Option<String>,
|
||||
pub additional: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CachedData {
|
||||
#[serde(with = "ts_seconds")]
|
||||
pub timestamp: chrono::DateTime<Utc>,
|
||||
pub items: Vec<MenuItem>,
|
||||
pub soup_items: Vec<MenuItem>,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue