@synstack/text 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,6 @@ List of items:
46
46
 
47
47
  **What's baked in ?**
48
48
 
49
- - Functions are called
50
49
  - Promises even nested in arrays are resolved in parallel
51
50
  - Array values are joined with a newline
52
51
  - Text is trimmed
@@ -165,9 +164,6 @@ const textFail: Text.String<{ type: "extra"; value: string }> =
165
164
  const text: string & { __extra: { type: "extra"; value: string } } =
166
165
  t`Hello ${{ type: "extra" as const, value: "Hello" }} World`;
167
166
 
168
- console.log(text);
169
- // Hello %STR_EXTRA%{"type":"extra","value":"Hello"}%!STR_EXTRA% World
170
-
171
167
  console.log(tParse(text));
172
168
  // ["Hello ", { type: "extra", value: "Hello" }, " World"]
173
169
  ```
@@ -22,6 +22,7 @@ var text_index_exports = {};
22
22
  __export(text_index_exports, {
23
23
  TextParseExtraItemException: () => TextParseExtraItemException,
24
24
  t: () => t,
25
+ tIf: () => tIf,
25
26
  tParse: () => tParse
26
27
  });
27
28
  module.exports = __toCommonJS(text_index_exports);
@@ -125,12 +126,21 @@ ${(0, import_str.str)(cause instanceof Error ? cause.message : cause).indent(2).
125
126
  );
126
127
  }
127
128
  };
129
+ var tIf = (condition) => (template, ...args) => {
130
+ if (!condition) return "";
131
+ if (condition instanceof Promise)
132
+ return condition.then(
133
+ (c) => c ? t(template, ...args) : ""
134
+ );
135
+ return t(template, ...args);
136
+ };
128
137
  var t = Text.t;
129
138
  var tParse = Text.parse;
130
139
  // Annotate the CommonJS export names for ESM import in node:
131
140
  0 && (module.exports = {
132
141
  TextParseExtraItemException,
133
142
  t,
143
+ tIf,
134
144
  tParse
135
145
  });
136
146
  //# sourceMappingURL=text.index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/text.index.ts","../src/text.lib.ts"],"sourcesContent":["export * from \"./text.bundle.ts\";\nexport { TextParseExtraItemException } from \"./text.lib.ts\";\nexport type { Text } from \"./text.lib.ts\";\n","import { json } from \"@synstack/json\";\nimport { resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\nexport const t = Text.t;\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
1
+ {"version":3,"sources":["../src/text.index.ts","../src/text.lib.ts"],"sourcesContent":["export * from \"./text.bundle.ts\";\nexport { TextParseExtraItemException } from \"./text.lib.ts\";\nexport type { Text } from \"./text.lib.ts\";\n","import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
@@ -59,7 +59,63 @@ declare namespace Text {
59
59
  declare class TextParseExtraItemException extends Error {
60
60
  constructor(itemString: string, cause: any);
61
61
  }
62
+ /**
63
+ * @param condition - The condition to check, can be:
64
+ * - primitive values
65
+ * - promise of a primitive value
66
+ * @returns The result of the template string if the resolved value is true, otherwise an empty string
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const text = tIf(true)`Hello World`;
71
+ * assert.equal(text, "Hello World");
72
+ * ```
73
+ */
74
+ declare const tIf: <CHECK extends unknown>(condition: CHECK) => <TValues extends Array<Text.TemplateValue.Base>>(template: TemplateStringsArray, ...args: TValues) => CHECK extends Promise<any> ? Text.Return<TValues> extends Promise<any> ? Text.Return<TValues> : Promise<Text.Return<TValues>> : Text.Return<TValues>;
75
+ /**
76
+ * Serializes and formats a template string with values into a single string while preserving extra values typings
77
+ * @returns The serialized and formatted string
78
+ *
79
+ * @features
80
+ * - Promises even nested in arrays are resolved in parallel
81
+ * - Array values are joined with a newline
82
+ * - Text is trimmed
83
+ * - Base indentation is removed
84
+ * - Nested indentation is preserved for multi-line values
85
+ * - Returned value is either a string or a promise of a string based on interpolated values
86
+ * - Extra values are serialized and added to the returned value type
87
+ *
88
+ * @example
89
+ * For the following template:
90
+ * ```ts
91
+ * const text: string = t`
92
+ * Hello
93
+ * ${"- Item 1\n- Item 2"}
94
+ * World
95
+ * `;
96
+ * ```
97
+ *
98
+ * The output will be:
99
+ * ```plain
100
+ * Hello
101
+ * - Item 1
102
+ * - Item 2
103
+ * World
104
+ * ```
105
+ */
62
106
  declare const t: typeof Text.t;
107
+ /**
108
+ * Deserializes a serialized string with extra values
109
+ *
110
+ * @param text - The text string to parse
111
+ * @returns Array of strings and extra objects
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const text = t`Hello ${{type: "item", value: "World"}}`;
116
+ * assert.equal(tParse(text), ["Hello", { type: "item", value: "World" }]);
117
+ * ```
118
+ */
63
119
  declare const tParse: typeof Text.parse;
64
120
 
65
- export { Text, TextParseExtraItemException, t, tParse };
121
+ export { Text, TextParseExtraItemException, t, tIf, tParse };
@@ -59,7 +59,63 @@ declare namespace Text {
59
59
  declare class TextParseExtraItemException extends Error {
60
60
  constructor(itemString: string, cause: any);
61
61
  }
62
+ /**
63
+ * @param condition - The condition to check, can be:
64
+ * - primitive values
65
+ * - promise of a primitive value
66
+ * @returns The result of the template string if the resolved value is true, otherwise an empty string
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const text = tIf(true)`Hello World`;
71
+ * assert.equal(text, "Hello World");
72
+ * ```
73
+ */
74
+ declare const tIf: <CHECK extends unknown>(condition: CHECK) => <TValues extends Array<Text.TemplateValue.Base>>(template: TemplateStringsArray, ...args: TValues) => CHECK extends Promise<any> ? Text.Return<TValues> extends Promise<any> ? Text.Return<TValues> : Promise<Text.Return<TValues>> : Text.Return<TValues>;
75
+ /**
76
+ * Serializes and formats a template string with values into a single string while preserving extra values typings
77
+ * @returns The serialized and formatted string
78
+ *
79
+ * @features
80
+ * - Promises even nested in arrays are resolved in parallel
81
+ * - Array values are joined with a newline
82
+ * - Text is trimmed
83
+ * - Base indentation is removed
84
+ * - Nested indentation is preserved for multi-line values
85
+ * - Returned value is either a string or a promise of a string based on interpolated values
86
+ * - Extra values are serialized and added to the returned value type
87
+ *
88
+ * @example
89
+ * For the following template:
90
+ * ```ts
91
+ * const text: string = t`
92
+ * Hello
93
+ * ${"- Item 1\n- Item 2"}
94
+ * World
95
+ * `;
96
+ * ```
97
+ *
98
+ * The output will be:
99
+ * ```plain
100
+ * Hello
101
+ * - Item 1
102
+ * - Item 2
103
+ * World
104
+ * ```
105
+ */
62
106
  declare const t: typeof Text.t;
107
+ /**
108
+ * Deserializes a serialized string with extra values
109
+ *
110
+ * @param text - The text string to parse
111
+ * @returns Array of strings and extra objects
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const text = t`Hello ${{type: "item", value: "World"}}`;
116
+ * assert.equal(tParse(text), ["Hello", { type: "item", value: "World" }]);
117
+ * ```
118
+ */
63
119
  declare const tParse: typeof Text.parse;
64
120
 
65
- export { Text, TextParseExtraItemException, t, tParse };
121
+ export { Text, TextParseExtraItemException, t, tIf, tParse };
@@ -97,11 +97,20 @@ ${str(cause instanceof Error ? cause.message : cause).indent(2).toString()}
97
97
  );
98
98
  }
99
99
  };
100
+ var tIf = (condition) => (template, ...args) => {
101
+ if (!condition) return "";
102
+ if (condition instanceof Promise)
103
+ return condition.then(
104
+ (c) => c ? t(template, ...args) : ""
105
+ );
106
+ return t(template, ...args);
107
+ };
100
108
  var t = Text.t;
101
109
  var tParse = Text.parse;
102
110
  export {
103
111
  TextParseExtraItemException,
104
112
  t,
113
+ tIf,
105
114
  tParse
106
115
  };
107
116
  //# sourceMappingURL=text.index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/text.lib.ts"],"sourcesContent":["import { json } from \"@synstack/json\";\nimport { resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\nexport const t = Text.t;\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
1
+ {"version":3,"sources":["../src/text.lib.ts"],"sourcesContent":["import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "1.1.5",
7
+ "version": "1.1.6",
8
8
  "description": "String templating as it was meant to be",
9
9
  "keywords": [
10
10
  "string",
@@ -50,9 +50,9 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@synstack/json": "1.1.3",
54
- "@synstack/resolved": "1.2.0",
55
- "@synstack/str": "1.2.1"
53
+ "@synstack/json": "1.1.4",
54
+ "@synstack/resolved": "1.2.1",
55
+ "@synstack/str": "1.2.2"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^22.10.1",
@@ -64,5 +64,5 @@
64
64
  "!src/**/*.test.ts",
65
65
  "dist/**/*"
66
66
  ],
67
- "gitHead": "348d1987aba7aee8fbc5469d071e3c71663fc775"
67
+ "gitHead": "2868de5f8935b959b5b5faa6606a2fa9fcad27ab"
68
68
  }
@@ -1 +1 @@
1
- export { t, tParse } from "./text.lib.ts";
1
+ export { t, tIf, tParse } from "./text.lib.ts";
package/src/text.lib.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { json } from "@synstack/json";
2
- import { resolvable } from "@synstack/resolved";
2
+ import { resolvable, type Resolvable } from "@synstack/resolved";
3
3
  import { callable, type CallableResolvable } from "@synstack/resolved/callable";
4
4
  import { str } from "@synstack/str";
5
5
  import { type MaybeArray } from "../../shared/src/ts.utils.ts";
@@ -211,5 +211,85 @@ ${str(cause instanceof Error ? cause.message : (cause as string))
211
211
  }
212
212
  }
213
213
 
214
+ /**
215
+ * @param condition - The condition to check, can be:
216
+ * - primitive values
217
+ * - promise of a primitive value
218
+ * @returns The result of the template string if the resolved value is true, otherwise an empty string
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const text = tIf(true)`Hello World`;
223
+ * assert.equal(text, "Hello World");
224
+ * ```
225
+ */
226
+ export const tIf =
227
+ <CHECK extends Resolvable<any>>(condition: CHECK) =>
228
+ <TValues extends Array<Text.TemplateValue.Base>>(
229
+ template: TemplateStringsArray,
230
+ ...args: TValues
231
+ ): CHECK extends Promise<any>
232
+ ? // CHECK is a promise
233
+ Text.Return<TValues> extends Promise<any>
234
+ ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise
235
+ Text.Return<TValues>
236
+ : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise
237
+ Promise<Text.Return<TValues>>
238
+ : // Neither are promises, so we don't need to wrap the return value in a promise
239
+ Text.Return<TValues> => {
240
+ if (!condition) return "" as any;
241
+
242
+ if (condition instanceof Promise)
243
+ return condition.then((c) =>
244
+ c ? t(template, ...args) : ("" as any),
245
+ ) as any;
246
+
247
+ return t(template, ...args) as any;
248
+ };
249
+
250
+ /**
251
+ * Serializes and formats a template string with values into a single string while preserving extra values typings
252
+ * @returns The serialized and formatted string
253
+ *
254
+ * @features
255
+ * - Promises even nested in arrays are resolved in parallel
256
+ * - Array values are joined with a newline
257
+ * - Text is trimmed
258
+ * - Base indentation is removed
259
+ * - Nested indentation is preserved for multi-line values
260
+ * - Returned value is either a string or a promise of a string based on interpolated values
261
+ * - Extra values are serialized and added to the returned value type
262
+ *
263
+ * @example
264
+ * For the following template:
265
+ * ```ts
266
+ * const text: string = t`
267
+ * Hello
268
+ * ${"- Item 1\n- Item 2"}
269
+ * World
270
+ * `;
271
+ * ```
272
+ *
273
+ * The output will be:
274
+ * ```plain
275
+ * Hello
276
+ * - Item 1
277
+ * - Item 2
278
+ * World
279
+ * ```
280
+ */
214
281
  export const t = Text.t;
282
+
283
+ /**
284
+ * Deserializes a serialized string with extra values
285
+ *
286
+ * @param text - The text string to parse
287
+ * @returns Array of strings and extra objects
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * const text = t`Hello ${{type: "item", value: "World"}}`;
292
+ * assert.equal(tParse(text), ["Hello", { type: "item", value: "World" }]);
293
+ * ```
294
+ */
215
295
  export const tParse = Text.parse;