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