add headline anchors
This commit is contained in:
parent
b1591eebae
commit
1b81973a56
4 changed files with 345 additions and 3 deletions
274
Cargo.lock
generated
274
Cargo.lock
generated
|
@ -694,6 +694,33 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
|
||||
dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"matches",
|
||||
"phf",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"smallvec",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-macros"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.11"
|
||||
|
@ -735,6 +762,21 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa-short"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -825,6 +867,16 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.13"
|
||||
|
@ -927,11 +979,15 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"html5ever",
|
||||
"kuchiki",
|
||||
"linkify",
|
||||
"log",
|
||||
"markup5ever",
|
||||
"percent-encoding",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"slug",
|
||||
"tera",
|
||||
]
|
||||
|
||||
|
@ -1070,6 +1126,20 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.3"
|
||||
|
@ -1184,6 +1254,18 @@ dependencies = [
|
|||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchiki"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"html5ever",
|
||||
"matches",
|
||||
"selectors",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.2.2"
|
||||
|
@ -1244,12 +1326,32 @@ dependencies = [
|
|||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
|
@ -1376,6 +1478,18 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
|
@ -1531,6 +1645,60 @@ dependencies = [
|
|||
"sha-1 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "0.4.27"
|
||||
|
@ -1595,6 +1763,12 @@ version = "0.2.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
|
@ -1654,6 +1828,7 @@ dependencies = [
|
|||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc 0.2.0",
|
||||
"rand_pcg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1724,6 +1899,15 @@ dependencies = [
|
|||
"rand_core 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.5"
|
||||
|
@ -1797,6 +1981,26 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cssparser",
|
||||
"derive_more",
|
||||
"fxhash",
|
||||
"log",
|
||||
"matches",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"precomputed-hash",
|
||||
"servo_arc",
|
||||
"smallvec",
|
||||
"thin-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
|
@ -1855,6 +2059,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
|
||||
dependencies = [
|
||||
"nodrop",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
|
@ -1895,6 +2109,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
|
@ -1927,6 +2147,12 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "standback"
|
||||
version = "0.2.15"
|
||||
|
@ -1985,6 +2211,31 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"new_debug_unreachable",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -2002,6 +2253,17 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.6.1"
|
||||
|
@ -2042,6 +2304,12 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-slice"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.24"
|
||||
|
@ -2385,6 +2653,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "v_escape"
|
||||
version = "0.15.0"
|
||||
|
|
|
@ -24,7 +24,12 @@ pulldown-cmark = "0.8.0"
|
|||
|
||||
tera = "1"
|
||||
|
||||
kuchiki = "0.8.1"
|
||||
html5ever = "*"
|
||||
markup5ever = "*"
|
||||
|
||||
chrono = "0.4"
|
||||
regex = "1"
|
||||
linkify = "0.5"
|
||||
slug = "0.1.4"
|
||||
percent-encoding = "2.1.0"
|
||||
|
|
54
src/main.rs
54
src/main.rs
|
@ -1,14 +1,19 @@
|
|||
use crate::markup5ever::tendril::TendrilSink;
|
||||
use actix_files::NamedFile;
|
||||
use actix_web::error::ErrorInternalServerError;
|
||||
use actix_web::{error, get, http, middleware, web, App, Error, HttpResponse, HttpServer};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, Local};
|
||||
use clap::{App as ClapApp, Arg};
|
||||
use html5ever::serialize::{serialize, SerializeOpts};
|
||||
use kuchiki::{parse_fragment, Attribute, ExpandedName, NodeRef};
|
||||
use linkify::LinkFinder;
|
||||
use log::{info, trace};
|
||||
use markup5ever::QualName;
|
||||
use percent_encoding::{percent_decode_str, utf8_percent_encode};
|
||||
use pulldown_cmark::{html, Event, Options, Parser, Tag};
|
||||
use regex::{Captures, Regex};
|
||||
use slug::slugify;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
@ -19,6 +24,9 @@ use std::time::SystemTime;
|
|||
use std::{env, fs};
|
||||
use tera::{Context, Tera};
|
||||
|
||||
#[macro_use]
|
||||
extern crate markup5ever;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
garden_dir: PathBuf,
|
||||
|
@ -323,7 +331,7 @@ fn update_garden<P: AsRef<Path>>(
|
|||
let mut file_string = String::new();
|
||||
file.read_to_string(&mut file_string)?;
|
||||
let markdown_source = preprocess_markdown(file_string);
|
||||
let result = GardenParser::parse(&markdown_source);
|
||||
let result = GardenParser::parse(&markdown_source)?;
|
||||
|
||||
pages.insert(
|
||||
String::from(path.to_str().unwrap()),
|
||||
|
@ -366,7 +374,7 @@ struct ParseResult {
|
|||
}
|
||||
|
||||
impl<'a> GardenParser<'a> {
|
||||
fn parse<S: AsRef<str>>(text: &'a S) -> ParseResult {
|
||||
fn parse<S: AsRef<str>>(text: &'a S) -> anyhow::Result<ParseResult> {
|
||||
let mut title: Option<String> = None;
|
||||
let mut links: Vec<String> = vec![];
|
||||
|
||||
|
@ -380,8 +388,9 @@ impl<'a> GardenParser<'a> {
|
|||
|
||||
let mut html = String::new();
|
||||
html::push_html(&mut html, parser);
|
||||
html = postprocess_html(html)?;
|
||||
|
||||
ParseResult { html, title, links }
|
||||
Ok(ParseResult { html, title, links })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,6 +458,45 @@ fn preprocess_markdown(string: String) -> String {
|
|||
result_string
|
||||
}
|
||||
|
||||
fn postprocess_html<T: AsRef<str>>(document: T) -> anyhow::Result<String> {
|
||||
let document_bytes = String::from(document.as_ref());
|
||||
let frag = parse_fragment(QualName::new(None, ns!(html), local_name!("body")), vec![])
|
||||
.from_utf8()
|
||||
.read_from(&mut document_bytes.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
frag.select("h1,h2,h3,h4,h5")
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.for_each(|el| {
|
||||
let id = slugify(el.text_contents());
|
||||
el.attributes.borrow_mut().insert("id", id.clone());
|
||||
el.as_node().prepend(NodeRef::new_element(
|
||||
QualName::new(None, ns!(html), local_name!("a")),
|
||||
vec![
|
||||
(
|
||||
ExpandedName::new(ns!(), local_name!("class")),
|
||||
Attribute {
|
||||
prefix: None,
|
||||
value: "anchor".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
ExpandedName::new(ns!(), local_name!("href")),
|
||||
Attribute {
|
||||
prefix: None,
|
||||
value: format!("#{}", id),
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
});
|
||||
|
||||
let mut bytes = vec![];
|
||||
serialize(&mut bytes, &frag, SerializeOpts::default())?;
|
||||
Ok(String::from_utf8(bytes)?)
|
||||
}
|
||||
|
||||
fn normalize_name(filename: &str) -> String {
|
||||
let decoded = percent_decode_str(filename).decode_utf8_lossy();
|
||||
let result = decoded.strip_suffix(".md");
|
||||
|
|
|
@ -49,6 +49,21 @@ li p {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
text-decoration: none;
|
||||
opacity: .5;
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.anchor:after {
|
||||
content: "#";
|
||||
margin-right: .15em;
|
||||
}
|
||||
|
||||
.anchor:visited {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
aside h1 {
|
||||
font-size: 16pt;
|
||||
text-decoration: underline;
|
||||
|
|
Loading…
Reference in a new issue