Skip to content

Commit

Permalink
allow to set collapsible default value for each directive
Browse files Browse the repository at this point in the history
  • Loading branch information
yannickseurin authored and tommilligan committed May 19, 2024
1 parent c17a664 commit 5d5b73d
Show file tree
Hide file tree
Showing 9 changed files with 483 additions and 148 deletions.
8 changes: 3 additions & 5 deletions book/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ git-repository-url = "https://github.com/tommilligan/mdbook-admonish"

[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.0.1" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.0.2" # do not edit: managed by `mdbook-admonish install`

[[preprocessor.admonish.custom]]
directive = "expensive"
icon = "./money-bag.svg"
color = "#24ab38"
[preprocessor.admonish.directive.custom]
expensive = { icon = "./money-bag.svg", color = "#24ab38" }

[preprocessor.toc]
command = "mdbook-toc"
Expand Down
36 changes: 29 additions & 7 deletions book/src/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,28 +75,50 @@ Subfields:
- For the `html` renderer, the default value is `html`.
- For all other renderers, the default value is `preserve`.

### `custom`
### `directive`

Optional.

Additional type of block to support.
You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.
Settings relating to each type of block.

#### `builtin`

Optional.

Override the settings of a builtin directive.

Add blocks using TOML's [Array of Tables](https://toml.io/en/v1.0.0#array-of-tables) notation:
The subkey of `builtin` is the directive to override. This must be the first directive listed in the [Directives](#directives) section below, e.g. `warning` (not `caution` or other aliases).

```toml
[[preprocessor.admonish.custom]]
directive = "expensive"
[preprocessor.admonish.directive.builtin.warning]
collapsible = true
```

Subfields:

- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.

#### `custom`

Optional.

Additional types of block to support. The subkey of `custom` is the new directive to support.

You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.

```toml
[preprocessor.admonish.directive.custom.expensive]
icon = "./money-bag.svg"
color = "#24ab38"
collapsible = true
aliases = ["money", "cash", "budget"]
```

Subfields:

- `directive`: The keyword to use this type of block.
- `icon`: A filepath relative to the book root to load an SVG icon from.
- `color`: An RGB hex encoded color to use for the icon.
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
- `aliases` (optional): One or more alternative directives to use this block.
- `title` (optional): The default title for this type of block. If not specified, defaults to the directive in title case. To give each alias a custom title, add multiple custom blocks.

Expand Down
180 changes: 165 additions & 15 deletions src/book_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

use crate::types::AdmonitionDefaults;
use crate::types::{AdmonitionDefaults, BuiltinDirective, BuiltinDirectiveConfig};

/// Loads the plugin configuration from mdbook internals.
///
Expand All @@ -20,9 +20,42 @@ pub(crate) fn admonish_config_from_context(ctx: &PreprocessorContext) -> Result<
}

pub(crate) fn admonish_config_from_str(data: &str) -> Result<Config> {
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")
let readonly: ConfigReadonly =
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")?;
let config = readonly.into();
log::debug!("Loaded admonish config: {:?}", config);
Ok(config)
}

/// All valid input states including back-compatibility fields.
///
/// This struct deliberately does not implement Serialize as it never meant to
/// be written, only converted to Config.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
struct ConfigReadonly {
#[serde(default)]
pub on_failure: OnFailure,

#[serde(default)]
pub default: AdmonitionDefaults,

#[serde(default)]
pub renderer: HashMap<String, RendererConfig>,

#[serde(default)]
pub assets_version: Option<String>,

#[serde(default)]
pub custom: Vec<CustomDirectiveReadonly>,

#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,

#[serde(default)]
pub directive: DirectiveConfig,
}

/// The canonical config format, without back-compatibility
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct Config {
#[serde(default)]
Expand All @@ -38,14 +71,50 @@ pub(crate) struct Config {
pub assets_version: Option<String>,

#[serde(default)]
pub custom: Vec<CustomDirective>,
pub directive: DirectiveConfig,
}

impl From<ConfigReadonly> for Config {
fn from(other: ConfigReadonly) -> Self {
let ConfigReadonly {
on_failure,
default,
renderer,
assets_version,
custom,
builtin,
mut directive,
} = other;

// Merge deprecated config fields into main config object
directive.custom.extend(
custom
.into_iter()
.map(|CustomDirectiveReadonly { directive, config }| (directive, config)),
);
directive.builtin.extend(builtin);

Self {
on_failure,
default,
renderer,
assets_version,
directive,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct DirectiveConfig {
#[serde(default)]
pub custom: HashMap<String, CustomDirective>,

#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirective {
/// The primary directive. Used for CSS classnames
pub directive: String,

/// Path to an SVG file, relative to the book root.
pub icon: PathBuf,

Expand All @@ -59,6 +128,20 @@ pub(crate) struct CustomDirective {
/// Title to use, human readable.
#[serde(default)]
pub title: Option<String>,

/// Default collapsible value.
#[serde(default)]
pub collapsible: Option<bool>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirectiveReadonly {
/// The primary directive. Used for CSS classnames
pub directive: String,

/// Path to an SVG file, relative to the book root.
#[serde(flatten)]
config: CustomDirective,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
Expand Down Expand Up @@ -92,6 +175,8 @@ mod test {
use super::*;
use pretty_assertions::assert_eq;

use crate::types::BuiltinDirective;

#[test]
fn empty_config_okay() -> Result<()> {
let actual = admonish_config_from_str("")?;
Expand Down Expand Up @@ -120,6 +205,57 @@ mod test {
Ok(())
}

#[test]
fn merge_old_and_new_custom_directives() -> Result<()> {
let serialized = r##"
[directive.custom.purple]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "Purple"
collapsible = true
[[custom]]
directive = "blue"
icon = "/tmp/test-directive.svg"
color = "#0038A8"
aliases = []
title = "Blue"
"##;
let expected = Config {
directive: DirectiveConfig {
custom: HashMap::from([
(
"purple".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("Purple".to_owned()),
collapsible: Some(true),
},
),
(
"blue".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((0, 56, 168)),
aliases: vec![],
title: Some("Blue".to_owned()),
collapsible: None,
},
),
]),
..Default::default()
},
..Default::default()
};

let actual = admonish_config_from_str(serialized)?;
assert_eq!(actual, expected);
Ok(())
}

#[test]
fn full_config_roundtrip() -> Result<()> {
let input = Config {
Expand All @@ -129,13 +265,24 @@ mod test {
title: Some("".to_owned()),
},
assets_version: Some("1.1.1".to_owned()),
custom: vec![CustomDirective {
directive: "test-directive".to_owned(),
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("test-directive-title".to_owned()),
}],
directive: DirectiveConfig {
custom: HashMap::from([(
"test-directive".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("test-directive-title".to_owned()),
collapsible: Some(true),
},
)]),
builtin: HashMap::from([(
BuiltinDirective::Warning,
BuiltinDirectiveConfig {
collapsible: Some(true),
},
)]),
},
on_failure: OnFailure::Bail,
renderer: HashMap::from([(
"test-mode".to_owned(),
Expand All @@ -156,12 +303,15 @@ css_id_prefix = "flam-"
[renderer.test-mode]
render_mode = "strip"
[[custom]]
directive = "test-directive"
[directive.custom.test-directive]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "test-directive-title"
collapsible = true
[directive.builtin.warning]
collapsible = true
"##;

let serialized = toml::to_string(&input)?;
Expand Down
6 changes: 3 additions & 3 deletions src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,18 @@ fn directive_css(name: &str, svg_data: &str, tint: HexColor) -> String {
#[doc(hidden)]
pub fn css_from_config(book_dir: &Path, config: &str) -> Result<String> {
let config = crate::book_config::admonish_config_from_str(config)?;
let custom_directives = config.custom;
let custom_directives = config.directive.custom;

if custom_directives.is_empty() {
return Err(anyhow!("No custom directives provided"));
}

log::info!("Loaded {} custom directives", custom_directives.len());
let mut css = String::new();
for directive in custom_directives.iter() {
for (directive_name, directive) in custom_directives.iter() {
let svg = fs::read_to_string(book_dir.join(&directive.icon))
.with_context(|| format!("can't read icon file '{}'", directive.icon.display()))?;
css.push_str(&directive_css(&directive.directive, &svg, directive.color));
css.push_str(&directive_css(directive_name, &svg, directive.color));
}
Ok(css)
}
Expand Down
Loading

0 comments on commit 5d5b73d

Please sign in to comment.