parent
1cf099845b
commit
bd58b7aecd
@ -0,0 +1,128 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_yaml::Value;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Challenge {
|
||||
pub japanese: Vec<Vec<Vec<ChallengeWord>>>,
|
||||
pub english: Option<Vec<String>>,
|
||||
pub song: Option<Song>,
|
||||
pub youtube: Option<String>,
|
||||
pub spotify: Option<String>,
|
||||
pub translation: Option<Translation>,
|
||||
pub suggester: Option<String>,
|
||||
pub date: chrono::NaiveDate,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Song {
|
||||
pub japanese: Option<String>,
|
||||
pub english: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ChallengeWord {
|
||||
pub dictionary: Option<String>,
|
||||
pub pos: Option<PartOfSpeech>,
|
||||
pub text: Option<Vec<Furigana>>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ChallengeWord {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = Deserialize::deserialize(deserializer)?;
|
||||
let map = if let Value::Mapping(map) = value {
|
||||
map
|
||||
} else {
|
||||
return Err(serde::de::Error::invalid_type(
|
||||
serde::de::Unexpected::Other("not a string or map"),
|
||||
&"a string or map",
|
||||
));
|
||||
};
|
||||
let dictionary = map.get("dictionary").and_then(|value| match value {
|
||||
Value::Null => None,
|
||||
_ => Some(value.as_str().unwrap().to_owned()),
|
||||
});
|
||||
let pos: Option<PartOfSpeech> = map
|
||||
.get("pos")
|
||||
.map(|value| value.as_str().unwrap().parse().unwrap());
|
||||
Ok(ChallengeWord {
|
||||
dictionary,
|
||||
pos,
|
||||
text: map.get("text").map(|value| match value {
|
||||
Value::String(string) => vec![Furigana {
|
||||
kanji: string.clone(),
|
||||
furigana: None,
|
||||
}],
|
||||
Value::Sequence(sequence) => sequence
|
||||
.iter()
|
||||
.map(|value| match value {
|
||||
Value::String(kanji) => Furigana {
|
||||
kanji: kanji.to_owned(),
|
||||
furigana: None,
|
||||
},
|
||||
Value::Mapping(mapping) => {
|
||||
let (kanji, furigana) = mapping.iter().next().unwrap();
|
||||
Furigana {
|
||||
kanji: kanji.as_str().unwrap().to_owned(),
|
||||
furigana: Some(furigana.as_str().unwrap().to_owned()),
|
||||
}
|
||||
}
|
||||
_ => panic!(),
|
||||
})
|
||||
.collect(),
|
||||
_ => panic!(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PartOfSpeech {
|
||||
Noun,
|
||||
Adjective,
|
||||
Verb,
|
||||
Adverb,
|
||||
Particle,
|
||||
Phrase,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParsePartOfSpeechError;
|
||||
|
||||
impl FromStr for PartOfSpeech {
|
||||
type Err = ParsePartOfSpeechError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use PartOfSpeech::*;
|
||||
Ok(match s {
|
||||
"noun" => Noun,
|
||||
"adjective" => Adjective,
|
||||
"verb" => Verb,
|
||||
"adverb" => Adverb,
|
||||
"particle" => Particle,
|
||||
"phrase" => Phrase,
|
||||
_ => return Err(ParsePartOfSpeechError),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Furigana {
|
||||
pub kanji: String,
|
||||
pub furigana: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Translation {
|
||||
pub author: Option<String>,
|
||||
pub site: TranslationSite,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TranslationSite {
|
||||
pub name: String,
|
||||
pub link: String,
|
||||
}
|
@ -1,19 +1,56 @@
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use rocket_dyn_templates::{Template, context};
|
||||
use core::panic;
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use std::fs;
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> Template {
|
||||
Template::render("index", context! {
|
||||
challenge: 114,
|
||||
})
|
||||
mod challenge;
|
||||
use challenge::Challenge;
|
||||
|
||||
#[get("/<challenge>")]
|
||||
fn get_challenge(challenge: u32) -> Template {
|
||||
Template::render(
|
||||
"index",
|
||||
context! {
|
||||
challenge,
|
||||
content: {
|
||||
use comrak::{parse_document, Arena, ComrakOptions};
|
||||
let options = {
|
||||
let mut options = ComrakOptions::default();
|
||||
options.extension.front_matter_delimiter = Some("---".to_owned());
|
||||
options
|
||||
};
|
||||
let arena = Arena::new();
|
||||
let root = parse_document(
|
||||
&arena,
|
||||
&fs::read_to_string(format!("content/challenges/{challenge}.md")).expect("Couldn't find challenge file"),
|
||||
&options,
|
||||
);
|
||||
if let Some(node) = root.children().next() {
|
||||
if let comrak::nodes::NodeValue::FrontMatter(frontmatter) = &node.data.borrow().value {
|
||||
let frontmatter = {
|
||||
// Trim starting and ending fences
|
||||
let lines: Vec<&str> = frontmatter.trim().lines().collect();
|
||||
lines[1..lines.len() - 1].join("\n")
|
||||
};
|
||||
let challenge: Challenge = serde_yaml::from_str(&frontmatter).unwrap();
|
||||
challenge
|
||||
} else {
|
||||
panic!("No frontmatter!")
|
||||
}
|
||||
} else {
|
||||
panic!("Empty document!")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
let config = rocket::Config::figment()
|
||||
.merge(("port", 1313));
|
||||
let config = rocket::Config::figment().merge(("port", 1313));
|
||||
rocket::custom(config)
|
||||
.mount("/", routes![index])
|
||||
.mount("/", routes![get_challenge])
|
||||
.attach(Template::fairing())
|
||||
}
|
Loading…
Reference in new issue