unused-filename
Advanced tools
Comparing version 2.1.0 to 3.0.0
139
index.d.ts
@@ -0,9 +1,127 @@ | ||
/* eslint-disable no-redeclare */ | ||
declare namespace unusedFilename { | ||
interface Options { | ||
/** | ||
Custom function to increment a filename. | ||
Default: Parentheses incrementer: `file.txt` → `file (1).txt` | ||
*/ | ||
readonly incrementer?: Incrementer; | ||
/** | ||
The max number of attempts to find an unused filename. | ||
When the limit is reached, the function will throw `unusedFilename.MaxTryError`. | ||
@default Infinity | ||
*/ | ||
readonly maxTries?: number; | ||
} | ||
/** | ||
A function that accepts a file path, and increments its index. It's the incrementer's responsibility to extract an already existing index from the given file path so that it picks up and continues incrementing an already present index instead of appending a second one. | ||
The incrementer has to return a tuple of `[originalFilename, incrementedFilename]`, where `originalFilename` is the filename without the index, and `incrementedFilename` is a filename with input's index bumped by one. | ||
@param filename - The filename of the file path. | ||
@param extension - The extension of the file path. | ||
@returns A tuple of original filename, and new incremented filename, including extension. | ||
@example | ||
``` | ||
// Example incrementer that inserts a new index as a prefix. | ||
import unusedFilename = require('unused-filename'); | ||
(async () => { | ||
const filename = await unusedFilename('rainbow.txt', { | ||
incrementer(filename, extension) => { | ||
const match = filename.match(/^(?<index>\d+)_(?<originalFilename>.*)$/); | ||
let {originalFilename, index} = match ? match.groups : {originalFilename: filename, index: 0}; | ||
let originalFilename = originalFilename.trim(); | ||
return [`${originalFilename}${extension}`, `${++index}_${originalFilename}${extension}`; | ||
} | ||
}); | ||
console.log(filename); | ||
//=> '1_rainbow.txt' | ||
})(); | ||
``` | ||
*/ | ||
type Incrementer = (filename: string, extension: string) => string; | ||
/** | ||
The error thrown when `maxTries` limit is reached without finding an unused filename. | ||
@param originalPath - Path without the incrementation sequence. | ||
@param lastTriedPath - The last tested incremented path. | ||
@example | ||
``` | ||
import unusedFilename = require('unused-filename'); | ||
const {MaxTryError} = unusedFilename; | ||
try { | ||
const path = await unusedFilename('rainbow (1).txt', {maxTries: 0}); | ||
} catch (error) { | ||
if (error instanceof MaxTryError) { | ||
console.log(error.originalPath); // 'rainbow.txt' | ||
console.log(error.lastTriedPath); // 'rainbow (1).txt' | ||
} | ||
} | ||
``` | ||
*/ | ||
interface MaxTryError extends Error { | ||
originalPath: string; | ||
lastTriedPath: string; | ||
} | ||
} | ||
declare const unusedFilename: { | ||
/** | ||
Creates an incrementer that appends a number after a separator. | ||
`separatorIncrementer('_')` will increment `file.txt` → `file_1.txt`. | ||
Not all characters can be used as separators: | ||
- On Unix-like systems, `/` is reserved. | ||
- On Windows, `<>:"/|?*` along with trailing periods are reserved. | ||
@example | ||
``` | ||
import unusedFilename = require('unused-filename'); | ||
console.log(await unusedFilename('rainbow.txt', {incrementer: unusedFilename.separatorIncrementer('_')})); | ||
//=> 'rainbow_1.txt' | ||
``` | ||
*/ | ||
separatorIncrementer: (separator: string) => unusedFilename.Incrementer; | ||
MaxTryError: unusedFilename.MaxTryError; | ||
// TODO: Remove this for the next major release | ||
default: typeof unusedFilename; | ||
/** | ||
Get an unused filename by appending a number if it exists: `file.txt` → `file (1).txt`. | ||
@param filePath - The path to check for filename collision. | ||
@returns Either the original `filename` or the `filename` appended with a number. | ||
@returns Either the original `filename` or the `filename` appended with a number (or modified by `option.incrementer` if specified). | ||
If an already incremented `filePath` is passed, `unusedFilename` will simply increment and replace the already existing index: | ||
@example | ||
``` | ||
import unusedFilename = require('unused-filename'); | ||
(async () => { | ||
console.log(await unusedFilename('rainbow (1).txt')); | ||
//=> 'rainbow (2).txt' | ||
})(); | ||
``` | ||
*/ | ||
(filePath: string): Promise<string>; | ||
(filePath: string, options?: unusedFilename.Options): Promise<string>; | ||
@@ -14,10 +132,17 @@ /** | ||
@param filePath - The path to check for filename collision. | ||
@returns Either the original `filename` or the `filename` appended with a number. | ||
@returns Either the original `filename` or the `filename` appended with a number (or modified by `option.incrementer` if specified). | ||
If an already incremented `filePath` is passed, `unusedFilename` will simply increment and replace the already existing index: | ||
@example | ||
``` | ||
import unusedFilename = require('unused-filename'); | ||
console.log(unusedFilename.sync('rainbow (1).txt')); | ||
//=> 'rainbow (2).txt' | ||
``` | ||
*/ | ||
sync(filePath: string): string; | ||
// TODO: Remove this for the next major release | ||
default: typeof unusedFilename; | ||
sync(filePath: string, options?: unusedFilename.Options): string; | ||
}; | ||
export = unusedFilename; |
83
index.js
'use strict'; | ||
const path = require('path'); | ||
const pathExists = require('path-exists'); | ||
const modifyFilename = require('modify-filename'); | ||
const escapeStringRegexp = require('escape-string-regexp'); | ||
const incrementer = filePath => { | ||
let counter = 0; | ||
return () => modifyFilename(filePath, (filename, extension) => `${filename} (${++counter})${extension}`); | ||
class MaxTryError extends Error { | ||
constructor(originalPath, lastTriedPath) { | ||
super('Max tries reached.'); | ||
this.originalPath = originalPath; | ||
this.lastTriedPath = lastTriedPath; | ||
} | ||
} | ||
const parenthesesIncrementer = (inputFilename, extension) => { | ||
const match = inputFilename.match(/^(?<filename>.*)\((?<index>\d+)\)$/); | ||
let {filename, index} = match ? match.groups : {filename: inputFilename, index: 0}; | ||
filename = filename.trim(); | ||
return [`${filename}${extension}`, `${filename} (${++index})${extension}`]; | ||
}; | ||
const unusedFilename = filePath => { | ||
const getFilePath = incrementer(filePath); | ||
const find = async newFilePath => await pathExists(newFilePath) ? find(getFilePath()) : newFilePath; | ||
return find(filePath); | ||
const separatorIncrementer = separator => { | ||
const escapedSeparator = escapeStringRegexp(separator); | ||
return (inputFilename, extension) => { | ||
const match = new RegExp(`^(?<filename>.*)${escapedSeparator}(?<index>\\d+)$`).exec(inputFilename); | ||
let {filename, index} = match ? match.groups : {filename: inputFilename, index: 0}; | ||
return [`${filename}${extension}`, `${filename.trim()}_${++index}${extension}`]; | ||
}; | ||
}; | ||
const incrementPath = (filePath, incrementer) => { | ||
const ext = path.extname(filePath); | ||
const dirname = path.dirname(filePath); | ||
const [originalFilename, incrementedFilename] = incrementer(path.basename(filePath, ext), ext); | ||
return [path.join(dirname, originalFilename), path.join(dirname, incrementedFilename)]; | ||
}; | ||
const unusedFilename = async (filePath, {incrementer = parenthesesIncrementer, maxTries = Number.POSITIVE_INFINITY} = {}) => { | ||
let tries = 0; | ||
let [originalPath] = incrementPath(filePath, incrementer); | ||
let unusedPath = filePath; | ||
/* eslint-disable no-await-in-loop, no-constant-condition */ | ||
while (true) { | ||
if (!(await pathExists(unusedPath))) { | ||
return unusedPath; | ||
} | ||
if (++tries > maxTries) { | ||
throw new MaxTryError(originalPath, unusedPath); | ||
} | ||
[originalPath, unusedPath] = incrementPath(unusedPath, incrementer); | ||
} | ||
/* eslint-enable no-await-in-loop, no-constant-condition */ | ||
}; | ||
module.exports = unusedFilename; | ||
@@ -20,6 +62,23 @@ // TODO: Remove this for the next major release | ||
module.exports.sync = filePath => { | ||
const getFilePath = incrementer(filePath); | ||
const find = newFilePath => pathExists.sync(newFilePath) ? find(getFilePath()) : newFilePath; | ||
return find(filePath); | ||
module.exports.sync = (filePath, {incrementer = parenthesesIncrementer, maxTries = Number.POSITIVE_INFINITY} = {}) => { | ||
let tries = 0; | ||
let [originalPath] = incrementPath(filePath, incrementer); | ||
let unusedPath = filePath; | ||
/* eslint-disable no-constant-condition */ | ||
while (true) { | ||
if (!pathExists.sync(unusedPath)) { | ||
return unusedPath; | ||
} | ||
if (++tries > maxTries) { | ||
throw new MaxTryError(originalPath, unusedPath); | ||
} | ||
[originalPath, unusedPath] = incrementPath(unusedPath, incrementer); | ||
} | ||
/* eslint-enable no-constant-condition */ | ||
}; | ||
module.exports.MaxTryError = MaxTryError; | ||
module.exports.separatorIncrementer = separatorIncrementer; |
{ | ||
"name": "unused-filename", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"description": "Get an unused filename by appending a number if it exists: `file.txt` → `file (1).txt`", | ||
"license": "MIT", | ||
"repository": "sindresorhus/unused-filename", | ||
"funding": "https://github.com/sponsors/sindresorhus", | ||
"author": { | ||
"name": "Sindre Sorhus", | ||
"email": "sindresorhus@gmail.com", | ||
"url": "sindresorhus.com" | ||
"url": "https://sindresorhus.com" | ||
}, | ||
"engines": { | ||
"node": ">=8" | ||
"node": ">=12" | ||
}, | ||
@@ -38,10 +39,10 @@ "scripts": { | ||
"dependencies": { | ||
"modify-filename": "^1.1.0", | ||
"escape-string-regexp": "^4.0.0", | ||
"path-exists": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^1.4.1", | ||
"tsd": "^0.7.2", | ||
"xo": "^0.24.0" | ||
"ava": "^2.4.0", | ||
"tsd": "^0.15.0", | ||
"xo": "^0.39.0" | ||
} | ||
} |
107
readme.md
@@ -1,2 +0,2 @@ | ||
# unused-filename [![Build Status](https://travis-ci.org/sindresorhus/unused-filename.svg?branch=master)](https://travis-ci.org/sindresorhus/unused-filename) | ||
# unused-filename | ||
@@ -7,3 +7,2 @@ > Get an unused filename by appending a number if it exists: `file.txt` → `file (1).txt` | ||
## Install | ||
@@ -15,3 +14,2 @@ | ||
## Usage | ||
@@ -35,13 +33,23 @@ | ||
## API | ||
### unusedFilename(filePath) | ||
### unusedFilename(filePath, options?) | ||
Returns a `Promise<string>` containing either the original `filename` or the `filename` appended with a number. | ||
Returns a `Promise<string>` containing either the original `filename` or the `filename` increment by `options.incrementer`. | ||
### unusedFilename.sync(filePath) | ||
If an already incremented `filePath` is passed, `unusedFilename` will simply increment and replace the already existing index: | ||
Returns a `string` containing either the original `filename` or the `filename` appended with a number. | ||
```js | ||
const unusedFilename = require('unused-filename'); | ||
(async () => { | ||
console.log(await unusedFilename('rainbow (1).txt')); | ||
//=> 'rainbow (2).txt' | ||
})(); | ||
``` | ||
### unusedFilename.sync(filePath, options?) | ||
Synchronous version of `unusedFilename`. | ||
#### filePath | ||
@@ -53,10 +61,85 @@ | ||
#### options | ||
## Related | ||
Type: `object` | ||
- [filenamify](https://github.com/sindresorhus/filenamify) - Convert a string to a valid safe filename | ||
##### incrementer | ||
Type: `(filePath: string) => [string, string]`\ | ||
Default: Parentheses incrementer: `file.txt` → `file (1).txt` | ||
## License | ||
A function that accepts a file path, and increments its index. It's the incrementer's responsibility to extract an already existing index from the given file path so that it picks up and continues incrementing an already present index instead of appending a second one. | ||
MIT © [Sindre Sorhus](https://sindresorhus.com) | ||
The incrementer has to return a tuple of `[originalFilename, incrementedFilename]`, where `originalFilename` is the filename without the index, and `incrementedFilename` is a filename with input's index bumped by one. | ||
Example incrementer that inserts a new index as a prefix: | ||
```js | ||
const unusedFilename = require('unused-filename'); | ||
const prefixIncrementer = (filename, extension) => { | ||
const match = filename.match(/^(?<index>\d+)_(?<originalFilename>.*)$/); | ||
let {originalFilename, index} = match ? match.groups : {originalFilename: filename, index: 0}; | ||
let originalFilename = originalFilename.trim(); | ||
return [`${originalFilename}${extension}`, `${++index}_${originalFilename}${extension}`; | ||
}; | ||
console.log(await unusedFilename('rainbow.txt', {incrementer: prefixIncrementer})); | ||
//=> '1_rainbow.txt' | ||
``` | ||
##### maxTries | ||
Type: `number`\ | ||
Default: `Infinity` | ||
The max number of attempts to find an unused filename. | ||
When the limit is reached, the function will throw `unusedFilename.MaxTryError`. | ||
### unusedFilename.separatorIncrementer | ||
Creates an incrementer that appends a number after a separator. | ||
`separatorIncrementer('_')` will increment `file.txt` → `file_1.txt`. | ||
Not all characters can be used as separators: | ||
- On Unix-like systems, `/` is reserved. | ||
- On Windows, `<>:"/|?*` along with trailing periods are reserved. | ||
```js | ||
const unusedFilename = require('unused-filename'); | ||
console.log(await unusedFilename('rainbow.txt', {incrementer: unusedFilename.separatorIncrementer('_')})); | ||
//=> 'rainbow_1.txt' | ||
``` | ||
### unusedFilename.MaxTryError | ||
The error thrown when `maxTries` limit is reached without finding an unused filename. | ||
It comes with 2 custom properties: | ||
- `originalPath` - Path without incrementation sequence. | ||
- `lastTriedPath` - The last tested incremented path. | ||
Example: | ||
```js | ||
const unusedFilename = require('unused-filename'); | ||
const {MaxTryError} = unusedFilename; | ||
try { | ||
const path = await unusedFilename('rainbow (1).txt', {maxTries: 0}); | ||
} catch (error) { | ||
if (error instanceof MaxTryError) { | ||
console.log(error.originalPath); // 'rainbow.txt' | ||
console.log(error.lastTriedPath); // 'rainbow (1).txt' | ||
} | ||
} | ||
``` | ||
## Related | ||
- [filenamify](https://github.com/sindresorhus/filenamify) - Convert a string to a valid safe filename |
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
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
13253
179
142
+ Addedescape-string-regexp@^4.0.0
+ Addedescape-string-regexp@4.0.0(transitive)
- Removedmodify-filename@^1.1.0
- Removedmodify-filename@1.1.0(transitive)