GithubHelp home page GithubHelp logo

sindresorhus / unused-filename Goto Github PK

View Code? Open in Web Editor NEW
142.0 5.0 9.0 20 KB

Get an unused filename by appending a number if it exists: `file.txt` → `file (1).txt`

License: MIT License

JavaScript 93.84% TypeScript 6.16%

unused-filename's Introduction

unused-filename

Get an unused filename by appending a number if it exists: file.txtfile (1).txt

Useful for safely writing, copying, moving files without overwriting existing files.

Install

npm install unused-filename

Usage

.
├── rainbow (1).txt
├── rainbow.txt
└── unicorn.txt
import {unusedFilename} from 'unused-filename';

console.log(await unusedFilename('rainbow.txt'));
//=> 'rainbow (2).txt'

API

unusedFilename(filePath, options?)

Returns a Promise<string> containing either the original filename or the filename increment by options.incrementer.

If an already incremented filePath is passed, unusedFilename will simply increment and replace the already existing index:

import {unusedFilename} from 'unused-filename';

console.log(await unusedFilename('rainbow (1).txt'));
//=> 'rainbow (2).txt'

unusedFilenameSync(filePath, options?)

Synchronous version of unusedFilename.

filePath

Type: string

The path to check for filename collision.

options

Type: object

incrementer

Type: (filePath: string) => [string, string]
Default: Parentheses incrementer: file.txtfile (1).txt

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.

import {unusedFilename} from 'unused-filename';

// Incrementer that inserts a new index as a prefix.
const prefixIncrementer = (filename, extension) => {
	const match = filename.match(/^(?<index>\d+)_(?<originalFilename>.*)$/);
	let {originalFilename, index} = match ? match.groups : {originalFilename: filename, index: 0};
	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 maximum number of attempts to find an unused filename.

When the limit is reached, the function will throw MaxTryError.

separatorIncrementer

Creates an incrementer that appends a number after a separator.

separatorIncrementer('_') will increment file.txtfile_1.txt.

Not all characters can be used as separators:

  • On Unix-like systems, / is reserved.
  • On Windows, <>:"/|?* along with trailing periods are reserved.
import {unusedFilename, separatorIncrementer} from 'unused-filename';

console.log(await unusedFilename('rainbow.txt', {incrementer: separatorIncrementer('_')}));
//=> 'rainbow_1.txt'

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:

import {unusedFilename, MaxTryError} from 'unused-filename';

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 - Convert a string to a valid safe filename

unused-filename's People

Contributors

bendingbender avatar jopemachine avatar martinvd avatar richienb avatar sindresorhus avatar tomasklaen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

unused-filename's Issues

"'promises' is not exported" error w/ Vite

Hi!

First, Thank You for this and other great packages 👍

Just a heads up that this package fails to bundle with latest Vite (2.8.2) with following traceback:

'promises' is not exported by __vite-browser-external:node:fs, imported by node_modules/unused-filename/node_modules/path-exists/index.js
file: /Users/uninen/Code/Electron/--project--/node_modules/unused-filename/node_modules/path-exists/index.js:1:12
1: import fs, {promises as fsPromises} from 'node:fs';
               ^
2: 
3: export async function pathExists(path) {

Usually adding wonky packages to optimizeDeps config helps but couldn't get this one working at all. Not sure if the issue is with Vite, this package, my (TS) setup or just the color of the electricity today but I thought I'd report it anyways. Feel free to close if this doesn't seem valid.

Custom incrementer function

Some times I want to write to directories named like this: Foo-[RANDOM-STRING] (e.g. Foo-ABZ1) and if by any chance that's already present I don't want it to try Foo-ABZ1 (2) next, but I'd prefer to generate another random string, so it may try Foo-K3N1 next for instance.

Can I submit a PR for adding an optional options argument which includes the incrementer (or maybe named something differently) option for providing a custom incrementer function?

Also with custom incrementers like the one I described it might happen that there are no unused filenames available, so maybe another maxTries option or something could be added so that the library throws after a maxTries number of failed attempts?

Example code not working

It seems like the example code not working because of a small mistake.

I think this code could be changed to the below.

  const prefixIncrementer = (filename, extension) => {
    const match = filename.match(/^(?<index>\d+)_(?<originalFilename>.*)$/);
    let {originalFilename, index} = match ? match.groups : {originalFilename: filename, index: 0};
    originalFilename = originalFilename.trim();
    return [`${originalFilename}${extension}`, `${originalFilename}_${++index}${extension}`];
  };

Accounting for asynchronous operations

In order to make this module more reliable I think it should account for asynchronous operations and blacklist filenames until the user has actually used them.

If I'm doing the following in a loop (say I have lots of files to write at the same time):

  • Get an unused filename
  • Write to it asynchronously to the disk

I think this module will eventually return a file path which it actually returned in a previous call because the program hasn't written to it yet.

A solution to this might be to make this library return the following:

{
  filePath: string,
  resolve: Function
}

So that the library will never return filePath again until the resolve function has been called.

I'd be interested in this functionality and I'd like to submit a PR for this.

What do you think?

Custom delimiter

I think it'd be cool if there was a parameter to specify the delimiter. Maybe with a dummy variable to represent the number? I'm thinking something like this:

unusedFilename('rainbow.txt', ' [x]').then(filename => {
  console.log(filename);
  //=> 'rainbow [2].txt'
});

unusedFilename('rainbow.txt', '-x').then(filename => {
  console.log(filename);
  //=> 'rainbow-2.txt'
});

unusedFilename('rainbow.txt', '\xx\x').then(filename => {
  console.log(filename);
  //=> 'rainbowx2x.txt'
});

Please take into account each operating system's naming convention

This plugin doesn't seem very cross-platform friendly in its current state.

(1) is a Windows (and some apps) naming convention. OSX duplicates with copy, copy 2, etc.

I'd assume the various Linux distros have various conventions for this too but they could probably be picked off as they come up.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.