{"id":16806058,"url":"https://github.com/haltcase/strat","last_synced_at":"2026-03-07T20:04:29.793Z","repository":{"id":57371890,"uuid":"73732682","full_name":"haltcase/strat","owner":"haltcase","description":"Functional-ish JavaScript string formatting, with inspirations from Python. TypeScript friendly.","archived":false,"fork":false,"pushed_at":"2025-03-14T03:24:19.000Z","size":150,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-13T02:57:24.194Z","etag":null,"topics":["curried","formatting","functional","partial","placeholder","python","string","template","typescript","utility"],"latest_commit_sha":null,"homepage":"","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/haltcase.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}},"created_at":"2016-11-14T18:04:00.000Z","updated_at":"2025-03-14T03:22:26.000Z","dependencies_parsed_at":"2022-09-07T18:10:29.438Z","dependency_job_id":null,"html_url":"https://github.com/haltcase/strat","commit_stats":null,"previous_names":["citycide/strat"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/haltcase/strat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haltcase%2Fstrat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haltcase%2Fstrat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haltcase%2Fstrat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haltcase%2Fstrat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haltcase","download_url":"https://codeload.github.com/haltcase/strat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haltcase%2Fstrat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30229589,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T19:01:10.287Z","status":"ssl_error","status_checked_at":"2026-03-07T18:59:58.103Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["curried","formatting","functional","partial","placeholder","python","string","template","typescript","utility"],"created_at":"2024-10-13T09:49:59.250Z","updated_at":"2026-03-07T20:04:29.776Z","avatar_url":"https://github.com/haltcase.png","language":"TypeScript","readme":"# strat \u0026middot; ![JSR Version](https://img.shields.io/jsr/v/haltcase/strat?style=flat-square) [![npm](https://img.shields.io/npm/v/strat.svg?style=flat-square)](https://www.npmjs.com/package/strat) [![License](https://img.shields.io/npm/l/strat.svg?style=flat-square)](https://www.npmjs.com/package/strat)\n\n_strat_ is a modern, dependency-free TypeScript library for formatting strings\nin Deno and Node.js. It's inspired by Python's [`str.format()`][pythonref] but\nis focused more on being at home in TypeScript than strictly adhering to\nPython's conventions.\n\n\u003e If you want stricter Python conventions, check out\n\u003e [string-format][string-format] which served inspired the original version of\n\u003e this project.\n\n## features\n\n- formatting can be partially applied, allowing for reusable template functions\n- object property references with dot notation\n- object methods are called and can be passed arguments\n- TypeScript-friendly for Deno and Node.js\n\n## installation\n\n### deno\n\nImport and use:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport type { FormatPartial, Transformer /*...*/ } from \"jsr:@haltcase/strat\";\n```\n\n### node\n\n1. Install with your method of choice\n\n   ```shell\n   pnpm add strat\n   npm install strat\n   yarn add strat\n   bun add strat\n   ```\n\n2. Import\n\n   ```ts ignore\n   import { strat } from \"strat\";\n   ```\n\n## usage\n\n### view from the top\n\n```ts ignore\nconst series = {\n\ttitle: \"Arcane\",\n\tseasons: 2,\n\trottenTomatoesRating: 100,\n};\n\nstrat(\"{title} ({rottenTomatoesRating}%) had {seasons} great seasons\", series);\n// -\u003e \"Arcane (100%) had 2 great seasons.\"\n```\n\nCompare that to the equivalent string concatenation in ES5:\n\n```ts ignore\nseries.title + \" (\" + series.rottenTomatoesRating + \"%) had \" + series.seasons +\n\t\" great seasons.\";\n```\n\nOr the more modern template literals from ES2015:\n\n```ts ignore\n`${series.title} (${series.rottenTomatoesRating}%) had ${series.seasons} great seasons.`;\n```\n\nBut the _strat_ function can also be partially applied to create reusable\ntemplate functions. Just leave out the initial replacements like so:\n\n```ts ignore\nconst template = strat(\n\t\"{title} ({rottenTomatoesRating}%) had {seasons} great seasons\",\n);\n\ntemplate(madMen);\n// -\u003e \"Mad Men (94%) had 7 great seasons.\"\ntemplate(haltAndCatchFire);\n// -\u003e \"Halt and Catch Fire (90%) had 4 great seasons.\"\ntemplate(peakyBlinders);\n// -\u003e \"Peaky Blinders (93%) had 6 great seasons.\"\n```\n\n### partial application\n\nWhen you omit the `replacements`, `strat` returns a partially applied, reusable\ninstance.\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst result1 = strat(\"You got- you gotta run {}.\", \"Morty\");\nassertEquals(result1, \"You got- you gotta run Morty.\");\n\nconst result2 = strat(\"When you come to a {} in the road, {}.\", [\n\t\"fork\",\n\t\"take it\",\n]);\nassertEquals(result2, \"When you come to a fork in the road, take it.\");\n```\n\nHere, the first argument is your template string. The second argument is an\narray of replacement values, or just a single value.\n\nThe second argument can optionally be left out, in which case a new function\nwill be returned that you can call with your replacement parameters.\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst template = strat(\"Like {} and {}\");\n\nassertEquals(template([\"salt\", \"pepper\"]), \"Like salt and pepper\");\n\nassertEquals(\n\ttemplate([\"peanut butter\", \"jelly\"]),\n\t\"Like peanut butter and jelly\",\n);\n```\n\n### api\n\n#### `strat(template: string, replacements: any | [...values]): string`\n\nReturns the result of replacing each `{…}` placeholder in the template string\nwith its corresponding replacement.\n\nPlaceholders may contain numbers which refer to positional arguments:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nassertEquals(\n\tstrat(\"{0}, you have {1} unread message{2}\", [\"Holly\", 2, \"s\"]),\n\t\"Holly, you have 2 unread messages\",\n);\n```\n\nUnmatched placeholders produce no output:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nassertEquals(\n\tstrat(\"{0}, you have {1} unread message{2}\", [\"Steve\", 1]),\n\t\"Steve, you have 1 unread message\",\n);\n```\n\nA format string may reference a positional argument multiple times:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nassertEquals(\n\tstrat(`The name's {1}. {0} {1}.`, [\"James\", \"Bond\"]),\n\t\"The name's Bond. James Bond.\",\n);\n```\n\nPositional arguments may be referenced implicitly:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nassertEquals(\n\tstrat(\"{}, you have {} unread message{}\", [\"Steve\", 1]),\n\t\"Steve, you have 1 unread message\",\n);\n```\n\nA format string must not contain both implicit and explicit references:\n\n```ts ignore\nstrat(\"My name is {} {}. Do you like the name {0}?\", [\"Lemony\", \"Snicket\"]);\n// -\u003e Error: cannot mix implicit \u0026 explicit formatting\n```\n\nEscape `{` and `}` characters by doubling it ( ie. `{{` and `}}` produce `{` and\n`}` respectively ):\n\n```ts ignore\nstrat(\"{{}} creates an empty {} {}\", [\"object\", \"literal\"]);\n// -\u003e \"{} creates an empty object literal\"\n```\n\nDot notation may be used to reference object properties:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst rick = { firstName: \"Rick\", lastName: \"Sanchez\" };\nconst morty = { firstName: \"Morty\", lastName: \"Smith\" };\n\nassertEquals(\n\tstrat(\"{0.firstName} {0.lastName} and {1.firstName} {1.lastName}\", [\n\t\trick,\n\t\tmorty,\n\t]),\n\t\"Rick Sanchez and Morty Smith\",\n);\n```\n\n`0.` may be omitted when referencing a property of `{0}`:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst song = {\n\ttitle: \"Handlebars\",\n\tartist: \"Flobots\",\n\talbum: \"Fight With Tools\",\n};\n\nassertEquals(\n\tstrat(\"{title} | [{artist}] | {album}\", song),\n\t\"Handlebars | [Flobots] | Fight With Tools\",\n);\n```\n\nIf the referenced property is a method, it is invoked with no arguments to\ndetermine the replacement:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst album = {\n\tname: \"The Death of Peace of Mind\",\n\tartist: \"Bad Omens\",\n\treleaseDate: new Date(\"2022-02-25\"),\n};\n\nassertEquals(\n\tstrat(\"{name} was released {releaseDate.toISOString}.\", album),\n\t\"The Death of Peace of Mind was released 2022-02-25T00:00:00.000Z.\",\n);\n\nassertEquals(\n\tstrat(\"Listen to more from {artist.toUpperCase}\", album),\n\t\"BAD OMENS\",\n);\n```\n\nTo pass arguments to a method, pass them as a comma delimited list, with a space\nafter the method name:\n\n```ts\nimport { strat } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst person = {\n\treact(tired: string, mood: string) {\n\t\tif (tired) {\n\t\t\tif (mood === \"sad\") return \"cried\";\n\t\t\treturn \"rolled his eyes\";\n\t\t} else {\n\t\t\tif (mood === \"mad\") return \"broke stuff\";\n\t\t\treturn \"shook his fist\";\n\t\t}\n\t},\n};\n\nassertEquals(\n\tstrat(\"Average Joe {react true, indifferent}.\", person),\n\t\"Average Joe rolled his eyes.\",\n);\n```\n\nNote that all arguments are passed as strings, so you'll have to parse them\nappropriately if you need, for example, a number or boolean.\n\nHowever, you can use `_` to pass the falsy `null` value in the argument list:\n\n```ts ignore\nstrat(\"Average Joe {react _, mad}.\", person);\n// -\u003e \"Average Joe broke stuff.\"\n```\n\n#### `strat.create(transformers?: { ...functions })`\n\nYou can create a new instance of _strat_ by calling `strat.create()`. You may\nalso optionally supply an object containing transformer functions that you can\nuse in `strat()` to modify string replacements.\n\nTransformers are very similar to a function you'd pass to\n[`Array#map()`][mdn-array-map]. They receive three arguments: the value on which\nit's being used, the key, and the full collection of replacements provided to\nthe template.\n\n```ts ignore\ntransform(value: string, key: string, collection: [...values]): string\n```\n\nTo use a transformer, call it by prefixing it with `!` after the field name in\nthe template string. For example, `{reaction!exclaim}` where `exclaim` was\npreviously passed in the `transformers` object.\n\nHere's a simple example operating only on the `value` argument:\n\n```ts\nimport { create } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\nconst instance = create({\n\texclaim: (value) =\u003e value.toUpperCase() + \"!\",\n});\n\nassertEquals(instance(\"Hello, {!exclaim}\", \"world\"), \"Hello, WORLD!\");\n```\n\nAnd here's one that semi-intelligently pluralizes units:\n\n```ts\nimport { create } from \"jsr:@haltcase/strat\";\nimport { assertEquals } from \"jsr:@std/assert/equals\";\n\ntype MiniUnitShape = {\n\tdays: number;\n\tdaysLabel: string;\n};\n\ntype NonLabelKeys = Exclude\u003ckeyof MiniUnitShape, `${string}Label`\u003e;\n\nconst instance = create({\n\tpluralize(_value, key, collection: MiniUnitShape[]) {\n\t\tconst labelSuffix = \"Label\";\n\n\t\tconst pluralUnit = key.slice(0, -labelSuffix.length) as NonLabelKeys;\n\t\tconst singularUnit = pluralUnit.slice(0, -1);\n\n\t\treturn collection[0][pluralUnit] === 1 ? singularUnit : pluralUnit;\n\t},\n});\n\nconst template = instance(\"{days} {daysLabel!pluralize}\");\n\nassertEquals(template({ days: 2, daysLabel: \"days\" }), \"2 days\");\nassertEquals(template({ days: 1, daysLabel: \"days\" }), \"1 day\");\n```\n\n## see also\n\n- [`logger-neue`][logger-neue] \u0026ndash; refined logging utility that utilizes\n  _strat_\n\n## development\n\n_strat_ is built with Deno and cross-published to npm for Node.js.\n\n1. Clone the repo: `git clone https://github.com/haltcase/strat.git`\n2. Move into the new directory: `cd strat`\n3. Install dependencies: `deno install`\n4. Run tests: `deno test`\n5. Check code quality (lint, format, types): `deno run qc`\n6. Build for npm: `deno run build-npm`\n\n## contributing\n\nPull requests and any [issues](https://github.com/haltcase/strat/issues) found\nare always welcome.\n\n1. Fork the project, and preferably create a branch named something like\n   `feat-make-better`\n2. Follow the development steps [above](#development) but using your forked repo\n3. Modify the source files as needed\n4. Make sure all tests continue to pass, and it never hurts to have more tests\n5. Push \u0026 pull request! :tada:\n\n## license\n\nMIT © [Bo Lingen / haltcase](https://github.com/haltcase)\n\n[string-format]: https://github.com/davidchambers/string-format\n[pythonref]: http://docs.python.org/library/stdtypes.html#str.format\n[mdn-array-map]: https://mdn.io/array/map\n[logger-neue]: https://github.com/haltcase/logger-neue\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaltcase%2Fstrat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaltcase%2Fstrat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaltcase%2Fstrat/lists"}