@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 +0 -4
- package/dist/text.index.cjs +10 -0
- package/dist/text.index.cjs.map +1 -1
- package/dist/text.index.d.cts +57 -1
- package/dist/text.index.d.ts +57 -1
- package/dist/text.index.js +9 -0
- package/dist/text.index.js.map +1 -1
- package/package.json +5 -5
- package/src/text.bundle.ts +1 -1
- package/src/text.lib.ts +81 -1
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
|
```
|
package/dist/text.index.cjs
CHANGED
@@ -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
|
package/dist/text.index.cjs.map
CHANGED
@@ -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"]}
|
package/dist/text.index.d.cts
CHANGED
@@ -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 };
|
package/dist/text.index.d.ts
CHANGED
@@ -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 };
|
package/dist/text.index.js
CHANGED
@@ -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
|
package/dist/text.index.js.map
CHANGED
@@ -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.
|
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.
|
54
|
-
"@synstack/resolved": "1.2.
|
55
|
-
"@synstack/str": "1.2.
|
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": "
|
67
|
+
"gitHead": "2868de5f8935b959b5b5faa6606a2fa9fcad27ab"
|
68
68
|
}
|
package/src/text.bundle.ts
CHANGED
@@ -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;
|