// Copyright 2018 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 filesystems provides the fine grained file systems used by Hugo. These
// are typically virtual filesystems that are composites of project and theme content.
package filesystems
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
"github.com/rogpeppe/go-internal/lockedfile"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/modules"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/spf13/afero"
)
const (
// Used to control concurrency between multiple Hugo instances, e.g.
// a running server and building new content with 'hugo new'.
// It's placed in the project root.
lockFileBuild = ".hugo_build.lock"
)
var filePathSeparator = string(filepath.Separator)
// BaseFs contains the core base filesystems used by Hugo. The name "base" is used
// to underline that even if they can be composites, they all have a base path set to a specific
// resource folder, e.g "/my-project/content". So, no absolute filenames needed.
type BaseFs struct {
// SourceFilesystems contains the different source file systems.
*SourceFilesystems
// The project source.
SourceFs afero.Fs
// The filesystem used to publish the rendered site.
// This usually maps to /my-project/public.
PublishFs afero.Fs
// The filesystem used for static files.
PublishFsStatic afero.Fs
// A read-only filesystem starting from the project workDir.
WorkDir afero.Fs
theBigFs *filesystemsCollector
workingDir string
// Locks.
buildMu Lockable // <project>/.hugo_build.lock
}
type Lockable interface {
Lock() (unlock func(), err error)
}
type fakeLockfileMutex struct {
mu sync.Mutex
}
func (f *fakeLockfileMutex) Lock() (func(), error) {
f.mu.Lock()
return func() { f.mu.Unlock() }, nil
}
// Tries to acquire a build lock.
func (fs *BaseFs) LockBuild() (unlock func(), err error) {
return fs.buildMu.Lock()
}
// TODO(bep) we can get regular files in here and that is fine, but
// we need to clean up the naming.
func (fs *BaseFs) WatchDirs() []hugofs.FileMetaInfo {
var dirs []hugofs.FileMetaInfo
for _, dir := range fs.AllDirs() {
if dir.Meta().Watch {
dirs = append(dirs, dir)
}
}
return dirs
}
func (fs *BaseFs) AllDirs() []hugofs.FileMetaInfo {
var dirs []hugofs.FileMetaInfo
for _, dirSet := range [][]hugofs.FileMetaInfo{
fs.Archetypes.Dirs,
fs.I18n.Dirs,
fs.Data.Dirs,
fs.Content.Dirs,
fs.Assets.Dirs,
fs.Layouts.Dirs,
// fs.Resources.Dirs,
fs.StaticDirs,
} {
dirs = append(dirs, dirSet...)
}
return dirs
}
// RelContentDir tries to create a path relative to the content root from
// the given filename. The return value is the path and language code.
func (b *BaseFs) RelContentDir(filename string) string {
for _, dir := range b.SourceFilesystems.Content.Dirs {
dirname := dir.Meta().Filename
if strings.HasPrefix(filename