Add OnSymlink options: Shallow/Deep/Skip
diff --git a/all_test.go b/all_test.go
index 5232557..8f41734 100644
--- a/all_test.go
+++ b/all_test.go
@@ -73,28 +73,39 @@
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(0)
})
- When(t, "source directory includes a dangling symbolic link, but with option to follow it", func(t *testing.T) {
- err := Copy("testdata/case03", "testdata.copy/case03", Opts{FollowSymlink: func(string) bool { return true }})
- Expect(t, err).Not().ToBe(nil)
- })
+ When(t, "symlink with Opt.OnSymlink provided", func(t *testing.T) {
+ opt := Options{OnSymlink: func(string) SymlinkAction { return Deep }}
+ err := Copy("testdata/case03", "testdata.copy/case03.deep", opt)
+ Expect(t, err).ToBe(nil)
+ info, err := os.Lstat("testdata.copy/case03.deep/case01")
+ Expect(t, err).ToBe(nil)
+ Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.FileMode(0))
- When(t, "source directory includes a symbolic link, but with option not to follow it", func(t *testing.T) {
- err := Copy("testdata/case06", "testdata.copy/case06-2", Opts{FollowSymlink: func(string) bool { return true }})
+ opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }}
+ err = Copy("testdata/case03", "testdata.copy/case03.shallow", opt)
Expect(t, err).ToBe(nil)
+ info, err = os.Lstat("testdata.copy/case03.shallow/case01")
Expect(t, err).ToBe(nil)
- info, err := os.Lstat("testdata.copy/case06-2/README.md")
- Expect(t, err).ToBe(nil)
- Expect(t, int(info.Mode()&os.ModeSymlink)).ToBe(0)
- })
+ Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
- When(t, "source directory includes a symbolic link, but with option to follow it", func(t *testing.T) {
- err := Copy("testdata/case06", "testdata.copy/case06")
+ opt = Options{OnSymlink: func(string) SymlinkAction { return Skip }}
+ err = Copy("testdata/case03", "testdata.copy/case03.skip", opt)
Expect(t, err).ToBe(nil)
+ _, err = os.Stat("testdata.copy/case03.skip/case01")
+ Expect(t, os.IsNotExist(err)).ToBe(true)
+
+ err = Copy("testdata/case03", "testdata.copy/case03.default")
Expect(t, err).ToBe(nil)
- info, err := os.Lstat("testdata.copy/case06/README.md")
+ info, err = os.Lstat("testdata.copy/case03.default/case01")
Expect(t, err).ToBe(nil)
- Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.ModeSymlink)
- os.RemoveAll("testdata.copy/case06")
+ Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
+
+ opt = Options{OnSymlink: nil}
+ err = Copy("testdata/case03", "testdata.copy/case03.not-specified", opt)
+ Expect(t, err).ToBe(nil)
+ info, err = os.Lstat("testdata.copy/case03.not-specified/case01")
+ Expect(t, err).ToBe(nil)
+ Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
})
When(t, "try to copy to an existing path", func(t *testing.T) {
@@ -103,7 +114,7 @@
})
When(t, "try to copy READ-not-allowed source", func(t *testing.T) {
- err := Copy("testdata/case07", "testdata.copy/case07")
+ err := Copy("testdata/case06", "testdata.copy/case06")
Expect(t, err).Not().ToBe(nil)
})
diff --git a/copy.go b/copy.go
index bd339c4..2fdb3ea 100644
--- a/copy.go
+++ b/copy.go
@@ -14,41 +14,25 @@
tmpPermissionForDirectory = os.FileMode(0755)
)
-type Opts struct {
- // FollowSymlink is called with a source path it is found to be a symlink. If
- // this function returns false, Copy copies the symlink itself. Else, Copy
- // follows the link. If this field is not set, Copy never follows symlinks.
- FollowSymlink func(path string) bool
-}
-
-// Copy copies src to dest, doesn't matter if src is a directory or a file. An
-// optional arg opts specifies the options to the copy operations. There can be
-// at most one opts.
-func Copy(src, dest string, opts ...Opts) error {
- var opt Opts
- if len(opts) > 0 {
- if len(opts) > 1 {
- panic("too many opts")
- }
- opt = opts[0]
- }
+// Copy copies src to dest, doesn't matter if src is a directory or a file.
+func Copy(src, dest string, opt ...Options) error {
+ opt = append(opt, DefaultOptions)
info, err := os.Lstat(src)
if err != nil {
return err
}
- return copy(src, dest, opt, info)
+ return copy(src, dest, info, opt[0])
}
// copy dispatches copy-funcs according to the mode.
// Because this "copy" could be called recursively,
// "info" MUST be given here, NOT nil.
-func copy(src, dest string, opts Opts, info os.FileInfo) error {
- if info.Mode()&os.ModeSymlink != 0 &&
- (opts.FollowSymlink == nil || !opts.FollowSymlink(src)) {
- return lcopy(src, dest, info)
+func copy(src, dest string, info os.FileInfo, opt Options) error {
+ if info.Mode()&os.ModeSymlink != 0 {
+ return onsymlink(src, dest, info, opt)
}
if info.IsDir() {
- return dcopy(src, dest, opts, info)
+ return dcopy(src, dest, info, opt)
}
return fcopy(src, dest, info)
}
@@ -85,7 +69,7 @@
// dcopy is for a directory,
// with scanning contents inside the directory
// and pass everything to "copy" recursively.
-func dcopy(srcdir, destdir string, opts Opts, info os.FileInfo) (err error) {
+func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
originalMode := info.Mode()
@@ -103,7 +87,7 @@
for _, content := range contents {
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
- if err := copy(cs, cd, opts, content); err != nil {
+ if err := copy(cs, cd, content, opt); err != nil {
// If any error, exit immediately
return err
}
@@ -112,9 +96,35 @@
return nil
}
+func onsymlink(src, dest string, info os.FileInfo, opt Options) error {
+
+ if opt.OnSymlink == nil {
+ opt.OnSymlink = DefaultOptions.OnSymlink
+ }
+
+ switch opt.OnSymlink(src) {
+ case Shallow:
+ return lcopy(src, dest)
+ case Deep:
+ orig, err := os.Readlink(src)
+ if err != nil {
+ return err
+ }
+ info, err = os.Lstat(orig)
+ if err != nil {
+ return err
+ }
+ return copy(orig, dest, info, opt)
+ case Skip:
+ fallthrough
+ default:
+ return nil // do nothing
+ }
+}
+
// lcopy is for a symlink,
// with just creating a new symlink by replicating src symlink.
-func lcopy(src, dest string, info os.FileInfo) error {
+func lcopy(src, dest string) error {
src, err := os.Readlink(src)
if err != nil {
return err
diff --git a/options.go b/options.go
new file mode 100644
index 0000000..4053a3f
--- /dev/null
+++ b/options.go
@@ -0,0 +1,26 @@
+package copy
+
+// Options specifies optional actions on copying.
+type Options struct {
+ // OnSymlink can specify what to do on symlink
+ OnSymlink func(p string) SymlinkAction
+}
+
+// SymlinkAction represents what to do on symlink.
+type SymlinkAction int
+
+const (
+ // Deep creates hard-copy of contents.
+ Deep SymlinkAction = iota
+ // Shallow creates new symlink to the dest of symlink.
+ Shallow
+ // Skip does nothing with symlink.
+ Skip
+)
+
+// DefaultOptions by default.
+var DefaultOptions = Options{
+ OnSymlink: func(string) SymlinkAction {
+ return Shallow
+ },
+}
diff --git a/testdata/case06/README.md b/testdata/case06/README.md
deleted file mode 120000
index ea72647..0000000
--- a/testdata/case06/README.md
+++ /dev/null
@@ -1 +0,0 @@
-../case05/README.md
\ No newline at end of file