1/* 2 * Utilities: A classic collection of JavaScript utilities 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17*/ 18 19let fs = require('fs'); 20let path = require('path'); 21 22/** 23 @name file 24 @namespace file 25*/ 26 27let fileUtils = new (function () { 28 29 // Recursively copy files and directories 30 let _copyFile = function (fromPath, toPath, opts) { 31 let from = path.normalize(fromPath); 32 let to = path.normalize(toPath); 33 let options = opts || {}; 34 let fromStat; 35 let toStat; 36 let destExists; 37 let destDoesNotExistErr; 38 let content; 39 let filename; 40 let dirContents; 41 let targetDir; 42 43 fromStat = fs.statSync(from); 44 45 try { 46 //console.dir(to + ' destExists'); 47 toStat = fs.statSync(to); 48 destExists = true; 49 } 50 catch(e) { 51 //console.dir(to + ' does not exist'); 52 destDoesNotExistErr = e; 53 destExists = false; 54 } 55 // Destination dir or file exists, copy into (directory) 56 // or overwrite (file) 57 if (destExists) { 58 59 // If there's a rename-via-copy file/dir name passed, use it. 60 // Otherwise use the actual file/dir name 61 filename = options.rename || path.basename(from); 62 63 // Copying a directory 64 if (fromStat.isDirectory()) { 65 dirContents = fs.readdirSync(from); 66 targetDir = path.join(to, filename); 67 // We don't care if the target dir already exists 68 try { 69 fs.mkdirSync(targetDir, {mode: fromStat.mode & 0o777}); 70 } 71 catch(e) { 72 if (e.code !== 'EEXIST') { 73 throw e; 74 } 75 } 76 for (let i = 0, ii = dirContents.length; i < ii; i++) { 77 _copyFile(path.join(from, dirContents[i]), targetDir, {preserveMode: options.preserveMode}); 78 } 79 } 80 // Copying a file 81 else { 82 content = fs.readFileSync(from); 83 let mode = fromStat.mode & 0o777; 84 let targetFile = to; 85 86 if (toStat.isDirectory()) { 87 targetFile = path.join(to, filename); 88 } 89 90 let fileExists = fs.existsSync(targetFile); 91 fs.writeFileSync(targetFile, content); 92 93 // If the file didn't already exist, use the original file mode. 94 // Otherwise, only update the mode if preserverMode is true. 95 if(!fileExists || options.preserveMode) { 96 fs.chmodSync(targetFile, mode); 97 } 98 } 99 } 100 // Dest doesn't exist, can't create it 101 else { 102 throw destDoesNotExistErr; 103 } 104 }; 105 106 // Remove the given directory 107 let _rmDir = function (dirPath) { 108 let dir = path.normalize(dirPath); 109 let paths = []; 110 paths = fs.readdirSync(dir); 111 paths.forEach(function (p) { 112 let curr = path.join(dir, p); 113 let stat = fs.lstatSync(curr); 114 if (stat.isDirectory()) { 115 _rmDir(curr); 116 } 117 else { 118 try { 119 fs.unlinkSync(curr); 120 } catch(e) { 121 if (e.code === 'EPERM') { 122 fs.chmodSync(curr, parseInt(666, 8)); 123 fs.unlinkSync(curr); 124 } else { 125 throw e; 126 } 127 } 128 } 129 }); 130 fs.rmdirSync(dir); 131 }; 132 133 /** 134 @name file#cpR 135 @public 136 @function 137 @description Copies a directory/file to a destination 138 @param {String} fromPath The source path to copy from 139 @param {String} toPath The destination path to copy to 140 @param {Object} opts Options to use 141 @param {Boolean} [opts.preserveMode] If target file already exists, this 142 determines whether the original file's mode is copied over. The default of 143 false mimics the behavior of the `cp` command line tool. (Default: false) 144 */ 145 this.cpR = function (fromPath, toPath, options) { 146 let from = path.normalize(fromPath); 147 let to = path.normalize(toPath); 148 let toStat; 149 let doesNotExistErr; 150 let filename; 151 let opts = options || {}; 152 153 if (from == to) { 154 throw new Error('Cannot copy ' + from + ' to itself.'); 155 } 156 157 // Handle rename-via-copy 158 try { 159 toStat = fs.statSync(to); 160 } 161 catch(e) { 162 doesNotExistErr = e; 163 164 // Get abs path so it's possible to check parent dir 165 if (!this.isAbsolute(to)) { 166 to = path.join(process.cwd(), to); 167 } 168 169 // Save the file/dir name 170 filename = path.basename(to); 171 // See if a parent dir exists, so there's a place to put the 172 /// renamed file/dir (resets the destination for the copy) 173 to = path.dirname(to); 174 try { 175 toStat = fs.statSync(to); 176 } 177 catch(e) {} 178 if (toStat && toStat.isDirectory()) { 179 // Set the rename opt to pass to the copy func, will be used 180 // as the new file/dir name 181 opts.rename = filename; 182 //console.log('filename ' + filename); 183 } 184 else { 185 throw doesNotExistErr; 186 } 187 } 188 189 _copyFile(from, to, opts); 190 }; 191 192 /** 193 @name file#mkdirP 194 @public 195 @function 196 @description Create the given directory(ies) using the given mode permissions 197 @param {String} dir The directory to create 198 @param {Number} mode The mode to give the created directory(ies)(Default: 0755) 199 */ 200 this.mkdirP = function (dir, mode) { 201 let dirPath = path.normalize(dir); 202 let paths = dirPath.split(/\/|\\/); 203 let currPath = ''; 204 let next; 205 206 if (paths[0] == '' || /^[A-Za-z]+:/.test(paths[0])) { 207 currPath = paths.shift() || '/'; 208 currPath = path.join(currPath, paths.shift()); 209 //console.log('basedir'); 210 } 211 while ((next = paths.shift())) { 212 if (next == '..') { 213 currPath = path.join(currPath, next); 214 continue; 215 } 216 currPath = path.join(currPath, next); 217 try { 218 //console.log('making ' + currPath); 219 fs.mkdirSync(currPath, mode || parseInt(755, 8)); 220 } 221 catch(e) { 222 if (e.code != 'EEXIST') { 223 throw e; 224 } 225 } 226 } 227 }; 228 229 /** 230 @name file#rmRf 231 @public 232 @function 233 @description Deletes the given directory/file 234 @param {String} p The path to delete, can be a directory or file 235 */ 236 this.rmRf = function (p, options) { 237 let stat; 238 try { 239 stat = fs.lstatSync(p); 240 if (stat.isDirectory()) { 241 _rmDir(p); 242 } 243 else { 244 fs.unlinkSync(p); 245 } 246 } 247 catch (e) {} 248 }; 249 250 /** 251 @name file#isAbsolute 252 @public 253 @function 254 @return {Boolean/String} If it's absolute the first character is returned otherwise false 255 @description Checks if a given path is absolute or relative 256 @param {String} p Path to check 257 */ 258 this.isAbsolute = function (p) { 259 let match = /^[A-Za-z]+:\\|^\//.exec(p); 260 if (match && match.length) { 261 return match[0]; 262 } 263 return false; 264 }; 265 266 /** 267 @name file#absolutize 268 @public 269 @function 270 @return {String} Returns the absolute path for the given path 271 @description Returns the absolute path for the given path 272 @param {String} p The path to get the absolute path for 273 */ 274 this.absolutize = function (p) { 275 if (this.isAbsolute(p)) { 276 return p; 277 } 278 else { 279 return path.join(process.cwd(), p); 280 } 281 }; 282 283})(); 284 285module.exports = fileUtils; 286 287