ref: 6a848cbc3a2487c8b015e715c2de44aef6051080
parent: 746ba803afee8f0f56ee0655cc55087f1822d39c
author: Helder Pereira <helfper@gmail.com>
date: Wed Sep 9 18:41:53 EDT 2020
markup/asciidocext: Fix AsciiDoc TOC with code Fixes #7649
--- a/docs/content/en/content-management/formats.md
+++ b/docs/content/en/content-management/formats.md
@@ -49,7 +49,7 @@
Hugo passes reasonable default arguments to these external helpers by default:
-- `asciidoctor`: `--no-header-footer --trace -`
+- `asciidoctor`: `--no-header-footer -`
- `rst2html`: `--leave-comments --initial-header-level=2`
- `pandoc`: `--mathjax`
@@ -81,7 +81,7 @@
safemode | `unsafe` | Safe mode level `unsafe`, `safe`, `server` or `secure`. Don't change this unless you know what you are doing.
sectionnumbers | `false` | Auto-number section titles.
verbose | `false` | Verbosely print processing information and configuration file checks to stderr.
-trace | `true` | Include backtrace information on errors.
+trace | `false` | Include backtrace information on errors.
failurelevel | `fatal` | The minimum logging level that triggers a non-zero exit code (failure).
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`.
--- a/docs/content/en/content-management/toc.md
+++ b/docs/content/en/content-management/toc.md
@@ -92,11 +92,11 @@
With the preceding example, even pages with > 400 words *and* `toc` not set to `false` will not render a table of contents if there are no headings in the page for the `{{.TableOfContents}}` variable to pull from.
{{% /note %}}
-## Usage with asciidoc
+## Usage with AsciiDoc
-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 style:
+In the header of your content file, specify the AsciiDoc TOC directives, by using the macro or auto style:
```asciidoc
// <!-- Your front matter up here -->
@@ -117,7 +117,7 @@
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 Asciddoc and create a table of contents store it in the page variable `.TableOfContents`, in the same as described for Markdown.
+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
[front matter]: /content-management/front-matter/
--- a/markup/asciidocext/convert.go
+++ b/markup/asciidocext/convert.go
@@ -11,13 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package asciidocext converts Asciidoc to HTML using Asciidoc or Asciidoctor
-// external binaries. The `asciidoc` module is reserved for a future golang
+// Package asciidocext converts AsciiDoc to HTML using Asciidoctor
+// external binary. The `asciidoc` module is reserved for a future golang
// implementation.
package asciidocext
import (
"bytes"
+ "io"
"os/exec"
"path/filepath"
@@ -77,12 +78,12 @@
return false
}
-// getAsciidocContent calls asciidoctor or asciidoc as an external helper
+// getAsciidocContent calls asciidoctor as an external helper
// to convert AsciiDoc content to HTML.
func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) []byte {
path := getAsciidoctorExecPath()
if path == "" {
- a.cfg.Logger.ERROR.Println("asciidoctor / asciidoc not found in $PATH: Please install.\n",
+ a.cfg.Logger.ERROR.Println("asciidoctor not found in $PATH: Please install.\n",
" Leaving AsciiDoc content unrendered.")
return src
}
@@ -216,30 +217,21 @@
toVisit []*html.Node
)
f = func(n *html.Node) bool {
- if n.Type == html.ElementNode && n.Data == "div" {
- for _, a := range n.Attr {
- if a.Key == "id" && a.Val == "toc" {
- toc, err = parseTOC(n)
- if err != nil {
- return false
- }
- n.Parent.RemoveChild(n)
- return true
- }
- }
+ if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
+ toc = parseTOC(n)
+ n.Parent.RemoveChild(n)
+ return true
}
if n.FirstChild != nil {
toVisit = append(toVisit, n.FirstChild)
}
- if n.NextSibling != nil {
- if ok := f(n.NextSibling); ok {
- return true
- }
+ if n.NextSibling != nil && f(n.NextSibling) {
+ return true
}
for len(toVisit) > 0 {
nv := toVisit[0]
toVisit = toVisit[1:]
- if ok := f(nv); ok {
+ if f(nv) {
return true
}
}
@@ -261,50 +253,58 @@
}
// parseTOC returns a TOC root from the given toc Node
-func parseTOC(doc *html.Node) (tableofcontents.Root, error) {
+func parseTOC(doc *html.Node) tableofcontents.Root {
var (
toc tableofcontents.Root
f func(*html.Node, int, int)
)
- f = func(n *html.Node, parent, level int) {
+ f = func(n *html.Node, row, level int) {
if n.Type == html.ElementNode {
switch n.Data {
case "ul":
if level == 0 {
- parent += 1
+ row++
}
- level += 1
- f(n.FirstChild, parent, level)
+ level++
+ f(n.FirstChild, row, level)
case "li":
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type != html.ElementNode || c.Data != "a" {
continue
}
- var href string
- for _, a := range c.Attr {
- if a.Key == "href" {
- href = a.Val[1:]
- break
- }
- }
- for d := c.FirstChild; d != nil; d = d.NextSibling {
- if d.Type == html.TextNode {
- toc.AddAt(tableofcontents.Header{
- Text: d.Data,
- ID: href,
- }, parent, level)
- }
- }
+ href := attr(c, "href")[1:]
+ toc.AddAt(tableofcontents.Header{
+ Text: nodeContent(c),
+ ID: href,
+ }, row, level)
}
- f(n.FirstChild, parent, level)
+ f(n.FirstChild, row, level)
}
}
if n.NextSibling != nil {
- f(n.NextSibling, parent, level)
+ f(n.NextSibling, row, level)
}
}
f(doc.FirstChild, 0, 0)
- return toc, nil
+ return toc
+}
+
+func attr(node *html.Node, key string) string {
+ for _, a := range node.Attr {
+ if a.Key == key {
+ return a.Val
+ }
+ }
+ return ""
+}
+
+func nodeContent(node *html.Node) string {
+ var buf bytes.Buffer
+ w := io.Writer(&buf)
+ for c := node.FirstChild; c != nil; c = c.NextSibling {
+ html.Render(w, c)
+ }
+ return buf.String()
}
// Supports returns whether Asciidoctor is installed on this computer.
--- a/markup/asciidocext/convert_test.go
+++ b/markup/asciidocext/convert_test.go
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package asciidocext converts Asciidoc to HTML using Asciidoc or Asciidoctor
-// external binaries. The `asciidoc` module is reserved for a future golang
+// Package asciidocext converts AsciiDoc to HTML using Asciidoctor
+// external binary. The `asciidoc` module is reserved for a future golang
// implementation.
package asciidocext
@@ -24,6 +24,7 @@
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
@@ -250,7 +251,7 @@
func TestConvert(t *testing.T) {
if !Supports() {
- t.Skip("asciidoc/asciidoctor not installed")
+ t.Skip("asciidoctor not installed")
}
c := qt.New(t)
@@ -273,7 +274,7 @@
func TestTableOfContents(t *testing.T) {
if !Supports() {
- t.Skip("asciidoc/asciidoctor not installed")
+ t.Skip("asciidoctor not installed")
}
c := qt.New(t)
p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
@@ -304,4 +305,43 @@
root := toc.TableOfContents()
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>")
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>")
+}
+
+func TestTableOfContentsWithCode(t *testing.T) {
+ if !Supports() {
+ t.Skip("asciidoctor not installed")
+ }
+ c := qt.New(t)
+ mconf := markup_config.Default
+ 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)
+ b, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
+
+== Some ` + "`code`" + ` in the title
+`)})
+ c.Assert(err, qt.IsNil)
+ toc, ok := b.(converter.TableOfContentsProvider)
+ c.Assert(ok, qt.Equals, true)
+ expected := tableofcontents.Headers{
+ {},
+ {
+ ID: "",
+ Text: "",
+ Headers: tableofcontents.Headers{
+ {
+ ID: "_some_code_in_the_title",
+ Text: "Some <code>code</code> in the title",
+ Headers: nil,
+ },
+ },
+ },
+ }
+ c.Assert(toc.TableOfContents().Headers, qt.DeepEquals, expected)
}
--- a/markup/tableofcontents/tableofcontents.go
+++ b/markup/tableofcontents/tableofcontents.go
@@ -40,19 +40,19 @@
}
// AddAt adds the header into the given location.
-func (toc *Root) AddAt(h Header, y, x int) {
- for i := len(toc.Headers); i <= y; i++ {
+func (toc *Root) AddAt(h Header, row, level int) {
+ for i := len(toc.Headers); i <= row; i++ {
toc.Headers = append(toc.Headers, Header{})
}
- if x == 0 {
- toc.Headers[y] = h
+ if level == 0 {
+ toc.Headers[row] = h
return
}
- header := &toc.Headers[y]
+ header := &toc.Headers[row]
- for i := 1; i < x; i++ {
+ for i := 1; i < level; i++ {
if len(header.Headers) == 0 {
header.Headers = append(header.Headers, Header{})
}