shithub: hugo

Download patch

ref: 79d9f82e79014fffabaedd34a3997475967508f6
parent: 207d8fb7af6d1b7bcb9753a290fd60e90f8e5e0c
author: Noah Campbell <[email protected]>
date: Tue Sep 3 11:38:20 EDT 2013

Code reorg, helpers.go has been decomposed.

It started with wanting to move templates in template bundles and the
rest followed.  I did my best to start grouping related functions
together, but there are some that I missed.  There is also the method
Urlize that seems to be a special function used in both worlds.  I'll
need to revisit this method.

--- a/hugolib/helpers.go
+++ /dev/null
@@ -1,367 +1,0 @@
-// Copyright © 2013 Steve Francia <[email protected]>.
-//
-// 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 (
-	"bytes"
-	"html/template"
-	"errors"
-	"fmt"
-	"github.com/kr/pretty"
-	"os"
-	"os/exec"
-	"reflect"
-	"regexp"
-	"strconv"
-	"strings"
-	"time"
-)
-
-var sanitizeRegexp = regexp.MustCompile("[^a-zA-Z0-9./_-]")
-var summaryLength = 70
-var summaryDivider = []byte("<!--more-->")
-
-// TODO: Make these wrappers private
-// Wrapper around Fprintf taking verbose flag in account.
-func Printvf(format string, a ...interface{}) {
-	//if *verbose {
-	fmt.Fprintf(os.Stderr, format, a...)
-	//}
-}
-
-func Printer(x interface{}) {
-	fmt.Printf("%#v", pretty.Formatter(x))
-	fmt.Println("")
-}
-
-// Wrapper around Fprintln taking verbose flag in account.
-func Printvln(a ...interface{}) {
-	//if *verbose {
-	fmt.Fprintln(os.Stderr, a...)
-	//}
-}
-
-func FatalErr(str string) {
-	fmt.Println(str)
-	os.Exit(1)
-}
-
-func PrintErr(str string, a ...interface{}) {
-	fmt.Fprintln(os.Stderr, str, a)
-}
-
-func Error(str string, a ...interface{}) {
-	fmt.Fprintln(os.Stderr, str, a)
-}
-
-func interfaceToStringToDate(i interface{}) time.Time {
-	s := interfaceToString(i)
-
-	if d, e := parseDateWith(s, []string{
-		time.RFC3339,
-		time.RFC1123Z,
-		time.RFC1123,
-		time.RFC822Z,
-		time.RFC822,
-		time.ANSIC,
-		time.UnixDate,
-		time.RubyDate,
-		"2006-01-02 15:04:05Z07:00",
-		"02 Jan 06 15:04 MST",
-		"2006-01-02",
-		"02 Jan 2006",
-	}); e == nil {
-		return d
-	}
-
-	return time.Unix(0, 0)
-}
-
-func parseDateWith(s string, dates []string) (d time.Time, e error) {
-	for _, dateType := range dates {
-		if d, e = time.Parse(dateType, s); e == nil {
-			return
-		}
-	}
-	return d, errors.New(fmt.Sprintf("Unable to parse date: %s", s))
-}
-
-func interfaceToBool(i interface{}) bool {
-	switch b := i.(type) {
-	case bool:
-		return b
-	default:
-		Error("Only Boolean values are supported for this YAML key")
-	}
-
-	return false
-
-}
-
-func interfaceArrayToStringArray(i interface{}) []string {
-	var a []string
-
-	switch vv := i.(type) {
-	case []interface{}:
-		for _, u := range vv {
-			a = append(a, interfaceToString(u))
-		}
-	}
-
-	return a
-}
-
-func interfaceToString(i interface{}) string {
-	switch s := i.(type) {
-	case string:
-		return s
-	default:
-		Error("Only Strings are supported for this YAML key")
-	}
-
-	return ""
-}
-
-// Check if Exists && is Directory
-func dirExists(path string) (bool, error) {
-	fi, err := os.Stat(path)
-	if err == nil && fi.IsDir() {
-		return true, nil
-	}
-	if os.IsNotExist(err) {
-		return false, nil
-	}
-	return false, err
-}
-
-// Check if File / Directory Exists
-func exists(path string) (bool, error) {
-	_, err := os.Stat(path)
-	if err == nil {
-		return true, nil
-	}
-	if os.IsNotExist(err) {
-		return false, nil
-	}
-	return false, err
-}
-
-func mkdirIf(path string) error {
-	return os.MkdirAll(path, 0777)
-}
-
-func Urlize(url string) string {
-	return Sanitize(strings.ToLower(strings.Replace(strings.TrimSpace(url), " ", "-", -1)))
-}
-
-func AbsUrl(url string, base string) template.HTML {
-	if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
-		return template.HTML(url)
-	}
-	return template.HTML(MakePermalink(base, url))
-}
-
-func Gt(a interface{}, b interface{}) bool {
-	var left, right int64
-	av := reflect.ValueOf(a)
-
-	switch av.Kind() {
-	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
-		left = int64(av.Len())
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		left = av.Int()
-	case reflect.String:
-		left, _ = strconv.ParseInt(av.String(), 10, 64)
-	}
-
-	bv := reflect.ValueOf(b)
-
-	switch bv.Kind() {
-	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
-		right = int64(bv.Len())
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		right = bv.Int()
-	case reflect.String:
-		right, _ = strconv.ParseInt(bv.String(), 10, 64)
-	}
-
-	return left > right
-}
-
-func IsSet(a interface{}, key interface{}) bool {
-	av := reflect.ValueOf(a)
-	kv := reflect.ValueOf(key)
-
-	switch av.Kind() {
-	case reflect.Array, reflect.Chan, reflect.Slice:
-		if int64(av.Len()) > kv.Int() {
-			return true
-		}
-	case reflect.Map:
-		if kv.Type() == av.Type().Key() {
-			return av.MapIndex(kv).IsValid()
-		}
-	}
-
-	return false
-}
-
-func ReturnWhenSet(a interface{}, index int) interface{} {
-	av := reflect.ValueOf(a)
-
-	switch av.Kind() {
-	case reflect.Array, reflect.Slice:
-		if av.Len() > index {
-
-			avv := av.Index(index)
-			switch avv.Kind() {
-			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-				return avv.Int()
-			case reflect.String:
-				return avv.String()
-			}
-		}
-	}
-
-	return ""
-}
-
-func Sanitize(s string) string {
-	return sanitizeRegexp.ReplaceAllString(s, "")
-}
-
-func fileExt(path string) (file, ext string) {
-	if strings.Contains(path, ".") {
-		i := len(path) - 1
-		for path[i] != '.' {
-			i--
-		}
-		return path[:i], path[i+1:]
-	}
-	return path, ""
-}
-
-func replaceExtension(path string, newExt string) string {
-	f, _ := fileExt(path)
-	return f + "." + newExt
-}
-
-func TotalWords(s string) int {
-	return len(strings.Fields(s))
-}
-
-func WordCount(s string) map[string]int {
-	m := make(map[string]int)
-	for _, f := range strings.Fields(s) {
-		m[f] += 1
-	}
-
-	return m
-}
-
-func RemoveSummaryDivider(content []byte) []byte {
-	return bytes.Replace(content, summaryDivider, []byte(""), -1)
-}
-
-func StripHTML(s string) string {
-	output := ""
-
-	// Shortcut strings with no tags in them
-	if !strings.ContainsAny(s, "<>") {
-		output = s
-	} else {
-		s = strings.Replace(s, "\n", " ", -1)
-		s = strings.Replace(s, "</p>", " \n", -1)
-		s = strings.Replace(s, "<br>", " \n", -1)
-		s = strings.Replace(s, "</br>", " \n", -1)
-
-		// Walk through the string removing all tags
-		b := new(bytes.Buffer)
-		inTag := false
-		for _, r := range s {
-			switch r {
-			case '<':
-				inTag = true
-			case '>':
-				inTag = false
-			default:
-				if !inTag {
-					b.WriteRune(r)
-				}
-			}
-		}
-		output = b.String()
-	}
-	return output
-}
-
-func TruncateWords(s string, max int) string {
-	words := strings.Fields(s)
-	if max > len(words) {
-		return strings.Join(words, " ")
-	}
-
-	return strings.Join(words[:max], " ")
-}
-
-func TruncateWordsToWholeSentence(s string, max int) string {
-	words := strings.Fields(s)
-	if max > len(words) {
-		return strings.Join(words, " ")
-	}
-
-	for counter, word := range words[max:] {
-		if strings.HasSuffix(word, ".") ||
-			strings.HasSuffix(word, "?") ||
-			strings.HasSuffix(word, ".\"") ||
-			strings.HasSuffix(word, "!") {
-			return strings.Join(words[:max+counter+1], " ")
-		}
-	}
-
-	return strings.Join(words[:max], " ")
-}
-
-func MakePermalink(domain string, path string) string {
-	return strings.TrimRight(domain, "/") + "/" + strings.TrimLeft(path, "/")
-}
-
-func getSummaryString(content []byte) ([]byte, bool) {
-	if bytes.Contains(content, summaryDivider) {
-		return bytes.Split(content, summaryDivider)[0], false
-	} else {
-		plainContent := StripHTML(StripShortcodes(string(content)))
-		return []byte(TruncateWordsToWholeSentence(plainContent, summaryLength)), true
-	}
-}
-
-func getRstContent(content []byte) string {
-	cleanContent := bytes.Replace(content, summaryDivider, []byte(""), 1)
-
-	cmd := exec.Command("rst2html.py", "--leave-comments")
-	cmd.Stdin = bytes.NewReader(cleanContent)
-	var out bytes.Buffer
-	cmd.Stdout = &out
-	if err := cmd.Run(); err != nil {
-		fmt.Println(err)
-	}
-
-	rstLines := strings.Split(out.String(), "\n")
-	for i, line := range rstLines {
-		if strings.HasPrefix(line, "<body>") {
-			rstLines = (rstLines[i+1 : len(rstLines)-3])
-		}
-	}
-	return strings.Join(rstLines, "\n")
-}
--- a/hugolib/index.go
+++ b/hugolib/index.go
@@ -14,6 +14,7 @@
 package hugolib
 
 import (
+	"github.com/spf13/hugo/template"
 	"sort"
 )
 
@@ -30,7 +31,7 @@
 
 // KeyPrep... Indexes should be case insensitive. Can make it easily conditional later.
 func kp(in string) string {
-	return Urlize(in)
+	return template.Urlize(in)
 }
 
 func (i Index) Get(key string) Pages { return i[kp(key)] }
--- /dev/null
+++ b/hugolib/metadata.go
@@ -1,0 +1,81 @@
+package hugolib
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"time"
+)
+
+func interfaceToStringToDate(i interface{}) time.Time {
+	s := interfaceToString(i)
+
+	if d, e := parseDateWith(s, []string{
+		time.RFC3339,
+		time.RFC1123Z,
+		time.RFC1123,
+		time.RFC822Z,
+		time.RFC822,
+		time.ANSIC,
+		time.UnixDate,
+		time.RubyDate,
+		"2006-01-02 15:04:05Z07:00",
+		"02 Jan 06 15:04 MST",
+		"2006-01-02",
+		"02 Jan 2006",
+	}); e == nil {
+		return d
+	}
+
+	return time.Unix(0, 0)
+}
+
+// TODO remove this and return a proper error.
+func errorf(str string, a ...interface{}) {
+	fmt.Fprintln(os.Stderr, str, a)
+}
+
+func parseDateWith(s string, dates []string) (d time.Time, e error) {
+	for _, dateType := range dates {
+		if d, e = time.Parse(dateType, s); e == nil {
+			return
+		}
+	}
+	return d, errors.New(fmt.Sprintf("Unable to parse date: %s", s))
+}
+
+func interfaceToBool(i interface{}) bool {
+	switch b := i.(type) {
+	case bool:
+		return b
+	default:
+		errorf("Only Boolean values are supported for this YAML key")
+	}
+
+	return false
+
+}
+
+func interfaceArrayToStringArray(i interface{}) []string {
+	var a []string
+
+	switch vv := i.(type) {
+	case []interface{}:
+		for _, u := range vv {
+			a = append(a, interfaceToString(u))
+		}
+	}
+
+	return a
+}
+
+func interfaceToString(i interface{}) string {
+	switch s := i.(type) {
+	case string:
+		return s
+	default:
+		errorf("Only Strings are supported for this YAML key")
+	}
+
+	return ""
+}
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -14,8 +14,8 @@
 package hugolib
 
 import (
-	"time"
 	"html/template"
+	"time"
 )
 
 type Node struct {
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -20,9 +20,11 @@
 	"errors"
 	"fmt"
 	"github.com/BurntSushi/toml"
+	helper "github.com/spf13/hugo/template"
+	"github.com/spf13/hugo/template/bundle"
 	"github.com/theplant/blackfriday"
-	"io"
 	"html/template"
+	"io"
 	"io/ioutil"
 	"launchpad.net/goyaml"
 	"os"
@@ -46,7 +48,7 @@
 	contentType     string
 	Draft           bool
 	Aliases         []string
-	Tmpl            Template
+	Tmpl            bundle.Template
 	Markup          string
 	PageMeta
 	File
@@ -78,6 +80,15 @@
 func (p Pages) Sort()             { sort.Sort(p) }
 func (p Pages) Limit(n int) Pages { return p[0:n] }
 
+func getSummaryString(content []byte) ([]byte, bool) {
+	if bytes.Contains(content, summaryDivider) {
+		return bytes.Split(content, summaryDivider)[0], false
+	} else {
+		plainContent := StripHTML(StripShortcodes(string(content)))
+		return []byte(TruncateWordsToWholeSentence(plainContent, summaryLength)), true
+	}
+}
+
 // TODO abstract further to support loading from more
 // than just files on disk. Should load reader (file, []byte)
 func NewPage(filename string) *Page {
@@ -91,6 +102,38 @@
 	return &page
 }
 
+func StripHTML(s string) string {
+	output := ""
+
+	// Shortcut strings with no tags in them
+	if !strings.ContainsAny(s, "<>") {
+		output = s
+	} else {
+		s = strings.Replace(s, "\n", " ", -1)
+		s = strings.Replace(s, "</p>", " \n", -1)
+		s = strings.Replace(s, "<br>", " \n", -1)
+		s = strings.Replace(s, "</br>", " \n", -1)
+
+		// Walk through the string removing all tags
+		b := new(bytes.Buffer)
+		inTag := false
+		for _, r := range s {
+			switch r {
+			case '<':
+				inTag = true
+			case '>':
+				inTag = false
+			default:
+				if !inTag {
+					b.WriteRune(r)
+				}
+			}
+		}
+		output = b.String()
+	}
+	return output
+}
+
 func (page *Page) Initalize() error {
 	err := page.buildPageFromFile()
 	if err != nil {
@@ -246,12 +289,12 @@
 		case "description":
 			page.Description = interfaceToString(v)
 		case "slug":
-			page.Slug = Urlize(interfaceToString(v))
+			page.Slug = helper.Urlize(interfaceToString(v))
 		case "url":
 			if url := interfaceToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
 				return fmt.Errorf("Only relative urls are supported, %v provided", url)
 			}
-			page.Url = Urlize(interfaceToString(v))
+			page.Url = helper.Urlize(interfaceToString(v))
 		case "type":
 			page.contentType = interfaceToString(v)
 		case "keywords":
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1,11 +1,11 @@
 package hugolib
 
 import (
+	"html/template"
 	"path/filepath"
-	"time"
 	"strings"
 	"testing"
-	"html/template"
+	"time"
 )
 
 var EMPTY_PAGE = ""
--- /dev/null
+++ b/hugolib/path.go
@@ -1,0 +1,34 @@
+package hugolib
+
+import (
+	"os"
+	"strings"
+)
+
+func fileExt(path string) (file, ext string) {
+	if strings.Contains(path, ".") {
+		i := len(path) - 1
+		for path[i] != '.' {
+			i--
+		}
+		return path[:i], path[i+1:]
+	}
+	return path, ""
+}
+
+func replaceExtension(path string, newExt string) string {
+	f, _ := fileExt(path)
+	return f + "." + newExt
+}
+
+// Check if Exists && is Directory
+func dirExists(path string) (bool, error) {
+	fi, err := os.Stat(path)
+	if err == nil && fi.IsDir() {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
--- a/hugolib/path_seperators_windows_test.go
+++ /dev/null
@@ -1,17 +1,0 @@
-package hugolib
-
-import (
-	"testing"
-)
-
-const (
-	win_base = "c:\\a\\windows\\path\\layout"
-	win_path = "c:\\a\\windows\\path\\layout\\sub1\\index.html"
-)
-
-func TestTemplatePathSeperator(t *testing.T) {
-	tmpl := new(GoHtmlTemplate)
-	if name := tmpl.generateTemplateNameFrom(win_base, win_path); name != "sub1/index.html" {
-		t.Fatalf("Template name incorrect.  Expected: %s, Got: %s", "sub1/index.html", name)
-	}
-}
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -16,6 +16,7 @@
 import (
 	"bytes"
 	"fmt"
+	"github.com/spf13/hugo/template/bundle"
 	"strings"
 	"unicode"
 )
@@ -36,7 +37,7 @@
 
 type Shortcodes map[string]ShortcodeFunc
 
-func ShortcodesHandle(stringToParse string, p *Page, t Template) string {
+func ShortcodesHandle(stringToParse string, p *Page, t bundle.Template) string {
 	posStart := strings.Index(stringToParse, "{{%")
 	if posStart > 0 {
 		posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
@@ -123,7 +124,7 @@
 	return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:])
 }
 
-func ShortcodeRender(name string, data *ShortcodeWithPage, t Template) string {
+func ShortcodeRender(name string, data *ShortcodeWithPage, t bundle.Template) string {
 	buffer := new(bytes.Buffer)
 	t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data)
 	return buffer.String()
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -15,11 +15,13 @@
 
 import (
 	"bitbucket.org/pkg/inflect"
-	"html/template"
 	"bytes"
 	"fmt"
 	"github.com/spf13/hugo/target"
+	helpers "github.com/spf13/hugo/template"
+	"github.com/spf13/hugo/template/bundle"
 	"github.com/spf13/nitro"
+	"html/template"
 	"os"
 	"path/filepath"
 	"strings"
@@ -28,10 +30,27 @@
 
 var DefaultTimer = nitro.Initalize()
 
+func MakePermalink(domain string, path string) string {
+	return strings.TrimRight(domain, "/") + "/" + strings.TrimLeft(path, "/")
+}
+
+func mkdirIf(path string) error {
+	return os.MkdirAll(path, 0777)
+}
+
+func FatalErr(str string) {
+	fmt.Println(str)
+	os.Exit(1)
+}
+
+func PrintErr(str string, a ...interface{}) {
+	fmt.Fprintln(os.Stderr, str, a)
+}
+
 type Site struct {
 	Config     Config
 	Pages      Pages
-	Tmpl       Template
+	Tmpl       bundle.Template
 	Indexes    IndexList
 	Files      []string
 	Sections   Index
@@ -83,7 +102,7 @@
 }
 
 func (s *Site) prepTemplates() {
-	s.Tmpl = NewTemplate()
+	s.Tmpl = bundle.NewTemplate()
 	s.Tmpl.LoadTemplates(s.absLayoutDir())
 }
 
@@ -150,7 +169,6 @@
 
 	walker := func(path string, fi os.FileInfo, err error) error {
 		if err != nil {
-			PrintErr("Walker: ", err)
 			return nil
 		}
 
@@ -179,6 +197,18 @@
 	s.Shortcodes = make(map[string]ShortcodeFunc)
 }
 
+// Check if File / Directory Exists
+func exists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
 func ignoreDotFile(path string) bool {
 	return filepath.Base(path)[0] == '.'
 }
@@ -420,7 +450,7 @@
 		for k, o := range s.Indexes[plural] {
 			n := s.NewNode()
 			n.Title = strings.Title(k)
-			url := Urlize(plural + "/" + k)
+			url := helpers.Urlize(plural + "/" + k)
 			plink := url
 			if s.Config.UglyUrls {
 				n.Url = url + ".html"
@@ -455,9 +485,9 @@
 				// XML Feed
 				y := s.NewXMLBuffer()
 				if s.Config.UglyUrls {
-					n.Url = Urlize(plural + "/" + k + ".xml")
+					n.Url = helpers.Urlize(plural + "/" + k + ".xml")
 				} else {
-					n.Url = Urlize(plural + "/" + k + "/" + "index.xml")
+					n.Url = helpers.Urlize(plural + "/" + k + "/" + "index.xml")
 				}
 				n.Permalink = permalink(s, n.Url)
 				s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
@@ -477,7 +507,7 @@
 		for singular, plural := range s.Config.Indexes {
 			n := s.NewNode()
 			n.Title = strings.Title(plural)
-			url := Urlize(plural)
+			url := helpers.Urlize(plural)
 			n.Url = url + "/index.html"
 			n.Permalink = permalink(s, n.Url)
 			n.Data["Singular"] = singular
@@ -503,7 +533,7 @@
 	for section, data := range s.Sections {
 		n := s.NewNode()
 		n.Title = strings.Title(inflect.Pluralize(section))
-		n.Url = Urlize(section + "/" + "index.html")
+		n.Url = helpers.Urlize(section + "/" + "index.html")
 		n.Permalink = permalink(s, n.Url)
 		n.RSSlink = permalink(s, section+".xml")
 		n.Date = data[0].Date
@@ -522,9 +552,9 @@
 		if a := s.Tmpl.Lookup("rss.xml"); a != nil {
 			// XML Feed
 			if s.Config.UglyUrls {
-				n.Url = Urlize(section + ".xml")
+				n.Url = helpers.Urlize(section + ".xml")
 			} else {
-				n.Url = Urlize(section + "/" + "index.xml")
+				n.Url = helpers.Urlize(section + "/" + "index.xml")
 			}
 			n.Permalink = template.HTML(string(n.Site.BaseUrl) + n.Url)
 			y := s.NewXMLBuffer()
@@ -539,7 +569,7 @@
 func (s *Site) RenderHomePage() error {
 	n := s.NewNode()
 	n.Title = n.Site.Title
-	n.Url = Urlize(string(n.Site.BaseUrl))
+	n.Url = helpers.Urlize(string(n.Site.BaseUrl))
 	n.RSSlink = permalink(s, "index.xml")
 	n.Permalink = permalink(s, "")
 	if len(s.Pages) > 0 {
@@ -561,7 +591,7 @@
 
 	if a := s.Tmpl.Lookup("rss.xml"); a != nil {
 		// XML Feed
-		n.Url = Urlize("index.xml")
+		n.Url = helpers.Urlize("index.xml")
 		n.Title = "Recent Content"
 		n.Permalink = permalink(s, "index.xml")
 		y := s.NewXMLBuffer()
@@ -571,7 +601,7 @@
 	}
 
 	if a := s.Tmpl.Lookup("404.html"); a != nil {
-		n.Url = Urlize("404.html")
+		n.Url = helpers.Urlize("404.html")
 		n.Title = "404 Page not found"
 		n.Permalink = permalink(s, "404.html")
 		x, err := s.RenderThing(n, "404.html")
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -3,9 +3,9 @@
 import (
 	"bytes"
 	"fmt"
+	"html/template"
 	"strings"
 	"testing"
-	"html/template"
 )
 
 var TEMPLATE_TITLE = "{{ .Title }}"
--- /dev/null
+++ b/hugolib/summary.go
@@ -1,0 +1,75 @@
+package hugolib
+
+import (
+	"bytes"
+	"fmt"
+	"os/exec"
+	"strings"
+)
+
+var summaryLength = 70
+var summaryDivider = []byte("<!--more-->")
+
+func TotalWords(s string) int {
+	return len(strings.Fields(s))
+}
+
+func WordCount(s string) map[string]int {
+	m := make(map[string]int)
+	for _, f := range strings.Fields(s) {
+		m[f] += 1
+	}
+
+	return m
+}
+
+func RemoveSummaryDivider(content []byte) []byte {
+	return bytes.Replace(content, summaryDivider, []byte(""), -1)
+}
+
+func TruncateWords(s string, max int) string {
+	words := strings.Fields(s)
+	if max > len(words) {
+		return strings.Join(words, " ")
+	}
+
+	return strings.Join(words[:max], " ")
+}
+
+func TruncateWordsToWholeSentence(s string, max int) string {
+	words := strings.Fields(s)
+	if max > len(words) {
+		return strings.Join(words, " ")
+	}
+
+	for counter, word := range words[max:] {
+		if strings.HasSuffix(word, ".") ||
+			strings.HasSuffix(word, "?") ||
+			strings.HasSuffix(word, ".\"") ||
+			strings.HasSuffix(word, "!") {
+			return strings.Join(words[:max+counter+1], " ")
+		}
+	}
+
+	return strings.Join(words[:max], " ")
+}
+
+func getRstContent(content []byte) string {
+	cleanContent := bytes.Replace(content, summaryDivider, []byte(""), 1)
+
+	cmd := exec.Command("rst2html.py", "--leave-comments")
+	cmd.Stdin = bytes.NewReader(cleanContent)
+	var out bytes.Buffer
+	cmd.Stdout = &out
+	if err := cmd.Run(); err != nil {
+		fmt.Println(err)
+	}
+
+	rstLines := strings.Split(out.String(), "\n")
+	for i, line := range rstLines {
+		if strings.HasPrefix(line, "<body>") {
+			rstLines = (rstLines[i+1 : len(rstLines)-3])
+		}
+	}
+	return strings.Join(rstLines, "\n")
+}
--- a/hugolib/template.go
+++ /dev/null
@@ -1,123 +1,0 @@
-package hugolib
-
-import (
-	"io/ioutil"
-	"github.com/eknkc/amber"
-	"html/template"
-	"io"
-	"os"
-	"path/filepath"
-	"strings"
-)
-
-// HTML encapsulates a known safe HTML document fragment.
-// It should not be used for HTML from a third-party, or HTML with
-// unclosed tags or comments. The outputs of a sound HTML sanitizer
-// and a template escaped by this package are fine for use with HTML.
-
-type Template interface {
-	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
-	Lookup(name string) *template.Template
-	Templates() []*template.Template
-	New(name string) *template.Template
-	LoadTemplates(absPath string)
-	AddTemplate(name, tpl string) error
-}
-
-type templateErr struct {
-	name string
-	err  error
-}
-
-type GoHtmlTemplate struct {
-	template.Template
-	errors []*templateErr
-}
-
-func NewTemplate() Template {
-	var templates = &GoHtmlTemplate{
-		Template: *template.New(""),
-		errors:   make([]*templateErr, 0),
-	}
-
-	funcMap := template.FuncMap{
-		"urlize":    Urlize,
-		"gt":        Gt,
-		"isset":     IsSet,
-		"echoParam": ReturnWhenSet,
-	}
-
-	templates.Funcs(funcMap)
-	templates.primeTemplates()
-	return templates
-}
-
-func (t *GoHtmlTemplate) AddTemplate(name, tpl string) error {
-	_, err := t.New(name).Parse(tpl)
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-	}
-	return err
-}
-
-func (t *GoHtmlTemplate) AddTemplateFile(name, path string) error {
-	b, err := ioutil.ReadFile(path)
-	if err != nil {
-		return err
-	}
-	s := string(b)
-	_, err = t.New(name).Parse(s)
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-	}
-	return err
-}
-
-func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string {
-	return filepath.ToSlash(path[len(base)+1:])
-}
-
-func (t *GoHtmlTemplate) primeTemplates() {
-	alias := "<!DOCTYPE html>\n <html>\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
-	alias_xhtml := "<!DOCTYPE html>\n <html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
-
-	t.AddTemplate("alias", alias)
-	t.AddTemplate("alias-xhtml", alias_xhtml)
-}
-
-func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
-	walker := func(path string, fi os.FileInfo, err error) error {
-		if err != nil {
-			PrintErr("Walker: ", err)
-			return nil
-		}
-
-		if !fi.IsDir() {
-			if ignoreDotFile(path) {
-				return nil
-			}
-
-			tplName := t.generateTemplateNameFrom(absPath, path)
-
-			if strings.HasSuffix(path, ".amber") {
-				compiler := amber.New()
-				// Parse the input file
-				if err := compiler.ParseFile(path); err != nil {
-					return nil
-				}
-
-				// note t.New(tplName)
-				if _, err := compiler.CompileWithTemplate(t.New(tplName)); err != nil {
-					PrintErr("Could not compile amber file: "+path, err)
-					return err
-				}
-
-			} else {
-				t.AddTemplateFile(tplName, path)
-			}
-		}
-		return nil
-	}
-
-	filepath.Walk(absPath, walker)
-}
--- /dev/null
+++ b/template/bundle/bundle_test.go
@@ -1,0 +1,12 @@
+package bundle
+
+import (
+	"testing"
+)
+
+func TestNothing(t *testing.T) {
+	b := NewTemplate()
+	if b.Lookup("alias") == nil {
+		t.Fatalf("Expecting alias to be initialized with new bundle")
+	}
+}
--- /dev/null
+++ b/template/bundle/path_seperators_windows_test.go
@@ -1,0 +1,17 @@
+package bundle
+
+import (
+	"testing"
+)
+
+const (
+	win_base = "c:\\a\\windows\\path\\layout"
+	win_path = "c:\\a\\windows\\path\\layout\\sub1\\index.html"
+)
+
+func TestTemplatePathSeperator(t *testing.T) {
+	tmpl := new(GoHtmlTemplate)
+	if name := tmpl.generateTemplateNameFrom(win_base, win_path); name != "sub1/index.html" {
+		t.Fatalf("Template name incorrect.  Expected: %s, Got: %s", "sub1/index.html", name)
+	}
+}
--- /dev/null
+++ b/template/bundle/template.go
@@ -1,0 +1,188 @@
+package bundle
+
+import (
+	"github.com/eknkc/amber"
+	helpers "github.com/spf13/hugo/template"
+	"html/template"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+func Gt(a interface{}, b interface{}) bool {
+	var left, right int64
+	av := reflect.ValueOf(a)
+
+	switch av.Kind() {
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+		left = int64(av.Len())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		left = av.Int()
+	case reflect.String:
+		left, _ = strconv.ParseInt(av.String(), 10, 64)
+	}
+
+	bv := reflect.ValueOf(b)
+
+	switch bv.Kind() {
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+		right = int64(bv.Len())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		right = bv.Int()
+	case reflect.String:
+		right, _ = strconv.ParseInt(bv.String(), 10, 64)
+	}
+
+	return left > right
+}
+
+func IsSet(a interface{}, key interface{}) bool {
+	av := reflect.ValueOf(a)
+	kv := reflect.ValueOf(key)
+
+	switch av.Kind() {
+	case reflect.Array, reflect.Chan, reflect.Slice:
+		if int64(av.Len()) > kv.Int() {
+			return true
+		}
+	case reflect.Map:
+		if kv.Type() == av.Type().Key() {
+			return av.MapIndex(kv).IsValid()
+		}
+	}
+
+	return false
+}
+
+func ReturnWhenSet(a interface{}, index int) interface{} {
+	av := reflect.ValueOf(a)
+
+	switch av.Kind() {
+	case reflect.Array, reflect.Slice:
+		if av.Len() > index {
+
+			avv := av.Index(index)
+			switch avv.Kind() {
+			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+				return avv.Int()
+			case reflect.String:
+				return avv.String()
+			}
+		}
+	}
+
+	return ""
+}
+
+type Template interface {
+	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
+	Lookup(name string) *template.Template
+	Templates() []*template.Template
+	New(name string) *template.Template
+	LoadTemplates(absPath string)
+	AddTemplate(name, tpl string) error
+}
+
+type templateErr struct {
+	name string
+	err  error
+}
+
+type GoHtmlTemplate struct {
+	template.Template
+	errors []*templateErr
+}
+
+func NewTemplate() Template {
+	var templates = &GoHtmlTemplate{
+		Template: *template.New(""),
+		errors:   make([]*templateErr, 0),
+	}
+
+	funcMap := template.FuncMap{
+		"urlize":    helpers.Urlize,
+		"gt":        Gt,
+		"isset":     IsSet,
+		"echoParam": ReturnWhenSet,
+	}
+
+	templates.Funcs(funcMap)
+	templates.primeTemplates()
+	return templates
+}
+
+func (t *GoHtmlTemplate) AddTemplate(name, tpl string) error {
+	_, err := t.New(name).Parse(tpl)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+	}
+	return err
+}
+
+func (t *GoHtmlTemplate) AddTemplateFile(name, path string) error {
+	b, err := ioutil.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	s := string(b)
+	_, err = t.New(name).Parse(s)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+	}
+	return err
+}
+
+func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string {
+	return filepath.ToSlash(path[len(base)+1:])
+}
+
+func (t *GoHtmlTemplate) primeTemplates() {
+	alias := "<!DOCTYPE html>\n <html>\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
+	alias_xhtml := "<!DOCTYPE html>\n <html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
+
+	t.AddTemplate("alias", alias)
+	t.AddTemplate("alias-xhtml", alias_xhtml)
+}
+
+func ignoreDotFile(path string) bool {
+	return filepath.Base(path)[0] == '.'
+}
+
+func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
+	walker := func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return nil
+		}
+
+		if !fi.IsDir() {
+			if ignoreDotFile(path) {
+				return nil
+			}
+
+			tplName := t.generateTemplateNameFrom(absPath, path)
+
+			if strings.HasSuffix(path, ".amber") {
+				compiler := amber.New()
+				// Parse the input file
+				if err := compiler.ParseFile(path); err != nil {
+					return nil
+				}
+
+				// note t.New(tplName)
+				if _, err := compiler.CompileWithTemplate(t.New(tplName)); err != nil {
+					return err
+				}
+
+			} else {
+				t.AddTemplateFile(tplName, path)
+			}
+		}
+		return nil
+	}
+
+	filepath.Walk(absPath, walker)
+}
--- /dev/null
+++ b/template/helpers.go
@@ -1,0 +1,29 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// 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 template
+
+import (
+	"regexp"
+	"strings"
+)
+
+var sanitizeRegexp = regexp.MustCompile("[^a-zA-Z0-9./_-]")
+
+func Urlize(url string) string {
+	return Sanitize(strings.ToLower(strings.Replace(strings.TrimSpace(url), " ", "-", -1)))
+}
+
+func Sanitize(s string) string {
+	return sanitizeRegexp.ReplaceAllString(s, "")
+}