github.com/blend/go-sdk@v1.20220411.3/sourceutil/copy_all.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package sourceutil 9 10 import ( 11 "io" 12 "os" 13 "path/filepath" 14 15 "github.com/blend/go-sdk/stringutil" 16 ) 17 18 // CopyAll copies all files and directories from a source path to a destination path recurrsively. 19 func CopyAll(destination, source string, opts ...CopyAllOption) error { 20 info, err := os.Lstat(source) 21 if err != nil { 22 return err 23 } 24 var finalOptions CopyAllOptions 25 for _, opt := range opts { 26 opt(&finalOptions) 27 } 28 return copyAll(destination, source, info, finalOptions) 29 } 30 31 // OptCopyAllSymlinkMode sets the symlink mode. 32 func OptCopyAllSymlinkMode(mode CopyAllSymlinkMode) CopyAllOption { 33 return func(cop *CopyAllOptions) { cop.SymlinkMode = mode } 34 } 35 36 // OptCopyAllSkipGlobs sets the skip provider to a glob matcher based on a given set of glob(s). 37 func OptCopyAllSkipGlobs(globs ...string) CopyAllOption { 38 return func(cop *CopyAllOptions) { 39 cop.SkipProvider = func(fileInfo os.FileInfo) bool { 40 for _, glob := range globs { 41 if stringutil.Glob(fileInfo.Name(), glob) { 42 return true 43 } 44 } 45 return false 46 } 47 } 48 } 49 50 // CopyAllOptions are the options for copy all. 51 type CopyAllOptions struct { 52 SymlinkMode CopyAllSymlinkMode 53 SkipProvider func(os.FileInfo) bool 54 } 55 56 // CopyAllOption is a mutator for copy all options 57 type CopyAllOption func(*CopyAllOptions) 58 59 // CopyAllSymlinkMode is how symlinks should be handled 60 type CopyAllSymlinkMode int 61 62 // CopyAllSymlinkMode(s) 63 var ( 64 // CopyAllSymlinkModeShallow will copy links from the source to the destination as links. 65 CopyAllSymlinkModeShallow CopyAllSymlinkMode = 0 66 // CopyAllSymlinkModeDeep will traverse into the link destination and copy any files recursively. 67 CopyAllSymlinkModeDeep CopyAllSymlinkMode = 1 68 // CopyAllSymlinkModeSkip will skip any links discovered. 69 CopyAllSymlinkModeSkip CopyAllSymlinkMode = 2 70 ) 71 72 // copyAll switches proper copy functions regarding file type, etc... 73 // If there would be anything else here, add a case to this switchboard. 74 func copyAll(destination, source string, info os.FileInfo, opts CopyAllOptions) error { 75 if opts.SkipProvider != nil { 76 if opts.SkipProvider(info) { 77 return nil 78 } 79 } 80 81 switch { 82 case info.Mode()&os.ModeSymlink != 0: 83 return symCopy(destination, source, info, opts) 84 case info.IsDir(): 85 return dirCopy(destination, source, info, opts) 86 default: 87 return fileCopy(destination, source, info, opts) 88 } 89 } 90 91 func symCopy(destination, source string, sourceDirInfo os.FileInfo, opts CopyAllOptions) error { 92 switch opts.SymlinkMode { 93 case CopyAllSymlinkModeShallow: 94 return linkCopy(destination, source) 95 case CopyAllSymlinkModeDeep: 96 orig, err := os.Readlink(source) 97 if err != nil { 98 return err 99 } 100 originalSourceDirInfo, err := os.Lstat(orig) 101 if err != nil { 102 return err 103 } 104 return copyAll(destination, orig, originalSourceDirInfo, opts) 105 case CopyAllSymlinkModeSkip: 106 fallthrough 107 default: 108 return nil // do nothing 109 } 110 } 111 112 // linkCopy copies a symlink. 113 func linkCopy(destination, source string) error { 114 src, err := os.Readlink(source) 115 if err != nil { 116 return err 117 } 118 return os.Symlink(src, destination) 119 } 120 121 // dirCopy copies a directory recursively 122 func dirCopy(destination, source string, sourceDirInfo os.FileInfo, opts CopyAllOptions) (err error) { 123 if _, statErr := os.Stat(destination); statErr != nil { 124 // create the destination with the source permissions 125 if err = os.MkdirAll(destination, sourceDirInfo.Mode()); err != nil { 126 return 127 } 128 } 129 130 var sourceDirContents []os.DirEntry 131 sourceDirContents, err = os.ReadDir(source) 132 if err != nil { 133 return 134 } 135 for _, sourceDirItem := range sourceDirContents { 136 destinationName := filepath.Join(destination, sourceDirItem.Name()) 137 sourceName := filepath.Join(source, sourceDirItem.Name()) 138 var sourceDirItemInfo os.FileInfo 139 sourceDirItemInfo, err = sourceDirItem.Info() 140 if err != nil { 141 return 142 } 143 144 if _, statErr := os.Stat(destinationName); statErr != nil { 145 if err = copyAll(destinationName, sourceName, sourceDirItemInfo, opts); err != nil { 146 return 147 } 148 } 149 } 150 return 151 } 152 153 // fileCopy copies a single file 154 func fileCopy(destination, source string, sourceInfo os.FileInfo, opts CopyAllOptions) (err error) { 155 destinationDir := filepath.Dir(destination) 156 if _, statErr := os.Stat(destinationDir); statErr != nil { 157 if err = os.MkdirAll(destinationDir, sourceInfo.Mode()); err != nil { 158 return 159 } 160 } 161 162 f, err := os.Create(destination) 163 if err != nil { 164 return 165 } 166 defer fclose(f, &err) 167 168 s, err := os.Open(source) 169 if err != nil { 170 return 171 } 172 defer fclose(s, &err) 173 174 if _, err = io.Copy(f, s); err != nil { 175 return 176 } 177 return 178 } 179 180 // fclose ANYHOW closes file, 181 // with asiging error raised during Close, 182 // BUT respecting the error already reported. 183 func fclose(f *os.File, reported *error) { 184 if err := f.Close(); *reported == nil { 185 *reported = err 186 } 187 }