github.com/vugu/vugu@v0.3.5/distutil/copy-files.go (about) 1 package distutil 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "regexp" 9 "time" 10 ) 11 12 // DefaultFileInclPattern is a sensible default set of "static" files. 13 // This might be updated from time to time to include new types of assets used on the web. 14 // Extensions which are used for server executables, server configuration files, or files with 15 // an empty extension will not be added here. 16 var DefaultFileInclPattern = regexp.MustCompile(`[.](css|js|html|map|jpg|jpeg|png|gif|svg|eot|ttf|otf|woff|woff2|wasm)$`) 17 18 func must(err error) { 19 if err != nil { 20 panic(err) 21 } 22 } 23 24 // MustCopyDirFiltered is like CopyDirFiltered but panics on error. 25 func MustCopyDirFiltered(srcDir, dstDir string, fileInclPattern *regexp.Regexp) { 26 must(CopyDirFiltered(srcDir, dstDir, fileInclPattern)) 27 } 28 29 // CopyDirFiltered recursively copies from srcDir to dstDir that match fileInclPattern. 30 // fileInclPattern is only checked against the base name of the file, not its directory. 31 // If fileInclPattern is nil, DefaultFileInclPattern will be used. 32 // The dstDir is skipped if encountered when recursing into srcDir. 33 // Directories are only created in the output dir if there's a file there. 34 // dstDir must already exist. For individual file copies, CopyFile() is used, which means 35 // files with the same name, modification time and size are assumed to be up to date and 36 // the function returns immediately. Conversely when the copy succeeds the modification 37 // time is set to that of the source. 38 func CopyDirFiltered(srcDir, dstDir string, fileInclPattern *regexp.Regexp) error { 39 40 if fileInclPattern == nil { 41 fileInclPattern = DefaultFileInclPattern 42 } 43 44 srcDir, err := filepath.Abs(srcDir) 45 if err != nil { 46 return err 47 } 48 dstDir, err = filepath.Abs(dstDir) 49 if err != nil { 50 return err 51 } 52 53 var copydir func(src, dst string) error 54 copydir = func(src, dst string) error { 55 56 src, err := filepath.Abs(src) 57 if err != nil { 58 return err 59 } 60 if src == dstDir { // makes it so dstDir can be inside srcDir without causing problems 61 // fmt.Printf("skipping destination dir: %s\n", dstDir) 62 return nil 63 } 64 dst, err = filepath.Abs(dst) 65 if err != nil { 66 return err 67 } 68 69 srcf, err := os.Open(src) 70 if err != nil { 71 return err 72 } 73 defer srcf.Close() 74 75 srcFIs, err := srcf.Readdir(-1) 76 if err != nil { 77 return err 78 } 79 80 for _, srcFI := range srcFIs { 81 82 // for directories we recurse... 83 if srcFI.IsDir() { 84 nextSrc := filepath.Join(src, srcFI.Name()) 85 nextDst := filepath.Join(dst, srcFI.Name()) 86 err := copydir(nextSrc, nextDst) 87 if err != nil { 88 return err 89 } 90 continue 91 } 92 93 // for files... 94 95 // skip if it doesn't match the pattern 96 if !fileInclPattern.MatchString(srcFI.Name()) { 97 continue 98 } 99 100 // make sure the destination directory exists 101 err = os.MkdirAll(dst, 0755) 102 if err != nil { 103 return err 104 } 105 106 srcFile := filepath.Join(src, srcFI.Name()) 107 dstFile := filepath.Join(dst, srcFI.Name()) 108 109 // copy the file 110 err = CopyFile(srcFile, dstFile) 111 if err != nil { 112 return err 113 } 114 115 } 116 117 return nil 118 } 119 return copydir(srcDir, dstDir) 120 121 } 122 123 // MustCopyFile is like CopyFile but panics on error. 124 func MustCopyFile(src, dst string) { 125 must(CopyFile(src, dst)) 126 } 127 128 // CopyFile copies src to dest. Will not copy directories. Symlinks will have their contents copied. 129 // Files with the same name, modification time and size are assumed to be up to date and 130 // the function returns immediately. Conversely when the copy succeeds the modification 131 // time is set to that of the source. 132 func CopyFile(src, dst string) error { 133 134 src, err := filepath.Abs(src) 135 if err != nil { 136 return err 137 } 138 dst, err = filepath.Abs(dst) 139 if err != nil { 140 return err 141 } 142 143 srcFI, err := os.Stat(src) 144 if err != nil { 145 return err 146 } 147 148 dstFI, err := os.Stat(dst) 149 if err == nil { 150 151 if dstFI.IsDir() { 152 return fmt.Errorf("destination (%q) is directory, cannot CopyFile", dst) 153 } 154 155 // destination file exists, let's see if it looks like it's the same 156 dstModTime := dstFI.ModTime().Truncate(time.Second) 157 srcModTime := srcFI.ModTime().Truncate(time.Second) 158 if dstModTime == srcModTime && srcFI.Size() == dstFI.Size() { 159 return nil // looks like our work is already done, just return 160 } 161 } 162 163 // open the file, create if it doesn't exist, truncate if it does 164 dstF, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, srcFI.Mode()) 165 if err != nil { 166 return err 167 } 168 defer dstF.Close() 169 170 srcF, err := os.Open(src) 171 if err != nil { 172 return err 173 } 174 defer srcF.Close() 175 176 _, err = io.Copy(dstF, srcF) 177 if err != nil { 178 return err 179 } 180 181 // update destionation file's mod timestamp to match the source file 182 err = os.Chtimes(dst, time.Now(), srcFI.ModTime()) 183 if err != nil { 184 return err 185 } 186 187 return nil 188 }