From f8bee7fe05fab98a6ec077a1132ffb995028cb84 Mon Sep 17 00:00:00 2001 From: MorevM Date: Sun, 24 Mar 2024 15:50:36 +0300 Subject: [PATCH] feat(strings): Add `stripIndent` utility --- src/strings/index.ts | 1 + src/strings/strip-indent/strip-indent.test.ts | 56 ++++++++++++++++++ src/strings/strip-indent/strip-indent.ts | 58 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/strings/strip-indent/strip-indent.test.ts create mode 100644 src/strings/strip-indent/strip-indent.ts diff --git a/src/strings/index.ts b/src/strings/index.ts index ab2231b..b77a738 100644 --- a/src/strings/index.ts +++ b/src/strings/index.ts @@ -11,4 +11,5 @@ export * from './quote/quote'; export * from './random-string/random-string'; export * from './romanize/romanize'; export * from './snake-case/snake-case'; +export * from './strip-indent/strip-indent'; export * from './unquote/unquote'; diff --git a/src/strings/strip-indent/strip-indent.test.ts b/src/strings/strip-indent/strip-indent.test.ts new file mode 100644 index 0000000..5a7f1eb --- /dev/null +++ b/src/strings/strip-indent/strip-indent.test.ts @@ -0,0 +1,56 @@ +import { stripIndent } from './strip-indent'; + +describe('strip-indent', () => { + it('Returns the string as is if there is no extra indentation and leading/trailing linebreaks', () => { + expect(stripIndent('')).toBe(''); + expect(stripIndent('foo')).toBe('foo'); + expect(stripIndent('foo\n\t\t\tbar\n\nbar')).toBe('foo\n\t\t\tbar\n\nbar'); + }); + + it('Removes leading/trailing linebreaks and lines containing only space\tab characters by default', () => { + expect(stripIndent('\nfoo\n')).toBe('foo'); + expect(stripIndent('\n\t \nfoo\n\t\t \t\t\n')).toBe('foo'); + }); + + it('Preserves leading linebreaks and lines containing only space\tab characters using `trimLeadingSpacings: false`', () => { + expect(stripIndent('\nfoo\n', { trimLeadingSpacings: false })).toBe('\nfoo'); + expect(stripIndent('\n\t \nfoo\n\t\t \t\t\n', { trimLeadingSpacings: false })).toBe('\n\t \nfoo'); + }); + + it('Preserves trailing linebreaks and lines containing only space\tab characters using `trimTrailingSpacings: false`', () => { + expect(stripIndent('\nfoo\n', { trimTrailingSpacings: false })).toBe('foo\n'); + expect(stripIndent('\n\t \nfoo\n\t\t \t\t\n', { trimTrailingSpacings: false })).toBe('foo\n\t\t \t\t\n'); + }); + + it('Removes extra indentation and linebreaks', () => { + expect(stripIndent(` + +
+
+
+ + `)).toBe('
\n\t
\n
'); + }); + + it('Removes extra indentation and linebreaks considering extra spacings', () => { + expect(stripIndent(` + +
+
+
+
+
+ + `)).toBe('
\n\t
\n\t
\n\t\t\t
\n
'); + }); + + it('Removes only extra indentation using `trim[X]Spacings: false`', () => { + expect(stripIndent(` + +
+
+
+ + `, { trimLeadingSpacings: false, trimTrailingSpacings: false })).toBe('\n\n
\n\t
\n
\n\n\t\t'); + }); +}); diff --git a/src/strings/strip-indent/strip-indent.ts b/src/strings/strip-indent/strip-indent.ts new file mode 100644 index 0000000..a9b4145 --- /dev/null +++ b/src/strings/strip-indent/strip-indent.ts @@ -0,0 +1,58 @@ +import { mergeObjects } from '../../objects/merge-objects/merge-objects'; + +const _getMinimalIndent = (input: string) => { + const match = input.match(/^[\t ]*(?=\S)/gm); + if (!match) return 0; + + return match.reduce((minimalIndentation, spacings) => + Math.min(minimalIndentation, spacings.length), Infinity); +}; + + +type Options = { + /** + * Whether to remove leading linebreaks and spacings (before the first non-space character) + * + * @default true + */ + trimLeadingSpacings: boolean; + + /** + * Whether to remove trailing linebreaks and spacings (after the last non-space character) + * + * @default true + */ + trimTrailingSpacings: boolean; +}; + +const DEFAULTS: Options = { + trimLeadingSpacings: true, + trimTrailingSpacings: true, +}; + +const _applyOptions = (input: string, options: Options) => { + options.trimLeadingSpacings && (input = input.replace(/^\n\s*(?=\S)/, '')); + options.trimTrailingSpacings && (input = input.replace(/\n\s*$/, '')); + + return input; +}; + +/** + * Removes an extra indentation of the string. \ + * Useful to work with template literal strings. + * + * @param input String to remove an extra indentation. + * @param userOptions Extra options. + * + * @returns The string with minimal required indentation. + */ +export const stripIndent = (input: string, userOptions?: Partial) => { + const options = mergeObjects(DEFAULTS, userOptions) as Required; + + const minIndent = _getMinimalIndent(input); + if (minIndent === 0) return _applyOptions(input, options); + + const regex = new RegExp(`^[\t ]{${minIndent}}`, 'gm'); + + return _applyOptions(input, options).replace(regex, ''); +};