rework generation to support multiple files; introduce a more proper CLI
This commit is contained in:
parent
27414d4254
commit
7e84005a6b
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -17,6 +17,15 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -290,6 +299,19 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -447,6 +469,12 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.27"
|
version = "0.14.27"
|
||||||
|
@ -731,6 +759,35 @@ dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
|
@ -1068,13 +1125,16 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"copy_dir",
|
"copy_dir",
|
||||||
|
"env_logger",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
|
"log",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-livereload",
|
"tower-livereload",
|
||||||
"treehouse-format",
|
"treehouse-format",
|
||||||
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -3,5 +3,6 @@ members = ["crates/*"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
log = "0.4.20"
|
||||||
|
|
||||||
treehouse-format = { path = "crates/treehouse-format" }
|
treehouse-format = { path = "crates/treehouse-format" }
|
||||||
|
|
1
content/secret.tree
Normal file
1
content/secret.tree
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- He is behind the tree.
|
|
@ -4,17 +4,22 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
treehouse-format = { workspace = true }
|
||||||
|
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
axum = "0.6.20"
|
axum = "0.6.20"
|
||||||
clap = { version = "4.3.22", features = ["derive"] }
|
clap = { version = "4.3.22", features = ["derive"] }
|
||||||
codespan-reporting = "0.11.1"
|
codespan-reporting = "0.11.1"
|
||||||
copy_dir = "0.1.3"
|
copy_dir = "0.1.3"
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
log = { workspace = true }
|
||||||
handlebars = "4.3.7"
|
handlebars = "4.3.7"
|
||||||
pulldown-cmark = { version = "0.9.3", default-features = false }
|
pulldown-cmark = { version = "0.9.3", default-features = false }
|
||||||
serde = { version = "1.0.183", features = ["derive"] }
|
serde = { version = "1.0.183", features = ["derive"] }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
tower-http = { version = "0.4.3", features = ["fs"] }
|
tower-http = { version = "0.4.3", features = ["fs"] }
|
||||||
tower-livereload = "0.8.0"
|
tower-livereload = "0.8.0"
|
||||||
|
walkdir = "2.3.3"
|
||||||
|
|
||||||
treehouse-format = { workspace = true }
|
|
||||||
|
|
||||||
|
|
|
@ -1,74 +1,242 @@
|
||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use codespan_reporting::{
|
use codespan_reporting::{
|
||||||
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
||||||
files::SimpleFile,
|
files::{Files as _, SimpleFiles},
|
||||||
term::termcolor::{ColorChoice, StandardStream},
|
term::termcolor::{ColorChoice, StandardStream},
|
||||||
};
|
};
|
||||||
use copy_dir::copy_dir;
|
use copy_dir::copy_dir;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
|
use log::{debug, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use tower_livereload::LiveReloadLayer;
|
use tower_livereload::LiveReloadLayer;
|
||||||
use treehouse_format::ast::Roots;
|
use treehouse_format::ast::Roots;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::html::tree::branches_to_html;
|
use crate::html::tree::branches_to_html;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Default)]
|
||||||
pub struct TemplateData {
|
struct Generator {
|
||||||
pub tree: String,
|
tree_files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regenerate() -> anyhow::Result<()> {
|
type Files = SimpleFiles<String, String>;
|
||||||
let _ = std::fs::remove_dir_all("target/site");
|
type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
||||||
std::fs::create_dir_all("target/site")?;
|
|
||||||
|
|
||||||
copy_dir("static", "target/site/static")?;
|
pub struct Diagnosis {
|
||||||
|
pub files: Files,
|
||||||
|
pub diagnostics: Vec<Diagnostic<FileId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator {
|
||||||
|
fn add_directory_rec(&mut self, directory: &Path) -> anyhow::Result<()> {
|
||||||
|
for entry in WalkDir::new(directory) {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.path().extension() == Some(OsStr::new("tree")) {
|
||||||
|
self.tree_files.push(entry.path().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_template(
|
||||||
|
handlebars: &mut Handlebars<'_>,
|
||||||
|
diagnosis: &mut Diagnosis,
|
||||||
|
name: &str,
|
||||||
|
path: &Path,
|
||||||
|
) -> anyhow::Result<FileId> {
|
||||||
|
let source = std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("cannot read template file {path:?}"))?;
|
||||||
|
let file_id = diagnosis
|
||||||
|
.files
|
||||||
|
.add(path.to_string_lossy().into_owned(), source);
|
||||||
|
let file = diagnosis
|
||||||
|
.files
|
||||||
|
.get(file_id)
|
||||||
|
.expect("file was just added to the list");
|
||||||
|
let source = file.source();
|
||||||
|
if let Err(error) = handlebars.register_template_string(name, source) {
|
||||||
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
|
diagnosis,
|
||||||
|
file_id,
|
||||||
|
error.line_no,
|
||||||
|
error.column_no,
|
||||||
|
error.reason().to_string(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(file_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrangle_handlebars_error_into_diagnostic(
|
||||||
|
diagnosis: &mut Diagnosis,
|
||||||
|
file_id: FileId,
|
||||||
|
line: Option<usize>,
|
||||||
|
column: Option<usize>,
|
||||||
|
message: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let (Some(line), Some(column)) = (line, column) {
|
||||||
|
let line_range = diagnosis
|
||||||
|
.files
|
||||||
|
.line_range(file_id, line)
|
||||||
|
.expect("file was added to the list");
|
||||||
|
diagnosis.diagnostics.push(Diagnostic {
|
||||||
|
severity: Severity::Error,
|
||||||
|
code: Some("template".into()),
|
||||||
|
message,
|
||||||
|
labels: vec![Label {
|
||||||
|
style: LabelStyle::Primary,
|
||||||
|
file_id,
|
||||||
|
range: line_range.start + column..line_range.start + column + 1,
|
||||||
|
message: String::new(),
|
||||||
|
}],
|
||||||
|
notes: vec![],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let file = diagnosis
|
||||||
|
.files
|
||||||
|
.get(file_id)
|
||||||
|
.expect("file should already be in the list");
|
||||||
|
bail!("template error in {}: {message}", file.name());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result<Diagnosis> {
|
||||||
|
let mut diagnosis = Diagnosis {
|
||||||
|
files: Files::new(),
|
||||||
|
diagnostics: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
let mut handlebars = Handlebars::new();
|
let mut handlebars = Handlebars::new();
|
||||||
handlebars.register_template_file("template/index.hbs", "template/index.hbs")?;
|
let tree_template = Self::register_template(
|
||||||
|
&mut handlebars,
|
||||||
|
&mut diagnosis,
|
||||||
|
"tree",
|
||||||
|
&dirs.template_dir.join("tree.hbs"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for path in &self.tree_files {
|
||||||
|
let utf8_filename = path.to_string_lossy();
|
||||||
|
let target_file = path.strip_prefix(dirs.content_dir).unwrap_or(path);
|
||||||
|
let target_path = if target_file == OsStr::new("index.tree") {
|
||||||
|
dirs.target_dir.join("index.html")
|
||||||
|
} else {
|
||||||
|
dirs.target_dir.join(target_file).with_extension("html")
|
||||||
|
};
|
||||||
|
debug!("generating: {path:?} -> {target_path:?}");
|
||||||
|
|
||||||
|
let source = match std::fs::read_to_string(path) {
|
||||||
|
Ok(source) => source,
|
||||||
|
Err(error) => {
|
||||||
|
diagnosis.diagnostics.push(Diagnostic {
|
||||||
|
severity: Severity::Error,
|
||||||
|
code: None,
|
||||||
|
message: format!("{utf8_filename}: cannot read file: {error}"),
|
||||||
|
labels: vec![],
|
||||||
|
notes: vec![],
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file_id = diagnosis.files.add(utf8_filename.into_owned(), source);
|
||||||
|
let source = diagnosis
|
||||||
|
.files
|
||||||
|
.get(file_id)
|
||||||
|
.expect("file was just added to the list")
|
||||||
|
.source();
|
||||||
|
|
||||||
let root_file = std::fs::read_to_string("content/tree/index.tree")?;
|
|
||||||
let parse_result = Roots::parse(&mut treehouse_format::pull::Parser {
|
let parse_result = Roots::parse(&mut treehouse_format::pull::Parser {
|
||||||
input: &root_file,
|
input: source,
|
||||||
position: 0,
|
position: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
match parse_result {
|
match parse_result {
|
||||||
Ok(roots) => {
|
Ok(roots) => {
|
||||||
let mut tree = String::new();
|
let mut tree = String::new();
|
||||||
branches_to_html(&mut tree, &roots.branches, &root_file);
|
branches_to_html(&mut tree, &roots.branches, source);
|
||||||
|
|
||||||
let index_html = handlebars.render("template/index.hbs", &TemplateData { tree })?;
|
let template_data = TemplateData { tree };
|
||||||
|
let templated_html = match handlebars.render("tree", &template_data) {
|
||||||
std::fs::write("target/site/index.html", index_html)?;
|
Ok(html) => html,
|
||||||
}
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
let config = codespan_reporting::term::Config::default();
|
&mut diagnosis,
|
||||||
let files = SimpleFile::new("index.tree", &root_file);
|
tree_template,
|
||||||
let diagnostic = Diagnostic {
|
error.line_no,
|
||||||
|
error.column_no,
|
||||||
|
error.desc,
|
||||||
|
)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::fs::write(target_path, templated_html)?;
|
||||||
|
}
|
||||||
|
Err(error) => diagnosis.diagnostics.push(Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
code: None,
|
code: Some("tree".into()),
|
||||||
message: error.kind.to_string(),
|
message: error.kind.to_string(),
|
||||||
labels: vec![Label {
|
labels: vec![Label {
|
||||||
style: LabelStyle::Primary,
|
style: LabelStyle::Primary,
|
||||||
file_id: (),
|
file_id,
|
||||||
range: error.range,
|
range: error.range,
|
||||||
message: String::new(),
|
message: String::new(),
|
||||||
}],
|
}],
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
};
|
}),
|
||||||
codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(diagnosis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Dirs<'a> {
|
||||||
|
pub target_dir: &'a Path,
|
||||||
|
pub static_dir: &'a Path,
|
||||||
|
pub template_dir: &'a Path,
|
||||||
|
pub content_dir: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct TemplateData {
|
||||||
|
pub tree: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> {
|
||||||
|
info!("cleaning target directory");
|
||||||
|
let _ = std::fs::remove_dir_all(dirs.target_dir);
|
||||||
|
std::fs::create_dir_all(dirs.target_dir)?;
|
||||||
|
|
||||||
|
info!("copying static directory to target directory");
|
||||||
|
copy_dir(dirs.static_dir, dirs.target_dir.join("static"))?;
|
||||||
|
|
||||||
|
info!("generating standalone pages");
|
||||||
|
let mut generator = Generator::default();
|
||||||
|
generator.add_directory_rec(dirs.content_dir)?;
|
||||||
|
let diagnosis = generator.generate_all_files(dirs)?;
|
||||||
|
|
||||||
|
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||||
|
let config = codespan_reporting::term::Config::default();
|
||||||
|
for diagnostic in &diagnosis.diagnostics {
|
||||||
|
codespan_reporting::term::emit(&mut writer.lock(), &config, &diagnosis.files, diagnostic)
|
||||||
|
.context("could not emit diagnostic")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regenerate_or_report_error() {
|
pub fn regenerate_or_report_error(dirs: &Dirs<'_>) {
|
||||||
eprintln!("regenerating");
|
info!("regenerating site content");
|
||||||
|
|
||||||
match regenerate() {
|
match regenerate(dirs) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(error) => eprintln!("error: {error:?}"),
|
Err(error) => eprintln!("error: {error:?}"),
|
||||||
}
|
}
|
||||||
|
@ -80,7 +248,7 @@ pub async fn web_server() -> anyhow::Result<()> {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let app = app.layer(LiveReloadLayer::new());
|
let app = app.layer(LiveReloadLayer::new());
|
||||||
|
|
||||||
eprintln!("serving on port 8080");
|
info!("serving on port 8080");
|
||||||
Ok(axum::Server::bind(&([0, 0, 0, 0], 8080).into())
|
Ok(axum::Server::bind(&([0, 0, 0, 0], 8080).into())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?)
|
.await?)
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{
|
use cli::{
|
||||||
regenerate::{self, regenerate_or_report_error},
|
regenerate::{self, regenerate_or_report_error, Dirs},
|
||||||
Command, ProgramArgs,
|
Command, ProgramArgs,
|
||||||
};
|
};
|
||||||
|
use log::{error, info};
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod html;
|
mod html;
|
||||||
|
@ -12,7 +15,18 @@ async fn fallible_main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Regenerate(regenerate_args) => {
|
Command::Regenerate(regenerate_args) => {
|
||||||
regenerate_or_report_error();
|
let dirs = Dirs {
|
||||||
|
target_dir: Path::new("target/site"),
|
||||||
|
|
||||||
|
// NOTE: These are intentionally left unconfigurable from within treehouse.toml
|
||||||
|
// because this is is one of those things that should be consistent between sites.
|
||||||
|
static_dir: Path::new("static"),
|
||||||
|
template_dir: Path::new("template"),
|
||||||
|
content_dir: Path::new("content"),
|
||||||
|
};
|
||||||
|
info!("regenerating using directories: {dirs:#?}");
|
||||||
|
|
||||||
|
regenerate_or_report_error(&dirs);
|
||||||
|
|
||||||
if regenerate_args.serve {
|
if regenerate_args.serve {
|
||||||
regenerate::web_server().await?;
|
regenerate::web_server().await?;
|
||||||
|
@ -25,9 +39,13 @@ async fn fallible_main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
env_logger::Builder::new()
|
||||||
|
.filter_module("treehouse", log::LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
match fallible_main().await {
|
match fallible_main().await {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(error) => eprintln!("fatal: {error:?}"),
|
Err(error) => error!("fatal: {error:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -10,7 +10,7 @@ body {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: rgb(255, 253, 246);
|
background-color: rgb(255, 253, 246);
|
||||||
color: #333;
|
color: #55423e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set up typography */
|
/* Set up typography */
|
||||||
|
|
Loading…
Reference in a new issue