Skip to content

Commit

Permalink
Back to Go1.13, see #388, REVERT BACK LATER
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Mar 17, 2021
1 parent e061c21 commit 7b72a41
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 28 deletions.
233 changes: 233 additions & 0 deletions cmd/minify/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package main

import (
"errors"
"os"
"path"
"sort"
)

// An FS provides access to a hierarchical file system.
//
// The FS interface is the minimum implementation required of the file system.
// A file system may implement additional interfaces,
// such as ReadFileFS, to provide additional or optimized functionality.
type FS interface {
// Open opens the named file.
//
// When Open returns an error, it should be of type *PathError
// with the Op field set to "open", the Path field set to name,
// and the Err field describing the problem.
//
// Open should reject attempts to open names that do not satisfy
// ValidPath(name), returning a *PathError with Err set to
// ErrInvalid or ErrNotExist.
Open(name string) (*os.File, error)
Stat(name string) (os.FileInfo, error)
}

// ReadDir reads the named directory,
// returning all its directory entries sorted by filename.
// If an error occurs reading the directory,
// ReadDir returns the entries it was able to read before the error,
// along with the error.
func ReadDir(fsys FS, name string) ([]DirEntry, error) {
f, err := fsys.Open(name)
if err != nil {
return nil, err
}
defer f.Close()

infos, err := f.Readdir(-1)
dirs := make([]DirEntry, len(infos))
for i, info := range infos {
dirs[i] = &statDirEntry{info}
}
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
return dirs, err
}

// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir.
//
// Note that DirFS("/prefix") only guarantees that the Open calls it makes to the
// operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the
// same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside
// the /prefix tree, then using DirFS does not stop the access any more than using
// os.Open does. DirFS is therefore not a general substitute for a chroot-style security
// mechanism when the directory tree contains arbitrary content.
func DirFS(dir string) FS {
return dirFS(dir)
}

func containsAny(s, chars string) bool {
for i := 0; i < len(s); i++ {
for j := 0; j < len(chars); j++ {
if s[i] == chars[j] {
return true
}
}
}
return false
}

type dirFS string

func (dir dirFS) Open(name string) (*os.File, error) {
//if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
// return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
//}
f, err := os.Open(string(dir) + "/" + name)
if err != nil {
return nil, err
}
return f, nil
}

func (dir dirFS) Stat(name string) (os.FileInfo, error) {
info, err := os.Stat(string(dir) + "/" + name)
if err != nil {
return info, err
}
return info, nil
}

// SkipDir is used as a return value from WalkDirFuncs to indicate that
// the directory named in the call is to be skipped. It is not returned
// as an error by any function.
var SkipDir = errors.New("skip this directory")

// WalkDirFunc is the type of the function called by WalkDir to visit
// each file or directory.
//
// The path argument contains the argument to WalkDir as a prefix.
// That is, if WalkDir is called with root argument "dir" and finds a file
// named "a" in that directory, the walk function will be called with
// argument "dir/a".
//
// The d argument is the fs.DirEntry for the named path.
//
// The error result returned by the function controls how WalkDir
// continues. If the function returns the special value SkipDir, WalkDir
// skips the current directory (path if d.IsDir() is true, otherwise
// path's parent directory). Otherwise, if the function returns a non-nil
// error, WalkDir stops entirely and returns that error.
//
// The err argument reports an error related to path, signaling that
// WalkDir will not walk into that directory. The function can decide how
// to handle that error; as described earlier, returning the error will
// cause WalkDir to stop walking the entire tree.
//
// WalkDir calls the function with a non-nil err argument in two cases.
//
// First, if the initial fs.Stat on the root directory fails, WalkDir
// calls the function with path set to root, d set to nil, and err set to
// the error from fs.Stat.
//
// Second, if a directory's ReadDir method fails, WalkDir calls the
// function with path set to the directory's path, d set to an
// fs.DirEntry describing the directory, and err set to the error from
// ReadDir. In this second case, the function is called twice with the
// path of the directory: the first call is before the directory read is
// attempted and has err set to nil, giving the function a chance to
// return SkipDir and avoid the ReadDir entirely. The second call is
// after a failed ReadDir and reports the error from ReadDir.
// (If ReadDir succeeds, there is no second call.)
//
// The differences between WalkDirFunc compared to filepath.WalkFunc are:
//
// - The second argument has type fs.DirEntry instead of fs.FileInfo.
// - The function is called before reading a directory, to allow SkipDir
// to bypass the directory read entirely.
// - If a directory read fails, the function is called a second time
// for that directory to report the error.
//
type WalkDirFunc func(path string, d DirEntry, err error) error

// walkDir recursively descends path, calling walkDirFn.
func walkDir(fsys FS, name string, d DirEntry, walkDirFn WalkDirFunc) error {
if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() {
if err == SkipDir && d.IsDir() {
// Successfully skipped directory.
err = nil
}
return err
}

dirs, err := ReadDir(fsys, name)
if err != nil {
// Second call, to report ReadDir error.
err = walkDirFn(name, d, err)
if err != nil {
return err
}
}

for _, d1 := range dirs {
name1 := path.Join(name, d1.Name())
if err := walkDir(fsys, name1, d1, walkDirFn); err != nil {
if err == SkipDir {
break
}
return err
}
}
return nil
}

// WalkDir walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root.
//
// All errors that arise visiting files and directories are filtered by fn:
// see the fs.WalkDirFunc documentation for details.
//
// The files are walked in lexical order, which makes the output deterministic
// but requires WalkDir to read an entire directory into memory before proceeding
// to walk that directory.
//
// WalkDir does not follow symbolic links found in directories,
// but if root itself is a symbolic link, its target will be walked.
func WalkDir(fsys FS, root string, fn WalkDirFunc) error {
info, err := fsys.Stat(root)
if err != nil {
err = fn(root, nil, err)
} else {
err = walkDir(fsys, root, &statDirEntry{info}, fn)
}
if err == SkipDir {
return nil
}
return err
}

// A DirEntry is an entry read from a directory
// (using the ReadDir function or a ReadDirFile's ReadDir method).
type DirEntry interface {
// Name returns the name of the file (or subdirectory) described by the entry.
// This name is only the final element of the path (the base name), not the entire path.
// For example, Name would return "hello.go" not "/home/gopher/hello.go".
Name() string

// IsDir reports whether the entry describes a directory.
IsDir() bool

// Type returns the type bits for the entry.
// The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
Type() os.FileMode

// Info returns the FileInfo for the file or subdirectory described by the entry.
// The returned FileInfo may be from the time of the original directory read
// or from the time of the call to Info. If the file has been removed or renamed
// since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
// If the entry denotes a symbolic link, Info reports the information about the link itself,
// not the link's target.
Info() (os.FileInfo, error)
}

type statDirEntry struct {
info os.FileInfo
}

func (d *statDirEntry) Name() string { return d.info.Name() }
func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
func (d *statDirEntry) Type() os.FileMode { return d.info.Mode() & os.ModeType }
func (d *statDirEntry) Info() (os.FileInfo, error) { return d.info, nil }
47 changes: 23 additions & 24 deletions cmd/minify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"net/url"
Expand Down Expand Up @@ -474,7 +473,7 @@ func sanitizePath(p string) string {
}
if isDir {
p += "/"
} else if info, err := os.Lstat(p); err == nil && info.Mode().IsDir() && info.Mode()&fs.ModeSymlink == 0 {
} else if info, err := os.Lstat(p); err == nil && info.Mode().IsDir() && info.Mode()&os.ModeSymlink == 0 {
p += "/"
}
if isCur {
Expand All @@ -500,23 +499,23 @@ func fileMatches(filename string) bool {
return true
}

type DirEntry struct {
fs.FileInfo
}

func (d DirEntry) Type() fs.FileMode {
return d.Mode().Type()
}

func (d DirEntry) Info() (fs.FileInfo, error) {
return d.FileInfo, nil
}
//type DirEntry struct {
// fs.FileInfo
//}
//
//func (d DirEntry) Type() fs.FileMode {
// return d.Mode().Type()
//}
//
//func (d DirEntry) Info() (fs.FileInfo, error) {
// return d.FileInfo, nil
//}

func createTasks(inputs []string, output string) ([]Task, []string, error) {
tasks := []Task{}
roots := []string{}
for _, input := range inputs {
var info fs.FileInfo
var info os.FileInfo
var err error
if !preserveSymlinks {
info, err = os.Stat(input)
Expand All @@ -527,7 +526,7 @@ func createTasks(inputs []string, output string) ([]Task, []string, error) {
return nil, nil, err
}

if info.Mode()&fs.ModeSymlink != 0 {
if info.Mode()&os.ModeSymlink != 0 {
if !sync {
Warning.Println("--sync not specified, omitting symbolic link", input)
continue
Expand All @@ -553,33 +552,33 @@ func createTasks(inputs []string, output string) ([]Task, []string, error) {
}
roots = append(roots, input)

var walkFn func(string, fs.DirEntry, error) error
walkFn = func(path string, d fs.DirEntry, err error) error {
var walkFn func(string, DirEntry, error) error
walkFn = func(path string, d DirEntry, err error) error {
if err != nil {
return err
} else if d.Name() == "." || d.Name() == ".." {
return nil
} else if len(d.Name()) == 0 || !hidden && d.Name()[0] == '.' {
if d.IsDir() {
return fs.SkipDir
return SkipDir
}
return nil
}
path = sanitizePath(path)

if !preserveSymlinks && d.Type()&fs.ModeSymlink != 0 {
if !preserveSymlinks && d.Type()&os.ModeSymlink != 0 {
// follow and dereference symlinks
info, err := os.Stat(path)
if err != nil {
return err
}
if info.IsDir() {
return fs.WalkDir(os.DirFS(input), path, walkFn)
return WalkDir(DirFS(input), path, walkFn)
}
d = DirEntry{info}
d = &statDirEntry{info}
}

if preserveSymlinks && d.Type()&fs.ModeSymlink != 0 {
if preserveSymlinks && d.Type()&os.ModeSymlink != 0 {
// copy symlinks as is
if !sync {
Warning.Println("--sync not specified, omitting symbolic link", path)
Expand All @@ -602,7 +601,7 @@ func createTasks(inputs []string, output string) ([]Task, []string, error) {
}
return nil
}
if err := fs.WalkDir(os.DirFS("."), path.Clean(input), walkFn); err != nil {
if err := WalkDir(DirFS("."), path.Clean(input), walkFn); err != nil {
return nil, nil, err
}
} else {
Expand Down Expand Up @@ -669,7 +668,7 @@ func minify(t Task) bool {
if t.sync {
if t.srcs[0] == t.dst {
return true
} else if info, err := os.Lstat(t.srcs[0]); preserveSymlinks && err == nil && info.Mode()&fs.ModeSymlink != 0 {
} else if info, err := os.Lstat(t.srcs[0]); preserveSymlinks && err == nil && info.Mode()&os.ModeSymlink != 0 {
src, err := os.Readlink(t.srcs[0])
if err != nil {
Error.Println(err)
Expand Down
5 changes: 2 additions & 3 deletions cmd/minify/watch.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"io/fs"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -50,13 +49,13 @@ func (w *Watcher) AddPath(root string) error {
}
w.dirs[root] = true
} else if info.Mode().IsDir() && w.recursive {
return fs.WalkDir(os.DirFS("."), filepath.Clean(root), func(path string, d fs.DirEntry, err error) error {
return WalkDir(DirFS("."), filepath.Clean(root), func(path string, d DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if w.dirs[path] {
return fs.SkipDir
return SkipDir
}
if err := w.watcher.Add(path); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/tdewolff/minify/v2

go 1.16
go 1.13

require (
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
Expand Down

0 comments on commit 7b72a41

Please sign in to comment.