UNPKG

@visulima/string

Version:

Functions for manipulating strings.

881 lines (663 loc) 27.3 kB
<div align="center"> <h3>visulima string</h3> <p> A robust string manipulation library providing utilities for common string operations with support for multiple languages. </p> </div> <br /> <div align="center"> [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] </div> --- <div align="center"> <p> <sup> Daniel Bannert's open source work is supported by the community on <a href="https://github.com/sponsors/prisis">GitHub Sponsors</a> </sup> </p> </div> ## Features ### Case Conversion - **Multiple Case Styles**: - `camelCase`: Convert to camelCase style - `PascalCase`: Convert to PascalCase style - `snake_case`: Convert to snake_case style - `kebab-case`: Convert to kebab-case style - `CONSTANT_CASE`: Convert to CONSTANT_CASE style - `dot.case`: Convert to dot.case style - `path/case`: Convert to path/case style - `Sentence case`: Convert to Sentence case style - `Title Case`: Convert to Title Case with smart minor word handling ### String Manipulation - **Smart String Splitting**: - Split by case transitions (camelCase → ["camel", "Case"]) - Split by script boundaries (日本語Text → ["日本語", "Text"]) - Split by separators (foo-bar → ["foo", "bar"]) - Preserve known acronyms (XMLHttpRequest → ["XML", "Http", "Request"]) - **Text Indentation**: - `outdent`: Remove leading indentation while preserving relative indentation - Handles template literals and string inputs - Normalizes newlines across platforms - Configurable trimming behavior - **String Width Calculation**: - `getStringWidth`: Calculate visual width of strings with Unicode support - `getStringTruncatedWidth`: Width calculation with smart truncation - Handles CJK characters, emojis, ANSI codes, and more - Configurable character width settings - Support for zero-width and combining characters - Customizable truncation with ellipsis ### Multi-Script Support - **CJK Scripts**: - Japanese (Hiragana, Katakana, Kanji) - Korean (Hangul) - Chinese (Han characters) - **European Scripts**: - Cyrillic (Russian, Ukrainian, etc.) - Greek - Latin (with extended characters) - **RTL Scripts**: - Arabic - Hebrew - **Indic Scripts**: - Devanagari - Bengali - Tamil - And more... - **Southeast Asian**: - Thai - Lao - Khmer ### Enhanced Type Safety - **Native String Type Extensions**: - Type-safe string operations - Compile-time type checking - Improved IDE support - **Generic Type Parameters**: - Flexible type constraints - Template literal type support - Conditional type inference ### Performance Features - **Optimized Processing**: - Precompiled regex patterns - Efficient string manipulation - **Caching Mechanisms**: - Smart caching for repeated operations - WeakMap-based caching for template literals - Configurable cache options - Fast paths for common cases - **Memory Efficiency**: - Minimal string allocations - Efficient string splitting - Optimized concatenation ### Developer Experience - **Comprehensive API**: - Consistent method signatures - Chainable operations - Flexible configuration options - **Robust Error Handling**: - Graceful handling of edge cases - Clear error messages - Type-safe error prevention - **Full TypeScript Support**: - Complete type definitions - IntelliSense support - Type inference --- ## Install ```sh npm install @visulima/string ``` ```sh yarn add @visulima/string ``` ```sh pnpm add @visulima/string ``` ## Usage ### Outdent (Remove Indentation) The `outdent` function removes leading indentation from multi-line strings while preserving the relative indentation structure. ```typescript import { outdent } from '@visulima/string'; // Basic usage with template literals const text = outdent` This text will have its indentation removed while preserving relative indentation. This line will still be indented relative to the others. `; // Output: // This text will have its indentation removed // while preserving relative indentation. // This line will still be indented relative to the others. // With string input const result = outdent.string(' Hello World '); // Output: "Hello\nWorld" // With custom options const customOutdent = outdent({ trimLeadingNewline: false, // Keep the leading newline trimTrailingNewline: false, // Keep the trailing newline newline: '\r\n', // Normalize all newlines to CRLF cache: true // Enable caching (default) }); // Using with interpolation const name = 'World'; const greeting = outdent` Hello ${name}! Welcome to outdent. `; ``` #### Performance Optimization with Caching The `outdent` function supports caching to improve performance when the same template is used multiple times: ```typescript // Default behavior - caching enabled const dedent = outdent(); // Disable caching if memory usage is a concern const noCacheDedent = outdent({ cache: false }); // Use a custom cache store (advanced usage) const customCache = new WeakMap(); const customCacheDedent = outdent({ cacheStore: customCache }); ``` ### Word Wrapping The `wordWrap` function provides flexible text wrapping with support for ANSI color codes and Unicode. ```typescript import { wordWrap, WrapMode } from "@visulima/string"; // Basic usage with default options (80 character width, preserve words) const wrapped = wordWrap("This is a long text that will be wrapped to fit within the specified width limit."); // With custom width (40 characters) const narrowWrapped = wordWrap("This text will be wrapped to fit within a 40-character width.", { width: 40 }); // Different wrapping modes const preserveWords = wordWrap("Long words will stay intact but may exceed the width limit.", { width: 20, wrapMode: WrapMode.PRESERVE_WORDS, // Default - keeps words intact }); const breakAtCharacters = wordWrap("Words will be broken at character boundaries to fit width.", { width: 20, wrapMode: WrapMode.BREAK_AT_CHARACTERS, // Breaks words to fit width exactly }); const strictWidth = wordWrap("Text will be broken exactly at the width limit.", { width: 20, wrapMode: WrapMode.STRICT_WIDTH, // Forces strict adherence to width }); // Handling ANSI color codes const coloredText = "\u001B[31mThis red text\u001B[0m will be wrapped while preserving the color codes."; const wrappedColored = wordWrap(coloredText, { width: 20 }); // Color codes are preserved across line breaks // Additional options const customWrapped = wordWrap("Text with\u200Bzero-width characters and\u200Btrailing spaces.", { width: 30, trim: false, // Don't trim whitespace from lines (default: true) removeZeroWidthCharacters: false, // Don't remove zero-width characters (default: true) }); ``` ### String Splitting The `splitByCase` function is a powerful utility that splits strings based on various patterns: ```typescript import { splitByCase } from "@visulima/string"; // Basic Case Transitions splitByCase("camelCase"); // ['camel', 'Case'] splitByCase("PascalCase"); // ['Pascal', 'Case'] splitByCase("snake_case"); // ['snake', 'case'] splitByCase("kebab-case"); // ['kebab', 'case'] // Numbers and Acronyms splitByCase("XMLHttpRequest"); // ['XML', 'Http', 'Request'] splitByCase("iOS8"); // ['i', 'OS', '8'] splitByCase("IPv6Address"); // ['IP', 'v6', 'Address'] // Multi-Script Support // Japanese splitByCase("ひらがなカタカナABC", { locale: "ja" }); // ['ひらがな', 'カタカナ', 'ABC'] // Korean splitByCase("한글Text", { locale: "ko" }); // ['한글', 'Text'] // Chinese splitByCase("中文Text", { locale: "zh" }); // ['中文', 'Text'] // Cyrillic splitByCase("русскийText", { locale: "ru" }); // ['русский', 'Text'] // Greek splitByCase("ελληνικάText", { locale: "el" }); // ['ελληνικά', 'Text'] // Advanced Options splitByCase("MyXMLParser", { knownAcronyms: ["XML"], // Preserve known acronyms normalize: true, // Normalize case locale: "en", // Specify locale }); // ['My', 'XML', 'Parser'] // ANSI and Emoji Handling splitByCase("🎉HappyBirthday🎂", { handleEmoji: true, // Handle emoji boundaries }); // ['🎉', 'Happy', 'Birthday', '🎂'] ``` ### Case Conversion Functions #### camelCase Converts a string to camelCase. ```typescript camelCase("foo bar"); // 'fooBar' camelCase("foo-bar"); // 'fooBar' camelCase("foo_bar"); // 'fooBar' camelCase("XMLHttpRequest"); // 'xmlHttpRequest' camelCase("AJAXRequest"); // 'ajaxRequest' camelCase("QueryXML123String"); // 'queryXml123String' ``` #### pascalCase Converts a string to PascalCase. ```typescript pascalCase("foo bar"); // 'FooBar' pascalCase("foo-bar"); // 'FooBar' pascalCase("foo_bar"); // 'FooBar' pascalCase("XMLHttpRequest"); // 'XmlHttpRequest' pascalCase("AJAXRequest"); // 'AjaxRequest' pascalCase("QueryXML123String"); // 'QueryXml123String' ``` #### snakeCase Converts a string to snake_case. ```typescript snakeCase("fooBar"); // 'foo_bar' snakeCase("foo bar"); // 'foo_bar' snakeCase("foo-bar"); // 'foo_bar' snakeCase("XMLHttpRequest"); // 'xml_http_request' snakeCase("AJAXRequest"); // 'ajax_request' snakeCase("QueryXML123String"); // 'query_xml_123_string' ``` #### kebabCase Converts a string to kebab-case. ```typescript kebabCase("fooBar"); // 'foo-bar' kebabCase("foo bar"); // 'foo-bar' kebabCase("foo_bar"); // 'foo-bar' kebabCase("XMLHttpRequest"); // 'xml-http-request' kebabCase("AJAXRequest"); // 'ajax-request' kebabCase("QueryXML123String"); // 'query-xml-123-string' ``` #### titleCase Converts a string to Title Case, with smart handling of minor words. ```typescript titleCase("this-IS-aTitle"); // 'This is a Title' titleCase("XMLHttpRequest"); // 'XML Http Request' titleCase("AJAXRequest"); // 'AJAX Request' titleCase("QueryXML123String"); // 'Query XML 123 String' ``` #### pathCase Converts a string to path/case. ```typescript pathCase("foo bar"); // 'foo/bar' pathCase("foo-bar"); // 'foo/bar' pathCase("foo_bar"); // 'foo/bar' pathCase("XMLHttpRequest"); // 'xml/http/request' pathCase("AJAXRequest"); // 'ajax/request' pathCase("QueryXML123String"); // 'query/xml/123/string' ``` #### dotCase Converts a string to dot.case. ```typescript dotCase("foo bar"); // 'foo.bar' dotCase("foo-bar"); // 'foo.bar' dotCase("foo_bar"); // 'foo.bar' dotCase("XMLHttpRequest"); // 'xml.http.request' dotCase("AJAXRequest"); // 'ajax.request' dotCase("QueryXML123String"); // 'query.xml.123.string' ``` #### constantCase Converts a string to CONSTANT_CASE. ```typescript constantCase("foo bar"); // 'FOO_BAR' constantCase("foo-bar"); // 'FOO_BAR' constantCase("foo_bar"); // 'FOO_BAR' constantCase("XMLHttpRequest"); // 'XML_HTTP_REQUEST' constantCase("AJAXRequest"); // 'AJAX_REQUEST' constantCase("QueryXML123String"); // 'QUERY_XML_123_STRING' ``` #### sentenceCase Converts a string to Sentence case. ```typescript sentenceCase("foo bar"); // 'Foo bar' sentenceCase("foo-bar"); // 'Foo bar' sentenceCase("foo_bar"); // 'Foo bar' sentenceCase("XMLHttpRequest"); // 'Xml http request' sentenceCase("AJAXRequest"); // 'Ajax request' sentenceCase("QueryXML123String"); // 'Query xml 123 string' ``` ### String Width Calculation The package provides two functions for calculating string widths: `getStringWidth` for basic width calculation and `getStringTruncatedWidth` for width calculation with truncation support. #### Basic Width Calculation The `getStringWidth` function calculates the visual width of strings, taking into account various Unicode characters, emojis, ANSI escape codes, and more: ```typescript import { getStringWidth } from "@visulima/string"; // Basic usage getStringWidth("hello"); // => 5 getStringWidth("👋 hello"); // => 7 (emoji is width 2) getStringWidth("あいう"); // => 6 (each character is width 2) // With custom options getStringWidth("hello", { regularWidth: 2 }); // => 10 getStringWidth("あいう", { ambiguousIsNarrow: true }); // => 3 // ANSI escape codes getStringWidth("\u001B[31mRed\u001B[39m"); // => 3 getStringWidth("\u001B[31mRed\u001B[39m", { countAnsiEscapeCodes: true }); // => 11 // Advanced Unicode support getStringWidth("한글"); // => 4 (Korean characters) getStringWidth("你好"); // => 4 (Chinese characters) getStringWidth("👨‍👩‍👧‍👦"); // => 2 (family emoji with ZWJ sequences) ``` #### Configuration Options ```typescript interface StringWidthOptions { ambiguousIsNarrow?: boolean; // Treat ambiguous-width characters as narrow ambiguousWidth?: number; // Width of ambiguous-width characters (default: 1) ansiWidth?: number; // Width of ANSI escape sequences (default: 0) controlWidth?: number; // Width of control characters (default: 0) countAnsiEscapeCodes?: boolean; // Include ANSI escape codes in width (default: false) emojiWidth?: number; // Width of emoji characters (default: 2) fullWidth?: number; // Width of full-width characters (default: 2) regularWidth?: number; // Width of regular characters (default: 1) tabWidth?: number; // Width of tab characters (default: 8) wideWidth?: number; // Width of wide characters (default: 2) } ``` #### Width Calculation with Truncation The `getStringTruncatedWidth` function extends the basic width calculation with truncation support: ```typescript import { getStringTruncatedWidth } from "@visulima/string"; // Basic truncation getStringTruncatedWidth("hello world", { limit: 8, ellipsis: "...", }); // => { width: 8, truncated: true, ellipsed: true, index: 5 } // Custom character widths with truncation getStringTruncatedWidth("あいうえお", { limit: 6, ellipsis: "...", fullWidth: 2, }); // => { width: 6, truncated: true, ellipsed: true, index: 2 } // ANSI codes with truncation getStringTruncatedWidth("\u001B[31mRed Text\u001B[39m", { limit: 5, ellipsis: "...", countAnsiEscapeCodes: true, }); // => { width: 5, truncated: true, ellipsed: true, index: 4 } // Complex Unicode with truncation getStringTruncatedWidth("👨‍👩‍👧‍👦 Family", { limit: 7, ellipsis: "...", }); // => { width: 7, truncated: true, ellipsed: true, index: 11 } ``` ### String Truncation The `truncate` function provides a convenient way to truncate strings with support for different positions, Unicode characters, ANSI escape codes, and more. ```typescript import { truncate } from "@visulima/string"; // Basic truncation (end position) truncate("unicorn", 4); // => 'un…' truncate("unicorn", 4, { position: "end" }); // => 'un…' // Different positions truncate("unicorn", 5, { position: "start" }); // => '…orn' truncate("unicorn", 5, { position: "middle" }); // => 'un…n' // With custom ellipsis truncate("unicorns", 5, { ellipsis: "." }); // => 'unic.' truncate("unicorns", 5, { ellipsis: " ." }); // => 'uni .' // Smart truncation on spaces truncate("dragons are awesome", 15, { position: "end", preferTruncationOnSpace: true }); // => 'dragons are…' truncate("unicorns rainbow dragons", 20, { position: "middle", preferTruncationOnSpace: true }); // => 'unicorns…dragons' // With ANSI escape codes truncate("\u001B[31municorn\u001B[39m", 4); // => '\u001B[31mun\u001B[39m…' // With Unicode characters truncate("안녕하세요", 3, { width: { fullWidth: 2 } }); // => '안…' ``` #### Truncation Options ```typescript interface TruncateOptions { // String to append when truncation occurs ellipsis?: string; // default: '' // Width of the ellipsis string // If not provided, it will be calculated using getStringTruncatedWidth ellipsisWidth?: number; // The position to truncate the string position?: "end" | "middle" | "start"; // default: 'end' // Truncate the string from a whitespace if it is within 3 characters // from the actual breaking point preferTruncationOnSpace?: boolean; // default: false // Width calculation options width?: Omit<StringTruncatedWidthOptions, "ellipsis" | "ellipsisWidth" | "limit">; } interface StringTruncatedWidthOptions extends StringWidthOptions { // Truncation-specific options ellipsis?: string; // String to append when truncation occurs (default: '') ellipsisWidth?: number; // Width of ellipsis, auto-calculated if not provided limit?: number; // Maximum width limit for the string (default: Infinity) } // Return value structure interface StringTruncatedWidthResult { width: number; // The calculated visual width of the string truncated: boolean; // Whether the string was truncated ellipsed: boolean; // Whether an ellipsis was added index: number; // The index at which truncation occurred (if any) } ``` ### Common Options All case conversion functions accept these common options: ```typescript interface CaseOptions { // Enable caching for better performance cache?: boolean; // Maximum size of the cache (default: 1000) cacheMaxSize?: number; // Custom cache store cacheStore?: Map<string, string>; // Known acronyms to preserve knownAcronyms?: ReadonlyArray<string>; // Locale for script-aware case conversion locale?: string; } ``` ### String Splitting The `splitByCase` function accepts these configuration options: ```typescript interface SplitOptions { // Locale for script-aware splitting (e.g., 'ja', 'ko', 'zh') locale?: string; // Known acronyms to preserve (e.g., ['XML', 'HTTP']) knownAcronyms?: ReadonlyArray<string>; // Handle ANSI escape sequences handleAnsi?: boolean; // Handle emoji sequences handleEmoji?: boolean; // Normalize case (convert all-upper tokens to title case) normalize?: boolean; // Custom separators (string[] or RegExp) separators?: ReadonlyArray<string> | RegExp; // Strip ANSI sequences stripAnsi?: boolean; // Strip emoji sequences stripEmoji?: boolean; } ``` ### Supported Scripts The library provides comprehensive support for various scripts and writing systems: - **Latin**: Standard ASCII and extended Latin characters - **CJK**: - Japanese (Hiragana, Katakana, Kanji) - Korean (Hangul) - Chinese (Han characters) - **Cyrillic**: Russian, Ukrainian, Bulgarian, etc. - **Greek**: Modern Greek script - **RTL Scripts**: Arabic, Hebrew - **Indic Scripts**: Devanagari, Bengali, Tamil, etc. - **Southeast Asian**: Thai, Lao, Khmer ### Performance Optimization The library includes several optimizations: - Precompiled regex patterns for script detection - Fast paths for single-script strings - Efficient handling of case transitions - Smart caching of character type checks ### Error Handling The library handles various edge cases gracefully: ```typescript // Empty strings splitByCase(""); // [] // Invalid input splitByCase(null); // [] splitByCase(undefined); // [] // Single characters splitByCase("A"); // ['A'] // All uppercase splitByCase("URL", { knownAcronyms: ["URL"] }); // ['URL'] ``` ### Native String Types The library provides enhanced TypeScript type definitions for native string methods. These types provide better type inference and compile-time checks. #### Configuration Configure your `tsconfig.json` file to include the types: ```json { "compilerOptions": { "types": ["@visulima/string/native-string-types"] } } ``` Alternatively, you can add a triple-slash reference in your TypeScript files: ```typescript /// <reference types="@visulima/string/native-string-types" /> ``` #### Usage Examples ```typescript // Type-safe string operations const str = "Hello, World!"; // charAt with type inference str.charAt<typeof str, 0>(); // type: 'H' str.charAt<typeof str, 1>(); // type: 'e' // concat with type inference str.concat<typeof str, "Hi">(); // type: 'Hello, World!Hi' // endsWith with literal type checking str.endsWith<typeof str, "World!">(); // type: true str.endsWith<typeof str, "Hello">(); // type: false // includes with position str.includes<typeof str, "World", 0>(); // type: true str.includes<typeof str, "World", 7>(); // type: false // length with type inference type Length = (typeof str)["length"]; // type: 13 // padStart/padEnd with type inference str.padStart<typeof str, 15, "_">(); // type: '_Hello, World!' str.padEnd<typeof str, 15, "_">(); // type: 'Hello, World!__' // replace with type inference str.replace<typeof str, "World", "TypeScript">(); // type: 'Hello, TypeScript!' // replaceAll with type inference str.replaceAll<typeof str, "l", "L">(); // type: 'HeLLo, WorLd!' // slice with type inference str.slice<typeof str, 0, 5>(); // type: 'Hello' // split with type inference str.split<typeof str, ", ">(); // type: ['Hello', 'World!'] // startsWith with type inference str.startsWith<typeof str, "Hello">(); // type: true // toLowerCase/toUpperCase with type inference str.toLowerCase<typeof str>(); // type: 'hello, world!' str.toUpperCase<typeof str>(); // type: 'HELLO, WORLD!' // trim/trimStart/trimEnd with type inference const paddedStr = " hello "; paddedStr.trim<typeof paddedStr>(); // type: 'hello' paddedStr.trimStart<typeof paddedStr>(); // type: 'hello ' paddedStr.trimEnd<typeof paddedStr>(); // type: ' hello' ``` These enhanced types provide several benefits: 1. **Compile-Time Type Safety**: - Catch type errors before runtime - Get accurate type inference for method results - Validate string operations at compile time 2. **Better IDE Support**: - Improved autocompletion - More accurate type hints - Better refactoring support 3. **Type-Level String Manipulation**: - Perform string operations at the type level - Get literal type results for string operations - Chain operations with type safety 4. **Advanced Type Features**: - Generic type parameters for flexible usage - Conditional type inference - Template literal type support ```typescript // Example of chaining operations with type safety const result = "Hello, World!".toLowerCase<string>().replace<string, "hello", "hi">().split<string, " ">().join("-"); // TypeScript knows the exact type at each step ``` ## Testing Utilities The package includes specialized utilities for testing ANSI colored strings, making it easier to write tests for terminal output and colored text. ### ANSI String Formatting and Comparison The `formatAnsiString` function helps format ANSI strings for test output, providing multiple representations: ```typescript import { formatAnsiString } from "@visulima/string/test/utils"; import { red } from "@visulima/colorize"; const coloredText = red("Error message"); const formatted = formatAnsiString(coloredText); // Returns an object with: // - ansi: Original string with ANSI codes // - stripped: String with ANSI codes removed // - visible: String with escape codes shown as visible characters // - json: JSON stringified version // - lengthDifference: Difference between ANSI and stripped length ``` ### Comparing ANSI Strings The `compareAnsiStrings` function provides detailed comparison between two ANSI strings: ```typescript import { compareAnsiStrings } from "@visulima/string/test/utils"; import { red, blue } from "@visulima/colorize"; const string1 = red("Error"); const string2 = blue("Error"); const result = compareAnsiStrings(string1, string2); // Returns comparison details including: // - ansiEqual: Whether the strings are identical including ANSI codes // - strippedEqual: Whether the visible content is the same // - summary: Length information and comparison results // - actual/expected: Formatted representations of both strings ``` ### Vitest Integration The package includes a custom matcher for [Vitest](https://vitest.dev/) that makes testing ANSI strings straightforward: ```typescript import { expect, describe, it } from "vitest"; import { toEqualAnsi } from "@visulima/string/test/vitest"; import { red, green } from "@visulima/colorize"; // Extend Vitest with the custom matcher expect.extend({ toEqualAnsi }); describe("colored output tests", () => { it("should display the correct error message", () => { const actual = getErrorMessage(); // Returns colored string const expected = red("Error: ") + green("File not found"); // Compare ANSI strings with detailed error messages on failure expect(actual).toEqualAnsi(expected); }); }); ``` The custom matcher provides detailed error messages when tests fail, showing: - The visible content of both strings - The ANSI escape codes in both strings - Whether the visible content matches but the colors differ - Length information for both strings ## Related - [change-case](https://github.com/blakeembrey/change-case) - Simple string case utilities - [lodash](https://lodash.com/) - Comprehensive utility library with string manipulation - [scule](https://github.com/unjs/scule) - 🧵 String Case Utils - [case-anything](https://github.com/mesqueeb/case-anything) - camelCase, kebab-case, PascalCase... a simple integration with nano package size. (SMALL footprint!) - [cli-truncate](https://github.com/sindresorhus/cli-truncate) - Truncate strings for terminal output - [string-width](https://github.com/sindresorhus/string-width) - Measure string width - [ansi-slice](https://github.com/sindresorhus/ansi-slice) - Slice strings with ANSI escape codes - [fast-string-truncated-width](https://github.com/fabiospampinato/fast-string-truncated-width) - Fast string truncated width - [ansi-truncate](https://github.com/fabiospampinato/ansi-truncate) - Truncate strings with ANSI escape codes - [string-ts](https://github.com/gustavoguichard/string-ts) - Strongly typed string functions ## Supported Node.js Versions Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule). Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a). ## Contributing If you would like to help, take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines. > **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. ## Credits - [Daniel Bannert](https://github.com/prisis) - [All Contributors](https://github.com/visulima/visulima/graphs/contributors) ## License The visulima string is open-sourced software licensed under the [MIT][license-url] [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript [typescript-url]: https://www.typescriptlang.org/ "TypeScript" [license-image]: https://img.shields.io/npm/l/@visulima/string?color=blueviolet&style=for-the-badge [license-url]: LICENSE.md "license" [npm-image]: https://img.shields.io/npm/v/@visulima/string/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://www.npmjs.com/package/@visulima/string/v/latest "npm"