basefs.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
basefs.go (24362B)
---
1 // Copyright 2024 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package filesystems provides the fine grained file systems used by Hugo. These
15 // are typically virtual filesystems that are composites of project and theme content.
16 package filesystems
17
18 import (
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 "strings"
24 "sync"
25
26 "github.com/bep/overlayfs"
27 "github.com/gohugoio/hugo/config"
28 "github.com/gohugoio/hugo/htesting"
29 "github.com/gohugoio/hugo/hugofs/glob"
30
31 "github.com/gohugoio/hugo/common/herrors"
32 "github.com/gohugoio/hugo/common/loggers"
33 "github.com/gohugoio/hugo/common/types"
34
35 "github.com/rogpeppe/go-internal/lockedfile"
36
37 "github.com/gohugoio/hugo/hugofs/files"
38
39 "github.com/gohugoio/hugo/modules"
40
41 hpaths "github.com/gohugoio/hugo/common/paths"
42 "github.com/gohugoio/hugo/hugofs"
43 "github.com/gohugoio/hugo/hugolib/paths"
44 "github.com/spf13/afero"
45 )
46
47 const (
48 // Used to control concurrency between multiple Hugo instances, e.g.
49 // a running server and building new content with 'hugo new'.
50 // It's placed in the project root.
51 lockFileBuild = ".hugo_build.lock"
52 )
53
54 var filePathSeparator = string(filepath.Separator)
55
56 // BaseFs contains the core base filesystems used by Hugo. The name "base" is used
57 // to underline that even if they can be composites, they all have a base path set to a specific
58 // resource folder, e.g "/my-project/content". So, no absolute filenames needed.
59 type BaseFs struct {
60 // SourceFilesystems contains the different source file systems.
61 *SourceFilesystems
62
63 // The source filesystem (needs absolute filenames).
64 SourceFs afero.Fs
65
66 // The project source.
67 ProjectSourceFs afero.Fs
68
69 // The filesystem used to publish the rendered site.
70 // This usually maps to /my-project/public.
71 PublishFs afero.Fs
72
73 // The filesystem used for static files.
74 PublishFsStatic afero.Fs
75
76 // A read-only filesystem starting from the project workDir.
77 WorkDir afero.Fs
78
79 theBigFs *filesystemsCollector
80
81 workingDir string
82
83 // Locks.
84 buildMu Lockable // <project>/.hugo_build.lock
85 }
86
87 type Lockable interface {
88 Lock() (unlock func(), err error)
89 }
90
91 type fakeLockfileMutex struct {
92 mu sync.Mutex
93 }
94
95 func (f *fakeLockfileMutex) Lock() (func(), error) {
96 f.mu.Lock()
97 return func() { f.mu.Unlock() }, nil
98 }
99
100 // Tries to acquire a build lock.
101 func (b *BaseFs) LockBuild() (unlock func(), err error) {
102 return b.buildMu.Lock()
103 }
104
105 func (b *BaseFs) WatchFilenames() []string {
106 var filenames []string
107 sourceFs := b.SourceFs
108
109 for _, rfs := range b.RootFss {
110 for _, component := range files.ComponentFolders {
111 fis, err := rfs.Mounts(component)
112 if err != nil {
113 continue
114 }
115
116 for _, fim := range fis {
117 meta := fim.Meta()
118 if !meta.Watch {
119 continue
120 }
121
122 if !fim.IsDir() {
123 filenames = append(filenames, meta.Filename)
124 continue
125 }
126
127 w := hugofs.NewWalkway(hugofs.WalkwayConfig{
128 Fs: sourceFs,
129 Root: meta.Filename,
130 WalkFn: func(path string, fi hugofs.FileMetaInfo) error {
131 if !fi.IsDir() {
132 return nil
133 }
134 if fi.Name() == ".git" ||
135 fi.Name() == "node_modules" || fi.Name() == "bower_components" {
136 return filepath.SkipDir
137 }
138 filenames = append(filenames, fi.Meta().Filename)
139 return nil
140 },
141 })
142
143 w.Walk()
144 }
145
146 }
147 }
148
149 return filenames
150 }
151
152 func (b *BaseFs) mountsForComponent(component string) []hugofs.FileMetaInfo {
153 var result []hugofs.FileMetaInfo
154 for _, rfs := range b.RootFss {
155 dirs, err := rfs.Mounts(component)
156 if err == nil {
157 result = append(result, dirs...)
158 }
159 }
160 return result
161 }
162
163 // AbsProjectContentDir tries to construct a filename below the most
164 // relevant content directory.
165 func (b *BaseFs) AbsProjectContentDir(filename string) (string, string, error) {
166 isAbs := filepath.IsAbs(filename)
167 for _, fi := range b.mountsForComponent(files.ComponentFolderContent) {
168 if !fi.IsDir() {
169 continue
170 }
171 meta := fi.Meta()
172 if !meta.IsProject {
173 continue
174 }
175
176 if isAbs {
177 if strings.HasPrefix(filename, meta.Filename) {
178 return strings.TrimPrefix(filename, meta.Filename+filePathSeparator), filename, nil
179 }
180 } else {
181 contentDir := strings.TrimPrefix(strings.TrimPrefix(meta.Filename, meta.BaseDir), filePathSeparator) + filePathSeparator
182
183 if strings.HasPrefix(filename, contentDir) {
184 relFilename := strings.TrimPrefix(filename, contentDir)
185 absFilename := filepath.Join(meta.Filename, relFilename)
186 return relFilename, absFilename, nil
187 }
188 }
189
190 }
191
192 if !isAbs {
193 // A filename on the form "posts/mypage.md", put it inside
194 // the first content folder, usually <workDir>/content.
195 // Pick the first project dir (which is probably the most important one).
196 for _, dir := range b.SourceFilesystems.Content.mounts() {
197 if !dir.IsDir() {
198 continue
199 }
200 meta := dir.Meta()
201 if meta.IsProject {
202 return filename, filepath.Join(meta.Filename, filename), nil
203 }
204 }
205 }
206
207 return "", "", fmt.Errorf("could not determine content directory for %q", filename)
208 }
209
210 // ResolveJSConfigFile resolves the JS-related config file to a absolute
211 // filename. One example of such would be postcss.config.js.
212 func (b *BaseFs) ResolveJSConfigFile(name string) string {
213 // First look in assets/_jsconfig
214 fi, err := b.Assets.Fs.Stat(filepath.Join(files.FolderJSConfig, name))
215 if err == nil {
216 return fi.(hugofs.FileMetaInfo).Meta().Filename
217 }
218 // Fall back to the work dir.
219 fi, err = b.Work.Stat(name)
220 if err == nil {
221 return fi.(hugofs.FileMetaInfo).Meta().Filename
222 }
223
224 return ""
225 }
226
227 // SourceFilesystems contains the different source file systems. These can be
228 // composite file systems (theme and project etc.), and they have all root
229 // set to the source type the provides: data, i18n, static, layouts.
230 type SourceFilesystems struct {
231 Content *SourceFilesystem
232 Data *SourceFilesystem
233 I18n *SourceFilesystem
234 Layouts *SourceFilesystem
235 Archetypes *SourceFilesystem
236 Assets *SourceFilesystem
237
238 AssetsWithDuplicatesPreserved *SourceFilesystem
239
240 RootFss []*hugofs.RootMappingFs
241
242 // Writable filesystem on top the project's resources directory,
243 // with any sub module's resource fs layered below.
244 ResourcesCache afero.Fs
245
246 // The work folder (may be a composite of project and theme components).
247 Work afero.Fs
248
249 // When in multihost we have one static filesystem per language. The sync
250 // static files is currently done outside of the Hugo build (where there is
251 // a concept of a site per language).
252 // When in non-multihost mode there will be one entry in this map with a blank key.
253 Static map[string]*SourceFilesystem
254
255 conf config.AllProvider
256 }
257
258 // A SourceFilesystem holds the filesystem for a given source type in Hugo (data,
259 // i18n, layouts, static) and additional metadata to be able to use that filesystem
260 // in server mode.
261 type SourceFilesystem struct {
262 // Name matches one in files.ComponentFolders
263 Name string
264
265 // This is a virtual composite filesystem. It expects path relative to a context.
266 Fs afero.Fs
267
268 // The source filesystem (usually the OS filesystem).
269 SourceFs afero.Fs
270
271 // When syncing a source folder to the target (e.g. /public), this may
272 // be set to publish into a subfolder. This is used for static syncing
273 // in multihost mode.
274 PublishFolder string
275 }
276
277 // StaticFs returns the static filesystem for the given language.
278 // This can be a composite filesystem.
279 func (s SourceFilesystems) StaticFs(lang string) afero.Fs {
280 var staticFs afero.Fs = hugofs.NoOpFs
281
282 if fs, ok := s.Static[lang]; ok {
283 staticFs = fs.Fs
284 } else if fs, ok := s.Static[""]; ok {
285 staticFs = fs.Fs
286 }
287
288 return staticFs
289 }
290
291 // StatResource looks for a resource in these filesystems in order: static, assets and finally content.
292 // If found in any of them, it returns FileInfo and the relevant filesystem.
293 // Any non herrors.IsNotExist error will be returned.
294 // An herrors.IsNotExist error will be returned only if all filesystems return such an error.
295 // Note that if we only wanted to find the file, we could create a composite Afero fs,
296 // but we also need to know which filesystem root it lives in.
297 func (s SourceFilesystems) StatResource(lang, filename string) (fi os.FileInfo, fs afero.Fs, err error) {
298 for _, fsToCheck := range []afero.Fs{s.StaticFs(lang), s.Assets.Fs, s.Content.Fs} {
299 fs = fsToCheck
300 fi, err = fs.Stat(filename)
301 if err == nil || !herrors.IsNotExist(err) {
302 return
303 }
304 }
305 // Not found.
306 return
307 }
308
309 // IsStatic returns true if the given filename is a member of one of the static
310 // filesystems.
311 func (s SourceFilesystems) IsStatic(filename string) bool {
312 for _, staticFs := range s.Static {
313 if staticFs.Contains(filename) {
314 return true
315 }
316 }
317 return false
318 }
319
320 // IsContent returns true if the given filename is a member of the content filesystem.
321 func (s SourceFilesystems) IsContent(filename string) bool {
322 return s.Content.Contains(filename)
323 }
324
325 // ResolvePaths resolves the given filename to a list of paths in the filesystems.
326 func (s *SourceFilesystems) ResolvePaths(filename string) []hugofs.ComponentPath {
327 var cpss []hugofs.ComponentPath
328 for _, rfs := range s.RootFss {
329 cps, err := rfs.ReverseLookup(filename)
330 if err != nil {
331 panic(err)
332 }
333 cpss = append(cpss, cps...)
334 }
335 return cpss
336 }
337
338 // MakeStaticPathRelative makes an absolute static filename into a relative one.
339 // It will return an empty string if the filename is not a member of a static filesystem.
340 func (s SourceFilesystems) MakeStaticPathRelative(filename string) string {
341 for _, staticFs := range s.Static {
342 rel, _ := staticFs.MakePathRelative(filename, true)
343 if rel != "" {
344 return rel
345 }
346 }
347 return ""
348 }
349
350 // MakePathRelative creates a relative path from the given filename.
351 func (d *SourceFilesystem) MakePathRelative(filename string, checkExists bool) (string, bool) {
352 cps, err := d.ReverseLookup(filename, checkExists)
353 if err != nil {
354 panic(err)
355 }
356 if len(cps) == 0 {
357 return "", false
358 }
359
360 return filepath.FromSlash(cps[0].Path), true
361 }
362
363 // ReverseLookup returns the component paths for the given filename.
364 func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]hugofs.ComponentPath, error) {
365 var cps []hugofs.ComponentPath
366 hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
367 if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
368 if c, err := rfs.ReverseLookupComponent(d.Name, filename); err == nil {
369 if checkExists {
370 n := 0
371 for _, cp := range c {
372 if _, err := d.Fs.Stat(filepath.FromSlash(cp.Path)); err == nil {
373 c[n] = cp
374 n++
375 }
376 }
377 c = c[:n]
378 }
379 cps = append(cps, c...)
380 }
381 }
382 return false
383 })
384 return cps, nil
385 }
386
387 func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
388 var m []hugofs.FileMetaInfo
389 hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
390 if rfs, ok := fs.(*hugofs.RootMappingFs); ok {
391 mounts, err := rfs.Mounts(d.Name)
392 if err == nil {
393 m = append(m, mounts...)
394 }
395 }
396 return false
397 })
398
399 // Filter out any mounts not belonging to this filesystem.
400 // TODO(bep) I think this is superfluous.
401 n := 0
402 for _, mm := range m {
403 if mm.Meta().Component == d.Name {
404 m[n] = mm
405 n++
406 }
407 }
408 m = m[:n]
409
410 return m
411 }
412
413 func (d *SourceFilesystem) RealFilename(rel string) string {
414 fi, err := d.Fs.Stat(rel)
415 if err != nil {
416 return rel
417 }
418 if realfi, ok := fi.(hugofs.FileMetaInfo); ok {
419 return realfi.Meta().Filename
420 }
421
422 return rel
423 }
424
425 // Contains returns whether the given filename is a member of the current filesystem.
426 func (d *SourceFilesystem) Contains(filename string) bool {
427 for _, dir := range d.mounts() {
428 if !dir.IsDir() {
429 continue
430 }
431 if strings.HasPrefix(filename, dir.Meta().Filename) {
432 return true
433 }
434 }
435 return false
436 }
437
438 // RealDirs gets a list of absolute paths to directories starting from the given
439 // path.
440 func (d *SourceFilesystem) RealDirs(from string) []string {
441 var dirnames []string
442 for _, m := range d.mounts() {
443 if !m.IsDir() {
444 continue
445 }
446 dirname := filepath.Join(m.Meta().Filename, from)
447 if _, err := d.SourceFs.Stat(dirname); err == nil {
448 dirnames = append(dirnames, dirname)
449 }
450 }
451 return dirnames
452 }
453
454 // WithBaseFs allows reuse of some potentially expensive to create parts that remain
455 // the same across sites/languages.
456 func WithBaseFs(b *BaseFs) func(*BaseFs) error {
457 return func(bb *BaseFs) error {
458 bb.theBigFs = b.theBigFs
459 bb.SourceFilesystems = b.SourceFilesystems
460 return nil
461 }
462 }
463
464 // NewBase builds the filesystems used by Hugo given the paths and options provided.NewBase
465 func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) error) (*BaseFs, error) {
466 fs := p.Fs
467 if logger == nil {
468 logger = loggers.NewDefault()
469 }
470
471 publishFs := hugofs.NewBaseFileDecorator(fs.PublishDir)
472 projectSourceFs := hugofs.NewBaseFileDecorator(hugofs.NewBasePathFs(fs.Source, p.Cfg.BaseConfig().WorkingDir))
473 sourceFs := hugofs.NewBaseFileDecorator(fs.Source)
474 publishFsStatic := fs.PublishDirStatic
475
476 var buildMu Lockable
477 if p.Cfg.NoBuildLock() || htesting.IsTest {
478 buildMu = &fakeLockfileMutex{}
479 } else {
480 buildMu = lockedfile.MutexAt(filepath.Join(p.Cfg.BaseConfig().WorkingDir, lockFileBuild))
481 }
482
483 b := &BaseFs{
484 SourceFs: sourceFs,
485 ProjectSourceFs: projectSourceFs,
486 WorkDir: fs.WorkingDirReadOnly,
487 PublishFs: publishFs,
488 PublishFsStatic: publishFsStatic,
489 workingDir: p.Cfg.BaseConfig().WorkingDir,
490 buildMu: buildMu,
491 }
492
493 for _, opt := range options {
494 if err := opt(b); err != nil {
495 return nil, err
496 }
497 }
498
499 if b.theBigFs != nil && b.SourceFilesystems != nil {
500 return b, nil
501 }
502
503 builder := newSourceFilesystemsBuilder(p, logger, b)
504 sourceFilesystems, err := builder.Build()
505 if err != nil {
506 return nil, fmt.Errorf("build filesystems: %w", err)
507 }
508
509 b.SourceFilesystems = sourceFilesystems
510 b.theBigFs = builder.theBigFs
511
512 return b, nil
513 }
514
515 type sourceFilesystemsBuilder struct {
516 logger loggers.Logger
517 p *paths.Paths
518 sourceFs afero.Fs
519 result *SourceFilesystems
520 theBigFs *filesystemsCollector
521 }
522
523 func newSourceFilesystemsBuilder(p *paths.Paths, logger loggers.Logger, b *BaseFs) *sourceFilesystemsBuilder {
524 sourceFs := hugofs.NewBaseFileDecorator(p.Fs.Source)
525 return &sourceFilesystemsBuilder{
526 p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs,
527 result: &SourceFilesystems{
528 conf: p.Cfg,
529 },
530 }
531 }
532
533 func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs) *SourceFilesystem {
534 return &SourceFilesystem{
535 Name: name,
536 Fs: fs,
537 SourceFs: b.sourceFs,
538 }
539 }
540
541 func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
542 if b.theBigFs == nil {
543 theBigFs, err := b.createMainOverlayFs(b.p)
544 if err != nil {
545 return nil, fmt.Errorf("create main fs: %w", err)
546 }
547
548 b.theBigFs = theBigFs
549 }
550
551 createView := func(componentID string, overlayFs *overlayfs.OverlayFs) *SourceFilesystem {
552 if b.theBigFs == nil || b.theBigFs.overlayMounts == nil {
553 return b.newSourceFilesystem(componentID, hugofs.NoOpFs)
554 }
555
556 fs := hugofs.NewComponentFs(
557 hugofs.ComponentFsOptions{
558 Fs: overlayFs,
559 Component: componentID,
560 DefaultContentLanguage: b.p.Cfg.DefaultContentLanguage(),
561 PathParser: b.p.Cfg.PathParser(),
562 },
563 )
564
565 return b.newSourceFilesystem(componentID, fs)
566 }
567
568 b.result.Archetypes = createView(files.ComponentFolderArchetypes, b.theBigFs.overlayMounts)
569 b.result.Layouts = createView(files.ComponentFolderLayouts, b.theBigFs.overlayMounts)
570 b.result.Assets = createView(files.ComponentFolderAssets, b.theBigFs.overlayMounts)
571 b.result.ResourcesCache = b.theBigFs.overlayResources
572 b.result.RootFss = b.theBigFs.rootFss
573
574 // data and i18n needs a different merge strategy.
575 overlayMountsPreserveDupes := b.theBigFs.overlayMounts.WithDirsMerger(hugofs.AppendDirsMerger)
576 b.result.Data = createView(files.ComponentFolderData, overlayMountsPreserveDupes)
577 b.result.I18n = createView(files.ComponentFolderI18n, overlayMountsPreserveDupes)
578 b.result.AssetsWithDuplicatesPreserved = createView(files.ComponentFolderAssets, overlayMountsPreserveDupes)
579
580 contentFs := hugofs.NewComponentFs(
581 hugofs.ComponentFsOptions{
582 Fs: b.theBigFs.overlayMountsContent,
583 Component: files.ComponentFolderContent,
584 DefaultContentLanguage: b.p.Cfg.DefaultContentLanguage(),
585 PathParser: b.p.Cfg.PathParser(),
586 },
587 )
588
589 b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs)
590 b.result.Work = hugofs.NewReadOnlyFs(b.theBigFs.overlayFull)
591
592 // Create static filesystem(s)
593 ms := make(map[string]*SourceFilesystem)
594 b.result.Static = ms
595
596 if b.theBigFs.staticPerLanguage != nil {
597 // Multihost mode
598 for k, v := range b.theBigFs.staticPerLanguage {
599 sfs := b.newSourceFilesystem(files.ComponentFolderStatic, v)
600 sfs.PublishFolder = k
601 ms[k] = sfs
602 }
603 } else {
604 bfs := hugofs.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
605 ms[""] = b.newSourceFilesystem(files.ComponentFolderStatic, bfs)
606 }
607
608 return b.result, nil
609 }
610
611 func (b *sourceFilesystemsBuilder) createMainOverlayFs(p *paths.Paths) (*filesystemsCollector, error) {
612 var staticFsMap map[string]*overlayfs.OverlayFs
613 if b.p.Cfg.IsMultihost() {
614 languages := b.p.Cfg.Languages()
615 staticFsMap = make(map[string]*overlayfs.OverlayFs)
616 for _, l := range languages {
617 staticFsMap[l.Lang] = overlayfs.New(overlayfs.Options{})
618 }
619 }
620
621 collector := &filesystemsCollector{
622 sourceProject: b.sourceFs,
623 sourceModules: b.sourceFs,
624 staticPerLanguage: staticFsMap,
625
626 overlayMounts: overlayfs.New(overlayfs.Options{}),
627 overlayMountsContent: overlayfs.New(overlayfs.Options{DirsMerger: hugofs.LanguageDirsMerger}),
628 overlayMountsStatic: overlayfs.New(overlayfs.Options{DirsMerger: hugofs.LanguageDirsMerger}),
629 overlayFull: overlayfs.New(overlayfs.Options{}),
630 overlayResources: overlayfs.New(overlayfs.Options{FirstWritable: true}),
631 }
632
633 mods := p.AllModules()
634
635 mounts := make([]mountsDescriptor, len(mods))
636
637 for i := range mods {
638 mod := mods[i]
639 dir := mod.Dir()
640
641 isMainProject := mod.Owner() == nil
642 mounts[i] = mountsDescriptor{
643 Module: mod,
644 dir: dir,
645 isMainProject: isMainProject,
646 ordinal: i,
647 }
648
649 }
650
651 err := b.createOverlayFs(collector, mounts)
652
653 return collector, err
654 }
655
656 func (b *sourceFilesystemsBuilder) isContentMount(mnt modules.Mount) bool {
657 return strings.HasPrefix(mnt.Target, files.ComponentFolderContent)
658 }
659
660 func (b *sourceFilesystemsBuilder) isStaticMount(mnt modules.Mount) bool {
661 return strings.HasPrefix(mnt.Target, files.ComponentFolderStatic)
662 }
663
664 func (b *sourceFilesystemsBuilder) createOverlayFs(
665 collector *filesystemsCollector,
666 mounts []mountsDescriptor,
667 ) error {
668 if len(mounts) == 0 {
669 appendNopIfEmpty := func(ofs *overlayfs.OverlayFs) *overlayfs.OverlayFs {
670 if ofs.NumFilesystems() > 0 {
671 return ofs
672 }
673 return ofs.Append(hugofs.NoOpFs)
674 }
675 collector.overlayMounts = appendNopIfEmpty(collector.overlayMounts)
676 collector.overlayMountsContent = appendNopIfEmpty(collector.overlayMountsContent)
677 collector.overlayMountsStatic = appendNopIfEmpty(collector.overlayMountsStatic)
678 collector.overlayMountsFull = appendNopIfEmpty(collector.overlayMountsFull)
679 collector.overlayFull = appendNopIfEmpty(collector.overlayFull)
680 collector.overlayResources = appendNopIfEmpty(collector.overlayResources)
681
682 return nil
683 }
684
685 for _, md := range mounts {
686 var (
687 fromTo []hugofs.RootMapping
688 fromToContent []hugofs.RootMapping
689 fromToStatic []hugofs.RootMapping
690 )
691
692 absPathify := func(path string) (string, string) {
693 if filepath.IsAbs(path) {
694 return "", path
695 }
696 return md.dir, hpaths.AbsPathify(md.dir, path)
697 }
698
699 for i, mount := range md.Mounts() {
700 // Add more weight to early mounts.
701 // When two mounts contain the same filename,
702 // the first entry wins.
703 mountWeight := (10 + md.ordinal) * (len(md.Mounts()) - i)
704
705 inclusionFilter, err := glob.NewFilenameFilter(
706 types.ToStringSlicePreserveString(mount.IncludeFiles),
707 types.ToStringSlicePreserveString(mount.ExcludeFiles),
708 )
709 if err != nil {
710 return err
711 }
712
713 base, filename := absPathify(mount.Source)
714
715 rm := hugofs.RootMapping{
716 From: mount.Target,
717 To: filename,
718 ToBase: base,
719 Module: md.Module.Path(),
720 ModuleOrdinal: md.ordinal,
721 IsProject: md.isMainProject,
722 Meta: &hugofs.FileMeta{
723 Watch: !mount.DisableWatch && md.Watch(),
724 Weight: mountWeight,
725 InclusionFilter: inclusionFilter,
726 },
727 }
728
729 isContentMount := b.isContentMount(mount)
730
731 lang := mount.Lang
732 if lang == "" && isContentMount {
733 lang = b.p.Cfg.DefaultContentLanguage()
734 }
735
736 rm.Meta.Lang = lang
737
738 if isContentMount {
739 fromToContent = append(fromToContent, rm)
740 } else if b.isStaticMount(mount) {
741 fromToStatic = append(fromToStatic, rm)
742 } else {
743 fromTo = append(fromTo, rm)
744 }
745 }
746
747 modBase := collector.sourceProject
748 if !md.isMainProject {
749 modBase = collector.sourceModules
750 }
751
752 sourceStatic := modBase
753
754 rmfs, err := hugofs.NewRootMappingFs(modBase, fromTo...)
755 if err != nil {
756 return err
757 }
758 rmfsContent, err := hugofs.NewRootMappingFs(modBase, fromToContent...)
759 if err != nil {
760 return err
761 }
762 rmfsStatic, err := hugofs.NewRootMappingFs(sourceStatic, fromToStatic...)
763 if err != nil {
764 return err
765 }
766
767 // We need to keep the list of directories for watching.
768 collector.addRootFs(rmfs)
769 collector.addRootFs(rmfsContent)
770 collector.addRootFs(rmfsStatic)
771
772 if collector.staticPerLanguage != nil {
773 for _, l := range b.p.Cfg.Languages() {
774 lang := l.Lang
775
776 lfs := rmfsStatic.Filter(func(rm hugofs.RootMapping) bool {
777 rlang := rm.Meta.Lang
778 return rlang == "" || rlang == lang
779 })
780 bfs := hugofs.NewBasePathFs(lfs, files.ComponentFolderStatic)
781 collector.staticPerLanguage[lang] = collector.staticPerLanguage[lang].Append(bfs)
782 }
783 }
784
785 getResourcesDir := func() string {
786 if md.isMainProject {
787 return b.p.AbsResourcesDir
788 }
789 _, filename := absPathify(files.FolderResources)
790 return filename
791 }
792
793 collector.overlayMounts = collector.overlayMounts.Append(rmfs)
794 collector.overlayMountsContent = collector.overlayMountsContent.Append(rmfsContent)
795 collector.overlayMountsStatic = collector.overlayMountsStatic.Append(rmfsStatic)
796 collector.overlayFull = collector.overlayFull.Append(hugofs.NewBasePathFs(modBase, md.dir))
797 collector.overlayResources = collector.overlayResources.Append(hugofs.NewBasePathFs(modBase, getResourcesDir()))
798
799 }
800
801 return nil
802 }
803
804 //lint:ignore U1000 useful for debugging
805 func printFs(fs afero.Fs, path string, w io.Writer) {
806 if fs == nil {
807 return
808 }
809 afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
810 if err != nil {
811 return err
812 }
813 if info.IsDir() {
814 return nil
815 }
816 var filename string
817 if fim, ok := info.(hugofs.FileMetaInfo); ok {
818 filename = fim.Meta().Filename
819 }
820 fmt.Fprintf(w, " %q %q\n", path, filename)
821 return nil
822 })
823 }
824
825 type filesystemsCollector struct {
826 sourceProject afero.Fs // Source for project folders
827 sourceModules afero.Fs // Source for modules/themes
828
829 overlayMounts *overlayfs.OverlayFs
830 overlayMountsContent *overlayfs.OverlayFs
831 overlayMountsStatic *overlayfs.OverlayFs
832 overlayMountsFull *overlayfs.OverlayFs
833 overlayFull *overlayfs.OverlayFs
834 overlayResources *overlayfs.OverlayFs
835
836 rootFss []*hugofs.RootMappingFs
837
838 // Set if in multihost mode
839 staticPerLanguage map[string]*overlayfs.OverlayFs
840 }
841
842 func (c *filesystemsCollector) addRootFs(rfs *hugofs.RootMappingFs) {
843 c.rootFss = append(c.rootFss, rfs)
844 }
845
846 type mountsDescriptor struct {
847 modules.Module
848 dir string
849 isMainProject bool
850 ordinal int // zero based starting from the project.
851 }