mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-29 19:42:10 -05:00
313 lines
7.5 KiB
Go
313 lines
7.5 KiB
Go
// Copyright 2019 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 create provides functions to create new content.
|
|
package create
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gohugoio/hugo/hugofs"
|
|
|
|
"github.com/gohugoio/hugo/helpers"
|
|
"github.com/gohugoio/hugo/hugolib"
|
|
"github.com/spf13/afero"
|
|
jww "github.com/spf13/jwalterweatherman"
|
|
)
|
|
|
|
// NewContent creates a new content file in the content directory based upon the
|
|
// given kind, which is used to lookup an archetype.
|
|
func NewContent(
|
|
sites *hugolib.HugoSites, kind, targetPath string) error {
|
|
targetPath = filepath.Clean(targetPath)
|
|
ext := helpers.Ext(targetPath)
|
|
ps := sites.PathSpec
|
|
archetypeFs := ps.BaseFs.SourceFilesystems.Archetypes.Fs
|
|
sourceFs := ps.Fs.Source
|
|
|
|
jww.INFO.Printf("attempting to create %q of %q of ext %q", targetPath, kind, ext)
|
|
|
|
archetypeFilename, isDir := findArchetype(ps, kind, ext)
|
|
contentPath, s := resolveContentPath(sites, sourceFs, targetPath)
|
|
|
|
if isDir {
|
|
|
|
langFs := hugofs.NewLanguageFs(s.Language().Lang, sites.LanguageSet(), archetypeFs)
|
|
|
|
cm, err := mapArcheTypeDir(ps, langFs, archetypeFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cm.siteUsed {
|
|
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
name := filepath.Base(targetPath)
|
|
return newContentFromDir(archetypeFilename, sites, archetypeFs, sourceFs, cm, name, contentPath)
|
|
}
|
|
|
|
// Building the sites can be expensive, so only do it if really needed.
|
|
siteUsed := false
|
|
|
|
if archetypeFilename != "" {
|
|
|
|
var err error
|
|
siteUsed, err = usesSiteVar(archetypeFs, archetypeFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if siteUsed {
|
|
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
content, err := executeArcheTypeAsTemplate(s, "", kind, targetPath, archetypeFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil {
|
|
return err
|
|
}
|
|
|
|
jww.FEEDBACK.Println(contentPath, "created")
|
|
|
|
editor := s.Cfg.GetString("newContentEditor")
|
|
if editor != "" {
|
|
jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor)
|
|
|
|
cmd := exec.Command(editor, contentPath)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func targetSite(sites *hugolib.HugoSites, fi *hugofs.LanguageFileInfo) *hugolib.Site {
|
|
for _, s := range sites.Sites {
|
|
if fi.Lang() == s.Language().Lang {
|
|
return s
|
|
}
|
|
}
|
|
return sites.Sites[0]
|
|
}
|
|
|
|
func newContentFromDir(
|
|
archetypeDir string,
|
|
sites *hugolib.HugoSites,
|
|
sourceFs, targetFs afero.Fs,
|
|
cm archetypeMap, name, targetPath string) error {
|
|
|
|
for _, f := range cm.otherFiles {
|
|
filename := f.Filename()
|
|
// Just copy the file to destination.
|
|
in, err := sourceFs.Open(filename)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to open non-content file")
|
|
}
|
|
|
|
targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir))
|
|
|
|
targetDir := filepath.Dir(targetFilename)
|
|
if err := targetFs.MkdirAll(targetDir, 0777); err != nil && !os.IsExist(err) {
|
|
return errors.Wrapf(err, "failed to create target directory for %s:", targetDir)
|
|
}
|
|
|
|
out, err := targetFs.Create(targetFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(out, in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
in.Close()
|
|
out.Close()
|
|
}
|
|
|
|
for _, f := range cm.contentFiles {
|
|
filename := f.Filename()
|
|
s := targetSite(sites, f)
|
|
targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir))
|
|
|
|
content, err := executeArcheTypeAsTemplate(s, name, archetypeDir, targetFilename, filename)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to execute archetype template")
|
|
}
|
|
|
|
if err := helpers.SafeWriteToDisk(targetFilename, bytes.NewReader(content), targetFs); err != nil {
|
|
return errors.Wrap(err, "failed to save results")
|
|
}
|
|
}
|
|
|
|
jww.FEEDBACK.Println(targetPath, "created")
|
|
|
|
return nil
|
|
}
|
|
|
|
type archetypeMap struct {
|
|
// These needs to be parsed and executed as Go templates.
|
|
contentFiles []*hugofs.LanguageFileInfo
|
|
// These are just copied to destination.
|
|
otherFiles []*hugofs.LanguageFileInfo
|
|
// If the templates needs a fully built site. This can potentially be
|
|
// expensive, so only do when needed.
|
|
siteUsed bool
|
|
}
|
|
|
|
func mapArcheTypeDir(
|
|
ps *helpers.PathSpec,
|
|
fs afero.Fs,
|
|
archetypeDir string) (archetypeMap, error) {
|
|
|
|
var m archetypeMap
|
|
|
|
walkFn := func(filename string, fi os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
fil := fi.(*hugofs.LanguageFileInfo)
|
|
|
|
if hugolib.IsContentFile(filename) {
|
|
m.contentFiles = append(m.contentFiles, fil)
|
|
if !m.siteUsed {
|
|
m.siteUsed, err = usesSiteVar(fs, filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.otherFiles = append(m.otherFiles, fil)
|
|
|
|
return nil
|
|
}
|
|
|
|
if err := helpers.SymbolicWalk(fs, archetypeDir, walkFn); err != nil {
|
|
return m, errors.Wrapf(err, "failed to walk archetype dir %q", archetypeDir)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func usesSiteVar(fs afero.Fs, filename string) (bool, error) {
|
|
f, err := fs.Open(filename)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to open archetype file")
|
|
}
|
|
defer f.Close()
|
|
return helpers.ReaderContains(f, []byte(".Site")), nil
|
|
}
|
|
|
|
// Resolve the target content path.
|
|
func resolveContentPath(sites *hugolib.HugoSites, fs afero.Fs, targetPath string) (string, *hugolib.Site) {
|
|
targetDir := filepath.Dir(targetPath)
|
|
first := sites.Sites[0]
|
|
|
|
var (
|
|
s *hugolib.Site
|
|
siteContentDir string
|
|
)
|
|
|
|
// Try the filename: my-post.en.md
|
|
for _, ss := range sites.Sites {
|
|
if strings.Contains(targetPath, "."+ss.Language().Lang+".") {
|
|
s = ss
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, ss := range sites.Sites {
|
|
contentDir := ss.PathSpec.ContentDir
|
|
if !strings.HasSuffix(contentDir, helpers.FilePathSeparator) {
|
|
contentDir += helpers.FilePathSeparator
|
|
}
|
|
if strings.HasPrefix(targetPath, contentDir) {
|
|
siteContentDir = ss.PathSpec.ContentDir
|
|
if s == nil {
|
|
s = ss
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if s == nil {
|
|
s = first
|
|
}
|
|
|
|
if targetDir != "" && targetDir != "." {
|
|
exists, _ := helpers.Exists(targetDir, fs)
|
|
|
|
if exists {
|
|
return targetPath, s
|
|
}
|
|
}
|
|
|
|
if siteContentDir != "" {
|
|
pp := filepath.Join(siteContentDir, strings.TrimPrefix(targetPath, siteContentDir))
|
|
return s.PathSpec.AbsPathify(pp), s
|
|
|
|
} else {
|
|
return s.PathSpec.AbsPathify(filepath.Join(first.PathSpec.ContentDir, targetPath)), s
|
|
}
|
|
|
|
}
|
|
|
|
// FindArchetype takes a given kind/archetype of content and returns the path
|
|
// to the archetype in the archetype filesystem, blank if none found.
|
|
func findArchetype(ps *helpers.PathSpec, kind, ext string) (outpath string, isDir bool) {
|
|
fs := ps.BaseFs.Archetypes.Fs
|
|
|
|
var pathsToCheck []string
|
|
|
|
if kind != "" {
|
|
pathsToCheck = append(pathsToCheck, kind+ext)
|
|
}
|
|
pathsToCheck = append(pathsToCheck, "default"+ext, "default")
|
|
|
|
for _, p := range pathsToCheck {
|
|
fi, err := fs.Stat(p)
|
|
if err == nil {
|
|
return p, fi.IsDir()
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|