From cd558958a0c0ecd06f7560a38e27334fe983e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 11 Apr 2016 13:17:25 +0200 Subject: [PATCH] Use Node.ID for anchor ID Fixes #2057 --- helpers/content.go | 19 ++++++++++--------- helpers/content_test.go | 10 +++++----- hugolib/handler_test.go | 10 +++++----- hugolib/hugolib_test.go | 34 ++++++++++++++++++++++++++++++++++ hugolib/node.go | 12 ++++++++++-- hugolib/node_test.go | 17 +++++++---------- hugolib/page.go | 4 ++-- hugolib/page_test.go | 7 +++++-- hugolib/shortcode.go | 2 +- hugolib/shortcode_test.go | 9 +++++++-- hugolib/site_test.go | 17 ++++++++++------- 11 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 hugolib/hugolib_test.go diff --git a/helpers/content.go b/helpers/content.go index f6034905..3ac91b36 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -23,6 +23,10 @@ import ( "os/exec" "unicode/utf8" + "fmt" + "strings" + "sync" + "github.com/miekg/mmark" "github.com/mitchellh/mapstructure" "github.com/russross/blackfriday" @@ -30,9 +34,6 @@ import ( bp "github.com/spf13/hugo/bufferpool" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" - - "strings" - "sync" ) // SummaryLength is the length of the summary that Hugo extracts from a content. @@ -167,11 +168,11 @@ func getHTMLRenderer(defaultFlags int, ctx *RenderingContext) blackfriday.Render FootnoteReturnLinkContents: viper.GetString("FootnoteReturnLinkContents"), } - b := len(ctx.DocumentID) != 0 + b := ctx.DocumentID != 0 if b && !ctx.getConfig().PlainIDAnchors { - renderParameters.FootnoteAnchorPrefix = ctx.DocumentID + ":" + renderParameters.FootnoteAnchorPrefix - renderParameters.HeaderIDSuffix = ":" + ctx.DocumentID + renderParameters.FootnoteAnchorPrefix = fmt.Sprintf("%d:%s", ctx.DocumentID, renderParameters.FootnoteAnchorPrefix) + renderParameters.HeaderIDSuffix = fmt.Sprintf(":%d", ctx.DocumentID) } htmlFlags := defaultFlags @@ -258,10 +259,10 @@ func getMmarkHTMLRenderer(defaultFlags int, ctx *RenderingContext) mmark.Rendere FootnoteReturnLinkContents: viper.GetString("FootnoteReturnLinkContents"), } - b := len(ctx.DocumentID) != 0 + b := ctx.DocumentID != 0 if b && !ctx.getConfig().PlainIDAnchors { - renderParameters.FootnoteAnchorPrefix = ctx.DocumentID + ":" + renderParameters.FootnoteAnchorPrefix + renderParameters.FootnoteAnchorPrefix = fmt.Sprintf("%d:%s", ctx.DocumentID, renderParameters.FootnoteAnchorPrefix) // renderParameters.HeaderIDSuffix = ":" + ctx.DocumentId } @@ -343,7 +344,7 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) { type RenderingContext struct { Content []byte PageFmt string - DocumentID string + DocumentID int Config *Blackfriday FileResolver FileResolverFunc LinkResolver LinkResolverFunc diff --git a/helpers/content_test.go b/helpers/content_test.go index a89b4992..347884f0 100644 --- a/helpers/content_test.go +++ b/helpers/content_test.go @@ -172,15 +172,15 @@ func TestGetHTMLRendererAllFlags(t *testing.T) { func TestGetHTMLRendererAnchors(t *testing.T) { ctx := &RenderingContext{} - ctx.DocumentID = "testid" + ctx.DocumentID = 123 ctx.Config = ctx.getConfig() ctx.Config.PlainIDAnchors = false actualRenderer := getHTMLRenderer(0, ctx) headerBuffer := &bytes.Buffer{} footnoteBuffer := &bytes.Buffer{} - expectedFootnoteHref := []byte("href=\"#fn:testid:href\"") - expectedHeaderID := []byte("

\n") + expectedFootnoteHref := []byte("href=\"#fn:123:href\"") + expectedHeaderID := []byte("

\n") actualRenderer.Header(headerBuffer, func() bool { return true }, 1, "id") actualRenderer.FootnoteRef(footnoteBuffer, []byte("href"), 1) @@ -196,14 +196,14 @@ func TestGetHTMLRendererAnchors(t *testing.T) { func TestGetMmarkHTMLRenderer(t *testing.T) { ctx := &RenderingContext{} - ctx.DocumentID = "testid" + ctx.DocumentID = 321 ctx.Config = ctx.getConfig() ctx.Config.PlainIDAnchors = false actualRenderer := getMmarkHTMLRenderer(0, ctx) headerBuffer := &bytes.Buffer{} footnoteBuffer := &bytes.Buffer{} - expectedFootnoteHref := []byte("href=\"#fn:testid:href\"") + expectedFootnoteHref := []byte("href=\"#fn:321:href\"") expectedHeaderID := []byte("

") actualRenderer.FootnoteRef(footnoteBuffer, []byte("href"), 1) diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index 29b1161e..e48d2693 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -25,8 +25,8 @@ import ( ) func TestDefaultHandler(t *testing.T) { - viper.Reset() - defer viper.Reset() + setUp() + defer tearDown() hugofs.InitMemFs() sources := []source.ByteSource{ @@ -63,14 +63,14 @@ func TestDefaultHandler(t *testing.T) { doc string expected string }{ - {filepath.FromSlash("sect/doc1.html"), "\n\n

title

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc1.html"), "\n\n

title

\n\n

some content

\n"}, {filepath.FromSlash("sect/doc2.html"), "more content"}, - {filepath.FromSlash("sect/doc3.html"), "\n\n

doc3

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc3.html"), "\n\n

doc3

\n\n

some content

\n"}, {filepath.FromSlash("sect/doc3/img1.png"), string([]byte("‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚"))}, {filepath.FromSlash("sect/img2.gif"), string([]byte("GIF89a��€��ÿÿÿ���,�������D�;"))}, {filepath.FromSlash("sect/img2.spf"), string([]byte("****FAKE-FILETYPE****"))}, {filepath.FromSlash("doc7.html"), "doc7 content"}, - {filepath.FromSlash("sect/doc8.html"), "\n\n

title

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc8.html"), "\n\n

title

\n\n

some content

\n"}, } for _, test := range tests { diff --git a/hugolib/hugolib_test.go b/hugolib/hugolib_test.go new file mode 100644 index 00000000..8f17553d --- /dev/null +++ b/hugolib/hugolib_test.go @@ -0,0 +1,34 @@ +// Copyright 2016 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hugolib + +import ( + "github.com/spf13/viper" +) + +var stableNodeIDProvider nodeIDProviderFunc = func(n *Node) int { + return 42 +} + +// common test setup. +func setUp() { + viper.Reset() + nodeIDProvider = stableNodeIDProvider +} + +// common test cleanup. +func tearDown() { + viper.Reset() + nodeIDProvider = defaultNodeIDProvider +} diff --git a/hugolib/node.go b/hugolib/node.go index 7e9ad745..dde1bd69 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -47,6 +47,15 @@ type Node struct { // but that would lead to massive changes; do it simple for now. var nodeIDCounter uint64 +type nodeIDProviderFunc func(n *Node) int + +var defaultNodeIDProvider nodeIDProviderFunc = func(n *Node) int { + n.idInit.Do(func() { n.id = nextNodeID() }) + return n.id +} + +var nodeIDProvider nodeIDProviderFunc = defaultNodeIDProvider + func nextNodeID() int { return int(atomic.AddUint64(&nodeIDCounter, 1)) } @@ -55,8 +64,7 @@ func nextNodeID() int { // This is unique for a given Hugo build, but must not be considered stable. // See UniqueID on Page for an identify that is stable for repeated builds. func (n *Node) ID() int { - n.idInit.Do(func() { n.id = nextNodeID() }) - return n.id + return nodeIDProvider(n) } func (n *Node) Now() time.Time { diff --git a/hugolib/node_test.go b/hugolib/node_test.go index 5b83cc0a..a0d08ed0 100644 --- a/hugolib/node_test.go +++ b/hugolib/node_test.go @@ -43,14 +43,6 @@ func TestNodeSimpleMethods(t *testing.T) { } func TestNodeID(t *testing.T) { - t.Parallel() - - n1 := &Node{} - n2 := &Node{} - - assert.True(t, n1.ID() > 0) - assert.Equal(t, n1.ID(), n1.ID()) - assert.True(t, n2.ID() > n1.ID()) var wg sync.WaitGroup @@ -58,8 +50,13 @@ func TestNodeID(t *testing.T) { wg.Add(1) go func(j int) { for k := 0; k < 10; k++ { - n := &Node{} - assert.True(t, n.ID() > 0) + n1 := &Node{} + n2 := &Node{} + + assert.True(t, n1.ID() > 0) + assert.Equal(t, n1.ID(), n1.ID()) + assert.True(t, n2.ID() > n1.ID()) + } wg.Done() }(i) diff --git a/hugolib/page.go b/hugolib/page.go index cff84737..9a9deecc 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -265,7 +265,7 @@ func (p *Page) renderBytes(content []byte) []byte { } return helpers.RenderBytes( &helpers.RenderingContext{Content: content, PageFmt: p.determineMarkupType(), - DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn}) + DocumentID: p.ID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn}) } func (p *Page) renderContent(content []byte) []byte { @@ -280,7 +280,7 @@ func (p *Page) renderContent(content []byte) []byte { } } return helpers.RenderBytesWithTOC(&helpers.RenderingContext{Content: content, PageFmt: p.determineMarkupType(), - DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn}) + DocumentID: p.ID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn}) } func (p *Page) getRenderingConfig() *helpers.Blackfriday { diff --git a/hugolib/page_test.go b/hugolib/page_test.go index b492bab2..6e636261 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -605,14 +605,17 @@ func TestPageWithAdditionalExtension(t *testing.T) { } func TestTableOfContents(t *testing.T) { + setUp() + defer tearDown() + p, _ := NewPage("tocpage.md") _, err := p.ReadFrom(strings.NewReader(pageWithToC)) p.Convert() if err != nil { t.Fatalf("Unable to create a page with frontmatter and body content: %s", err) } - checkPageContent(t, p, "\n\n

For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.

\n\n

AA

\n\n

I have no idea, of course, how long it took me to reach the limit of the plain,\nbut at last I entered the foothills, following a pretty little canyon upward\ntoward the mountains. Beside me frolicked a laughing brooklet, hurrying upon\nits noisy way down to the silent sea. In its quieter pools I discovered many\nsmall fish, of four-or five-pound weight I should imagine. In appearance,\nexcept as to size and color, they were not unlike the whale of our own seas. As\nI watched them playing about I discovered, not only that they suckled their\nyoung, but that at intervals they rose to the surface to breathe as well as to\nfeed upon certain grasses and a strange, scarlet lichen which grew upon the\nrocks just above the water line.

\n\n

AAA

\n\n

I remember I felt an extraordinary persuasion that I was being played with,\nthat presently, when I was upon the very verge of safety, this mysterious\ndeath–as swift as the passage of light–would leap after me from the pit about\nthe cylinder and strike me down. ## BB

\n\n

BBB

\n\n

“You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”

\n") - checkPageTOC(t, p, "") + checkPageContent(t, p, "\n\n

For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.

\n\n

AA

\n\n

I have no idea, of course, how long it took me to reach the limit of the plain,\nbut at last I entered the foothills, following a pretty little canyon upward\ntoward the mountains. Beside me frolicked a laughing brooklet, hurrying upon\nits noisy way down to the silent sea. In its quieter pools I discovered many\nsmall fish, of four-or five-pound weight I should imagine. In appearance,\nexcept as to size and color, they were not unlike the whale of our own seas. As\nI watched them playing about I discovered, not only that they suckled their\nyoung, but that at intervals they rose to the surface to breathe as well as to\nfeed upon certain grasses and a strange, scarlet lichen which grew upon the\nrocks just above the water line.

\n\n

AAA

\n\n

I remember I felt an extraordinary persuasion that I was being played with,\nthat presently, when I was upon the very verge of safety, this mysterious\ndeath–as swift as the passage of light–would leap after me from the pit about\nthe cylinder and strike me down. ## BB

\n\n

BBB

\n\n

“You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”

\n") + checkPageTOC(t, p, "") } func TestPageWithMoreTag(t *testing.T) { diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 876e9293..dfe26d4d 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -235,7 +235,7 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page, t tpl.Tem if sc.doMarkup { newInner := helpers.RenderBytes(&helpers.RenderingContext{ Content: []byte(inner), PageFmt: p.determineMarkupType(), - DocumentID: p.UniqueID(), Config: p.getRenderingConfig()}) + DocumentID: p.ID(), Config: p.getRenderingConfig()}) // If the type is “unknown” or “markdown”, we assume the markdown // generation has been performed. Given the input: `a line`, markdown diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index ab764b84..68c7f922 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -168,6 +168,9 @@ func TestInnerSC(t *testing.T) { } func TestInnerSCWithMarkdown(t *testing.T) { + setUp() + defer tearDown() + tem := tpl.New() tem.AddInternalShortcode("inside.html", `{{ .Inner }}`) @@ -176,10 +179,12 @@ func TestInnerSCWithMarkdown(t *testing.T) { [link](http://spf13.com) and text -{{% /inside %}}`, "

More Here

\n\n

link and text

\n
", tem) +{{% /inside %}}`, "

More Here

\n\n

link and text

\n
", tem) } func TestInnerSCWithAndWithoutMarkdown(t *testing.T) { + setUp() + defer tearDown() tem := tpl.New() tem.AddInternalShortcode("inside.html", `{{ .Inner }}`) @@ -198,7 +203,7 @@ And then: This is **plain** text. {{< /inside >}} -`, "

More Here

\n\n

link and text

\n
\n\nAnd then:\n\n
\n# More Here\n\nThis is **plain** text.\n\n
\n", tem) +`, "

More Here

\n\n

link and text

\n
\n\nAnd then:\n\n
\n# More Here\n\nThis is **plain** text.\n\n
\n", tem) } func TestEmbeddedSC(t *testing.T) { diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 9f0abc98..55e7b529 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -142,6 +142,9 @@ func NopCloser(w io.Writer) io.WriteCloser { } func TestRenderThing(t *testing.T) { + setUp() + defer tearDown() + tests := []struct { content string template string @@ -149,7 +152,7 @@ func TestRenderThing(t *testing.T) { }{ {pageSimpleTitle, templateTitle, "simple template"}, {pageSimpleTitle, templateFunc, "simple-template"}, - {pageWithMd, templateContent, "\n\n

heading 1

\n\n

text

\n\n

heading 2

\n\n

more text

\n"}, + {pageWithMd, templateContent, "\n\n

heading 1

\n\n

text

\n\n

heading 2

\n\n

more text

\n"}, {simplePageRFC3339Date, templateDate, "2013-05-17 16:59:30 +0000 UTC"}, } @@ -556,8 +559,8 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { } func TestSkipRender(t *testing.T) { - viper.Reset() - defer viper.Reset() + setUp() + defer tearDown() hugofs.InitMemFs() sources := []source.ByteSource{ @@ -593,14 +596,14 @@ func TestSkipRender(t *testing.T) { doc string expected string }{ - {filepath.FromSlash("sect/doc1.html"), "\n\n

title

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc1.html"), "\n\n

title

\n\n

some content

\n"}, {filepath.FromSlash("sect/doc2.html"), "more content"}, - {filepath.FromSlash("sect/doc3.html"), "\n\n

doc3

\n\n

some content

\n"}, - {filepath.FromSlash("sect/doc4.html"), "\n\n

doc4

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc3.html"), "\n\n

doc3

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc4.html"), "\n\n

doc4

\n\n

some content

\n"}, {filepath.FromSlash("sect/doc5.html"), "body5"}, {filepath.FromSlash("sect/doc6.html"), "body5"}, {filepath.FromSlash("doc7.html"), "doc7 content"}, - {filepath.FromSlash("sect/doc8.html"), "\n\n

title

\n\n

some content

\n"}, + {filepath.FromSlash("sect/doc8.html"), "\n\n

title

\n\n

some content

\n"}, } for _, test := range tests {