Skip to content

Commit

Permalink
Merge pull request #17 from otiai10/feature/opt-symlink
Browse files Browse the repository at this point in the history
Feature/opt symlink
  • Loading branch information
otiai10 committed Mar 5, 2020
2 parents f71bf16 + ec4d969 commit 9530851
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 9 deletions.
35 changes: 35 additions & 0 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ func TestCopy(t *testing.T) {
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)
Expand Down
45 changes: 36 additions & 9 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,25 @@ const (
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)
}
Expand Down Expand Up @@ -68,7 +69,7 @@ func fcopy(src, dest string, info os.FileInfo) (err error) {
// 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()

Expand All @@ -86,7 +87,7 @@ func dcopy(srcdir, destdir string, info os.FileInfo) (err error) {

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
}
Expand All @@ -95,9 +96,35 @@ func dcopy(srcdir, destdir string, info os.FileInfo) (err error) {
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
Expand Down
26 changes: 26 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -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
},
}

0 comments on commit 9530851

Please sign in to comment.