Preliminary Theme Support
authorspf13 <steve.francia@gmail.com>
Thu, 10 Apr 2014 12:10:12 +0000 (08:10 -0400)
committerspf13 <steve.francia@gmail.com>
Thu, 10 Apr 2014 12:10:12 +0000 (08:10 -0400)
commands/hugo.go
hugolib/site.go
template/bundle/template.go

index a6db4d19d5bc8bab426a2a4fc1fccb7b3999a5de..b446e696a4ccfdcb8823ac555d027574528ab3e1 100644 (file)
@@ -49,7 +49,7 @@ Complete documentation is available at http://hugo.spf13.com`,
 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 @@ func init() {
        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 @@ func InitializeConfig() {
                }
                viper.Set("BaseUrl", BaseUrl)
        }
+
+       if Theme != "" {
+               viper.Set("theme", Theme)
+       }
+
        if Destination != "" {
                viper.Set("PublishDir", Destination)
        }
@@ -176,10 +182,24 @@ func build(watches ...bool) {
 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,10 +222,17 @@ func getDirList() []string {
        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) {
        startTime := time.Now()
        site := &hugolib.Site{}
index 63810c1aa061dbf3a0e280c44aefa56a5a8d625c..050eaafbfa0b2a37b5886fc024724fe17c37813a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright © 2013 Steve Francia <spf@spf13.com>.
+// Copyright © 2013-14 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.
@@ -132,6 +132,9 @@ func (s *Site) Analyze() {
 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) initializeSiteInfo() {
        }
 }
 
+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 @@ func (s *Site) RenderPages() (err error) {
                                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 @@ func (s *Site) RenderPages() (err error) {
        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 @@ func (s *Site) RenderTaxonomiesLists() (err error) {
                                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 @@ func (s *Site) RenderTaxonomiesLists() (err error) {
                                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,28 +515,27 @@ func (s *Site) RenderTaxonomiesLists() (err error) {
 
 // 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
                        }
                }
        }
+
        return
 }
 
@@ -508,17 +547,18 @@ func (s *Site) RenderSectionLists() error {
                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
                }
 
                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 @@ func (s *Site) RenderHomePage() error {
        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 @@ func (s *Site) RenderHomePage() error {
                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 @@ func (s *Site) RenderHomePage() error {
                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
index 20316de4902117b95aabe1d2d673e449fbfd6a8e..30d24144b6db1e35b5da1859980cd7486a3b90b6 100644 (file)
@@ -138,6 +138,7 @@ type Template interface {
        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 @@ func (t *GoHtmlTemplate) AddTemplateFile(name, path string) error {
        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 @@ func ignoreDotFile(path string) bool {
        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 @@ func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
 
                        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 @@ func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
                                        return nil
                                }
 
-                               // note t.New(tplName)
                                if _, err := compiler.CompileWithTemplate(t.New(tplName)); err != nil {
                                        return err
                                }
@@ -261,3 +261,11 @@ func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
 
        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, "")
+}