From 662e12f348a638a6fcc92a416ee7f7c2a7ef8792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 18 Jun 2017 19:39:42 +0200 Subject: [PATCH] commands, create: Add .Site to the archetype templates This commit completes the "The Revival of the Archetypes!" If `.Site` is used in the arcetype template, the site is built and added to the template context. Note that this may be potentially time consuming for big sites. A more complete example would then be for the section `newsletter` and the archetype file `archetypes/newsletter.md`: ``` --- title: "{{ replace .TranslationBaseName "-" " " | title }}" date: {{ .Date }} tags: - x categories: - x draft: true --- {{ range first 10 ( where .Site.RegularPages "Type" "cool" ) }} * {{ .Title }} {{ end }} ``` And then create a new post with: ```bash hugo new newsletter/the-latest-cool.stuff.md ``` **Hot Tip:** If you set the `newContentEditor` configuration variable to an editor on your `PATH`, the newly created article will be opened. The above _newsletter type archetype_ illustrates the possibilities: The full Hugo `.Site` and all of Hugo's template funcs can be used in the archetype file. Fixes #1629 --- commands/new.go | 35 ++++++++++++++++++++++--- create/content.go | 42 +++++++++++++++++++++--------- create/content_template_handler.go | 36 +++++++++++++++++++------ create/content_test.go | 8 ++++-- helpers/path.go | 2 +- helpers/pathspec.go | 8 ++++-- 6 files changed, 101 insertions(+), 30 deletions(-) diff --git a/commands/new.go b/commands/new.go index 4288b6b08..aaae5c6ed 100644 --- a/commands/new.go +++ b/commands/new.go @@ -115,13 +115,40 @@ func NewContent(cmd *cobra.Command, args []string) error { kind = contentType } - s, err := hugolib.NewSite(*cfg) - + ps, err := helpers.NewPathSpec(cfg.Fs, cfg.Cfg) if err != nil { - return newSystemError(err) + return err } - return create.NewContent(s, kind, createPath) + // If a site isn't in use in the archetype template, we can skip the build. + siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) { + if !siteUsed { + return hugolib.NewSite(*cfg) + } + var s *hugolib.Site + if err := c.initSites(); err != nil { + return nil, err + } + + if err := Hugo.Build(hugolib.BuildCfg{SkipRender: true, PrintStats: false}); err != nil { + return nil, err + } + + s = Hugo.Sites[0] + + if len(Hugo.Sites) > 1 { + // Find the best match. + for _, ss := range Hugo.Sites { + if strings.Contains(createPath, "."+ss.Language.Lang) { + s = ss + break + } + } + } + return s, nil + } + + return create.NewContent(ps, siteFactory, kind, createPath) } func doNewSite(fs *hugofs.Fs, basepath string, force bool) error { diff --git a/create/content.go b/create/content.go index e29ea9ac8..e992b0836 100644 --- a/create/content.go +++ b/create/content.go @@ -27,15 +27,31 @@ import ( // NewContent creates a new content file in the content directory based upon the // given kind, which is used to lookup an archetype. -func NewContent(s *hugolib.Site, kind, targetPath string) error { +func NewContent( + ps *helpers.PathSpec, + siteFactory func(filename string, siteUsed bool) (*hugolib.Site, error), kind, targetPath string) error { + jww.INFO.Println("attempting to create ", targetPath, "of", kind) - archetypeFilename := findArchetype(s, kind) + archetypeFilename := findArchetype(ps, kind) - var ( - content []byte - err error - ) + f, err := ps.Fs.Source.Open(archetypeFilename) + if err != nil { + return err + } + defer f.Close() + // Building the sites can be expensive, so only do it if really needed. + siteUsed := false + if helpers.ReaderContains(f, []byte(".Site")) { + siteUsed = true + } + + s, err := siteFactory(targetPath, siteUsed) + if err != nil { + return err + } + + var content []byte content, err = executeArcheTypeAsTemplate(s, kind, targetPath, archetypeFilename) if err != nil { @@ -68,13 +84,13 @@ func NewContent(s *hugolib.Site, kind, targetPath string) error { // FindArchetype takes a given kind/archetype of content and returns an output // path for that archetype. If no archetype is found, an empty string is // returned. -func findArchetype(s *hugolib.Site, kind string) (outpath string) { - search := []string{s.PathSpec.AbsPathify(s.Cfg.GetString("archetypeDir"))} +func findArchetype(ps *helpers.PathSpec, kind string) (outpath string) { + search := []string{ps.AbsPathify(ps.Cfg.GetString("archetypeDir"))} - if s.Cfg.GetString("theme") != "" { - themeDir := filepath.Join(s.PathSpec.AbsPathify(s.Cfg.GetString("themesDir")+"/"+s.Cfg.GetString("theme")), "/archetypes/") - if _, err := s.Fs.Source.Stat(themeDir); os.IsNotExist(err) { - jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", s.Cfg.GetString("theme"), themeDir) + if ps.Cfg.GetString("theme") != "" { + themeDir := filepath.Join(ps.AbsPathify(ps.Cfg.GetString("themesDir")+"/"+ps.Cfg.GetString("theme")), "/archetypes/") + if _, err := ps.Fs.Source.Stat(themeDir); os.IsNotExist(err) { + jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", ps.Cfg.GetString("theme"), themeDir) } else { search = append(search, themeDir) } @@ -94,7 +110,7 @@ func findArchetype(s *hugolib.Site, kind string) (outpath string) { for _, p := range pathsToCheck { curpath := filepath.Join(x, p) jww.DEBUG.Println("checking", curpath, "for archetypes") - if exists, _ := helpers.Exists(curpath, s.Fs.Source); exists { + if exists, _ := helpers.Exists(curpath, ps.Fs.Source); exists { jww.INFO.Println("curpath: " + curpath) return curpath } diff --git a/create/content_template_handler.go b/create/content_template_handler.go index 9903c3dec..48e7f3b4b 100644 --- a/create/content_template_handler.go +++ b/create/content_template_handler.go @@ -18,6 +18,7 @@ import ( "fmt" "time" + "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/hugolib" @@ -25,9 +26,30 @@ import ( "github.com/spf13/afero" ) +// ArchetypeFileData represents the data available to an archetype template. +type ArchetypeFileData struct { + // The archetype content type, either given as --kind option or extracted + // from the target path's section, i.e. "blog/mypost.md" will resolve to + // "blog". + Type string + + // The current date and time as a RFC3339 formatted string, suitable for use in front matter. + Date string + + // The Site, fully equipped with all the pages etc. Note: This will only be set if it is actually + // used in the archetype template. Also, if this is a multilingual setup, + // this site is the site that best matches the target content file, based + // on the presence of language code in the filename. + Site *hugolib.Site + + // The target content file. Note that the .Content will be empty, as that + // has not been created yet. + *source.File +} + const ( archetypeTemplateTemplate = `+++ -title = "{{ replace .BaseFileName "-" " " | title }}" +title = "{{ replace .TranslationBaseName "-" " " | title }}" date = {{ .Date }} draft = true +++` @@ -44,14 +66,11 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFile sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs) f := sp.NewFile(targetPath) - data := struct { - Type string - Date string - *source.File - }{ + data := ArchetypeFileData{ Type: kind, Date: time.Now().Format(time.RFC3339), File: f, + Site: s, } if archetypeFilename == "" { @@ -67,11 +86,12 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFile // Reuse the Hugo template setup to get the template funcs properly set up. templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler) - if err := templateHandler.AddTemplate("_text/archetype", string(archetypeTemplate)); err != nil { + templateName := "_text/" + helpers.Filename(archetypeFilename) + if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil { return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err) } - templ := templateHandler.Lookup("_text/archetype") + templ := templateHandler.Lookup(templateName) var buff bytes.Buffer if err := templ.Execute(&buff, data); err != nil { diff --git a/create/content_test.go b/create/content_test.go index aa7ed3fcf..8b6c2c12c 100644 --- a/create/content_test.go +++ b/create/content_test.go @@ -52,13 +52,17 @@ func TestNewContent(t *testing.T) { for _, c := range cases { cfg, fs := newTestCfg() + ps, err := helpers.NewPathSpec(fs, cfg) + require.NoError(t, err) h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs}) require.NoError(t, err) require.NoError(t, initFs(fs)) - s := h.Sites[0] + siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) { + return h.Sites[0], nil + } - require.NoError(t, create.NewContent(s, c.kind, c.path)) + require.NoError(t, create.NewContent(ps, siteFactory, c.kind, c.path)) fname := filepath.Join("content", filepath.FromSlash(c.path)) content := readFileFromFs(t, fs.Source, fname) diff --git a/helpers/path.go b/helpers/path.go index de3de2a02..679740de9 100644 --- a/helpers/path.go +++ b/helpers/path.go @@ -215,7 +215,7 @@ func (p *PathSpec) getThemeDirPath(path string) (string, error) { } themeDir := filepath.Join(p.GetThemeDir(), path) - if _, err := p.fs.Source.Stat(themeDir); os.IsNotExist(err) { + if _, err := p.Fs.Source.Stat(themeDir); os.IsNotExist(err) { return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, p.theme, themeDir) } diff --git a/helpers/pathspec.go b/helpers/pathspec.go index bd55d8965..643d05646 100644 --- a/helpers/pathspec.go +++ b/helpers/pathspec.go @@ -52,7 +52,10 @@ type PathSpec struct { multilingual bool // The file systems to use - fs *hugofs.Fs + Fs *hugofs.Fs + + // The config provider to use + Cfg config.Provider } func (p PathSpec) String() string { @@ -70,7 +73,8 @@ func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) (*PathSpec, error) { } ps := &PathSpec{ - fs: fs, + Fs: fs, + Cfg: cfg, disablePathToLower: cfg.GetBool("disablePathToLower"), removePathAccents: cfg.GetBool("removePathAccents"), uglyURLs: cfg.GetBool("uglyURLs"),