init
This commit is contained in:
commit
ff337b1c07
10 changed files with 390 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "stundenplaner"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.43"
|
||||
deno_core = "0.98.0"
|
||||
minify-html = { version = "0.6.8", features = ["js-esbuild"] }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
structopt = "0.3.23"
|
||||
tera = "1.12.1"
|
||||
tokio = { version = "1.11.0", features = ["rt-multi-thread", "macros", "fs"] }
|
||||
|
||||
[build-dependencies]
|
||||
# used to create engine snapshot in build script
|
||||
deno_console = "0.16.0"
|
||||
deno_core = "0.98.0"
|
36
assets/lib.js
Normal file
36
assets/lib.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
// replace default console impl (which uses some remote debugger stuff) with the deno_console one.
|
||||
globalThis.console = new globalThis.__bootstrap.console.Console((msg, level) =>
|
||||
Deno.core.print(msg, level > 1)
|
||||
);
|
||||
|
||||
spInt = {};
|
||||
|
||||
spInt.Classes = class {
|
||||
classes = [];
|
||||
|
||||
add(name, roomNr) {
|
||||
if (name == undefined) {
|
||||
throw "name of class must not be undefined!";
|
||||
}
|
||||
|
||||
this.classes.push({ name: name, room_nr: roomNr });
|
||||
}
|
||||
};
|
||||
|
||||
spInt.collectClasses = function (day) {
|
||||
const classes = new spInt.Classes();
|
||||
getClasses(day, classes);
|
||||
return classes.classes;
|
||||
};
|
||||
|
||||
spInt.defaultLocale = {
|
||||
time: "Time",
|
||||
|
||||
mo: "Monday",
|
||||
tu: "Tuesday",
|
||||
we: "Wednesday",
|
||||
th: "Thursday",
|
||||
fr: "Friday",
|
||||
};
|
||||
|
||||
spInt.defaultRepeat = 1;
|
19
assets/runscript.js
Normal file
19
assets/runscript.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
if (typeof outfile === "undefined") {
|
||||
throw "`outfile` does not exist!";
|
||||
}
|
||||
|
||||
var obj = {
|
||||
days: {
|
||||
mo: spInt.collectClasses("mo"),
|
||||
tu: spInt.collectClasses("tu"),
|
||||
we: spInt.collectClasses("we"),
|
||||
th: spInt.collectClasses("th"),
|
||||
fr: spInt.collectClasses("fr"),
|
||||
},
|
||||
outfile: outfile,
|
||||
locale: typeof locale === "undefined" ? spInt.defaultLocale : locale,
|
||||
repeat: typeof repeat === "undefined" ? spInt.defaultRepeat : repeat,
|
||||
times: typeof times === "undefined" ? [] : times,
|
||||
};
|
||||
|
||||
obj;
|
105
assets/template.html.tera
Normal file
105
assets/template.html.tera
Normal file
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Plan</title>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica,
|
||||
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||
}
|
||||
|
||||
table td,
|
||||
table th {
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% for i in range(end=repeat) %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
{% if times %}<th align="left">{{ locale.time }}</th>{% endif %}
|
||||
<th align="left">{{ locale.mo }}</th>
|
||||
<th align="left">{{ locale.tu }}</th>
|
||||
<th align="left">{{ locale.we }}</th>
|
||||
<th align="left">{{ locale.th }}</th>
|
||||
<th align="left">{{ locale.fr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td>{{ r.idx }}</td>
|
||||
|
||||
{% if times %}
|
||||
<td>{{ r.time }}</td>
|
||||
{% endif %}
|
||||
|
||||
<td>
|
||||
{% if r.mo %} {% if r.mo.room_nr %}
|
||||
<b>{{ r.mo.room_nr }}</b>
|
||||
{% endif %}
|
||||
|
||||
{{ r.mo.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.tu %} {% if r.tu.room_nr %}
|
||||
<b>{{ r.tu.room_nr }}</b>
|
||||
{% endif %}
|
||||
|
||||
{{ r.tu.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.we %} {% if r.we.room_nr %}
|
||||
<b>{{ r.we.room_nr }}</b>
|
||||
{% endif %}
|
||||
|
||||
{{ r.we.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.th %} {% if r.th.room_nr %}
|
||||
<b>{{ r.th.room_nr }}</b>
|
||||
{% endif %}
|
||||
|
||||
{{ r.th.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.fr %} {% if r.fr.room_nr %}
|
||||
<b>{{ r.fr.room_nr }}</b>
|
||||
{% endif %}
|
||||
|
||||
{{ r.fr.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if i != repeat - 1 %}
|
||||
<hr />
|
||||
{% endif %}{% endfor %}
|
||||
</body>
|
||||
</html>
|
24
build.rs
Normal file
24
build.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use deno_core::{include_js_files, Extension, JsRuntime, RuntimeOptions};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let mut js = JsRuntime::new(RuntimeOptions {
|
||||
will_snapshot: true,
|
||||
extensions: vec![
|
||||
deno_console::init(),
|
||||
Extension::builder()
|
||||
.js(include_js_files!(
|
||||
prefix "stundenplaner",
|
||||
"assets/lib.js",
|
||||
))
|
||||
.build(),
|
||||
],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
std::fs::write(
|
||||
Path::new(&std::env::var("OUT_DIR").unwrap()).join("runtime_state.bin"),
|
||||
js.snapshot(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
12
rustfmt.toml
Normal file
12
rustfmt.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
unstable_features = true
|
||||
binop_separator = "Back"
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
format_strings = true
|
||||
imports_layout = "HorizontalVertical"
|
||||
match_block_trailing_comma = true
|
||||
merge_imports = true
|
||||
normalize_comments = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
26
src/main.rs
Normal file
26
src/main.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use anyhow::Context;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod renderer;
|
||||
mod script;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Opt {
|
||||
infile: PathBuf,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let opt = Opt::from_args();
|
||||
|
||||
let s_data = script::run_buildscript(opt.infile).await?;
|
||||
let path = s_data.outfile.clone();
|
||||
let rendered = renderer::render(s_data)?;
|
||||
|
||||
tokio::fs::write(path, rendered)
|
||||
.await
|
||||
.context("failed to write outfile")?;
|
||||
|
||||
Ok(())
|
||||
}
|
73
src/renderer.rs
Normal file
73
src/renderer.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use crate::script::{Class, Locale};
|
||||
use anyhow::Context;
|
||||
use serde::Serialize;
|
||||
use tera::Tera;
|
||||
|
||||
use crate::script::ScriptData;
|
||||
|
||||
pub fn render(sdata: ScriptData) -> anyhow::Result<Vec<u8>> {
|
||||
let days = sdata.days;
|
||||
let max_len = days.cols().iter().map(|c| c.len()).max().unwrap_or(0);
|
||||
|
||||
let mut rows = vec![];
|
||||
for i in 0..max_len {
|
||||
rows.push(Row {
|
||||
idx: i + 1,
|
||||
time: sdata.times.get(i).cloned().unwrap_or_default(),
|
||||
|
||||
mo: days.mo.get(i).cloned(),
|
||||
tu: days.tu.get(i).cloned(),
|
||||
we: days.we.get(i).cloned(),
|
||||
th: days.th.get(i).cloned(),
|
||||
fr: days.fr.get(i).cloned(),
|
||||
});
|
||||
}
|
||||
|
||||
let rendered = Tera::one_off(
|
||||
include_str!("../assets/template.html.tera"),
|
||||
&tera::Context::from_serialize(RenderData {
|
||||
rows,
|
||||
times: !sdata.times.is_empty(),
|
||||
locale: sdata.locale,
|
||||
repeat: sdata.repeat,
|
||||
})
|
||||
.context("failed to serialize template data")?,
|
||||
false,
|
||||
)
|
||||
.context("failed to render template")?;
|
||||
|
||||
Ok(minify_html::minify(
|
||||
rendered.as_bytes(),
|
||||
&minify_html::Cfg {
|
||||
do_not_minify_doctype: true,
|
||||
ensure_spec_compliant_unquoted_attribute_values: false,
|
||||
keep_closing_tags: true,
|
||||
keep_html_and_head_opening_tags: true,
|
||||
keep_spaces_between_attributes: true,
|
||||
keep_comments: false,
|
||||
minify_css: true,
|
||||
minify_js: true,
|
||||
remove_bangs: true,
|
||||
remove_processing_instructions: false,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RenderData {
|
||||
rows: Vec<Row>,
|
||||
locale: Locale,
|
||||
repeat: usize,
|
||||
times: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Row {
|
||||
idx: usize,
|
||||
time: String,
|
||||
mo: Option<Class>,
|
||||
tu: Option<Class>,
|
||||
we: Option<Class>,
|
||||
th: Option<Class>,
|
||||
fr: Option<Class>,
|
||||
}
|
73
src/script.rs
Normal file
73
src/script.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use anyhow::Context;
|
||||
use deno_core::{v8::Local, JsRuntime, RuntimeOptions, Snapshot};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub async fn run_buildscript(infile: PathBuf) -> anyhow::Result<ScriptData> {
|
||||
let mut js = JsRuntime::new(RuntimeOptions {
|
||||
startup_snapshot: Some(Snapshot::Static(include_bytes!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/runtime_state.bin"
|
||||
)))),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
js.execute_script(
|
||||
"script_init",
|
||||
std::str::from_utf8(&tokio::fs::read(infile).await?)?,
|
||||
)
|
||||
.context("failed to initialize script")?;
|
||||
|
||||
let data = js
|
||||
.execute_script("script_run", include_str!("../assets/runscript.js"))
|
||||
.context("failed to run script")?;
|
||||
let mut scope = js.handle_scope();
|
||||
let local = Local::new(&mut scope, data);
|
||||
let data = deno_core::serde_v8::from_v8::<ScriptData>(&mut scope, local)
|
||||
.context("failed to deserialize script data")?;
|
||||
drop(scope);
|
||||
|
||||
js.run_event_loop(false).await?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ScriptData {
|
||||
pub days: Days,
|
||||
pub outfile: PathBuf,
|
||||
pub locale: Locale,
|
||||
pub repeat: usize,
|
||||
pub times: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Days {
|
||||
pub mo: Vec<Class>,
|
||||
pub tu: Vec<Class>,
|
||||
pub we: Vec<Class>,
|
||||
pub th: Vec<Class>,
|
||||
pub fr: Vec<Class>,
|
||||
}
|
||||
|
||||
impl Days {
|
||||
pub fn cols(&self) -> [&Vec<Class>; 5] {
|
||||
[&self.mo, &self.tu, &self.we, &self.th, &self.fr]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Class {
|
||||
pub room_nr: Option<String>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Locale {
|
||||
pub time: String,
|
||||
pub mo: String,
|
||||
pub tu: String,
|
||||
pub we: String,
|
||||
pub th: String,
|
||||
pub fr: String,
|
||||
}
|
Loading…
Reference in a new issue