summaryrefslogtreecommitdiff
path: root/docs/modules
diff options
context:
space:
mode:
Diffstat (limited to 'docs/modules')
-rw-r--r--docs/modules/convert/nav.adoc1
-rw-r--r--docs/modules/convert/pages/templates.adoc684
2 files changed, 685 insertions, 0 deletions
diff --git a/docs/modules/convert/nav.adoc b/docs/modules/convert/nav.adoc
index 45ba5f4b..a44ce7ff 100644
--- a/docs/modules/convert/nav.adoc
+++ b/docs/modules/convert/nav.adoc
@@ -1,4 +1,5 @@
* xref:index.adoc[]
** xref:available.adoc[]
** xref:custom.adoc[]
+** xref:templates.adoc[]
** xref:contexts-ref.adoc[]
diff --git a/docs/modules/convert/pages/templates.adoc b/docs/modules/convert/pages/templates.adoc
new file mode 100644
index 00000000..f0350b46
--- /dev/null
+++ b/docs/modules/convert/pages/templates.adoc
@@ -0,0 +1,684 @@
+= Converter Templates
+: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: http://slim-lang.com
+:!listing-caption:
+
+All output produced by Asciidoctor is customizable.
+One way to customize the output is to use a custom converter.
+Converter templates offer a simpler way.
+
+Converters that have the `supports_templates` trait enabled, which includes all built-in converters, allow you to substitute 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 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 to make use of it to customize the converted output.
+
+== 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 where 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.
+
+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.
+
+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).
+
+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.
+
+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 https://github.com/rtomayko/tilt/blob/master/README.md[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 the output of a converter à la carte.
+
+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 reoccuring 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.
+
+=== Template directories
+
+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 thus 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.
+But, 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 and the template engine using the `-E` option.
+
+ $ asciidoctor -T /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.
+However, you can include this segment in the path if you prefer.
+
+== 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.
+
+The preferred way of installing a gem is to add it to the [.path]_Gemfile_ in your project.
+
+.Gemfile
+[,ruby]
+----
+gem 'tilt'
+gem 'slim'
+----
+
+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
+
+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
+
+=== 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.
+You can do so by passing the directory containing the templates using the `-T` option and the name of the template engine using the `-E` option.
+
+ $ 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` and `slim` 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:
+
+ $ asciidoctor -T path/to/templates -E slim doc.adoc
++
+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.