// Copyright 2021 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hugolib import ( "io" "path/filepath" "strings" "time" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/resources/page" "github.com/pkg/errors" "github.com/spf13/afero" ) // ContentFactory creates content files from archetype templates. type ContentFactory struct { h *HugoSites // We parse the archetype templates as Go templates, so we need // to replace any shortcode with a temporary placeholder. shortocdeReplacerPre *strings.Replacer shortocdeReplacerPost *strings.Replacer } // AppplyArchetypeFilename archetypeFilename to w as a template using the given Page p as the foundation for the data context. func (f ContentFactory) AppplyArchetypeFilename(w io.Writer, p page.Page, archetypeKind, archetypeFilename string) error { fi, err := f.h.SourceFilesystems.Archetypes.Fs.Stat(archetypeFilename) if err != nil { return err } if fi.IsDir() { return errors.Errorf("archetype directory (%q) not supported", archetypeFilename) } templateSource, err := afero.ReadFile(f.h.SourceFilesystems.Archetypes.Fs, archetypeFilename) if err != nil { return errors.Wrapf(err, "failed to read archetype file %q: %s", archetypeFilename, err) } return f.AppplyArchetypeTemplate(w, p, archetypeKind, string(templateSource)) } // AppplyArchetypeFilename templateSource to w as a template using the given Page p as the foundation for the data context. func (f ContentFactory) AppplyArchetypeTemplate(w io.Writer, p page.Page, archetypeKind, templateSource string) error { ps := p.(*pageState) if archetypeKind == "" { archetypeKind = p.Type() } d := &archetypeFileData{ Type: archetypeKind, Date: time.Now().Format(time.RFC3339), Page: p, File: p.File(), } templateSource = f.shortocdeReplacerPre.Replace(templateSource) templ, err := ps.s.TextTmpl().Parse("archetype.md", string(templateSource)) if err != nil { return errors.Wrapf(err, "failed to parse archetype template: %s", err) } result, err := executeToString(ps.s.Tmpl(), templ, d) if err != nil { return errors.Wrapf(err, "failed to execute archetype template: %s", err) } _, err = io.WriteString(w, f.shortocdeReplacerPost.Replace(result)) return err } func (f ContentFactory) SectionFromFilename(filename string) string { filename = filepath.Clean(filename) rel, _ := f.h.AbsProjectContentDir(filename) if rel == "" { return "" } parts := strings.Split(helpers.ToSlashTrimLeading(rel), "/") if len(parts) < 2 { return "" } return parts[0] } // CreateContentPlaceHolder creates a content placeholder file inside the // best matching content directory. func (f ContentFactory) CreateContentPlaceHolder(filename string) (string, error) { filename = filepath.Clean(filename) _, abs := f.h.AbsProjectContentDir(filename) contentDir := filepath.Dir(abs) if err := f.h.Fs.Source.MkdirAll(contentDir, 0777); err != nil { return "", err } // This will be overwritten later, just write a placholder to get // the paths correct. placeholder := `--- title: "Content Placeholder" _build: render: never list: never publishResources: false --- ` if err := afero.WriteFile(f.h.Fs.Source, abs, []byte(placeholder), 0777); err != nil { return "", err } return abs, nil } // NewContentFactory creates a new ContentFactory for h. func NewContentFactory(h *HugoSites) ContentFactory { return ContentFactory{ h: h, shortocdeReplacerPre: strings.NewReplacer( "{{<", "{x{<", "{{%", "{x{%", ">}}", ">}x}", "%}}", "%}x}"), shortocdeReplacerPost: strings.NewReplacer( "{x{<", "{{<", "{x{%", "{{%", ">}x}", ">}}", "%}x}", "%}}"), } } // archetypeFileData represents the data available to an archetype template. type archetypeFileData struct { // The archetype content type, either given as --kind option or extracted // from the target path's section, i.e. "blog/mypost.md" will resolve to // "blog". Type string // The current date and time as a RFC3339 formatted string, suitable for use in front matter. Date string // The temporary page. Note that only the file path information is relevant at this stage. Page page.Page // File is the same as Page.File, embedded here for historic reasons. // TODO(bep) make this a method. source.File } func (f *archetypeFileData) Site() page.Site { return f.Page.Site() } func (f *archetypeFileData) Name() string { return f.Page.File().ContentBaseName() }