Skip to content

Commit 422057f

Browse files
authored
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
1 parent 4aa1239 commit 422057f

File tree

5 files changed

+115
-99
lines changed

5 files changed

+115
-99
lines changed

‎commands/new.go‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ var (
4242
func init() {
4343
newSiteCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "config & frontmatter format")
4444
newSiteCmd.Flags().Bool("force", false, "init inside non-empty directory")
45-
newCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "frontmatter format")
4645
newCmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
4746
newCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
4847
newCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
@@ -98,10 +97,6 @@ func NewContent(cmd *cobra.Command, args []string) error {
9897
return err
9998
}
10099

101-
if cmd.Flags().Changed("format") {
102-
c.Set("metaDataFormat", configFormat)
103-
}
104-
105100
if cmd.Flags().Changed("editor") {
106101
c.Set("newContentEditor", contentEditor)
107102
}

‎create/content.go‎

Lines changed: 12 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,68 +19,40 @@ import (
1919
"os"
2020
"os/exec"
2121
"path/filepath"
22-
"strings"
23-
"time"
2422

2523
"github.com/gohugoio/hugo/helpers"
2624
"github.com/gohugoio/hugo/hugolib"
27-
"github.com/gohugoio/hugo/parser"
28-
"github.com/spf13/afero"
29-
"github.com/spf13/cast"
3025
jww "github.com/spf13/jwalterweatherman"
3126
)
3227

3328
// NewContent creates a new content file in the content directory based upon the
3429
// given kind, which is used to lookup an archetype.
35-
func NewContent(s *hugolib.Site, kind, name string) (err error) {
36-
jww.INFO.Println("attempting to create ", name, "of", kind)
30+
func NewContent(s *hugolib.Site, kind, targetPath string) error {
31+
jww.INFO.Println("attempting to create ", targetPath, "of", kind)
3732

38-
location := FindArchetype(s, kind)
33+
archetypeFilename := findArchetype(s, kind)
3934

40-
var by []byte
35+
var (
36+
content []byte
37+
err error
38+
)
4139

42-
if location != "" {
43-
by, err = afero.ReadFile(s.Fs.Source, location)
44-
if err != nil {
45-
jww.ERROR.Println(err)
46-
}
47-
}
48-
if location == "" || err != nil {
49-
by = []byte("+++\ndraft = true \n+++\n")
50-
}
51-
52-
psr, err := parser.ReadFrom(bytes.NewReader(by))
40+
content, err = executeArcheTypeAsTemplate(s, kind, targetPath, archetypeFilename)
5341
if err != nil {
5442
return err
5543
}
5644

57-
metadata, err := createMetadata(psr, name)
58-
if err != nil {
59-
jww.ERROR.Printf("Error processing archetype file %s: %s\n", location, err)
60-
return err
61-
}
45+
contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath))
6246

63-
page, err := s.NewPage(name)
64-
if err != nil {
47+
if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil {
6548
return err
6649
}
6750

68-
if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(s.Cfg.GetString("metaDataFormat"))); err != nil {
69-
return
70-
}
71-
72-
page.SetSourceContent(psr.Content())
73-
74-
contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), name))
75-
76-
if err = page.SafeSaveSourceAs(contentPath); err != nil {
77-
return
78-
}
7951
jww.FEEDBACK.Println(contentPath, "created")
8052

8153
editor := s.Cfg.GetString("newContentEditor")
8254
if editor != "" {
83-
jww.FEEDBACK.Printf("Editing %s with %q ...\n", name, editor)
55+
jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor)
8456

8557
cmd := exec.Command(editor, contentPath)
8658
cmd.Stdin = os.Stdin
@@ -93,59 +65,10 @@ func NewContent(s *hugolib.Site, kind, name string) (err error) {
9365
return nil
9466
}
9567

96-
// createMetadata generates Metadata for a new page based upon the metadata
97-
// found in an archetype.
98-
func createMetadata(archetype parser.Page, name string) (map[string]interface{}, error) {
99-
archMetadata, err := archetype.Metadata()
100-
if err != nil {
101-
return nil, err
102-
}
103-
104-
metadata, err := cast.ToStringMapE(archMetadata)
105-
if err != nil {
106-
return nil, err
107-
}
108-
109-
var date time.Time
110-
111-
for k, v := range metadata {
112-
if v == "" {
113-
continue
114-
}
115-
lk := strings.ToLower(k)
116-
switch lk {
117-
case "date":
118-
date, err = cast.ToTimeE(v)
119-
if err != nil {
120-
return nil, err
121-
}
122-
case "title":
123-
// Use the archetype title as is
124-
metadata[lk] = v
125-
}
126-
}
127-
128-
if metadata == nil {
129-
metadata = make(map[string]interface{})
130-
}
131-
132-
if date.IsZero() {
133-
date = time.Now()
134-
}
135-
136-
if _, ok := metadata["title"]; !ok {
137-
metadata["title"] = helpers.MakeTitle(helpers.Filename(name))
138-
}
139-
140-
metadata["date"] = date.Format(time.RFC3339)
141-
142-
return metadata, nil
143-
}
144-
14568
// FindArchetype takes a given kind/archetype of content and returns an output
14669
// path for that archetype. If no archetype is found, an empty string is
14770
// returned.
148-
func FindArchetype(s *hugolib.Site, kind string) (outpath string) {
71+
func findArchetype(s *hugolib.Site, kind string) (outpath string) {
14972
search := []string{s.PathSpec.AbsPathify(s.Cfg.GetString("archetypeDir"))}
15073

15174
if s.Cfg.GetString("theme") != "" {

‎create/content_template_handler.go‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2017 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package create
15+
16+
import (
17+
"bytes"
18+
"fmt"
19+
"time"
20+
21+
"github.com/gohugoio/hugo/source"
22+
23+
"github.com/gohugoio/hugo/hugolib"
24+
"github.com/gohugoio/hugo/tpl"
25+
"github.com/spf13/afero"
26+
)
27+
28+
const (
29+
archetypeTemplateTemplate = `+++
30+
title = "{{ replace .BaseFileName "-" " " | title }}"
31+
date = {{ .Date }}
32+
draft = true
33+
+++`
34+
)
35+
36+
func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFilename string) ([]byte, error) {
37+
38+
var (
39+
archetypeContent []byte
40+
archetypeTemplate []byte
41+
err error
42+
)
43+
44+
sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs)
45+
f := sp.NewFile(targetPath)
46+
47+
data := struct {
48+
Type string
49+
Date string
50+
*source.File
51+
}{
52+
Type: kind,
53+
Date: time.Now().Format(time.RFC3339),
54+
File: f,
55+
}
56+
57+
if archetypeFilename == "" {
58+
// TODO(bep) archetype revive the issue about wrong tpl funcs arg order
59+
archetypeTemplate = []byte(archetypeTemplateTemplate)
60+
} else {
61+
archetypeTemplate, err = afero.ReadFile(s.Fs.Source, archetypeFilename)
62+
if err != nil {
63+
return nil, fmt.Errorf("Failed to read archetype file %q: %s", archetypeFilename, err)
64+
}
65+
66+
}
67+
68+
// Reuse the Hugo template setup to get the template funcs properly set up.
69+
templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler)
70+
if err := templateHandler.AddTemplate("_text/archetype", string(archetypeTemplate)); err != nil {
71+
return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err)
72+
}
73+
74+
templ := templateHandler.Lookup("_text/archetype")
75+
76+
var buff bytes.Buffer
77+
if err := templ.Execute(&buff, data); err != nil {
78+
return nil, fmt.Errorf("Failed to process archetype file %q: %s", archetypeFilename, err)
79+
}
80+
81+
archetypeContent = buff.Bytes()
82+
83+
if !bytes.Contains(archetypeContent, []byte("date")) || !bytes.Contains(archetypeContent, []byte("title")) {
84+
// TODO(bep) remove some time in the future.
85+
s.Log.FEEDBACK.Println(fmt.Sprintf(`WARNING: date and/or title missing from archetype file %q.
86+
From Hugo 0.24 this must be provided in the archetype file itself, if needed. Example:
87+
%s
88+
`, archetypeFilename, archetypeTemplateTemplate))
89+
90+
}
91+
92+
return archetypeContent, nil
93+
94+
}

‎create/content_test.go‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func TestNewContent(t *testing.T) {
4545
}{
4646
{"post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
4747
{"emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}},
48-
{"stump", "stump/sample-2.md", []string{`title = "sample 2"`}}, // no archetype file
49-
{"", "sample-3.md", []string{`title = "sample 3"`}}, // no archetype
50-
{"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter
48+
{"stump", "stump/sample-2.md", []string{`title = "Sample 2"`}}, // no archetype file
49+
{"", "sample-3.md", []string{`title = "Sample 3"`}}, // no archetype
50+
{"product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter
5151
}
5252

5353
for _, c := range cases {
@@ -108,8 +108,10 @@ func initFs(fs *hugofs.Fs) error {
108108
content: "+++\ndate = \"2015-01-12T19:20:04-07:00\"\ntitle = \"Post Arch title\"\ntest = \"test1\"\n+++\n",
109109
},
110110
{
111-
path: filepath.Join("archetypes", "product.md"),
112-
content: "+++\n+++\n",
111+
path: filepath.Join("archetypes", "product.md"),
112+
content: `+++
113+
title = "{{ .BaseFileName | upper }}"
114+
+++`,
113115
},
114116
{
115117
path: filepath.Join("archetypes", "emptydate.md"),

‎parser/frontmatter.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
package parser
1515

16+
// TODO(bep) archetype remove unused from this package.
17+
1618
import (
1719
"bytes"
1820
"encoding/json"

0 commit comments

Comments
 (0)