Merge pull request #17 from otiai10/feature/opt-symlink
Feature/opt symlink
diff --git a/all_test.go b/all_test.go
index 4dfa389..8f41734 100644
--- a/all_test.go
+++ b/all_test.go
@@ -73,6 +73,41 @@
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(0)
})
+ 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))
+
+ 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)
+ Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
+
+ 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/case03.default/case01")
+ Expect(t, err).ToBe(nil)
+ 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) {
err := Copy("testdata/case03", "testdata.copy/case03")
Expect(t, err).Not().ToBe(nil)
diff --git a/copy.go b/copy.go
index c9f14c5..2fdb3ea 100644
--- a/copy.go
+++ b/copy.go
@@ -14,24 +14,25 @@
tmpPermissionForDirectory = os.FileMode(0755)
)
-// Copy copies src to dest, doesn't matter if src is a directory or a file
-func Copy(src, dest string) error {
+// 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, 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, info os.FileInfo) error {
+func copy(src, dest string, info os.FileInfo, opt Options) error {
if info.Mode()&os.ModeSymlink != 0 {
- return lcopy(src, dest, info)
+ return onsymlink(src, dest, info, opt)
}
if info.IsDir() {
- return dcopy(src, dest, info)
+ return dcopy(src, dest, info, opt)
}
return fcopy(src, dest, info)
}
@@ -68,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, info os.FileInfo) (err error) {
+func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
originalMode := info.Mode()
@@ -86,7 +87,7 @@
for _, content := range contents {
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
- if err := copy(cs, cd, content); err != nil {
+ if err := copy(cs, cd, content, opt); err != nil {
// If any error, exit immediately
return err
}
@@ -95,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
+ },
+}