Tutorial #
This tutorial provides comprehensive guidance on using Fabric and the Fabric Configuration Language (FCL) for document generation. We’ll systematically cover creating a basic template, incorporating data blocks, applying data filtering and mutation, installing plugins, and rendering text with external content providers.
Prerequisites #
Before you follow this tutorial, make sure you have the following:
- Fabric CLI installed and
fabric
CLI command available - (optional) OpenAI API token
Hello, Fabric #
Let’s start with a straightforward “Hello, Fabric!” template to confirm that everything is configured correctly.
Create a new hello.fabric
file and define a simple template:
document "greeting" {
content text {
value = "Hello, Fabric!"
}
}
In this snippet, document.greeting
block defines a template with a single anonymous content
block with the static text “Hello, Fabric!”
To render the document, execute fabric
command in the directory with hello.fabric
file, or
explicitly specify a path to another directory with --source-dir
CLI argument:
fabric render document.greeting
The command should produce Hello, Fabric!
string:
$ fabric render document.greeting
Hello, Fabric!
Document title #
Documents usually have titles and the document
block supports title
argument as an
easy way to set a title.
With the new title
argument, document.greeting
template should look like this:
document "greeting" {
title = "The Greeting"
content text {
value = "Hello, Fabric!"
}
}
title
argument for document
block is a syntactic sugar translated into content.title
block
during rendering:
content title {
value = "The Greeting"
}
See content.title
content provider
documentation for the details.
The rendered Markdown output should now include the document title:
$ fabric render document.greeting
# The Greeting
Hello, Fabric!
Variables #
For this tutorial, instead of defining the data requirements with data
blocks, we will use
variables defined in vars
block.
Change the template in the hello.fabric
file to include a vars
block and add another
content.text
block:
document "greeting" {
vars {
solar_system = {
planets = [
"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"
]
moons_count = 146
}
}
title = "The Greeting"
content text {
value = "Hello, Fabric!"
}
content text {
local_var = query_jq(".vars.solar_system.planets | length")
value = <<-EOT
There are {{ .vars.local }} planets and {{ .vars.solar_system.moons_count }} moons in our solar system.
EOT
}
}
Here, we defined inline data inside vars
block (see Variables), used local_var
(see Local variable), and queried it with query_jq()
function (see Querying the
context) inside the content
block.
The value
argument in the new content block is a template string – content.text
blocks support
Go templates in value
argument. The templates can access the
evaluation context, so it’s easy to use JSON path and include the values of local
and
solar_system.moons_count
variables.
The rendered output will now include the new sentence:
$ fabric render document.greeting
# The Greeting
Hello, Fabric!
There are 8 planets and 146 moons in our solar system.
Content providers #
Fabric uses both internal implementations and integrates with external APIs for content generation. An excellent example is the use of the OpenAI API to dynamically generate text with prompts.
In scenarios where providing the exact text or a template string for the content block proves challenging or impossible, we can leverage generative AI for text generation. This allows us to dynamically create context-aware text.
Lets use openai_text
content
provider for generating text with OpenAI API.
Installation #
Before using openai_text
content
provider, it’s necessary to add blackstork/openai
plugin as a
dependency and install it locally.
First, update the hello.fabric
file with the global configuration block:
fabric {
plugin_versions = {
"blackstork/openai" = ">= 0.4.0"
}
}
document "greeting" {
vars {
solar_system = {
planets = [
"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"
]
moons_count = 146
}
}
title = "The Greeting"
content text {
value = "Hello, Fabric!"
}
content text {
local_var = query_jq(".vars.solar_system.planets | length")
value = <<-EOT
There are {{ .vars.local }} planets and {{ .vars.solar_system.moons_count }} moons in our solar system.
EOT
}
}
Here, plugin blackstork/openai
is in the list of dependencies, listed in plugin_versions
argument
in the global configuration.
With updated hello.fabric
file, install all required plugins with the fabric install
command:
$ fabric install
Mar 11 19:20:10.769 INF Searching plugin name=blackstork/openai constraints=">=v0.4.0"
Mar 11 19:20:10.787 INF Installing plugin name=blackstork/openai version=0.4.0
$
Fabric fetched the blackstork/openai
plugin release from the plugin registry and installed it in
the local ./.fabric/
folder.
The versions in the command output on your system might be different. With >= 0.4.0
version
constraint, Fabric will install the latest stable version (higher than 0.4.0
) of the plugin.
Configuration #
OpenAI API requires API key for authentication. The key must be set in openai_text
provider’s configuration block. It’s recommended
to store credentials separately from Fabric code, and use the env
object (see Environment variables).
We can specify OpenAI API key in OPENAI_API_KEY
environment variable and access it in Fabric file
with env.OPENAI_API_KEY
.
The config
block for the openai_text
content provider looks like this:
config content openai_text {
api_key = env.OPENAI_API_KEY
}
Add this block to the root level of hello.fabric
file, outside document
block.
Usage #
Lets define the content block that uses openai_text
content provider:
# ...
document "greeting" {
# ...
content openai_text {
local_var = query_jq("{planet: .vars.solar_system.planets[-1]}")
prompt = <<-EOT
Share a fact about the planet specified in the provided data:
{{ .vars.local | toRawJson }}
EOT
}
}
In this block we again use local_var
. A JQ query "{planet: .vars.solar_system.planets[-1]}
fetches the last item from the list of planets (Neptune
) and creates a new JSON object {"planet": "Neptune"}
.
openai_text
provider. See the provider’s documentation for more configuration options.The complete content of the hello.fabric
file should look like this:
fabric {
plugin_versions = {
"blackstork/openai" = ">= 0.4"
}
}
config content openai_text {
api_key = env.OPENAI_API_KEY
}
document "greeting" {
vars {
solar_system = {
planets = [
"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"
]
moons_count = 146
}
}
title = "The Greeting"
content text {
value = "Hello, Fabric!"
}
content text {
local_var = query_jq(".vars.solar_system.planets | length")
value = <<-EOT
There are {{ .vars.local }} planets and {{ .vars.solar_system.moons_count }} moons in our solar system.
EOT
}
content openai_text {
local_var = query_jq("{planet: .vars.solar_system.planets[-1]}")
prompt = <<-EOT
Share a fact about the planet specified in the provided data:
{{ .vars.local | toRawJson }}
EOT
}
}
To render the document, set OPENAI_API_KEY
environment variable when running fabric
command:
$ OPENAI_API_KEY="<key-value>" fabric render document.greeting
...
<key-value>
in the CLI command with your OpenAI API key value.The results of the render should look similar to the following:
$ OPENAI_API_KEY="<key-value>" ./fabric render document.greeting
fabric render document.greeting
Jun 23 17:14:23.910 INF Parsing fabric files command=render
Jun 23 17:14:23.912 INF Loading plugin resolver command=render includeRemote=false
Jun 23 17:14:23.912 INF Loading plugin runner command=render
Jun 23 17:14:23.939 INF Rendering content command=render target=greeting
Jun 23 17:14:23.939 INF Loading document command=render target=greeting
# The Greeting
Hello, Fabric!
There are 8 planets and 146 moons in our solar system.
Neptune is the eighth and farthest known planet from the Sun in the Solar System. It is classified as an ice giant and is the fourth-largest planet by diameter.
Publishing #
By default, Fabric prints rendered document into standard output formatted as Markdown. We can use any Markdown editor, for example MacDown for macOS, to render Markdown, but it’s also possible to produce HTML and PDF documents with Fabric.
To format the document as HTML, PDF, or Markdown, and publish it to a local or an external
destination, use publish
blocks.
Add a publish
block to the document template:
document "greeting" {
# ...
# Publishing to a local HTML file
publish local_file {
path = "./greeting-{{ now | date \"2006_01_02\" }}.{{.format}}"
format = "html"
}
}
Note that similarly to content.text
blocks, publish
block supports Go template string as the
path
argument value. This means we can specify a dynamic path for the output file - in this case,
the filename will contain a date and the output format.
To render the document and publish the output to a local file, use --publish
flag when running fabric render
:
$ fabric render document.greeting --publish
Jun 23 17:28:03.027 INF Parsing fabric files command=render
Jun 23 17:28:03.028 INF Loading plugin resolver command=render includeRemote=false
Jun 23 17:28:03.028 INF Loading plugin runner command=render
Jun 23 17:28:03.056 INF Publishing document command=render target=greeting
Jun 23 17:28:03.056 INF Loading document command=render target=greeting
Jun 23 17:28:04.213 INF Writing to a file command=render path=/tmp/greeting-2024_06_23.html
$
You can find the produced HTML file in the current directory (or at the path specified in path
argument). The file contents should look similar to this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Greeting</title>
</head>
<body>
<h1 id="the-greeting">The Greeting</h1>
<p>Hello, Fabric!</p>
<p>There are 8 planets and 146 moons in our solar system.</p>
<p>Neptune is the eighth and most distant planet in our solar system, located about 4.5 billion kilometers away from the Sun.</p>
</body>
</html>
To learn hot to add JS and CSS to the produced HTML document, see Formatting documentation.
Next steps #
Congratulations! By completing this tutorial, you’ve gained a good understanding of Fabric and its core principles.
Take a look at the detailed FCL specification, explore the open-source templates the community made, and see if there are integrations for your tech stack in Fabric plugins.
If you have any questions, feel free to ask in the Community Slack and we’ll be glad to assist you!