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