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
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
|
= Converter Templates
:page-aliases: api:load-templates.adoc
:apidoc-root: {url-api-gems}/asciidoctor/{release-version}/Asciidoctor
:apidoc-abstract-node: {apidoc-root}/AbstractNode
:apidoc-block: {apidoc-root}/Block
:url-pry: http://pry.github.io
:url-slim: https://slim-template.github.io
:url-tilt: https://github.com/rtomayko/tilt
:!listing-caption:
All output produced by Asciidoctor is customizable.
One way to customize the output is to use a xref:custom.adoc[custom converter].
Converter templates offer a simpler way.
The built-in converters, and others that have the `supports_templates` trait enabled, allow you to replace the convert handler for any convertible context with a template.
The goal of this mechanism is to make it easier to customize the output of a converter (e.g., HTML) without having to develop, register, and use a custom converter.
In other words, you can customize the output of a converter à la carte without having to write code (aside from what's in the template).
This page explains how the converter template mechanism works in Asciidoctor and how you can make use of it to customize the output of a converter that supports it.
== What is a template?
A template is a type of source file that's focused on producing output.
When we use the term [.term]_template_ in this context, we're specifically referring to the source file for a Ruby template engine.
You can think of template as source code in which the output text forms the primary structure and the programming logic is weaved in between.
If you come from a programming background, it's as though the program logic and strings are flipped.
When you write a template, you begin with static text.
Then you intersperse logic around regions of that text.
This logic allows you to either conditionally select text or repeat it based on information provided by a backing data model.
The syntax of that interspersed logic is the template language.
The most common template engine in Ruby is ERB because it's built into the language.
Here's an example of an ERB template that outputs a paragraph element using the content provided by the backing data model:
.paragraph.html.erb
[,erb]
----
<p><%= content %></p>
----
Some template languages, such as Slim and Haml, are more structured.
Here's the previous example written in Slim:
.paragraph.html.slim
[,slim]
----
p =content
----
and in Haml:
.paragraph.html.haml
[,haml]
----
%p =content
----
Notice there are no angled brackets in these templates.
That's because Slim and Haml assume that the start of each statement is the name of an XML element (Haml requires a leading `%`).
The equals sign tells Slim and Haml to evaluate the Ruby expression that follows and insert the result into the output.
In this case, the template is reading the variable `content`, technically a property of the backing data model.
Slim and Haml use indentation to infer nesting in the HTML structure (much like in YAML).
At runtime, the template engine transforms the template into runnable code and invokes it.
We refer to this operation as invoking the template.
Effectively, this operation invokes the logic and expands variable references in the template to produce the resolved output.
Now that you're familiar with the basic concept of a template, let's look at how templates are used in Asciidoctor.
== Templates in Asciidoctor
Templates are used in Asciidoctor to customize the output generated by a converter (as long as the converter has the `supports_templates` trait enabled).
We refer to these as converter templates.
Converter templates work in conjunction with the converter over which they are applied.
You can reuse the same set of templates you develop for Asciidoctor with AsciidoctorJ, making the templates portable between the two runtimes.
Asciidoctor.js provides its own xref:asciidoctor.js:extend:converter/template-converter.adoc[template converter], which means you have to develop a different set of templates if you're using Asciidoctor.js.
There are three keys points to understand about using converter templates in Asciidoctor:
* How Asciidoctor selects the template engine to use.
* What backing data model Asciidoctor passes to the template.
* The available template names.
Let's study templates through the lens of Asciidoctor, then explore common APIs, helpers, and debugging.
=== Template engine selection
Asciidoctor uses https://github.com/rtomayko/tilt[Tilt^] to load and invoke templates.
Tilt is a generic interface to multiple Ruby template engines and is provided by the required *tilt* gem.
You can compose templates in any template language that's supported by Tilt.
If you use a template engine that requires additional libraries (i.e., gems), you must install them first.
For example, to use templates written in Haml, you must install the *haml* gem.
Tilt examines the file extension of the template, matches it to a registered and available template engine, and passes the template to the engine to be invoked.
If the file extension is not recognized, or the template engine is not installed, this process will fail.
A template has a double file extension (e.g., `.html.haml`).
The outer file extension is the file extension of the template.
In other words, it identifies the template language.
The inner file extension is the file extension of the output file.
In other words, it identifies the output format.
Let's assume you have a template named [.path]_paragraph.html.haml_.
The _.haml_ file extension tells Tilt to delegate to Haml.
The remaining part of the filename (e.g., [.path]_paragraph.html_) would be used as the output file.
However, in Asciidoctor, the result of the template isn't output to a file.
Instead, it is combined with the rest of the output of the converter.
But the file extension should still match the file extension of the output file that Asciidoctor produces.
You can find a list of template engines that Tilt supports, along with any required libraries, in the {url-tilt}#readme[Tilt README^].
The most popular template engines for this purpose are {url-slim}[Slim^], Haml, and ERB.
We strongly recommend using Slim.
ERB is also a solid choice since it's built into the Ruby language and thus doesn't have any dependencies.
You're encouraged to read the documentation for the template engine of your choice to understand how to use that particular template language.
=== Available template names
When we talk about the name of a template, we're talking about the basename of the template minus the double file extension.
For example, the name of the template [.path]_paragraph.html.slim_ is _paragraph_.
This name is significant because it maps 1-to-1 with the xref:contexts-ref.adoc[convertible contexts] in Asciidoctor (excluding the leading colon).
The convertible contexts are roughly the nodes in the parsed AsciiDoc document.
If the name of a template matches the name of a convertible context, Asciidoctor will use that template to produce the output for any node with that context when the convert method is called on that node.
You can create a template for as many or as few contexts as you like.
If Asciidoctor is unable to locate a template for a convertible context, it will fall back to using the handler provided by the converter.
By using templates, you can customize some or all of the output produced by a converter.
NOTE: Recall that templates can only be used if supported by the converter.
All built-in converters in Asciidoctor support the use of templates to customize the converter.
The outer template is either named document or embedded, depending on whether or not the document is being converted in standalone mode.
Asciidoctor doesn't walk the document tree itself and invoke the corresponding templates.
Rather, it's up to the template to trigger the other templates by invoking the `content` method on block nodes.
So, if you replace the document or embedded template, and don't invoke the `content` method, that explains why no other templates get called.
Let's look at the backend data model to understand the logic that can be used in a template.
=== Backing data model
The backing data model for a template in Asciidoctor is always the node being converted (specifically an {apidoc-abstract-node}[`AbstractNode`]).
(A node in the Asciidoctor document model is similar to an XML DOM node).
For example, when writing a template for a paragraph, the backing data model is an instance of {apidoc-block}[`Block`] with the context `:paragraph`.
You can access the node itself using the `self` keyword.
[,slim]
----
- puts self
----
In a Slim template, a line that starts with `-` executes a Ruby statement.
An expression that starts with `=` (either at the start of the line or following a tag name) invokes a method and inserts the return value into the template.
Within the template, you can access all the instance variables and methods of the node using the name of that member, just as you would inside a method call (e.g., `@id` or `title`).
(You can think of the template as a method call on the node object).
When referencing an accessor method, the name of the member is synonymous with a template variable.
CAUTION: Accessing instance variables of the node from the template (e.g., `@id`) is generally discouraged as it tightly couples your template to the internal model.
It's better to stick with using public accessors and methods.
To access the converted content of the node, you use the template variable `content`.
[,slim]
----
p =content
----
The syntax `=content` invokes the `content` accessor method on the node and inserts the result into the template.
You can access the document for all nodes from the template using the `document` property.
The document is most commonly used for looking up document attributes, as shown here:
[,slim]
----
p lang=(document.attr 'lang') =content
----
You can also use the document object to lookup other nodes in the document using `Document#find_by`.
Let's look at a complete example of a paragraph template that mimics the output of the built-in HTML converter.
.paragraph.html.slim
[,slim]
----
div id=id class=['paragraph', role]
- if title?
.title =title
p =content
----
Assuming id is "hello", title is "Hello, World!", role is nil, and content is "Your first template!", this template will produce the following HTML:
[,html]
----
<div id="hello" class="paragraph">
<div class="title">Hello, World!</div>
<p>Your first template!</p>
</div>
----
This template uses the `id`, `role`, `title`, and `content` properties, as well as the `title?` method.
You may notice that Slim infers some logic for you.
If the role is not set, it will drop that entry from the array, join the remaining entries on a space, and output the class attribute.
If the `id` property were nil instead of "hello", the template will not output the id attribute.
To help you understand what properties and methods are available on a node, you can print them using the following expression:
[,slim]
----
- pp (public_methods - Object.instance_methods).reject {|it| it.end_with? '=' }
----
All properties are reported as methods, which is why this statement uses `public_methods`.
You can inspect all the attributes available on the current node and current document as follows:
[,slim]
----
- pp attributes
- pp document.attributes
----
To discover more about these properties and methods, and what they return, refer to the {apidoc-abstract-node}[API docs].
Also refer to <<Debugging>> to learn more about how to inspect the backing data model.
=== Common APIs
Each node shares a common set of properties, such as id, role, attributes, context, its parent node, and the document node.
Block and inline nodes have additional properties that are specific to their purpose.
For example, a block node has a `content` property to access its converted content, a list node has an `items` property to access the list items, and an inline node has a `text` property to access the converted text.
Each template has access to any API in the document model that is accessible from the node being converted.
The following table provides a list of the APIs you'll likely use most often.
[cols="1,2m,1"]
|===
|Name |Example (Slim) |Description
|document
|- if document.attr 'icons', 'font'
|A reference to the current document (and all of its nodes).
|content
|=content
|Converts the children of this block node (if any) and returns the result.
|items
|- items.each do \|item\|
|Provides access to the items in a list node.
Note that the list template must process its own items.
|text
|=text
|Returns the converted text of this inline node.
|target
|=target
|Returns the converted target of this inline node, if applicable (e.g., anchor, image).
|id
|div id=id
|The id assigned to the block or nil if no id is assigned.
|role
|div class=role
|A convenience method that returns the role attribute for the block, or nil if the block does not have a role.
|role?
|if role? 'lead'
|A convenience method to check whether the block has the role attribute.
|attr
|div class=(attr 'toc-class', 'toc')
|Retrieves the value of the specified attribute on the element, using the name as a key.
If the name is written as a symbol, it will be automatically converted to a string before lookup.
The second argument is a fallback value if the attribute is not set.
|attr?
|- if attr? 'icons'
|Checks whether the specified attribute exists on the element, using the name as the key.
If the name is written as a symbol, it will be automatically converted to a string before lookup.
If the second argument is provided (a match), it additionally checks whether the attribute value matches the specified value.
|style
|- if style == 'source'
|Retrieves the style (qualifier) for a block node.
If the block does not have a style, nil is returned.
|title
|=title
|Retrieves the title of the block with normal substitutions (escape XML, render links, etc) applied.
|title?
|- if title?
|Checks whether a title has been assigned to this block.
This method does not have side effects (e.g., checks for existence only, does not apply substitutions)
|captioned_title
|=captioned_title
|Retrieves the title of the block with caption and normal substitutions (escape XML, render links, etc) applied.
|option?
|video autoplay=(option? 'autoplay')
|A convenience method to check whether the specified option attribute (e.g., autoplay-option) is present.
|type
|- if type == :xref
|Returns the node variant for inline nodes that have variants (e.g., anchor, quoted, etc).
|image_uri
|img src=(image_uri attr 'target')
|Converts the path into an image URI (reference or embedded data) to be used in an HTML img element.
Applies security restrictions, cleans path and can embed image data if :data-uri: attribute is enabled on document.
Always use this method when dealing with image references.
Relative image paths are resolved relative to document directory unless overridden using :imagesdir:
|icon_uri
|img src=(icon_uri attr 'target')
|Same as image_uri except it specifically works with icons.
By default, it will look in the subdirectory images/icons, unless overridden using :iconsdir:
|media_uri
|audio src=(media_uri attr 'target')
|Similar to image_uri, except it does not support embedding the data into the document.
Intended for video and audio paths.
|normalize_web_path
|link href=(normalize_web_path attr 'stylesheet')
|Joins the path to the relative_root and normalizes parent and self references. Access to parent directories may be restricted based on safe mode setting.
|===
=== Helpers
As previously stated, the backing data model for a template primarily consists of the properties and methods of the node being converted.
Helpers provided by the template engine are also available as top-level functions in the template.
Refer to the documentation for the template engine for details.
If you find yourself putting a lot of logic in the template, you may want to extract that logic into custom helper functions.
When using Haml or Slim, you can define these helper functions in the file [.path]_helper.rb_ located in the same folder as the templates.
These helper functions can simplify reoccurring elements that appear across multiple templates.
The helper file must define the Ruby module `Haml::Helpers` or `Slim::Helpers`, depending on which template engine your templates target.
Every method defined in that module becomes a top-level function in the template.
The method is effectively mixed into the node, so the `self` reference in the function is the node itself.
TIP: Helpers provided by the template engine are also available as top-level functions.
For example, Haml provides the `html_tag` helper for creating an HTML element dynamically.
Refer to the documentation for the template engine for details.
Let's assume that we're creating a template for sections, and we want to output the section title with the section number, but only if automatic section numbering is enabled.
We can create a helper function for this purpose:
.helpers.rb
[,ruby]
----
module Slim::Helpers
def section_title
if caption
captioned_title
elsif numbered && level <= (document.attr :sectnumlevels, 3).to_i
if level < 2 && document.doctype == 'book'
case sectname
when 'chapter'
%(#{(signifier = document.attr 'chapter-signifier') ? signifier.to_s + ' ' : ''}#{sectnum} #{title})
when 'part'
%(#{(signifier = document.attr 'part-signifier') ? signifier.to_s + ' ' : ''}#{sectnum nil, ':'} #{title})
else
%(#{sectnum} #{title})
end
else
%(#{sectnum} #{title})
end
else
title
end
end
end
----
You can now use this helper in your section template as follows:
.section.html.slim
[,slim]
----
*{ tag: %(h#{level + 1}) } =section_title <1>
=content <2>
----
<1> We're leveraging a special syntax in Slim to create the HTML heading element dynamically.
<2> It's necessary to invoke the `content` method to convert the child nodes of a node that contains other nodes.
If you prefer your helpers to be pure functions, you can pass in the node as the first argument and only use that reference to access properties of the backing data model.
.helpers.rb using pure functions
[,ruby]
----
module Slim::Helpers
def section_title node = self
if node.caption
node.captioned_title
elsif node.numbered && node.level <= (node.document.attr :sectnumlevels, 3).to_i
...
else
node.title
end
end
end
----
Which style you use to write your helpers is up to you.
But if you find that you need to reuse a function for different scenarios, you might find the investment in pure functions to be worthwhile.
=== Debugging
There are two approaches to debug a template by exploring the backing model:
* print messages and return values to STDOUT using `puts` or `pp`
* jump into the context of the template using an interactive debugger
==== Print debugging information
To print the current node in string form to STDOUT, you can use the following statement in your template:
[,slim]
----
- puts self
----
You can print structured information about the current node using `pp`:
[,slim]
----
- pp self
----
However, since a node has circular references, that output can be extremely verbose.
You might find it more useful to print more specific information.
You can see what attributes are available on the current node and document using these statements:
[,slim]
----
- pp attributes
- pp document.attributes
----
You can see what properties and methods are available on a node using the following expression:
[,slim]
----
- pp (public_methods - Object.instance_methods).reject {|it| it.end_with? '=' }
----
Using print statements, you have to update the template and rerun Asciidoctor each time you want to further your inspection.
A more efficient approach is to use an interactive debugger.
==== Use an interactive debugger
{url-pry}[Pry] is a powerful debugger for Ruby that features syntax highlighting, tab-completion, and documentation and source code browsing.
You can use it to interactively discover the object hierarchy of the backing model available to an Asciidoctor template.
To use Pry, you first need to install it, either using `gem install`:
$ gem install pry
or by adding it to your [.path]_Gemfile_ and running `bundle`.
In order to be dropped into the debugger at a specific point in a template, add the following two lines to the template you want to inspect:
.paragraph.html.slim
[,slim]
----
- require 'pry'
- binding.pry
----
When you run Asciidoctor, it will pause in the template and give you an interactive console.
[.output]
....
From: /path/to/templates/html5/paragraph.html.slim:7 self.__tilt_800:
1: - require 'pry'
=> 2: - binding.pry
[1] pry(#<Asciidoctor::Block>)>
....
From there, you can inspect the objects in the backend model.
[.output]
[1] pry(#<Asciidoctor::Block>)> attributes
You can also query Asciidoctor's API documentation:
[.output]
[1] pry(#<Asciidoctor::Block>)> ? find_by
Type exit to leave the interactive console:
[.output]
[1] pry(#<Asciidoctor::Block>)> exit
To learn more about what you can do with Pry, we recommend watching the http://vimeo.com/26391171[introductory screencast^].
Refer to the https://github.com/pry/pry/wiki[Pry wiki^] for details about how to use it.
== How to use templates
Now that you know what templates are and how to make them, let's look at how to use them in Asciidoctor.
=== Organize your templates
You should group templates for a specific backend together in a single folder.
In that folder, each template file should be named using the pattern `<context><output-ext><template-ext>`, where `context` is the name of a xref:contexts-ref.adoc[convertible context], `output-ext` is the file extension of the output file, and `template-ext` is the file extension for the template language (e.g., [.path]_paragraph.html.slim_).
Those are the only requirement you have to follow in order for Asciidoctor to discover and load your templates.
If you're creating templates for multiple backends, you may decide to further group your templates in folders named after the backend (and perhaps even an additional folder for the template language, not shown here).
[listing]
----
📒 templates <1>
📂 html5 <2>
📄 paragraph.html.slim <3>
----
<1> The folder containing the templates for various backends.
<2> The folder containing the templates for the html5 backend.
<3> The converter template for paragraphs.
If you're only targeting a single backend, you can simply name the folder [.path]_templates_.
[listing]
----
📒 templates
📄 paragraph.html.slim
----
Recall that the backend is a moniker for the expected output format, and in turn, the converter that produces it.
=== Install the template engine
To use converter templates, you must always install the *tilt* gem.
If you're using a template engine that has one or more required libraries, you must first install those libraries.
Once the library is installed, Asciidoctor will use Tilt to load it on demand.
TIP: If you write your templates in ERB, no additional libraries are required.
Let's assume you're writing your templates in Slim (which the template engine we most recommend).
You will need both the *tilt* and *slim* gems installed.
If you're using Bundler, you install gems first by declaring them in [.path]_Gemfile_.
.Gemfile
[,ruby]
----
gem 'tilt'
gem 'slim'
----
Then, you install the gems using Bundler:
$ bundle
If you're not using Bundler, and you have configured Ruby to install gems in your user/home directory, then you can use the `gem` command instead:
$ gem install tilt slim
Either way, the *tilt* and *slim* gems must be available on the load path when running Asciidoctor in order to use templates written in the Slim template language.
=== Apply your templates
Instructing Asciidoctor to apply your templates is the easiest part.
You only need to tell Asciidoctor where the templates are located and which template engine you're using.
(Technically, you don't need to specify the template engine.
However, by doing so, it makes the scan more efficient and deterministic.)
If you're using the CLI, you specify the template directory using the `-T` option (longhand: `--template-dir`) and the template engine using the `-E` option (longhand: `--template-engine`).
$ asciidoctor -T /path/to/templates -E slim doc.adoc
When using JRuby, you can refer to a directory on the classpath by prefixing the path with `uri:classloader:`.
For example:
$ asciidoctor -r /path/to/templates.jar -T uri:classloader:/path/to/templates -E slim doc.adoc
If you're using the API, you specify the template directory (or directories) using the `:template_dirs` option and the template engine using the `:template_engine` option.
[,ruby]
----
Asciidoctor.convert_file 'doc.adoc', safe: :safe,
template_dirs: ['/path/to/templates'], template_engine: 'slim'
----
Notice that we didn't specify the segment [.path]_html5_ in the path where the templates are located.
That's because Asciidoctor automatically looks for a folder that matches the backend name when scanning for templates (e.g., [.path]_/path/to/templates/html5_).
However, you can include the segment for the backend in the path if you prefer.
=== Use multiple template directories
You can distribute templates for a single backend across multiple directories.
For example, you may have a set of common templates for all projects (e.g., [.path]_/path/to/common-templates_) and a set of specialized templates that supplement and/or override those templates for a particular project (e.g., [.path]_/path/to/specialized-templates_).
To load templates from multiple directories when using the CLI, you can pass each directory to Asciidoctor by specifying the `-T` option multiple times:
$ asciidoctor -T /path/to/common-templates -T /path/to/specialized-templates -E slim doc.adoc
When using the API, you add all the template directories to the array value of the `:template_dirs` option:
[,ruby]
----
Asciidoctor.convert_file 'doc.adoc', safe: :safe,
template_dirs: ['/path/to/common-templates', '/path/to/specialized-templates'], template_engine: 'slim'
----
In both cases, if the same template is found in more than one location, the template discovered in the directory listed later in the list will be used.
== Tutorial: Your first converter template
This section provides a tutorial you can follow to quickly learn how to write and use your first converter template.
In this tutorial, you'll create a converter template to customize the HTML produced by the built-in HTML converter for unordered lists.
You'll compose the template in the Slim template language.
You'll then observe the result of this customization by using the template when converting the AsciiDoc document to HTML with Asciidoctor.
=== Add and install required gems
You first need to install the required libraries (i.e., gems) for the template engine.
Since you'll be using Slim, you need to install the *slim* gem.
You also need to install the *tilt* gem, which provides Tilt, the generic interface for Ruby template engines that Asciidoctor uses to load and invoke templates.
Although not required, Asciidoctor will prompt you to also install the *concurrent-ruby* gem to properly implement the template cache.
The preferred way of installing a gem is to add it to the [.path]_Gemfile_ in your project.
.Gemfile
[,ruby]
----
source 'https://rubygems.org'
gem 'asciidoctor'
# ...any other gems you are using
gem 'tilt'
gem 'slim'
gem 'concurrent-ruby'
----
Run Bundler to install the gems into your project.
$ bundle
If you're not using Bundler, and you have configured Ruby to install gems in your user/home directory, then you can use the `gem` command instead:
$ gem install tilt slim concurrent-ruby
Now that you've installed Tilt and the Slim template engine, you can get started writing templates.
=== Create templates folder
Next, create a new folder named [.path]_templates_ to store your templates.
We also recommend creating a nested folder named [.path]_html5_ to organize the templates by backend.
$ mkdir -p templates/html5
You can further organize templates into folders by engine, though that's not required.
=== Compose a template
Let's compose the template to customize the HTML for unordered lists.
Since the context for unordered lists is `:ulist` (see xref:contexts-ref.adoc[]), you'll name the template [.path]_ulist.html.slim_.
.ulist.html.slim
[,slim]
----
- if title?
figure.list.unordered id=id
figcaption=title
ul class=[style, role]
- items.each do |_item|
li
span.primary=_item.text
- if _item.blocks?
=_item.content
- else
ul id=id class=[style, role]
- items.each do |_item|
li
span.primary=_item.text
- if _item.blocks?
=_item.content
----
=== Apply the templates
The final step is to use the templates when you invoke Asciidoctor.
Create an AsciiDoc file named [.path]_doc.adoc_ that contains an unordered list.
.doc.adoc
[,asciidoc]
----
* cats
* dogs
* birds
----
You can now instruct Asciidoctor to convert this list using your template by passing the directory containing the templates using the `-T` option and the name of the template engine using the `-E` option.
If you used Bundler to install gems, run Asciidoctor as follows:
$ bundle exec asciidoctor -T templates -E slim doc.adoc
Otherwise, you can drop the `bundle exec` prefix:
$ asciidoctor -T templates -E slim doc.adoc
Now that you've created your first converter template, you're well on your way to customizing the HTML that Asciidoctor produces to suit your own needs!
=== A quick review
Here's a quick review for how to start using templates written in Slim to customize the output of the built-in HTML 5 converter.
. Install the `tilt`, `slim`, and `concurrent-ruby` gems using `bundle` or `gem install`.
. Create a folder named [.path]_templates/html5_ to store the templates.
. Create a template named [.path]_paragraph.html.slim_ in that folder.
. Populate the template with your own template logic.
Here's a simple example:
+
.paragraph.html.slim
[,slim]
----
p id=id role=role =content
----
. Load the templates using the -T flag from the CLI:
$ bundle exec asciidoctor -T path/to/templates -E slim doc.adoc
+
or
$ asciidoctor -T path/to/templates -E slim doc.adoc
TIP: When invoking Asciidoctor via the API, you load the templates by passing the path to the `:templates` option.
We hope you'll agree that using templates makes it easy to customize the output that Asciidoctor produces.
|