Big refactor of pages code. Changed TOC code to only parse when actually used

This commit is contained in:
spf13 2014-01-27 17:16:28 -05:00
parent f62e3e9940
commit d0825a211a
5 changed files with 642 additions and 618 deletions

View file

@ -28,29 +28,27 @@ import (
json "launchpad.net/rjson"
"net/url"
"path"
"sort"
"strings"
"time"
)
type Page struct {
Status string
Images []string
RawContent []byte
Content template.HTML
Summary template.HTML
TableOfContents template.HTML
Truncated bool
plain string // TODO should be []byte
Params map[string]interface{}
contentType string
Draft bool
Aliases []string
Tmpl bundle.Template
Markup string
renderable bool
layout string
linkTitle string
Status string
Images []string
rawContent []byte
Content template.HTML
Summary template.HTML
Truncated bool
plain string // TODO should be []byte
Params map[string]interface{}
contentType string
Draft bool
Aliases []string
Tmpl bundle.Template
Markup string
renderable bool
layout string
linkTitle string
PageMeta
File
Position
@ -75,107 +73,39 @@ type Position struct {
type Pages []*Page
/*
* 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 {
func (p *Page) Plain() string {
if len(p.plain) == 0 {
p.plain = StripHTML(StripShortcodes(string(p.Content)))
p.plain = StripHTML(StripShortcodes(string(p.rawContent)))
}
return p.plain
}
// nb: this is only called for recognised types; so while .html might work for
// creating posts, it results in missing summaries.
func getSummaryString(content []byte, pagefmt string) (summary []byte, truncates bool) {
if bytes.Contains(content, summaryDivider) {
func (p *Page) setSummary() {
if bytes.Contains(p.rawContent, summaryDivider) {
// If user defines split:
// Split then render
truncates = true // by definition
summary = renderBytes(bytes.Split(content, summaryDivider)[0], pagefmt)
p.Truncated = true // by definition
header := string(bytes.Split(p.rawContent, summaryDivider)[0])
p.Summary = bytesToHTML(p.renderBytes([]byte(ShortcodesHandle(header, p, p.Tmpl))))
} else {
// If hugo defines split:
// render, strip html, then split
plain := strings.TrimSpace(StripHTML(StripShortcodes(string(renderBytes(content, pagefmt)))))
summary = []byte(TruncateWordsToWholeSentence(plain, summaryLength))
truncates = len(summary) != len(plain)
plain := strings.TrimSpace(p.Plain())
p.Summary = bytesToHTML([]byte(TruncateWordsToWholeSentence(plain, summaryLength)))
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 {
@ -293,22 +223,20 @@ func ReadFrom(buf io.Reader, name string) (page *Page, err error) {
return nil, errors.New("Zero length page name")
}
// Create new page
p := newPage(name)
// Parse for metadata & body
if err = p.parse(buf); err != nil {
return
}
//analyze for raw stats
p.analyzePage()
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() {
p.WordCount = TotalWords(p.Plain())
p.FuzzyWordCount = int((p.WordCount+100)/100) * 100
@ -543,7 +471,7 @@ func (p *Page) Render(layout ...string) template.HTML {
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 {
@ -577,7 +505,7 @@ func (page *Page) guessMarkupType() string {
}
func guessType(in string) string {
switch in {
switch strings.ToLower(in) {
case "md", "markdown", "mdown":
return "markdown"
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
}
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 {
switch page.guessMarkupType() {
case "markdown":
page.convertMarkdown(bytes.NewReader([]byte(page.Content)))
case "rst":
page.convertRestructuredText(bytes.NewReader([]byte(page.Content)))
markupType := page.guessMarkupType()
switch markupType {
case "markdown", "rst":
page.Content = bytesToHTML(page.renderString(string(RemoveSummaryDivider(page.rawContent))))
case "html":
page.Content = bytesToHTML(page.rawContent)
default:
return errors.New("Error converting unsupported file type " + markupType)
}
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 |= blackfriday.HTML_SKIP_SCRIPT
htmlFlags |= blackfriday.HTML_TOC
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
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.ReadFrom(lines)
content := 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
return b.Bytes()
}
func (p *Page) TargetPath() (outfile string) {

96
hugolib/pageSort.go Normal file
View 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
}

View file

@ -296,6 +296,7 @@ func TestPageWithDate(t *testing.T) {
func TestWordCount(t *testing.T) {
p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_WITH_LONG_CONTENT), "simple.md")
p.Convert()
p.analyzePage()
if err != nil {
t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
}

View file

@ -1,11 +1,11 @@
package hugolib
import (
"testing"
"testing"
)
func TestTableOfContents(t *testing.T) {
text := `
text := `
Blah blah blah blah blah.
## AA
@ -25,10 +25,10 @@ Blah blah blah blah blah.
Blah blah blah blah blah.
`
markdown := RemoveSummaryDivider([]byte(text))
toc := string(getTableOfContents(markdown))
markdown := RemoveSummaryDivider([]byte(text))
toc := string(tableOfContentsFromBytes(markdown))
expected := `<nav>
expected := `<nav>
<ul>
<li>
<ul>
@ -45,7 +45,7 @@ Blah blah blah blah blah.
</nav>
`
if toc != expected {
t.Errorf("Expected table of contents: %s, got: %s", expected, toc)
}
if toc != expected {
t.Errorf("Expected table of contents: %s, got: %s", expected, toc)
}
}

File diff suppressed because it is too large Load diff