diff --git a/filenamify-path.d.ts b/filenamify-path.d.ts new file mode 100644 index 0000000..2346602 --- /dev/null +++ b/filenamify-path.d.ts @@ -0,0 +1,8 @@ +import filenamify = require('./filenamify'); + +/** +Convert the filename in a path a valid filename and return the augmented path. +*/ +declare const filenamifyPath: (path: string, options?: filenamify.Options) => string; + +export = filenamifyPath; diff --git a/filenamify-path.js b/filenamify-path.js new file mode 100644 index 0000000..359c119 --- /dev/null +++ b/filenamify-path.js @@ -0,0 +1,10 @@ +'use strict'; +const path = require('path'); +const filenamify = require('./filenamify'); + +const filenamifyPath = (filePath, options) => { + filePath = path.resolve(filePath); + return path.join(path.dirname(filePath), filenamify(path.basename(filePath), options)); +}; + +module.exports = filenamifyPath; diff --git a/filenamify.d.ts b/filenamify.d.ts new file mode 100644 index 0000000..4d73ddc --- /dev/null +++ b/filenamify.d.ts @@ -0,0 +1,39 @@ +declare namespace filenamify { + interface Options { + /** + String to use as replacement for reserved filename characters. + + Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` + + @default '!' + */ + readonly replacement?: string; + + /** + Truncate the filename to the given length. + + Systems generally allow up to 255 characters, but we default to 100 for usability reasons. + + @default 100 + */ + readonly maxLength?: number; + } +} + +/** +Convert a string to a valid filename. + +@example +``` +import filenamify = require('filenamify'); + +filenamify(''); +//=> 'foo!bar' + +filenamify('foo:"bar"', {replacement: '🐴'}); +//=> 'foo🐴bar' +``` +*/ +declare const filenamify: (string: string, options?: filenamify.Options) => string; + +export = filenamify; diff --git a/filenamify.js b/filenamify.js new file mode 100644 index 0000000..274c228 --- /dev/null +++ b/filenamify.js @@ -0,0 +1,38 @@ +'use strict'; +const trimRepeated = require('trim-repeated'); +const filenameReservedRegex = require('filename-reserved-regex'); +const stripOuter = require('strip-outer'); + +// Doesn't make sense to have longer filenames +const MAX_FILENAME_LENGTH = 100; + +const reControlChars = /[\u0000-\u001f\u0080-\u009f]/g; // eslint-disable-line no-control-regex +const reRelativePath = /^\.+/; + +const filenamify = (string, options = {}) => { + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } + + const replacement = options.replacement === undefined ? '!' : options.replacement; + + if (filenameReservedRegex().test(replacement) && reControlChars.test(replacement)) { + throw new Error('Replacement string cannot contain reserved filename characters'); + } + + string = string.replace(filenameReservedRegex(), replacement); + string = string.replace(reControlChars, replacement); + string = string.replace(reRelativePath, replacement); + + if (replacement.length > 0) { + string = trimRepeated(string, replacement); + string = string.length > 1 ? stripOuter(string, replacement) : string; + } + + string = filenameReservedRegex.windowsNames().test(string) ? string + replacement : string; + string = string.slice(0, typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH); + + return string; +}; + +module.exports = filenamify; diff --git a/index.d.ts b/index.d.ts index b0faa2c..0dd9716 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,26 +1,7 @@ -declare namespace filenamify { - interface Options { - /** - String to use as replacement for reserved filename characters. +import filenamify = require('./filenamify'); +import filenamifyPath = require('./filenamify-path'); - Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` - - @default '!' - */ - readonly replacement?: string; - - /** - Truncate the filename to the given length. - - Systems generally allow up to 255 characters, but we default to 100 for usability reasons. - - @default 100 - */ - readonly maxLength?: number; - } -} - -declare const filenamify: { +declare const filenamifyCombined: { /** Convert a string to a valid filename. @@ -37,10 +18,7 @@ declare const filenamify: { */ (string: string, options?: filenamify.Options): string; - /** - Convert the filename in a path a valid filename and return the augmented path. - */ - path(path: string, options?: filenamify.Options): string; + path: typeof filenamifyPath; }; -export = filenamify; +export = filenamifyCombined; diff --git a/index.js b/index.js index e37c18a..260bbd1 100644 --- a/index.js +++ b/index.js @@ -1,44 +1,8 @@ 'use strict'; -const path = require('path'); -const trimRepeated = require('trim-repeated'); -const filenameReservedRegex = require('filename-reserved-regex'); -const stripOuter = require('strip-outer'); +const filenamify = require('./filenamify'); +const filenamifyPath = require('./filenamify-path'); -// Doesn't make sense to have longer filenames -const MAX_FILENAME_LENGTH = 100; - -const reControlChars = /[\u0000-\u001f\u0080-\u009f]/g; // eslint-disable-line no-control-regex -const reRelativePath = /^\.+/; - -const filenamify = (string, options = {}) => { - if (typeof string !== 'string') { - throw new TypeError('Expected a string'); - } - - const replacement = options.replacement === undefined ? '!' : options.replacement; - - if (filenameReservedRegex().test(replacement) && reControlChars.test(replacement)) { - throw new Error('Replacement string cannot contain reserved filename characters'); - } - - string = string.replace(filenameReservedRegex(), replacement); - string = string.replace(reControlChars, replacement); - string = string.replace(reRelativePath, replacement); - - if (replacement.length > 0) { - string = trimRepeated(string, replacement); - string = string.length > 1 ? stripOuter(string, replacement) : string; - } - - string = filenameReservedRegex.windowsNames().test(string) ? string + replacement : string; - string = string.slice(0, typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH); - - return string; -}; - -filenamify.path = (filePath, options) => { - filePath = path.resolve(filePath); - return path.join(path.dirname(filePath), filenamify(path.basename(filePath), options)); -}; +const filenamifyCombined = filenamify; +filenamifyCombined.path = filenamifyPath; module.exports = filenamify; diff --git a/package.json b/package.json index da1e297..1041b52 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,17 @@ "test": "xo && ava && tsd" }, "files": [ - "index.js", - "index.d.ts" + "filenamify-path.d.ts", + "filenamify-path.js", + "filenamify.d.ts", + "filenamify.js", + "index.d.ts", + "index.js" ], + "exports": { + ".": "./index.js", + "./browser": "./filenamify.js" + }, "keywords": [ "filename", "safe", diff --git a/readme.md b/readme.md index e1c96b0..43ae76a 100644 --- a/readme.md +++ b/readme.md @@ -57,6 +57,17 @@ Truncate the filename to the given length. Systems generally allow up to 255 characters, but we default to 100 for usability reasons. +## Browser-only import + +You can also import `filenamify/browser`, which only imports `filenamify` and not `filenamify.path`, which relies on `path` being available or polyfilled. Importing `filenamify` this way is therefore useful when it is shipped using `webpack` or similar tools, and if `filenamify.path` is not needed. + +```js +const filenamify = require('filenamify/browser'); + +filenamify(''); +//=> 'foo!bar' +``` + ## Related