mirror of
https://github.com/zensical/zensical.git
synced 2026-09-24 15:25:40 +00:00
refactor: finalize scheduler and plugin integration
Signed-off-by: squidfunk <martin.donath@squidfunk.com>
This commit is contained in:
@@ -3,6 +3,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// All contributions are certified under the DCO
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
//! Native compatibility pipeline for filesystem-backed awesome navigation.
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
@@ -26,6 +46,21 @@ mod pattern;
|
||||
mod resolver;
|
||||
mod sort;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Enums
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Level {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Structs
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Native awesome-nav pipeline.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AwesomeNav {
|
||||
@@ -40,29 +75,22 @@ pub struct Dependencies<'a> {
|
||||
pub pages: &'a Stream<Id, Page>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Level {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Diagnostic {
|
||||
pub level: Level,
|
||||
pub message: String,
|
||||
struct Diagnostic {
|
||||
level: Level,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Logs {
|
||||
pub nav_override: Level,
|
||||
pub root_title: Level,
|
||||
pub root_hide: Level,
|
||||
pub no_matches: Level,
|
||||
struct Logs {
|
||||
nav_override: Level,
|
||||
root_title: Level,
|
||||
root_hide: Level,
|
||||
no_matches: Level,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Settings {
|
||||
struct Settings {
|
||||
enabled: bool,
|
||||
docs: String,
|
||||
filename: String,
|
||||
@@ -89,6 +117,10 @@ struct Pages(Arc<Vec<Page>>);
|
||||
|
||||
impl Value for Pages {}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Implementations
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl AwesomeNav {
|
||||
/// Resolves immutable settings for one workflow lifetime.
|
||||
pub fn new(config: &Config, strict: bool) -> Result<Self> {
|
||||
@@ -183,6 +215,10 @@ impl Logs {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn level(value: Option<&str>, default: Level) -> Result<Level> {
|
||||
match value {
|
||||
None => Ok(default),
|
||||
@@ -219,6 +255,10 @@ fn is_config_file(path: &SourcePath, filename: &str) -> bool {
|
||||
.is_some_and(|prefix| prefix.ends_with('/'))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_config_file, level, Level};
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// All contributions are certified under the DCO
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
//! Strict `.nav.yml` parsing and configuration models.
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// All contributions are certified under the DCO
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
//! Replaceable awesome-nav pattern matching boundary.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// All contributions are certified under the DCO
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
//! Filesystem-derived awesome-nav resolution.
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// All contributions are certified under the DCO
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
//! Awesome-nav sorting compatible with upstream's natsort settings.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
|
||||
@@ -173,7 +173,7 @@ impl Meta {
|
||||
}
|
||||
|
||||
/// Returns the immutable settings shared with resource classification.
|
||||
pub(crate) fn settings(&self) -> &Settings {
|
||||
pub fn settings(&self) -> &Settings {
|
||||
&self.settings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,17 +89,17 @@ impl Minify {
|
||||
}
|
||||
|
||||
/// Returns the normalized plugin configuration for the asset stage.
|
||||
pub(super) fn config(&self) -> &MinifyPluginConfig {
|
||||
fn config(&self) -> &MinifyPluginConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Returns the site output directory owned by this pipeline.
|
||||
pub(super) fn output(&self) -> &OutputRoot {
|
||||
fn output(&self) -> &OutputRoot {
|
||||
&self.output
|
||||
}
|
||||
|
||||
/// Returns the template-visible project configuration.
|
||||
pub(super) fn project(&self) -> &Arc<Project> {
|
||||
fn project(&self) -> &Arc<Project> {
|
||||
&self.project
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ impl Value for Manifest {}
|
||||
|
||||
/// Transforms selected resources, writes every effective asset, and publishes
|
||||
/// the project view whose configured asset paths name the emitted files.
|
||||
pub(super) fn setup(
|
||||
pub fn setup(
|
||||
plugin: &Minify, resources: &Stream<Id, Resource>,
|
||||
) -> Signal<Id, Manifest> {
|
||||
let settings = Settings::new(plugin.config());
|
||||
|
||||
@@ -83,19 +83,16 @@ impl Mkdocstrings {
|
||||
module
|
||||
.call_method1("get_inventory", (cached,))?
|
||||
.extract::<Vec<u8>>()
|
||||
});
|
||||
})?;
|
||||
|
||||
if let Ok(data) = data {
|
||||
let path = pipeline.output.join(
|
||||
&"objects.inv"
|
||||
.parse::<SitePath>()
|
||||
.expect("static site path"),
|
||||
);
|
||||
let _ = fs::create_dir_all(path.parent().expect("invariant"));
|
||||
let _ = fs::write(path, &data);
|
||||
let _ = fs::create_dir_all(&pipeline.cache);
|
||||
let _ = fs::write(&cache_path, &data);
|
||||
}
|
||||
let path = pipeline.output.join(
|
||||
&"objects.inv".parse::<SitePath>().expect("static site path"),
|
||||
);
|
||||
fs::create_dir_all(path.parent().expect("invariant"))?;
|
||||
fs::write(path, &data)?;
|
||||
fs::create_dir_all(&pipeline.cache)?;
|
||||
fs::write(&cache_path, &data)?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +89,16 @@ impl Resources {
|
||||
}
|
||||
|
||||
/// Classifies sources and resolves docs-over-theme precedence.
|
||||
pub fn setup(&self, deps: Dependencies<'_>) -> Stream<Id, Resource> {
|
||||
pub fn setup(
|
||||
&self, dependencies: Dependencies<'_>,
|
||||
) -> Stream<Id, Resource> {
|
||||
let classifier = self.classifier.clone();
|
||||
let resources =
|
||||
deps.sources.filter_map(move |id: &Id, source: &Source| {
|
||||
classifier.classify(id, source)
|
||||
});
|
||||
dependencies
|
||||
.sources
|
||||
.filter_map(move |id: &Id, source: &Source| {
|
||||
classifier.classify(id, source)
|
||||
});
|
||||
|
||||
// Settle precedence before consumers transform or write a resource.
|
||||
// Removing a docs override therefore reveals its theme fallback in
|
||||
|
||||
+41
-10
@@ -239,7 +239,7 @@ fn run(config_file: &PathBuf, mode: Mode) -> PyResult<bool> {
|
||||
let run = runner
|
||||
.settle()
|
||||
.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
|
||||
report_failures(&run)?;
|
||||
report_failures(&run);
|
||||
// Create channel for reload notifications
|
||||
let (sender, receiver) = unbounded();
|
||||
|
||||
@@ -315,7 +315,7 @@ fn run(config_file: &PathBuf, mode: Mode) -> PyResult<bool> {
|
||||
let run = runner
|
||||
.settle()
|
||||
.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
|
||||
report_failures(&run)?;
|
||||
report_failures(&run);
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
Err(RecvTimeoutError::Disconnected) => match mode {
|
||||
@@ -345,14 +345,21 @@ fn run(config_file: &PathBuf, mode: Mode) -> PyResult<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Returns the first action failure reported by one settled run.
|
||||
fn report_failures(run: &zrx::stream::Run<Id>) -> PyResult<()> {
|
||||
for invocation in run.report().invocations() {
|
||||
if let Some(failure) = invocation.outcomes.failures().first() {
|
||||
return Err(PyRuntimeError::new_err(format!("{failure:#}")));
|
||||
}
|
||||
/// Prints action failures reported by one settled run.
|
||||
fn report_failures(run: &zrx::stream::Run<Id>) {
|
||||
for failure in reported_failures(run) {
|
||||
eprintln!("Error - {failure}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Formats action failures reported by one settled run.
|
||||
fn reported_failures(run: &zrx::stream::Run<Id>) -> Vec<String> {
|
||||
run.report()
|
||||
.invocations()
|
||||
.iter()
|
||||
.flat_map(|invocation| invocation.outcomes.failures())
|
||||
.map(|failure| format!("{failure:#}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -410,8 +417,32 @@ fn zensical(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
use zrx::id::Id;
|
||||
use zrx::stream::{Key, Workflow};
|
||||
|
||||
use super::clear_dir;
|
||||
use super::{clear_dir, reported_failures};
|
||||
|
||||
#[test]
|
||||
fn operator_failures_are_formatted_for_cli_reporting() {
|
||||
let workflow = Workflow::<Id>::build(|workflow| {
|
||||
let input = workflow.input::<u64>();
|
||||
let output = input.map(|_: &u64| -> anyhow::Result<u64> {
|
||||
anyhow::bail!("broken callback")
|
||||
});
|
||||
workflow.output(&output);
|
||||
});
|
||||
let mut runner = workflow.runner().unwrap();
|
||||
let input = runner.input::<u64>().unwrap();
|
||||
let mut revision = input.begin().unwrap();
|
||||
revision
|
||||
.insert(Key::from_iter(std::iter::empty()), 1)
|
||||
.unwrap();
|
||||
let _input = revision.seal().unwrap();
|
||||
let run = runner.settle().unwrap();
|
||||
|
||||
assert_eq!(reported_failures(&run), ["broken callback"]);
|
||||
assert_eq!(runner.errors().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_dir_removes_non_hidden_file() {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
|
||||
Vendored
+31
-1
@@ -171,6 +171,18 @@ class TestPluginShimming:
|
||||
config = self._parse_yaml(tmp_path, plugins={"glightbox": {}})
|
||||
assert GlightboxExtension.name in config["markdown_extensions"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entry", ["material/meta", {"material/meta": None}]
|
||||
)
|
||||
def test_material_meta_presence_enables_defaults(
|
||||
self, tmp_path: Path, entry: object
|
||||
) -> None:
|
||||
config = self._parse_yaml(tmp_path, plugins=[entry])
|
||||
assert config["plugins"]["meta"]["config"] == {
|
||||
"enabled": True,
|
||||
"meta_file": ".meta.yml",
|
||||
}
|
||||
|
||||
def test_material_meta_plugin_is_normalized(self, tmp_path: Path) -> None:
|
||||
config = self._parse_yaml(
|
||||
tmp_path,
|
||||
@@ -183,6 +195,25 @@ class TestPluginShimming:
|
||||
}
|
||||
assert config["plugins_hash"] == cfg_module._hash(config["plugins"])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"material/meta",
|
||||
"redirects",
|
||||
"minify",
|
||||
"literate-nav",
|
||||
"awesome-nav",
|
||||
],
|
||||
)
|
||||
def test_native_plugin_configuration_must_be_a_mapping(
|
||||
self, tmp_path: Path, name: str
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
cfg_module.ConfigurationError,
|
||||
match=rf"{name} configuration must be a mapping",
|
||||
):
|
||||
self._parse_yaml(tmp_path, plugins={name: []})
|
||||
|
||||
def test_redirects_plugin_is_normalized(self, tmp_path: Path) -> None:
|
||||
config = self._parse_yaml(
|
||||
tmp_path,
|
||||
@@ -292,7 +323,6 @@ class TestPluginShimming:
|
||||
@pytest.mark.parametrize(
|
||||
("plugin", "message"),
|
||||
[
|
||||
([], "configuration must be a mapping"),
|
||||
({"unknown": True}, "unknown awesome-nav option"),
|
||||
({"filename": 42}, "filename must be a string"),
|
||||
({"filename": ""}, "filename must not be empty"),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
|
||||
+28
-42
@@ -1292,8 +1292,8 @@ def _convert_plugin_markdown_extensions(
|
||||
|
||||
def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
"""Convert plugins configuration to something we can work with."""
|
||||
plugins = {}
|
||||
tags = []
|
||||
plugins: dict[str, Any] = {}
|
||||
tags: list[dict[str, Any]] = []
|
||||
|
||||
def add(name: str, data: Any) -> None:
|
||||
"""Preserve tags instances while retaining legacy map semantics."""
|
||||
@@ -1334,35 +1334,23 @@ def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
|
||||
# Consume Material's public plugin name and normalize it to the internal
|
||||
# identifier extracted into typed Rust configuration.
|
||||
material_meta = plugins.pop("material/meta", None)
|
||||
if material_meta is None:
|
||||
meta = {"enabled": False, "meta_file": ".meta.yml"}
|
||||
else:
|
||||
meta = dict(material_meta or {})
|
||||
set_default(meta, "enabled", True, bool)
|
||||
set_default(meta, "meta_file", ".meta.yml", str)
|
||||
present, meta = _pop_plugin_config(plugins, "material/meta")
|
||||
set_default(meta, "enabled", present, bool)
|
||||
set_default(meta, "meta_file", ".meta.yml", str)
|
||||
plugins["meta"] = meta
|
||||
|
||||
# Normalize redirects into typed native configuration. The enabled flag is
|
||||
# internal; plugin presence retains MkDocs' activation semantics.
|
||||
if "redirects" not in plugins:
|
||||
redirects = {"enabled": False, "redirect_maps": {}}
|
||||
else:
|
||||
redirects = dict(plugins["redirects"] or {})
|
||||
set_default(redirects, "enabled", True, bool)
|
||||
set_default(redirects, "redirect_maps", {}, dict)
|
||||
present, redirects = _pop_plugin_config(plugins, "redirects")
|
||||
set_default(redirects, "enabled", present, bool)
|
||||
set_default(redirects, "redirect_maps", {}, dict)
|
||||
plugins["redirects"] = redirects
|
||||
|
||||
# Normalize the complete mkdocs-minify-plugin configuration surface. Asset
|
||||
# settings are retained for the dedicated copy/output stage; the inline
|
||||
# switches are Zensical extensions handled by the final HTML pass.
|
||||
minify: dict[str, Any]
|
||||
if "minify" not in plugins:
|
||||
minify = {"enabled": False}
|
||||
else:
|
||||
minify = dict(plugins["minify"] or {})
|
||||
set_default(minify, "enabled", True, bool)
|
||||
|
||||
present, minify = _pop_plugin_config(plugins, "minify")
|
||||
set_default(minify, "enabled", present, bool)
|
||||
set_default(minify, "minify_html", False, bool)
|
||||
set_default(minify, "minify_js", False, bool)
|
||||
set_default(minify, "minify_css", False, bool)
|
||||
@@ -1401,13 +1389,8 @@ def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
# Normalize mkdocs-literate-nav without importing or executing the plugin.
|
||||
# Python retains extension objects and callables for the narrow Markdown
|
||||
# rendering boundary; Rust owns discovery and navigation resolution.
|
||||
literate_nav: dict[str, Any]
|
||||
if "literate-nav" not in plugins:
|
||||
literate_nav = {"enabled": False}
|
||||
else:
|
||||
literate_nav_config = plugins.pop("literate-nav")
|
||||
literate_nav = dict(literate_nav_config or {})
|
||||
set_default(literate_nav, "enabled", True, bool)
|
||||
present, literate_nav = _pop_plugin_config(plugins, "literate-nav")
|
||||
set_default(literate_nav, "enabled", present, bool)
|
||||
set_default(literate_nav, "nav_file", "SUMMARY.md", str)
|
||||
set_default(literate_nav, "implicit_index", False, bool)
|
||||
set_default(literate_nav, "tab_length", 4, int)
|
||||
@@ -1420,19 +1403,8 @@ def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
|
||||
# Normalize mkdocs-awesome-nav without importing or executing the plugin.
|
||||
# Rust owns discovery, YAML parsing, matching and navigation resolution.
|
||||
awesome_nav: dict[str, Any]
|
||||
if "awesome-nav" not in plugins:
|
||||
awesome_nav = {"enabled": False}
|
||||
else:
|
||||
awesome_nav_config = plugins.pop("awesome-nav")
|
||||
if awesome_nav_config is not None and not isinstance(
|
||||
awesome_nav_config, dict
|
||||
):
|
||||
raise ConfigurationError(
|
||||
"awesome-nav configuration must be a mapping"
|
||||
)
|
||||
awesome_nav = dict(awesome_nav_config or {})
|
||||
set_default(awesome_nav, "enabled", True, bool)
|
||||
present, awesome_nav = _pop_plugin_config(plugins, "awesome-nav")
|
||||
set_default(awesome_nav, "enabled", present, bool)
|
||||
unknown = set(awesome_nav) - {"enabled", "filename", "logs"}
|
||||
if unknown:
|
||||
option = sorted(unknown)[0]
|
||||
@@ -1516,3 +1488,17 @@ def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
|
||||
# Return plugins
|
||||
return plugins
|
||||
|
||||
|
||||
def _pop_plugin_config(
|
||||
plugins: dict[str, Any], name: str
|
||||
) -> tuple[bool, dict[str, Any]]:
|
||||
"""Consume one optional mapping while preserving presence semantics."""
|
||||
if name not in plugins:
|
||||
return False, {}
|
||||
value = plugins.pop(name)
|
||||
if value is None:
|
||||
return True, {}
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigurationError(f"{name} configuration must be a mapping")
|
||||
return True, dict(value)
|
||||
|
||||
Reference in New Issue
Block a user