github.com/jmooring/hugo@v0.47.1/hugofs/rootmapping_fs.go (about) 1 // Copyright 2018 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 "os" 18 "path/filepath" 19 "strings" 20 "time" 21 22 radix "github.com/hashicorp/go-immutable-radix" 23 "github.com/spf13/afero" 24 ) 25 26 var filepathSeparator = string(filepath.Separator) 27 28 // A RootMappingFs maps several roots into one. Note that the root of this filesystem 29 // is directories only, and they will be returned in Readdir and Readdirnames 30 // in the order given. 31 type RootMappingFs struct { 32 afero.Fs 33 rootMapToReal *radix.Node 34 virtualRoots []string 35 } 36 37 type rootMappingFile struct { 38 afero.File 39 fs *RootMappingFs 40 name string 41 } 42 43 type rootMappingFileInfo struct { 44 name string 45 } 46 47 func (fi *rootMappingFileInfo) Name() string { 48 return fi.name 49 } 50 51 func (fi *rootMappingFileInfo) Size() int64 { 52 panic("not implemented") 53 } 54 55 func (fi *rootMappingFileInfo) Mode() os.FileMode { 56 return os.ModeDir 57 } 58 59 func (fi *rootMappingFileInfo) ModTime() time.Time { 60 panic("not implemented") 61 } 62 63 func (fi *rootMappingFileInfo) IsDir() bool { 64 return true 65 } 66 67 func (fi *rootMappingFileInfo) Sys() interface{} { 68 return nil 69 } 70 71 func newRootMappingDirFileInfo(name string) *rootMappingFileInfo { 72 return &rootMappingFileInfo{name: name} 73 } 74 75 // NewRootMappingFs creates a new RootMappingFs on top of the provided with 76 // a list of from, to string pairs of root mappings. 77 // Note that 'from' represents a virtual root that maps to the actual filename in 'to'. 78 func NewRootMappingFs(fs afero.Fs, fromTo ...string) (*RootMappingFs, error) { 79 rootMapToReal := radix.New().Txn() 80 var virtualRoots []string 81 82 for i := 0; i < len(fromTo); i += 2 { 83 vr := filepath.Clean(fromTo[i]) 84 rr := filepath.Clean(fromTo[i+1]) 85 86 // We need to preserve the original order for Readdir 87 virtualRoots = append(virtualRoots, vr) 88 89 rootMapToReal.Insert([]byte(vr), rr) 90 } 91 92 return &RootMappingFs{Fs: fs, 93 virtualRoots: virtualRoots, 94 rootMapToReal: rootMapToReal.Commit().Root()}, nil 95 } 96 97 func (fs *RootMappingFs) Stat(name string) (os.FileInfo, error) { 98 if fs.isRoot(name) { 99 return newRootMappingDirFileInfo(name), nil 100 } 101 realName := fs.realName(name) 102 return fs.Fs.Stat(realName) 103 } 104 105 func (fs *RootMappingFs) isRoot(name string) bool { 106 return name == "" || name == filepathSeparator 107 108 } 109 110 func (fs *RootMappingFs) Open(name string) (afero.File, error) { 111 if fs.isRoot(name) { 112 return &rootMappingFile{name: name, fs: fs}, nil 113 } 114 realName := fs.realName(name) 115 f, err := fs.Fs.Open(realName) 116 if err != nil { 117 return nil, err 118 } 119 return &rootMappingFile{File: f, name: name, fs: fs}, nil 120 } 121 122 func (fs *RootMappingFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 123 if fs.isRoot(name) { 124 return newRootMappingDirFileInfo(name), false, nil 125 } 126 name = fs.realName(name) 127 if ls, ok := fs.Fs.(afero.Lstater); ok { 128 return ls.LstatIfPossible(name) 129 } 130 fi, err := fs.Stat(name) 131 return fi, false, err 132 } 133 134 func (fs *RootMappingFs) realName(name string) string { 135 key, val, found := fs.rootMapToReal.LongestPrefix([]byte(filepath.Clean(name))) 136 if !found { 137 return name 138 } 139 keystr := string(key) 140 141 return filepath.Join(val.(string), strings.TrimPrefix(name, keystr)) 142 } 143 144 func (f *rootMappingFile) Readdir(count int) ([]os.FileInfo, error) { 145 if f.File == nil { 146 dirsn := make([]os.FileInfo, 0) 147 for i := 0; i < len(f.fs.virtualRoots); i++ { 148 if count != -1 && i >= count { 149 break 150 } 151 dirsn = append(dirsn, newRootMappingDirFileInfo(f.fs.virtualRoots[i])) 152 } 153 return dirsn, nil 154 } 155 return f.File.Readdir(count) 156 157 } 158 159 func (f *rootMappingFile) Readdirnames(count int) ([]string, error) { 160 dirs, err := f.Readdir(count) 161 if err != nil { 162 return nil, err 163 } 164 dirss := make([]string, len(dirs)) 165 for i, d := range dirs { 166 dirss[i] = d.Name() 167 } 168 return dirss, nil 169 } 170 171 func (f *rootMappingFile) Name() string { 172 return f.name 173 } 174 175 func (f *rootMappingFile) Close() error { 176 if f.File == nil { 177 return nil 178 } 179 return f.File.Close() 180 }