Comparing version 0.3.1 to 0.4.0
"use strict"; | ||
var Model = require("nature").Model, | ||
/** @module renamer */ | ||
var mfs = require("more-fs"), | ||
w = require("wodge"), | ||
dope = require("console-dope"), | ||
path = require("path"), | ||
@@ -7,122 +10,84 @@ util = require("util"), | ||
Result = require("./Result"), | ||
mfs = require("more-fs"), | ||
Glob = require("glob").Glob, | ||
w = require("wodge"); | ||
RenamerOptions = require("./RenamerOptions"); | ||
exports.RenameOptions = RenameOptions; | ||
exports.Renamer = Renamer; | ||
exports.Result = Result; | ||
exports.RenamerOptions = RenamerOptions; | ||
exports.replace = replace; | ||
exports.expand = expand; | ||
exports.rename = rename; | ||
exports.dryRun = dryRun; | ||
exports.replaceIndexToken = replaceIndexToken; | ||
function RenameOptions(){ | ||
this.define({ | ||
name: "files", | ||
type: Array, | ||
defaultOption: true, | ||
value: [] | ||
}) | ||
.define({ name: "find", type: "string", alias: "f" }) | ||
.define({ name: "replace", type: "string", alias: "r", value: "" }) | ||
.define({ name: "regex", type: "boolean", alias: "e" }) | ||
.define({ name: "dry-run", type: "boolean", alias: "d" }) | ||
.define({ name: "insensitive", type: "boolean", alias: "i" }); | ||
} | ||
util.inherits(RenameOptions, Model); | ||
/** | ||
@constructor | ||
*/ | ||
function Renamer(options){ | ||
this._options = new RenameOptions().set(options); | ||
if (!this._options.valid) throw new Error("Invalid options: " + this._options.validationMessages); | ||
} | ||
Perform the replace. If no `options.find` is supplied, the entire basename is replaced by `options.replace`. | ||
/** | ||
@method process | ||
@return {Array} results An array of result objects | ||
@example | ||
[ | ||
{ before: "file1.txt", after: "clive.txt", renamed: false, error: "file exists" } | ||
{ before: "file2.txt", after: "clive2.txt", renamed: true } | ||
] | ||
@alias module:renamer.replace | ||
@param {RenamerOptions} options - Contains the file list and renaming options | ||
@returns {Array} An array of ResultObject instances containing `before` and `after` info | ||
*/ | ||
Renamer.prototype.process = function(){ | ||
var options = this._options, | ||
fileStats = new mfs.FileStats(options.files); | ||
function replace(options){ | ||
options = new RenamerOptions(options); | ||
var findRegex = regExBuilder(options); | ||
return options.files.map(function(file){ | ||
var result = new Result(), | ||
dirname = path.dirname(file), | ||
basename = path.basename(file); | ||
options.files = fileStats.files | ||
.concat(fileStats.dirs.reverse()) | ||
.map(function(file){ return { before: file }; }); | ||
if(options.find){ | ||
if (basename.search(findRegex) > -1){ | ||
basename = basename.replace(findRegex, options.replace); | ||
result.after = path.join(dirname, basename); | ||
} else { | ||
/* leave result.after blank, signifying no replace was performed */ | ||
} | ||
} else { | ||
result.after = path.join(dirname, options.replace); | ||
} | ||
var results = options.files | ||
.map(this._renameFile.bind(this)) | ||
.map(replaceIndexToken) | ||
.map(this._dryRun.bind(this)) | ||
.map(this._renameOnDisk.bind(this)); | ||
result.before = path.normalize(file); | ||
return result; | ||
}); | ||
} | ||
return results; | ||
}; | ||
/** | ||
Search globally by default. If `options.regex` is not set then ensure any special regex characters in `options.find` are escaped. | ||
*/ | ||
function regExBuilder(options){ | ||
var re = options.regex ? options.find : w.escapeRegExp(options.find), | ||
reOptions = "g" + (options.insensitive ? "i" : ""); | ||
return new RegExp(re, reOptions); | ||
function expand(files){ | ||
var fileStats = new mfs.FileStats(files); | ||
fileStats.filesAndDirs = fileStats.files.concat(fileStats.dirs.reverse()); | ||
return fileStats; | ||
} | ||
/** | ||
Perform the replace, on the basename only. If no `options.find` is supplied, the entire basename is replaced by `options.replace`. | ||
@method | ||
@param result {ResultObject} input | ||
@returns {ResultObject} resultObject An object containing the input path before and after the rename. | ||
*/ | ||
Renamer.prototype._renameFile = function(result, index, resultsSoFar){ | ||
var after, | ||
options = this._options, | ||
dirname = path.dirname(result.before), | ||
basename = path.basename(result.before), | ||
re = regExBuilder(options); | ||
function dryRun(resultArray){ | ||
return resultArray.map(function(result, index, resultsSoFar){ | ||
var existing = resultsSoFar.filter(function(prevResult, prevIndex){ | ||
return prevIndex < index && (prevResult.before !== result.before) && (prevResult.after === result.after); | ||
}); | ||
if(options.find){ | ||
basename = basename.replace(re, options.replace); | ||
after = path.join(dirname, basename); | ||
} else { | ||
after = path.join(dirname, options.replace); | ||
} | ||
if (result.before === result.after || !result.after){ | ||
result.renamed = false; | ||
result.error = "no change"; | ||
} else if (existing.length){ | ||
result.renamed = false; | ||
result.error = "file exists"; | ||
} else { | ||
result.renamed = true; | ||
} | ||
result.before = path.normalize(result.before); | ||
result.after = after; | ||
result.renamed = true; | ||
return result; | ||
}; | ||
Renamer.prototype._dryRun = function(result, index, resultsSoFar){ | ||
var existing = resultsSoFar.filter(function(prevResult, prevIndex){ | ||
return prevIndex < index && (prevResult.before !== result.before) && (prevResult.after === result.after); | ||
return result; | ||
}); | ||
} | ||
if (result.before === result.after ){ | ||
result.renamed = false; | ||
} else if (existing.length || fs.existsSync(result.after)){ | ||
result.renamed = false; | ||
result.error = "file exists"; | ||
} | ||
return result; | ||
}; | ||
/** | ||
Perform the replace, on disk. Sets the `renamed` and/or `error` properties on the resultObject. | ||
@method | ||
@param result {ResultObject} input | ||
@returns {ResultObject} resultObject | ||
*/ | ||
Renamer.prototype._renameOnDisk = function(result){ | ||
var options = this._options; | ||
if (!options["dry-run"] && result.renamed === true) { | ||
function rename(resultArray){ | ||
return resultArray.map(function(result){ | ||
if (!result.after){ | ||
result.renamed = false; | ||
result.error = "no change"; | ||
return result; | ||
} | ||
try { | ||
fs.renameSync(result.before, result.after); | ||
if (fs.existsSync(result.after)){ | ||
result.renamed = false; | ||
result.error = "file exists"; | ||
} else { | ||
fs.renameSync(result.before, result.after); | ||
result.renamed = true; | ||
} | ||
} catch(e){ | ||
@@ -132,12 +97,22 @@ result.renamed = false; | ||
} | ||
} | ||
return result; | ||
}); | ||
} | ||
return result; | ||
}; | ||
function replaceIndexToken(resultArray){ | ||
return resultArray.map(function(result, index){ | ||
if (result.after){ | ||
result.after = result.after.replace("{{index}}", index + 1); | ||
} | ||
return result; | ||
}); | ||
} | ||
function replaceIndexToken(result, index){ | ||
if (result.after){ | ||
result.after = result.after.replace("{{index}}", index + 1); | ||
} | ||
return result; | ||
/** | ||
Search globally by default. If `options.regex` is not set then ensure any special regex characters in `options.find` are escaped. | ||
*/ | ||
function regExBuilder(options){ | ||
var re = options.regex ? options.find : w.escapeRegExp(options.find), | ||
reOptions = "g" + (options.insensitive ? "i" : ""); | ||
return new RegExp(re, reOptions); | ||
} |
module.exports = Result; | ||
function Result(){ | ||
this.before = null; | ||
this.after = null; | ||
this.renamed = null; | ||
this.error = null; | ||
// this.before = null; | ||
// this.after = null; | ||
// this.renamed = null; | ||
// this.error = null; | ||
} |
{ | ||
"name": "renamer", | ||
"description": "Batch rename files and folders", | ||
"version": "0.3.1", | ||
"bin": "cli.js", | ||
"version": "0.4.0", | ||
"bin": "bin/cli.js", | ||
"main": "./lib/renamer", | ||
@@ -10,3 +10,3 @@ "repository": "https://github.com/75lb/renamer", | ||
"scripts": { | ||
"testx": "mocha --reporter spec test/unit-*.js" | ||
"test": "tape test/*.js" | ||
}, | ||
@@ -18,5 +18,7 @@ "dependencies": { | ||
"console-dope": "~0.3", | ||
"more-fs": "~0.1.2" | ||
"more-fs": "~0.2" | ||
}, | ||
"devDependencies": {} | ||
"devDependencies": { | ||
"tape": "~2.12.0" | ||
} | ||
} |
270
README.md
@@ -38,136 +38,198 @@ [![NPM version](https://badge.fury.io/js/renamer.png)](http://badge.fury.io/js/renamer) | ||
For more information on Regular Expressions, see [this useful guide](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions). | ||
For more information on Regular Expressions, see [this useful guide](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions). | ||
**Don't forget to test your rename first using `--dry-run`!** | ||
Globbing | ||
-------- | ||
Renamer comes with globbing support built in (provided by [node-glob](https://github.com/isaacs/node-glob)). If you want to override your shell's native [expansion](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions) behaviour (say, for example it lacks the [globstar](http://www.linuxjournal.com/content/globstar-new-bash-globbing-option) option), pass the glob expression in single quotes and renamer will expand it. For example, this command operates on all js files, recursively: | ||
Recursing | ||
--------- | ||
Renamer comes with globbing support built in (provided by [node-glob](https://github.com/isaacs/node-glob)), enabling recursive operations. To recurse, use the `**` wildcard where a directory name would appear to apply the meaning "any directory, including this one". | ||
$ renamer -f 'this' -r 'that' '**/*.js' | ||
For example, this command operates on all js files in the current directory: | ||
$ renamer --find this --replace that *.js | ||
this command operates on all js files, recursively: | ||
$ renamer --find this --replace that **/*.js | ||
this command operates on all js files from the `lib` directory downward: | ||
$ renamer --find this --replace that lib/**/*.js | ||
**Bash users without globstar will need to enclose the glob expression in quotes to prevent native file expansion**, i.e. `"**/*.js"` | ||
Examples | ||
-------- | ||
_Simple replace_ | ||
###Simple replace | ||
```sh | ||
$ tree -N | ||
. | ||
├── A poem [bad].txt | ||
├── A story [bad].txt | ||
$ renamer --find '[bad]' --replace '[good]' * | ||
``` | ||
$ tree -N | ||
. | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── A poem [bad].txt | ||
├── A story [bad].txt</code></pre></td> | ||
<td><pre><code>. | ||
├── A poem [good].txt | ||
├── A story [good].txt | ||
``` | ||
├── A story [good].txt</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_Case insenstive finds_ | ||
###Case insenstive finds | ||
```sh | ||
$ tree -N | ||
. | ||
$ renamer --insensitive --find 'mpeg4' --replace 'mp4' * | ||
``` | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── A video.MPEG4 | ||
├── Another video.Mpeg4 | ||
$ renamer --insensitive --find 'mpeg4' --replace 'mp4' * | ||
$ tree -N | ||
. | ||
├── Another video.Mpeg4</code></pre></td> | ||
<td><pre><code>. | ||
├── A video.mp4 | ||
├── Another video.mp4 | ||
``` | ||
├── Another video.mp4</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_Strip out unwanted text_: | ||
###Strip out unwanted text | ||
```sh | ||
$ tree -N | ||
. | ||
├── Season 1 - Some crappy episode.mp4 | ||
├── Season 1 - Load of bollocks.mp4 | ||
$ renamer --find 'Season 1 - ' * | ||
``` | ||
$ tree -N | ||
. | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── Season 1 - Some crappy episode.mp4 | ||
├── Season 1 - Load of bollocks.mp4</code></pre></td> | ||
<td><pre><code>. | ||
├── Some crappy episode.mp4 | ||
├── Load of bollocks.mp4 | ||
``` | ||
├── Load of bollocks.mp4</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_Simple filename cleanup_: | ||
###Simple filename cleanup | ||
```sh | ||
$ tree | ||
. | ||
$ renamer --regex --find '.*_(\d+)_.*' --replace 'Video $1.mp4' * | ||
``` | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── [ag]_Annoying_filename_-_3_[38881CD1].mp4 | ||
├── [ag]_Annoying_filename_-_34_[38881CD1].mp4 | ||
├── [ag]_Annoying_filename_-_53_[38881CD1].mp4 | ||
$ renamer --regex --find '.*_(\d+)_.*' --replace 'Video $1.mp4' * | ||
$ tree | ||
. | ||
├── [ag]_Annoying_filename_-_53_[38881CD1].mp4</code></pre></td> | ||
<td><pre><code>. | ||
├── Video 3.mp4 | ||
├── Video 34.mp4 | ||
├── Video 53.mp4 | ||
``` | ||
├── Video 53.mp4</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_Give your images a new numbering scheme_: | ||
###Give your images a new numbering scheme | ||
```sh | ||
$ tree | ||
. | ||
$ renamer --replace 'Image{{index}}.jpg' * | ||
``` | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── IMG_5776.JPG | ||
├── IMG_5777.JPG | ||
├── IMG_5778.JPG | ||
$ renamer --replace 'Image{{index}}.jpg' * | ||
$ tree | ||
. | ||
├── IMG_5778.JPG</code></pre></td> | ||
<td><pre><code>. | ||
├── Image1.jpg | ||
├── Image2.jpg | ||
├── Image3.jpg | ||
``` | ||
├── Image3.jpg</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_do something about all those full stops_: | ||
###do something about all those full stops | ||
```sh | ||
$ tree | ||
. | ||
├── loads.of.full.stops.every.where.jpeg | ||
├── loads.of.full.stops.every.where.mp4 | ||
$ renamer --regex --find '\.(?!\w+$)' --replace ' ' * | ||
``` | ||
$ tree | ||
. | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── loads.of.full.stops.every.where.jpeg | ||
├── loads.of.full.stops.every.where.mp4</code></pre></td> | ||
<td><pre><code>. | ||
├── loads of full stops every where.jpeg | ||
├── loads of full stops every where.mp4 | ||
├── loads of full stops every where.mp4</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
###if not already done, add your name to a load of files | ||
```sh | ||
$ renamer --regex --find '(data\d)(\.\w+)' --replace '$1 (checked by Lloyd)$2' * | ||
``` | ||
_if not already done, add your name to a load of files_: | ||
```sh | ||
$ tree | ||
. | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── data1.csv | ||
├── data2 (checked by Lloyd).csv | ||
├── data3.xls | ||
├── data3.xls</code></pre></td> | ||
<td><pre><code>. | ||
├── data1 (checked by Lloyd).csv | ||
├── data2 (checked by Lloyd).csv | ||
├── data3 (checked by Lloyd).xls</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
$ renamer --regex --find '(data\d)(\.\w+)' --replace '$1 (checked by Lloyd)$2' * | ||
$ tree | ||
. | ||
├── data1 (checked by Lloyd).csv | ||
├── data2 (checked by Lloyd).csv | ||
├── data3 (checked by Lloyd).xls | ||
###rename files and folders, recursively | ||
```sh | ||
$ renamer --find 'pic' --replace 'photo' '**' | ||
``` | ||
_rename files and folders, recursively_ | ||
```sh | ||
$ tree | ||
. | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── pic1.jpg | ||
@@ -178,7 +240,4 @@ ├── pic2.jpg | ||
└── pic4.jpg | ||
$ renamer --find 'pic' --replace 'photo' '**' | ||
$ tree | ||
. | ||
</code></pre></td> | ||
<td><pre><code>. | ||
├── photo1.jpg | ||
@@ -188,10 +247,20 @@ ├── photo2.jpg | ||
├── photo3.jpg | ||
└── photo4.jpg | ||
``` | ||
└── photo4.jpg</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
_prefix files and folders, recursively_ | ||
###prefix files and folders, recursively | ||
```sh | ||
$ tree | ||
. | ||
$ renamer --regex --find '^' --replace 'good-' '**' | ||
``` | ||
<table> | ||
<thead> | ||
<tr><th>Before</th><th>After</th></tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td><pre><code>. | ||
├── pic1.jpg | ||
@@ -202,7 +271,4 @@ ├── pic2.jpg | ||
└── pic4.jpg | ||
$ renamer --regex --find '^' --replace 'good-' '**' | ||
$ tree | ||
. | ||
</code></pre></td> | ||
<td><pre><code>. | ||
├── good-pic1.jpg | ||
@@ -212,5 +278,7 @@ ├── good-pic2.jpg | ||
├── good-pic3.jpg | ||
└── good-pic4.jpg | ||
``` | ||
└── good-pic4.jpg</code></pre></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
![NPM](https://nodei.co/npm-dl/renamer.png?months=3) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28795
14
597
280
1
1
+ Addedmore-fs@0.2.2(transitive)
- Removedmore-fs@0.1.4(transitive)
Updatedmore-fs@~0.2