Comparing version 1.0.0-4 to 1.0.0-5
23
index.js
@@ -14,23 +14,26 @@ const EventEmitter = require('events').EventEmitter | ||
* @param {object} options - The renamer options | ||
* @param {string[]} [options.files] - One or more glob patterns or names of files to rename. | ||
* @param {sting|RegExp} [options.find] - Find expression. | ||
* @param {string} [options.replace] | ||
* @param {boolean} [options.dryRun] | ||
* @param {boolean} [options.force] | ||
* @param {string} [options.view] - The default view outputs one line per rename. Set `--view detail` to see more info including a diff. | ||
* @param {string[]} [options.plugin] | ||
* @param {string[]} [options.files] - One or more glob patterns or filenames to process. | ||
* @param {boolean} [options.dryRun] - Set this to do everything but rename the file. You should always set this flag until certain the output looks correct. | ||
* @param {boolean} [options.force] - If a target path exists, renamer will stop. With this flag set the target path will be overwritten. The main use-case for this flag is to enable changing the case of files on case-insensitive systems. Use with caution. | ||
* @param {string[]} [options.plugin] - One or more replacer plugins to use, set the `--plugin` option multiple times to build a chain. For each value, supply either: a) a path to a plugin file b) a path to a plugin package c) the name of a plugin package installed globally or in the current working directory (or above) or d) the name of a built-in plugin, either `default` or `index`. The default plugin chain is `default` then `index`, be sure to set `-p default -p index` before your plugin if you wish to extend default behaviour. | ||
* @param {sting|RegExp} [options.find] - Optional find string (e.g. `one`) or regular expression literal (e.g. `/one/i`). If omitted, the whole filename will be matched and replaced. | ||
* @param {string} [options.replace] - The replace string. If omitted, defaults to a empty string. The special token `{{index}}` will insert a number, incremented each time a file is replaced. | ||
* @emits module:renamer#rename-start | ||
*/ | ||
rename (options) { | ||
options = options || {} | ||
const renameFile = require('./lib/rename-file') | ||
const Replacer = require('./lib/replacer') | ||
const util = require('./lib/util') | ||
const files = util.expandGlobPatterns(options.files) | ||
const arrayify = require('array-back') | ||
const files = util.expandGlobPatterns(arrayify(options.files)) | ||
const replacer = new Replacer(options.plugin) | ||
const replaceResults = files | ||
.map((file, index) => replacer.replace(file, options, index, files)) | ||
.sort((a, b) => util.depthFirstCompare(a.from, b.from)) | ||
if (!options.dryRun) { | ||
replaceResults.sort((a, b) => util.depthFirstCompare(a.from, b.from)) | ||
} | ||
for (const replaceResult of replaceResults) { | ||
/** | ||
* Rename start | ||
* Emitted just before each file is processed. | ||
* @event module:renamer#rename-start | ||
@@ -37,0 +40,0 @@ * @type {object} |
@@ -6,5 +6,13 @@ const cliOptions = require('../lib/cli-options') | ||
async start () { | ||
const nodeVersionMatches = require('node-version-matches') | ||
if (!nodeVersionMatches('>=8.9.0')) { | ||
const err = new Error('Renamer requires node v8.9.0 or above. Visit the website to upgrade: https://nodejs.org/') | ||
this.halt(err) | ||
return | ||
} | ||
try { | ||
const commandLinePlugin = require('command-line-plugin') | ||
const path = require('path') | ||
const globalModules = require('global-modules') | ||
const { options, allOptionDefinitions } = commandLinePlugin(cliOptions.optionDefinitions, { | ||
@@ -16,3 +24,3 @@ camelCase: true, | ||
}, | ||
paths: [ path.resolve(__dirname, 'plugin'), '.' ], | ||
paths: [ path.resolve(__dirname, 'plugin'), '.', globalModules ], | ||
prefix: 'renamer-' | ||
@@ -22,3 +30,3 @@ }) | ||
if (options.help) { | ||
this.printUsage(allOptionDefinitions) | ||
this.printUsage(allOptionDefinitions, options.plugin) | ||
} else { | ||
@@ -43,4 +51,3 @@ if (!options.files.length) { | ||
const util = require('./util') | ||
/* create find regexp, incorporating --regexp and --insensitive */ | ||
options.find = util.regExpBuilder(options) | ||
options.find = util.regExpBuilder(options.find) | ||
renamer.rename(options) | ||
@@ -69,3 +76,3 @@ } catch (err) { | ||
const errDesc = result.error ? chalk`({red ${result.error}})` : '' | ||
if (view === 'detail') { | ||
if (view === 'long') { | ||
this.log('Renamed:'.padEnd(8), symbol) | ||
@@ -101,5 +108,5 @@ this.log('Before:'.padEnd(8), result.from) | ||
printUsage (allOptionDefinitions) { | ||
printUsage (allOptionDefinitions, plugins) { | ||
const commandLineUsage = require('command-line-usage') | ||
this.log(commandLineUsage(cliOptions.usageSections(allOptionDefinitions))) | ||
this.log(commandLineUsage(cliOptions.usageSections(allOptionDefinitions, plugins))) | ||
} | ||
@@ -106,0 +113,0 @@ |
@@ -8,3 +8,3 @@ exports.optionDefinitions = [ | ||
defaultValue: [], | ||
description: 'The files to rename. This is the default option.' | ||
description: 'One or more glob patterns or filenames to process.' | ||
}, | ||
@@ -15,3 +15,3 @@ { | ||
alias: 'd', | ||
description: 'Used for test runs. Set this to do everything but rename the file.' | ||
description: 'Set this to do everything but rename the file. You should always set this flag until certain the output looks correct.' | ||
}, | ||
@@ -21,3 +21,3 @@ { | ||
type: Boolean, | ||
description: 'If the target path already exists, overwrite it. Use with caution.' | ||
description: 'If a target path exists, renamer will stop. With this flag set the target path will be overwritten. The main use-case for this flag is to enable changing the case of files on case-insensitive systems. Use with caution.' | ||
}, | ||
@@ -27,4 +27,4 @@ { | ||
type: String, | ||
description: 'The default view outputs one line per rename. Set `--view detail` to see a longer, less condensed view and `--view diff` to include a diff.', | ||
typeLabel: 'detail|diff' | ||
description: 'The default view outputs one line per rename. Set `--view long` to see a longer, less condensed view and `--view diff` to include a diff.', | ||
typeLabel: 'long|diff' | ||
}, | ||
@@ -36,3 +36,3 @@ { | ||
lazyMultiple: true, | ||
description: 'One or more replacer plugins to use, set the `--plugin` option multiple times to build a chain. For each value, supply either a) a path to a plugin file b) a path to a plugin package c) the name of a plugin package installed in the current working directory or above. The default plugin chain is `default` then `index`, be sure to set `-p default -p index` before your plugin if you wish to extend default behaviour.', | ||
description: 'One or more replacer plugins to use, set the `--plugin` option multiple times to build a chain. For each value, supply either: a) a path to a plugin file b) a path to a plugin package c) the name of a plugin package installed globally or in the current working directory (or above) or d) the name of a built-in plugin, either `default` or `index`. The default plugin chain is `default` then `index`, be sure to set `-p default -p index` before your plugin if you wish to extend default behaviour.', | ||
plugin: true, | ||
@@ -55,3 +55,5 @@ defaultValue: [ 'default', 'index' ] | ||
exports.usageSections = function (optionDefinitions) { | ||
exports.usageSections = function (optionDefinitions, plugins) { | ||
const util = require('./util') | ||
plugins = util.loadPlugins(plugins) | ||
return [ | ||
@@ -67,2 +69,11 @@ { | ||
{ | ||
header: 'Replace chain', | ||
content: plugins.length | ||
? plugins.map(mw => ({ | ||
name: '↓ ' + mw.constructor.name, | ||
desc: mw.description && mw.description() | ||
})) | ||
: '{italic Stack empty, supply one or more middlewares using --stack.}' | ||
}, | ||
{ | ||
header: 'Options', | ||
@@ -69,0 +80,0 @@ optionList: optionDefinitions, |
@@ -1,3 +0,7 @@ | ||
class PluginBase {} | ||
class PluginBase { | ||
replace () { | ||
throw new Error('not implemented') | ||
} | ||
} | ||
module.exports = PluginBase |
@@ -1,2 +0,6 @@ | ||
module.exports = PluginBase => class PluginDefault extends PluginBase { | ||
module.exports = PluginBase => class Default extends PluginBase { | ||
description () { | ||
return 'Find and replace strings and regular expressions.' | ||
} | ||
optionDefinitions () { | ||
@@ -16,2 +20,7 @@ return [ | ||
description: 'The replace string. If omitted, defaults to a empty string. The special token `\\{\\{index\\}\\}` will insert a number, incremented each time a file is replaced.' | ||
}, | ||
{ | ||
name: 'path-element', | ||
alias: 'e', | ||
description: 'The path element to rename, valid values are "base" (the default), "name" and "ext".' | ||
} | ||
@@ -21,15 +30,27 @@ ] | ||
replace (file, options) { | ||
replace (filePath, options) { | ||
const path = require('path') | ||
let { find, replace } = options | ||
const basename = path.basename(file) | ||
const dirname = path.dirname(file) | ||
find = find || basename | ||
const newBasename = basename.replace(find, replace) | ||
if (newBasename) { | ||
return path.join(dirname, basename.replace(find, replace)) | ||
let { find, replace } = options || {} | ||
const file = path.parse(filePath) | ||
find = find || file.base | ||
const element = options.pathElement || 'base' | ||
if (element === 'base') { | ||
const basename = file.base.replace(find, replace) | ||
if (basename) { | ||
return path.join(file.dir, basename) | ||
} else { | ||
throw new Error(`Replace resulted in empty filename. File: ${filePath}, find: ${find}, replace: ${replace}`) | ||
} | ||
} else if ([ 'name', 'ext' ].includes(element)) { | ||
file[element] = file[element].replace(find, replace) | ||
const basename = file.name + file.ext | ||
if (basename) { | ||
return path.join(file.dir, basename) | ||
} else { | ||
throw new Error(`Replace resulted in empty filename. File: ${filePath}, find: ${find}, replace: ${replace}`) | ||
} | ||
} else { | ||
throw new Error(`Replace resulted in empty filename. File: ${file}, find: ${find}, replace: ${replace}`) | ||
throw new Error('Invalid path element: ' + element) | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
module.exports = PluginBase => class RenamerIndex extends PluginBase { | ||
module.exports = PluginBase => class Index extends PluginBase { | ||
constructor () { | ||
@@ -7,24 +7,27 @@ super() | ||
description () { | ||
return 'Replace the `\\{\\{index\\}\\}` token in a replace expression with a number incremented for each file renamed.' | ||
} | ||
optionDefinitions () { | ||
return [ | ||
{ name: 'index-format', description: 'The format of the number to replace `\\{\\{index\\}\\}` with. Specify a standard printf format string, e.g `%03d`. Defaults to `%d`.' } | ||
{ name: 'index-format', description: 'The format of the number to replace `\\{\\{index\\}\\}` with. Specify a standard printf format string, for example `%03d` would yield 001, 002, 003 etc. Defaults to `%d`.' } | ||
] | ||
} | ||
replace (file, options) { | ||
if (/{{index}}/.test(file)) { | ||
replace (filePath, options) { | ||
if (/{{index}}/.test(filePath)) { | ||
const path = require('path') | ||
const basename = path.basename(file) | ||
const dirname = path.dirname(file) | ||
const newBasename = this.replaceIndexToken(basename, this.matchCount++, options.indexFormat || '%d') | ||
return path.join(dirname, newBasename) | ||
const file = path.parse(filePath) | ||
const newBasename = this.replaceIndexToken(file.base, this.matchCount++, options.indexFormat || '%d') | ||
return path.join(file.dir, newBasename) | ||
} else { | ||
return file | ||
return filePath | ||
} | ||
} | ||
replaceIndexToken (file, index, format) { | ||
replaceIndexToken (basename, index, format) { | ||
const sprintf = require('printj').sprintf | ||
return file.replace(/{{index}}/g, sprintf(format, index)) | ||
return basename.replace(/{{index}}/g, sprintf(format, index)) | ||
} | ||
} |
@@ -1,5 +0,11 @@ | ||
'use strict' | ||
const fs = require('fs') | ||
const dryRunLog = {} | ||
/** | ||
* @param {string} - The current file path. | ||
* @param {string} - To file path to renamer to. | ||
* @param {object} [options] - Options object. | ||
* @param {boolean} [options.force] - Force the rename, even if `fs.exists` reports the `to` path exists. | ||
* @param {boolean} [options.dryRun] - Simulate the rename. | ||
*/ | ||
function renameFile (from, to, options) { | ||
@@ -6,0 +12,0 @@ options = options || {} |
@@ -1,7 +0,7 @@ | ||
'use strict' | ||
class Replacer { | ||
constructor (pluginNames) { | ||
const util = require('./util') | ||
this.plugins = util.loadPlugins(pluginNames || [ 'default', 'index' ]) | ||
const arrayify = require('array-back') | ||
pluginNames = arrayify(pluginNames) | ||
this.plugins = util.loadPlugins(pluginNames.length ? pluginNames : [ 'default', 'index' ]) | ||
} | ||
@@ -8,0 +8,0 @@ |
/** | ||
* Search globally by default. If `options.regexp` is not set then ensure any special regex characters in `options.find` are escaped. Do nothing if `options.find` is not set. | ||
* Convert a string like `/something/g` to a RegExp instance. | ||
*/ | ||
function regExpBuilder (options) { | ||
function regExpBuilder (find) { | ||
const regExpRe = /\/(.*)\/(\w*)/ | ||
const find = options.find | ||
if (find) { | ||
@@ -54,9 +53,11 @@ if (regExpRe.test(find)) { | ||
const path = require('path') | ||
const loadModule = require('load-module') | ||
const arrayify = require('array-back') | ||
const t = require('typical') | ||
return arrayify(pluginNames).map(pluginName => { | ||
let createPlugin | ||
if (typeof pluginName === 'string') { | ||
const loadModule = require('load-module') | ||
const globalModules = require('global-modules') | ||
createPlugin = loadModule(pluginName, { | ||
paths: [ path.resolve(__dirname, 'plugin'), '.' ], | ||
paths: [ path.resolve(__dirname, 'plugin'), '.', globalModules ], | ||
prefix: 'renamer-' | ||
@@ -67,3 +68,12 @@ }) | ||
} | ||
if (!t.isFunction(createPlugin)) { | ||
throw new Error('Invalid plugin: plugin module must export a function') | ||
} | ||
const Plugin = createPlugin(require('./plugin-base')) | ||
if (!t.isClass(Plugin)) { | ||
throw new Error('Invalid plugin: plugin module must export a function which returns a class') | ||
} | ||
if (!Plugin.prototype.replace) { | ||
throw new Error('Invalid plugin: plugin class must implement a replace method') | ||
} | ||
return new Plugin() | ||
@@ -70,0 +80,0 @@ }) |
{ | ||
"name": "renamer", | ||
"description": "Rename files in bulk", | ||
"version": "1.0.0-4", | ||
"version": "1.0.0-5", | ||
"author": "Lloyd Brookes <75pound@gmail.com>", | ||
@@ -39,10 +39,14 @@ "bin": "bin/cli.js", | ||
"glob": "^7.1.2", | ||
"global-modules": "^1.0.0", | ||
"load-module": "^1.0.0", | ||
"node-version-matches": "^1.0.0", | ||
"printj": "^1.1.2", | ||
"reduce-flatten": "^2.0.0", | ||
"reduce-unique": "^2.0.1", | ||
"stream-read-all": "^1.0.1" | ||
"stream-read-all": "^1.0.1", | ||
"typical": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^3.0.2", | ||
"jsdoc-to-markdown": "^4.0.1", | ||
"mkdirp2": "^1.0.4", | ||
@@ -49,0 +53,0 @@ "rimraf": "^2.6.2", |
@@ -62,2 +62,19 @@ [![view on npm](http://img.shields.io/npm/v/renamer/next.svg)](https://www.npmjs.org/package/renamer) | ||
If the built-in replace behaviour doesn't fit your needs you can supply a custom replace plugin. For example, this trivial plugin appends the extension `.jpg` to every input file. Save it as `my-plugin.js`. | ||
``` | ||
module.exports = PluginBase => class Jpg extends PluginBase { | ||
replace (filePath) { | ||
return filePath + '.jpg' | ||
} | ||
} | ||
``` | ||
Use your custom replace plugin by supplying its filename to `--plugin`. | ||
``` | ||
$ renamer --plugin my-plugin.js images/* | ||
``` | ||
The full set of command-line options. | ||
@@ -64,0 +81,0 @@ |
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
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
24710
460
141
15
5
+ Addedglobal-modules@^1.0.0
+ Addednode-version-matches@^1.0.0
+ Addedtypical@^3.0.0
+ Addedexpand-tilde@2.0.2(transitive)
+ Addedglobal-modules@1.0.0(transitive)
+ Addedglobal-prefix@1.0.2(transitive)
+ Addedhomedir-polyfill@1.0.3(transitive)
+ Addedini@1.3.8(transitive)
+ Addedis-windows@1.0.2(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addednode-version-matches@1.0.1(transitive)
+ Addedparse-passwd@1.0.0(transitive)
+ Addedresolve-dir@1.0.1(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedtypical@3.0.2(transitive)
+ Addedwhich@1.3.1(transitive)