summaryrefslogtreecommitdiffhomepage
path: root/hugolib/filesystems
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2018-02-20 10:02:14 +0100
committerBjørn Erik Pedersen <[email protected]>2018-07-06 11:46:12 +0200
commitdea71670c059ab4d5a42bd22503f18c087dd22d4 (patch)
tree52889fd27a2d316fad5a04c0f2fe2198491c6cd1 /hugolib/filesystems
parenta5d0a57e6bdab583134a68c035aac9b3007f006a (diff)
downloadhugo-dea71670c059ab4d5a42bd22503f18c087dd22d4.tar.gz
hugo-dea71670c059ab4d5a42bd22503f18c087dd22d4.zip
Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo. This commit adds * A new `/assets` top-level project or theme dir (configurable via `assetDir`) * A new template func, `resources.Get` which can be used to "get a resource" that can be further processed. This means that you can now do this in your templates (or shortcodes): ```bash {{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }} ``` This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed: ``` HUGO_BUILD_TAGS=extended mage install ``` Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo. The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline: ```bash {{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }} <link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen"> ``` The transformation funcs above have aliases, so it can be shortened to: ```bash {{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }} <link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen"> ``` A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding. Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test New functions to create `Resource` objects: * `resources.Get` (see above) * `resources.FromString`: Create a Resource from a string. New `Resource` transformation funcs: * `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`. * `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option). * `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`. * `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity.. * `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler. * `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template. Fixes #4381 Fixes #4903 Fixes #4858
Diffstat (limited to 'hugolib/filesystems')
-rw-r--r--hugolib/filesystems/basefs.go231
-rw-r--r--hugolib/filesystems/basefs_test.go92
2 files changed, 263 insertions, 60 deletions
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index deecd69a5..d4a7fcde7 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -28,7 +28,6 @@ import (
"fmt"
- "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/langs"
"github.com/spf13/afero"
@@ -45,20 +44,10 @@ var filePathSeparator = string(filepath.Separator)
// 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 {
- // TODO(bep) make this go away
- AbsContentDirs []types.KeyValueStr
-
- // The filesystem used to capture content. This can be a composite and
- // language aware file system.
- ContentFs afero.Fs
// SourceFilesystems contains the different source file systems.
*SourceFilesystems
- // The filesystem used to store resources (processed images etc.).
- // This usually maps to /my-project/resources.
- ResourcesFs afero.Fs
-
// The filesystem used to publish the rendered site.
// This usually maps to /my-project/public.
PublishFs afero.Fs
@@ -71,35 +60,31 @@ type BaseFs struct {
// 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, string) {
- for _, dir := range b.AbsContentDirs {
- if strings.HasPrefix(filename, dir.Value) {
- rel := strings.TrimPrefix(filename, dir.Value)
- return strings.TrimPrefix(rel, filePathSeparator), dir.Key
+func (b *BaseFs) RelContentDir(filename string) string {
+ for _, dirname := range b.SourceFilesystems.Content.Dirnames {
+ if strings.HasPrefix(filename, dirname) {
+ rel := strings.TrimPrefix(filename, dirname)
+ return strings.TrimPrefix(rel, filePathSeparator)
}
}
// Either not a content dir or already relative.
- return filename, ""
-}
-
-// IsContent returns whether the given filename is in the content filesystem.
-func (b *BaseFs) IsContent(filename string) bool {
- for _, dir := range b.AbsContentDirs {
- if strings.HasPrefix(filename, dir.Value) {
- return true
- }
- }
- return false
+ return filename
}
// SourceFilesystems contains the different source file systems. These can be
// composite file systems (theme and project etc.), and they have all root
// set to the source type the provides: data, i18n, static, layouts.
type SourceFilesystems struct {
+ Content *SourceFilesystem
Data *SourceFilesystem
I18n *SourceFilesystem
Layouts *SourceFilesystem
Archetypes *SourceFilesystem
+ Assets *SourceFilesystem
+ Resources *SourceFilesystem
+
+ // This is a unified read-only view of the project's and themes' workdir.
+ Work *SourceFilesystem
// When in multihost we have one static filesystem per language. The sync
// static files is currently done outside of the Hugo build (where there is
@@ -112,8 +97,14 @@ type SourceFilesystems struct {
// i18n, layouts, static) and additional metadata to be able to use that filesystem
// in server mode.
type SourceFilesystem struct {
+ // This is a virtual composite filesystem. It expects path relative to a context.
Fs afero.Fs
+ // This is the base source filesystem. In real Hugo, this will be the OS filesystem.
+ // Use this if you need to resolve items in Dirnames below.
+ SourceFs afero.Fs
+
+ // Dirnames is absolute filenames to the directories in this filesystem.
Dirnames []string
// When syncing a source folder to the target (e.g. /public), this may
@@ -122,6 +113,50 @@ type SourceFilesystem struct {
PublishFolder string
}
+// ContentStaticAssetFs will create a new composite filesystem from the content,
+// static, and asset filesystems. The site language is needed to pick the correct static filesystem.
+// The order is content, static and then assets.
+// TODO(bep) check usage
+func (s SourceFilesystems) ContentStaticAssetFs(lang string) afero.Fs {
+ staticFs := s.StaticFs(lang)
+
+ base := afero.NewCopyOnWriteFs(s.Assets.Fs, staticFs)
+ return afero.NewCopyOnWriteFs(base, s.Content.Fs)
+
+}
+
+// StaticFs returns the static filesystem for the given language.
+// This can be a composite filesystem.
+func (s SourceFilesystems) StaticFs(lang string) afero.Fs {
+ var staticFs afero.Fs = hugofs.NoOpFs
+
+ if fs, ok := s.Static[lang]; ok {
+ staticFs = fs.Fs
+ } else if fs, ok := s.Static[""]; ok {
+ staticFs = fs.Fs
+ }
+
+ return staticFs
+}
+
+// StatResource looks for a resource in these filesystems in order: static, assets and finally content.
+// If found in any of them, it returns FileInfo and the relevant filesystem.
+// Any non os.IsNotExist error will be returned.
+// An os.IsNotExist error wil be returned only if all filesystems return such an error.
+// Note that if we only wanted to find the file, we could create a composite Afero fs,
+// but we also need to know which filesystem root it lives in.
+func (s SourceFilesystems) StatResource(lang, filename string) (fi os.FileInfo, fs afero.Fs, err error) {
+ for _, fsToCheck := range []afero.Fs{s.StaticFs(lang), s.Assets.Fs, s.Content.Fs} {
+ fs = fsToCheck
+ fi, err = fs.Stat(filename)
+ if err == nil || !os.IsNotExist(err) {
+ return
+ }
+ }
+ // Not found.
+ return
+}
+
// IsStatic returns true if the given filename is a member of one of the static
// filesystems.
func (s SourceFilesystems) IsStatic(filename string) bool {
@@ -133,6 +168,11 @@ func (s SourceFilesystems) IsStatic(filename string) bool {
return false
}
+// IsContent returns true if the given filename is a member of the content filesystem.
+func (s SourceFilesystems) IsContent(filename string) bool {
+ return s.Content.Contains(filename)
+}
+
// IsLayout returns true if the given filename is a member of the layouts filesystem.
func (s SourceFilesystems) IsLayout(filename string) bool {
return s.Layouts.Contains(filename)
@@ -143,6 +183,11 @@ func (s SourceFilesystems) IsData(filename string) bool {
return s.Data.Contains(filename)
}
+// IsAsset returns true if the given filename is a member of the data filesystem.
+func (s SourceFilesystems) IsAsset(filename string) bool {
+ return s.Assets.Contains(filename)
+}
+
// IsI18n returns true if the given filename is a member of the i18n filesystem.
func (s SourceFilesystems) IsI18n(filename string) bool {
return s.I18n.Contains(filename)
@@ -171,6 +216,18 @@ func (d *SourceFilesystem) MakePathRelative(filename string) string {
return ""
}
+func (d *SourceFilesystem) RealFilename(rel string) string {
+ fi, err := d.Fs.Stat(rel)
+ if err != nil {
+ return rel
+ }
+ if realfi, ok := fi.(hugofs.RealFilenameInfo); ok {
+ return realfi.RealFilename()
+ }
+
+ return rel
+}
+
// Contains returns whether the given filename is a member of the current filesystem.
func (d *SourceFilesystem) Contains(filename string) bool {
for _, dir := range d.Dirnames {
@@ -181,6 +238,20 @@ func (d *SourceFilesystem) Contains(filename string) bool {
return false
}
+// RealDirs gets a list of absolute paths to directorys starting from the given
+// path.
+func (d *SourceFilesystem) RealDirs(from string) []string {
+ var dirnames []string
+ for _, dir := range d.Dirnames {
+ dirname := filepath.Join(dir, from)
+
+ if _, err := hugofs.Os.Stat(dirname); err == nil {
+ dirnames = append(dirnames, dirname)
+ }
+ }
+ return dirnames
+}
+
// WithBaseFs allows reuse of some potentially expensive to create parts that remain
// the same across sites/languages.
func WithBaseFs(b *BaseFs) func(*BaseFs) error {
@@ -191,11 +262,15 @@ func WithBaseFs(b *BaseFs) func(*BaseFs) error {
}
}
+func newRealBase(base afero.Fs) afero.Fs {
+ return hugofs.NewBasePathRealFilenameFs(base.(*afero.BasePathFs))
+
+}
+
// NewBase builds the filesystems used by Hugo given the paths and options provided.NewBase
func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
fs := p.Fs
- resourcesFs := afero.NewBasePathFs(fs.Source, p.AbsResourcesDir)
publishFs := afero.NewBasePathFs(fs.Destination, p.AbsPublishDir)
contentFs, absContentDirs, err := createContentFs(fs.Source, p.WorkingDir, p.DefaultContentLanguage, p.Languages)
@@ -209,17 +284,14 @@ func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
if i == j {
continue
}
- if strings.HasPrefix(d1.Value, d2.Value) || strings.HasPrefix(d2.Value, d1.Value) {
+ if strings.HasPrefix(d1, d2) || strings.HasPrefix(d2, d1) {
return nil, fmt.Errorf("found overlapping content dirs (%q and %q)", d1, d2)
}
}
}
b := &BaseFs{
- AbsContentDirs: absContentDirs,
- ContentFs: contentFs,
- ResourcesFs: resourcesFs,
- PublishFs: publishFs,
+ PublishFs: publishFs,
}
for _, opt := range options {
@@ -234,6 +306,12 @@ func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
return nil, err
}
+ sourceFilesystems.Content = &SourceFilesystem{
+ SourceFs: fs.Source,
+ Fs: contentFs,
+ Dirnames: absContentDirs,
+ }
+
b.SourceFilesystems = sourceFilesystems
b.themeFs = builder.themeFs
b.AbsThemeDirs = builder.absThemeDirs
@@ -281,18 +359,39 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
}
b.result.I18n = sfs
- sfs, err = b.createFs("layoutDir", "layouts")
+ sfs, err = b.createFs(false, true, "layoutDir", "layouts")
if err != nil {
return nil, err
}
b.result.Layouts = sfs
- sfs, err = b.createFs("archetypeDir", "archetypes")
+ sfs, err = b.createFs(false, true, "archetypeDir", "archetypes")
if err != nil {
return nil, err
}
b.result.Archetypes = sfs
+ sfs, err = b.createFs(false, true, "assetDir", "assets")
+ if err != nil {
+ return nil, err
+ }
+ b.result.Assets = sfs
+
+ sfs, err = b.createFs(true, false, "resourceDir", "resources")
+ if err != nil {
+ return nil, err
+ }
+
+ b.result.Resources = sfs
+
+ err = b.createStaticFs()
+
+ sfs, err = b.createFs(false, true, "", "")
+ if err != nil {
+ return nil, err
+ }
+ b.result.Work = sfs
+
err = b.createStaticFs()
if err != nil {
return nil, err
@@ -301,23 +400,38 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return b.result, nil
}
-func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*SourceFilesystem, error) {
- s := &SourceFilesystem{}
- dir := b.p.Cfg.GetString(dirKey)
- if dir == "" {
- return s, fmt.Errorf("config %q not set", dirKey)
+func (b *sourceFilesystemsBuilder) createFs(
+ mkdir bool,
+ readOnly bool,
+ dirKey, themeFolder string) (*SourceFilesystem, error) {
+ s := &SourceFilesystem{
+ SourceFs: b.p.Fs.Source,
+ }
+ var dir string
+ if dirKey != "" {
+ dir = b.p.Cfg.GetString(dirKey)
+ if dir == "" {
+ return s, fmt.Errorf("config %q not set", dirKey)
+ }
}
var fs afero.Fs
absDir := b.p.AbsPathify(dir)
- if b.existsInSource(absDir) {
- fs = afero.NewBasePathFs(b.p.Fs.Source, absDir)
+ existsInSource := b.existsInSource(absDir)
+ if !existsInSource && mkdir {
+ // We really need this directory. Make it.
+ if err := b.p.Fs.Source.MkdirAll(absDir, 0777); err == nil {
+ existsInSource = true
+ }
+ }
+ if existsInSource {
+ fs = newRealBase(afero.NewBasePathFs(b.p.Fs.Source, absDir))
s.Dirnames = []string{absDir}
}
if b.hasTheme {
- themeFolderFs := afero.NewBasePathFs(b.themeFs, themeFolder)
+ themeFolderFs := newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder))
if fs == nil {
fs = themeFolderFs
} else {
@@ -334,8 +448,10 @@ func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*Source
if fs == nil {
s.Fs = hugofs.NoOpFs
- } else {
+ } else if readOnly {
s.Fs = afero.NewReadOnlyFs(fs)
+ } else {
+ s.Fs = fs
}
return s, nil
@@ -344,7 +460,9 @@ func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*Source
// Used for data, i18n -- we cannot use overlay filsesystems for those, but we need
// to keep a strict order.
func (b *sourceFilesystemsBuilder) createRootMappingFs(dirKey, themeFolder string) (*SourceFilesystem, error) {
- s := &SourceFilesystem{}
+ s := &SourceFilesystem{
+ SourceFs: b.p.Fs.Source,
+ }
projectDir := b.p.Cfg.GetString(dirKey)
if projectDir == "" {
@@ -396,7 +514,9 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
if isMultihost {
for _, l := range b.p.Languages {
- s := &SourceFilesystem{PublishFolder: l.Lang}
+ s := &SourceFilesystem{
+ SourceFs: b.p.Fs.Source,
+ PublishFolder: l.Lang}
staticDirs := removeDuplicatesKeepRight(getStaticDirs(l))
if len(staticDirs) == 0 {
continue
@@ -424,7 +544,10 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
return nil
}
- s := &SourceFilesystem{}
+ s := &SourceFilesystem{
+ SourceFs: b.p.Fs.Source,
+ }
+
var staticDirs []string
for _, l := range b.p.Languages {
@@ -451,7 +574,7 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
if b.hasTheme {
themeFolder := "static"
- fs = afero.NewCopyOnWriteFs(afero.NewBasePathFs(b.themeFs, themeFolder), fs)
+ fs = afero.NewCopyOnWriteFs(newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)), fs)
for _, absThemeDir := range b.absThemeDirs {
s.Dirnames = append(s.Dirnames, filepath.Join(absThemeDir, themeFolder))
}
@@ -484,7 +607,7 @@ func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
func createContentFs(fs afero.Fs,
workingDir,
defaultContentLanguage string,
- languages langs.Languages) (afero.Fs, []types.KeyValueStr, error) {
+ languages langs.Languages) (afero.Fs, []string, error) {
var contentLanguages langs.Languages
var contentDirSeen = make(map[string]bool)
@@ -511,7 +634,7 @@ func createContentFs(fs afero.Fs,
}
- var absContentDirs []types.KeyValueStr
+ var absContentDirs []string
fs, err := createContentOverlayFs(fs, workingDir, contentLanguages, languageSet, &absContentDirs)
return fs, absContentDirs, err
@@ -522,7 +645,7 @@ func createContentOverlayFs(source afero.Fs,
workingDir string,
languages langs.Languages,
languageSet map[string]bool,
- absContentDirs *[]types.KeyValueStr) (afero.Fs, error) {
+ absContentDirs *[]string) (afero.Fs, error) {
if len(languages) == 0 {
return source, nil
}
@@ -548,7 +671,7 @@ func createContentOverlayFs(source afero.Fs,
return nil, fmt.Errorf("invalid content dir %q: Path is too short", absContentDir)
}
- *absContentDirs = append(*absContentDirs, types.KeyValueStr{Key: language.Lang, Value: absContentDir})
+ *absContentDirs = append(*absContentDirs, absContentDir)
overlay := hugofs.NewLanguageFs(language.Lang, languageSet, afero.NewBasePathFs(source, absContentDir))
if len(languages) == 1 {
@@ -597,10 +720,10 @@ func createOverlayFs(source afero.Fs, absPaths []string) (afero.Fs, error) {
}
if len(absPaths) == 1 {
- return afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0])), nil
+ return afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0]))), nil
}
- base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0]))
+ base := afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0])))
overlay, err := createOverlayFs(source, absPaths[1:])
if err != nil {
return nil, err
diff --git a/hugolib/filesystems/basefs_test.go b/hugolib/filesystems/basefs_test.go
index ea09cd8fd..3e043966f 100644
--- a/hugolib/filesystems/basefs_test.go
+++ b/hugolib/filesystems/basefs_test.go
@@ -60,6 +60,10 @@ theme = ["atheme"]
setConfigAndWriteSomeFilesTo(fs.Source, v, "staticDir", "mystatic", 6)
setConfigAndWriteSomeFilesTo(fs.Source, v, "dataDir", "mydata", 7)
setConfigAndWriteSomeFilesTo(fs.Source, v, "archetypeDir", "myarchetypes", 8)
+ setConfigAndWriteSomeFilesTo(fs.Source, v, "assetDir", "myassets", 9)
+ setConfigAndWriteSomeFilesTo(fs.Source, v, "resourceDir", "myrsesource", 10)
+
+ v.Set("publishDir", "public")
p, err := paths.New(fs, v)
assert.NoError(err)
@@ -88,12 +92,15 @@ theme = ["atheme"]
_, err = ff.Readdirnames(-1)
assert.NoError(err)
- checkFileCount(bfs.ContentFs, "", assert, 3)
+ checkFileCount(bfs.Content.Fs, "", assert, 3)
checkFileCount(bfs.I18n.Fs, "", assert, 6) // 4 + 2 themes
checkFileCount(bfs.Layouts.Fs, "", assert, 5)
checkFileCount(bfs.Static[""].Fs, "", assert, 6)
checkFileCount(bfs.Data.Fs, "", assert, 9) // 7 + 2 themes
checkFileCount(bfs.Archetypes.Fs, "", assert, 8)
+ checkFileCount(bfs.Assets.Fs, "", assert, 9)
+ checkFileCount(bfs.Resources.Fs, "", assert, 10)
+ checkFileCount(bfs.Work.Fs, "", assert, 57)
assert.Equal([]string{filepath.FromSlash("/my/work/mydata"), filepath.FromSlash("/my/work/themes/btheme/data"), filepath.FromSlash("/my/work/themes/atheme/data")}, bfs.Data.Dirnames)
@@ -101,15 +108,16 @@ theme = ["atheme"]
assert.True(bfs.IsI18n(filepath.Join(workingDir, "myi18n", "file1.txt")))
assert.True(bfs.IsLayout(filepath.Join(workingDir, "mylayouts", "file1.txt")))
assert.True(bfs.IsStatic(filepath.Join(workingDir, "mystatic", "file1.txt")))
+ assert.True(bfs.IsAsset(filepath.Join(workingDir, "myassets", "file1.txt")))
+
contentFilename := filepath.Join(workingDir, "mycontent", "file1.txt")
assert.True(bfs.IsContent(contentFilename))
- rel, _ := bfs.RelContentDir(contentFilename)
+ rel := bfs.RelContentDir(contentFilename)
assert.Equal("file1.txt", rel)
}
-func TestNewBaseFsEmpty(t *testing.T) {
- assert := require.New(t)
+func createConfig() *viper.Viper {
v := viper.New()
v.Set("contentDir", "mycontent")
v.Set("i18nDir", "myi18n")
@@ -117,18 +125,90 @@ func TestNewBaseFsEmpty(t *testing.T) {
v.Set("dataDir", "mydata")
v.Set("layoutDir", "mylayouts")
v.Set("archetypeDir", "myarchetypes")
+ v.Set("assetDir", "myassets")
+ v.Set("resourceDir", "resources")
+ v.Set("publishDir", "public")
+
+ return v
+}
+func TestNewBaseFsEmpty(t *testing.T) {
+ assert := require.New(t)
+ v := createConfig()
fs := hugofs.NewMem(v)
p, err := paths.New(fs, v)
+ assert.NoError(err)
bfs, err := NewBase(p)
assert.NoError(err)
assert.NotNil(bfs)
assert.Equal(hugofs.NoOpFs, bfs.Archetypes.Fs)
assert.Equal(hugofs.NoOpFs, bfs.Layouts.Fs)
assert.Equal(hugofs.NoOpFs, bfs.Data.Fs)
+ assert.Equal(hugofs.NoOpFs, bfs.Assets.Fs)
assert.Equal(hugofs.NoOpFs, bfs.I18n.Fs)
- assert.NotNil(hugofs.NoOpFs, bfs.ContentFs)
- assert.NotNil(hugofs.NoOpFs, bfs.Static)
+ assert.NotNil(bfs.Work.Fs)
+ assert.NotNil(bfs.Content.Fs)
+ assert.NotNil(bfs.Static)
+}
+
+func TestRealDirs(t *testing.T) {
+ assert := require.New(t)
+ v := createConfig()
+ fs := hugofs.NewDefault(v)
+ sfs := fs.Source
+
+ root, err := afero.TempDir(sfs, "", "realdir")
+ assert.NoError(err)
+ themesDir, err := afero.TempDir(sfs, "", "themesDir")
+ assert.NoError(err)
+ defer func() {
+ os.RemoveAll(root)
+ os.RemoveAll(themesDir)
+ }()
+
+ v.Set("workingDir", root)
+ v.Set("contentDir", "content")
+ v.Set("resourceDir", "resources")
+ v.Set("publishDir", "public")
+ v.Set("themesDir", themesDir)
+ v.Set("theme", "mytheme")
+
+ assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755))
+ assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755))
+ assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755))
+ assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3"), 0755))
+ assert.NoError(sfs.MkdirAll(filepath.Join(root, "resources"), 0755))
+ assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "resources"), 0755))
+
+ assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "js", "f2"), 0755))
+
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf1", "a1.scss")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "a2.scss")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3", "a4.scss")), []byte("content"), 0755)
+
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "resources", "t1.txt")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p1.txt")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p2.txt")), []byte("content"), 0755)
+
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "f2", "a1.js")), []byte("content"), 0755)
+ afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "a2.js")), []byte("content"), 0755)
+
+ p, err := paths.New(fs, v)
+ assert.NoError(err)
+ bfs, err := NewBase(p)
+ assert.NoError(err)
+ assert.NotNil(bfs)
+ checkFileCount(bfs.Assets.Fs, "", assert, 6)
+
+ realDirs := bfs.Assets.RealDirs("scss")
+ assert.Equal(2, len(realDirs))
+ assert.Equal(filepath.Join(root, "myassets/scss"), realDirs[0])
+ assert.Equal(filepath.Join(themesDir, "mytheme/assets/scss"), realDirs[len(realDirs)-1])
+
+ checkFileCount(bfs.Resources.Fs, "", assert, 3)
+
}
func checkFileCount(fs afero.Fs, dirname string, assert *require.Assertions, expected int) {