fs.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
---
fs.go (7093B)
---
1 // Copyright 2019 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 hugofs provides the file systems used by Hugo.
15 package hugofs
16
17 import (
18 "fmt"
19 "os"
20 "strings"
21
22 "github.com/bep/overlayfs"
23 "github.com/gohugoio/hugo/common/paths"
24 "github.com/gohugoio/hugo/config"
25 "github.com/spf13/afero"
26 )
27
28 // Os points to the (real) Os filesystem.
29 var Os = &afero.OsFs{}
30
31 // Fs holds the core filesystems used by Hugo.
32 type Fs struct {
33 // Source is Hugo's source file system.
34 // Note that this will always be a "plain" Afero filesystem:
35 // * afero.OsFs when running in production
36 // * afero.MemMapFs for many of the tests.
37 Source afero.Fs
38
39 // PublishDir is where Hugo publishes its rendered content.
40 // It's mounted inside publishDir (default /public).
41 PublishDir afero.Fs
42
43 // PublishDirStatic is the file system used for static files.
44 PublishDirStatic afero.Fs
45
46 // PublishDirServer is the file system used for serving the public directory with Hugo's development server.
47 // This will typically be the same as PublishDir, but not if --renderStaticToDisk is set.
48 PublishDirServer afero.Fs
49
50 // Os is an OS file system.
51 // NOTE: Field is currently unused.
52 Os afero.Fs
53
54 // WorkingDirReadOnly is a read-only file system
55 // restricted to the project working dir.
56 WorkingDirReadOnly afero.Fs
57
58 // WorkingDirWritable is a writable file system
59 // restricted to the project working dir.
60 WorkingDirWritable afero.Fs
61 }
62
63 func NewDefault(cfg config.Provider) *Fs {
64 workingDir, publishDir := getWorkingPublishDir(cfg)
65 fs := Os
66 return newFs(fs, fs, workingDir, publishDir)
67 }
68
69 // NewFrom creates a new Fs based on the provided Afero Fs
70 // as source and destination file systems.
71 // Useful for testing.
72 func NewFrom(fs afero.Fs, conf config.BaseConfig) *Fs {
73 return newFs(fs, fs, conf.WorkingDir, conf.PublishDir)
74 }
75
76 func NewFromOld(fs afero.Fs, cfg config.Provider) *Fs {
77 workingDir, publishDir := getWorkingPublishDir(cfg)
78 return newFs(fs, fs, workingDir, publishDir)
79 }
80
81 // NewFromSourceAndDestination creates a new Fs based on the provided Afero Fss
82 // as the source and destination file systems.
83 func NewFromSourceAndDestination(source, destination afero.Fs, cfg config.Provider) *Fs {
84 workingDir, publishDir := getWorkingPublishDir(cfg)
85 return newFs(source, destination, workingDir, publishDir)
86 }
87
88 func getWorkingPublishDir(cfg config.Provider) (string, string) {
89 workingDir := cfg.GetString("workingDir")
90 publishDir := cfg.GetString("publishDirDynamic")
91 if publishDir == "" {
92 publishDir = cfg.GetString("publishDir")
93 }
94 return workingDir, publishDir
95 }
96
97 func newFs(source, destination afero.Fs, workingDir, publishDir string) *Fs {
98 if publishDir == "" {
99 panic("publishDir is empty")
100 }
101
102 if workingDir == "." {
103 workingDir = ""
104 }
105
106 // Sanity check
107 if IsOsFs(source) && len(workingDir) < 2 {
108 panic("workingDir is too short")
109 }
110
111 // If this does not exist, it will be created later.
112 absPublishDir := paths.AbsPathify(workingDir, publishDir)
113
114 pubFs := NewBasePathFs(destination, absPublishDir)
115
116 return &Fs{
117 Source: source,
118 PublishDir: pubFs,
119 PublishDirServer: pubFs,
120 PublishDirStatic: pubFs,
121 Os: &afero.OsFs{},
122 WorkingDirReadOnly: getWorkingDirFsReadOnly(source, workingDir),
123 WorkingDirWritable: getWorkingDirFsWritable(source, workingDir),
124 }
125 }
126
127 func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs {
128 if workingDir == "" {
129 return NewReadOnlyFs(base)
130 }
131 return NewBasePathFs(NewReadOnlyFs(base), workingDir)
132 }
133
134 func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs {
135 if workingDir == "" {
136 return base
137 }
138 return NewBasePathFs(base, workingDir)
139 }
140
141 func isWrite(flag int) bool {
142 return flag&os.O_RDWR != 0 || flag&os.O_WRONLY != 0
143 }
144
145 // MakeReadableAndRemoveAllModulePkgDir makes any subdir in dir readable and then
146 // removes the root.
147 // TODO(bep) move this to a more suitable place.
148 func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) {
149 // Safe guard
150 // Note that the base directory changed from pkg to gomod_cache in Go 1.23.
151 if !strings.Contains(dir, "pkg") && !strings.Contains(dir, "gomod") {
152 panic(fmt.Sprint("invalid dir:", dir))
153 }
154
155 counter := 0
156 afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error {
157 if err != nil {
158 return nil
159 }
160 if info.IsDir() {
161 counter++
162 fs.Chmod(path, 0o777)
163 }
164 return nil
165 })
166
167 return counter, fs.RemoveAll(dir)
168 }
169
170 // IsOsFs returns whether fs is an OsFs or if it fs wraps an OsFs.
171 // TODO(bep) make this more robust.
172 func IsOsFs(fs afero.Fs) bool {
173 var isOsFs bool
174 WalkFilesystems(fs, func(fs afero.Fs) bool {
175 switch fs.(type) {
176 case *afero.MemMapFs:
177 isOsFs = false
178 case *afero.OsFs:
179 isOsFs = true
180 }
181 return isOsFs
182 })
183 return isOsFs
184 }
185
186 // FilesystemsUnwrapper returns the underlying filesystems.
187 type FilesystemsUnwrapper interface {
188 UnwrapFilesystems() []afero.Fs
189 }
190
191 // FilesystemUnwrapper returns the underlying filesystem.
192 type FilesystemUnwrapper interface {
193 UnwrapFilesystem() afero.Fs
194 }
195
196 // WalkFn is the walk func for WalkFilesystems.
197 type WalkFn func(fs afero.Fs) bool
198
199 // WalkFilesystems walks fs recursively and calls fn.
200 // If fn returns true, walking is stopped.
201 func WalkFilesystems(fs afero.Fs, fn WalkFn) bool {
202 if fn(fs) {
203 return true
204 }
205
206 if afs, ok := fs.(FilesystemUnwrapper); ok {
207 if WalkFilesystems(afs.UnwrapFilesystem(), fn) {
208 return true
209 }
210 } else if bfs, ok := fs.(FilesystemsUnwrapper); ok {
211 for _, sf := range bfs.UnwrapFilesystems() {
212 if WalkFilesystems(sf, fn) {
213 return true
214 }
215 }
216 } else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok {
217 for i := range cfs.NumFilesystems() {
218 if WalkFilesystems(cfs.Filesystem(i), fn) {
219 return true
220 }
221 }
222 }
223
224 return false
225 }
226
227 var _ FilesystemUnwrapper = (*filesystemsWrapper)(nil)
228
229 // NewBasePathFs creates a new BasePathFs.
230 func NewBasePathFs(source afero.Fs, path string) afero.Fs {
231 return WrapFilesystem(afero.NewBasePathFs(source, path), source)
232 }
233
234 // NewReadOnlyFs creates a new ReadOnlyFs.
235 func NewReadOnlyFs(source afero.Fs) afero.Fs {
236 return WrapFilesystem(afero.NewReadOnlyFs(source), source)
237 }
238
239 // WrapFilesystem is typically used to wrap a afero.BasePathFs to allow
240 // access to the underlying filesystem if needed.
241 func WrapFilesystem(container, content afero.Fs) afero.Fs {
242 return filesystemsWrapper{Fs: container, content: content}
243 }
244
245 type filesystemsWrapper struct {
246 afero.Fs
247 content afero.Fs
248 }
249
250 func (w filesystemsWrapper) UnwrapFilesystem() afero.Fs {
251 return w.content
252 }