github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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  }