github.com/v2fly/tools@v0.100.0/godoc/vfs/mapfs/mapfs.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package mapfs file provides an implementation of the FileSystem
     6  // interface based on the contents of a map[string]string.
     7  package mapfs // import "github.com/v2fly/tools/godoc/vfs/mapfs"
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	pathpkg "path"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/v2fly/tools/godoc/vfs"
    19  )
    20  
    21  // New returns a new FileSystem from the provided map.
    22  // Map keys must be forward slash-separated paths with
    23  // no leading slash, such as "file1.txt" or "dir/file2.txt".
    24  // New panics if any of the paths contain a leading slash.
    25  func New(m map[string]string) vfs.FileSystem {
    26  	// Verify all provided paths are relative before proceeding.
    27  	var pathsWithLeadingSlash []string
    28  	for p := range m {
    29  		if strings.HasPrefix(p, "/") {
    30  			pathsWithLeadingSlash = append(pathsWithLeadingSlash, p)
    31  		}
    32  	}
    33  	if len(pathsWithLeadingSlash) > 0 {
    34  		panic(fmt.Errorf("mapfs.New: invalid paths with a leading slash: %q", pathsWithLeadingSlash))
    35  	}
    36  
    37  	return mapFS(m)
    38  }
    39  
    40  // mapFS is the map based implementation of FileSystem
    41  type mapFS map[string]string
    42  
    43  func (fs mapFS) String() string { return "mapfs" }
    44  
    45  func (fs mapFS) RootType(p string) vfs.RootType {
    46  	return ""
    47  }
    48  
    49  func (fs mapFS) Close() error { return nil }
    50  
    51  func filename(p string) string {
    52  	return strings.TrimPrefix(p, "/")
    53  }
    54  
    55  func (fs mapFS) Open(p string) (vfs.ReadSeekCloser, error) {
    56  	b, ok := fs[filename(p)]
    57  	if !ok {
    58  		return nil, os.ErrNotExist
    59  	}
    60  	return nopCloser{strings.NewReader(b)}, nil
    61  }
    62  
    63  func fileInfo(name, contents string) os.FileInfo {
    64  	return mapFI{name: pathpkg.Base(name), size: len(contents)}
    65  }
    66  
    67  func dirInfo(name string) os.FileInfo {
    68  	return mapFI{name: pathpkg.Base(name), dir: true}
    69  }
    70  
    71  func (fs mapFS) Lstat(p string) (os.FileInfo, error) {
    72  	b, ok := fs[filename(p)]
    73  	if ok {
    74  		return fileInfo(p, b), nil
    75  	}
    76  	ents, _ := fs.ReadDir(p)
    77  	if len(ents) > 0 {
    78  		return dirInfo(p), nil
    79  	}
    80  	return nil, os.ErrNotExist
    81  }
    82  
    83  func (fs mapFS) Stat(p string) (os.FileInfo, error) {
    84  	return fs.Lstat(p)
    85  }
    86  
    87  // slashdir returns path.Dir(p), but special-cases paths not beginning
    88  // with a slash to be in the root.
    89  func slashdir(p string) string {
    90  	d := pathpkg.Dir(p)
    91  	if d == "." {
    92  		return "/"
    93  	}
    94  	if strings.HasPrefix(p, "/") {
    95  		return d
    96  	}
    97  	return "/" + d
    98  }
    99  
   100  func (fs mapFS) ReadDir(p string) ([]os.FileInfo, error) {
   101  	p = pathpkg.Clean(p)
   102  	var ents []string
   103  	fim := make(map[string]os.FileInfo) // base -> fi
   104  	for fn, b := range fs {
   105  		dir := slashdir(fn)
   106  		isFile := true
   107  		var lastBase string
   108  		for {
   109  			if dir == p {
   110  				base := lastBase
   111  				if isFile {
   112  					base = pathpkg.Base(fn)
   113  				}
   114  				if fim[base] == nil {
   115  					var fi os.FileInfo
   116  					if isFile {
   117  						fi = fileInfo(fn, b)
   118  					} else {
   119  						fi = dirInfo(base)
   120  					}
   121  					ents = append(ents, base)
   122  					fim[base] = fi
   123  				}
   124  			}
   125  			if dir == "/" {
   126  				break
   127  			} else {
   128  				isFile = false
   129  				lastBase = pathpkg.Base(dir)
   130  				dir = pathpkg.Dir(dir)
   131  			}
   132  		}
   133  	}
   134  	if len(ents) == 0 {
   135  		return nil, os.ErrNotExist
   136  	}
   137  
   138  	sort.Strings(ents)
   139  	var list []os.FileInfo
   140  	for _, dir := range ents {
   141  		list = append(list, fim[dir])
   142  	}
   143  	return list, nil
   144  }
   145  
   146  // mapFI is the map-based implementation of FileInfo.
   147  type mapFI struct {
   148  	name string
   149  	size int
   150  	dir  bool
   151  }
   152  
   153  func (fi mapFI) IsDir() bool        { return fi.dir }
   154  func (fi mapFI) ModTime() time.Time { return time.Time{} }
   155  func (fi mapFI) Mode() os.FileMode {
   156  	if fi.IsDir() {
   157  		return 0755 | os.ModeDir
   158  	}
   159  	return 0444
   160  }
   161  func (fi mapFI) Name() string     { return pathpkg.Base(fi.name) }
   162  func (fi mapFI) Size() int64      { return int64(fi.size) }
   163  func (fi mapFI) Sys() interface{} { return nil }
   164  
   165  type nopCloser struct {
   166  	io.ReadSeeker
   167  }
   168  
   169  func (nc nopCloser) Close() error { return nil }