decorators.go (5388B)
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
15
16 import (
17 "fmt"
18 "os"
19 "path/filepath"
20 "strings"
21
22 "github.com/spf13/afero"
23 )
24
25 var (
26 _ FilesystemUnwrapper = (*baseFileDecoratorFs)(nil)
27 )
28
29 func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs {
30 ffs := &baseFileDecoratorFs{Fs: fs}
31
32 decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
33 if !fi.IsDir() {
34 // Leave regular files as they are.
35 return fi, nil
36 }
37
38 return decorateFileInfo(fi, fs, nil, "", "", meta), nil
39 }
40
41 ffs.decorate = decorator
42
43 return ffs
44 }
45
46 func decoratePath(fs afero.Fs, createPath func(name string) string) afero.Fs {
47 ffs := &baseFileDecoratorFs{Fs: fs}
48
49 decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
50 path := createPath(name)
51
52 return decorateFileInfo(fi, fs, nil, "", path, nil), nil
53 }
54
55 ffs.decorate = decorator
56
57 return ffs
58 }
59
60 // DecorateBasePathFs adds Path info to files and directories in the
61 // provided BasePathFs, using the base as base.
62 func DecorateBasePathFs(base *afero.BasePathFs) afero.Fs {
63 basePath, _ := base.RealPath("")
64 if !strings.HasSuffix(basePath, filepathSeparator) {
65 basePath += filepathSeparator
66 }
67
68 ffs := &baseFileDecoratorFs{Fs: base}
69
70 decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) {
71 path := strings.TrimPrefix(name, basePath)
72
73 return decorateFileInfo(fi, base, nil, "", path, nil), nil
74 }
75
76 ffs.decorate = decorator
77
78 return ffs
79 }
80
81 // NewBaseFileDecorator decorates the given Fs to provide the real filename
82 // and an Opener func.
83 func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero.Fs {
84 ffs := &baseFileDecoratorFs{Fs: fs}
85
86 decorator := func(fi os.FileInfo, filename string) (os.FileInfo, error) {
87 // Store away the original in case it's a symlink.
88 meta := NewFileMeta()
89 meta.Name = fi.Name()
90
91 if fi.IsDir() {
92 meta.JoinStatFunc = func(name string) (FileMetaInfo, error) {
93 joinedFilename := filepath.Join(filename, name)
94 fi, _, err := lstatIfPossible(fs, joinedFilename)
95 if err != nil {
96 return nil, err
97 }
98
99 fi, err = ffs.decorate(fi, joinedFilename)
100 if err != nil {
101 return nil, err
102 }
103
104 return fi.(FileMetaInfo), nil
105 }
106 }
107
108 isSymlink := isSymlink(fi)
109 if isSymlink {
110 meta.OriginalFilename = filename
111 var link string
112 var err error
113 link, fi, err = evalSymlinks(fs, filename)
114 if err != nil {
115 return nil, err
116 }
117 filename = link
118 meta.IsSymlink = true
119 }
120
121 opener := func() (afero.File, error) {
122 return ffs.open(filename)
123 }
124
125 fim := decorateFileInfo(fi, ffs, opener, filename, "", meta)
126
127 for _, cb := range callbacks {
128 cb(fim)
129 }
130
131 return fim, nil
132 }
133
134 ffs.decorate = decorator
135 return ffs
136 }
137
138 func evalSymlinks(fs afero.Fs, filename string) (string, os.FileInfo, error) {
139 link, err := filepath.EvalSymlinks(filename)
140 if err != nil {
141 return "", nil, err
142 }
143
144 fi, err := fs.Stat(link)
145 if err != nil {
146 return "", nil, err
147 }
148
149 return link, fi, nil
150 }
151
152 type baseFileDecoratorFs struct {
153 afero.Fs
154 decorate func(fi os.FileInfo, filename string) (os.FileInfo, error)
155 }
156
157 func (fs *baseFileDecoratorFs) UnwrapFilesystem() afero.Fs {
158 return fs.Fs
159 }
160
161 func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) {
162 fi, err := fs.Fs.Stat(name)
163 if err != nil {
164 return nil, err
165 }
166
167 return fs.decorate(fi, name)
168 }
169
170 func (fs *baseFileDecoratorFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
171 var (
172 fi os.FileInfo
173 err error
174 ok bool
175 )
176
177 if lstater, isLstater := fs.Fs.(afero.Lstater); isLstater {
178 fi, ok, err = lstater.LstatIfPossible(name)
179 } else {
180 fi, err = fs.Fs.Stat(name)
181 }
182
183 if err != nil {
184 return nil, false, err
185 }
186
187 fi, err = fs.decorate(fi, name)
188
189 return fi, ok, err
190 }
191
192 func (fs *baseFileDecoratorFs) Open(name string) (afero.File, error) {
193 return fs.open(name)
194 }
195
196 func (fs *baseFileDecoratorFs) open(name string) (afero.File, error) {
197 f, err := fs.Fs.Open(name)
198 if err != nil {
199 return nil, err
200 }
201 return &baseFileDecoratorFile{File: f, fs: fs}, nil
202 }
203
204 type baseFileDecoratorFile struct {
205 afero.File
206 fs *baseFileDecoratorFs
207 }
208
209 func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) {
210 dirnames, err := l.File.Readdirnames(c)
211 if err != nil {
212 return nil, err
213 }
214
215 fisp := make([]os.FileInfo, 0, len(dirnames))
216
217 for _, dirname := range dirnames {
218 filename := dirname
219
220 if l.Name() != "" && l.Name() != filepathSeparator {
221 filename = filepath.Join(l.Name(), dirname)
222 }
223
224 // We need to resolve any symlink info.
225 fi, _, err := lstatIfPossible(l.fs.Fs, filename)
226 if err != nil {
227 if os.IsNotExist(err) {
228 continue
229 }
230 return nil, err
231 }
232 fi, err = l.fs.decorate(fi, filename)
233 if err != nil {
234 return nil, fmt.Errorf("decorate: %w", err)
235 }
236 fisp = append(fisp, fi)
237 }
238
239 return fisp, err
240 }