mirror of
https://github.com/gohugoio/hugo.git
synced 2024-12-28 02:10:50 +00:00
347 lines
8.1 KiB
Go
347 lines
8.1 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"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/gohugoio/hugo/common/hexec"
|
|
"github.com/gohugoio/hugo/hugofs/files"
|
|
|
|
"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, err := hugofs.NewLanguageFs(sites.LanguageSet(), archetypeFs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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, 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)
|
|
|
|
editorCmd := append(strings.Fields(editor), contentPath)
|
|
cmd, err := hexec.SafeCommand(editorCmd[0], editorCmd[1:]...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func targetSite(sites *hugolib.HugoSites, fi hugofs.FileMetaInfo) *hugolib.Site {
|
|
for _, s := range sites.Sites {
|
|
if fi.Meta().Lang() == s.Language().Lang {
|
|
return s
|
|
}
|
|
}
|
|
return sites.Sites[0]
|
|
}
|
|
|
|
func newContentFromDir(
|
|
archetypeDir string,
|
|
sites *hugolib.HugoSites,
|
|
targetFs afero.Fs,
|
|
cm archetypeMap, name, targetPath string) error {
|
|
for _, f := range cm.otherFiles {
|
|
meta := f.Meta()
|
|
filename := meta.Path()
|
|
// Just copy the file to destination.
|
|
in, err := meta.Open()
|
|
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.Meta().Path()
|
|
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.FileMetaInfo
|
|
// These are just copied to destination.
|
|
otherFiles []hugofs.FileMetaInfo
|
|
// 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(path string, fi hugofs.FileMetaInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
fil := fi.(hugofs.FileMetaInfo)
|
|
|
|
if files.IsContentFile(path) {
|
|
m.contentFiles = append(m.contentFiles, fil)
|
|
if !m.siteUsed {
|
|
m.siteUsed, err = usesSiteVar(fs, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.otherFiles = append(m.otherFiles, fil)
|
|
|
|
return nil
|
|
}
|
|
|
|
walkCfg := hugofs.WalkwayConfig{
|
|
WalkFn: walkFn,
|
|
Fs: fs,
|
|
Root: archetypeDir,
|
|
}
|
|
|
|
w := hugofs.NewWalkway(walkCfg)
|
|
|
|
if err := w.Walk(); 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
|
|
}
|
|
}
|
|
|
|
var dirLang string
|
|
|
|
for _, dir := range sites.BaseFs.Content.Dirs {
|
|
meta := dir.Meta()
|
|
contentDir := meta.Filename()
|
|
|
|
if !strings.HasSuffix(contentDir, helpers.FilePathSeparator) {
|
|
contentDir += helpers.FilePathSeparator
|
|
}
|
|
|
|
if strings.HasPrefix(targetPath, contentDir) {
|
|
siteContentDir = contentDir
|
|
dirLang = meta.Lang()
|
|
break
|
|
}
|
|
}
|
|
|
|
if s == nil && dirLang != "" {
|
|
for _, ss := range sites.Sites {
|
|
if ss.Lang() == dirLang {
|
|
s = ss
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if s == nil {
|
|
s = first
|
|
}
|
|
|
|
if targetDir != "" && targetDir != "." {
|
|
exists, _ := helpers.Exists(targetDir, fs)
|
|
|
|
if exists {
|
|
return targetPath, s
|
|
}
|
|
}
|
|
|
|
if siteContentDir == "" {
|
|
}
|
|
|
|
if siteContentDir != "" {
|
|
pp := filepath.Join(siteContentDir, strings.TrimPrefix(targetPath, siteContentDir))
|
|
return s.PathSpec.AbsPathify(pp), s
|
|
} else {
|
|
var contentDir string
|
|
for _, dir := range sites.BaseFs.Content.Dirs {
|
|
contentDir = dir.Meta().Filename()
|
|
if dir.Meta().Lang() == s.Lang() {
|
|
break
|
|
}
|
|
}
|
|
return s.PathSpec.AbsPathify(filepath.Join(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
|
|
}
|