From 422057f60709696bbbd1c38c9ead2bf114d47e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 18 Jun 2017 19:06:28 +0200 Subject: [PATCH] create: Use archetype template as-is as a Go template This commit removes the fragile front matter decoding, and takes the provided archetype file as-is and processes it as a template. This also means that we no longer will attempt to fill in default values for `title` and `date`. The upside is that it is now easy to create these values in a dynamic way: ```toml +++ title = {{ .BaseFileName | title }} date = {{ .Date }} draft = true +++ ``` You can currently use all of Hugo's template funcs, but the data context is currently very shallow: * `.Type` gives the archetype kind provided * `.Name` gives the target file name without extension. * `.Path` gives the target file name * `.Date` gives the current time as RFC3339 formatted string The above will probably be extended in #1629. Fixes #452 Updates #1629 --- commands/new.go | 5 -- create/content.go | 103 ++++------------------------- create/content_template_handler.go | 94 ++++++++++++++++++++++++++ create/content_test.go | 12 ++-- parser/frontmatter.go | 2 + 5 files changed, 116 insertions(+), 100 deletions(-) create mode 100644 create/content_template_handler.go diff --git a/commands/new.go b/commands/new.go index 7b39cb9e..4288b6b0 100644 --- a/commands/new.go +++ b/commands/new.go @@ -42,7 +42,6 @@ var ( func init() { newSiteCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "config & frontmatter format") newSiteCmd.Flags().Bool("force", false, "init inside non-empty directory") - newCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "frontmatter format") newCmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create") newCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") newCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) @@ -98,10 +97,6 @@ func NewContent(cmd *cobra.Command, args []string) error { return err } - if cmd.Flags().Changed("format") { - c.Set("metaDataFormat", configFormat) - } - if cmd.Flags().Changed("editor") { c.Set("newContentEditor", contentEditor) } diff --git a/create/content.go b/create/content.go index a6222717..e29ea9ac 100644 --- a/create/content.go +++ b/create/content.go @@ -19,68 +19,40 @@ import ( "os" "os/exec" "path/filepath" - "strings" - "time" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/parser" - "github.com/spf13/afero" - "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" ) // 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, name string) (err error) { - jww.INFO.Println("attempting to create ", name, "of", kind) +func NewContent(s *hugolib.Site, kind, targetPath string) error { + jww.INFO.Println("attempting to create ", targetPath, "of", kind) - location := FindArchetype(s, kind) + archetypeFilename := findArchetype(s, kind) - var by []byte + var ( + content []byte + err error + ) - if location != "" { - by, err = afero.ReadFile(s.Fs.Source, location) - if err != nil { - jww.ERROR.Println(err) - } - } - if location == "" || err != nil { - by = []byte("+++\ndraft = true \n+++\n") - } - - psr, err := parser.ReadFrom(bytes.NewReader(by)) + content, err = executeArcheTypeAsTemplate(s, kind, targetPath, archetypeFilename) if err != nil { return err } - metadata, err := createMetadata(psr, name) - if err != nil { - jww.ERROR.Printf("Error processing archetype file %s: %s\n", location, err) + contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath)) + + if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil { return err } - page, err := s.NewPage(name) - if err != nil { - return err - } - - if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(s.Cfg.GetString("metaDataFormat"))); err != nil { - return - } - - page.SetSourceContent(psr.Content()) - - contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), name)) - - if err = page.SafeSaveSourceAs(contentPath); err != nil { - return - } jww.FEEDBACK.Println(contentPath, "created") editor := s.Cfg.GetString("newContentEditor") if editor != "" { - jww.FEEDBACK.Printf("Editing %s with %q ...\n", name, editor) + jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor) cmd := exec.Command(editor, contentPath) cmd.Stdin = os.Stdin @@ -93,59 +65,10 @@ func NewContent(s *hugolib.Site, kind, name string) (err error) { return nil } -// createMetadata generates Metadata for a new page based upon the metadata -// found in an archetype. -func createMetadata(archetype parser.Page, name string) (map[string]interface{}, error) { - archMetadata, err := archetype.Metadata() - if err != nil { - return nil, err - } - - metadata, err := cast.ToStringMapE(archMetadata) - if err != nil { - return nil, err - } - - var date time.Time - - for k, v := range metadata { - if v == "" { - continue - } - lk := strings.ToLower(k) - switch lk { - case "date": - date, err = cast.ToTimeE(v) - if err != nil { - return nil, err - } - case "title": - // Use the archetype title as is - metadata[lk] = v - } - } - - if metadata == nil { - metadata = make(map[string]interface{}) - } - - if date.IsZero() { - date = time.Now() - } - - if _, ok := metadata["title"]; !ok { - metadata["title"] = helpers.MakeTitle(helpers.Filename(name)) - } - - metadata["date"] = date.Format(time.RFC3339) - - return metadata, nil -} - // 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) { +func findArchetype(s *hugolib.Site, kind string) (outpath string) { search := []string{s.PathSpec.AbsPathify(s.Cfg.GetString("archetypeDir"))} if s.Cfg.GetString("theme") != "" { diff --git a/create/content_template_handler.go b/create/content_template_handler.go new file mode 100644 index 00000000..9903c3de --- /dev/null +++ b/create/content_template_handler.go @@ -0,0 +1,94 @@ +// Copyright 2017 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package create + +import ( + "bytes" + "fmt" + "time" + + "github.com/gohugoio/hugo/source" + + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/tpl" + "github.com/spf13/afero" +) + +const ( + archetypeTemplateTemplate = `+++ +title = "{{ replace .BaseFileName "-" " " | title }}" +date = {{ .Date }} +draft = true ++++` +) + +func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFilename string) ([]byte, error) { + + var ( + archetypeContent []byte + archetypeTemplate []byte + err error + ) + + sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs) + f := sp.NewFile(targetPath) + + data := struct { + Type string + Date string + *source.File + }{ + Type: kind, + Date: time.Now().Format(time.RFC3339), + File: f, + } + + if archetypeFilename == "" { + // TODO(bep) archetype revive the issue about wrong tpl funcs arg order + archetypeTemplate = []byte(archetypeTemplateTemplate) + } else { + archetypeTemplate, err = afero.ReadFile(s.Fs.Source, archetypeFilename) + if err != nil { + return nil, fmt.Errorf("Failed to read archetype file %q: %s", archetypeFilename, err) + } + + } + + // 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 { + return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err) + } + + templ := templateHandler.Lookup("_text/archetype") + + var buff bytes.Buffer + if err := templ.Execute(&buff, data); err != nil { + return nil, fmt.Errorf("Failed to process archetype file %q: %s", archetypeFilename, err) + } + + archetypeContent = buff.Bytes() + + if !bytes.Contains(archetypeContent, []byte("date")) || !bytes.Contains(archetypeContent, []byte("title")) { + // TODO(bep) remove some time in the future. + s.Log.FEEDBACK.Println(fmt.Sprintf(`WARNING: date and/or title missing from archetype file %q. +From Hugo 0.24 this must be provided in the archetype file itself, if needed. Example: +%s +`, archetypeFilename, archetypeTemplateTemplate)) + + } + + return archetypeContent, nil + +} diff --git a/create/content_test.go b/create/content_test.go index 8eaaf7bf..aa7ed3fc 100644 --- a/create/content_test.go +++ b/create/content_test.go @@ -45,9 +45,9 @@ func TestNewContent(t *testing.T) { }{ {"post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}}, {"emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}}, - {"stump", "stump/sample-2.md", []string{`title = "sample 2"`}}, // no archetype file - {"", "sample-3.md", []string{`title = "sample 3"`}}, // no archetype - {"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter + {"stump", "stump/sample-2.md", []string{`title = "Sample 2"`}}, // no archetype file + {"", "sample-3.md", []string{`title = "Sample 3"`}}, // no archetype + {"product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter } for _, c := range cases { @@ -108,8 +108,10 @@ func initFs(fs *hugofs.Fs) error { content: "+++\ndate = \"2015-01-12T19:20:04-07:00\"\ntitle = \"Post Arch title\"\ntest = \"test1\"\n+++\n", }, { - path: filepath.Join("archetypes", "product.md"), - content: "+++\n+++\n", + path: filepath.Join("archetypes", "product.md"), + content: `+++ +title = "{{ .BaseFileName | upper }}" ++++`, }, { path: filepath.Join("archetypes", "emptydate.md"), diff --git a/parser/frontmatter.go b/parser/frontmatter.go index 627d5ef4..ab56b14d 100644 --- a/parser/frontmatter.go +++ b/parser/frontmatter.go @@ -13,6 +13,8 @@ package parser +// TODO(bep) archetype remove unused from this package. + import ( "bytes" "encoding/json"