mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 08:03:43 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			119 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			119 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package fsutil
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/tonistiigi/fsutil/types"
 | |
| )
 | |
| 
 | |
| type FS interface {
 | |
| 	Walk(context.Context, filepath.WalkFunc) error
 | |
| 	Open(string) (io.ReadCloser, error)
 | |
| }
 | |
| 
 | |
| func NewFS(root string, opt *WalkOpt) FS {
 | |
| 	return &fs{
 | |
| 		root: root,
 | |
| 		opt:  opt,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type fs struct {
 | |
| 	root string
 | |
| 	opt  *WalkOpt
 | |
| }
 | |
| 
 | |
| func (fs *fs) Walk(ctx context.Context, fn filepath.WalkFunc) error {
 | |
| 	return Walk(ctx, fs.root, fs.opt, fn)
 | |
| }
 | |
| 
 | |
| func (fs *fs) Open(p string) (io.ReadCloser, error) {
 | |
| 	return os.Open(filepath.Join(fs.root, p))
 | |
| }
 | |
| 
 | |
| type Dir struct {
 | |
| 	Stat types.Stat
 | |
| 	FS   FS
 | |
| }
 | |
| 
 | |
| func SubDirFS(dirs []Dir) (FS, error) {
 | |
| 	sort.Slice(dirs, func(i, j int) bool {
 | |
| 		return dirs[i].Stat.Path < dirs[j].Stat.Path
 | |
| 	})
 | |
| 	m := map[string]Dir{}
 | |
| 	for _, d := range dirs {
 | |
| 		if path.Base(d.Stat.Path) != d.Stat.Path {
 | |
| 			return nil, errors.Errorf("subdir %s must be single file", d.Stat.Path)
 | |
| 		}
 | |
| 		if _, ok := m[d.Stat.Path]; ok {
 | |
| 			return nil, errors.Errorf("invalid path %s", d.Stat.Path)
 | |
| 		}
 | |
| 		m[d.Stat.Path] = d
 | |
| 	}
 | |
| 	return &subDirFS{m: m, dirs: dirs}, nil
 | |
| }
 | |
| 
 | |
| type subDirFS struct {
 | |
| 	m    map[string]Dir
 | |
| 	dirs []Dir
 | |
| }
 | |
| 
 | |
| func (fs *subDirFS) Walk(ctx context.Context, fn filepath.WalkFunc) error {
 | |
| 	for _, d := range fs.dirs {
 | |
| 		fi := &StatInfo{Stat: &d.Stat}
 | |
| 		if !fi.IsDir() {
 | |
| 			return errors.Errorf("fs subdir %s not mode directory", d.Stat.Path)
 | |
| 		}
 | |
| 		if err := fn(d.Stat.Path, fi, nil); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if err := d.FS.Walk(ctx, func(p string, fi os.FileInfo, err error) error {
 | |
| 			stat, ok := fi.Sys().(*types.Stat)
 | |
| 			if !ok {
 | |
| 				return errors.Wrapf(err, "invalid fileinfo without stat info: %s", p)
 | |
| 			}
 | |
| 			stat.Path = path.Join(d.Stat.Path, stat.Path)
 | |
| 			if stat.Linkname != "" {
 | |
| 				if fi.Mode()&os.ModeSymlink != 0 {
 | |
| 					if strings.HasPrefix(stat.Linkname, "/") {
 | |
| 						stat.Linkname = path.Join("/"+d.Stat.Path, stat.Linkname)
 | |
| 					}
 | |
| 				} else {
 | |
| 					stat.Linkname = path.Join(d.Stat.Path, stat.Linkname)
 | |
| 				}
 | |
| 			}
 | |
| 			return fn(filepath.Join(d.Stat.Path, p), &StatInfo{stat}, nil)
 | |
| 		}); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *subDirFS) Open(p string) (io.ReadCloser, error) {
 | |
| 	parts := strings.SplitN(filepath.Clean(p), string(filepath.Separator), 2)
 | |
| 	if len(parts) == 0 {
 | |
| 		return ioutil.NopCloser(&emptyReader{}), nil
 | |
| 	}
 | |
| 	d, ok := fs.m[parts[0]]
 | |
| 	if !ok {
 | |
| 		return nil, os.ErrNotExist
 | |
| 	}
 | |
| 	return d.FS.Open(parts[1])
 | |
| }
 | |
| 
 | |
| type emptyReader struct {
 | |
| }
 | |
| 
 | |
| func (*emptyReader) Read([]byte) (int, error) {
 | |
| 	return 0, io.EOF
 | |
| }
 | 
