toc

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2023 License: MIT Imports: 6 Imported by: 1

README

goldmark-toc

Go Reference Go codecov

goldmark-toc is an add-on for the goldmark Markdown parser that adds support for rendering a table-of-contents.

Demo: A web-based demonstration of the extension is available at https://abhinav.github.io/goldmark-toc/demo/.

Installation

go get go.abhg.dev/goldmark/toc@latest

Usage

To use goldmark-toc, import the toc package.

import "go.abhg.dev/goldmark/toc"

Following that, you have three options for using this package:

  • Extension: This is the easiest way to get a table of contents into your document and provides very little control over the output.

  • Transformer: This is the next easiest option and provides more control over the output.

  • Manual: This option requires the most work but also provides the most control.

Extension

To use this package as a simple Goldmark extension, install the Extender when constructing the goldmark.Markdown object.

markdown := goldmark.New(
    // ...
    goldmark.WithParserOptions(parser.WithAutoHeadingID()),
    goldmark.WithExtensions(
        // ...
        &toc.Extender{},
    ),
)

This will add a "Table of Contents" section to the top of every Markdown document parsed by this Markdown object.

NOTE: The example above enables parser.WithAutoHeadingID. Without this or a custom implementation of parser.IDs, none of the headings in the document will have links generated for them.

Changing the title

If you want to use a title other than "Table of Contents", set the Title field of Extender.

&toc.Extender{
  Title: "Contents",
}
Limiting the Table of Contents

By default, goldmark-toc will include all headers in the table of contents. If you want to limit the depth of the table of contents, use the MaxDepth field.

&toc.Extender{
  MaxDepth: 3,
}

Headers with a level higher than the specified value will not be included in the table of contents.

Transformer

Installing this package as an AST Transformer provides slightly more control over the output. To use it, install the AST transformer on the Goldmark Markdown parser.

markdown := goldmark.New(...)
markdown.Parser().AddOptions(
    parser.WithAutoHeadingID(),
    parser.WithASTTransformers(
        util.Prioritized(&toc.Transformer{
            Title: "Contents",
        }, 100),
    ),
)

This will generate a "Contents" section at the top of all Markdown documents parsed by this parser.

As with the previous example, this enables parser.WithAutoHeadingID to get auto-generated heading IDs.

Manual

If you use this package manually to generate Tables of Contents, you have a lot more control over the behavior. This requires a few steps.

Parse Markdown

Parse a Markdown document with goldmark.

markdown := goldmark.New(...)
markdown.Parser().AddOptions(parser.WithAutoHeadingID())
doc := markdown.Parser().Parse(text.NewReader(src))

Note that the parser must be configured to generate IDs for headers or the headers in the table of contents won't have anything to point to. This can be accomplished by adding the parser.WithAutoHeadingID option as in the example above, or with a custom implementation of goldmark/parser.IDs by using the snippet below.

markdown := goldmark.New(...)
pctx := parser.NewContext(parser.WithIDs(ids))
doc := parser.Parse(text.NewReader(src), parser.WithContext(pctx))
Build a table of contents

After parsing a Markdown document, inspect it with toc.

tree, err := toc.Inspect(doc, src)
if err != nil {
  // handle the error
}

If you need to limit the depth of the table of contents, use the MaxDepth option.

tree, err := toc.Inspect(doc, src, toc.MaxDepth(3))
Generate a Markdown list

You can render the table of contents into a Markdown list with toc.RenderList.

list := toc.RenderList(tree)

This builds a list representation of the table of contents to be rendered as Markdown or HTML.

You may manipulate the tree before rendering the list.

Render HTML

Finally, render this table of contents along with your Markdown document:

markdown.Renderer().Render(output, src, list) // table of contents
markdown.Renderer().Render(output, src, doc)  // document

Alternatively, include the table of contents into your Markdown document in your desired position and render it using your Markdown renderer.

// Prepend table of contents to the front of the document.
doc.InsertBefore(doc, doc.FirstChild(), list)

// Render the document.
markdown.Renderer().Render(output, src, doc)

Documentation

Overview

Package toc provides support for building a Table of Contents from a goldmark Markdown document.

The package operates in two stages: inspection and rendering. During inspection, the package analyzes an existing Markdown document, and builds a Table of Contents from it.

markdown := goldmark.New(...)

parser := markdown.Parser()
doc := parser.Parse(text.NewReader(src))
tocTree, err := toc.Inspect(doc, src)

During rendering, it converts the Table of Contents into a list of headings with nested items under each as a goldmark Markdown document. You may manipulate the TOC, removing items from it or simplifying it, before rendering.

tocList := toc.RenderList(tocTree)

You can render that Markdown document using goldmark into whatever form you prefer.

renderer := markdown.Renderer()
renderer.Render(out, src, tocList)

The following diagram summarizes the flow of information with goldmark-toc.

   src
+--------+                           +-------------------+
|        |   goldmark/Parser.Parse   |                   |
| []byte :---------------------------> goldmark/ast.Node |
|        |                           |                   |
+---.----+                           +-------.-----.-----+
    |                                        |     |
    '----------------.     .-----------------'     |
                      \   /                        |
                       \ /                         |
                        |                          |
                        | toc.Inspect              |
                        |                          |
                   +----v----+                     |
                   |         |                     |
                   | toc.TOC |                     |
                   |         |                     |
                   +----.----+                     |
                        |                          |
                        | toc/Renderer.Render      |
                        |                          |
              +---------v---------+                |
              |                   |                |
              | goldmark/ast.Node |                |
              |                   |                |
              +---------.---------+                |
                        |                          |
                        '-------.   .--------------'
                                 \ /
                                  |
         goldmark/Renderer.Render |
                                  |
                                  v
                              +------+
                              | HTML |
                              +------+
Example
src := []byte(`
# A section

Hello

# Another section

## A sub-section

### A sub-sub-section

Bye
`)

markdown := goldmark.New()

// Request that IDs are automatically assigned to headers.
markdown.Parser().AddOptions(parser.WithAutoHeadingID())
// Alternatively, we can provide our own implementation of parser.IDs
// and use,
//
//   pctx := parser.NewContext(parser.WithIDs(ids))
//   doc := parser.Parse(text.NewReader(src), parser.WithContext(pctx))

doc := markdown.Parser().Parse(text.NewReader(src))

// Inspect the parsed Markdown document to find headers and build a
// tree for the table of contents.
tree, err := toc.Inspect(doc, src)
if err != nil {
	panic(err)
}

// Render the tree as-is into a Markdown list.
treeList := toc.RenderList(tree)

// Render the Markdown list into HTML.
markdown.Renderer().Render(os.Stdout, src, treeList)
Output:

<ul>
<li>
<a href="#a-section">A section</a></li>
<li>
<a href="#another-section">Another section</a><ul>
<li>
<a href="#a-sub-section">A sub-section</a><ul>
<li>
<a href="#a-sub-sub-section">A sub-sub-section</a></li>
</ul>
</li>
</ul>
</li>
</ul>

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderList

func RenderList(toc *TOC) ast.Node

RenderList renders a table of contents as a nested list with a sane, default configuration for the ListRenderer.

Types

type Extender

type Extender struct {
	// Title is the title of the table of contents section.
	// Defaults to "Table of Contents" if unspecified.
	Title string

	// MaxDepth is the maximum depth of the table of contents.
	// Headings with a level greater than the specified depth will be ignored.
	// See the documentation for MaxDepth for more information.
	//
	// Defaults to 0 (no limit) if unspecified.
	MaxDepth int

	// MinDepth is the minimum depth of the table of contents.
	// Headings with a level greater than the specified depth will be ignored.
	// See the documentation for MaxDepth for more information.
	//
	// Defaults to 0 (no limit) if unspecified.
	MinDepth int
}

Extender extends a Goldmark Markdown parser and renderer to always include a table of contents in the output.

To use this, install it into your Goldmark Markdown object.

md := goldmark.New(
  // ...
  goldmark.WithParserOptions(parser.WithAutoHeadingID()),
  goldmark.WithExtensions(
    // ...
    &toc.Extender{
    },
  ),
)

This will install the default Transformer. For more control, install the Transformer directly on the Markdown Parser.

NOTE: Unless you've supplied your own parser.IDs implementation, you'll need to enable the WithAutoHeadingID option on the parser to generate IDs and links for headings.

func (*Extender) Extend

func (e *Extender) Extend(md goldmark.Markdown)

Extend adds support for rendering a table of contents to the provided Markdown parser/renderer.

type InspectOption

type InspectOption interface {
	// contains filtered or unexported methods
}

InspectOption customizes the behavior of Inspect.

func MaxDepth

func MaxDepth(depth int) InspectOption

MaxDepth limits the depth of the table of contents. Headings with a level greater than the specified depth will be ignored.

For example, given the following:

# Foo
## Bar
### Baz
# Quux
## Qux

MaxDepth(1) will result in the following:

TOC{Items: ...}
 |
 +--- &Item{Title: "Foo", ID: "foo"}
 |
 +--- &Item{Title: "Quux", ID: "quux", Items: ...}

Whereas, MaxDepth(2) will result in the following:

TOC{Items: ...}
 |
 +--- &Item{Title: "Foo", ID: "foo", Items: ...}
 |     |
 |     +--- &Item{Title: "Bar", ID: "bar"}
 |
 +--- &Item{Title: "Quux", ID: "quux", Items: ...}
       |
       +--- &Item{Title: "Qux", ID: "qux"}

A value of 0 or less will result in no limit.

The default is no limit.

func MinDepth

func MinDepth(depth int) InspectOption

min depth

type Item

type Item struct {
	// Title of this item in the table of contents.
	//
	// This may be blank for items that don't refer to a heading, and only
	// have sub-items.
	Title []byte

	// ID is the identifier for the heading that this item refers to. This
	// is the fragment portion of the link without the "#".
	//
	// This may be blank if the item doesn't have an id assigned to it, or
	// if it doesn't have a title.
	//
	// Enable AutoHeadingID in your parser if you expected these to be set
	// but they weren't.
	ID []byte

	// Items references children of this item.
	//
	// For a heading at level 3, Items, contains the headings at level 4
	// under that section.
	Items Items
}

Item is a single item in the table of contents.

type Items

type Items []*Item

Items is a list of items in a table of contents.

type ListRenderer

type ListRenderer struct {
	// Marker for elements of the list, e.g. '-', '*', etc.
	//
	// Defaults to '*'.
	Marker byte
}

ListRenderer builds a nested list from a table of contents.

For example,

# Foo
## Bar
## Baz
# Qux

// becomes

- Foo
  - Bar
  - Baz
- Qux

func (*ListRenderer) Render

func (r *ListRenderer) Render(toc *TOC) ast.Node

Render renders the table of contents into Markdown.

type TOC

type TOC struct {
	// Items holds the top-level headings under the table of contents.
	Items Items
}

TOC is the table of contents. It's the top-level object under which the rest of the table of contents resides.

func Inspect

func Inspect(n ast.Node, src []byte, options ...InspectOption) (*TOC, error)

Inspect builds a table of contents by inspecting the provided document.

The table of contents is represents as a tree where each item represents a heading or a heading level with zero or more children.

For example,

# Section 1
## Subsection 1.1
## Subsection 1.2
# Section 2
## Subsection 2.1
# Section 3

Will result in the following items.

TOC{Items: ...}
 |
 +--- &Item{Title: "Section 1", ID: "section-1", Items: ...}
 |     |
 |     +--- &Item{Title: "Subsection 1.1", ID: "subsection-1-1"}
 |     |
 |     +--- &Item{Title: "Subsection 1.2", ID: "subsection-1-2"}
 |
 +--- &Item{Title: "Section 2", ID: "section-2", Items: ...}
 |     |
 |     +--- &Item{Title: "Subsection 2.1", ID: "subsection-2-1"}
 |
 +--- &Item{Title: "Section 3", ID: "section-3"}

You may analyze or manipulate the table of contents before rendering it.

type Transformer

type Transformer struct {
	// Title is the title of the table of contents section.
	// Defaults to "Table of Contents" if unspecified.
	Title string

	// MaxDepth is the maximum depth of the table of contents.
	// See the documentation for MaxDepth for more information.
	MaxDepth int

	// MinDepth is the minimum depth of the table of contents.
	// See the documentation for MinDepth for more information.
	MinDepth int
}

Transformer is a Goldmark AST transformer adds a TOC to the top of a Markdown document.

To use this, either install the Extender on the goldmark.Markdown object, or install the AST transformer on the Markdown parser like so.

markdown := goldmark.New(...)
markdown.Parser().AddOptions(
  parser.WithAutoHeadingID(),
  parser.WithASTTransformers(
    util.Prioritized(&toc.Transformer{}, 100),
  ),
)

NOTE: Unless you've supplied your own parser.IDs implementation, you'll need to enable the WithAutoHeadingID option on the parser to generate IDs and links for headings.

func (*Transformer) Transform

func (t *Transformer) Transform(doc *ast.Document, reader text.Reader, _ parser.Context)

Transform adds a table of contents to the provided Markdown document.

Errors encountered while transforming are ignored. For more fine-grained control, use Inspect and transform the document manually.

Directories

Path Synopsis
demo module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL