From 10b1917ae5750100888899bc1f0645b45d848851 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Sun, 11 Dec 2022 17:46:18 -0500 Subject: [PATCH] feat(utils): `changeExt` Signed-off-by: Lexus Drumgold --- src/utils/__tests__/change-ext.spec.ts | 40 ++++++++++++++++++ src/utils/change-ext.ts | 56 ++++++++++++++++++++++++++ src/utils/index.ts | 1 + 3 files changed, 97 insertions(+) create mode 100644 src/utils/__tests__/change-ext.spec.ts create mode 100644 src/utils/change-ext.ts diff --git a/src/utils/__tests__/change-ext.spec.ts b/src/utils/__tests__/change-ext.spec.ts new file mode 100644 index 00000000..cfca71c3 --- /dev/null +++ b/src/utils/__tests__/change-ext.spec.ts @@ -0,0 +1,40 @@ +/** + * @file Unit Tests - changeExt + * @module pathe/utils/tests/unit/changeExt + */ + +import testSubject from '../change-ext' + +describe('unit:utils/changeExt', () => { + it('should return path with new file extension', () => { + // Arrange + const cases: [...Parameters, string][] = [ + ['file', 'mjs', 'file.mjs'], + ['file', 'mts', 'file.mts'], + ['file.', '.mjs', 'file.mjs'], + ['file.', '.mts', 'file.mts'], + ['file.mts', 'd.mts', 'file.d.mts'], + ['file.min.cjs', '.mjs', 'file.min.mjs'] + ] + + // Act + Expect + cases.forEach(([path, ext, expected]) => { + expect(testSubject(path, ext)).to.equal(expected) + }) + }) + + it('should return path without modications if ext is empty', () => { + // Arrange + const cases: [...Parameters, string][] = [ + ['file.cjs', '', 'file.cjs'], + ['file.cts', ' ', 'file.cts'], + ['file.mjs', null, 'file.mjs'], + ['file.mts', undefined, 'file.mts'] + ] + + // Act + Expect + cases.forEach(([path, ext, expected]) => { + expect(testSubject(path, ext)).to.equal(expected) + }) + }) +}) diff --git a/src/utils/change-ext.ts b/src/utils/change-ext.ts new file mode 100644 index 00000000..ad4aebb1 --- /dev/null +++ b/src/utils/change-ext.ts @@ -0,0 +1,56 @@ +/** + * @file Utilities - changeExt + * @module pathe/utils/changeExt + */ + +import validateString from '#src/internal/validate-string' +import extname from '#src/lib/extname' +import type { Ext } from '#src/types' +import type { EmptyString, Nullable } from '@flex-development/tutils' +import addExt from './add-ext' +import formatExt from './format-ext' + +/** + * Changes the file extension of the given `path`. + * + * Does nothing if a file extension isn't provided. + * + * @example + * changeExt('file') // 'file' + * @example + * changeExt('file', 'cjs') // 'file.cjs' + * @example + * changeExt('file', '.mjs') // 'file.mjs' + * @example + * changeExt('file.mts', '.d.mts') // 'file.d.mts' + * @example + * changeExt('file.min.cjs', 'mjs') // 'file.min.mjs' + * + * @param {string} path - Path to evaluate + * @param {Nullable} [ext] - File extension to add + * @return {string} `path` unmodified or with new file extension + * @throws {TypeError} If `path` is not a string or `ext` is not a string + */ +const changeExt = (path: string, ext?: Nullable): string => { + validateString(path, 'path') + + // exit early if extension isn't provided or validate ext + if (ext === null || ext === undefined) return path + else validateString(ext, 'ext') + + // exit early if extension is empty string + if (!ext.trim()) return path + + /** + * File extension of {@linkcode path}. + * + * @const {EmptyString | Ext} extension + */ + const extension: EmptyString | Ext = extname((path = path.replace(/\.$/, ''))) + + return extension + ? path.replace(new RegExp(`\\${extension}$`), formatExt(ext)) + : addExt(path, ext) +} + +export default changeExt diff --git a/src/utils/index.ts b/src/utils/index.ts index b863b619..e2bce538 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,5 +4,6 @@ */ export { default as addExt } from './add-ext' +export { default as changeExt } from './change-ext' export { default as formatExt } from './format-ext' export { default as removeExt } from './remove-ext'