Skip to content

Commit

Permalink
add link_checker settings for external_level and internal_level (#1848)
Browse files Browse the repository at this point in the history
* add external_level and internal_level

* remove unnecessary debug derive on LinkDef

* clarify doc comment about link check levels

* simplify link checker logging

* add missing warn prefix

* simplify link level logging, remove "Level" from linklevel variants

* remove link level config from test site

* switch back to using bail! from get_link_domain

* move console's deps to libs

* remove unnecessary reference

* calling console::error/warn directly

* emit one error, or one warning, per link checker run

* various link checker level changes

* add docs about link checker levels

* remove accidentally committed test site

* remove completed TODO
  • Loading branch information
mwcz authored May 11, 2022
1 parent 2291c6e commit 6240ed5
Show file tree
Hide file tree
Showing 23 changed files with 357 additions and 234 deletions.
17 changes: 14 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ time = "0.3"
name = "zola"

[dependencies]
atty = "0.2.11"
clap = { version = "3", features = ["derive"] }
termcolor = "1.0.4"
# Below is for the serve cmd
hyper = { version = "0.14.1", default-features = false, features = ["runtime", "server", "http2", "http1"] }
tokio = { version = "1.0.1", default-features = false, features = ["rt", "fs", "time"] }
Expand All @@ -39,6 +37,7 @@ mime_guess = "2.0"

site = { path = "components/site" }
errors = { path = "components/errors" }
console = { path = "components/console" }
utils = { path = "components/utils" }
libs = { path = "components/libs" }

Expand Down
18 changes: 18 additions & 0 deletions components/config/src/config/link_checker.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum LinkCheckerLevel {
#[serde(rename = "error")]
Error,
#[serde(rename = "warn")]
Warn,
}

impl Default for LinkCheckerLevel {
fn default() -> Self {
Self::Error
}
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct LinkChecker {
/// Skip link checking for these URL prefixes
pub skip_prefixes: Vec<String>,
/// Skip anchor checking for these URL prefixes
pub skip_anchor_prefixes: Vec<String>,
/// Emit either "error" or "warn" for broken internal links (including anchor links).
pub internal_level: LinkCheckerLevel,
/// Emit either "error" or "warn" for broken external links (including anchor links).
pub external_level: LinkCheckerLevel,
}
4 changes: 2 additions & 2 deletions components/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ mod theme;
use std::path::Path;

pub use crate::config::{
languages::LanguageOptions, link_checker::LinkChecker, search::Search, slugify::Slugify,
taxonomies::TaxonomyConfig, Config,
languages::LanguageOptions, link_checker::LinkChecker, link_checker::LinkCheckerLevel,
search::Search, slugify::Slugify, taxonomies::TaxonomyConfig, Config,
};
use errors::Result;

Expand Down
8 changes: 8 additions & 0 deletions components/console/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "console"
version = "0.1.0"
edition = "2021"

[dependencies]
errors = { path = "../errors" }
libs = { path = "../libs" }
57 changes: 57 additions & 0 deletions components/console/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::env;
use std::io::Write;

use libs::atty;
use libs::once_cell::sync::Lazy;
use libs::termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

/// Termcolor color choice.
/// We do not rely on ColorChoice::Auto behavior
/// as the check is already performed by has_color.
static COLOR_CHOICE: Lazy<ColorChoice> =
Lazy::new(|| if has_color() { ColorChoice::Always } else { ColorChoice::Never });

pub fn info(message: &str) {
colorize(message, ColorSpec::new().set_bold(true), StandardStream::stdout(*COLOR_CHOICE));
}

pub fn warn(message: &str) {
colorize(
&format!("{}{}", "Warning: ", message),
ColorSpec::new().set_bold(true).set_fg(Some(Color::Yellow)),
StandardStream::stdout(*COLOR_CHOICE),
);
}

pub fn success(message: &str) {
colorize(
message,
ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)),
StandardStream::stdout(*COLOR_CHOICE),
);
}

pub fn error(message: &str) {
colorize(
&format!("{}{}", "Error: ", message),
ColorSpec::new().set_bold(true).set_fg(Some(Color::Red)),
StandardStream::stderr(*COLOR_CHOICE),
);
}

/// Print a colorized message to stdout
fn colorize(message: &str, color: &ColorSpec, mut stream: StandardStream) {
stream.set_color(color).unwrap();
write!(stream, "{}", message).unwrap();
stream.set_color(&ColorSpec::new()).unwrap();
writeln!(stream).unwrap();
}

/// Check whether to output colors
fn has_color() -> bool {
let use_colors = env::var("CLICOLOR").unwrap_or_else(|_| "1".to_string()) != "0"
&& env::var("NO_COLOR").is_err();
let force_colors = env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".to_string()) != "0";

force_colors || use_colors && atty::is(atty::Stream::Stdout)
}
2 changes: 2 additions & 0 deletions components/libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
ahash = "0.7.6"
ammonia = "3"
atty = "0.2.11"
base64 = "0.13"
csv = "1"
elasticlunr-rs = {version = "2", default-features = false, features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] }
Expand Down Expand Up @@ -34,6 +35,7 @@ slug = "0.1"
svg_metadata = "0.4"
syntect = "5"
tera = { version = "1", features = ["preserve_order"] }
termcolor = "1.0.4"
time = "0.3"
toml = "0.5"
unic-langid = "0.9"
Expand Down
2 changes: 2 additions & 0 deletions components/libs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub use ahash;
pub use ammonia;
pub use atty;
pub use base64;
pub use csv;
pub use elasticlunr;
Expand Down Expand Up @@ -34,6 +35,7 @@ pub use slug;
pub use svg_metadata;
pub use syntect;
pub use tera;
pub use termcolor;
pub use time;
pub use toml;
pub use unic_langid;
Expand Down
1 change: 1 addition & 0 deletions components/markdown/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pest_derive = "2"
errors = { path = "../errors" }
utils = { path = "../utils" }
config = { path = "../config" }
console = { path = "../console" }
libs = { path = "../libs" }

[dev-dependencies]
Expand Down
16 changes: 14 additions & 2 deletions components/markdown/src/markdown.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::fmt::Write;

use errors::bail;
use libs::gh_emoji::Replacer as EmojiReplacer;
use libs::once_cell::sync::Lazy;
use libs::pulldown_cmark as cmark;
use libs::tera;

use crate::context::RenderContext;
use errors::{anyhow, Context, Error, Result};
use errors::{Context, Error, Result};
use libs::pulldown_cmark::escape::escape_html;
use utils::site::resolve_internal_link;
use utils::slugs::slugify_anchors;
Expand Down Expand Up @@ -139,7 +140,18 @@ fn fix_link(
resolved.permalink
}
Err(_) => {
return Err(anyhow!("Relative link {} not found.", link));
let msg = format!(
"Broken relative link `{}` in {}",
link,
context.current_page_path.unwrap_or("unknown"),
);
match context.config.link_checker.internal_level {
config::LinkCheckerLevel::Error => bail!(msg),
config::LinkCheckerLevel::Warn => {
console::warn(&msg);
link.to_string()
}
}
}
}
} else if is_external_link(link) {
Expand Down
1 change: 1 addition & 0 deletions components/site/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ serde = { version = "1.0", features = ["derive"] }

errors = { path = "../errors" }
config = { path = "../config" }
console = { path = "../console" }
utils = { path = "../utils" }
templates = { path = "../templates" }
search = { path = "../search" }
Expand Down
39 changes: 37 additions & 2 deletions components/site/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,45 @@ impl Site {
tpls::register_tera_global_fns(self);

// Needs to be done after rendering markdown as we only get the anchors at that point
link_checking::check_internal_links_with_anchors(self)?;
let internal_link_messages = link_checking::check_internal_links_with_anchors(self);

// log any broken internal links and error out if needed
if let Err(messages) = internal_link_messages {
let messages: Vec<String> = messages
.iter()
.enumerate()
.map(|(i, msg)| format!(" {}. {}", i + 1, msg))
.collect();
let msg = format!(
"Found {} broken internal anchor link(s)\n{}",
messages.len(),
messages.join("\n")
);
match self.config.link_checker.internal_level {
config::LinkCheckerLevel::Warn => console::warn(&msg),
config::LinkCheckerLevel::Error => return Err(anyhow!(msg.clone())),
}
}

// check external links, log the results, and error out if needed
if self.config.is_in_check_mode() {
link_checking::check_external_links(self)?;
let external_link_messages = link_checking::check_external_links(self);
if let Err(messages) = external_link_messages {
let messages: Vec<String> = messages
.iter()
.enumerate()
.map(|(i, msg)| format!(" {}. {}", i + 1, msg))
.collect();
let msg = format!(
"Found {} broken external link(s)\n{}",
messages.len(),
messages.join("\n")
);
match self.config.link_checker.external_level {
config::LinkCheckerLevel::Warn => console::warn(&msg),
config::LinkCheckerLevel::Error => return Err(anyhow!(msg.clone())),
}
}
}

Ok(())
Expand Down
Loading

0 comments on commit 6240ed5

Please sign in to comment.