diff --git a/commands/hugo.go b/commands/hugo.go index a6db4d19d..b446e696a 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -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{} diff --git a/hugolib/site.go b/hugolib/site.go index 63810c1aa..050eaafbf 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1,4 +1,4 @@ -// Copyright © 2013 Steve Francia . +// Copyright © 2013-14 Steve Francia . // // 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 diff --git a/template/bundle/template.go b/template/bundle/template.go index 20316de49..30d24144b 100644 --- a/template/bundle/template.go +++ b/template/bundle/template.go @@ -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, "") +}