mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
markup/asciidocext: Add preserveTOC option
This commit is contained in:
parent
d4fc70a3b3
commit
8e553dcdef
6 changed files with 159 additions and 50 deletions
|
@ -43,7 +43,7 @@ The `markup identifier` is fetched from either the `markup` variable in front ma
|
||||||
|
|
||||||
## External Helpers
|
## External Helpers
|
||||||
|
|
||||||
Some of the formats in the table above needs external helpers installed on your PC. For example, for AsciiDoc files,
|
Some of the formats in the table above need external helpers installed on your PC. For example, for AsciiDoc files,
|
||||||
Hugo will try to call the `asciidoctor` command. This means that you will have to install the associated
|
Hugo will try to call the `asciidoctor` command. This means that you will have to install the associated
|
||||||
tool on your machine to be able to use these formats.
|
tool on your machine to be able to use these formats.
|
||||||
|
|
||||||
|
@ -69,33 +69,48 @@ The Asciidoctor community offers a wide set of tools for the AsciiDoc format tha
|
||||||
[See the Asciidoctor docs for installation instructions](https://asciidoctor.org/docs/install-toolchain/). Make sure that also all
|
[See the Asciidoctor docs for installation instructions](https://asciidoctor.org/docs/install-toolchain/). Make sure that also all
|
||||||
optional extensions like `asciidoctor-diagram` or `asciidoctor-html5s` are installed if required.
|
optional extensions like `asciidoctor-diagram` or `asciidoctor-html5s` are installed if required.
|
||||||
|
|
||||||
Asciidoctor parameters can be customized in Hugo:
|
{{% note %}}
|
||||||
|
External `asciidoctor` command requires Hugo rendering to _disk_ to a specific destination directory. It is required to run Hugo with the command option `--destination`.
|
||||||
|
{{% /note %}}
|
||||||
|
|
||||||
Parameter | Default | Comment
|
Some [Asciidoctor](https://asciidoctor.org/man/asciidoctor/) parameters can be customized in Hugo:
|
||||||
--- | --- | ---
|
|
||||||
backend | `html5` | Don't change this unless you know what you are doing.
|
Parameter | Comment
|
||||||
doctype | `article` | Currently supported Document type is `article`.
|
--- | ---
|
||||||
extensions | | Possible extensions are `asciidoctor-html5s`, `asciidoctor-bibtex`, `asciidoctor-diagram`, `asciidoctor-interdoc-reftext`, `asciidoctor-katex`, `asciidoctor-latex`, `asciidoctor-mathematical`, `asciidoctor-question`, `asciidoctor-rouge`.
|
backend | Don't change this unless you know what you are doing.
|
||||||
attributes | | Variables to be referenced in your `adoc` file. This is a list of variable name/value maps. See [Asciidoctor#attributes](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#attributes-and-substitutions).
|
doctype | Currently, the only document type supported in Hugo is `article`.
|
||||||
noheaderorfooter | true | Output an embeddable document, which excludes the header, the footer, and everything outside the body of the document. Don't change this unless you know what you are doing.
|
extensions | Possible extensions are `asciidoctor-html5s`, `asciidoctor-bibtex`, `asciidoctor-diagram`, `asciidoctor-interdoc-reftext`, `asciidoctor-katex`, `asciidoctor-latex`, `asciidoctor-mathematical`, `asciidoctor-question`, `asciidoctor-rouge`.
|
||||||
safemode | `unsafe` | Safe mode level `unsafe`, `safe`, `server` or `secure`. Don't change this unless you know what you are doing.
|
attributes | Variables to be referenced in your AsciiDoc file. This is a list of variable name/value maps. See [Asciidoctor's attributes](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#attributes-and-substitutions).
|
||||||
sectionnumbers | `false` | Auto-number section titles.
|
noHeaderOrFooter | Output an embeddable document, which excludes the header, the footer, and everything outside the body of the document. Don't change this unless you know what you are doing.
|
||||||
verbose | `false` | Verbosely print processing information and configuration file checks to stderr.
|
safeMode | Safe mode level `unsafe`, `safe`, `server` or `secure`. Don't change this unless you know what you are doing.
|
||||||
trace | `false` | Include backtrace information on errors.
|
sectionNumbers | Auto-number section titles.
|
||||||
failurelevel | `fatal` | The minimum logging level that triggers a non-zero exit code (failure).
|
verbose | Verbosely print processing information and configuration file checks to stderr.
|
||||||
workingfoldercurrent | `false` | Set the working folder to the rendered `adoc` file, so [include](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files) will work with relative paths. This setting uses the `asciidoctor` cli parameter `--base-dir` and attribute `outdir=`. For rendering [asciidoctor-diagram](https://asciidoctor.org/docs/asciidoctor-diagram/) `workingfoldercurrent` must be set to `true`.
|
trace | Include backtrace information on errors.
|
||||||
|
failureLevel | The minimum logging level that triggers a non-zero exit code (failure).
|
||||||
|
|
||||||
|
Hugo provides additional settings that don't map directly to Asciidoctor's CLI options:
|
||||||
|
|
||||||
|
workingFolderCurrent
|
||||||
|
: Sets the working directory to be the same as that of the AsciiDoc file being processed, so that [include](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files) will work with relative paths. This setting uses the `asciidoctor` cli parameter `--base-dir` and attribute `outdir=`. For rendering diagrams with [asciidoctor-diagram](https://asciidoctor.org/docs/asciidoctor-diagram/), `workingFolderCurrent` must be set to `true`.
|
||||||
|
|
||||||
|
preserveTOC
|
||||||
|
: By default, Hugo removes the table of contents generated by Asciidoctor and provides it through the built-in variable [`.TableOfContents`](/content-management/toc/) to enable further customization and better integration with the various Hugo themes. This option can be set to `true` to preserve Asciidoctor's TOC in the generated page.
|
||||||
|
|
||||||
|
Below are all the AsciiDoc related settings in Hugo with their default values:
|
||||||
|
|
||||||
|
{{< code-toggle config="markup.asciidocExt" />}}
|
||||||
|
|
||||||
|
Example of how to set extensions and attributes:
|
||||||
|
|
||||||
```
|
```
|
||||||
[markup.asciidocext]
|
[markup.asciidocExt]
|
||||||
extensions = ["asciidoctor-html5s", "asciidoctor-diagram"]
|
extensions = ["asciidoctor-html5s", "asciidoctor-diagram"]
|
||||||
workingFolderCurrent = true
|
workingFolderCurrent = true
|
||||||
[markup.asciidocext.attributes]
|
[markup.asciidocExt.attributes]
|
||||||
my-base-url = "https://example.com/"
|
my-base-url = "https://example.com/"
|
||||||
my-attribute-name = "my value"
|
my-attribute-name = "my value"
|
||||||
```
|
```
|
||||||
|
|
||||||
Important: External `asciidoctor` requires Hugo rendering to _disk_ to a specific destination folder. It is required to run Hugo with the command option `--destination`!
|
|
||||||
|
|
||||||
In a complex Asciidoctor environment it is sometimes helpful to debug the exact call to your external helper with all
|
In a complex Asciidoctor environment it is sometimes helpful to debug the exact call to your external helper with all
|
||||||
parameters. Run Hugo with `-v`. You will get an output like
|
parameters. Run Hugo with `-v`. You will get an output like
|
||||||
|
|
||||||
|
|
|
@ -96,14 +96,13 @@ With the preceding example, even pages with > 400 words *and* `toc` not set to `
|
||||||
|
|
||||||
Hugo supports table of contents with AsciiDoc content format.
|
Hugo supports table of contents with AsciiDoc content format.
|
||||||
|
|
||||||
In the header of your content file, specify the AsciiDoc TOC directives, by using the macro or auto style:
|
In the header of your content file, specify the AsciiDoc TOC directives necessary to ensure that the table of contents is generated. Hugo will use the generated TOC to populate the page variable `.TableOfContents` in the same way as described for Markdown. See example below:
|
||||||
|
|
||||||
```asciidoc
|
```asciidoc
|
||||||
// <!-- Your front matter up here -->
|
// <!-- Your front matter up here -->
|
||||||
:toc: macro
|
:toc:
|
||||||
// Set toclevels to be at least your hugo [markup.tableOfContents.endLevel] config key
|
// Set toclevels to be at least your hugo [markup.tableOfContents.endLevel] config key
|
||||||
:toclevels: 4
|
:toclevels: 4
|
||||||
toc::[]
|
|
||||||
|
|
||||||
== Introduction
|
== Introduction
|
||||||
|
|
||||||
|
@ -117,7 +116,6 @@ He lay on his armour-like back, and if he lifted his head a little he could see
|
||||||
|
|
||||||
A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops
|
A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops
|
||||||
```
|
```
|
||||||
Hugo will take this AsciiDoc and create a table of contents store it in the page variable `.TableOfContents`, in the same as described for Markdown.
|
|
||||||
|
|
||||||
[conditionals]: /templates/introduction/#conditionals
|
[conditionals]: /templates/introduction/#conditionals
|
||||||
[front matter]: /content-management/front-matter/
|
[front matter]: /content-management/front-matter/
|
||||||
|
|
|
@ -1535,7 +1535,8 @@
|
||||||
"verbose": false,
|
"verbose": false,
|
||||||
"trace": false,
|
"trace": false,
|
||||||
"failureLevel": "fatal",
|
"failureLevel": "fatal",
|
||||||
"workingFolderCurrent": false
|
"workingFolderCurrent": false,
|
||||||
|
"preserveTOC": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minify": {
|
"minify": {
|
||||||
|
|
|
@ -27,6 +27,7 @@ var (
|
||||||
Trace: false,
|
Trace: false,
|
||||||
FailureLevel: "fatal",
|
FailureLevel: "fatal",
|
||||||
WorkingFolderCurrent: false,
|
WorkingFolderCurrent: false,
|
||||||
|
PreserveTOC: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliDefault holds Asciidoctor CLI defaults (see https://asciidoctor.org/docs/user-manual/)
|
// CliDefault holds Asciidoctor CLI defaults (see https://asciidoctor.org/docs/user-manual/)
|
||||||
|
@ -86,4 +87,5 @@ type Config struct {
|
||||||
Trace bool
|
Trace bool
|
||||||
FailureLevel string
|
FailureLevel string
|
||||||
WorkingFolderCurrent bool
|
WorkingFolderCurrent bool
|
||||||
|
PreserveTOC bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package asciidocext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ type asciidocConverter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
|
func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
|
||||||
content, toc, err := extractTOC(a.getAsciidocContent(ctx.Src, a.ctx))
|
content, toc, err := a.extractTOC(a.getAsciidocContent(ctx.Src, a.ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -204,7 +203,7 @@ func getAsciidoctorExecPath() string {
|
||||||
|
|
||||||
// extractTOC extracts the toc from the given src html.
|
// extractTOC extracts the toc from the given src html.
|
||||||
// It returns the html without the TOC, and the TOC data
|
// It returns the html without the TOC, and the TOC data
|
||||||
func extractTOC(src []byte) ([]byte, tableofcontents.Root, error) {
|
func (a *asciidocConverter) extractTOC(src []byte) ([]byte, tableofcontents.Root, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.Write(src)
|
buf.Write(src)
|
||||||
node, err := html.Parse(&buf)
|
node, err := html.Parse(&buf)
|
||||||
|
@ -219,7 +218,9 @@ func extractTOC(src []byte) ([]byte, tableofcontents.Root, error) {
|
||||||
f = func(n *html.Node) bool {
|
f = func(n *html.Node) bool {
|
||||||
if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
|
if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
|
||||||
toc = parseTOC(n)
|
toc = parseTOC(n)
|
||||||
n.Parent.RemoveChild(n)
|
if !a.cfg.MarkupConfig.AsciidocExt.PreserveTOC {
|
||||||
|
n.Parent.RemoveChild(n)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if n.FirstChild != nil {
|
if n.FirstChild != nil {
|
||||||
|
@ -285,7 +286,7 @@ func parseTOC(doc *html.Node) tableofcontents.Root {
|
||||||
f(n.NextSibling, row, level)
|
f(n.NextSibling, row, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(doc.FirstChild, 0, 0)
|
f(doc.FirstChild, -1, 0)
|
||||||
return toc
|
return toc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,9 +301,8 @@ func attr(node *html.Node, key string) string {
|
||||||
|
|
||||||
func nodeContent(node *html.Node) string {
|
func nodeContent(node *html.Node) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := io.Writer(&buf)
|
|
||||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||||
html.Render(w, c)
|
html.Render(&buf, c)
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,11 +277,17 @@ func TestTableOfContents(t *testing.T) {
|
||||||
t.Skip("asciidoctor not installed")
|
t.Skip("asciidoctor not installed")
|
||||||
}
|
}
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
|
mconf := markup_config.Default
|
||||||
|
p, err := Provider.New(
|
||||||
|
converter.ProviderConfig{
|
||||||
|
MarkupConfig: mconf,
|
||||||
|
Logger: loggers.NewErrorLogger(),
|
||||||
|
},
|
||||||
|
)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
conv, err := p.New(converter.DocumentContext{})
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
b, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro
|
r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro
|
||||||
:toclevels: 4
|
:toclevels: 4
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
|
@ -300,11 +306,52 @@ testContent
|
||||||
== Section 2
|
== Section 2
|
||||||
`)})
|
`)})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
toc, ok := b.(converter.TableOfContentsProvider)
|
toc, ok := r.(converter.TableOfContentsProvider)
|
||||||
c.Assert(ok, qt.Equals, true)
|
c.Assert(ok, qt.Equals, true)
|
||||||
root := toc.TableOfContents()
|
expected := tableofcontents.Root{
|
||||||
c.Assert(root.ToHTML(2, 4, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a>\n <ul>\n <li><a href=\"#_section_1_1_1\">Section 1.1.1</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
Headers: tableofcontents.Headers{
|
||||||
c.Assert(root.ToHTML(2, 3, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a></li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
{
|
||||||
|
ID: "",
|
||||||
|
Text: "",
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "_introduction",
|
||||||
|
Text: "Introduction",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "_section_1",
|
||||||
|
Text: "Section 1",
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "_section_1_1",
|
||||||
|
Text: "Section 1.1",
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "_section_1_1_1",
|
||||||
|
Text: "Section 1.1.1",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "_section_1_2",
|
||||||
|
Text: "Section 1.2",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "_section_2",
|
||||||
|
Text: "Section 2",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
|
||||||
|
c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableOfContentsWithCode(t *testing.T) {
|
func TestTableOfContentsWithCode(t *testing.T) {
|
||||||
|
@ -322,26 +369,72 @@ func TestTableOfContentsWithCode(t *testing.T) {
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
conv, err := p.New(converter.DocumentContext{})
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
b, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
|
r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
|
||||||
|
|
||||||
== Some ` + "`code`" + ` in the title
|
== Some ` + "`code`" + ` in the title
|
||||||
`)})
|
`)})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
toc, ok := b.(converter.TableOfContentsProvider)
|
toc, ok := r.(converter.TableOfContentsProvider)
|
||||||
c.Assert(ok, qt.Equals, true)
|
c.Assert(ok, qt.Equals, true)
|
||||||
expected := tableofcontents.Headers{
|
expected := tableofcontents.Root{
|
||||||
{},
|
Headers: tableofcontents.Headers{
|
||||||
{
|
{
|
||||||
ID: "",
|
ID: "",
|
||||||
Text: "",
|
Text: "",
|
||||||
Headers: tableofcontents.Headers{
|
Headers: tableofcontents.Headers{
|
||||||
{
|
{
|
||||||
ID: "_some_code_in_the_title",
|
ID: "_some_code_in_the_title",
|
||||||
Text: "Some <code>code</code> in the title",
|
Text: "Some <code>code</code> in the title",
|
||||||
Headers: nil,
|
Headers: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.Assert(toc.TableOfContents().Headers, qt.DeepEquals, expected)
|
c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
|
||||||
|
c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableOfContentsPreserveTOC(t *testing.T) {
|
||||||
|
if !Supports() {
|
||||||
|
t.Skip("asciidoctor not installed")
|
||||||
|
}
|
||||||
|
c := qt.New(t)
|
||||||
|
mconf := markup_config.Default
|
||||||
|
mconf.AsciidocExt.PreserveTOC = true
|
||||||
|
p, err := Provider.New(
|
||||||
|
converter.ProviderConfig{
|
||||||
|
MarkupConfig: mconf,
|
||||||
|
Logger: loggers.NewErrorLogger(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc:
|
||||||
|
:idprefix:
|
||||||
|
:idseparator: -
|
||||||
|
|
||||||
|
== Some title
|
||||||
|
`)})
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
toc, ok := r.(converter.TableOfContentsProvider)
|
||||||
|
c.Assert(ok, qt.Equals, true)
|
||||||
|
expected := tableofcontents.Root{
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "",
|
||||||
|
Text: "",
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "some-title",
|
||||||
|
Text: "Some title",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
|
||||||
|
c.Assert(string(r.Bytes()), qt.Contains, "<div id=\"toc\" class=\"toc\">")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue