var common = require('./common'); var fs = require('fs'); // Recursively removes 'dir' // Adapted from https://github.com/ryanmcgrath/wrench-js // // Copyright (c) 2010 Ryan McGrath // Copyright (c) 2012 Artur Adib // // Licensed under the MIT License // http://www.opensource.org/licenses/mit-license.php function rmdirSyncRecursive(dir, force) { var files; files = fs.readdirSync(dir); // Loop through and delete everything in the sub-tree after checking it for(var i = 0; i < files.length; i++) { var file = dir + "/" + files[i], currFile = fs.lstatSync(file); if(currFile.isDirectory()) { // Recursive function back to the beginning rmdirSyncRecursive(file, force); } else if(currFile.isSymbolicLink()) { // Unlink symlinks if (force || isWriteable(file)) { try { common.unlinkSync(file); } catch (e) { common.error('could not remove file (code '+e.code+'): ' + file, true); } } } else // Assume it's a file - perhaps a try/catch belongs here? if (force || isWriteable(file)) { try { common.unlinkSync(file); } catch (e) { common.error('could not remove file (code '+e.code+'): ' + file, true); } } } // Now that we know everything in the sub-tree has been deleted, we can delete the main directory. // Huzzah for the shopkeep. var result; try { // Retry on windows, sometimes it takes a little time before all the files in the directory are gone var start = Date.now(); while (true) { try { result = fs.rmdirSync(dir); if (fs.existsSync(dir)) throw { code: "EAGAIN" }; break; } catch(er) { // In addition to error codes, also check if the directory still exists and loop again if true if (process.platform === "win32" && (er.code === "ENOTEMPTY" || er.code === "EBUSY" || er.code === "EPERM" || er.code === "EAGAIN")) { if (Date.now() - start > 1000) throw er; } else if (er.code === "ENOENT") { // Directory did not exist, deletion was successful break; } else { throw er; } } } } catch(e) { common.error('could not remove directory (code '+e.code+'): ' + dir, true); } return result; } // rmdirSyncRecursive // Hack to determine if file has write permissions for current user // Avoids having to check user, group, etc, but it's probably slow function isWriteable(file) { var writePermission = true; try { var __fd = fs.openSync(file, 'a'); fs.closeSync(__fd); } catch(e) { writePermission = false; } return writePermission; } //@ //@ ### rm([options,] file [, file ...]) //@ ### rm([options,] file_array) //@ Available options: //@ //@ + `-f`: force //@ + `-r, -R`: recursive //@ //@ Examples: //@ //@ ```javascript //@ rm('-rf', '/tmp/*'); //@ rm('some_file.txt', 'another_file.txt'); //@ rm(['some_file.txt', 'another_file.txt']); // same as above //@ ``` //@ //@ Removes files. The wildcard `*` is accepted. function _rm(options, files) { options = common.parseOptions(options, { 'f': 'force', 'r': 'recursive', 'R': 'recursive' }); if (!files) common.error('no paths given'); if (typeof files === 'string') files = [].slice.call(arguments, 1); // if it's array leave it as it is files = common.expand(files); files.forEach(function(file) { if (!fs.existsSync(file)) { // Path does not exist, no force flag given if (!options.force) common.error('no such file or directory: '+file, true); return; // skip file } // If here, path exists var stats = fs.lstatSync(file); if (stats.isFile() || stats.isSymbolicLink()) { // Do not check for file writing permissions if (options.force) { common.unlinkSync(file); return; } if (isWriteable(file)) common.unlinkSync(file); else common.error('permission denied: '+file, true); return; } // simple file // Path is an existing directory, but no -r flag given if (stats.isDirectory() && !options.recursive) { common.error('path is a directory', true); return; // skip path } // Recursively remove existing directory if (stats.isDirectory() && options.recursive) { rmdirSyncRecursive(file, options.force); } }); // forEach(file) } // rm module.exports = _rm;