shithub: hugo

Download patch

ref: 8afff8c7c43d3e593313832dfa371461a0cb133b
parent: c0a046cbfb24d64847e6b8a5cd8be8a7e8a0fc80
author: spf13 <[email protected]>
date: Thu Apr 10 04:10:12 EDT 2014

Preliminary Theme Support

--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -49,7 +49,7 @@
 var hugoCmdV *cobra.Command
 
 var BuildWatch, Draft, UglyUrls, Verbose, Logging, VerboseLog, DisableRSS bool
-var Source, Destination, BaseUrl, CfgFile, LogFile string
+var Source, Destination, Theme, BaseUrl, CfgFile, LogFile string
 
 func Execute() {
 	AddCommands()
@@ -68,6 +68,7 @@
 	HugoCmd.PersistentFlags().BoolVar(&DisableRSS, "disableRSS", false, "Do not build RSS files")
 	HugoCmd.PersistentFlags().StringVarP(&Source, "source", "s", "", "filesystem path to read files relative from")
 	HugoCmd.PersistentFlags().StringVarP(&Destination, "destination", "d", "", "filesystem path to write files to")
+	HugoCmd.PersistentFlags().StringVarP(&Theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)")
 	HugoCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
 	HugoCmd.PersistentFlags().BoolVar(&UglyUrls, "uglyurls", false, "if true, use /filename.html instead of /filename/")
 	HugoCmd.PersistentFlags().StringVarP(&BaseUrl, "base-url", "b", "", "hostname (and path) to the root eg. http://spf13.com/")
@@ -126,6 +127,11 @@
 		}
 		viper.Set("BaseUrl", BaseUrl)
 	}
+
+	if Theme != "" {
+		viper.Set("theme", Theme)
+	}
+
 	if Destination != "" {
 		viper.Set("PublishDir", Destination)
 	}
@@ -176,10 +182,24 @@
 func copyStatic() error {
 	staticDir := helpers.AbsPathify(viper.GetString("StaticDir")) + "/"
 	if _, err := os.Stat(staticDir); os.IsNotExist(err) {
+		jww.ERROR.Println("Unable to find Static Directory:", viper.GetString("theme"), "in", staticDir)
 		return nil
 	}
 
 	publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + "/"
+
+	if themeSet() {
+		themeDir := helpers.AbsPathify("themes/"+viper.GetString("theme")) + "/static/"
+		if _, err := os.Stat(themeDir); os.IsNotExist(err) {
+			jww.ERROR.Println("Unable to find static directory for theme :", viper.GetString("theme"), "in", themeDir)
+			return nil
+		}
+
+		// Copy Static to Destination
+		jww.INFO.Println("syncing from", themeDir, "to", publishDir)
+		return fsync.Sync(publishDir, themeDir)
+	}
+
 	// Copy Static to Destination
 	jww.INFO.Println("syncing from", staticDir, "to", publishDir)
 	return fsync.Sync(publishDir, staticDir)
@@ -202,8 +222,15 @@
 	filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker)
 	filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker)
 	filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker)
+	if themeSet() {
+		filepath.Walk(helpers.AbsPathify("themes/"+viper.GetString("theme")), walker)
+	}
 
 	return a
+}
+
+func themeSet() bool {
+	return viper.GetString("theme") != ""
 }
 
 func buildSite(watching ...bool) (err error) {
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -1,4 +1,4 @@
-// Copyright © 2013 Steve Francia <[email protected]>.
+// Copyright © 2013-14 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.
@@ -132,6 +132,9 @@
 func (s *Site) prepTemplates() {
 	s.Tmpl = bundle.NewTemplate()
 	s.Tmpl.LoadTemplates(s.absLayoutDir())
+	if s.hasTheme() {
+		s.Tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
+	}
 }
 
 func (s *Site) addTemplate(name, data string) error {
@@ -244,6 +247,14 @@
 	}
 }
 
+func (s *Site) hasTheme() bool {
+	return viper.GetString("theme") != ""
+}
+
+func (s *Site) absThemeDir() string {
+	return helpers.AbsPathify("themes/" + viper.GetString("theme"))
+}
+
 func (s *Site) absLayoutDir() string {
 	return helpers.AbsPathify(viper.GetString("LayoutDir"))
 }
@@ -422,7 +433,7 @@
 				layouts = append(layouts, "_default/single.html")
 			}
 
-			return s.render(p, p.TargetPath(), layouts...)
+			return s.render(p, p.TargetPath(), s.appendThemeTemplates(layouts)...)
 		}(page)
 	}
 	wg.Wait()
@@ -433,6 +444,34 @@
 	return nil
 }
 
+func (s *Site) appendThemeTemplates(in []string) []string {
+	if s.hasTheme() {
+		out := []string{}
+		// First place all non internal templates
+		for _, t := range in {
+			if !strings.HasPrefix("_internal/", t) {
+				out = append(out, t)
+			}
+		}
+
+		// Then place theme templates with the same names
+		for _, t := range in {
+			if !strings.HasPrefix("_internal/", t) {
+				out = append(out, "theme/"+t)
+			}
+		}
+		// Lastly place internal templates
+		for _, t := range in {
+			if strings.HasPrefix("_internal/", t) {
+				out = append(out, "theme/"+t)
+			}
+		}
+		return out
+	} else {
+		return in
+	}
+}
+
 // Render the listing pages based on the meta data
 // each unique term within a taxonomy will have a page created
 func (s *Site) RenderTaxonomiesLists() (err error) {
@@ -451,8 +490,8 @@
 				n.Date = o[0].Page.Date
 				n.Data[singular] = o
 				n.Data["Pages"] = o.Pages()
-				err = s.render(n, base+".html", "taxonomies/"+singular+".html", "indexes/"+singular+".html")
-				//TODO add , "_default/taxonomy.html", "_default/list.html"
+				layouts := []string{"taxonomy/" + singular + ".html", "indexes/" + singular + ".html", "_default/taxonomy.html", "_default/list.html"}
+				err = s.render(n, base+".html", s.appendThemeTemplates(layouts)...)
 				if err != nil {
 					return err
 				}
@@ -460,7 +499,8 @@
 				if !viper.GetBool("DisableRSS") {
 					// XML Feed
 					s.setUrls(n, base+".xml")
-					err := s.render(n, base+".xml", "rss.xml", "_internal/_default/rss.xml")
+					rssLayouts := []string{"taxonomy/" + singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
+					err := s.render(n, base+".xml", s.appendThemeTemplates(rssLayouts)...)
 					if err != nil {
 						return err
 					}
@@ -475,22 +515,20 @@
 
 // Render a page per taxonomy that lists the terms for that taxonomy
 func (s *Site) RenderListsOfTaxonomyTerms() (err error) {
-	layouts := []string{"taxonomies/termslist.html", "indexes/indexes.html"}
-	// TODO add "_default/termsList.html", "_default/termslist.html"
-	// TODO add support for unique taxonomy terms list (`single`terms.html)
-	if s.layoutExists(layouts...) {
-		taxonomies := viper.GetStringMapString("Taxonomies")
-		for singular, plural := range taxonomies {
-			n := s.NewNode()
-			n.Title = strings.Title(plural)
-			s.setUrls(n, plural)
-			n.Data["Singular"] = singular
-			n.Data["Plural"] = plural
-			n.Data["Terms"] = s.Taxonomies[plural]
-			// keep the following just for legacy reasons
-			n.Data["OrderedIndex"] = n.Data["Terms"]
-			n.Data["Index"] = n.Data["Terms"]
-
+	taxonomies := viper.GetStringMapString("Taxonomies")
+	for singular, plural := range taxonomies {
+		n := s.NewNode()
+		n.Title = strings.Title(plural)
+		s.setUrls(n, plural)
+		n.Data["Singular"] = singular
+		n.Data["Plural"] = plural
+		n.Data["Terms"] = s.Taxonomies[plural]
+		// keep the following just for legacy reasons
+		n.Data["OrderedIndex"] = n.Data["Terms"]
+		n.Data["Index"] = n.Data["Terms"]
+		layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"}
+		layouts = s.appendThemeTemplates(layouts)
+		if s.layoutExists(layouts...) {
 			err := s.render(n, plural+"/index.html", layouts...)
 			if err != nil {
 				return err
@@ -497,6 +535,7 @@
 			}
 		}
 	}
+
 	return
 }
 
@@ -508,8 +547,9 @@
 		s.setUrls(n, section)
 		n.Date = data[0].Page.Date
 		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(n, section, "section/"+section+".html", "indexes/"+section+".html", "_default/section.html", "_default/list.html", "_default/indexes.html")
+		err := s.render(n, section, s.appendThemeTemplates(layouts)...)
 		if err != nil {
 			return err
 		}
@@ -516,9 +556,9 @@
 
 		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(n, section+".xml", "rss.xml", "_internal/_default/rss.xml")
-			//TODO add section specific rss
+			err = s.render(n, section+".xml", s.appendThemeTemplates(rssLayouts)...)
 			if err != nil {
 				return err
 			}
@@ -532,7 +572,8 @@
 	n.Title = n.Site.Title
 	s.setUrls(n, "/")
 	n.Data["Pages"] = s.Pages
-	err := s.render(n, "/", "index.html")
+	layouts := []string{"index.html"}
+	err := s.render(n, "/", s.appendThemeTemplates(layouts)...)
 	if err != nil {
 		return err
 	}
@@ -550,9 +591,13 @@
 		if len(s.Pages) > 0 {
 			n.Date = s.Pages[0].Date
 		}
-		err := s.render(n, ".xml", "rss.xml", "_internal/_default/rss.xml")
-		if err != nil {
-			return err
+
+		if !viper.GetBool("DisableRSS") {
+			rssLayouts := []string{"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"}
+			err := s.render(n, ".xml", s.appendThemeTemplates(rssLayouts)...)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -560,7 +605,9 @@
 		n.Url = helpers.Urlize("404.html")
 		n.Title = "404 Page not found"
 		n.Permalink = s.permalink("404.html")
-		return s.render(n, "404.html", "404.html")
+
+		layouts := []string{"404.html"}
+		return s.render(n, "404.html", s.appendThemeTemplates(layouts)...)
 	}
 
 	return nil
--- a/template/bundle/template.go
+++ b/template/bundle/template.go
@@ -138,6 +138,7 @@
 	Templates() []*template.Template
 	New(name string) *template.Template
 	LoadTemplates(absPath string)
+	LoadTemplatesWithPrefix(absPath, prefix string)
 	AddTemplate(name, tpl string) error
 	AddInternalTemplate(prefix, name, tpl string) error
 	AddInternalShortcode(name, tpl string) error
@@ -211,12 +212,7 @@
 	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
+	return t.AddTemplate(name, string(b))
 }
 
 func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string {
@@ -227,7 +223,7 @@
 	return filepath.Base(path)[0] == '.'
 }
 
-func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
+func (t *GoHtmlTemplate) loadTemplates(absPath string, prefix string) {
 	walker := func(path string, fi os.FileInfo, err error) error {
 		if err != nil {
 			return nil
@@ -240,6 +236,11 @@
 
 			tplName := t.generateTemplateNameFrom(absPath, path)
 
+			if prefix != "" {
+				tplName = strings.Trim(prefix, "/") + "/" + tplName
+			}
+
+			// TODO move this into the AddTemplateFile function
 			if strings.HasSuffix(path, ".amber") {
 				compiler := amber.New()
 				// Parse the input file
@@ -247,7 +248,6 @@
 					return nil
 				}
 
-				// note t.New(tplName)
 				if _, err := compiler.CompileWithTemplate(t.New(tplName)); err != nil {
 					return err
 				}
@@ -260,4 +260,12 @@
 	}
 
 	filepath.Walk(absPath, walker)
+}
+
+func (t *GoHtmlTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
+	t.loadTemplates(absPath, prefix)
+}
+
+func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
+	t.loadTemplates(absPath, "")
 }