content_factory.go (4989B)
1 // Copyright 2021 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 hugolib
15
16 import (
17 "fmt"
18 "io"
19 "path/filepath"
20 "strings"
21 "time"
22
23 "github.com/gohugoio/hugo/common/htime"
24 "github.com/gohugoio/hugo/helpers"
25
26 "github.com/gohugoio/hugo/source"
27
28 "github.com/gohugoio/hugo/resources/page"
29
30 "github.com/spf13/afero"
31 )
32
33 // ContentFactory creates content files from archetype templates.
34 type ContentFactory struct {
35 h *HugoSites
36
37 // We parse the archetype templates as Go templates, so we need
38 // to replace any shortcode with a temporary placeholder.
39 shortcodeReplacerPre *strings.Replacer
40 shortcodeReplacerPost *strings.Replacer
41 }
42
43 // ApplyArchetypeFilename archetypeFilename to w as a template using the given Page p as the foundation for the data context.
44 func (f ContentFactory) ApplyArchetypeFilename(w io.Writer, p page.Page, archetypeKind, archetypeFilename string) error {
45
46 fi, err := f.h.SourceFilesystems.Archetypes.Fs.Stat(archetypeFilename)
47 if err != nil {
48 return err
49 }
50
51 if fi.IsDir() {
52 return fmt.Errorf("archetype directory (%q) not supported", archetypeFilename)
53 }
54
55 templateSource, err := afero.ReadFile(f.h.SourceFilesystems.Archetypes.Fs, archetypeFilename)
56 if err != nil {
57 return fmt.Errorf("failed to read archetype file %q: %s: %w", archetypeFilename, err, err)
58
59 }
60
61 return f.ApplyArchetypeTemplate(w, p, archetypeKind, string(templateSource))
62
63 }
64
65 // ApplyArchetypeTemplate templateSource to w as a template using the given Page p as the foundation for the data context.
66 func (f ContentFactory) ApplyArchetypeTemplate(w io.Writer, p page.Page, archetypeKind, templateSource string) error {
67 ps := p.(*pageState)
68 if archetypeKind == "" {
69 archetypeKind = p.Type()
70 }
71
72 d := &archetypeFileData{
73 Type: archetypeKind,
74 Date: htime.Now().Format(time.RFC3339),
75 Page: p,
76 File: p.File(),
77 }
78
79 templateSource = f.shortcodeReplacerPre.Replace(templateSource)
80
81 templ, err := ps.s.TextTmpl().Parse("archetype.md", string(templateSource))
82 if err != nil {
83 return fmt.Errorf("failed to parse archetype template: %s: %w", err, err)
84 }
85
86 result, err := executeToString(ps.s.Tmpl(), templ, d)
87 if err != nil {
88 return fmt.Errorf("failed to execute archetype template: %s: %w", err, err)
89 }
90
91 _, err = io.WriteString(w, f.shortcodeReplacerPost.Replace(result))
92
93 return err
94
95 }
96
97 func (f ContentFactory) SectionFromFilename(filename string) (string, error) {
98 filename = filepath.Clean(filename)
99 rel, _, err := f.h.AbsProjectContentDir(filename)
100 if err != nil {
101 return "", err
102 }
103
104 parts := strings.Split(helpers.ToSlashTrimLeading(rel), "/")
105 if len(parts) < 2 {
106 return "", nil
107 }
108 return parts[0], nil
109 }
110
111 // CreateContentPlaceHolder creates a content placeholder file inside the
112 // best matching content directory.
113 func (f ContentFactory) CreateContentPlaceHolder(filename string) (string, error) {
114 filename = filepath.Clean(filename)
115 _, abs, err := f.h.AbsProjectContentDir(filename)
116
117 if err != nil {
118 return "", err
119 }
120
121 // This will be overwritten later, just write a placeholder to get
122 // the paths correct.
123 placeholder := `---
124 title: "Content Placeholder"
125 _build:
126 render: never
127 list: never
128 publishResources: false
129 ---
130
131 `
132
133 return abs, afero.SafeWriteReader(f.h.Fs.Source, abs, strings.NewReader(placeholder))
134 }
135
136 // NewContentFactory creates a new ContentFactory for h.
137 func NewContentFactory(h *HugoSites) ContentFactory {
138 return ContentFactory{
139 h: h,
140 shortcodeReplacerPre: strings.NewReplacer(
141 "{{<", "{x{<",
142 "{{%", "{x{%",
143 ">}}", ">}x}",
144 "%}}", "%}x}"),
145 shortcodeReplacerPost: strings.NewReplacer(
146 "{x{<", "{{<",
147 "{x{%", "{{%",
148 ">}x}", ">}}",
149 "%}x}", "%}}"),
150 }
151 }
152
153 // archetypeFileData represents the data available to an archetype template.
154 type archetypeFileData struct {
155 // The archetype content type, either given as --kind option or extracted
156 // from the target path's section, i.e. "blog/mypost.md" will resolve to
157 // "blog".
158 Type string
159
160 // The current date and time as a RFC3339 formatted string, suitable for use in front matter.
161 Date string
162
163 // The temporary page. Note that only the file path information is relevant at this stage.
164 Page page.Page
165
166 // File is the same as Page.File, embedded here for historic reasons.
167 // TODO(bep) make this a method.
168 source.File
169 }
170
171 func (f *archetypeFileData) Site() page.Site {
172 return f.Page.Site()
173 }
174
175 func (f *archetypeFileData) Name() string {
176 return f.Page.File().ContentBaseName()
177 }