diff options
Diffstat (limited to 'hugolib/filesystems/basefs.go')
-rw-r--r-- | hugolib/filesystems/basefs.go | 704 |
1 files changed, 324 insertions, 380 deletions
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go index d88141efd..47ae5d0e0 100644 --- a/hugolib/filesystems/basefs.go +++ b/hugolib/filesystems/basefs.go @@ -16,27 +16,27 @@ package filesystems import ( - "errors" + "io" "os" + "path" "path/filepath" "strings" + "sync" - "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/hugofs/files" + + "github.com/pkg/errors" + + "github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/hugofs" "fmt" "github.com/gohugoio/hugo/hugolib/paths" - "github.com/gohugoio/hugo/langs" "github.com/spf13/afero" ) -// When we create a virtual filesystem with data and i18n bundles for the project and the themes, -// this is the name of the project's virtual root. It got it's funky name to make sure -// (or very unlikely) that it collides with a theme name. -const projectVirtualFolder = "__h__project" - var filePathSeparator = string(filepath.Separator) // BaseFs contains the core base filesystems used by Hugo. The name "base" is used @@ -51,16 +51,43 @@ type BaseFs struct { // This usually maps to /my-project/public. PublishFs afero.Fs - themeFs afero.Fs + theBigFs *filesystemsCollector +} + +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...) + } - // TODO(bep) improve the "theme interaction" - AbsThemeDirs []string + 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 _, dirname := range b.SourceFilesystems.Content.Dirnames { + for _, dir := range b.SourceFilesystems.Content.Dirs { + dirname := dir.Meta().Filename() if strings.HasPrefix(filename, dirname) { rel := strings.TrimPrefix(filename, dirname) return strings.TrimPrefix(rel, filePathSeparator) @@ -80,16 +107,22 @@ type SourceFilesystems struct { Layouts *SourceFilesystem Archetypes *SourceFilesystem Assets *SourceFilesystem - Resources *SourceFilesystem - // This is a unified read-only view of the project's and themes' workdir. - Work *SourceFilesystem + // Writable filesystem on top the project's resources directory, + // with any sub module's resource fs layered below. + ResourcesCache afero.Fs + + // The project folder. + Work afero.Fs // 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 // a concept of a site per language). // When in non-multihost mode there will be one entry in this map with a blank key. Static map[string]*SourceFilesystem + + // All the /static dirs (including themes/modules). + StaticDirs []hugofs.FileMetaInfo } // A SourceFilesystem holds the filesystem for a given source type in Hugo (data, @@ -99,12 +132,9 @@ 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 + // This filesystem as separate root directories, starting from project and down + // to the themes/modules. + Dirs []hugofs.FileMetaInfo // When syncing a source folder to the target (e.g. /public), this may // be set to publish into a subfolder. This is used for static syncing @@ -207,7 +237,8 @@ func (s SourceFilesystems) MakeStaticPathRelative(filename string) string { // MakePathRelative creates a relative path from the given filename. // It will return an empty string if the filename is not a member of this filesystem. func (d *SourceFilesystem) MakePathRelative(filename string) string { - for _, currentPath := range d.Dirnames { + for _, dir := range d.Dirs { + currentPath := dir.(hugofs.FileMetaInfo).Meta().Filename() if strings.HasPrefix(filename, currentPath) { return strings.TrimPrefix(filename, currentPath) } @@ -220,8 +251,8 @@ func (d *SourceFilesystem) RealFilename(rel string) string { if err != nil { return rel } - if realfi, ok := fi.(hugofs.RealFilenameInfo); ok { - return realfi.RealFilename() + if realfi, ok := fi.(hugofs.FileMetaInfo); ok { + return realfi.Meta().Filename() } return rel @@ -229,8 +260,8 @@ func (d *SourceFilesystem) RealFilename(rel string) string { // 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 { - if strings.HasPrefix(filename, dir) { + for _, dir := range d.Dirs { + if strings.HasPrefix(filename, dir.Meta().Filename()) { return true } } @@ -241,9 +272,12 @@ func (d *SourceFilesystem) Contains(filename string) bool { // path. func (d *SourceFilesystem) RealDirs(from string) []string { var dirnames []string - for _, dir := range d.Dirnames { - dirname := filepath.Join(dir, from) - if _, err := d.SourceFs.Stat(dirname); err == nil { + for _, dir := range d.Dirs { + meta := dir.Meta() + dirname := filepath.Join(meta.Filename(), from) + _, err := meta.Fs().Stat(from) + + if err == nil { dirnames = append(dirnames, dirname) } } @@ -254,40 +288,18 @@ func (d *SourceFilesystem) RealDirs(from string) []string { // the same across sites/languages. func WithBaseFs(b *BaseFs) func(*BaseFs) error { return func(bb *BaseFs) error { - bb.themeFs = b.themeFs - bb.AbsThemeDirs = b.AbsThemeDirs + bb.theBigFs = b.theBigFs + bb.SourceFilesystems = b.SourceFilesystems return nil } } -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 publishFs := afero.NewBasePathFs(fs.Destination, p.AbsPublishDir) - contentFs, absContentDirs, err := createContentFs(fs.Source, p.WorkingDir, p.DefaultContentLanguage, p.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, d2) || strings.HasPrefix(d2, d1) { - return nil, fmt.Errorf("found overlapping content dirs (%q and %q)", d1, d2) - } - } - } - b := &BaseFs{ PublishFs: publishFs, } @@ -298,463 +310,395 @@ func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) { } } + if b.theBigFs != nil && b.SourceFilesystems != nil { + return b, nil + } + builder := newSourceFilesystemsBuilder(p, b) sourceFilesystems, err := builder.Build() if err != nil { - return nil, err - } - - sourceFilesystems.Content = &SourceFilesystem{ - SourceFs: fs.Source, - Fs: contentFs, - Dirnames: absContentDirs, + return nil, errors.Wrap(err, "build filesystems") } b.SourceFilesystems = sourceFilesystems - b.themeFs = builder.themeFs - b.AbsThemeDirs = builder.absThemeDirs + b.theBigFs = builder.theBigFs return b, nil } type sourceFilesystemsBuilder struct { - p *paths.Paths - result *SourceFilesystems - themeFs afero.Fs - hasTheme bool - absThemeDirs []string + p *paths.Paths + sourceFs afero.Fs + result *SourceFilesystems + theBigFs *filesystemsCollector } func newSourceFilesystemsBuilder(p *paths.Paths, b *BaseFs) *sourceFilesystemsBuilder { - return &sourceFilesystemsBuilder{p: p, themeFs: b.themeFs, absThemeDirs: b.AbsThemeDirs, result: &SourceFilesystems{}} + sourceFs := hugofs.NewBaseFileDecorator(p.Fs.Source) + return &sourceFilesystemsBuilder{p: p, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}} } +func (b *sourceFilesystemsBuilder) newSourceFilesystem(fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem { + return &SourceFilesystem{ + Fs: fs, + Dirs: dirs, + } +} func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) { - if b.themeFs == nil && b.p.ThemeSet() { - themeFs, absThemeDirs, err := createThemesOverlayFs(b.p) + + if b.theBigFs == nil { + + theBigFs, err := b.createMainOverlayFs(b.p) if err != nil { - return nil, err - } - if themeFs == nil { - panic("createThemesFs returned nil") + return nil, errors.Wrap(err, "create main fs") } - b.themeFs = themeFs - b.absThemeDirs = absThemeDirs + b.theBigFs = theBigFs } - b.hasTheme = len(b.absThemeDirs) > 0 + createView := func(componentID string) *SourceFilesystem { + if b.theBigFs == nil || b.theBigFs.overlayMounts == nil { + return b.newSourceFilesystem(hugofs.NoOpFs, nil) + } - sfs, err := b.createRootMappingFs("dataDir", "data") - if err != nil { - return nil, err - } - b.result.Data = sfs + dirs := b.theBigFs.overlayDirs[componentID] - sfs, err = b.createRootMappingFs("i18nDir", "i18n") - if err != nil { - return nil, err - } - b.result.I18n = sfs + return b.newSourceFilesystem(afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs) - sfs, err = b.createFs(false, true, "layoutDir", "layouts") - if err != nil { - return nil, err } - b.result.Layouts = sfs - sfs, err = b.createFs(false, true, "archetypeDir", "archetypes") - if err != nil { - return nil, err - } - b.result.Archetypes = sfs + b.theBigFs.finalizeDirs() - sfs, err = b.createFs(false, true, "assetDir", "assets") - if err != nil { - return nil, err - } - b.result.Assets = sfs + b.result.Archetypes = createView(files.ComponentFolderArchetypes) + b.result.Layouts = createView(files.ComponentFolderLayouts) + b.result.Assets = createView(files.ComponentFolderAssets) + b.result.ResourcesCache = b.theBigFs.overlayResources - sfs, err = b.createFs(true, false, "resourceDir", "resources") + // Data, i18n and content cannot use the overlay fs + dataDirs := b.theBigFs.overlayDirs[files.ComponentFolderData] + dataFs, err := hugofs.NewSliceFs(dataDirs...) if err != nil { return nil, err } - b.result.Resources = sfs + b.result.Data = b.newSourceFilesystem(dataFs, dataDirs) - sfs, err = b.createFs(false, true, "", "") + i18nDirs := b.theBigFs.overlayDirs[files.ComponentFolderI18n] + i18nFs, err := hugofs.NewSliceFs(i18nDirs...) if err != nil { return nil, err } - b.result.Work = sfs + b.result.I18n = b.newSourceFilesystem(i18nFs, i18nDirs) + + contentDirs := b.theBigFs.overlayDirs[files.ComponentFolderContent] + contentBfs := afero.NewBasePathFs(b.theBigFs.overlayMountsContent, files.ComponentFolderContent) - err = b.createStaticFs() + contentFs, err := hugofs.NewLanguageFs(b.p.LanguagesDefaultFirst.AsOrdinalSet(), contentBfs) if err != nil { - return nil, err + return nil, errors.Wrap(err, "create content filesystem") } - return b.result, nil -} - -func (b *sourceFilesystemsBuilder) createFs( - mkdir bool, - readOnly bool, - dirKey, themeFolder string) (*SourceFilesystem, error) { - s := &SourceFilesystem{ - SourceFs: b.p.Fs.Source, - } + b.result.Content = b.newSourceFilesystem(contentFs, contentDirs) - if themeFolder == "" { - themeFolder = filePathSeparator - } + b.result.Work = afero.NewReadOnlyFs(b.theBigFs.overlayFull) - var dir string - if dirKey != "" { - dir = b.p.Cfg.GetString(dirKey) - if dir == "" { - return s, fmt.Errorf("config %q not set", dirKey) + // Create static filesystem(s) + ms := make(map[string]*SourceFilesystem) + b.result.Static = ms + b.result.StaticDirs = b.theBigFs.overlayDirs[files.ComponentFolderStatic] + + if b.theBigFs.staticPerLanguage != nil { + // Multihost mode + for k, v := range b.theBigFs.staticPerLanguage { + sfs := b.newSourceFilesystem(v, b.result.StaticDirs) + sfs.PublishFolder = k + ms[k] = sfs } + } else { + bfs := afero.NewBasePathFs(b.theBigFs.overlayMounts, files.ComponentFolderStatic) + ms[""] = b.newSourceFilesystem(bfs, b.result.StaticDirs) } - var fs afero.Fs + return b.result, nil - absDir := b.p.AbsPathify(dir) - 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 { - if !strings.HasPrefix(themeFolder, filePathSeparator) { - themeFolder = filePathSeparator + themeFolder - } - themeFolderFs := newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)) - if fs == nil { - fs = themeFolderFs - } else { - fs = afero.NewCopyOnWriteFs(themeFolderFs, fs) - } +func (b *sourceFilesystemsBuilder) createMainOverlayFs(p *paths.Paths) (*filesystemsCollector, error) { - for _, absThemeDir := range b.absThemeDirs { - absThemeFolderDir := filepath.Join(absThemeDir, themeFolder) - if b.existsInSource(absThemeFolderDir) { - s.Dirnames = append(s.Dirnames, absThemeFolderDir) - } - } + var staticFsMap map[string]afero.Fs + if b.p.Cfg.GetBool("multihost") { + staticFsMap = make(map[string]afero.Fs) } - if fs == nil { - s.Fs = hugofs.NoOpFs - } else if readOnly { - s.Fs = afero.NewReadOnlyFs(fs) - } else { - s.Fs = fs + collector := &filesystemsCollector{ + sourceProject: b.sourceFs, + sourceModules: hugofs.NewNoSymlinkFs(b.sourceFs), + overlayDirs: make(map[string][]hugofs.FileMetaInfo), + staticPerLanguage: staticFsMap, } - return s, nil -} + mods := p.AllModules -// 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{ - SourceFs: b.p.Fs.Source, + if len(mods) == 0 { + return collector, nil } - projectDir := b.p.Cfg.GetString(dirKey) - if projectDir == "" { - return nil, fmt.Errorf("config %q not set", dirKey) - } + modsReversed := make([]mountsDescriptor, len(mods)) - var fromTo []string - to := b.p.AbsPathify(projectDir) + // The theme components are ordered from left to right. + // We need to revert it to get the + // overlay logic below working as expected, with the project on top (last). - if b.existsInSource(to) { - s.Dirnames = []string{to} - fromTo = []string{projectVirtualFolder, to} - } + for i, mod := range mods { + dir := mod.Dir() - for _, theme := range b.p.AllThemes { - to := b.p.AbsPathify(filepath.Join(b.p.ThemesDir, theme.Name, themeFolder)) - if b.existsInSource(to) { - s.Dirnames = append(s.Dirnames, to) - from := theme - fromTo = append(fromTo, from.Name, to) + if i < len(mods)-1 { + i = len(mods) - 2 - i } - } - if len(fromTo) == 0 { - s.Fs = hugofs.NoOpFs - return s, nil + isMainProject := mod.Owner() == nil + modsReversed[i] = mountsDescriptor{ + mounts: mod.Mounts(), + dir: dir, + watch: mod.Watch(), + isMainProject: isMainProject, + } } - fs, err := hugofs.NewRootMappingFs(b.p.Fs.Source, fromTo...) - if err != nil { - return nil, err - } + err := b.createOverlayFs(collector, modsReversed) - s.Fs = afero.NewReadOnlyFs(fs) + return collector, err - return s, nil } -func (b *sourceFilesystemsBuilder) existsInSource(abspath string) bool { - exists, _ := afero.Exists(b.p.Fs.Source, abspath) - return exists +func (b *sourceFilesystemsBuilder) isContentMount(mnt modules.Mount) bool { + return strings.HasPrefix(mnt.Target, files.ComponentFolderContent) } -func (b *sourceFilesystemsBuilder) createStaticFs() error { - isMultihost := b.p.Cfg.GetBool("multihost") - ms := make(map[string]*SourceFilesystem) - b.result.Static = ms - - if isMultihost { - for _, l := range b.p.Languages { - s := &SourceFilesystem{ - SourceFs: b.p.Fs.Source, - PublishFolder: l.Lang} - staticDirs := removeDuplicatesKeepRight(getStaticDirs(l)) - if len(staticDirs) == 0 { - continue - } +func (b *sourceFilesystemsBuilder) createModFs( + collector *filesystemsCollector, + md mountsDescriptor) error { - for _, dir := range staticDirs { - absDir := b.p.AbsPathify(dir) - if !b.existsInSource(absDir) { - continue - } + var ( + fromTo []hugofs.RootMapping + fromToContent []hugofs.RootMapping + ) - s.Dirnames = append(s.Dirnames, absDir) - } + absPathify := func(path string) string { + return paths.AbsPathify(md.dir, path) + } - fs, err := createOverlayFs(b.p.Fs.Source, s.Dirnames) - if err != nil { - return err - } + seen := make(map[string]bool) - if b.hasTheme { - themeFolder := "static" - fs = afero.NewCopyOnWriteFs(newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)), fs) - for _, absThemeDir := range b.absThemeDirs { - s.Dirnames = append(s.Dirnames, filepath.Join(absThemeDir, themeFolder)) - } - } + var mounts []modules.Mount - s.Fs = fs - ms[l.Lang] = s +OUTER: + for i, mount := range md.mounts { + key := path.Join(mount.Lang, mount.Source, mount.Target) + if seen[key] { + continue + } + seen[key] = true + // Prevent overlapping mounts + for j, mount2 := range md.mounts { + if j == i || mount2.Target != mount.Target { + continue + } + source := mount.Source + if !strings.HasSuffix(source, filePathSeparator) { + source += filePathSeparator + } + if strings.HasPrefix(mount2.Source, source) { + continue OUTER + } } - return nil + mounts = append(mounts, mount) } - s := &SourceFilesystem{ - SourceFs: b.p.Fs.Source, - } + for _, mount := range mounts { - var staticDirs []string + mountWeight := 1 + if md.isMainProject { + mountWeight++ + } - for _, l := range b.p.Languages { - staticDirs = append(staticDirs, getStaticDirs(l)...) - } + rm := hugofs.RootMapping{ + From: mount.Target, + To: absPathify(mount.Source), + Meta: hugofs.FileMeta{ + "watch": md.watch, + "mountWeight": mountWeight, + }, + } - staticDirs = removeDuplicatesKeepRight(staticDirs) - if len(staticDirs) == 0 { - return nil - } + isContentMount := b.isContentMount(mount) - for _, dir := range staticDirs { - absDir := b.p.AbsPathify(dir) - if !b.existsInSource(absDir) { - continue + lang := mount.Lang + if lang == "" && isContentMount { + lang = b.p.DefaultContentLanguage } - s.Dirnames = append(s.Dirnames, absDir) - } - fs, err := createOverlayFs(b.p.Fs.Source, s.Dirnames) - if err != nil { - return err - } + rm.Meta["lang"] = lang - if b.hasTheme { - themeFolder := "static" - fs = afero.NewCopyOnWriteFs(newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)), fs) - for _, absThemeDir := range b.absThemeDirs { - s.Dirnames = append(s.Dirnames, filepath.Join(absThemeDir, themeFolder)) + if isContentMount { + fromToContent = append(fromToContent, rm) + } else { + fromTo = append(fromTo, rm) } } - s.Fs = fs - ms[""] = s - - return nil -} - -func getStaticDirs(cfg config.Provider) []string { - var staticDirs []string - for i := -1; i <= 10; i++ { - staticDirs = append(staticDirs, getStringOrStringSlice(cfg, "staticDir", i)...) + modBase := collector.sourceProject + if !md.isMainProject { + modBase = collector.sourceModules } - return staticDirs -} - -func getStringOrStringSlice(cfg config.Provider, key string, id int) []string { - if id >= 0 { - key = fmt.Sprintf("%s%d", key, id) + rmfs, err := hugofs.NewRootMappingFs(modBase, fromTo...) + if err != nil { + return err + } + rmfsContent, err := hugofs.NewRootMappingFs(modBase, fromToContent...) + if err != nil { + return err } - return config.GetStringSlicePreserveString(cfg, key) + // We need to keep the ordered list of directories for watching and + // some special merge operations (data, i18n). + collector.addDirs(rmfs) + collector.addDirs(rmfsContent) -} + if collector.staticPerLanguage != nil { + for _, l := range b.p.Languages { + lang := l.Lang -func createContentFs(fs afero.Fs, - workingDir, - defaultContentLanguage string, - languages langs.Languages) (afero.Fs, []string, error) { + lfs := rmfs.Filter(func(rm hugofs.RootMapping) bool { + rlang := rm.Meta.Lang() + return rlang == "" || rlang == lang + }) - var contentLanguages langs.Languages - var contentDirSeen = make(map[string]bool) - languageSet := make(map[string]bool) + bfs := afero.NewBasePathFs(lfs, files.ComponentFolderStatic) - // The default content language needs to be first. - for _, language := range languages { - if language.Lang == defaultContentLanguage { - contentLanguages = append(contentLanguages, language) - contentDirSeen[language.ContentDir] = true - } - languageSet[language.Lang] = true - } + sfs, found := collector.staticPerLanguage[lang] + if found { + collector.staticPerLanguage[lang] = afero.NewCopyOnWriteFs(sfs, bfs) - for _, language := range languages { - if contentDirSeen[language.ContentDir] { - continue - } - if language.ContentDir == "" { - language.ContentDir = defaultContentLanguage + } else { + collector.staticPerLanguage[lang] = bfs + } } - contentDirSeen[language.ContentDir] = true - contentLanguages = append(contentLanguages, language) - } - var absContentDirs []string - - fs, err := createContentOverlayFs(fs, workingDir, contentLanguages, languageSet, &absContentDirs) - return fs, absContentDirs, err - -} - -func createContentOverlayFs(source afero.Fs, - workingDir string, - languages langs.Languages, - languageSet map[string]bool, - absContentDirs *[]string) (afero.Fs, error) { - if len(languages) == 0 { - return source, nil + getResourcesDir := func() string { + if md.isMainProject { + return b.p.AbsResourcesDir + } + return absPathify(files.FolderResources) } - language := languages[0] + if collector.overlayMounts == nil { + collector.overlayMounts = rmfs + collector.overlayMountsContent = rmfsContent + collector.overlayFull = afero.NewBasePathFs(modBase, md.dir) + collector.overlayResources = afero.NewBasePathFs(modBase, getResourcesDir()) + } else { - contentDir := language.ContentDir - if contentDir == "" { - panic("missing contentDir") + collector.overlayMounts = afero.NewCopyOnWriteFs(collector.overlayMounts, rmfs) + collector.overlayMountsContent = hugofs.NewLanguageCompositeFs(collector.overlayMountsContent, rmfsContent) + collector.overlayFull = afero.NewCopyOnWriteFs(collector.overlayFull, afero.NewBasePathFs(modBase, md.dir)) + collector.overlayResources = afero.NewCopyOnWriteFs(collector.overlayResources, afero.NewBasePathFs(modBase, getResourcesDir())) } - absContentDir := paths.AbsPathify(workingDir, language.ContentDir) - if !strings.HasSuffix(absContentDir, paths.FilePathSeparator) { - absContentDir += paths.FilePathSeparator - } + return nil - // If root, remove the second '/' - if absContentDir == "//" { - absContentDir = paths.FilePathSeparator - } +} - if len(absContentDir) < 6 { - return nil, fmt.Errorf("invalid content dir %q: Path is too short", absContentDir) +func printFs(fs afero.Fs, path string, w io.Writer) { + if fs == nil { + return } + afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + var filename string + if fim, ok := info.(hugofs.FileMetaInfo); ok { + filename = fim.Meta().Filename() + } + fmt.Fprintf(w, " %q %q\n", path, filename) + return nil + }) +} - *absContentDirs = append(*absContentDirs, absContentDir) +type filesystemsCollector struct { + sourceProject afero.Fs // Source for project folders + sourceModules afero.Fs // Source for modules/themes - overlay := hugofs.NewLanguageFs(language.Lang, languageSet, afero.NewBasePathFs(source, absContentDir)) - if len(languages) == 1 { - return overlay, nil - } + overlayMounts afero.Fs + overlayMountsContent afero.Fs + overlayFull afero.Fs + overlayResources afero.Fs - base, err := createContentOverlayFs(source, workingDir, languages[1:], languageSet, absContentDirs) - if err != nil { - return nil, err - } + // Maps component type (layouts, static, content etc.) an ordered list of + // directories representing the overlay filesystems above. + overlayDirs map[string][]hugofs.FileMetaInfo - return hugofs.NewLanguageCompositeFs(base, overlay), nil + // Set if in multihost mode + staticPerLanguage map[string]afero.Fs + finalizerInit sync.Once } -func createThemesOverlayFs(p *paths.Paths) (afero.Fs, []string, error) { - - themes := p.AllThemes +func (c *filesystemsCollector) addDirs(rfs *hugofs.RootMappingFs) { + for _, componentFolder := range files.ComponentFolders { + dirs, err := rfs.Dirs(componentFolder) - if len(themes) == 0 { - panic("AllThemes not set") + if err == nil { + c.overlayDirs[componentFolder] = append(c.overlayDirs[componentFolder], dirs...) + } } +} - themesDir := p.AbsPathify(p.ThemesDir) - if themesDir == "" { - return nil, nil, errors.New("no themes dir set") - } +func (c *filesystemsCollector) finalizeDirs() { + c.finalizerInit.Do(func() { + // Order the directories from top to bottom (project, theme a, theme ...). + for _, dirs := range c.overlayDirs { + c.reverseFis(dirs) + } + }) - absPaths := make([]string, len(themes)) +} - // The themes are ordered from left to right. We need to revert it to get the - // overlay logic below working as expected. - for i := 0; i < len(themes); i++ { - absPaths[i] = filepath.Join(themesDir, themes[len(themes)-1-i].Name) +func (c *filesystemsCollector) reverseFis(fis []hugofs.FileMetaInfo) { + for i := len(fis)/2 - 1; i >= 0; i-- { + opp := len(fis) - 1 - i + fis[i], fis[opp] = fis[opp], fis[i] } - - fs, err := createOverlayFs(p.Fs.Source, absPaths) - fs = hugofs.NewNoLstatFs(fs) - - return fs, absPaths, err - } -func createOverlayFs(source afero.Fs, absPaths []string) (afero.Fs, error) { - if len(absPaths) == 0 { - return hugofs.NoOpFs, nil - } +type mountsDescriptor struct { + mounts []modules.Mount + dir string + watch bool // whether this is a candidate for watching in server mode. + isMainProject bool +} - if len(absPaths) == 1 { - return afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0]))), nil +func (b *sourceFilesystemsBuilder) createOverlayFs(collector *filesystemsCollector, mounts []mountsDescriptor) error { + if len(mounts) == 0 { + return nil } - base := afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0]))) - overlay, err := createOverlayFs(source, absPaths[1:]) + err := b.createModFs(collector, mounts[0]) if err != nil { - return nil, err + return err } - return afero.NewCopyOnWriteFs(base, overlay), nil -} - -func removeDuplicatesKeepRight(in []string) []string { - seen := make(map[string]bool) - var out []string - for i := len(in) - 1; i >= 0; i-- { - v := in[i] - if seen[v] { - continue - } - out = append([]string{v}, out...) - seen[v] = true + if len(mounts) == 1 { + return nil } - return out + return b.createOverlayFs(collector, mounts[1:]) } |