mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-11 03:08:08 +00:00
Big refactor of pages code. Changed TOC code to only parse when actually used
This commit is contained in:
parent
f62e3e9940
commit
d0825a211a
5 changed files with 642 additions and 618 deletions
207
hugolib/page.go
207
hugolib/page.go
|
@ -28,29 +28,27 @@ import (
|
||||||
json "launchpad.net/rjson"
|
json "launchpad.net/rjson"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Status string
|
Status string
|
||||||
Images []string
|
Images []string
|
||||||
RawContent []byte
|
rawContent []byte
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
Summary template.HTML
|
Summary template.HTML
|
||||||
TableOfContents template.HTML
|
Truncated bool
|
||||||
Truncated bool
|
plain string // TODO should be []byte
|
||||||
plain string // TODO should be []byte
|
Params map[string]interface{}
|
||||||
Params map[string]interface{}
|
contentType string
|
||||||
contentType string
|
Draft bool
|
||||||
Draft bool
|
Aliases []string
|
||||||
Aliases []string
|
Tmpl bundle.Template
|
||||||
Tmpl bundle.Template
|
Markup string
|
||||||
Markup string
|
renderable bool
|
||||||
renderable bool
|
layout string
|
||||||
layout string
|
linkTitle string
|
||||||
linkTitle string
|
|
||||||
PageMeta
|
PageMeta
|
||||||
File
|
File
|
||||||
Position
|
Position
|
||||||
|
@ -75,107 +73,39 @@ type Position struct {
|
||||||
|
|
||||||
type Pages []*Page
|
type Pages []*Page
|
||||||
|
|
||||||
/*
|
func (p *Page) Plain() string {
|
||||||
* Implementation of a custom sorter for Pages
|
|
||||||
*/
|
|
||||||
|
|
||||||
// A type to implement the sort interface for Pages
|
|
||||||
type PageSorter struct {
|
|
||||||
pages Pages
|
|
||||||
by PageBy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closure used in the Sort.Less method.
|
|
||||||
type PageBy func(p1, p2 *Page) bool
|
|
||||||
|
|
||||||
func (by PageBy) Sort(pages Pages) {
|
|
||||||
ps := &PageSorter{
|
|
||||||
pages: pages,
|
|
||||||
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
|
|
||||||
}
|
|
||||||
sort.Sort(ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultPageSort = func(p1, p2 *Page) bool {
|
|
||||||
if p1.Weight == p2.Weight {
|
|
||||||
return p1.Date.Unix() > p2.Date.Unix()
|
|
||||||
} else {
|
|
||||||
return p1.Weight < p2.Weight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PageSorter) Len() int { return len(ps.pages) }
|
|
||||||
func (ps *PageSorter) Swap(i, j int) { ps.pages[i], ps.pages[j] = ps.pages[j], ps.pages[i] }
|
|
||||||
|
|
||||||
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
|
|
||||||
func (ps *PageSorter) Less(i, j int) bool { return ps.by(ps.pages[i], ps.pages[j]) }
|
|
||||||
|
|
||||||
func (p Pages) Sort() {
|
|
||||||
PageBy(DefaultPageSort).Sort(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pages) Limit(n int) Pages {
|
|
||||||
if len(p) < n {
|
|
||||||
return p[0:n]
|
|
||||||
} else {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pages) ByWeight() Pages {
|
|
||||||
PageBy(DefaultPageSort).Sort(p)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pages) ByDate() Pages {
|
|
||||||
date := func(p1, p2 *Page) bool {
|
|
||||||
return p1.Date.Unix() < p2.Date.Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
PageBy(date).Sort(p)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pages) ByLength() Pages {
|
|
||||||
length := func(p1, p2 *Page) bool {
|
|
||||||
return len(p1.Content) < len(p2.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
PageBy(length).Sort(p)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pages) Reverse() Pages {
|
|
||||||
for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
p[i], p[j] = p[j], p[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Page) Plain() string {
|
|
||||||
if len(p.plain) == 0 {
|
if len(p.plain) == 0 {
|
||||||
p.plain = StripHTML(StripShortcodes(string(p.Content)))
|
p.plain = StripHTML(StripShortcodes(string(p.rawContent)))
|
||||||
}
|
}
|
||||||
return p.plain
|
return p.plain
|
||||||
}
|
}
|
||||||
|
|
||||||
// nb: this is only called for recognised types; so while .html might work for
|
func (p *Page) setSummary() {
|
||||||
// creating posts, it results in missing summaries.
|
if bytes.Contains(p.rawContent, summaryDivider) {
|
||||||
func getSummaryString(content []byte, pagefmt string) (summary []byte, truncates bool) {
|
|
||||||
if bytes.Contains(content, summaryDivider) {
|
|
||||||
// If user defines split:
|
// If user defines split:
|
||||||
// Split then render
|
// Split then render
|
||||||
truncates = true // by definition
|
p.Truncated = true // by definition
|
||||||
summary = renderBytes(bytes.Split(content, summaryDivider)[0], pagefmt)
|
header := string(bytes.Split(p.rawContent, summaryDivider)[0])
|
||||||
|
p.Summary = bytesToHTML(p.renderBytes([]byte(ShortcodesHandle(header, p, p.Tmpl))))
|
||||||
} else {
|
} else {
|
||||||
// If hugo defines split:
|
// If hugo defines split:
|
||||||
// render, strip html, then split
|
// render, strip html, then split
|
||||||
plain := strings.TrimSpace(StripHTML(StripShortcodes(string(renderBytes(content, pagefmt)))))
|
plain := strings.TrimSpace(p.Plain())
|
||||||
summary = []byte(TruncateWordsToWholeSentence(plain, summaryLength))
|
p.Summary = bytesToHTML([]byte(TruncateWordsToWholeSentence(plain, summaryLength)))
|
||||||
truncates = len(summary) != len(plain)
|
p.Truncated = len(p.Summary) != len(plain)
|
||||||
}
|
}
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func bytesToHTML(b []byte) template.HTML {
|
||||||
|
return template.HTML(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) renderBytes(content []byte) []byte {
|
||||||
|
return renderBytes(content, p.guessMarkupType())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) renderString(content string) []byte {
|
||||||
|
return renderBytes([]byte(content), p.guessMarkupType())
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderBytes(content []byte, pagefmt string) []byte {
|
func renderBytes(content []byte, pagefmt string) []byte {
|
||||||
|
@ -293,22 +223,20 @@ func ReadFrom(buf io.Reader, name string) (page *Page, err error) {
|
||||||
return nil, errors.New("Zero length page name")
|
return nil, errors.New("Zero length page name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create new page
|
||||||
p := newPage(name)
|
p := newPage(name)
|
||||||
|
|
||||||
|
// Parse for metadata & body
|
||||||
if err = p.parse(buf); err != nil {
|
if err = p.parse(buf); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//analyze for raw stats
|
||||||
p.analyzePage()
|
p.analyzePage()
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) ProcessShortcodes(t bundle.Template) {
|
|
||||||
p.Content = template.HTML(ShortcodesHandle(string(p.Content), p, t))
|
|
||||||
p.Summary = template.HTML(ShortcodesHandle(string(p.Summary), p, t))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Page) analyzePage() {
|
func (p *Page) analyzePage() {
|
||||||
p.WordCount = TotalWords(p.Plain())
|
p.WordCount = TotalWords(p.Plain())
|
||||||
p.FuzzyWordCount = int((p.WordCount+100)/100) * 100
|
p.FuzzyWordCount = int((p.WordCount+100)/100) * 100
|
||||||
|
@ -543,7 +471,7 @@ func (p *Page) Render(layout ...string) template.HTML {
|
||||||
curLayout = layout[0]
|
curLayout = layout[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return template.HTML(string(p.ExecuteTemplate(curLayout).Bytes()))
|
return bytesToHTML(p.ExecuteTemplate(curLayout).Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer {
|
func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer {
|
||||||
|
@ -577,7 +505,7 @@ func (page *Page) guessMarkupType() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func guessType(in string) string {
|
func guessType(in string) string {
|
||||||
switch in {
|
switch strings.ToLower(in) {
|
||||||
case "md", "markdown", "mdown":
|
case "md", "markdown", "mdown":
|
||||||
return "markdown"
|
return "markdown"
|
||||||
case "rst":
|
case "rst":
|
||||||
|
@ -610,50 +538,49 @@ func (page *Page) parse(reader io.Reader) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
page.Content = template.HTML(p.Content())
|
page.rawContent = p.Content()
|
||||||
|
page.setSummary()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Page) ProcessShortcodes(t bundle.Template) {
|
||||||
|
p.rawContent = []byte(ShortcodesHandle(string(p.rawContent), p, t))
|
||||||
|
p.Summary = template.HTML(ShortcodesHandle(string(p.Summary), p, t))
|
||||||
|
}
|
||||||
|
|
||||||
func (page *Page) Convert() error {
|
func (page *Page) Convert() error {
|
||||||
switch page.guessMarkupType() {
|
markupType := page.guessMarkupType()
|
||||||
case "markdown":
|
switch markupType {
|
||||||
page.convertMarkdown(bytes.NewReader([]byte(page.Content)))
|
case "markdown", "rst":
|
||||||
case "rst":
|
page.Content = bytesToHTML(page.renderString(string(RemoveSummaryDivider(page.rawContent))))
|
||||||
page.convertRestructuredText(bytes.NewReader([]byte(page.Content)))
|
case "html":
|
||||||
|
page.Content = bytesToHTML(page.rawContent)
|
||||||
|
default:
|
||||||
|
return errors.New("Error converting unsupported file type " + markupType)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTableOfContents(content []byte) template.HTML {
|
// Lazily generate the TOC
|
||||||
|
func (page *Page) TableOfContents() template.HTML {
|
||||||
|
return tableOfContentsFromBytes([]byte(page.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableOfContentsFromBytes(content []byte) template.HTML {
|
||||||
htmlFlags := 0
|
htmlFlags := 0
|
||||||
htmlFlags |= blackfriday.HTML_SKIP_SCRIPT
|
htmlFlags |= blackfriday.HTML_SKIP_SCRIPT
|
||||||
htmlFlags |= blackfriday.HTML_TOC
|
htmlFlags |= blackfriday.HTML_TOC
|
||||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||||
renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
|
renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
return template.HTML(string(blackfriday.Markdown(content, renderer, 0)))
|
return template.HTML(string(blackfriday.Markdown(RemoveSummaryDivider(content), renderer, 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (page *Page) convertMarkdown(lines io.Reader) {
|
func ReaderToBytes(lines io.Reader) []byte {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
b.ReadFrom(lines)
|
b.ReadFrom(lines)
|
||||||
content := b.Bytes()
|
return b.Bytes()
|
||||||
page.Content = template.HTML(string(blackfriday.MarkdownCommon(RemoveSummaryDivider(content))))
|
|
||||||
summary, truncated := getSummaryString(content, "markdown")
|
|
||||||
page.Summary = template.HTML(string(summary))
|
|
||||||
page.TableOfContents = getTableOfContents(RemoveSummaryDivider(content))
|
|
||||||
page.Truncated = truncated
|
|
||||||
}
|
|
||||||
|
|
||||||
func (page *Page) convertRestructuredText(lines io.Reader) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
b.ReadFrom(lines)
|
|
||||||
content := b.Bytes()
|
|
||||||
page.Content = template.HTML(getRstContent(content))
|
|
||||||
summary, truncated := getSummaryString(content, "rst")
|
|
||||||
page.Summary = template.HTML(string(summary))
|
|
||||||
page.Truncated = truncated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) TargetPath() (outfile string) {
|
func (p *Page) TargetPath() (outfile string) {
|
||||||
|
|
96
hugolib/pageSort.go
Normal file
96
hugolib/pageSort.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of a custom sorter for Pages
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A type to implement the sort interface for Pages
|
||||||
|
type PageSorter struct {
|
||||||
|
pages Pages
|
||||||
|
by PageBy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closure used in the Sort.Less method.
|
||||||
|
type PageBy func(p1, p2 *Page) bool
|
||||||
|
|
||||||
|
func (by PageBy) Sort(pages Pages) {
|
||||||
|
ps := &PageSorter{
|
||||||
|
pages: pages,
|
||||||
|
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
|
||||||
|
}
|
||||||
|
sort.Sort(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultPageSort = func(p1, p2 *Page) bool {
|
||||||
|
if p1.Weight == p2.Weight {
|
||||||
|
return p1.Date.Unix() > p2.Date.Unix()
|
||||||
|
} else {
|
||||||
|
return p1.Weight < p2.Weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *PageSorter) Len() int { return len(ps.pages) }
|
||||||
|
func (ps *PageSorter) Swap(i, j int) { ps.pages[i], ps.pages[j] = ps.pages[j], ps.pages[i] }
|
||||||
|
|
||||||
|
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
|
||||||
|
func (ps *PageSorter) Less(i, j int) bool { return ps.by(ps.pages[i], ps.pages[j]) }
|
||||||
|
|
||||||
|
func (p Pages) Sort() {
|
||||||
|
PageBy(DefaultPageSort).Sort(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) Limit(n int) Pages {
|
||||||
|
if len(p) < n {
|
||||||
|
return p[0:n]
|
||||||
|
} else {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) ByWeight() Pages {
|
||||||
|
PageBy(DefaultPageSort).Sort(p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) ByDate() Pages {
|
||||||
|
date := func(p1, p2 *Page) bool {
|
||||||
|
return p1.Date.Unix() < p2.Date.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
PageBy(date).Sort(p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) ByLength() Pages {
|
||||||
|
length := func(p1, p2 *Page) bool {
|
||||||
|
return len(p1.Content) < len(p2.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
PageBy(length).Sort(p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) Reverse() Pages {
|
||||||
|
for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
p[i], p[j] = p[j], p[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
|
@ -296,6 +296,7 @@ func TestPageWithDate(t *testing.T) {
|
||||||
func TestWordCount(t *testing.T) {
|
func TestWordCount(t *testing.T) {
|
||||||
p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_WITH_LONG_CONTENT), "simple.md")
|
p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_WITH_LONG_CONTENT), "simple.md")
|
||||||
p.Convert()
|
p.Convert()
|
||||||
|
p.analyzePage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
|
t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package hugolib
|
package hugolib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTableOfContents(t *testing.T) {
|
func TestTableOfContents(t *testing.T) {
|
||||||
text := `
|
text := `
|
||||||
Blah blah blah blah blah.
|
Blah blah blah blah blah.
|
||||||
|
|
||||||
## AA
|
## AA
|
||||||
|
@ -25,10 +25,10 @@ Blah blah blah blah blah.
|
||||||
Blah blah blah blah blah.
|
Blah blah blah blah blah.
|
||||||
`
|
`
|
||||||
|
|
||||||
markdown := RemoveSummaryDivider([]byte(text))
|
markdown := RemoveSummaryDivider([]byte(text))
|
||||||
toc := string(getTableOfContents(markdown))
|
toc := string(tableOfContentsFromBytes(markdown))
|
||||||
|
|
||||||
expected := `<nav>
|
expected := `<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -45,7 +45,7 @@ Blah blah blah blah blah.
|
||||||
</nav>
|
</nav>
|
||||||
`
|
`
|
||||||
|
|
||||||
if toc != expected {
|
if toc != expected {
|
||||||
t.Errorf("Expected table of contents: %s, got: %s", expected, toc)
|
t.Errorf("Expected table of contents: %s, got: %s", expected, toc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
940
hugolib/site.go
940
hugolib/site.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue