summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock6
-rw-r--r--Gopkg.toml2
-rw-r--r--commands/hugo.go8
-rw-r--r--common/types/types.go6
-rw-r--r--create/content.go17
-rw-r--r--create/content_template_handler.go7
-rw-r--r--create/content_test.go2
-rw-r--r--deps/deps.go2
-rw-r--r--helpers/language.go16
-rw-r--r--helpers/language_test.go6
-rw-r--r--helpers/path.go24
-rw-r--r--helpers/path_test.go7
-rw-r--r--helpers/pathspec.go192
-rw-r--r--helpers/pathspec_test.go1
-rw-r--r--helpers/testhelpers_test.go1
-rw-r--r--helpers/url_test.go4
-rw-r--r--hugofs/base_fs.go35
-rw-r--r--hugofs/language_composite_fs.go51
-rw-r--r--hugofs/language_composite_fs_test.go106
-rw-r--r--hugofs/language_fs.go328
-rw-r--r--hugofs/language_fs_test.go54
-rw-r--r--hugolib/config.go8
-rw-r--r--hugolib/disableKinds_test.go4
-rw-r--r--hugolib/fileInfo.go19
-rw-r--r--hugolib/fileInfo_test.go61
-rw-r--r--hugolib/hugo_sites.go78
-rw-r--r--hugolib/hugo_sites_build_test.go52
-rw-r--r--hugolib/language_content_dir_test.go253
-rw-r--r--hugolib/menu_test.go4
-rw-r--r--hugolib/multilingual.go4
-rw-r--r--hugolib/page.go22
-rw-r--r--hugolib/page_bundler.go28
-rw-r--r--hugolib/page_bundler_capture.go185
-rw-r--r--hugolib/page_bundler_capture_test.go53
-rw-r--r--hugolib/page_bundler_handlers.go9
-rw-r--r--hugolib/page_bundler_test.go42
-rw-r--r--hugolib/page_collections.go5
-rw-r--r--hugolib/page_test.go54
-rw-r--r--hugolib/prune_resources.go10
-rw-r--r--hugolib/site.go21
-rw-r--r--hugolib/site_url_test.go2
-rw-r--r--hugolib/taxonomy_test.go4
-rw-r--r--hugolib/testhelpers_test.go23
-rw-r--r--i18n/i18n_test.go1
-rw-r--r--i18n/translationProvider.go2
-rw-r--r--resource/image.go41
-rw-r--r--resource/image_cache.go19
-rw-r--r--resource/image_test.go29
-rw-r--r--resource/resource.go84
-rw-r--r--resource/resource_test.go76
-rw-r--r--resource/testhelpers_test.go45
-rw-r--r--source/content_directory_test.go11
-rw-r--r--source/dirs_test.go3
-rw-r--r--source/fileInfo.go69
-rw-r--r--source/fileInfo_test.go44
-rw-r--r--source/filesystem.go6
-rw-r--r--source/filesystem_test.go6
-rw-r--r--source/sourceSpec.go27
-rw-r--r--tpl/collections/collections_test.go8
-rw-r--r--tpl/data/data_test.go5
-rw-r--r--tpl/data/resources_test.go8
-rw-r--r--tpl/os/os.go25
-rw-r--r--tpl/tplimpl/template_funcs_test.go6
-rw-r--r--tpl/tplimpl/template_test.go1
-rw-r--r--tpl/transform/remarshal_test.go13
-rw-r--r--tpl/transform/transform_test.go28
66 files changed, 1818 insertions, 555 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 29f233299..6b96deb67 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -293,8 +293,8 @@
".",
"mem"
]
- revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c"
- version = "v1.0.2"
+ revision = "63644898a8da0bc22138abf860edaf5277b6102e"
+ version = "v1.1.0"
[[projects]]
name = "github.com/spf13/cast"
@@ -424,6 +424,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "13ab39f8bfafadc12c05726e565ee3f3d94bf7d6c0e8adf04056de0691bf2dd6"
+ inputs-digest = "edb250b53926de21df1740c379c76351b7e9b110c96a77078a10ba69bf31a2d4"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index fc1af824b..e14d6dc00 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -78,7 +78,7 @@
[[constraint]]
name = "github.com/spf13/afero"
- version = "^1.0.1"
+ version = "^1.1.0"
[[constraint]]
name = "github.com/spf13/cast"
diff --git a/commands/hugo.go b/commands/hugo.go
index a5b2c8895..ba8c0aef4 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -705,7 +705,7 @@ func (c *commandeer) getDirList() ([]string, error) {
c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
return nil
}
- linkfi, err := helpers.LstatIfOs(c.Fs.Source, link)
+ linkfi, err := helpers.LstatIfPossible(c.Fs.Source, link)
if err != nil {
c.Logger.ERROR.Printf("Cannot stat %q: %s", link, err)
return nil
@@ -743,9 +743,13 @@ func (c *commandeer) getDirList() ([]string, error) {
// SymbolicWalk will log anny ERRORs
_ = helpers.SymbolicWalk(c.Fs.Source, dataDir, regularWalker)
- _ = helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")), symLinkWalker)
_ = helpers.SymbolicWalk(c.Fs.Source, i18nDir, regularWalker)
_ = helpers.SymbolicWalk(c.Fs.Source, layoutDir, regularWalker)
+
+ for _, contentDir := range c.PathSpec().ContentDirs() {
+ _ = helpers.SymbolicWalk(c.Fs.Source, contentDir.Value, symLinkWalker)
+ }
+
for _, staticDir := range staticDirs {
_ = helpers.SymbolicWalk(c.Fs.Source, staticDir, regularWalker)
}
diff --git a/common/types/types.go b/common/types/types.go
index 291bf6cf3..a5805d07a 100644
--- a/common/types/types.go
+++ b/common/types/types.go
@@ -20,6 +20,12 @@ import (
"github.com/spf13/cast"
)
+// KeyValueStr is a string tuple.
+type KeyValueStr struct {
+ Key string
+ Value string
+}
+
// KeyValues holds an key and a slice of values.
type KeyValues struct {
Key interface{}
diff --git a/create/content.go b/create/content.go
index 8af417294..29fe47394 100644
--- a/create/content.go
+++ b/create/content.go
@@ -63,7 +63,22 @@ func NewContent(
return err
}
- contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath))
+ // The site may have multiple content dirs, and we currently do not know which contentDir the
+ // user wants to create this content in. We should improve on this, but we start by testing if the
+ // provided path points to an existing dir. If so, use it as is.
+ var contentPath string
+ var exists bool
+ targetDir := filepath.Dir(targetPath)
+
+ if targetDir != "" && targetDir != "." {
+ exists, _ = helpers.Exists(targetDir, ps.Fs.Source)
+ }
+
+ if exists {
+ contentPath = targetPath
+ } else {
+ contentPath = s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath))
+ }
if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil {
return err
diff --git a/create/content_template_handler.go b/create/content_template_handler.go
index 705efbd20..e9e7cb62b 100644
--- a/create/content_template_handler.go
+++ b/create/content_template_handler.go
@@ -88,10 +88,15 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFile
err error
)
- sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs)
+ ps, err := helpers.NewPathSpec(s.Deps.Fs, s.Deps.Cfg)
+ sp := source.NewSourceSpec(ps, ps.Fs.Source)
+ if err != nil {
+ return nil, err
+ }
f := sp.NewFileInfo("", targetPath, false, nil)
name := f.TranslationBaseName()
+
if name == "index" || name == "_index" {
// Page bundles; the directory name will hopefully have a better name.
dir := strings.TrimSuffix(f.Dir(), helpers.FilePathSeparator)
diff --git a/create/content_test.go b/create/content_test.go
index 914759164..62d5ed1da 100644
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -75,7 +75,7 @@ func TestNewContent(t *testing.T) {
for i, v := range c.expected {
found := strings.Contains(content, v)
if !found {
- t.Errorf("[%d] %q missing from output:\n%q", i, v, content)
+ t.Fatalf("[%d] %q missing from output:\n%q", i, v, content)
}
}
}
diff --git a/deps/deps.go b/deps/deps.go
index ac89d6cd6..fd9635444 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -126,7 +126,7 @@ func New(cfg DepsCfg) (*Deps, error) {
return nil, err
}
- sp := source.NewSourceSpec(cfg.Language, fs)
+ sp := source.NewSourceSpec(ps, fs.Source)
d := &Deps{
Fs: fs,
diff --git a/helpers/language.go b/helpers/language.go
index 49a25ccf7..731e9b088 100644
--- a/helpers/language.go
+++ b/helpers/language.go
@@ -41,6 +41,14 @@ type Language struct {
Title string
Weight int
+ Disabled bool
+
+ // If set per language, this tells Hugo that all content files without any
+ // language indicator (e.g. my-page.en.md) is in this language.
+ // This is usually a path relative to the working dir, but it can be an
+ // absolute directory referenece. It is what we get.
+ ContentDir string
+
Cfg config.Provider
// These are params declared in the [params] section of the language merged with the
@@ -66,7 +74,13 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
params[k] = v
}
ToLowerMap(params)
- l := &Language{Lang: lang, Cfg: cfg, params: params, settings: make(map[string]interface{})}
+
+ defaultContentDir := cfg.GetString("contentDir")
+ if defaultContentDir == "" {
+ panic("contentDir not set")
+ }
+
+ l := &Language{Lang: lang, ContentDir: defaultContentDir, Cfg: cfg, params: params, settings: make(map[string]interface{})}
return l
}
diff --git a/helpers/language_test.go b/helpers/language_test.go
index 68ee3506d..4c4670321 100644
--- a/helpers/language_test.go
+++ b/helpers/language_test.go
@@ -22,11 +22,12 @@ import (
func TestGetGlobalOnlySetting(t *testing.T) {
v := viper.New()
+ v.Set("defaultContentLanguageInSubdir", true)
+ v.Set("contentDir", "content")
+ v.Set("paginatePath", "page")
lang := NewDefaultLanguage(v)
lang.Set("defaultContentLanguageInSubdir", false)
lang.Set("paginatePath", "side")
- v.Set("defaultContentLanguageInSubdir", true)
- v.Set("paginatePath", "page")
require.True(t, lang.GetBool("defaultContentLanguageInSubdir"))
require.Equal(t, "side", lang.GetString("paginatePath"))
@@ -37,6 +38,7 @@ func TestLanguageParams(t *testing.T) {
v := viper.New()
v.Set("p1", "p1cfg")
+ v.Set("contentDir", "content")
lang := NewDefaultLanguage(v)
lang.SetParam("p1", "p1p")
diff --git a/helpers/path.go b/helpers/path.go
index 0a8544357..7ac9208bf 100644
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -33,7 +33,7 @@ var (
ErrThemeUndefined = errors.New("no theme set")
// ErrWalkRootTooShort is returned when the root specified for a file walk is shorter than 4 characters.
- ErrWalkRootTooShort = errors.New("Path too short. Stop walking.")
+ ErrPathTooShort = errors.New("file path is too short")
)
// filepathPathBridge is a bridge for common functionality in filepath vs path
@@ -446,7 +446,7 @@ func SymbolicWalk(fs afero.Fs, root string, walker filepath.WalkFunc) error {
// Sanity check
if len(root) < 4 {
- return ErrWalkRootTooShort
+ return ErrPathTooShort
}
// Handle the root first
@@ -481,7 +481,7 @@ func SymbolicWalk(fs afero.Fs, root string, walker filepath.WalkFunc) error {
}
func getRealFileInfo(fs afero.Fs, path string) (os.FileInfo, string, error) {
- fileInfo, err := LstatIfOs(fs, path)
+ fileInfo, err := LstatIfPossible(fs, path)
realPath := path
if err != nil {
@@ -493,7 +493,7 @@ func getRealFileInfo(fs afero.Fs, path string) (os.FileInfo, string, error) {
if err != nil {
return nil, "", fmt.Errorf("Cannot read symbolic link '%s', error was: %s", path, err)
}
- fileInfo, err = LstatIfOs(fs, link)
+ fileInfo, err = LstatIfPossible(fs, link)
if err != nil {
return nil, "", fmt.Errorf("Cannot stat '%s', error was: %s", link, err)
}
@@ -514,16 +514,14 @@ func GetRealPath(fs afero.Fs, path string) (string, error) {
return realPath, nil
}
-// Code copied from Afero's path.go
-// if the filesystem is OsFs use Lstat, else use fs.Stat
-func LstatIfOs(fs afero.Fs, path string) (info os.FileInfo, err error) {
- _, ok := fs.(*afero.OsFs)
- if ok {
- info, err = os.Lstat(path)
- } else {
- info, err = fs.Stat(path)
+// LstatIfPossible can be used to call Lstat if possible, else Stat.
+func LstatIfPossible(fs afero.Fs, path string) (os.FileInfo, error) {
+ if lstater, ok := fs.(afero.Lstater); ok {
+ fi, _, err := lstater.LstatIfPossible(path)
+ return fi, err
}
- return
+
+ return fs.Stat(path)
}
// SafeWriteToDisk is the same as WriteToDisk
diff --git a/helpers/path_test.go b/helpers/path_test.go
index d2c577dae..c2ac19675 100644
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -57,8 +57,10 @@ func TestMakePath(t *testing.T) {
for _, test := range tests {
v := viper.New()
- l := NewDefaultLanguage(v)
+ v.Set("contentDir", "content")
v.Set("removePathAccents", test.removeAccents)
+
+ l := NewDefaultLanguage(v)
p, err := NewPathSpec(hugofs.NewMem(v), l)
require.NoError(t, err)
@@ -71,6 +73,8 @@ func TestMakePath(t *testing.T) {
func TestMakePathSanitized(t *testing.T) {
v := viper.New()
+ v.Set("contentDir", "content")
+
l := NewDefaultLanguage(v)
p, _ := NewPathSpec(hugofs.NewMem(v), l)
@@ -98,6 +102,7 @@ func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
v := viper.New()
v.Set("disablePathToLower", true)
+ v.Set("contentDir", "content")
l := NewDefaultLanguage(v)
p, _ := NewPathSpec(hugofs.NewMem(v), l)
diff --git a/helpers/pathspec.go b/helpers/pathspec.go
index d35538b85..b18408590 100644
--- a/helpers/pathspec.go
+++ b/helpers/pathspec.go
@@ -17,6 +17,9 @@ import (
"fmt"
"strings"
+ "github.com/spf13/afero"
+
+ "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cast"
@@ -44,11 +47,13 @@ type PathSpec struct {
theme string
// Directories
- contentDir string
- themesDir string
- layoutDir string
- workingDir string
- staticDirs []string
+ contentDir string
+ themesDir string
+ layoutDir string
+ workingDir string
+ staticDirs []string
+ absContentDirs []types.KeyValueStr
+
PublishDir string
// The PathSpec looks up its config settings in both the current language
@@ -65,6 +70,9 @@ type PathSpec struct {
// The file systems to use
Fs *hugofs.Fs
+ // The fine grained filesystems in play (resources, content etc.).
+ BaseFs *hugofs.BaseFs
+
// The config provider to use
Cfg config.Provider
}
@@ -105,8 +113,65 @@ func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) (*PathSpec, error) {
languages = l
}
+ defaultContentLanguage := cfg.GetString("defaultContentLanguage")
+
+ // We will eventually pull out this badly placed path logic.
+ contentDir := cfg.GetString("contentDir")
+ workingDir := cfg.GetString("workingDir")
+ resourceDir := cfg.GetString("resourceDir")
+ publishDir := cfg.GetString("publishDir")
+
+ if len(languages) == 0 {
+ // We have some old tests that does not test the entire chain, hence
+ // they have no languages. So create one so we get the proper filesystem.
+ languages = Languages{&Language{Lang: "en", ContentDir: contentDir}}
+ }
+
+ absPuslishDir := AbsPathify(workingDir, publishDir)
+ if !strings.HasSuffix(absPuslishDir, FilePathSeparator) {
+ absPuslishDir += FilePathSeparator
+ }
+ // If root, remove the second '/'
+ if absPuslishDir == "//" {
+ absPuslishDir = FilePathSeparator
+ }
+ absResourcesDir := AbsPathify(workingDir, resourceDir)
+ if !strings.HasSuffix(absResourcesDir, FilePathSeparator) {
+ absResourcesDir += FilePathSeparator
+ }
+ if absResourcesDir == "//" {
+ absResourcesDir = FilePathSeparator
+ }
+
+ contentFs, absContentDirs, err := createContentFs(fs.Source, workingDir, defaultContentLanguage, languages)
+ if err != nil {
+ return nil, err
+ }
+
+ // Make sure we don't have any overlapping content dirs. That will never work.
+ for i, d1 := range absContentDirs {
+ for j, d2 := range absContentDirs {
+ if i == j {
+ continue
+ }
+ if strings.HasPrefix(d1.Value, d2.Value) || strings.HasPrefix(d2.Value, d1.Value) {
+ return nil, fmt.Errorf("found overlapping content dirs (%q and %q)", d1, d2)
+ }
+ }
+ }
+
+ resourcesFs := afero.NewBasePathFs(fs.Source, absResourcesDir)
+ publishFs := afero.NewBasePathFs(