2017-02-01 22:21:35 +01:00
|
|
|
// Copyright 2016 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package pathtools
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-09-21 07:44:36 +02:00
|
|
|
"fmt"
|
2017-02-01 22:21:35 +01:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-10-04 00:58:50 +02:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2018-09-21 06:48:44 +02:00
|
|
|
"syscall"
|
|
|
|
"time"
|
2017-02-01 22:21:35 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
|
|
|
|
|
|
|
|
var OsFs FileSystem = osFs{}
|
|
|
|
|
|
|
|
func MockFs(files map[string][]byte) FileSystem {
|
|
|
|
fs := &mockFs{
|
2018-09-21 06:48:44 +02:00
|
|
|
files: make(map[string][]byte, len(files)),
|
|
|
|
dirs: make(map[string]bool),
|
|
|
|
symlinks: make(map[string]string),
|
|
|
|
all: []string(nil),
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for f, b := range files {
|
2018-09-21 06:48:44 +02:00
|
|
|
if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
|
|
|
|
fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
fs.files[filepath.Clean(f)] = b
|
|
|
|
dir := filepath.Dir(f)
|
|
|
|
for dir != "." && dir != "/" {
|
|
|
|
fs.dirs[dir] = true
|
|
|
|
dir = filepath.Dir(dir)
|
|
|
|
}
|
2017-07-14 17:16:00 +02:00
|
|
|
fs.dirs[dir] = true
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for f := range fs.files {
|
|
|
|
fs.all = append(fs.all, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
for d := range fs.dirs {
|
|
|
|
fs.all = append(fs.all, d)
|
|
|
|
}
|
|
|
|
|
2018-09-21 06:48:44 +02:00
|
|
|
for s := range fs.symlinks {
|
|
|
|
fs.all = append(fs.all, s)
|
|
|
|
}
|
|
|
|
|
2017-10-04 00:58:50 +02:00
|
|
|
sort.Strings(fs.all)
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
return fs
|
|
|
|
}
|
|
|
|
|
|
|
|
type FileSystem interface {
|
2018-09-21 06:48:44 +02:00
|
|
|
// Open opens a file for reading. Follows symlinks.
|
2017-02-01 22:21:35 +01:00
|
|
|
Open(name string) (io.ReadCloser, error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
|
|
|
// Exists returns whether the file exists and whether it is a directory. Follows symlinks.
|
2017-02-01 22:21:35 +01:00
|
|
|
Exists(name string) (bool, bool, error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
Glob(pattern string, excludes []string) (matches, dirs []string, err error)
|
|
|
|
glob(pattern string) (matches []string, err error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
|
|
|
// IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks.
|
|
|
|
// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
|
2017-02-01 22:21:35 +01:00
|
|
|
IsDir(name string) (bool, error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
|
|
|
// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
|
|
|
|
// not exist. Returns os.ErrNotExist if the path does not exist.
|
2018-09-20 23:36:10 +02:00
|
|
|
IsSymlink(name string) (bool, error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
|
|
|
// Lstat returns info on a file without following symlinks.
|
2017-08-24 02:30:05 +02:00
|
|
|
Lstat(name string) (os.FileInfo, error)
|
2018-09-21 06:48:44 +02:00
|
|
|
|
|
|
|
// ListDirsRecursive returns a list of all the directories in a path, following symlinks.
|
2017-10-04 00:58:50 +02:00
|
|
|
ListDirsRecursive(name string) (dirs []string, err error)
|
2018-09-21 07:44:36 +02:00
|
|
|
|
|
|
|
// ReadDirNames returns a list of everything in a directory.
|
|
|
|
ReadDirNames(name string) ([]string, error)
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// osFs implements FileSystem using the local disk.
|
|
|
|
type osFs struct{}
|
|
|
|
|
|
|
|
func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
|
|
|
|
func (osFs) Exists(name string) (bool, bool, error) {
|
|
|
|
stat, err := os.Stat(name)
|
|
|
|
if err == nil {
|
|
|
|
return true, stat.IsDir(), nil
|
|
|
|
} else if os.IsNotExist(err) {
|
|
|
|
return false, false, nil
|
|
|
|
} else {
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (osFs) IsDir(name string) (bool, error) {
|
|
|
|
info, err := os.Stat(name)
|
|
|
|
if err != nil {
|
2018-09-20 23:36:10 +02:00
|
|
|
return false, err
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
return info.IsDir(), nil
|
|
|
|
}
|
|
|
|
|
2018-09-20 23:36:10 +02:00
|
|
|
func (osFs) IsSymlink(name string) (bool, error) {
|
|
|
|
if info, err := os.Lstat(name); err != nil {
|
|
|
|
return false, err
|
|
|
|
} else {
|
|
|
|
return info.Mode()&os.ModeSymlink != 0, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
func (fs osFs) Glob(pattern string, excludes []string) (matches, dirs []string, err error) {
|
|
|
|
return startGlob(fs, pattern, excludes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (osFs) glob(pattern string) ([]string, error) {
|
|
|
|
return filepath.Glob(pattern)
|
|
|
|
}
|
|
|
|
|
2017-08-24 02:30:05 +02:00
|
|
|
func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
|
|
|
|
return os.Lstat(path)
|
|
|
|
}
|
|
|
|
|
2017-10-04 00:58:50 +02:00
|
|
|
// Returns a list of all directories under dir
|
|
|
|
func (osFs) ListDirsRecursive(name string) (dirs []string, err error) {
|
2018-09-21 07:44:36 +02:00
|
|
|
return listDirsRecursive(OsFs, name)
|
|
|
|
}
|
2017-10-04 00:58:50 +02:00
|
|
|
|
2018-09-21 07:44:36 +02:00
|
|
|
func (osFs) ReadDirNames(name string) ([]string, error) {
|
|
|
|
dir, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer dir.Close()
|
2017-10-04 00:58:50 +02:00
|
|
|
|
2018-09-21 07:44:36 +02:00
|
|
|
contents, err := dir.Readdirnames(-1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-10-04 00:58:50 +02:00
|
|
|
|
2018-09-21 07:44:36 +02:00
|
|
|
sort.Strings(contents)
|
|
|
|
return contents, nil
|
2017-10-04 00:58:50 +02:00
|
|
|
}
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
type mockFs struct {
|
2018-09-21 06:48:44 +02:00
|
|
|
files map[string][]byte
|
|
|
|
dirs map[string]bool
|
|
|
|
symlinks map[string]string
|
|
|
|
all []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockFs) followSymlinks(name string) string {
|
|
|
|
dir, file := saneSplit(name)
|
|
|
|
if dir != "." && dir != "/" {
|
|
|
|
dir = m.followSymlinks(dir)
|
|
|
|
}
|
|
|
|
name = filepath.Join(dir, file)
|
|
|
|
|
|
|
|
for i := 0; i < 255; i++ {
|
|
|
|
i++
|
|
|
|
if i > 255 {
|
|
|
|
panic("symlink loop")
|
|
|
|
}
|
|
|
|
to, exists := m.symlinks[name]
|
|
|
|
if !exists {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if filepath.IsAbs(to) {
|
|
|
|
name = to
|
|
|
|
} else {
|
|
|
|
name = filepath.Join(dir, to)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return name
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockFs) Open(name string) (io.ReadCloser, error) {
|
2018-09-21 06:48:44 +02:00
|
|
|
name = filepath.Clean(name)
|
|
|
|
name = m.followSymlinks(name)
|
2017-02-01 22:21:35 +01:00
|
|
|
if f, ok := m.files[name]; ok {
|
|
|
|
return struct {
|
|
|
|
io.Closer
|
|
|
|
*bytes.Reader
|
|
|
|
}{
|
|
|
|
ioutil.NopCloser(nil),
|
|
|
|
bytes.NewReader(f),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, &os.PathError{
|
|
|
|
Op: "open",
|
|
|
|
Path: name,
|
|
|
|
Err: os.ErrNotExist,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockFs) Exists(name string) (bool, bool, error) {
|
|
|
|
name = filepath.Clean(name)
|
2018-09-21 06:48:44 +02:00
|
|
|
name = m.followSymlinks(name)
|
2017-02-01 22:21:35 +01:00
|
|
|
if _, ok := m.files[name]; ok {
|
|
|
|
return ok, false, nil
|
|
|
|
}
|
|
|
|
if _, ok := m.dirs[name]; ok {
|
|
|
|
return ok, true, nil
|
|
|
|
}
|
|
|
|
return false, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockFs) IsDir(name string) (bool, error) {
|
2018-09-21 06:48:44 +02:00
|
|
|
dir := filepath.Dir(name)
|
|
|
|
if dir != "." && dir != "/" {
|
|
|
|
isDir, err := m.IsDir(dir)
|
|
|
|
|
|
|
|
if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
|
|
|
|
isDir = false
|
|
|
|
} else if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isDir {
|
|
|
|
return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name = filepath.Clean(name)
|
|
|
|
name = m.followSymlinks(name)
|
|
|
|
|
|
|
|
if _, ok := m.dirs[name]; ok {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
if _, ok := m.files[name]; ok {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, os.ErrNotExist
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
|
2018-09-20 23:36:10 +02:00
|
|
|
func (m *mockFs) IsSymlink(name string) (bool, error) {
|
2018-09-21 06:48:44 +02:00
|
|
|
dir, file := saneSplit(name)
|
|
|
|
dir = m.followSymlinks(dir)
|
|
|
|
name = filepath.Join(dir, file)
|
|
|
|
|
|
|
|
if _, isSymlink := m.symlinks[name]; isSymlink {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
if _, isDir := m.dirs[name]; isDir {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if _, isFile := m.files[name]; isFile {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, os.ErrNotExist
|
2018-09-20 23:36:10 +02:00
|
|
|
}
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
func (m *mockFs) Glob(pattern string, excludes []string) (matches, dirs []string, err error) {
|
|
|
|
return startGlob(m, pattern, excludes)
|
|
|
|
}
|
|
|
|
|
2018-09-21 06:48:44 +02:00
|
|
|
func unescapeGlob(s string) string {
|
|
|
|
i := 0
|
|
|
|
for i < len(s) {
|
|
|
|
if s[i] == '\\' {
|
|
|
|
s = s[:i] + s[i+1:]
|
|
|
|
} else {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
func (m *mockFs) glob(pattern string) ([]string, error) {
|
2018-09-21 06:48:44 +02:00
|
|
|
dir, file := saneSplit(pattern)
|
|
|
|
|
|
|
|
dir = unescapeGlob(dir)
|
|
|
|
toDir := m.followSymlinks(dir)
|
|
|
|
|
2017-02-01 22:21:35 +01:00
|
|
|
var matches []string
|
|
|
|
for _, f := range m.all {
|
2018-09-21 06:48:44 +02:00
|
|
|
fDir, fFile := saneSplit(f)
|
|
|
|
if toDir == fDir {
|
|
|
|
match, err := filepath.Match(file, fFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if f == "." && f != pattern {
|
|
|
|
// filepath.Glob won't return "." unless the pattern was "."
|
|
|
|
match = false
|
|
|
|
}
|
|
|
|
if match {
|
|
|
|
matches = append(matches, filepath.Join(dir, fFile))
|
|
|
|
}
|
2017-02-01 22:21:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return matches, nil
|
|
|
|
}
|
2017-08-24 02:30:05 +02:00
|
|
|
|
2018-09-21 06:48:44 +02:00
|
|
|
type mockStat struct {
|
|
|
|
name string
|
|
|
|
size int64
|
|
|
|
mode os.FileMode
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ms *mockStat) Name() string { return ms.name }
|
|
|
|
func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() }
|
|
|
|
func (ms *mockStat) Size() int64 { return ms.size }
|
|
|
|
func (ms *mockStat) Mode() os.FileMode { return ms.mode }
|
|
|
|
func (ms *mockStat) ModTime() time.Time { return time.Time{} }
|
|
|
|
func (ms *mockStat) Sys() interface{} { return nil }
|
|
|
|
|
|
|
|
func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
|
|
|
|
name = filepath.Clean(name)
|
|
|
|
|
|
|
|
ms := mockStat{
|
|
|
|
name: name,
|
|
|
|
size: int64(len(m.files[name])),
|
|
|
|
}
|
|
|
|
|
|
|
|
if isSymlink, err := m.IsSymlink(name); err != nil {
|
|
|
|
// IsSymlink handles ErrNotExist
|
|
|
|
return nil, err
|
|
|
|
} else if isSymlink {
|
|
|
|
ms.mode = os.ModeSymlink
|
|
|
|
} else if isDir, err := m.IsDir(name); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if isDir {
|
|
|
|
ms.mode = os.ModeDir
|
|
|
|
} else {
|
|
|
|
ms.mode = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ms, nil
|
2017-08-24 02:30:05 +02:00
|
|
|
}
|
2017-10-04 00:58:50 +02:00
|
|
|
|
2018-09-21 07:44:36 +02:00
|
|
|
func (m *mockFs) ReadDirNames(name string) ([]string, error) {
|
2017-10-04 00:58:50 +02:00
|
|
|
name = filepath.Clean(name)
|
2018-09-21 07:44:36 +02:00
|
|
|
name = m.followSymlinks(name)
|
|
|
|
|
|
|
|
exists, isDir, err := m.Exists(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return nil, os.ErrNotExist
|
2017-10-04 00:58:50 +02:00
|
|
|
}
|
2018-09-21 07:44:36 +02:00
|
|
|
if !isDir {
|
|
|
|
return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ret []string
|
2017-10-04 00:58:50 +02:00
|
|
|
for _, f := range m.all {
|
2018-09-21 07:44:36 +02:00
|
|
|
dir, file := saneSplit(f)
|
|
|
|
if dir == name && len(file) > 0 && file[0] != '.' {
|
|
|
|
ret = append(ret, file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockFs) ListDirsRecursive(name string) ([]string, error) {
|
|
|
|
return listDirsRecursive(m, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func listDirsRecursive(fs FileSystem, name string) ([]string, error) {
|
|
|
|
name = filepath.Clean(name)
|
|
|
|
|
|
|
|
isDir, err := fs.IsDir(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isDir {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dirs := []string{name}
|
|
|
|
|
|
|
|
subDirs, err := listDirsRecursiveRelative(fs, name, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range subDirs {
|
|
|
|
dirs = append(dirs, filepath.Join(name, d))
|
|
|
|
}
|
|
|
|
|
|
|
|
return dirs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func listDirsRecursiveRelative(fs FileSystem, name string, depth int) ([]string, error) {
|
|
|
|
depth++
|
|
|
|
if depth > 255 {
|
|
|
|
return nil, fmt.Errorf("too many symlinks")
|
|
|
|
}
|
|
|
|
contents, err := fs.ReadDirNames(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var dirs []string
|
|
|
|
for _, f := range contents {
|
|
|
|
if f[0] == '.' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
f = filepath.Join(name, f)
|
|
|
|
if isDir, _ := fs.IsDir(f); isDir {
|
|
|
|
dirs = append(dirs, f)
|
|
|
|
subDirs, err := listDirsRecursiveRelative(fs, f, depth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-10-04 00:58:50 +02:00
|
|
|
}
|
2018-09-21 07:44:36 +02:00
|
|
|
for _, s := range subDirs {
|
|
|
|
dirs = append(dirs, filepath.Join(f, s))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, d := range dirs {
|
|
|
|
rel, err := filepath.Rel(name, d)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-10-04 00:58:50 +02:00
|
|
|
}
|
2018-09-21 07:44:36 +02:00
|
|
|
dirs[i] = rel
|
2017-10-04 00:58:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return dirs, nil
|
|
|
|
}
|