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