pax_global_header00006660000000000000000000000064141363247220014516gustar00rootroot0000000000000052 comment=ac4a4ca251d16918f11198d6f7b9a30d2ee9ff9b go-vfs-4.1.0/000077500000000000000000000000001413632472200127215ustar00rootroot00000000000000go-vfs-4.1.0/.github/000077500000000000000000000000001413632472200142615ustar00rootroot00000000000000go-vfs-4.1.0/.github/workflows/000077500000000000000000000000001413632472200163165ustar00rootroot00000000000000go-vfs-4.1.0/.github/workflows/main.yml000066400000000000000000000017011413632472200177640ustar00rootroot00000000000000on: push: branches: - master tags: - 'v*' pull_request: name: go-vfs env: GOLANGCI_LINT_VERSION: 1.42.1 jobs: test: strategy: matrix: go-version: - 1.16 - 1.17 - 1.x os: - macos-latest - ubuntu-latest - windows-latest runs-on: ${{ matrix.os }} steps: - name: Setup Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Checkout uses: actions/checkout@v1 - name: Go test run: go test ./... - name: Check formatting if: matrix.go-version == '1.x' && matrix.os == 'ubuntu-latest' run: | make format git diff --exit-code lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Lint uses: golangci/golangci-lint-action@v2 with: version: v${{ env.GOLANGCI_LINT_VERSION }} go-vfs-4.1.0/.gitignore000066400000000000000000000000041413632472200147030ustar00rootroot00000000000000/bingo-vfs-4.1.0/.golangci.yml000066400000000000000000000023451413632472200153110ustar00rootroot00000000000000linters: enable: - asciicheck - bodyclose - deadcode - depguard - dogsled - dupl - durationcheck - errcheck - errorlint - exhaustive - exportloopref - forbidigo - forcetypeassert - gochecknoglobals - goconst - gocritic - gocyclo - godot - godox - goerr113 - gofmt - gofumpt - goheader - goimports - gomoddirectives - gomodguard - goprintffuncname - gosec - gosimple - govet - ifshort - importas - ineffassign - lll - makezero - misspell - nakedret - nilerr - noctx - nolintlint - prealloc - predeclared - promlinter - revive - rowserrcheck - sqlclosecheck - staticcheck - structcheck - stylecheck - tagliatelle - thelper - typecheck - unconvert - unparam - unused - varcheck - wastedassign - whitespace disable: - cyclop - exhaustivestruct - funlen - gci - gochecknoinits - gocognit - gomnd - nestif - nlreturn - paralleltest - testpackage - tparallel - wrapcheck - wsl linters-settings: goimports: local-prefixes: github.com/twpayne/go-vfs misspell: locale: US issues: exclude-rules: - linters: - goerr113 text: "do not define dynamic errors, use wrapped static errors instead" go-vfs-4.1.0/LICENSE000066400000000000000000000020641413632472200137300ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 Tom Payne Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-vfs-4.1.0/Makefile000066400000000000000000000016741413632472200143710ustar00rootroot00000000000000GO?=go GOLANGCI_LINT_VERSION=$(shell grep GOLANGCI_LINT_VERSION: .github/workflows/main.yml | awk '{ print $$2 }') .PHONY: smoketest smoketest: test lint .PHONY: test test: ${GO} test ./... .PHONY: lint lint: ensure-golangci-lint ./bin/golangci-lint run .PHONY: format format: ensure-gofumports find . -name \*.go | xargs ./bin/gofumports -local github.com/twpayne/chezmoi -w .PHONY: ensure-tools ensure-tools: \ ensure-gofumports \ ensure-golangci-lint .PHONY: ensure-gofumports ensure-gofumports: if [ ! -x bin/gofumports ] ; then \ mkdir -p bin ; \ GOBIN=$(shell pwd)/bin ${GO} install mvdan.cc/gofumpt/gofumports@latest ; \ fi .PHONY: ensure-golangci-lint ensure-golangci-lint: if [ ! -x bin/golangci-lint ] || ( ./bin/golangci-lint --version | grep -Fqv "version ${GOLANGCI_LINT_VERSION}" ) ; then \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- v${GOLANGCI_LINT_VERSION} ; \ figo-vfs-4.1.0/README.md000066400000000000000000000136531413632472200142100ustar00rootroot00000000000000# go-vfs [![PkgGoDev](https://pkg.go.dev/badge/github.com/twpayne/go-vfs)](https://pkg.go.dev/github.com/twpayne/go-vfs) [![Report Card](https://goreportcard.com/badge/github.com/twpayne/go-vfs)](https://goreportcard.com/report/github.com/twpayne/go-vfs) Package `vfs` provides an abstraction of the `os` and `io` packages that is easy to test. ## Key features * File system abstraction layer for commonly-used `os` and `io` functions from the standard library. * Powerful and easy-to-use declarative testing framework, `vfst`. You declare the desired state of the filesystem after your code has run, and `vfst` tests that the filesystem matches that state. For a quick tour of `vfst`'s features, see [the examples in the documentation](https://godoc.org/github.com/twpayne/go-vfs/vfst#pkg-examples). * Compatibility with [`github.com/spf13/afero`](https://github.com/spf13/afero) and [`github.com/src-d/go-billy`](https://github.com/src-d/go-billy). ## Quick start `vfs` provides implementations of the `FS` interface: ```go // An FS is an abstraction over commonly-used functions in the os and io // packages. type FS interface { Chmod(name string, mode fs.FileMode) error Chown(name string, uid, git int) error Chtimes(name string, atime, mtime time.Time) error Create(name string) (*os.File, error) Glob(pattern string) ([]string, error) Lchown(name string, uid, git int) error Link(oldname, newname string) error Lstat(name string) (fs.FileInfo, error) Mkdir(name string, perm fs.FileMode) error Open(name string) (fs.File, error) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) PathSeparator() rune RawPath(name string) (string, error) ReadDir(dirname string) ([]fs.DirEntry, error) ReadFile(filename string) ([]byte, error) Readlink(name string) (string, error) Remove(name string) error RemoveAll(name string) error Rename(oldpath, newpath string) error Stat(name string) (fs.FileInfo, error) Symlink(oldname, newname string) error Truncate(name string, size int64) error WriteFile(filename string, data []byte, perm fs.FileMode) error } ``` To use `vfs`, you write your code to use the `FS` interface, and then use `vfst` to test it. `vfs` also provides functions `MkdirAll` (equivalent to `os.MkdirAll`), `Contains` (an improved `filepath.HasPrefix`), and `Walk` (equivalent to `filepath.Walk`) that operate on an `FS`. The implementations of `FS` provided are: * `OSFS` which calls the underlying `os` and `io` functions directly. * `PathFS` which transforms all paths to provide a poor-man's `chroot`. * `ReadOnlyFS` which prevents modification of the underlying FS. * `TestFS` which assists running tests on a real filesystem but in a temporary directory that is easily cleaned up. It uses `OSFS` under the hood. Example usage: ```go // writeConfigFile is the function we're going to test. It can make arbitrary // changes to the filesystem through fileSystem. func writeConfigFile(fileSystem vfs.FS) error { return fileSystem.WriteFile("/home/user/app.conf", []byte(`app config`), 0644) } // TestWriteConfigFile is our test function. func TestWriteConfigFile(t *testing.T) { // Create and populate an temporary directory with a home directory. fileSystem, cleanup, err := vfst.NewTestFS(map[string]interface{}{ "/home/user/.bashrc": "# contents of user's .bashrc\n", }) // Check that the directory was populated successfully. if err != nil { t.Fatalf("vfsTest.NewTestFS(_) == _, _, %v, want _, _, ", err) } // Ensure that the temporary directory is removed. defer cleanup() // Call the function we want to test. if err := writeConfigFile(fileSystem); err != nil { t.Error(err) } // Check properties of the filesystem after our function has modified it. vfst.RunTest(t, fileSystem, "app_conf", vfst.PathTest("/home/user/app.conf", vfst.TestModeIsRegular, vfst.TestModePerm(0644), vfst.TestContentsString("app config"), ), ) } ``` ## `github.com/spf13/afero` compatibility There is a compatibility shim for [`github.com/spf13/afero`](https://github.com/spf13/afero) in [`github.com/twpayne/go-vfsafero`](https://github.com/twpayne/go-vfsafero). This allows you to use `vfst` to test existing code that uses [`afero.FS`](https://godoc.org/github.com/spf13/afero#Fs). See [the documentation](https://godoc.org/github.com/twpayne/go-vfsafero) for an example. ## `github.com/src-d/go-billy` compatibility There is a compatibility shim for [`github.com/src-d/go-billy`](https://github.com/src-d/go-billy) in [`github.com/twpayne/go-vfsbilly`](https://github.com/twpayne/go-vfsbilly). This allows you to use `vfst` to test existing code that uses [`billy.Filesystem`](https://godoc.org/github.com/src-d/go-billy#Filesystem). See [the documentation](https://godoc.org/github.com/twpayne/go-vfsbilly) for an example. ## Motivation `vfs` was inspired by [`github.com/spf13/afero`](https://github.com/spf13/afero). So, why not use `afero`? * `afero` has several critical bugs in its in-memory mock filesystem implementation `MemMapFs`, to the point that it is unusable for non-trivial test cases. `vfs` does not attempt to implement an in-memory mock filesystem, and instead only provides a thin layer around the standard library's `os` and `io` packages, and as such should have fewer bugs. * `afero` does not support creating or reading symbolic links, and its `LstatIfPossible` interface is clumsy to use as it is not part of the `afero.Fs` interface. `vfs` provides out-of-the-box support for symbolic links with all methods in the `FS` interface. * `afero` has been effectively abandoned by its author, and a "friendly fork" ([`github.com/absfs/afero`](https://github.com/absfs/afero)) has not seen much activity. `vfs`, by providing much less functionality than `afero`, should be smaller and easier to maintain. ## License MIT go-vfs-4.1.0/contains.go000066400000000000000000000031031413632472200150630ustar00rootroot00000000000000package vfs import ( "errors" "io/fs" "os" "path/filepath" "syscall" ) // A Stater implements Stat. It is assumed that the fs.FileInfos returned by // Stat are compatible with os.SameFile. type Stater interface { Stat(string) (fs.FileInfo, error) } // Contains returns true if p is reachable by traversing through prefix. prefix // must exist, but p may not. It is an expensive but accurate alternative to the // deprecated filepath.HasPrefix. func Contains(fileSystem Stater, p, prefix string) (bool, error) { prefixFI, err := fileSystem.Stat(prefix) if err != nil { return false, err } for { fi, err := fileSystem.Stat(p) switch { case err == nil: if os.SameFile(fi, prefixFI) { return true, nil } goto TryParent case errors.Is(err, fs.ErrNotExist): goto TryParent case errors.Is(err, fs.ErrPermission): goto TryParent default: // Remove any fs.PathError or os.SyscallError wrapping, if present. Unwrap: for { var pathError *fs.PathError var syscallError *os.SyscallError switch { case errors.As(err, &pathError): err = pathError.Err case errors.As(err, &syscallError): err = syscallError.Err default: break Unwrap } } // Ignore some syscall.Errnos. var syscallErrno syscall.Errno if errors.As(err, &syscallErrno) { if _, ignore := ignoreErrnoInContains[syscallErrno]; ignore { goto TryParent } } return false, err } TryParent: parentDir := filepath.Dir(p) if parentDir == p { // Return when we stop making progress. return false, nil } p = parentDir } } go-vfs-4.1.0/emptyfs.go000066400000000000000000000045671413632472200147530ustar00rootroot00000000000000package vfs import ( "io/fs" "os" "time" ) // An EmptyFS is a VFS that does not contain any files. type EmptyFS struct{} func (EmptyFS) Chmod(name string, mode fs.FileMode) error { return os.ErrNotExist } func (EmptyFS) Chown(name string, uid, git int) error { return os.ErrNotExist } func (EmptyFS) Chtimes(name string, atime, mtime time.Time) error { return os.ErrNotExist } func (EmptyFS) Create(name string) (*os.File, error) { return nil, os.ErrNotExist } func (EmptyFS) Glob(pattern string) ([]string, error) { return nil, os.ErrNotExist } func (EmptyFS) Lchown(name string, uid, git int) error { return os.ErrNotExist } func (EmptyFS) Link(oldname, newname string) error { return os.ErrNotExist } func (EmptyFS) Lstat(name string) (fs.FileInfo, error) { return nil, os.ErrNotExist } func (EmptyFS) Mkdir(name string, perm fs.FileMode) error { return os.ErrNotExist } func (EmptyFS) Open(name string) (fs.File, error) { return nil, os.ErrNotExist } func (EmptyFS) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { return nil, os.ErrNotExist } func (EmptyFS) PathSeparator() rune { return '/' } func (EmptyFS) RawPath(name string) (string, error) { return name, nil } func (EmptyFS) ReadDir(dirname string) ([]fs.DirEntry, error) { return nil, os.ErrNotExist } func (EmptyFS) ReadFile(filename string) ([]byte, error) { return nil, os.ErrNotExist } func (EmptyFS) Readlink(name string) (string, error) { return "", os.ErrNotExist } func (EmptyFS) Remove(name string) error { return nil } func (EmptyFS) RemoveAll(name string) error { return nil } func (EmptyFS) Rename(oldpath, newpath string) error { return os.ErrNotExist } func (EmptyFS) Stat(name string) (fs.FileInfo, error) { return nil, os.ErrNotExist } func (EmptyFS) Symlink(oldname, newname string) error { return os.ErrNotExist } func (EmptyFS) Truncate(name string, size int64) error { return os.ErrNotExist } func (EmptyFS) WriteFile(filename string, data []byte, perm fs.FileMode) error { return os.ErrNotExist } go-vfs-4.1.0/go.mod000066400000000000000000000004031413632472200140240ustar00rootroot00000000000000module github.com/twpayne/go-vfs/v4 go 1.16 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/stretchr/testify v1.7.0 golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) go-vfs-4.1.0/go.sum000066400000000000000000000026231413632472200140570ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-vfs-4.1.0/mkdirall.go000066400000000000000000000034211413632472200150470ustar00rootroot00000000000000package vfs import ( "errors" "io/fs" "path/filepath" ) // A MkdirStater implements all the functionality needed by MkdirAll. type MkdirStater interface { Mkdir(name string, perm fs.FileMode) error Stat(name string) (fs.FileInfo, error) } // MkdirAll is equivalent to os.MkdirAll but operates on fileSystem. func MkdirAll(fileSystem MkdirStater, path string, perm fs.FileMode) error { err := fileSystem.Mkdir(path, perm) switch { case err == nil: // Mkdir was successful. return nil case errors.Is(err, fs.ErrExist): // path already exists, but we don't know whether it's a directory or // something else. We get this error if we try to create a subdirectory // of a non-directory, for example if the parent directory of path is a // file. There's a race condition here between the call to Mkdir and the // call to Stat but we can't avoid it because there's not enough // information in the returned error from Mkdir. We need to distinguish // between "path already exists and is already a directory" and "path // already exists and is not a directory". Between the call to Mkdir and // the call to Stat path might have changed. info, statErr := fileSystem.Stat(path) if statErr != nil { return statErr } if !info.IsDir() { return err } return nil case errors.Is(err, fs.ErrNotExist): // Parent directory does not exist. Create the parent directory // recursively, then try again. parentDir := filepath.Dir(path) if parentDir == "/" || parentDir == "." { // We cannot create the root directory or the current directory, so // return the original error. return err } if err := MkdirAll(fileSystem, parentDir, perm); err != nil { return err } return fileSystem.Mkdir(path, perm) default: // Some other error. return err } } go-vfs-4.1.0/osfs.go000066400000000000000000000056511413632472200142310ustar00rootroot00000000000000package vfs import ( "io/fs" "os" "path/filepath" "time" ) type osfs struct{} // OSFS is the FS that calls os and io functions directly. //nolint:gochecknoglobals var OSFS = &osfs{} // Chmod implements os.Chmod. func (osfs) Chmod(name string, mode fs.FileMode) error { return os.Chmod(name, mode) } // Chown implements os.Chown. func (osfs) Chown(name string, uid, gid int) error { return os.Chown(name, uid, gid) } // Chtimes implements os.Chtimes. func (osfs) Chtimes(name string, atime, mtime time.Time) error { return os.Chtimes(name, atime, mtime) } // Create implements os.Create. func (osfs) Create(name string) (*os.File, error) { return os.Create(name) } // Glob implements filepath.Glob. func (osfs) Glob(pattern string) ([]string, error) { return filepath.Glob(pattern) } // Lchown implements os.Lchown. func (osfs) Lchown(name string, uid, gid int) error { return os.Lchown(name, uid, gid) } // Link implements os.Link. func (osfs) Link(oldname, newname string) error { return os.Link(oldname, newname) } // Lstat implements os.Lstat. func (osfs) Lstat(name string) (fs.FileInfo, error) { return os.Lstat(name) } // Mkdir implements os.Mkdir. func (osfs) Mkdir(name string, perm fs.FileMode) error { return os.Mkdir(name, perm) } // Open implements os.Open. func (osfs) Open(name string) (fs.File, error) { return os.Open(name) } // OpenFile implements os.OpenFile. func (osfs) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { return os.OpenFile(name, flag, perm) } // PathSeparator returns os.PathSeparator. func (osfs) PathSeparator() rune { return os.PathSeparator } // RawPath returns the path to path on the underlying filesystem. func (osfs) RawPath(path string) (string, error) { return path, nil } // ReadDir implements os.ReadDir. func (osfs) ReadDir(dirname string) ([]fs.DirEntry, error) { return os.ReadDir(dirname) } // ReadFile implements os.ReadFile. func (osfs) ReadFile(name string) ([]byte, error) { return os.ReadFile(name) } // Readlink implements os.Readlink. func (osfs) Readlink(name string) (string, error) { return os.Readlink(name) } // Remove implements os.Remove. func (osfs) Remove(name string) error { return os.Remove(name) } // RemoveAll implements os.RemoveAll. func (osfs) RemoveAll(name string) error { return os.RemoveAll(name) } // Rename implements os.Rename. func (osfs) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } // Stat implements os.Stat. func (osfs) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) } // Symlink implements os.Symlink. func (osfs) Symlink(oldname, newname string) error { return os.Symlink(oldname, newname) } // Truncate implements os.Truncate. func (osfs) Truncate(name string, size int64) error { return os.Truncate(name, size) } // WriteFile implements os.WriteFile. func (osfs) WriteFile(filename string, data []byte, perm fs.FileMode) error { return os.WriteFile(filename, data, perm) } go-vfs-4.1.0/osfs_test.go000066400000000000000000000000351413632472200152570ustar00rootroot00000000000000package vfs var _ FS = OSFS go-vfs-4.1.0/pathfs.go000066400000000000000000000144621413632472200145440ustar00rootroot00000000000000package vfs import ( "io/fs" "os" "path" "path/filepath" "syscall" "time" ) // A PathFS operates on an existing FS, but prefixes all names with a path. All // names must be absolute paths, with the exception of symlinks, which may be // relative. type PathFS struct { fileSystem FS path string } // NewPathFS returns a new *PathFS operating on fileSystem and prefixing all // names with path. func NewPathFS(fileSystem FS, path string) *PathFS { return &PathFS{ fileSystem: fileSystem, path: filepath.ToSlash(path), } } // Chmod implements os.Chmod. func (p *PathFS) Chmod(name string, mode fs.FileMode) error { realName, err := p.join("Chmod", name) if err != nil { return err } return p.fileSystem.Chmod(realName, mode) } // Chown implements os.Chown. func (p *PathFS) Chown(name string, uid, gid int) error { realName, err := p.join("Chown", name) if err != nil { return err } return p.fileSystem.Chown(realName, uid, gid) } // Chtimes implements os.Chtimes. func (p *PathFS) Chtimes(name string, atime, mtime time.Time) error { realName, err := p.join("Chtimes", name) if err != nil { return err } return p.fileSystem.Chtimes(realName, atime, mtime) } // Create implements os.Create. func (p *PathFS) Create(name string) (*os.File, error) { realName, err := p.join("Create", name) if err != nil { return nil, err } return p.fileSystem.Create(realName) } // Glob implements filepath.Glob. func (p *PathFS) Glob(pattern string) ([]string, error) { realPattern, err := p.join("Glob", pattern) if err != nil { return nil, err } matches, err := p.fileSystem.Glob(realPattern) if err != nil { return nil, err } for i, match := range matches { matches[i], err = trimPrefix(match, p.path) if err != nil { return nil, err } } return matches, nil } // Join returns p's path joined with name. func (p *PathFS) Join(op, name string) (string, error) { return p.join("Join", name) } // Lchown implements os.Lchown. func (p *PathFS) Lchown(name string, uid, gid int) error { realName, err := p.join("Lchown", name) if err != nil { return err } return p.fileSystem.Lchown(realName, uid, gid) } // Link implements os.Link. func (p *PathFS) Link(oldname, newname string) error { var realOldname string if path.IsAbs(oldname) { var err error realOldname, err = p.join("Link", oldname) if err != nil { return err } } else { realOldname = oldname } realNewname, err := p.join("Link", newname) if err != nil { return err } return p.fileSystem.Link(realOldname, realNewname) } // Lstat implements os.Lstat. func (p *PathFS) Lstat(name string) (fs.FileInfo, error) { realName, err := p.join("Lstat", name) if err != nil { return nil, err } return p.fileSystem.Lstat(realName) } // Mkdir implements os.Mkdir. func (p *PathFS) Mkdir(name string, perm fs.FileMode) error { realName, err := p.join("Mkdir", name) if err != nil { return err } return p.fileSystem.Mkdir(realName, perm) } // Open implements os.Open. func (p *PathFS) Open(name string) (fs.File, error) { realName, err := p.join("Open", name) if err != nil { return nil, err } return p.fileSystem.Open(realName) } // OpenFile implements os.OpenFile. func (p *PathFS) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { realName, err := p.join("OpenFile", name) if err != nil { return nil, err } return p.fileSystem.OpenFile(realName, flag, perm) } // PathSeparator implements PathSeparator. func (p *PathFS) PathSeparator() rune { return p.fileSystem.PathSeparator() } // RawPath implements RawPath. func (p *PathFS) RawPath(path string) (string, error) { return p.join("RawPath", path) } // ReadDir implements os.ReadDir. func (p *PathFS) ReadDir(dirname string) ([]fs.DirEntry, error) { realDirname, err := p.join("ReadDir", dirname) if err != nil { return nil, err } return p.fileSystem.ReadDir(realDirname) } // ReadFile implements os.ReadFile. func (p *PathFS) ReadFile(name string) ([]byte, error) { realName, err := p.join("ReadFile", name) if err != nil { return nil, err } return p.fileSystem.ReadFile(realName) } // Readlink implements os.Readlink. func (p *PathFS) Readlink(name string) (string, error) { realName, err := p.join("Readlink", name) if err != nil { return "", err } return p.fileSystem.Readlink(realName) } // Remove implements os.Remove. func (p *PathFS) Remove(name string) error { realName, err := p.join("Remove", name) if err != nil { return err } return p.fileSystem.Remove(realName) } // RemoveAll implements os.RemoveAll. func (p *PathFS) RemoveAll(name string) error { realName, err := p.join("RemoveAll", name) if err != nil { return err } return p.fileSystem.RemoveAll(realName) } // Rename implements os.Rename. func (p *PathFS) Rename(oldpath, newpath string) error { realOldpath, err := p.join("Rename", oldpath) if err != nil { return err } realNewpath, err := p.join("Rename", newpath) if err != nil { return err } return p.fileSystem.Rename(realOldpath, realNewpath) } // Stat implements os.Stat. func (p *PathFS) Stat(name string) (fs.FileInfo, error) { realName, err := p.join("Stat", name) if err != nil { return nil, err } return p.fileSystem.Stat(realName) } // Symlink implements os.Symlink. func (p *PathFS) Symlink(oldname, newname string) error { var realOldname string if path.IsAbs(oldname) { var err error realOldname, err = p.join("Symlink", oldname) if err != nil { return err } } else { realOldname = oldname } realNewname, err := p.join("Symlink", newname) if err != nil { return err } return p.fileSystem.Symlink(realOldname, realNewname) } // Truncate implements os.Truncate. func (p *PathFS) Truncate(name string, size int64) error { realName, err := p.join("Truncate", name) if err != nil { return err } return p.fileSystem.Truncate(realName, size) } // WriteFile implements io.WriteFile. func (p *PathFS) WriteFile(filename string, data []byte, perm fs.FileMode) error { realFilename, err := p.join("WriteFile", filename) if err != nil { return err } return p.fileSystem.WriteFile(realFilename, data, perm) } // join returns p's path joined with name. func (p *PathFS) join(op, name string) (string, error) { name = relativizePath(name) if !path.IsAbs(name) { return "", &os.PathError{ Op: op, Path: name, Err: syscall.EPERM, } } return filepath.Join(p.path, name), nil } go-vfs-4.1.0/pathfs_test.go000066400000000000000000000000421413632472200155700ustar00rootroot00000000000000package vfs var _ FS = &PathFS{} go-vfs-4.1.0/posix.go000066400000000000000000000011051413632472200144070ustar00rootroot00000000000000//go:build !windows // +build !windows package vfs import ( "strings" "syscall" ) //nolint:gochecknoglobals var ignoreErrnoInContains = map[syscall.Errno]struct{}{ syscall.ELOOP: {}, syscall.EMLINK: {}, syscall.ENAMETOOLONG: {}, syscall.ENOENT: {}, syscall.EOVERFLOW: {}, } // relativizePath, on POSIX systems, just returns path. func relativizePath(path string) string { return path } // trimPrefix, on POSIX systems, trims prefix from path. func trimPrefix(path, prefix string) (string, error) { return strings.TrimPrefix(path, prefix), nil } go-vfs-4.1.0/readonlyfs.go000066400000000000000000000072651413632472200154300ustar00rootroot00000000000000package vfs import ( "io/fs" "os" "syscall" "time" ) // A ReadOnlyFS operates on an existing FS, but any methods that // modify the FS return an error. type ReadOnlyFS struct { fileSystem FS } // NewReadOnlyFS returns a new *ReadOnlyFS operating on fileSystem. func NewReadOnlyFS(fileSystem FS) *ReadOnlyFS { return &ReadOnlyFS{ fileSystem: fileSystem, } } // Chmod implements os.Chmod. func (r *ReadOnlyFS) Chmod(name string, mode fs.FileMode) error { return permError("Chmod", name) } // Chown implements os.Chown. func (r *ReadOnlyFS) Chown(name string, uid, gid int) error { return permError("Chown", name) } // Chtimes implements os.Chtimes. func (r *ReadOnlyFS) Chtimes(name string, atime, mtime time.Time) error { return permError("Chtimes", name) } // Create implements os.Create. func (r *ReadOnlyFS) Create(name string) (*os.File, error) { return nil, permError("Create", name) } // Glob implements filepath.Glob. func (r *ReadOnlyFS) Glob(pattern string) ([]string, error) { return r.fileSystem.Glob(pattern) } // Lchown implements os.Lchown. func (r *ReadOnlyFS) Lchown(name string, uid, gid int) error { return permError("Lchown", name) } // Link implements os.Link. func (r *ReadOnlyFS) Link(oldname, newname string) error { return permError("Link", newname) } // Lstat implements os.Lstat. func (r *ReadOnlyFS) Lstat(name string) (fs.FileInfo, error) { return r.fileSystem.Lstat(name) } // Mkdir implements os.Mkdir. func (r *ReadOnlyFS) Mkdir(name string, perm fs.FileMode) error { return permError("Mkdir", name) } // Open implements os.Open. func (r *ReadOnlyFS) Open(name string) (fs.File, error) { return r.fileSystem.Open(name) } // OpenFile implements os.OpenFile. func (r *ReadOnlyFS) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { if flag&(os.O_RDONLY|os.O_WRONLY|os.O_RDWR) != os.O_RDONLY { return nil, permError("OpenFile", name) } return r.fileSystem.OpenFile(name, flag, perm) } // PathSeparator implements PathSeparator. func (r *ReadOnlyFS) PathSeparator() rune { return r.fileSystem.PathSeparator() } // ReadDir implements os.ReadDir. func (r *ReadOnlyFS) ReadDir(name string) ([]fs.DirEntry, error) { return r.fileSystem.ReadDir(name) } // ReadFile implements os.ReadFile. func (r *ReadOnlyFS) ReadFile(name string) ([]byte, error) { return r.fileSystem.ReadFile(name) } // Readlink implements os.Readlink. func (r *ReadOnlyFS) Readlink(name string) (string, error) { return r.fileSystem.Readlink(name) } // Remove implements os.Remove. func (r *ReadOnlyFS) Remove(name string) error { return permError("Remove", name) } // RemoveAll implements os.RemoveAll. func (r *ReadOnlyFS) RemoveAll(name string) error { return permError("RemoveAll", name) } // Rename implements os.Rename. func (r *ReadOnlyFS) Rename(oldpath, newpath string) error { return permError("Rename", oldpath) } // RawPath implements RawPath. func (r *ReadOnlyFS) RawPath(path string) (string, error) { return r.fileSystem.RawPath(path) } // Stat implements os.Stat. func (r *ReadOnlyFS) Stat(name string) (fs.FileInfo, error) { return r.fileSystem.Stat(name) } // Symlink implements os.Symlink. func (r *ReadOnlyFS) Symlink(oldname, newname string) error { return permError("Symlink", newname) } // Truncate implements os.Truncate. func (r *ReadOnlyFS) Truncate(name string, size int64) error { return permError("Truncate", name) } // WriteFile implements os.WriteFile. func (r *ReadOnlyFS) WriteFile(filename string, data []byte, perm fs.FileMode) error { return permError("WriteFile", filename) } // permError returns an *os.PathError with Err syscall.EPERM. func permError(op, path string) error { return &os.PathError{ Op: op, Path: path, Err: syscall.EPERM, } } go-vfs-4.1.0/readonlyfs_test.go000066400000000000000000000000461413632472200164550ustar00rootroot00000000000000package vfs var _ FS = &ReadOnlyFS{} go-vfs-4.1.0/vfs.go000066400000000000000000000022611413632472200140470ustar00rootroot00000000000000// Package vfs provides an abstraction of the os and io packages that is easy to // test. package vfs import ( "io/fs" "os" "time" ) // An FS is an abstraction over commonly-used functions in the os and io // packages. type FS interface { Chmod(name string, mode fs.FileMode) error Chown(name string, uid, git int) error Chtimes(name string, atime, mtime time.Time) error Create(name string) (*os.File, error) Glob(pattern string) ([]string, error) Lchown(name string, uid, git int) error Link(oldname, newname string) error Lstat(name string) (fs.FileInfo, error) Mkdir(name string, perm fs.FileMode) error Open(name string) (fs.File, error) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) PathSeparator() rune RawPath(name string) (string, error) ReadDir(dirname string) ([]fs.DirEntry, error) ReadFile(filename string) ([]byte, error) Readlink(name string) (string, error) Remove(name string) error RemoveAll(name string) error Rename(oldpath, newpath string) error Stat(name string) (fs.FileInfo, error) Symlink(oldname, newname string) error Truncate(name string, size int64) error WriteFile(filename string, data []byte, perm fs.FileMode) error } go-vfs-4.1.0/vfst/000077500000000000000000000000001413632472200137035ustar00rootroot00000000000000go-vfs-4.1.0/vfst/contains_test.go000066400000000000000000000101501413632472200171040ustar00rootroot00000000000000package vfst import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" vfs "github.com/twpayne/go-vfs/v4" ) func TestContains(t *testing.T) { type test struct { p string prefix string expectErr bool expected bool } for _, tc := range []struct { name string root interface{} tests []test }{ { name: "core", root: map[string]interface{}{ "/home/user/file": "contents", }, tests: []test{ { p: "/home/user", prefix: "/home/user", expected: true, }, { p: "/home/user", prefix: "/home", expected: true, }, { p: "/home/user", prefix: "/", expected: true, }, { p: "/home/user/notexistpath", prefix: "/home/user", expected: true, }, { p: "/home/user/notexistpath", prefix: "/home", expected: true, }, { p: "/home/user/notexistpath", prefix: "/", expected: true, }, { p: "/home/user/notexistdir/notexistpath", prefix: "/home/user", expected: true, }, { p: "/home", prefix: "/home/user", expected: false, }, { p: "/", prefix: "/home/user", expected: false, }, { p: "/notexistpath", prefix: "/home/user", expected: false, }, { p: "/notexistpath", prefix: "/home", expected: false, }, { p: "/notexistpath", prefix: "/", expected: true, }, }, }, { name: "nonexistant_prefix", root: map[string]interface{}{ "/home/user/file": "contents", }, tests: []test{ { p: "/home/user", prefix: "/notexistpath", expectErr: true, }, { p: "/home/user", prefix: "/notexistdir/notexistpath", expectErr: true, }, }, }, { name: "symlink_dir", root: []interface{}{ map[string]interface{}{ "/home/user/file": "contents", }, map[string]interface{}{ "/home/symlink": &Symlink{Target: "user"}, }, }, tests: []test{ { p: "/home/symlink", prefix: "/home/user", expected: true, }, { p: "/home/symlink", prefix: "/home", expected: true, }, { p: "/home/symlink", prefix: "/", expected: true, }, { p: "/home/symlink/notexistpath", prefix: "/home/user", expected: true, }, { p: "/home/symlink/notexistpath", prefix: "/home", expected: true, }, { p: "/home/symlink/notexistpath", prefix: "/", expected: true, }, { p: "/home/symlink/notexistdir/notexistpath", prefix: "/home/user", expected: true, }, { p: "/home/symlink/notexistdir/notexistpath", prefix: "/home", expected: true, }, { p: "/home/symlink/notexistdir/notexistpath", prefix: "/", expected: true, }, { p: "/home/symlink/notexistpath", prefix: "/home/user", expected: true, }, }, }, { name: "loop", root: map[string]interface{}{ "/home/user": &Symlink{Target: "user"}, }, tests: []test{ { p: "/home/user", prefix: "/home/user", expectErr: true, }, { p: "/home/user/notexistpath", prefix: "/home/user", expectErr: true, }, { p: "/home/user/notexistdir/notexistpath", prefix: "/home/user", expectErr: true, }, { p: "/home/user/notexistdir/notexistpath", prefix: "/home", expected: true, }, }, }, } { t.Run(tc.name, func(t *testing.T) { fileSystem, cleanup, err := NewTestFS(tc.root) require.NoError(t, err) defer cleanup() for _, test := range tc.tests { actual, err := vfs.Contains(fileSystem, test.p, test.prefix) if test.expectErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, test.expected, actual) } } }) } } go-vfs-4.1.0/vfst/example_test.go000066400000000000000000000063031413632472200167260ustar00rootroot00000000000000package vfst_test import ( "testing" "github.com/twpayne/go-vfs/v4/vfst" ) func ExampleNewTestFS_complex() { Test := func(t *testing.T) { t.Helper() // Describe the structure of the filesystem using a map from filenames to // file or directory contents. root := map[string]interface{}{ // A string or []byte is sets a file's contents. "/home/user/.bashrc": "# contents of user's .bashrc\n", "/home/user/empty": []byte{}, // To set non-default permissions on a file, create an &vfst.File. "/home/user/bin/hello.sh": &vfst.File{ Perm: 0o755, Contents: []byte("echo hello\n"), }, // Directories can be nested. "/home/user/foo": map[string]interface{}{ "bar": map[string]interface{}{ "baz": "qux", }, }, // To set non-default permissions on a directory, create an // &vfst.Dir. "/root": &vfst.Dir{ Perm: 0o700, Entries: map[string]interface{}{ ".bashrc": "# contents of root's .bashrc\n", }, }, } // Create and populate an *vfst.TestFS fileSystem, cleanup, err := vfst.NewTestFS(root) if err != nil { t.Fatal(err) } defer cleanup() // Create tests by creating data structures containing Tests. tests := []interface{}{ // Test multiple properties of a single path with TestPath. vfst.TestPath("/home", vfst.TestIsDir, vfst.TestModePerm(0o755), ), vfst.TestPath("/home/user", vfst.TestIsDir, vfst.TestModePerm(0o755), ), vfst.TestPath("/home/user/.bashrc", vfst.TestModeIsRegular, vfst.TestModePerm(0o644), vfst.TestContentsString("# contents of user's .bashrc\n"), ), // Maps with string keys create sub tests with testing.T.Run. The key // is used as the test name. map[string]interface{}{ "home_user_empty": vfst.TestPath("/home/user/empty", vfst.TestModeIsRegular, vfst.TestModePerm(0o644), vfst.TestSize(0), ), "foo_bar_baz": vfst.TestPath("/home/user/foo/bar/baz", vfst.TestModeIsRegular, vfst.TestModePerm(0o644), vfst.TestContentsString("qux"), ), "root": []interface{}{ vfst.TestPath("/root", vfst.TestIsDir, vfst.TestModePerm(0o700), ), vfst.TestPath("/root/.bashrc", vfst.TestModeIsRegular, vfst.TestModePerm(0o644), vfst.TestContentsString("# contents of root's .bashrc\n"), ), }, }, } // RunTests traverses the data structure and running all Tests. vfst.RunTests(t, fileSystem, "", tests) // Optionally, calling fileSystem.Keep() prevents the cleanup function // from removing the temporary directory, so you can inspect it later. // The directory itself is returned by fileSystem.TempDir(). // fileSystem.Keep() t.Logf("fs.TempDir() == %s", fileSystem.TempDir()) } Test(&testing.T{}) } func ExampleNewTestFS() { Test := func(t *testing.T) { t.Helper() fileSystem, cleanup, err := vfst.NewTestFS(map[string]interface{}{ "/home/user/.bashrc": "# contents of user's .bashrc\n", }) if err != nil { t.Fatal(err) } defer cleanup() vfst.RunTests(t, fileSystem, "bashrc", vfst.TestPath("/home/user/.bashrc", vfst.TestModeIsRegular, vfst.TestContentsString("# contents of user's .bashrc\n"), ), ) } Test(&testing.T{}) } go-vfs-4.1.0/vfst/fs_test.go000066400000000000000000000020711413632472200157010ustar00rootroot00000000000000package vfst import ( "io/fs" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" vfs "github.com/twpayne/go-vfs/v4" ) func TestWalk(t *testing.T) { fileSystem, cleanup, err := NewTestFS(map[string]interface{}{ "/home/user/.bashrc": "# .bashrc contents\n", "/home/user/skip/foo": "bar", "/home/user/symlink": &Symlink{Target: "baz"}, }) require.NoError(t, err) defer cleanup() pathTypeMap := make(map[string]fs.FileMode) require.NoError(t, vfs.Walk(fileSystem, "/", func(path string, info fs.FileInfo, err error) error { assert.NoError(t, err) pathTypeMap[filepath.ToSlash(path)] = info.Mode() & fs.ModeType if filepath.Base(path) == "skip" { return vfs.SkipDir } return nil })) expectedPathTypeMap := map[string]fs.FileMode{ "/": fs.ModeDir, "/home": fs.ModeDir, "/home/user": fs.ModeDir, "/home/user/.bashrc": 0, "/home/user/skip": fs.ModeDir, "/home/user/symlink": fs.ModeSymlink, } assert.Equal(t, expectedPathTypeMap, pathTypeMap) } go-vfs-4.1.0/vfst/test.go000066400000000000000000000021361413632472200152130ustar00rootroot00000000000000//go:build !windows // +build !windows package vfst import ( "io/fs" "syscall" "testing" vfs "github.com/twpayne/go-vfs/v4" ) func init() { umask = fs.FileMode(syscall.Umask(0)) syscall.Umask(int(umask)) } // permEqual returns if perm1 and perm2 represent the same permissions. On // Windows, it always returns true. func permEqual(perm1, perm2 fs.FileMode) bool { return perm1&fs.ModePerm&^umask == perm2&fs.ModePerm&^umask } // TestSysNlink returns a PathTest that verifies that the the path's // Sys().(*syscall.Stat_t).Nlink is equal to wantNlink. If path's Sys() cannot // be converted to a *syscall.Stat_t, it does nothing. func TestSysNlink(wantNlink int) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() info, err := fileSystem.Lstat(path) if err != nil { t.Errorf("fileSystem.Lstat(%q) == %+v, %v, want !, ", path, info, err) return } if stat, ok := info.Sys().(*syscall.Stat_t); ok && int(stat.Nlink) != wantNlink { t.Errorf("fileSystem.Lstat(%q).Sys().(*syscall.Stat_t).Nlink == %d, want %d", path, stat.Nlink, wantNlink) } } } go-vfs-4.1.0/vfst/test_windows.go000066400000000000000000000010451413632472200167630ustar00rootroot00000000000000package vfst import ( "io/fs" "testing" "github.com/twpayne/go-vfs/v4" ) // permEqual returns if perm1 and perm2 represent the same permissions. On // Windows, it always returns true. func permEqual(perm1, perm2 fs.FileMode) bool { return true } // TestSysNlink returns a PathTest that verifies that the the path's // Sys().(*syscall.Stat_t).Nlink is equal to wantNlink. If path's Sys() cannot // be converted to a *syscall.Stat_t, it does nothing. func TestSysNlink(wantNlink int) PathTest { return func(*testing.T, vfs.FS, string) { } } go-vfs-4.1.0/vfst/testfs.go000066400000000000000000000023331413632472200155430ustar00rootroot00000000000000package vfst import ( "os" vfs "github.com/twpayne/go-vfs/v4" ) // A TestFS is a virtual filesystem based in a temporary directory. type TestFS struct { vfs.PathFS tempDir string keep bool } func newTestFS() (*TestFS, func(), error) { tempDir, err := os.MkdirTemp("", "go-vfs-vfst") if err != nil { return nil, nil, err } t := &TestFS{ PathFS: *vfs.NewPathFS(vfs.OSFS, tempDir), tempDir: tempDir, keep: false, } return t, t.cleanup, nil } // NewTestFS returns a new *TestFS populated with root and a cleanup function. func NewTestFS(root interface{}, builderOptions ...BuilderOption) (*TestFS, func(), error) { fileSystem, cleanup, err := newTestFS() if err != nil { cleanup() return nil, nil, err } if err := NewBuilder(builderOptions...).Build(fileSystem, root); err != nil { cleanup() return nil, nil, err } return fileSystem, cleanup, nil } // Keep prevents t's cleanup function from removing the temporary directory. It // has no effect if cleanup has already been called. func (t *TestFS) Keep() { t.keep = true } // TempDir returns t's temporary directory. func (t *TestFS) TempDir() string { return t.tempDir } func (t *TestFS) cleanup() { if !t.keep { os.RemoveAll(t.tempDir) } } go-vfs-4.1.0/vfst/vfst.go000066400000000000000000000333601413632472200152210ustar00rootroot00000000000000// Package vfst provides helper functions for testing code that uses // github.com/twpayne/go-vfs. package vfst import ( "bytes" "errors" "fmt" "io/fs" "log" "path/filepath" "sort" "strconv" "testing" vfs "github.com/twpayne/go-vfs/v4" ) //nolint:gochecknoglobals var umask fs.FileMode // A Dir is a directory with a specified permissions and zero or more Entries. type Dir struct { Perm fs.FileMode Entries map[string]interface{} } // A File is a file with a specified permissions and contents. type File struct { Perm fs.FileMode Contents []byte } // A Symlink is a symbolic link with a specified target. type Symlink struct { Target string } // A Test is a test on an vfs.FS. type Test func(*testing.T, vfs.FS) // A PathTest is a test on a specified path in an vfs.FS. type PathTest func(*testing.T, vfs.FS, string) // A BuilderOption sets an option on a Builder. type BuilderOption func(*Builder) // A Builder populates an vfs.FS. type Builder struct { umask fs.FileMode verbose bool } // BuilderUmask sets a builder's umask. func BuilderUmask(umask fs.FileMode) BuilderOption { return func(b *Builder) { b.umask = umask } } // BuilderVerbose sets a builder's verbose flag. When true, the builder will // log all operations with the standard log package. func BuilderVerbose(verbose bool) BuilderOption { return func(b *Builder) { b.verbose = verbose } } // NewBuilder returns a new Builder with the given options set. func NewBuilder(options ...BuilderOption) *Builder { b := &Builder{ umask: umask, verbose: false, } for _, option := range options { option(b) } return b } // build is a recursive helper for Build. func (b *Builder) build(fileSystem vfs.FS, path string, i interface{}) error { switch i := i.(type) { case []interface{}: for _, element := range i { if err := b.build(fileSystem, path, element); err != nil { return err } } return nil case *Dir: if parentDir := filepath.Dir(path); parentDir != "." { if err := b.MkdirAll(fileSystem, parentDir, 0o777); err != nil { return err } } if err := b.Mkdir(fileSystem, path, i.Perm); err != nil { return err } entryNames := make([]string, 0, len(i.Entries)) for entryName := range i.Entries { entryNames = append(entryNames, entryName) } sort.Strings(entryNames) for _, entryName := range entryNames { if err := b.build(fileSystem, filepath.Join(path, entryName), i.Entries[entryName]); err != nil { return err } } return nil case map[string]interface{}: if err := b.MkdirAll(fileSystem, path, 0o777); err != nil { return err } entryNames := make([]string, 0, len(i)) for entryName := range i { entryNames = append(entryNames, entryName) } sort.Strings(entryNames) for _, entryName := range entryNames { if err := b.build(fileSystem, filepath.Join(path, entryName), i[entryName]); err != nil { return err } } return nil case map[string]string: if err := b.MkdirAll(fileSystem, path, 0o777); err != nil { return err } entryNames := make([]string, 0, len(i)) for entryName := range i { entryNames = append(entryNames, entryName) } sort.Strings(entryNames) for _, entryName := range entryNames { if err := b.WriteFile(fileSystem, filepath.Join(path, entryName), []byte(i[entryName]), 0o666); err != nil { return err } } return nil case *File: return b.WriteFile(fileSystem, path, i.Contents, i.Perm) case string: return b.WriteFile(fileSystem, path, []byte(i), 0o666) case []byte: return b.WriteFile(fileSystem, path, i, 0o666) case *Symlink: return b.Symlink(fileSystem, i.Target, path) case nil: return nil default: return fmt.Errorf("%s: unsupported type %T", path, i) } } // Build populates fileSystem from root. func (b *Builder) Build(fileSystem vfs.FS, root interface{}) error { return b.build(fileSystem, "/", root) } // Mkdir creates directory path with permissions perm. It is idempotent and // will not fail if path already exists, is a directory, and has permissions // perm. func (b *Builder) Mkdir(fileSystem vfs.FS, path string, perm fs.FileMode) error { if info, err := fileSystem.Lstat(path); errors.Is(err, fs.ErrNotExist) { if b.verbose { log.Printf("mkdir -m 0%o %s", perm&^b.umask, path) } return fileSystem.Mkdir(path, perm&^b.umask) } else if err != nil { return err } else if !info.IsDir() { return fmt.Errorf("%s: not a directory", path) } else if gotPerm, wantPerm := info.Mode()&fs.ModePerm, perm&^b.umask; !permEqual(gotPerm, wantPerm) { return fmt.Errorf("%s has permissions 0%o, want 0%o", path, gotPerm, wantPerm) } return nil } // MkdirAll creates directory path and any missing parent directories with // permissions perm. It is idempotent and will not file if path already exists // and is a directory. func (b *Builder) MkdirAll(fileSystem vfs.FS, path string, perm fs.FileMode) error { // Check path. info, err := fileSystem.Lstat(path) switch { case err != nil && errors.Is(err, fs.ErrNotExist): // path does not exist, fallthrough to create. case err == nil && info.IsDir(): // path already exists and is a directory. return nil case err == nil && !info.IsDir(): // path already exists, but is not a directory. return err default: // Some other error. return err } // Create path. if b.verbose { log.Printf("mkdir -p -m 0%o %s", perm&^b.umask, path) } return vfs.MkdirAll(fileSystem, path, perm&^b.umask) } // Symlink creates a symbolic link from newname to oldname. It will create any // missing parent directories with default permissions. It is idempotent and // will not fail if the symbolic link already exists and points to oldname. func (b *Builder) Symlink(fileSystem vfs.FS, oldname, newname string) error { // Check newname. info, err := fileSystem.Lstat(newname) switch { case err == nil && info.Mode()&fs.ModeType != fs.ModeSymlink: // newname exists, but it's not a symlink. return fmt.Errorf("%s: not a symbolic link", newname) case err == nil: // newname exists, and it's a symlink. Check that it is a symlink to // oldname. gotTarget, err := fileSystem.Readlink(newname) if err != nil { return err } if gotTarget != oldname { return fmt.Errorf("%s: has target %s, want %s", newname, gotTarget, oldname) } return nil case errors.Is(err, fs.ErrNotExist): // newname does not exist, fallthrough to create. default: // Some other error, return it. return err } // Create newname. if err := b.MkdirAll(fileSystem, filepath.Dir(newname), 0o777); err != nil { return err } if b.verbose { log.Printf("ln -s %s %s", oldname, newname) } return fileSystem.Symlink(oldname, newname) } // WriteFile writes file path withe contents contents and permissions perm. It // will create any missing parent directories with default permissions. It is // idempotent and will not fail if the file already exists, has contents // contents, and permissions perm. func (b *Builder) WriteFile(fileSystem vfs.FS, path string, contents []byte, perm fs.FileMode) error { if info, err := fileSystem.Lstat(path); errors.Is(err, fs.ErrNotExist) { // fallthrough to fileSystem.WriteFile } else if err != nil { return err } else if !info.Mode().IsRegular() { return fmt.Errorf("%s: not a regular file", path) } else if gotPerm, wantPerm := info.Mode()&fs.ModePerm, perm&^b.umask; !permEqual(gotPerm, wantPerm) { return fmt.Errorf("%s has permissions 0%o, want 0%o", path, gotPerm, wantPerm) } else { gotContents, err := fileSystem.ReadFile(path) if err != nil { return err } if !bytes.Equal(gotContents, contents) { return fmt.Errorf("%s: has contents %v, want %v", path, gotContents, contents) } return nil } if err := b.MkdirAll(fileSystem, filepath.Dir(path), 0o777); err != nil { return err } if b.verbose { log.Printf("install -m 0%o /dev/null %s", perm&^b.umask, path) } return fileSystem.WriteFile(path, contents, perm&^b.umask) } // runTests recursively runs tests on fileSystem. func runTests(t *testing.T, fileSystem vfs.FS, name string, test interface{}) { t.Helper() prefix := "" if name != "" { prefix = name + "_" } switch test := test.(type) { case Test: test(t, fileSystem) case []Test: for i, test := range test { t.Run(prefix+strconv.Itoa(i), func(t *testing.T) { //nolint:scopelint test(t, fileSystem) }) } case map[string]Test: testNames := make([]string, 0, len(test)) for testName := range test { testNames = append(testNames, testName) } sort.Strings(testNames) for _, testName := range testNames { t.Run(prefix+testName, func(t *testing.T) { //nolint:scopelint test[testName](t, fileSystem) }) } case []interface{}: for _, u := range test { runTests(t, fileSystem, name, u) } case map[string]interface{}: testNames := make([]string, 0, len(test)) for testName := range test { testNames = append(testNames, testName) } sort.Strings(testNames) for _, testName := range testNames { runTests(t, fileSystem, prefix+testName, test[testName]) } case nil: default: t.Fatalf("%s: unsupported type %T", name, test) } } // RunTests recursively runs tests on fileSystem. func RunTests(t *testing.T, fileSystem vfs.FS, name string, tests ...interface{}) { t.Helper() runTests(t, fileSystem, name, tests) } // TestContents returns a PathTest that verifies the contents of the file are // equal to wantContents. func TestContents(wantContents []byte) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() if gotContents, err := fileSystem.ReadFile(path); err != nil || !bytes.Equal(gotContents, wantContents) { t.Errorf("fileSystem.ReadFile(%q) == %v, %v, want %v, ", path, gotContents, err, wantContents) } } } // TestContentsString returns a PathTest that verifies the contetnts of the // file are equal to wantContentsStr. func TestContentsString(wantContentsStr string) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() if gotContents, err := fileSystem.ReadFile(path); err != nil || string(gotContents) != wantContentsStr { t.Errorf("fileSystem.ReadFile(%q) == %q, %v, want %q, ", path, gotContents, err, wantContentsStr) } } } // testDoesNotExist is a PathTest that verifies that a file or directory does // not exist. //nolint:gochecknoglobals var testDoesNotExist = func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() _, err := fileSystem.Lstat(path) if got, want := errors.Is(err, fs.ErrNotExist), true; got != want { t.Errorf("_, err := fileSystem.Lstat(%q); errors.Is(err, fs.ErrNotExist) == %v, want %v", path, got, want) } } // TestDoesNotExist is a PathTest that verifies that a file or directory does // not exist. //nolint:gochecknoglobals var TestDoesNotExist PathTest = testDoesNotExist // TestIsDir is a PathTest that verifies that the path is a directory. //nolint:gochecknoglobals var TestIsDir = TestModeType(fs.ModeDir) // TestModePerm returns a PathTest that verifies that the path's permissions // are equal to wantPerm. func TestModePerm(wantPerm fs.FileMode) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() info, err := fileSystem.Lstat(path) if err != nil { t.Errorf("fileSystem.Lstat(%q) == %+v, %v, want !, ", path, info, err) return } if gotPerm := info.Mode() & fs.ModePerm; !permEqual(gotPerm, wantPerm) { t.Errorf("fileSystem.Lstat(%q).Mode()&fs.ModePerm == 0%o, want 0%o", path, gotPerm, wantPerm) } } } // TestModeIsRegular is a PathTest that tests that the path is a regular file. //nolint:gochecknoglobals var TestModeIsRegular = TestModeType(0) // TestModeType returns a PathTest that verifies that the path's mode type is // equal to wantModeType. func TestModeType(wantModeType fs.FileMode) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() info, err := fileSystem.Lstat(path) if err != nil { t.Errorf("fileSystem.Lstat(%q) == %+v, %v, want !, ", path, info, err) return } if gotModeType := info.Mode() & fs.ModeType; gotModeType != wantModeType { t.Errorf("fileSystem.Lstat(%q).Mode()&fs.ModeType == %v, want %v", path, gotModeType, wantModeType) } } } // TestPath returns a Test that runs pathTests on path. func TestPath(path string, pathTests ...PathTest) Test { return func(t *testing.T, fileSystem vfs.FS) { t.Helper() for i, pathTest := range pathTests { t.Run(strconv.Itoa(i), func(t *testing.T) { //nolint:scopelint pathTest(t, fileSystem, path) }) } } } // TestSize returns a PathTest that tests that path's Size() is equal to // wantSize. func TestSize(wantSize int64) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() info, err := fileSystem.Lstat(path) if err != nil { t.Errorf("fileSystem.Lstat(%q) == %+v, %v, want !, ", path, info, err) return } if gotSize := info.Size(); gotSize != wantSize { t.Errorf("fileSystem.Lstat(%q).Size() == %d, want %d", path, gotSize, wantSize) } } } // TestSymlinkTarget returns a PathTest that tests that path's target is wantTarget. func TestSymlinkTarget(wantTarget string) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() if gotTarget, err := fileSystem.Readlink(path); err != nil || gotTarget != wantTarget { t.Errorf("fileSystem.Readlink(%q) == %q, %v, want %q, ", path, gotTarget, err, wantTarget) return } } } // TestMinSize returns a PathTest that tests that path's Size() is at least // wantMinSize. func TestMinSize(wantMinSize int64) PathTest { return func(t *testing.T, fileSystem vfs.FS, path string) { t.Helper() info, err := fileSystem.Lstat(path) if err != nil { t.Errorf("fileSystem.Lstat(%q) == %+v, %v, want !, ", path, info, err) return } if gotSize := info.Size(); gotSize < wantMinSize { t.Errorf("fileSystem.Lstat(%q).Size() == %d, want >=%d", path, gotSize, wantMinSize) } } } go-vfs-4.1.0/vfst/vfst_test.go000066400000000000000000000230311413632472200162520ustar00rootroot00000000000000package vfst import ( "errors" "io/fs" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" vfs "github.com/twpayne/go-vfs/v4" ) func TestBuilderBuild(t *testing.T) { for _, tc := range []struct { name string umask fs.FileMode root interface{} tests interface{} }{ { name: "empty", umask: 0o22, tests: []Test{}, }, { name: "dir", umask: 0o22, root: map[string]interface{}{ "foo": &Dir{ Perm: 0o755, Entries: map[string]interface{}{ "bar": "baz", }, }, }, tests: []Test{ TestPath("/foo", TestIsDir, TestModePerm(0o755), ), TestPath("/foo/bar", TestModeIsRegular, TestModePerm(0o644), TestContentsString("baz"), ), }, }, { name: "map_string_string", umask: 0o22, root: map[string]string{ "foo": "bar", }, tests: []Test{ TestPath("/foo", TestModeIsRegular, TestModePerm(0o644), TestContentsString("bar"), ), }, }, { name: "map_string_empty_interface", umask: 0o22, root: map[string]interface{}{ "foo": "bar", "baz": &File{Perm: 0o755, Contents: []byte("qux")}, "dir": &Dir{Perm: 0o700}, }, tests: []Test{ TestPath("/foo", TestModeIsRegular, TestModePerm(0o644), TestSize(3), TestContentsString("bar"), ), TestPath("/baz", TestModeIsRegular, TestModePerm(0o755), TestSize(3), TestContentsString("qux"), ), TestPath("/dir", TestIsDir, TestModePerm(0o700), ), }, }, { name: "long_paths", umask: 0o22, root: map[string]string{ "/foo/bar": "baz", }, tests: []Test{ TestPath("/foo", TestIsDir, TestModePerm(0o755), ), TestPath("/foo/bar", TestModeIsRegular, TestModePerm(0o644), TestSize(3), TestContentsString("baz"), ), }, }, { name: "symlink", umask: 0o22, root: map[string]interface{}{ "foo": &Symlink{Target: "bar"}, }, tests: []Test{ TestPath("/foo", TestModeType(fs.ModeSymlink), TestSymlinkTarget("bar"), ), }, }, } { t.Run(tc.name, func(t *testing.T) { fileSystem, cleanup, err := NewTestFS(tc.root, BuilderUmask(tc.umask), BuilderVerbose(true)) require.NoError(t, err) defer cleanup() RunTests(t, fileSystem, "", tc.tests) }) } } // TestCoverage exercises as much functionality as possible to increase test // coverage. func TestCoverage(t *testing.T) { fileSystem, cleanup, err := NewTestFS(map[string]interface{}{ "/home/user/.bashrc": "# contents of user's .bashrc\n", "/home/user/empty": []byte{}, "/home/user/symlink": &Symlink{Target: "empty"}, "/home/user/bin/hello.sh": &File{ Perm: 0o755, Contents: []byte("echo hello\n"), }, "/home/user/foo": map[string]interface{}{ "bar": map[string]interface{}{ "baz": "qux", }, }, "/root": &Dir{ Perm: 0o700, Entries: map[string]interface{}{ ".bashrc": "# contents of root's .bashrc\n", }, }, }) require.NoError(t, err) defer cleanup() RunTests(t, fileSystem, "", []interface{}{ TestPath("/home", TestIsDir, TestModePerm(0o755), ), TestPath("/notexist", TestDoesNotExist), map[string]Test{ "home_user_bashrc": TestPath("/home/user/.bashrc", TestModeIsRegular, TestModePerm(0o644), TestContentsString("# contents of user's .bashrc\n"), TestMinSize(1), TestSysNlink(1), ), }, map[string]interface{}{ "home_user_empty": TestPath("/home/user/empty", TestModeIsRegular, TestModePerm(0o644), TestSize(0), ), "home_user_symlink": TestPath("/home/user/symlink", TestModeType(fs.ModeSymlink), TestSymlinkTarget("empty"), ), "foo_bar_baz": []Test{ TestPath("/home/user/foo/bar/baz", TestModeIsRegular, TestModePerm(0o644), TestContentsString("qux"), ), }, "root": []interface{}{ TestPath("/root", TestIsDir, TestModePerm(0o700), ), TestPath("/root/.bashrc", TestModeIsRegular, TestModePerm(0o644), TestContentsString("# contents of root's .bashrc\n"), ), }, }, }) } func TestErrors(t *testing.T) { errSkip := errors.New("skip") for name, f := range map[string]func(*Builder, vfs.FS) error{ "write_file_with_different_content": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/.bashrc", nil, 0o644) }, "write_file_with_different_perms": func(b *Builder, fileSystem vfs.FS) error { if permEqual(0o644, 0o755) { return errSkip } return b.WriteFile(fileSystem, "/home/user/.bashrc", []byte("# bashrc\n"), 0o755) }, "write_file_to_existing_dir": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user", nil, 0o644) }, "write_file_to_existing_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/symlink", nil, 0o644) }, "write_file_via_existing_dir": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/empty/foo", nil, 0o644) }, "write_file_via_existing_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/symlink/foo", nil, 0o644) }, "mkdir_existing_dir_with_different_perms": func(b *Builder, fileSystem vfs.FS) error { if permEqual(0o755, 0o666) { return errSkip } return b.Mkdir(fileSystem, "/home/user", 0o666) }, "mkdir_to_existing_file": func(b *Builder, fileSystem vfs.FS) error { return b.Mkdir(fileSystem, "/home/user/empty", 0o755) }, "mkdir_to_existing_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.Mkdir(fileSystem, "/home/user/symlink", 0o755) }, "mkdir_all_to_existing_file": func(b *Builder, fileSystem vfs.FS) error { return b.Mkdir(fileSystem, "/home/user/empty", 0o755) }, "mkdir_all_via_existing_file": func(b *Builder, fileSystem vfs.FS) error { return b.MkdirAll(fileSystem, "/home/user/empty/foo", 0o755) }, "mkdir_all_via_existing_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.MkdirAll(fileSystem, "/home/user/symlink/foo", 0o755) }, } { t.Run(name, func(t *testing.T) { fileSystem, cleanup, err := newTestFS() require.NoError(t, err) defer cleanup() b := NewBuilder(BuilderVerbose(true)) root := []interface{}{ map[string]interface{}{ "/home/user/.bashrc": "# bashrc\n", "/home/user/empty": []byte{}, "/home/user/foo": &Dir{Perm: 0o755}, }, map[string]interface{}{ "/home/user/symlink": &Symlink{Target: "empty"}, }, } require.NoError(t, b.Build(fileSystem, root)) assert.Error(t, f(b, fileSystem)) }) } } func TestGlob(t *testing.T) { fileSystem, cleanup, err := NewTestFS(map[string]interface{}{ "/home/user/.bash_profile": "# contents of .bash_profile\n", "/home/user/.bashrc": "# contents of .bashrc\n", "/home/user/.zshrc": "# contents of .zshrc\n", }) require.NoError(t, err) defer cleanup() for _, tc := range []struct { name string pattern string expectedMatches []string }{ { name: "all", pattern: "/home/user/*", expectedMatches: []string{ "/home/user/.bash_profile", "/home/user/.bashrc", "/home/user/.zshrc", }, }, { name: "star_rc", pattern: "/home/user/*rc", expectedMatches: []string{ "/home/user/.bashrc", "/home/user/.zshrc", }, }, { name: "all_subdir", pattern: "/home/*/*", expectedMatches: []string{ "/home/user/.bash_profile", "/home/user/.bashrc", "/home/user/.zshrc", }, }, } { t.Run(tc.name, func(t *testing.T) { matches, err := fileSystem.Glob(tc.pattern) require.NoError(t, err) require.Len(t, matches, len(tc.expectedMatches)) for i, match := range matches { assert.True(t, filepath.IsAbs(match)) expected := filepath.FromSlash(tc.expectedMatches[i]) actual := strings.TrimPrefix(match, filepath.VolumeName(matches[i])) assert.Equal(t, expected, actual) } }) } } func TestIdempotency(t *testing.T) { for name, f := range map[string]func(*Builder, vfs.FS) error{ "write_new_file": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/empty", nil, 0o644) }, "write_file_with_same_content_and_perms": func(b *Builder, fileSystem vfs.FS) error { return b.WriteFile(fileSystem, "/home/user/.bashrc", []byte("# bashrc\n"), 0o644) }, "mkdir_existing_dir_with_same_perms": func(b *Builder, fileSystem vfs.FS) error { return b.Mkdir(fileSystem, "/home/user", 0o755) }, "mkdir_new_dir": func(b *Builder, fileSystem vfs.FS) error { return b.Mkdir(fileSystem, "/home/user/foo", 0o755) }, "mkdir_all_existing_dir": func(b *Builder, fileSystem vfs.FS) error { return b.MkdirAll(fileSystem, "/home/user", 0o755) }, "mkdir_all_new_dir": func(b *Builder, fileSystem vfs.FS) error { return b.MkdirAll(fileSystem, "/usr/bin", 0o755) }, "symlink_new_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.Symlink(fileSystem, ".bashrc", "/home/user/symlink2") }, "symlink_existing_symlink": func(b *Builder, fileSystem vfs.FS) error { return b.Symlink(fileSystem, ".bashrc", "/home/user/symlink") }, } { t.Run(name, func(t *testing.T) { fileSystem, cleanup, err := newTestFS() require.NoError(t, err) defer cleanup() b := NewBuilder(BuilderVerbose(true)) root := map[string]interface{}{ "/home/user/.bashrc": "# bashrc\n", "/home/user/symlink": &Symlink{Target: ".bashrc"}, } require.NoError(t, b.Build(fileSystem, root)) assert.NoError(t, f(b, fileSystem)) }) } } go-vfs-4.1.0/vfst/vfst_unix_test.go000066400000000000000000000001551413632472200173170ustar00rootroot00000000000000//go:build !windows // +build !windows package vfst import "syscall" func init() { syscall.Umask(0o22) } go-vfs-4.1.0/walk.go000066400000000000000000000040321413632472200142050ustar00rootroot00000000000000package vfs //nolint:godox // FIXME implement path/filepath.WalkDir import ( "errors" "io/fs" "path/filepath" "sort" ) // SkipDir is fs.SkipDir. //nolint:gochecknoglobals var SkipDir = fs.SkipDir // A LstatReadDirer implements all the functionality needed by Walk. type LstatReadDirer interface { Lstat(name string) (fs.FileInfo, error) ReadDir(name string) ([]fs.DirEntry, error) } type dirEntriesByName []fs.DirEntry func (is dirEntriesByName) Len() int { return len(is) } func (is dirEntriesByName) Less(i, j int) bool { return is[i].Name() < is[j].Name() } func (is dirEntriesByName) Swap(i, j int) { is[i], is[j] = is[j], is[i] } // walk recursively walks fileSystem from path. func walk(fileSystem LstatReadDirer, path string, walkFn filepath.WalkFunc, info fs.FileInfo, err error) error { if err != nil { return walkFn(path, info, err) } err = walkFn(path, info, nil) if !info.IsDir() { return err } if errors.Is(err, fs.SkipDir) { return nil } dirEntries, err := fileSystem.ReadDir(path) if err != nil { return err } sort.Sort(dirEntriesByName(dirEntries)) for _, dirEntry := range dirEntries { name := dirEntry.Name() if name == "." || name == ".." { continue } info, err := dirEntry.Info() if err != nil { return err } if err := walk(fileSystem, filepath.Join(path, dirEntry.Name()), walkFn, info, nil); err != nil { return err } } return nil } // Walk is the equivalent of filepath.Walk but operates on fileSystem. Entries // are returned in lexicographical order. func Walk(fileSystem LstatReadDirer, path string, walkFn filepath.WalkFunc) error { info, err := fileSystem.Lstat(path) return walk(fileSystem, path, walkFn, info, err) } // WalkSlash is the equivalent of Walk but all paths are converted to use // forward slashes with filepath.ToSlash. func WalkSlash(fileSystem LstatReadDirer, path string, walkFn filepath.WalkFunc) error { return Walk(fileSystem, path, func(path string, info fs.FileInfo, err error) error { return walkFn(filepath.ToSlash(path), info, err) }) } go-vfs-4.1.0/windows.go000066400000000000000000000024121413632472200147410ustar00rootroot00000000000000//go:build windows // +build windows package vfs import ( "path/filepath" "strings" "syscall" "golang.org/x/sys/windows" ) var ignoreErrnoInContains = map[syscall.Errno]struct{}{ syscall.ELOOP: {}, syscall.EMLINK: {}, syscall.ENAMETOOLONG: {}, syscall.ENOENT: {}, syscall.EOVERFLOW: {}, windows.ERROR_CANT_RESOLVE_FILENAME: {}, } // relativizePath, on Windows, strips any leading volume name from path. Since // this is used to prepare paths to have the prefix prepended, returned values // use slashes instead of backslashes. func relativizePath(path string) string { if volumeName := filepath.VolumeName(path); volumeName != "" { path = path[len(volumeName):] } return filepath.ToSlash(path) } // trimPrefix, on Windows, trims prefix from path and returns an absolute path. // prefix must be a /-separated path. Since this is used to prepare results to // be returned to the calling client, returned values use backslashes instead of // slashes func trimPrefix(path, prefix string) (string, error) { trimmedPath, err := filepath.Abs(strings.TrimPrefix(filepath.ToSlash(path), prefix)) if err != nil { return "", err } return filepath.FromSlash(trimmedPath), nil }