docs: drop the predecessor comparisons from comments and tests
This commit is contained in:
parent
36faa4fb93
commit
19e7cfaca3
19 changed files with 65 additions and 73 deletions
|
|
@ -3,8 +3,7 @@
|
|||
//! Invalidation is by modification time and length, which is what makes editing
|
||||
//! a file enough to see the change on the next request with no watcher and no
|
||||
//! restart. Values are built outside the lock, so two first hits on one file can
|
||||
//! both build it; the work is idempotent and the second insert wins, which is
|
||||
//! the trade the Python makes too.
|
||||
//! both build it; the work is idempotent and the second insert wins.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
|
@ -109,9 +108,9 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
/// Force a modification time change. Filesystem granularity is coarse
|
||||
/// enough that two writes in one test can otherwise share a timestamp,
|
||||
/// which is why the Python's live-reload test does the same thing.
|
||||
/// Force a modification time change. Filesystem granularity is coarse enough
|
||||
/// that two writes in one test can otherwise share a timestamp, which would
|
||||
/// make a stale value look correctly cached.
|
||||
fn bump_mtime(path: &Path) {
|
||||
let later = SystemTime::now() + Duration::from_secs(5);
|
||||
File::options()
|
||||
|
|
@ -137,7 +136,8 @@ mod tests {
|
|||
let first = cache.get_or_insert_with(&path, Stamp::of(&path).unwrap(), build).unwrap();
|
||||
let second = cache.get_or_insert_with(&path, Stamp::of(&path).unwrap(), build).unwrap();
|
||||
|
||||
// Identity, not just equality: the Python asserts `first is second`.
|
||||
// Identity, not equality: a repeat hit must return the cached value
|
||||
// rather than an equal rebuild.
|
||||
assert!(Arc::ptr_eq(&first, &second));
|
||||
assert_eq!(builds.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,8 +670,8 @@ impl Default for PageSettings {
|
|||
// the terminal edge.
|
||||
margin_left: 2,
|
||||
margin_right: 2,
|
||||
// One blank line between blocks. md2txt uses two, which reads as
|
||||
// double-spaced throughout.
|
||||
// One blank line between blocks; two reads as double-spaced
|
||||
// throughout.
|
||||
paragraph_spacing: 1,
|
||||
heading_styles: DEFAULT_HEADING_STYLES,
|
||||
blockquote_bars: true,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
//! The one parsed representation every output format consumes.
|
||||
//!
|
||||
//! smolweb parses each document twice, with two hand-rolled regex parsers that
|
||||
//! share seven identical patterns but disagree on the edges: that is where the
|
||||
//! `{.card}` directive leaks into gemtext as literal text, and why the two
|
||||
//! libraries carry two divergent sets of defaults. Parsing once into this
|
||||
//! structure removes the class of bug rather than the instances.
|
||||
//! One parse, shared by every format. Parsing per format is how two outputs come
|
||||
//! to disagree about the same document: a directive one understands and another
|
||||
//! emits as literal text, or two sets of defaults that drift apart. Parsing once
|
||||
//! into this structure removes that class of bug rather than its instances.
|
||||
//!
|
||||
//! It is a flat block sequence rather than a tree of nodes because that is what
|
||||
//! the consumers want: WML packs a linear run of blocks into byte-budgeted
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Media types for files served byte for byte.
|
||||
//!
|
||||
//! A fixed table rather than a system lookup: Python's `mimetypes` consults
|
||||
//! `/etc/mime.types` where it exists, so smolweb's `Content-Type` for the same
|
||||
//! file differs between hosts. Determinism is worth more here than coverage of
|
||||
//! the long tail, and an unknown extension has a correct answer anyway.
|
||||
//! A fixed table rather than a system lookup. A lookup reads `/etc/mime.types`
|
||||
//! where it exists, so the same file gets a different `Content-Type` depending on
|
||||
//! the host. Determinism is worth more here than coverage of the long tail, and
|
||||
//! an unknown extension has a correct answer anyway.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
|
|
|||
|
|
@ -427,8 +427,6 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parses_setext_headings() {
|
||||
// wapdown's parser handles these and md2txt's does not, so unifying the
|
||||
// two parsers gains them for the text formats.
|
||||
assert_eq!(
|
||||
blocks("Title\n=====\n"),
|
||||
vec![Block::Heading { level: 1, inline: text("Title") }]
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ fn encode_path(path: &str) -> String {
|
|||
/// Decode `%XX` escapes, leaving an invalid escape as the literal text it is.
|
||||
///
|
||||
/// Bytes that do not form valid UTF-8 become U+FFFD, which matches no filename,
|
||||
/// so a malformed target resolves to nothing rather than erroring. That matches
|
||||
/// Python's lossy `unquote` and is pinned by a test.
|
||||
/// so a malformed target resolves to nothing rather than erroring. A test pins
|
||||
/// that leniency, so it is not later tightened into an error.
|
||||
fn percent_decode(raw: &str) -> String {
|
||||
let bytes = raw.as_bytes();
|
||||
let mut out = Vec::with_capacity(bytes.len());
|
||||
|
|
@ -145,9 +145,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clamps_traversal_at_the_root() {
|
||||
// Ported from smolweb's TestPathTraversal: these must never reach above
|
||||
// the root, and since nothing is mounted at the clamped path they
|
||||
// resolve to a path that simply does not exist.
|
||||
// These must never reach above the root, and since nothing is mounted at
|
||||
// the clamped path they resolve to a path that simply does not exist.
|
||||
assert_eq!(clean("/../../etc/passwd"), "etc/passwd");
|
||||
assert_eq!(clean("/../../../../../../etc/passwd"), "etc/passwd");
|
||||
assert_eq!(clean("/foo/../../etc/passwd"), "etc/passwd");
|
||||
|
|
@ -159,7 +158,7 @@ mod tests {
|
|||
// before the clamp runs, or it would be treated as a literal segment.
|
||||
assert_eq!(clean("/%2e%2e/etc/passwd"), "etc/passwd");
|
||||
assert_eq!(clean("/%2E%2E/etc/passwd"), "etc/passwd");
|
||||
// An encoded separator becomes a separator, as Python's unquote does.
|
||||
// An encoded separator becomes a real one, so normalising then sees it.
|
||||
assert_eq!(clean("/dir%2fpage"), "dir/page");
|
||||
assert_eq!(clean("/hello%20world"), "hello world");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
//! [`crate::directives`].
|
||||
//!
|
||||
//! Together with that module this is the only part of the pipeline that opens
|
||||
//! files, which gives the root-containment check exactly one home. That closes
|
||||
//! the traversal smolweb has: md2txt resolves an include target and checks only
|
||||
//! that it exists, so `{.include ../../../../etc/passwd}` in any served document
|
||||
//! reads and emits that file.
|
||||
//! files, which gives the root-containment check exactly one home. An include
|
||||
//! target is canonicalised and required to be inside the root, so a target of
|
||||
//! `../../../../etc/passwd` resolves to nothing instead of being read: checking
|
||||
//! only that a target exists is what makes includes a traversal.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
|
|
@ -24,7 +24,7 @@ use crate::error::{Error, IncludeReason};
|
|||
const MAX_DEPTH: usize = 16;
|
||||
/// Caps on the expanded result. The cycle set is per-*stack*, so a diamond —
|
||||
/// `a` includes `b` and `c`, both include `d` — fans out exponentially without
|
||||
/// ever repeating a file on one path. smolweb has nothing that stops this.
|
||||
/// ever repeating a file on one path, so only these caps stop it.
|
||||
const MAX_LINES: usize = 200_000;
|
||||
const MAX_BYTES: usize = 8 * 1024 * 1024;
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ mod tests {
|
|||
assert_eq!(include_target("see ![[notes.md]] there"), None);
|
||||
assert_eq!(include_target("![[]]"), None);
|
||||
assert_eq!(include_target("![[unterminated"), None);
|
||||
// The directive smolweb also accepted is gone: one spelling, not two.
|
||||
// The brace-directive form is not accepted: one spelling, not two.
|
||||
assert_eq!(include_target("{.include notes.md}"), None);
|
||||
}
|
||||
|
||||
|
|
@ -263,8 +263,8 @@ mod tests {
|
|||
#[test]
|
||||
fn a_diamond_fan_out_is_stopped_by_the_size_cap() {
|
||||
// Each level doubles and no file repeats on any single path, so neither
|
||||
// the cycle set nor the depth cap catches it. smolweb expands this until
|
||||
// it runs out of memory.
|
||||
// the cycle set nor the depth cap catches it, which leaves the byte cap
|
||||
// as the only thing that stops it.
|
||||
let tree = Tree::new();
|
||||
tree.write("leaf.md", &"filler line\n".repeat(64));
|
||||
let mut previous = "leaf.md".to_string();
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
//! A format is a crate implementing [`Renderer`], registered at startup behind a
|
||||
//! cargo feature. The trait takes a parsed [`Doc`] rather than source text so
|
||||
//! that every format reads one parse: gemtext's `=>` link catalogue and the text
|
||||
//! formats' `[n]` references must agree about link identity and order, and in
|
||||
//! smolweb they can disagree because each library re-parses.
|
||||
//! formats' `[n]` references must agree about link identity and order, which
|
||||
//! cannot be relied on when each format parses the source for itself.
|
||||
//!
|
||||
//! A renderer must not open files or sockets. Includes and art are already
|
||||
//! resolved by the time it runs, which is what keeps the root-containment check
|
||||
|
|
|
|||
|
|
@ -267,8 +267,8 @@ mod tests {
|
|||
Site::new(root, registry(), vec!["stub".to_string()]).unwrap()
|
||||
}
|
||||
|
||||
/// Mirrors smolweb's `tests/conftest.py` fixture, so its assertions port
|
||||
/// across directly.
|
||||
/// One of each kind of thing a request can land on, shared by the resolution
|
||||
/// tests below.
|
||||
fn fixture() -> (tempfile::TempDir, Site) {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let root = dir.path();
|
||||
|
|
@ -449,9 +449,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn a_bare_unresolvable_segment_resolves_to_nothing() {
|
||||
// Regression carried over from smolweb: the Python reached this path
|
||||
// through `rpartition("/")`, where a bare top-level segment yields an
|
||||
// empty parent that must not be read as the root index.
|
||||
// The obvious implementation splits on the last `/`, where a bare
|
||||
// top-level segment yields an empty parent that must not then be read as
|
||||
// the root index.
|
||||
let (_dir, site) = fixture();
|
||||
assert_not_found(&site, "/totally-unresolvable-segment");
|
||||
}
|
||||
|
|
@ -568,9 +568,9 @@ mod tests {
|
|||
|
||||
#[cfg(test)]
|
||||
mod part_tests {
|
||||
//! Ported from smolweb's `TestWmlCardUrls`. A stub renderer stands in for a
|
||||
//! paginating format, so these rules are tested without the WML crate: core
|
||||
//! does not know which formats paginate, which is the point.
|
||||
//! A stub renderer stands in for a paginating format, so these rules are
|
||||
//! tested without the WML crate: core does not know which formats paginate,
|
||||
//! which is the point.
|
||||
|
||||
use std::fs;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue