{"id":32469484,"url":"https://github.com/vitaly-t/custom-string-formatter","last_synced_at":"2025-10-26T15:59:18.486Z","repository":{"id":307897761,"uuid":"1031016502","full_name":"vitaly-t/custom-string-formatter","owner":"vitaly-t","description":"Customizable String Formatter","archived":false,"fork":false,"pushed_at":"2025-09-04T02:45:59.000Z","size":288,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-04T15:08:04.160Z","etag":null,"topics":["formatting","string","template","variable"],"latest_commit_sha":null,"homepage":"https://vitaly-t.github.io/custom-string-formatter","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vitaly-t.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-02T20:29:17.000Z","updated_at":"2025-09-15T12:16:50.000Z","dependencies_parsed_at":"2025-08-02T22:23:18.580Z","dependency_job_id":"3180e186-a8bc-44fc-8b51-ce607e840e7d","html_url":"https://github.com/vitaly-t/custom-string-formatter","commit_stats":null,"previous_names":["vitaly-t/custom-string-formatter"],"tags_count":43,"template":false,"template_full_name":null,"purl":"pkg:github/vitaly-t/custom-string-formatter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaly-t%2Fcustom-string-formatter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaly-t%2Fcustom-string-formatter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaly-t%2Fcustom-string-formatter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaly-t%2Fcustom-string-formatter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vitaly-t","download_url":"https://codeload.github.com/vitaly-t/custom-string-formatter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaly-t%2Fcustom-string-formatter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281130785,"owners_count":26448690,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-26T02:00:06.575Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["formatting","string","template","variable"],"created_at":"2025-10-26T15:59:16.507Z","updated_at":"2025-10-26T15:59:18.477Z","avatar_url":"https://github.com/vitaly-t.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# custom-string-formatter\n\n[![ci](https://github.com/vitaly-t/custom-string-formatter/actions/workflows/ci.yml/badge.svg)](https://github.com/vitaly-t/custom-string-formatter/actions/workflows/ci.yml)\n[![Node Version](https://img.shields.io/badge/nodejs-20%20--%2024-green.svg?logo=node.js\u0026style=flat)](https://nodejs.org)\n\n* [Installation](#installation)\n* [Variable Syntax](#variable-syntax)\n* [Formatting Filters](#formatting-filters)\n    - [Filter Arguments](#filter-arguments)\n* [Self-Reference](#self-reference)\n* [Input Analysis](#input-analysis)\n* [Safety Checks](#safety-checks)\n* [Performance](#performance)\n\nReplaces variables in a string, using your own value formatting.\n\nThe variable syntax supports:\n\n* nested properties\n* filters chaining / pipeline\n* filter arguments\n\n\u003e ${prop1.prop2.prop3 | filter1 | filter2 | filter3 : arg1 : arg2}\n\n**Basic Use:**\n\n```ts\nimport {createFormatter, IFormatter} from 'custom-string-formatter';\n\nclass BaseFormatter implements IFormatter {\n    format(value: any): string {\n        // your own value formatting here;\n        return (value ?? 'null').toString();\n    }\n}\n\n// creating a reusable formatting function:\nconst format = createFormatter(new BaseFormatter());\n\n// formatting a string with values from an object:\nconst s = format('Hello ${title} ${name}!', {title: 'Mr.', name: 'Foreman'});\n\nconsole.log(s); //=\u003e Hello Mr. Foreman!\n```\n\n## Installation\n\n```sh\n$ npm i custom-string-formatter\n```\n\nCurrent GitHub CI is set up for just NodeJS v20-v24, but it works in all browsers the same.\n\n## Variable Syntax\n\n**Basic variable syntax is as below:**\n\n* `${propertyName}`\n* `$(propertyName)`\n* `$\u003cpropertyName\u003e`\n\nThe extra syntax is for cases like combining it with ES6 Template Literals, etc.\n\nProperty names follow a simple JavaScript variable notation: the name can contain letters (case-sensitive),\ndigits, `$`, `_` (underscore) and `.` for nested properties. For array access, like `prop[123].value`,\nuse `prop.123.value` syntax instead, for the fastest possible value resolution that avoids performance-expensive\nproperty tokenization.\n\nYou can use a combination of the above inside one string, but you cannot combine opener-closer pairs, i.e.\nsomething like `${propertyName)` is invalid, and won't be recognized as a variable.\n\n**Full Syntax:**\n\nFull variable syntax supports a chain of nested properties, plus optional filters with arguments:\n\n* `${prop1.prop2.prop3 | filter1 | filter2 | filter3 : arg1 : arg2}`.\n\nAll spaces in between are ignored, i.e. `${  prop  |  filter  :  arg  }` works the same as `${prop|filter:arg}`.\n\nSee the chapters below for further details.\n\n## Formatting Filters\n\nFormatting filters can be appended to the property name after `|` separator, for value transformation, in the form\nof `${propertyName | filter1 | filter2 | filter3}`.\n\nFilter names follow a simple JavaScript variable notation: the name can contain letters (case-sensitive),\ndigits, `$` and `_` (underscore).\n\nFilters perform value transformation in the same order in which they are specified, as a pipeline, with the\noutput from the last filter going to the formatter, to be converted into a string (if needed).\n\n**Example of using formatting filters:**\n\n```ts\nimport {createFormatter, IFormatter, IFilter} from 'custom-string-formatter';\n\nclass JsonFilter implements IFilter {\n    transform(value: any, args: string[]): any {\n        return JSON.stringify(value); // transform into a JSON string\n    }\n}\n\nclass BaseFormatter implements IFormatter {\n    format(value: any): string {\n        return (value ?? 'null').toString();\n    }\n\n    // name-\u003eobject map of all our filters:\n    filters = {\n        json: new JsonFilter()\n    };\n}\n\nconst format = createFormatter(new BaseFormatter());\n\nconst s = format('${title} ${name} address: ${address | json}', {\n    title: 'Mr.',\n    name: 'Foreman',\n    address: {street: 'Springfield', house: 10}\n});\n\nconsole.log(s); //=\u003e Mr. Foreman address: {\"street\":\"Springfield\",\"house\":10}\n```\n\n### Filter Arguments\n\nYou can pass optional arguments into a filter after `:` symbol:\n\n```\n${propertyName | filterName : -123.45 : Hello World!}\n```\n\nFor the example above, method `transform` will receive `args` set to `['-123.45', 'Hello World!']`.\n\nPassing in empty arguments like `filter:::Hello!` or `filter : : : Hello World! ` will produce a list of arguments\nset to `['', '', 'Hello World!']`.\n\n**IMPORTANT** ☝\n\u003e Filter arguments cannot contain symbols `|:{}\u003c\u003e()`, as they would conflict with the variable syntax.\n\u003e To pass those in, you need to HTML-encode them (see below).\n\nFilter arguments are automatically HTML-decoded (unless [decodeArguments] override is present):\n\n* `\u0026#8364;` =\u003e `€`: decimal symbol codes (1–6 digits)\n* `\u0026#x1F60a;` =\u003e `😊`: hexadecimal symbol codes (1–5 hex digits, case-insensitive)\n\nCodes for symbols that must be encoded inside filter arguments:\n\n| symbol | decimal  | hexadecimal |\n|:------:|:--------:|:-----------:|\n|  `\\|`  | `\u0026#124;` |  `\u0026#x7c;`   |\n|  `:`   | `\u0026#58;`  |  `\u0026#x3a;`   |\n|  `{`   | `\u0026#123;` |  `\u0026#x7b;`   |\n|  `}`   | `\u0026#125;` |  `\u0026#x7d;`   |\n|  `\u003c`   | `\u0026#60;`  |  `\u0026#x3c;`   |\n|  `\u003e`   | `\u0026#62;`  |  `\u0026#x3e;`   |\n|  `(`   | `\u0026#40;`  |  `\u0026#x28;`   |\n|  `)`   | `\u0026#41;`  |  `\u0026#x29;`   |\n\nFor dynamic filter arguments, you can use function [sanitizeFilterArg] to encode them.\n\nYou can also override method [decodeArguments], for the following purposes:\n\n* to let the filter control individual argument decoding\n* to optimize the filter's performance by not decoding some or all arguments\n* to also remove accents (diacritical marks), supported by [decodeFilterArg]\n\n## Self-Reference\n\nWhen a property chain starts with `this` (case-sensitive), the parser treats it as the reference to the parameter object\nitself. It is to avoid wrapping the parameter object into another object when you want to format that parameter object\nitself.\n\nFor the above example with the filter, we can use it like this:\n\n```ts\nconst s = format('Address: ${this | json}', {street: 'Springfield', house: 10});\n\nconsole.log(s); //=\u003e Address: {\"street\":\"Springfield\",\"house\":10}\n```\n\nAbove, we referenced the parameter object itself, and then forwarded formatting into our `json` filter.\n\nBecause `this` references the parameter object, its use with nested properties is also valid - `${this.prop1.prop2}`,\nthough it may not have a practical need (use of `this` in this case is superfluous), but just for logical consistency.\n\n## Input Analysis\n\nIf you need to verify an input string for the variable references it has, this library offers three global\nfunctions to help you with that:\n\n| Function         | Description                                    |\n|------------------|------------------------------------------------|\n| [hasVariables]   | A fast check if a string has variables in it.  |\n| [countVariables] | A fast count of variables in a string.         |\n| [enumVariables]  | Enumerates and parses variables from a string. |\n\n**Example:**\n\n```ts\nimport {enumVariables} from 'custom-string-formatter';\n\nenumVariables('${title} ${name} address: ${address | json}');\n// ==\u003e\n[\n    {match: '${title}', property: 'title', filters: []},\n    {match: '${name}', property: 'name', filters: []},\n    {\n        match: '${address | json}',\n        property: 'address',\n        filters: [{name: 'json', args: []}]\n    }\n]\n```\n\n## Safety Checks\n\n### Property-name Safety\n\nThe parser requires that any referenced property exists, or else it will throw `Property \"propName\" does not exist`.\nThis is to help with detection of invalid property names.\n\nIf a property is missing, it must be set to `undefined` before it can be referenced from a string, to avoid the error.\n\nYou can override such behavior by implementing [getDefaultValue] function inside [IFormatter] and return\na default value whenever the property cannot be resolved. This is not the safest approach when no error is thrown,\nas invalid property names can be easily missed.\n\n### Filter-name Safety\n\nWhen using an unknown filter, the parser will throw `Filter \"filterName\" not recognized`, to help with detection\nof invalid filter names.\n\nYou can override such behavior by implementing [getDefaultFilter] function inside [IFormatter] and return\nan alternative filter. This can have various uses, such as:\n\n* Support for filter aliases\n* Support for dynamic filters / lazy-loading\n\nCheck out [the examples](./examples).\n\n### Performance\n\nThe high performance of this library is enforced right in the unit tests (\nsee [./test/performance.spec.ts](./test/performance.spec.ts)).\n\nThe engine can replace over one million variables per second. It is faster than most alternatives\nout there, which make use of performance-expensive property tokenization.\n\nTested under NodeJS v20/24.\n\n[IFormatter]:https://vitaly-t.github.io/custom-string-formatter/interfaces/IFormatter.html\n\n[getDefaultValue]:https://vitaly-t.github.io/custom-string-formatter/interfaces/IFormatter.html#getdefaultvalue\n\n[getDefaultFilter]:https://vitaly-t.github.io/custom-string-formatter/interfaces/IFormatter.html#getdefaultfilter\n\n[hasVariables]:https://vitaly-t.github.io/custom-string-formatter/functions/hasVariables.html\n\n[countVariables]:https://vitaly-t.github.io/custom-string-formatter/functions/countVariables.html\n\n[enumVariables]:https://vitaly-t.github.io/custom-string-formatter/functions/enumVariables.html\n\n[sanitizeFilterArg]:https://vitaly-t.github.io/custom-string-formatter/functions/sanitizeFilterArg.html\n\n[decodeArguments]:https://vitaly-t.github.io/custom-string-formatter/interfaces/IFilter.html#decodearguments\n\n[decodeFilterArg]:https://vitaly-t.github.io/custom-string-formatter/functions/decodeFilterArg.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitaly-t%2Fcustom-string-formatter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvitaly-t%2Fcustom-string-formatter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitaly-t%2Fcustom-string-formatter/lists"}