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  }