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 }