asciidoc-parser 0.19.0

Parser for AsciiDoc format
Documentation
use crate::tests::prelude::*;

track_file!("ref/asciidoc-lang/docs/modules/macros/pages/icons-image.adoc");

non_normative!(
    r#"
= Image Icons Mode

Setting the `icons` attribute to `image` (or leaving it empty) instructs the AsciiDoc processor to use images for icons.
This page defines where the processor looks for these image files by default, which image file extension it prepends, and how to configure both.

"#
);

#[test]
fn enable_image_based_icons() {
    verifies!(
        r#"
== Enable image-based icons

To enable image-based icons, you set the `icons` attribute in the document header to the value `image`.

[source]
----
= Document Title
:icons: image
----

This setting has no affect on the parsing of the AsciiDoc document.
It only influences the output generated by the converters.

"#
    );

    // Setting `icons` to `image` resolves an icon macro to an `<img>` element.
    let doc = Parser::default().parse(":icons: image\n\nicon:tags[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags"></span>"#
    );

    // Leaving the `icons` attribute empty selects the same image mode.
    let doc = Parser::default().parse(":icons:\n\nicon:tags[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags"></span>"#
    );
}

#[test]
fn default_icons_directory_and_type() {
    verifies!(
        r#"
== Default icons directory and type

By default, the AsciiDoc processor will look for icons in the [.path]_icons_ directory relative to the value of the `imagesdir` attribute.
If you have not configured either attribute, that path resolves to [.path]_./images/icons_.

The processor won't look for icons of any type (i.e., format).
Instead, it will look for icons that have the _.png_ file extension.

"#
    );

    // With neither `iconsdir` nor `imagesdir` configured, an icon resolves under
    // the default `./images/icons` directory with the `.png` extension (the "if
    // you have not configured either attribute" outcome).
    let doc = Parser::default().parse(":icons: image\n\nicon:note[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/note.png" alt="note"></span>"#
    );

    // "Relative to the value of the `imagesdir` attribute": with a non-empty
    // `imagesdir` and no explicit `iconsdir`, the icons directory is derived as
    // `{imagesdir}/icons`.
    let doc = Parser::default().parse(":icons: image\n:imagesdir: assets\n\nicon:note[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="assets/icons/note.png" alt="note"></span>"#
    );

    // An explicit `iconsdir` in the header wins over the `imagesdir`-relative
    // default.
    let doc = Parser::default()
        .parse(":icons: image\n:imagesdir: assets\n:iconsdir: pics\n\nicon:note[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="pics/note.png" alt="note"></span>"#
    );
}

// The admonition example resolves the same default path for an admonition
// block's icon. This crate renders inline content only (not the surrounding
// admonition block markup that carries the icon), so the resolution is verified
// above via the icon macro rather than through the block below.
non_normative!(
    r#"
Let's assume you have the following NOTE admonition block in your document:

[source]
----
NOTE: Remember the milk!
----

The AsciiDoc processor will resolve the admonition icon to [.path]_./images/icons/note.png_.

"#
);

#[test]
fn configure_iconsdir() {
    verifies!(
        r#"
== Configure the icons directory using iconsdir

To change where the AsciiDoc processor looks for icons, you can specify a different location using the `iconsdir` attribute.

For example:

[source]
----
= Document Title
:icons: image
:iconsdir: icons
----

When converting this document, the AsciiDoc processor will look for images in the [.path]_icons_ directory instead of the default [.path]_./images/icons_.

"#
    );

    // With `iconsdir` set to `icons`, the icon resolves under that directory
    // instead of the default `./images/icons`.
    let doc = Parser::default().parse(":icons: image\n:iconsdir: icons\n\nicon:note[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="icons/note.png" alt="note"></span>"#
    );
}

#[test]
fn configure_icontype() {
    verifies!(
        r#"
== Configure the icon type using icontype

If the icon path is derived, such as for an admonition icon or if the target of the icon macro does not have a file extension, the AsciiDoc processor will use the `icontype` attribute to determine which image type (i.e., format) to look for.
By default, the value of this attribute is `png`, so the processor will look for an image with the file extension `.png`.

You can use the `icontype` document attribute to configure the default icon type.

For example:

[source]
----
= Document Title
:icons: image
:icontype: svg
----

"#
    );

    // By default the derived extension is `.png`.
    let doc = Parser::default().parse(":icons: image\n\nicon:heart[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/heart.png" alt="heart"></span>"#
    );

    // Setting `icontype` to `svg` changes the derived extension to `.svg`.
    let doc = Parser::default().parse(":icons: image\n:icontype: svg\n\nicon:heart[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/heart.svg" alt="heart"></span>"#
    );
}

// The admonition-specific outcome (resolving `note.svg`) is not emitted by this
// crate's inline renderer; the general derived-extension behavior is verified
// above via the icon macro.
non_normative!(
    r#"
For NOTE admonitions, the AsciiDoc processor will now look for the image [.path]_note.svg_ in the `iconsdir` instead of [.path]_note.png_.

"#
);

#[test]
fn icontype_ignored_when_target_has_extension() {
    verifies!(
        r#"
The value of the `icontype` attribute is ignored for the icon macro if the target has a file extension.
It's only used when the icon type must be inferred.
"#
    );

    // The target `heart.png` already carries an extension, so `icontype=svg` is
    // ignored and the target is used verbatim (no `.svg` is appended).
    let doc = Parser::default().parse(":icons: image\n:icontype: svg\n\nicon:heart.png[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="./images/icons/heart.png" alt="heart"></span>"#
    );
}

// Not part of the spec text, but pins an edge of the `imagesdir`-relative
// `iconsdir` default: an `iconsdir` configured via the API (rather than the
// document header) still wins over the derived `{imagesdir}/icons` default,
// because its stored value differs from the built-in default.
#[test]
fn api_iconsdir_wins_over_imagesdir_default() {
    let doc = Parser::default()
        .with_intrinsic_attribute("iconsdir", "pics", ModificationContext::Anywhere)
        .parse(":icons: image\n:imagesdir: assets\n\nicon:note[]");
    assert_eq!(
        rendered_paragraphs(&doc).join("\n"),
        r#"<span class="icon"><img src="pics/note.png" alt="note"></span>"#
    );
}