asciidoc-parser 0.19.0

Parser for AsciiDoc format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
use crate::tests::prelude::*;

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

non_normative!(
    r#"
= Icon Macro

In addition to built-in icons, you can add icons anywhere in your content where macros are substituted using the icon macro.
This page covers the anatomy of the icon macro, how the target is resolved, and what features it support (subject to the icon mode).

"#
);

mod anatomy {
    use crate::tests::prelude::*;

    non_normative!(
        r#"
== Anatomy

The icon macro is an inline macro.
Like other inline macros, its syntax follows the familiar pattern of the macro name and target separated by a colon followed by an attribute list enclosed in square brackets.

[source]
----
icon:<target>[<attrlist>]
----

The `<target>` is the icon name or path.
The `<attrlist>` specifies various named attributes to configure how the icon is displayed.

"#
    );

    #[test]
    fn example() {
        verifies!(
            r#"
For example:

[source]
----
icon:heart[2x,role=red]
----

"#
        );

        let doc = Parser::default().parse("icon:heart[2x,role=red]");

        assert_eq!(
            doc,
            Document {
                header: Header {
                    title_source: None,
                    title: None,
                    attributes: &[],
                    author_line: None,
                    revision_line: None,
                    comments: &[],
                    source: Span {
                        data: "",
                        line: 1,
                        col: 1,
                        offset: 0,
                    },
                },
                blocks: &[Block::Simple(SimpleBlock {
                    content: Content {
                        original: Span {
                            data: "icon:heart[2x,role=red]",
                            line: 1,
                            col: 1,
                            offset: 0,
                        },
                        rendered: "<span class=\"icon red\">[heart&#93;</span>",
                    },
                    source: Span {
                        data: "icon:heart[2x,role=red]",
                        line: 1,
                        col: 1,
                        offset: 0,
                    },
                    style: SimpleBlockStyle::Paragraph,
                    title_source: None,
                    title: None,
                    caption: None,
                    number: None,
                    anchor: None,
                    anchor_reftext: None,
                    attrlist: None,
                },),],
                source: Span {
                    data: "icon:heart[2x,role=red]",
                    line: 1,
                    col: 1,
                    offset: 0,
                },
                warnings: &[],
                source_map: SourceMap(&[]),
                catalog: Catalog::default(),
            }
        );
    }
}

mod example {
    use crate::tests::prelude::*;

    #[test]
    fn tags_example() {
        verifies!(
            r#"
== Example

Here's an example that shows how to inserts an icon named _tags_ in front of a list of tag names.

[source]
----
icon:tags[] ruby, asciidoctor
----

Here's how the HTML converter converts an icon macro when the `icons` attribute is not set or empty.

.Result: HTML output
[source,html]
----
<div class="paragraph">
  <p><span class="image"><img src="./images/icons/tags.png" alt="tags"></span> ruby, asciidoctor</p>
</div>
----

"#
        );

        // The spec's "not set or empty" wording is loose: with the `icons`
        // attribute genuinely unset the icon renders as bracketed fallback text
        // (see `how_the_icon_is_resolved` below); the image `<img>` output shown
        // here is produced when `icons` is *empty* (i.e. image mode). This crate
        // renders only the inline fragment, so the `<span class="icon">` wrapper
        // appears without the enclosing `<div class="paragraph"><p>` block markup
        // shown above. It also uses `class="icon"` (matching Asciidoctor's actual
        // output) rather than the `class="image"` printed in this spec example.
        let doc = Parser::default().parse(":icons:\n\nicon:tags[] ruby, asciidoctor");

        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags"></span> ruby, asciidoctor"#
        );
    }

    // The DocBook converter is not implemented by this crate, so the
    // `<inlinemediaobject>` rendering below is not verified here. Likewise, the
    // fallback behavior when an icon image can't be located requires file-system
    // access this crate leaves to the caller.
    non_normative!(
        r#"
Here's how the DocBook converter converts an icon macro.

[source,xml]
----
<inlinemediaobject>
  <imageobject>
    <imagedata fileref="./images/icons/tags.png"/>
  </imageobject>
  <textobject><phrase>tags</phrase></textobject>
</inlinemediaobject> ruby, asciidoctor
----

When the image for an icon can't be located in the icons directory, the AsciiDoc processor displays the icon macro's alt (i.e. fallback) text.

"#
    );
}

mod how_the_icon_is_resolved {
    use crate::tests::prelude::*;

    #[test]
    fn resolves_by_mode() {
        verifies!(
            r#"
== How the icon is resolved

The target of the icon macro is an icon name (or path).
How that target is resolved depends on the icon mode assigned to the xref:icons.adoc#icons-attribute[icons attribute].

text::
The icon name will be enclosed in square brackets (e.g., `[heart]`).

image::
The icon name will be resolved to a file in the `iconsdir` with the file extension specified by `icontype` (e.g., [.path]_./images/icons/heart.png_).

font::
The icon name will be resolved to a glyph in an icon font (as mapped by a CSS class) (e.g., `fa fa-heart`).

WARNING: If you include a file extension in the image target, the icon macro will not work correctly when using the font icon mode (i.e., `icons=font`).

"#
        );

        // text mode (the `icons` attribute is not set): the icon name is
        // enclosed in square brackets (the `]` is emitted as `&#93;`).
        let doc = Parser::default().parse("icon:heart[]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon">[heart&#93;</span>"#
        );

        // image mode: the name resolves to a file in `iconsdir` with the
        // `icontype` extension (default `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>"#
        );

        // font mode: the name resolves to a `fa fa-<name>` glyph class.
        let doc = Parser::default().parse(":icons: font\n\nicon:heart[]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-heart"></i></span>"#
        );

        // WARNING: a file extension in the target breaks font icon mode, because
        // the extension is carried into the glyph class (`fa-heart.png`).
        let doc = Parser::default().parse(":icons: font\n\nicon:heart.png[]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-heart.png"></i></span>"#
        );
    }
}

mod shared_attributes {
    use crate::tests::prelude::*;

    #[test]
    fn title() {
        verifies!(
            r#"
== Icon macro attributes (shared)

The following attributes of the icon macro are shared for all icon modes.

`role`::
The role applied to the element that surrounds the icon.

`title`::
The title of the image displayed when the mouse hovers over it.

`link`::
The URI target used for the icon, which will wrap the converted icon in a link.

`window`::
The target window of the link (when the `link` attribute is specified).

"#
        );

        // `role`, `link`, and `window` are each demonstrated by their own
        // example below; here we exercise `title`, which adds a `title`
        // attribute to the rendered image.
        let doc = Parser::default().parse(":icons: image\n\nicon:tags[title=Tags]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags" title="Tags"></span>"#
        );
    }

    #[test]
    fn role() {
        verifies!(
            r#"
=== Role

Here's an example of an icon that uses a role to specify the color.

[source]
----
icon:tags[role=blue] ruby, asciidoctor
----

"#
        );

        // The role is appended to the class of the `<span>` surrounding the icon.
        let doc =
            Parser::default().parse(":icons: image\n\nicon:tags[role=blue] ruby, asciidoctor");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon blue"><img src="./images/icons/tags.png" alt="tags"></span> ruby, asciidoctor"#
        );
    }

    #[test]
    fn link_and_window() {
        verifies!(
            r#"
=== Link and window

Here's an example of an icon with a link that targets a separate window:

[source]
----
icon:download[link=https://rubygems.org/downloads/whizbang-1.0.0.gem, window=_blank]
----

"#
        );

        // `link` wraps the icon in an `<a class="image">`; `window=_blank` sets
        // the link's `target` and adds `rel="noopener"`.
        let doc = Parser::default().parse(
            ":icons: image\n\nicon:download[link=https://rubygems.org/downloads/whizbang-1.0.0.gem, window=_blank]",
        );
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            concat!(
                r#"<span class="icon">"#,
                r#"<a class="image" href="https://rubygems.org/downloads/whizbang-1.0.0.gem" target="_blank" rel="noopener">"#,
                r#"<img src="./images/icons/download.png" alt="download"></a></span>"#,
            )
        );
    }
}

mod image_mode_attributes {
    use crate::tests::prelude::*;

    #[test]
    fn alt_and_width() {
        verifies!(
            r#"
== Icon macro attributes (image mode only)

The attributes listed below only apply when using the image icon mode.

`alt`::
The alternative text on the `<img>` element (HTML output) or text of `<inlinemediaobject>` element (DocBook output)

`width`::
The width applied to the image.

For example, here's how to control the icon alt text and width when using the image icon mode:

[source]
----
icon:tags[Tags,width=16] ruby, asciidoctor
----

"#
        );

        // The `alt` named attribute sets the `<img>` alt text in image mode.
        let doc = Parser::default().parse(":icons: image\n\nicon:tags[alt=Tags]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><img src="./images/icons/tags.png" alt="Tags"></span>"#
        );

        // The `width` named attribute sets the `<img>` width.
        let doc = Parser::default().parse(":icons: image\n\nicon:tags[width=16]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags" width="16"></span>"#
        );

        // NOTE: The icon macro's sole positional attribute is `size` (see the
        // font-mode section below), so in `icon:tags[Tags,width=16]` the leading
        // `Tags` is parsed as `size`, not `alt`. Because `size` has no effect in
        // image mode, the alt text falls back to the target-derived default
        // (`tags`) while `width=16` applies. To set the alt text explicitly, use
        // the named `alt` attribute as shown above.
        let doc =
            Parser::default().parse(":icons: image\n\nicon:tags[Tags,width=16] ruby, asciidoctor");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><img src="./images/icons/tags.png" alt="tags" width="16"></span> ruby, asciidoctor"#
        );
    }

    non_normative!(
        r#"
The icon macro doesn't support any options to change its physical position (such as alignment).

"#
    );
}

mod font_mode_attributes {
    use crate::tests::prelude::*;

    #[test]
    fn size_rotate_flip() {
        verifies!(
            r#"
== Icon macro attributes (font mode only)

The icon macro has a few attributes that modify the size and orientation of a font-based icon.
These attributes are only recognized in the font icon mode.

`size`::
First positional attribute; scales the icon; values: `1x` (default), `2x`, `3x`, `4x`, `5x`, `lg`, `fw`

`rotate`::
Rotates the icon; values: `90`, `180`, `270`

`flip`::
Flips the icon; values: `horizontal`, `vertical`

"#
        );

        // `size` scales the glyph via a `fa-<size>` class.
        let doc = Parser::default().parse(":icons: font\n\nicon:heart[2x]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-heart fa-2x"></i></span>"#
        );

        // `rotate` rotates the glyph via a `fa-rotate-<deg>` class.
        let doc = Parser::default().parse(":icons: font\n\nicon:shield[rotate=90]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-shield fa-rotate-90"></i></span>"#
        );

        // `flip` flips the glyph via a `fa-flip-<dir>` class.
        let doc = Parser::default().parse(":icons: font\n\nicon:shield[flip=vertical]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-shield fa-flip-vertical"></i></span>"#
        );
    }

    #[test]
    fn size() {
        verifies!(
            r#"
=== Size

To make the icon twice the size as the default, enter `2x` inside the square brackets.

[source]
----
icon:heart[2x]
----

or

[source]
----
icon:heart[size=2x]
----

"#
        );

        // The positional form (`2x`) and the named form (`size=2x`) are
        // equivalent, both adding the `fa-2x` class.
        let expected = r#"<span class="icon"><i class="fa fa-heart fa-2x"></i></span>"#;

        let doc = Parser::default().parse(":icons: font\n\nicon:heart[2x]");
        assert_eq!(rendered_paragraphs(&doc).join("\n"), expected);

        let doc = Parser::default().parse(":icons: font\n\nicon:heart[size=2x]");
        assert_eq!(rendered_paragraphs(&doc).join("\n"), expected);
    }

    #[test]
    fn fixed_width() {
        verifies!(
            r#"
[TIP]
====
If you want to line up icons so that you can use them as bullets in a list, use the `fw` size as follows:

----
[%hardbreaks]
icon:bolt[fw] bolt
icon:heart[fw] heart
----
====

"#
        );

        // The `fw` (fixed-width) size adds the `fa-fw` class.
        let doc = Parser::default().parse(":icons: font\n\nicon:bolt[fw] bolt");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-bolt fa-fw"></i></span> bolt"#
        );

        let doc = Parser::default().parse(":icons: font\n\nicon:heart[fw] heart");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-heart fa-fw"></i></span> heart"#
        );
    }

    #[test]
    fn rotate_and_flip() {
        verifies!(
            r#"
=== Rotate and flip

To rotate and flip an icon, specify these options using named attributes:

[source]
----
icon:shield[rotate=90, flip=vertical]
----
"#
        );

        // When both `rotate` and `flip` are supplied, `flip` takes precedence
        // (matching Asciidoctor, which selects the flip class and ignores the
        // rotate class).
        let doc = Parser::default().parse(":icons: font\n\nicon:shield[rotate=90, flip=vertical]");
        assert_eq!(
            rendered_paragraphs(&doc).join("\n"),
            r#"<span class="icon"><i class="fa fa-shield fa-flip-vertical"></i></span>"#
        );
    }
}