diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 919cdf4..8413309 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: ['1.15', '1.16', '1.17', '1.18', '1.19', '1.20'] + go: ['1.18', '1.19', '1.20'] steps: - name: Set up Go diff --git a/.github/workflows/vagrant-test.yml b/.github/workflows/vagrant-test.yml index 408bd54..c36cad9 100644 --- a/.github/workflows/vagrant-test.yml +++ b/.github/workflows/vagrant-test.yml @@ -8,7 +8,7 @@ on: jobs: vagrant-test: - runs-on: macos-10.15 + runs-on: macos-12 strategy: matrix: runtime: diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 5663666..59c61f9 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - go: ['1.17', '1.18', '1.19', '1.20'] + go: ['1.18', '1.19', '1.20'] steps: - name: Set up Go diff --git a/README.md b/README.md index dc66c4e..1cc8fc8 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,15 @@ type Options struct { // If zero, the internal default buffer of 32KB is used. // See https://golang.org/pkg/io/#CopyBuffer for more information. CopyBufferSize uint + + // If you want to add some limitation on reading src file, + // you can wrap the src and provide new reader, + // such as `RateLimitReader` in the test case. + WrapReader func(src io.Reader) io.Reader + + // If given, copy.Copy refers to this fs.FS instead of the OS filesystem. + // e.g., You can use embed.FS to copy files from embedded filesystem. + FS fs.FS } ``` diff --git a/all_test.go b/all_test.go index 3a24744..fbd12e5 100644 --- a/all_test.go +++ b/all_test.go @@ -1,6 +1,7 @@ package copy import ( + "embed" "errors" "io" "io/ioutil" @@ -14,6 +15,9 @@ import ( . "github.com/otiai10/mint" ) +//go:embed test/data/case18/assets +var assets embed.FS + func TestMain(m *testing.M) { setup(m) code := m.Run() @@ -26,6 +30,8 @@ func teardown(m *testing.M) { os.RemoveAll("test/data.copy") os.RemoveAll("test/data.copyTime") os.RemoveAll("test/owned-by-root") // Do not check the error ;) + Copy("test/data/case18/assets.backup", "test/data/case18/assets") + os.RemoveAll("test/data/case18/assets.backup") } func TestCopy(t *testing.T) { @@ -394,7 +400,7 @@ func TestOptions_CopyRateLimit(t *testing.T) { return } - opt := Options{WrapReader: func(src *os.File) io.Reader { + opt := Options{WrapReader: func(src io.Reader) io.Reader { return &SleepyReader{src, 1} }} @@ -445,8 +451,17 @@ func TestOptions_OnFileError(t *testing.T) { Expect(t, os.IsNotExist(err)).ToBe(true) } +func TestOptions_FS(t *testing.T) { + os.RemoveAll("test/data/case18/assets") + err := Copy("test/data/case18/assets", "test/data.copy/case18/assets", Options{ + FS: assets, + PermissionControl: AddPermission(200), // FIXME + }) + Expect(t, err).ToBe(nil) +} + type SleepyReader struct { - src *os.File + src io.Reader sec time.Duration } diff --git a/copy.go b/copy.go index a84ff7b..085db78 100644 --- a/copy.go +++ b/copy.go @@ -2,6 +2,7 @@ package copy import ( "io" + "io/fs" "io/ioutil" "os" "path/filepath" @@ -17,6 +18,13 @@ type timespec struct { // Copy copies src to dest, doesn't matter if src is a directory or a file. func Copy(src, dest string, opts ...Options) error { opt := assureOptions(src, dest, opts...) + if opt.FS != nil { + info, err := fs.Stat(opt.FS, src) + if err != nil { + return onError(src, dest, err, opt) + } + return switchboard(src, dest, info, opt) + } info, err := os.Lstat(src) if err != nil { return onError(src, dest, err, opt) @@ -65,14 +73,20 @@ func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error { // with considering existence of parent directory // and file permission. func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { - s, err := os.Open(src) + + var readcloser io.ReadCloser + if opt.FS != nil { + readcloser, err = opt.FS.Open(src) + } else { + readcloser, err = os.Open(src) + } if err != nil { if os.IsNotExist(err) { return nil } return } - defer fclose(s, &err) + defer fclose(readcloser, &err) if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { return @@ -92,10 +106,10 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { var buf []byte = nil var w io.Writer = f - var r io.Reader = s + var r io.Reader = readcloser if opt.WrapReader != nil { - r = opt.WrapReader(s) + r = opt.WrapReader(r) } if opt.CopyBufferSize != 0 { @@ -145,7 +159,23 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { } defer chmodfunc(&err) - contents, err := ioutil.ReadDir(srcdir) + var contents []os.FileInfo + if opt.FS != nil { + entries, err := fs.ReadDir(opt.FS, srcdir) + if err != nil { + return err + } + for _, e := range entries { + info, err := e.Info() + if err != nil { + return err + } + contents = append(contents, info) + } + } else { + contents, err = ioutil.ReadDir(srcdir) + } + if err != nil { if os.IsNotExist(err) { return nil @@ -237,7 +267,7 @@ func lcopy(src, dest string) error { // fclose ANYHOW closes file, // with asiging error raised during Close, // BUT respecting the error already reported. -func fclose(f *os.File, reported *error) { +func fclose(f io.Closer, reported *error) { if err := f.Close(); *reported == nil { *reported = err } diff --git a/options.go b/options.go index 52d636c..1b4e508 100644 --- a/options.go +++ b/options.go @@ -2,6 +2,7 @@ package copy import ( "io" + "io/fs" "os" ) @@ -58,7 +59,11 @@ type Options struct { // If you want to add some limitation on reading src file, // you can wrap the src and provide new reader, // such as `RateLimitReader` in the test case. - WrapReader func(src *os.File) io.Reader + WrapReader func(src io.Reader) io.Reader + + // If given, copy.Copy refers to this fs.FS instead of the OS filesystem. + // e.g., You can use embed.FS to copy files from embedded filesystem. + FS fs.FS intent struct { src string diff --git a/test/data/case18/assets/README.md b/test/data/case18/assets/README.md new file mode 100644 index 0000000..5570c7f --- /dev/null +++ b/test/data/case18/assets/README.md @@ -0,0 +1 @@ +# Hello \ No newline at end of file diff --git a/test_setup.go b/test_setup.go index f3c2d83..64a5292 100644 --- a/test_setup.go +++ b/test_setup.go @@ -16,4 +16,5 @@ func setup(m *testing.M) { os.Chmod("test/data/case07/dir_0555", 0o555) os.Chmod("test/data/case07/file_0444", 0o444) syscall.Mkfifo("test/data/case11/foo/bar", 0o555) + Copy("test/data/case18/assets", "test/data/case18/assets.backup") } diff --git a/test_setup_x.go b/test_setup_x.go index fc56b73..4c35b14 100644 --- a/test_setup_x.go +++ b/test_setup_x.go @@ -9,6 +9,7 @@ import ( ) func setup(m *testing.M) { + os.RemoveAll("test/data.copy") os.MkdirAll("test/data.copy", os.ModePerm) os.Symlink("test/data/case01", "test/data/case03/case01") os.Chmod("test/data/case07/dir_0555", 0555)