shithub: hugo

Download patch

ref: 7b960ac121fa8cdbd9b52c4f2adf66d39440758e
parent: e02dc6742af9d1a23bdeaa89704fdfcdd075c875
author: spf13 <[email protected]>
date: Mon Nov 3 19:39:37 EST 2014

New targets & new renderers and write methods [WIP]

--- a/hugolib/planner.go
+++ b/hugolib/planner.go
@@ -24,23 +24,23 @@
 		}
 		fmt.Fprintf(out, "\n")
 		fmt.Fprintf(out, " canonical => ")
-		if s.Target == nil {
+		if s.Targets.Page == nil {
 			fmt.Fprintf(out, "%s\n\n", "!no target specified!")
 			continue
 		}
 
-		trns, err := s.Target.Translate(p.TargetPath())
+		trns, err := s.PageTarget().Translate(p.TargetPath())
 		if err != nil {
 			return err
 		}
 		fmt.Fprintf(out, "%s\n", trns)
 
-		if s.Alias == nil {
+		if s.Targets.Alias == nil {
 			continue
 		}
 
 		for _, alias := range p.Aliases {
-			aliasTrans, err := s.Alias.Translate(alias)
+			aliasTrans, err := s.AliasTarget().Translate(alias)
 			if err != nil {
 				return err
 			}
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -69,8 +69,7 @@
 	Shortcodes  map[string]ShortcodeFunc
 	Menus       Menus
 	timer       *nitro.B
-	Target      target.Output
-	Alias       target.AliasPublisher
+	Targets     targetList
 	Completed   chan bool
 	RunMode     runmode
 	params      map[string]interface{}
@@ -78,6 +77,12 @@
 	futureCount int
 }
 
+type targetList struct {
+	Page  target.Output
+	File  target.Output
+	Alias target.AliasPublisher
+}
+
 type SiteInfo struct {
 	BaseUrl         template.URL
 	Taxonomies      TaxonomyList
@@ -157,10 +162,6 @@
 
 func (s *Site) Analyze() {
 	s.Process()
-	s.initTarget()
-	s.Alias = &target.HTMLRedirectAlias{
-		PublishDir: s.absPublishDir(),
-	}
 	s.ShowPlan(os.Stdout)
 }
 
@@ -671,7 +672,7 @@
 			if err != nil {
 				return err
 			}
-			if err := s.WriteAlias(a, template.HTML(plink)); err != nil {
+			if err := s.WriteDestAlias(a, template.HTML(plink)); err != nil {
 				return err
 			}
 		}
@@ -733,7 +734,12 @@
 			layouts = append(layouts, "_default/single.html")
 		}
 
-		results <- s.render("page "+p.FullFilePath(), p, p.TargetPath(), s.appendThemeTemplates(layouts)...)
+		b, err := s.renderPage("page "+p.FullFilePath(), p, s.appendThemeTemplates(layouts)...)
+		if err != nil {
+			results <- err
+		} else {
+			results <- s.WriteDestPage(p.TargetPath(), b)
+		}
 	}
 }
 
@@ -843,10 +849,15 @@
 	for t := range taxes {
 		n, base := s.newTaxonomyNode(t)
 		layouts := []string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"}
-		err := s.render("taxononomy "+t.singular, n, base+".html", s.appendThemeTemplates(layouts)...)
+		b, err := s.renderPage("taxononomy "+t.singular, n, s.appendThemeTemplates(layouts)...)
 		if err != nil {
 			results <- err
 			continue
+		} else {
+			err := s.WriteDestPage(base+".html", b)
+			if err != nil {
+				results <- err
+			}
 		}
 
 		if !viper.GetBool("DisableRSS") {
@@ -853,13 +864,17 @@
 			// XML Feed
 			s.setUrls(n, base+".xml")
 			rssLayouts := []string{"taxonomy/" + t.singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
-			err := s.render("taxonomy "+t.singular+" rss", n, base+".xml", s.appendThemeTemplates(rssLayouts)...)
+			b, err := s.renderXML("taxonomy "+t.singular+" rss", n, s.appendThemeTemplates(rssLayouts)...)
 			if err != nil {
 				results <- err
 				continue
+			} else {
+				err := s.WriteDestFile(base+".xml", b)
+				if err != nil {
+					results <- err
+				}
 			}
 		}
-		results <- nil
 	}
 }
 
@@ -879,10 +894,13 @@
 		layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"}
 		layouts = s.appendThemeTemplates(layouts)
 		if s.layoutExists(layouts...) {
-			err := s.render("taxonomy terms for "+singular, n, plural+"/index.html", layouts...)
+			b, err := s.renderPage("taxonomy terms for "+singular, n, layouts...)
 			if err != nil {
 				return err
 			}
+			if err := s.WriteDestPage(plural+"/index.html", b); err != nil {
+				return err
+			}
 		}
 	}
 
@@ -903,19 +921,25 @@
 		n.Data["Pages"] = data.Pages()
 		layouts := []string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"}
 
-		err := s.render("section "+section, n, section, s.appendThemeTemplates(layouts)...)
+		b, err := s.renderPage("section "+section, n, s.appendThemeTemplates(layouts)...)
 		if err != nil {
 			return err
 		}
+		if err := s.WriteDestPage(section, b); err != nil {
+			return err
+		}
 
 		if !viper.GetBool("DisableRSS") {
 			// XML Feed
 			rssLayouts := []string{"section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
 			s.setUrls(n, section+".xml")
-			err = s.render("section "+section+" rss", n, section+".xml", s.appendThemeTemplates(rssLayouts)...)
+			b, err = s.renderXML("section "+section+" rss", n, s.appendThemeTemplates(rssLayouts)...)
 			if err != nil {
 				return err
 			}
+			if err := s.WriteDestFile(section+".xml", b); err != nil {
+				return err
+			}
 		}
 	}
 	return nil
@@ -932,10 +956,13 @@
 func (s *Site) RenderHomePage() error {
 	n := s.newHomeNode()
 	layouts := []string{"index.html", "_default/list.html", "_default/single.html"}
-	err := s.render("homepage", n, "/", s.appendThemeTemplates(layouts)...)
+	b, err := s.renderPage("homepage", n, s.appendThemeTemplates(layouts)...)
 	if err != nil {
 		return err
 	}
+	if err := s.WriteDestPage("/", b); err != nil {
+		return err
+	}
 
 	if !viper.GetBool("DisableRSS") {
 		// XML Feed
@@ -953,31 +980,28 @@
 
 		if !viper.GetBool("DisableRSS") {
 			rssLayouts := []string{"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"}
-			err := s.render("homepage rss", n, ".xml", s.appendThemeTemplates(rssLayouts)...)
+			b, err := s.renderXML("homepage rss", n, s.appendThemeTemplates(rssLayouts)...)
 			if err != nil {
 				return err
 			}
+			if err := s.WriteDestFile("rss.xml", b); err != nil {
+				return err
+			}
 		}
 	}
 
-	// Force `UglyUrls` option to force `404.html` file name
-	switch s.Target.(type) {
-	case *target.Filesystem:
-		if !s.Target.(*target.Filesystem).UglyUrls {
-			s.Target.(*target.Filesystem).UglyUrls = true
-			defer func() { s.Target.(*target.Filesystem).UglyUrls = false }()
-		}
-	}
-
 	n.Url = helpers.Urlize("404.html")
 	n.Title = "404 Page not found"
 	n.Permalink = s.permalink("404.html")
 
 	nfLayouts := []string{"404.html"}
-	nfErr := s.render("404 page", n, "404.html", s.appendThemeTemplates(nfLayouts)...)
+	b, nfErr := s.renderPage("404 page", n, s.appendThemeTemplates(nfLayouts)...)
 	if nfErr != nil {
 		return nfErr
 	}
+	if err := s.WriteDestFile("404.html", b); err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -1017,20 +1041,23 @@
 	}
 
 	// Force `UglyUrls` option to force `sitemap.xml` file name
-	switch s.Target.(type) {
+	switch s.PageTarget().(type) {
 	case *target.Filesystem:
-		s.Target.(*target.Filesystem).UglyUrls = true
+		s.PageTarget().(*target.PagePub).UglyUrls = true
 		optChanged = true
 	}
 
 	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
-	err := s.render("sitemap", n, "sitemap.xml", s.appendThemeTemplates(smLayouts)...)
+	b, err := s.renderXML("sitemap", n, s.appendThemeTemplates(smLayouts)...)
 	if err != nil {
 		return err
 	}
+	if err := s.WriteDestFile("sitemap.xml", b); err != nil {
+		return err
+	}
 
 	if optChanged {
-		s.Target.(*target.Filesystem).UglyUrls = viper.GetBool("UglyUrls")
+		s.PageTarget().(*target.PagePub).UglyUrls = viper.GetBool("UglyUrls")
 	}
 
 	return nil
@@ -1087,20 +1114,24 @@
 	return found
 }
 
-func (s *Site) render(name string, d interface{}, out string, layouts ...string) (err error) {
+func (s *Site) renderXML(name string, d interface{}, layouts ...string) (io.Reader, error) {
+	renderBuffer := s.NewXMLBuffer()
+	err := s.render(name, d, renderBuffer, layouts...)
+	return renderBuffer, err
+}
 
-	layout, found := s.findFirstLayout(layouts...)
-	if found == false {
-		jww.WARN.Printf("Unable to locate layout for %s: %s\n", name, layouts)
-		return
-	}
+func (s *Site) renderPage(name string, d interface{}, layouts ...string) (io.Reader, error) {
+	renderBuffer := new(bytes.Buffer)
+	err := s.render(name, d, renderBuffer, layouts...)
 
+	var outBuffer = new(bytes.Buffer)
+
 	transformLinks := transform.NewEmptyTransforms()
 
 	if viper.GetBool("CanonifyUrls") {
 		absURL, err := transform.AbsURL(viper.GetString("BaseUrl"))
 		if err != nil {
-			return err
+			return nil, err
 		}
 		transformLinks = append(transformLinks, absURL...)
 	}
@@ -1110,17 +1141,18 @@
 	}
 
 	transformer := transform.NewChain(transformLinks...)
+	transformer.Apply(outBuffer, renderBuffer)
+	return outBuffer, err
+}
 
-	var renderBuffer *bytes.Buffer
-
-	if strings.HasSuffix(out, ".xml") {
-		renderBuffer = s.NewXMLBuffer()
-	} else {
-		renderBuffer = new(bytes.Buffer)
+func (s *Site) render(name string, d interface{}, renderBuffer *bytes.Buffer, layouts ...string) error {
+	layout, found := s.findFirstLayout(layouts...)
+	if found == false {
+		jww.WARN.Printf("Unable to locate layout for %s: %s\n", name, layouts)
+		return nil
 	}
 
-	err = s.renderThing(d, layout, renderBuffer)
-	if err != nil {
+	if err := s.renderThing(d, layout, renderBuffer); err != nil {
 		// Behavior here should be dependent on if running in server or watch mode.
 		jww.ERROR.Println(fmt.Errorf("Error while rendering %s: %v", name, err))
 		if !s.Running() {
@@ -1128,14 +1160,7 @@
 		}
 	}
 
-	var outBuffer = new(bytes.Buffer)
-	if strings.HasSuffix(out, ".xml") {
-		outBuffer = renderBuffer
-	} else {
-		transformer.Apply(outBuffer, renderBuffer)
-	}
-
-	return s.WritePublic(out, outBuffer)
+	return nil
 }
 
 func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
@@ -1160,33 +1185,48 @@
 	return bytes.NewBufferString(header)
 }
 
-func (s *Site) initTarget() {
-	if s.Target == nil {
-		s.Target = &target.Filesystem{
+func (s *Site) PageTarget() target.Output {
+	if s.Targets.Page == nil {
+		s.Targets.Page = &target.PagePub{
 			PublishDir: s.absPublishDir(),
 			UglyUrls:   viper.GetBool("UglyUrls"),
 		}
 	}
+	return s.Targets.Page
 }
 
-func (s *Site) WritePublic(path string, reader io.Reader) (err error) {
-	s.initTarget()
-
-	jww.DEBUG.Println("writing to", path)
-	return s.Target.Publish(path, reader)
+func (s *Site) FileTarget() target.Output {
+	if s.Targets.File == nil {
+		s.Targets.File = &target.Filesystem{
+			PublishDir: s.absPublishDir(),
+		}
+	}
+	return s.Targets.File
 }
 
-func (s *Site) WriteAlias(path string, permalink template.HTML) (err error) {
-	if s.Alias == nil {
-		s.initTarget()
-		s.Alias = &target.HTMLRedirectAlias{
+func (s *Site) AliasTarget() target.AliasPublisher {
+	if s.Targets.Alias == nil {
+		s.Targets.Alias = &target.HTMLRedirectAlias{
 			PublishDir: s.absPublishDir(),
 		}
+
 	}
+	return s.Targets.Alias
+}
 
-	jww.DEBUG.Println("alias created at", path)
+func (s *Site) WriteDestFile(path string, reader io.Reader) (err error) {
+	jww.DEBUG.Println("creating file:", path)
+	return s.FileTarget().Publish(path, reader)
+}
 
-	return s.Alias.Publish(path, permalink)
+func (s *Site) WriteDestPage(path string, reader io.Reader) (err error) {
+	jww.DEBUG.Println("creating page:", path)
+	return s.PageTarget().Publish(path, reader)
+}
+
+func (s *Site) WriteDestAlias(path string, permalink template.HTML) (err error) {
+	jww.DEBUG.Println("alias created at:", path)
+	return s.AliasTarget().Publish(path, permalink)
 }
 
 func (s *Site) draftStats() string {
--- a/target/file.go
+++ b/target/file.go
@@ -1,7 +1,6 @@
 package target
 
 import (
-	"fmt"
 	"io"
 	"path"
 
@@ -23,13 +22,10 @@
 }
 
 type Filesystem struct {
-	UglyUrls         bool
-	DefaultExtension string
-	PublishDir       string
+	PublishDir string
 }
 
 func (fs *Filesystem) Publish(path string, r io.Reader) (err error) {
-
 	translated, err := fs.Translate(path)
 	if err != nil {
 		return
@@ -39,42 +35,11 @@
 }
 
 func (fs *Filesystem) Translate(src string) (dest string, err error) {
-	if src == "/" {
-		if fs.PublishDir != "" {
-			return path.Join(fs.PublishDir, "index.html"), nil
-		}
-		return "index.html", nil
-	}
-
-	dir, file := path.Split(src)
-	ext := fs.extension(path.Ext(file))
-	name := filename(file)
-	if fs.PublishDir != "" {
-		dir = path.Join(fs.PublishDir, dir)
-	}
-
-	if fs.UglyUrls || file == "index.html" {
-		return path.Join(dir, fmt.Sprintf("%s%s", name, ext)), nil
-	}
-
-	return path.Join(dir, name, fmt.Sprintf("index%s", ext)), nil
+	return path.Join(fs.PublishDir, src), nil
 }
 
 func (fs *Filesystem) extension(ext string) string {
-	switch ext {
-	case ".md", ".rst": // TODO make this list configurable.  page.go has the list of markup types.
-		return ".html"
-	}
-
-	if ext != "" {
-		return ext
-	}
-
-	if fs.DefaultExtension != "" {
-		return fs.DefaultExtension
-	}
-
-	return ".html"
+	return ext
 }
 
 func filename(f string) string {
--- a/target/file_test.go
+++ /dev/null
@@ -1,93 +1,0 @@
-package target
-
-import (
-	"testing"
-)
-
-func TestFileTranslator(t *testing.T) {
-	tests := []struct {
-		content  string
-		expected string
-	}{
-		{"/", "index.html"},
-		{"index.html", "index.html"},
-		{"bar/index.html", "bar/index.html"},
-		{"foo", "foo/index.html"},
-		{"foo.html", "foo/index.html"},
-		{"foo.xhtml", "foo/index.xhtml"},
-		{"section", "section/index.html"},
-		{"section/", "section/index.html"},
-		{"section/foo", "section/foo/index.html"},
-		{"section/foo.html", "section/foo/index.html"},
-		{"section/foo.rss", "section/foo/index.rss"},
-	}
-
-	for _, test := range tests {
-		f := new(Filesystem)
-		dest, err := f.Translate(test.content)
-		if err != nil {
-			t.Fatalf("Translate returned and unexpected err: %s", err)
-		}
-
-		if dest != test.expected {
-			t.Errorf("Tranlate expected return: %s, got: %s", test.expected, dest)
-		}
-	}
-}
-
-func TestFileTranslatorBase(t *testing.T) {
-	tests := []struct {
-		content  string
-		expected string
-	}{
-		{"/", "a/base/index.html"},
-	}
-
-	for _, test := range tests {
-		f := &Filesystem{PublishDir: "a/base"}
-		fts := &Filesystem{PublishDir: "a/base/"}
-
-		for _, fs := range []*Filesystem{f, fts} {
-			dest, err := fs.Translate(test.content)
-			if err != nil {
-				t.Fatalf("Translated returned and err: %s", err)
-			}
-
-			if dest != test.expected {
-				t.Errorf("Translate expected: %s, got: %s", test.expected, dest)
-			}
-		}
-	}
-}
-
-func TestTranslateUglyUrls(t *testing.T) {
-	tests := []struct {
-		content  string
-		expected string
-	}{
-		{"foo.html", "foo.html"},
-		{"/", "index.html"},
-		{"section", "section.html"},
-		{"index.html", "index.html"},
-	}
-
-	for _, test := range tests {
-		f := &Filesystem{UglyUrls: true}
-		dest, err := f.Translate(test.content)
-		if err != nil {
-			t.Fatalf("Translate returned an unexpected err: %s", err)
-		}
-
-		if dest != test.expected {
-			t.Errorf("Translate expected return: %s, got: %s", test.expected, dest)
-		}
-	}
-}
-
-func TestTranslateDefaultExtension(t *testing.T) {
-	f := &Filesystem{DefaultExtension: ".foobar"}
-	dest, _ := f.Translate("baz")
-	if dest != "baz/index.foobar" {
-		t.Errorf("Translate expected return: %s, got %s", "baz/index.foobar", dest)
-	}
-}
--- /dev/null
+++ b/target/page.go
@@ -1,0 +1,71 @@
+package target
+
+import (
+	"fmt"
+	"html/template"
+	"io"
+	"path"
+
+	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
+)
+
+type PagePublisher interface {
+	Translator
+	Publish(string, template.HTML) error
+}
+
+type PagePub struct {
+	UglyUrls         bool
+	DefaultExtension string
+	PublishDir       string
+}
+
+func (pp *PagePub) Publish(path string, r io.Reader) (err error) {
+
+	translated, err := pp.Translate(path)
+	if err != nil {
+		return
+	}
+
+	return helpers.WriteToDisk(translated, r, hugofs.DestinationFS)
+}
+
+func (pp *PagePub) Translate(src string) (dest string, err error) {
+	if src == "/" {
+		if pp.PublishDir != "" {
+			return path.Join(pp.PublishDir, "index.html"), nil
+		}
+		return "index.html", nil
+	}
+
+	dir, file := path.Split(src)
+	ext := pp.extension(path.Ext(file))
+	name := filename(file)
+	if pp.PublishDir != "" {
+		dir = path.Join(pp.PublishDir, dir)
+	}
+
+	if pp.UglyUrls || file == "index.html" {
+		return path.Join(dir, fmt.Sprintf("%s%s", name, ext)), nil
+	}
+
+	return path.Join(dir, name, fmt.Sprintf("index%s", ext)), nil
+}
+
+func (pp *PagePub) extension(ext string) string {
+	switch ext {
+	case ".md", ".rst": // TODO make this list configurable.  page.go has the list of markup types.
+		return ".html"
+	}
+
+	if ext != "" {
+		return ext
+	}
+
+	if pp.DefaultExtension != "" {
+		return pp.DefaultExtension
+	}
+
+	return ".html"
+}
--- /dev/null
+++ b/target/page_test.go
@@ -1,0 +1,93 @@
+package target
+
+import (
+	"testing"
+)
+
+func TestPageTranslator(t *testing.T) {
+	tests := []struct {
+		content  string
+		expected string
+	}{
+		{"/", "index.html"},
+		{"index.html", "index.html"},
+		{"bar/index.html", "bar/index.html"},
+		{"foo", "foo/index.html"},
+		{"foo.html", "foo/index.html"},
+		{"foo.xhtml", "foo/index.xhtml"},
+		{"section", "section/index.html"},
+		{"section/", "section/index.html"},
+		{"section/foo", "section/foo/index.html"},
+		{"section/foo.html", "section/foo/index.html"},
+		{"section/foo.rss", "section/foo/index.rss"},
+	}
+
+	for _, test := range tests {
+		f := new(PagePub)
+		dest, err := f.Translate(test.content)
+		if err != nil {
+			t.Fatalf("Translate returned and unexpected err: %s", err)
+		}
+
+		if dest != test.expected {
+			t.Errorf("Tranlate expected return: %s, got: %s", test.expected, dest)
+		}
+	}
+}
+
+func TestPageTranslatorBase(t *testing.T) {
+	tests := []struct {
+		content  string
+		expected string
+	}{
+		{"/", "a/base/index.html"},
+	}
+
+	for _, test := range tests {
+		f := &PagePub{PublishDir: "a/base"}
+		fts := &PagePub{PublishDir: "a/base/"}
+
+		for _, fs := range []*PagePub{f, fts} {
+			dest, err := fs.Translate(test.content)
+			if err != nil {
+				t.Fatalf("Translated returned and err: %s", err)
+			}
+
+			if dest != test.expected {
+				t.Errorf("Translate expected: %s, got: %s", test.expected, dest)
+			}
+		}
+	}
+}
+
+func TestTranslateUglyUrls(t *testing.T) {
+	tests := []struct {
+		content  string
+		expected string
+	}{
+		{"foo.html", "foo.html"},
+		{"/", "index.html"},
+		{"section", "section.html"},
+		{"index.html", "index.html"},
+	}
+
+	for _, test := range tests {
+		f := &PagePub{UglyUrls: true}
+		dest, err := f.Translate(test.content)
+		if err != nil {
+			t.Fatalf("Translate returned an unexpected err: %s", err)
+		}
+
+		if dest != test.expected {
+			t.Errorf("Translate expected return: %s, got: %s", test.expected, dest)
+		}
+	}
+}
+
+func TestTranslateDefaultExtension(t *testing.T) {
+	f := &PagePub{DefaultExtension: ".foobar"}
+	dest, _ := f.Translate("baz")
+	if dest != "baz/index.foobar" {
+		t.Errorf("Translate expected return: %s, got %s", "baz/index.foobar", dest)
+	}
+}