Remove the remaining async code

This commit is contained in:
Anton Liaposhchenko 2024-12-28 21:41:07 +02:00
parent 0296432bee
commit e7aae35ef6
129 changed files with 182 additions and 11918 deletions

8865
meta.json

File diff suppressed because it is too large Load Diff

@ -233,7 +233,7 @@ export {createEmbedNode} from "./lib/node/include/embed";
export {createIncludeNode} from "./lib/node/include/include";
// node executors
export {executeNodeSynchronously, type TwingNodeExecutor, type TwingSynchronousNodeExecutor} from "./lib/node-executor";
export {executeNodeSynchronously, type TwingSynchronousNodeExecutor} from "./lib/node-executor";
// tag handlers
export type {TwingTagHandler, TwingTokenParser} from "./lib/tag-handler";
@ -263,18 +263,17 @@ export {createWithTagHandler} from "./lib/tag-handler/with";
// core
export type {
TwingCallable, TwingCallableArgument, TwingCallableWrapperOptions, TwingCallableWrapper, TwingSynchronousCallable, TwingSynchronousCallableWrapper
TwingCallableArgument, TwingCallableWrapperOptions, TwingSynchronousCallable, TwingSynchronousCallableWrapper
} from "./lib/callable-wrapper";
export {type TwingContext, createContext} from "./lib/context";
export type {TwingEnvironment, TwingEnvironmentOptions, TwingNumberFormat, TwingSynchronousEnvironment, TwingSynchronousEnvironmentOptions} from "./lib/environment";
export type {TwingEnvironmentOptions, TwingNumberFormat, TwingSynchronousEnvironment, TwingSynchronousEnvironmentOptions} from "./lib/environment";
export type {
TwingEscapingStrategy, TwingEscapingStrategyHandler, TwingEscapingStrategyResolver
} from "./lib/escaping-strategy";
export type {TwingExecutionContext, TwingSynchronousExecutionContext} from "./lib/execution-context";
export type {TwingExtension, TwingSynchronousExtension} from "./lib/extension";
export type {TwingSynchronousExecutionContext} from "./lib/execution-context";
export type {TwingSynchronousExtension} from "./lib/extension";
export type {TwingExtensionSet} from "./lib/extension-set";
export type {TwingFilter, TwingSynchronousFilter} from "./lib/filter";
export type {TwingFunction, TwingSynchronousFunction} from "./lib/function";
export type {TwingSynchronousFilter} from "./lib/filter";
export type {TwingSynchronousFunction} from "./lib/function";
export type {TwingLexer} from "./lib/lexer";
export type {TwingNodeVisitor} from "./lib/node-visitor";
export type {
@ -286,16 +285,12 @@ export type {TwingSandboxSecurityPolicy} from "./lib/sandbox/security-policy";
export type {TwingSource} from "./lib/source";
export type {TwingSourceMapRuntime} from "./lib/source-map-runtime";
export type {
TwingTemplateAliases,
TwingTemplateBlockMap,
TwingTemplateBlockHandler,
TwingTemplateMacroHandler,
TwingSynchronousTemplateAliases,
TwingSynchronousTemplateBlockHandler,
TwingSynchronousTemplateBlockMap,
TwingSynchronousTemplateMacroHandler
} from "./lib/template";
export type {TwingTest, TwingSynchronousTest} from "./lib/test";
export type {TwingSynchronousTest} from "./lib/test";
export type {TwingTokenStream} from "./lib/token-stream";
export {createSynchronousEnvironment} from "./lib/environment";

@ -1,6 +1,5 @@
import {TwingExecutionContext, TwingSynchronousExecutionContext} from "./execution-context";
import {TwingSynchronousExecutionContext} from "./execution-context";
export type TwingCallable<A extends Array<any> = any, R = any> = (executionContext: TwingExecutionContext, ...args: A) => Promise<R>;
export type TwingSynchronousCallable<A extends Array<any> = any, R = any> = (executionContext: TwingSynchronousExecutionContext, ...args: A) => R;
export type TwingCallableArgument = {
@ -14,10 +13,10 @@ export type TwingCallableWrapperOptions = {
alternative?: string;
}
export interface TwingCallableWrapper {
export interface TwingSynchronousCallableWrapper {
readonly acceptedArguments: Array<TwingCallableArgument>;
readonly alternative: string | undefined;
readonly callable: TwingCallable;
readonly callable: TwingSynchronousCallable;
readonly deprecatedVersion: string | boolean | undefined;
readonly isDeprecated: boolean;
readonly isVariadic: boolean;
@ -30,51 +29,6 @@ export interface TwingCallableWrapper {
nativeArguments: Array<string>;
}
export interface TwingSynchronousCallableWrapper extends Omit<TwingCallableWrapper, "callable"> {
readonly callable: TwingSynchronousCallable;
}
export const createCallableWrapper = (
name: string,
callable: TwingCallable,
acceptedArguments: Array<TwingCallableArgument>,
options: TwingCallableWrapperOptions
): TwingCallableWrapper => {
let nativeArguments: Array<string> = [];
const callableWrapper = {
get callable() {
return callable;
},
get name() {
return name;
},
get acceptedArguments() {
return acceptedArguments;
},
get alternative() {
return options.alternative;
},
get deprecatedVersion() {
return options.deprecated;
},
get isDeprecated() {
return options.deprecated ? true : false;
},
get isVariadic() {
return options.is_variadic || false;
},
get nativeArguments() {
return nativeArguments;
},
set nativeArguments(values) {
nativeArguments = values;
}
};
return callableWrapper;
};
export const createSynchronousCallableWrapper = (
name: string,
callable: TwingSynchronousCallable,

@ -1,71 +0,0 @@
export interface TwingContext<K extends string, V> {
readonly size: number;
[Symbol.iterator](): IterableIterator<[string, V]>;
clone(): TwingContext<K, V>;
delete(key: K): boolean;
entries(): IterableIterator<[string, V]>;
get(key: K): V | undefined;
has(key: K): boolean;
set(key: K, value: V): TwingContext<K, V>;
values(): IterableIterator<V>;
}
export const createContext = <K extends string, V>(
container: Map<K, V> = new Map()
): TwingContext<K, V> => {
const context: TwingContext<K, V> = {
get size() {
return container.size;
},
[Symbol.iterator]: () => {
return container[Symbol.iterator]();
},
clone: () => {
const clonedContainer: Map<K, V> = new Map();
for (const [key, value] of container) {
clonedContainer.set(key, value);
}
return createContext(clonedContainer);
},
delete: (key) => {
return container.delete(key);
},
entries: () => {
return container.entries();
},
get: (key) => {
return container.get(key);
},
has: (key) => {
return container.has(key);
},
set: (key, value) => {
container.set(key, value);
return context;
},
values: () => {
return container.values();
}
};
return context;
};
export const getEntries = <V>(context: Record<string, V>): IterableIterator<[string, V]> => {
return Object.entries(context)[Symbol.iterator]();
};
export const getValues = <V>(context: Record<string, V>): Array<V> => {
return Object.values(context);
};

@ -1,11 +1,11 @@
import {TwingTagHandler} from "./tag-handler";
import {TwingNodeVisitor} from "./node-visitor";
import {createExtensionSet} from "./extension-set";
import {TwingFilter, TwingSynchronousFilter} from "./filter";
import {TwingSynchronousFilter} from "./filter";
import {createParser, TwingParser, TwingParserOptions} from "./parser";
import {TwingLoader, TwingSynchronousLoader} from "./loader";
import {TwingSynchronousTest, TwingTest} from "./test";
import {TwingFunction, TwingSynchronousFunction} from "./function";
import {TwingSynchronousLoader} from "./loader";
import {TwingSynchronousTest} from "./test";
import {TwingSynchronousFunction} from "./function";
import {TwingOperator} from "./operator";
import {TwingEscapingStrategy, TwingEscapingStrategyHandler} from "./escaping-strategy";
import {createHtmlEscapingStrategyHandler} from "./escaping-stragegy/html";
@ -15,17 +15,17 @@ import {createUrlEscapingStrategyHandler} from "./escaping-stragegy/url";
import {createHtmlAttributeEscapingStrategyHandler} from "./escaping-stragegy/html-attribute";
import {TwingSource} from "./source";
import {createTokenStream, TwingTokenStream} from "./token-stream";
import {TwingExtension, TwingSynchronousExtension} from "./extension";
import {TwingSynchronousExtension} from "./extension";
import {TwingTemplateNode} from "./node/template";
import {RawSourceMap} from "source-map";
import {createSourceMapRuntime} from "./source-map-runtime";
import {createSandboxSecurityPolicy, TwingSandboxSecurityPolicy} from "./sandbox/security-policy";
import {TwingSynchronousTemplate, TwingTemplate} from "./template";
import {TwingSynchronousTemplate} from "./template";
import {Settings as DateTimeSettings} from "luxon";
import {createLexer, type TwingLexer} from "./lexer";
import {TwingCache, TwingSynchronousCache} from "./cache";
import {createSynchronousCoreExtension} from "./extension/core";
import {createAutoEscapeNode, createTemplateLoadingError, type TwingContext} from "../lib";
import {createAutoEscapeNode, createTemplateLoadingError} from "../lib";
import {createSynchronousTemplateLoader} from "./template-loader";
export type TwingNumberFormat = {
@ -67,93 +67,6 @@ export type TwingSynchronousEnvironmentOptions = Omit<TwingEnvironmentOptions, "
cache?: TwingSynchronousCache;
};
export interface TwingEnvironment {
readonly cache: TwingCache | null;
readonly charset: string;
readonly dateFormat: string;
readonly dateIntervalFormat: string;
readonly escapingStrategyHandlers: Record<TwingEscapingStrategy, TwingEscapingStrategyHandler>;
readonly numberFormat: TwingNumberFormat;
readonly filters: Map<string, TwingFilter>;
readonly functions: Map<string, TwingFunction>;
readonly globals: TwingContext<string, any>;
readonly loader: TwingLoader | TwingSynchronousLoader;
readonly sandboxPolicy: TwingSandboxSecurityPolicy;
readonly tests: Map<string, TwingTest>;
readonly timezone: string;
/**
* Convenient method...
*
* @param extension
*/
addExtension(extension: TwingExtension): void;
addFilter(filter: TwingFilter): void;
addFunction(aFunction: TwingFunction): void;
addNodeVisitor(visitor: TwingNodeVisitor): void;
addOperator(operator: TwingOperator): void;
addTagHandler(parser: TwingTagHandler): void;
addTest(test: TwingTest): void;
/**
* Loads a template by its name.
*
* @param name The name of the template to load
* @param from The name of the template that requested the load
*
* @throws {Error} When the template cannot be found
* @throws {TwingParsingError} When an error occurred during the parsing of the source
*
* @return
*/
loadTemplate(name: string, from?: string | null): Promise<TwingTemplate>;
/**
* Converts a token list to a template.
*
* @param {TwingTokenStream} stream
* @param {TwingParserOptions} options
* *
* @throws {TwingParsingError} When the token stream is syntactically or semantically wrong
*/
parse(stream: TwingTokenStream, options?: TwingParserOptions): TwingTemplateNode;
/**
* Convenient method that renders a template from its name.
*/
render(name: string, context: Record<string, any>, options?: {
sandboxed?: boolean;
strict?: boolean;
}): Promise<string>;
/**
* Convenient method that renders a template from its name and returns both the render result and its belonging source map.
*/
renderWithSourceMap(name: string, context: Record<string, any>, options?: {
sandboxed?: boolean;
strict?: boolean;
}): Promise<{
data: string;
sourceMap: RawSourceMap;
}>;
registerEscapingStrategy(handler: TwingEscapingStrategyHandler, name: string): void;
/**
* Tokenizes a source code.
*
* @param {TwingSource} source The source to tokenize
* @return {TwingTokenStream}
*/
tokenize(source: TwingSource): TwingTokenStream;
}
export interface TwingSynchronousEnvironment {
readonly cache: TwingSynchronousCache | null;
readonly charset: string;

@ -1,28 +1,10 @@
import type {TwingTemplate, TwingTemplateAliases, TwingTemplateBlockMap} from "./template";
import type {TwingContext} from "./context";
import type {TwingOutputBuffer} from "./output-buffer";
import type {TwingSourceMapRuntime} from "./source-map-runtime";
import type {TwingEnvironment, TwingSynchronousEnvironment} from "./environment";
import type {TwingNodeExecutor} from "./node-executor";
import type {TwingTemplateLoader} from "./template-loader";
import type {TwingSynchronousEnvironment} from "./environment";
import {TwingSynchronousNodeExecutor} from "./node-executor";
import {TwingSynchronousTemplate, TwingSynchronousTemplateAliases, TwingSynchronousTemplateBlockMap} from "./template";
import {TwingSynchronousTemplateLoader} from "./template-loader";
export type TwingExecutionContext = {
aliases: TwingTemplateAliases;
blocks: TwingTemplateBlockMap;
context: TwingContext<any, any>;
environment: TwingEnvironment;
nodeExecutor: TwingNodeExecutor;
outputBuffer: TwingOutputBuffer;
sandboxed: boolean;
sourceMapRuntime?: TwingSourceMapRuntime;
strict: boolean;
template: TwingTemplate;
templateLoader: TwingTemplateLoader;
};
export type TwingSynchronousExecutionContext = {
aliases: TwingSynchronousTemplateAliases;
blocks: TwingSynchronousTemplateBlockMap;

@ -1,9 +1,9 @@
import {TwingTagHandler} from "./tag-handler";
import {TwingNodeVisitor} from "./node-visitor";
import {TwingOperator} from "./operator";
import type {TwingExtension, TwingSynchronousExtension} from "./extension";
import type {TwingSynchronousExtension} from "./extension";
export interface TwingExtensionSet<Extension extends TwingExtension | TwingSynchronousExtension> {
export interface TwingExtensionSet<Extension extends TwingSynchronousExtension> {
readonly binaryOperators: Array<TwingOperator>;
readonly filters: Map<string, Extension["filters"][number]>;
readonly functions: Map<string, Extension["functions"][number]>;
@ -27,7 +27,7 @@ export interface TwingExtensionSet<Extension extends TwingExtension | TwingSynch
addTest(test: Extension["tests"][number]): void;
}
export const createExtensionSet = <Extension extends TwingExtension | TwingSynchronousExtension> (): TwingExtensionSet<Extension> => {
export const createExtensionSet = <Extension extends TwingSynchronousExtension> (): TwingExtensionSet<Extension> => {
const binaryOperators: Array<TwingOperator> = [];
const filters: Map<string, Extension["filters"][number]> = new Map();
const functions: Map<string, Extension["functions"][number]> = new Map();

@ -1,54 +1,10 @@
import {TwingTagHandler} from "./tag-handler";
import {TwingNodeVisitor} from "./node-visitor";
import {TwingFilter, TwingSynchronousFilter} from "./filter";
import {TwingFunction, TwingSynchronousFunction} from "./function";
import {TwingSynchronousTest, TwingTest} from "./test";
import {TwingSynchronousFilter} from "./filter";
import {TwingSynchronousFunction} from "./function";
import {TwingSynchronousTest} from "./test";
import {TwingOperator} from "./operator";
export interface TwingExtension {
/**
* Returns a list of filters to add to the existing list.
*
* @return Array<TwingFilter>
*/
readonly filters: Array<TwingFilter>;
/**
* Returns a list of functions to add to the existing list.
*
* @return Array<TwingFunction>
*/
readonly functions: Array<TwingFunction>;
/**
* Returns the node visitor instances to add to the existing list.
*
* @return Array<TwingNodeVisitor>
*/
readonly nodeVisitors: Array<TwingNodeVisitor>;
/**
* Returns a list of operators to add to the existing list.
*
* @return TwingOperator[]
*/
readonly operators: Array<TwingOperator>;
/**
* Returns the token parser instances to add to the existing list.
*
* @return Array<TwingTagHandler>
*/
readonly tagHandlers: Array<TwingTagHandler>;
/**
* Returns a list of tests to add to the existing list.
*
* @returns Array<TwingTest>
*/
readonly tests: Array<TwingTest>;
}
export interface TwingSynchronousExtension {
/**
* Returns a list of filters to add to the existing list.

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Return the absolute value of a number.
@ -6,10 +6,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
* @param _executionContext
* @param x
*/
export const abs: TwingCallable = (_executionContext, x: number): Promise<number> => {
return Promise.resolve(Math.abs(x));
};
export const absSynchronously: TwingSynchronousCallable = (_executionContext, x: number): number => {
return Math.abs(x);
};

@ -1,6 +1,6 @@
import {chunk, chunkSynchronously} from "../../../helpers/chunk";
import {chunkSynchronously} from "../../../helpers/chunk";
import {fillMap} from "../../../helpers/fill-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Batches item.
@ -13,29 +13,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns Promise<Map<any, any>[]>
*/
export const batch: TwingCallable<[
items: Array<any>,
size: number,
fill: any,
preserveKeys: boolean
], Array<Map<any, any>>> = (_executionContext, items, size, fill, preserveKeys) => {
if ((items === null) || (items === undefined)) {
return Promise.resolve([]);
}
return chunk(items, size, preserveKeys)
.then((chunks) => {
if (fill !== null && chunks.length) {
const last = chunks.length - 1;
const lastChunk: Map<any, any> = chunks[last];
fillMap(lastChunk, size, fill);
}
return chunks;
});
};
export const batchSynchronously: TwingSynchronousCallable<[
items: Array<any>,
size: number,

@ -1,5 +1,4 @@
import type {TwingMarkup} from "../../../markup";
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const words: (value: string) => string = require('capitalize');
@ -11,16 +10,6 @@ const words: (value: string) => string = require('capitalize');
*
* @returns {Promise<string>} The capitalized string
*/
export const capitalize: TwingCallable<[
string: string | TwingMarkup
], string> = (_executionContext, string) => {
if ((string === null) || (string === undefined) || string === '') {
return Promise.resolve(string);
}
return Promise.resolve(words(string.toString()));
};
export const capitalizeSynchronously: TwingSynchronousCallable<[
string: string | TwingMarkup
], string> = (_executionContext, string) => {

@ -1,7 +1,7 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {isPlainObject} from "../../../helpers/is-plain-object";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Return the values from a single column in the input array.
@ -11,30 +11,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @return {Promise<Array<any>>} The array of values
*/
export const column: TwingCallable = (_executionContext, thing: any, columnKey: any): Promise<Array<any>> => {
let map: Map<any, any>;
if (!isTraversable(thing) || isPlainObject(thing)) {
return Promise.reject(new Error(`The column filter only works with arrays or "Traversable", got "${typeof thing}" as first argument.`));
} else {
map = iteratorToMap(thing);
}
const result: Array<any> = [];
for (const value of map.values()) {
const valueAsMap: Map<any, any> = iteratorToMap(value);
for (const [key, value] of valueAsMap) {
if (key === columnKey) {
result.push(value);
}
}
}
return Promise.resolve(result);
};
export const columnSynchronously: TwingSynchronousCallable = (_executionContext, thing: any, columnKey: any): Array<any> => {
let map: Map<any, any>;

@ -1,13 +1,5 @@
import {iconv} from "../../../helpers/iconv";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const convertEncoding: TwingCallable<[
value: string | Buffer,
to: string,
from: string
], Buffer> = (_executionContext, value, to, from) => {
return Promise.resolve(iconv(from, to, Buffer.from(value)));
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const convertEncodingSynchronously: TwingSynchronousCallable<[
value: string | Buffer,

@ -1,6 +1,6 @@
import {DateTime} from "luxon";
import {createDateTime, createDateTimeSynchronously} from "../functions/date";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {createDateTimeSynchronously} from "../functions/date";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Returns a new date object modified.
@ -15,33 +15,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<DateTime>} A new date object
*/
export const dateModify: TwingCallable = (
executionContext,
date: Date | DateTime | string,
modifier: string
): Promise<DateTime> => {
const {environment} = executionContext;
const {timezone: defaultTimezone} = environment;
return createDateTime(defaultTimezone, date, null)
.then((dateTime) => {
let regExp = new RegExp(/(\+|-)([0-9])(.*)/);
let parts = regExp.exec(modifier)!;
let operator: string = parts[1];
let operand: number = Number.parseInt(parts[2]);
let unit: string = parts[3].trim();
let duration: any = {};
duration[unit] = operator === '-' ? -operand : operand;
dateTime = dateTime.plus(duration);
return dateTime;
});
};
export const dateModifySynchronously: TwingSynchronousCallable = (
executionContext,
date: Date | DateTime | string,

@ -1,8 +1,8 @@
import {DateTime, Duration} from "luxon";
import {formatDuration} from "../../../helpers/format-duration";
import {formatDateTime} from "../../../helpers/format-date-time";
import {date as createDate, dateSynchronously as createDateSynchronously} from "../functions/date";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {dateSynchronously as createDateSynchronously} from "../functions/date";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Converts a date to the given format.
@ -18,33 +18,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @return {Promise<string>} The formatted date
*/
export const date: TwingCallable = (
executionContext,
date: DateTime | Duration | string,
format: string | null,
timezone: string | null | false
): Promise<string> => {
const {environment} = executionContext;
const {dateFormat, dateIntervalFormat} = environment;
return createDate(executionContext, date, timezone)
.then((date) => {
if (date instanceof Duration) {
if (format === null) {
format = dateIntervalFormat;
}
return Promise.resolve(formatDuration(date, format));
}
if (format === null) {
format = dateFormat;
}
return Promise.resolve(formatDateTime(date, format));
});
};
export const dateFilterSynchronously: TwingSynchronousCallable = (
executionContext,
date: DateTime | Duration | string,

@ -1,22 +1,6 @@
import {isEmpty, isEmptySynchronously} from "../tests/is-empty";
import type {TwingCallable} from "../../../callable-wrapper";
import {isEmptySynchronously} from "../tests/is-empty";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const defaultFilter: TwingCallable<[
value: any,
defaultValue: any | null
]> = (executionContext, value, defaultValue) => {
return isEmpty(executionContext, value)
.then((isEmpty) => {
if (isEmpty) {
return Promise.resolve(defaultValue);
}
else {
return Promise.resolve(value);
}
});
};
export const defaultFilterSynchronously: TwingSynchronousCallable<[
value: any,
defaultValue: any | null

@ -1,33 +1,7 @@
import {createMarkup, TwingMarkup} from "../../../markup";
import type {TwingCallable} from "../../../callable-wrapper";
import {escapeValue, escapeValueSynchronously} from "../../../helpers/escape-value";
import {escapeValueSynchronously} from "../../../helpers/escape-value";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const escape: TwingCallable<[
value: string | TwingMarkup | null,
strategy: string | null
], string | boolean | TwingMarkup | null> = (
executionContext,
value,
strategy
) => {
if (strategy === null) {
strategy = "html";
}
const {template, environment} = executionContext;
// todo: probably we need to use traceable method
return escapeValue(template, environment, value, strategy, environment.charset)
.then((value) => {
if (typeof value === "string") {
return createMarkup(value, environment.charset);
}
return value;
});
};
export const escapeSynchronously: TwingSynchronousCallable<[
value: string | TwingMarkup | null,
strategy: string | null

@ -1,19 +1,5 @@
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const filter: TwingCallable = async (_executionContext, map: any, callback: (...args: Array<any>) => Promise<boolean>): Promise<Map<any, any>> => {
const result: Map<any, any> = new Map();
map = iteratorToMap(map);
for (const [key, value] of map) {
if (await callback(value)) {
result.set(key, value);
}
}
return Promise.resolve(result);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const filterSynchronously: TwingSynchronousCallable = (_executionContext, map: any, callback: (...args: Array<any>) => boolean): Map<any, any> => {
const result: Map<any, any> = new Map();

@ -1,6 +1,6 @@
import {getFirstValue} from "../../../helpers/get-first-value";
import {slice, sliceSynchronously} from "./slice";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {sliceSynchronously} from "./slice";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Returns the first element of the item.
@ -10,15 +10,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<any>} The first element of the item
*/
export const first: TwingCallable<[
item: any
]> = (executionContext, item) => {
return slice(executionContext, item, 0, 1, false)
.then((elements) => {
return typeof elements === 'string' ? elements : getFirstValue(elements);
});
}
export const firstSynchronously: TwingSynchronousCallable<[
item: any
]> = (executionContext, item) => {

@ -1,13 +1,7 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const sprintf = require('locutus/php/strings/sprintf');
export const format: TwingCallable = (_executionContext, ...args: any[]): Promise<string> => {
return Promise.resolve(sprintf(...args.map((arg) => {
return arg.toString();
})));
};
export const formatSynchronously: TwingSynchronousCallable = (_executionContext, ...args: any[]): string => {
return sprintf(...args.map((arg) => {
return arg.toString();

@ -1,6 +1,6 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Joins the values to a string.
@ -22,49 +22,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<string>} The concatenated string
*/
export const join: TwingCallable<[
value: any,
glue: string,
and: string | null
], string> = (_executionContext, value, glue, and) => {
const _do = (): string => {
if ((value == null) || (value === undefined)) {
return '';
}
if (isTraversable(value)) {
value = iteratorToArray(value);
// this is ugly, but we have to ensure that each element of the array is rendered as PHP would render it
const safeValue = value.map((item: any) => {
if (typeof item === 'boolean') {
return (item === true) ? '1' : ''
}
if (Array.isArray(item)) {
return 'Array';
}
return item;
});
if (and === null || and === glue) {
return safeValue.join(glue);
}
if (safeValue.length === 1) {
return safeValue[0];
}
return safeValue.slice(0, -1).join(glue) + and + safeValue[safeValue.length - 1];
}
return '';
};
return Promise.resolve(_do());
};
export const joinSynchronously: TwingSynchronousCallable<[
value: any,
glue: string,

@ -3,7 +3,7 @@ import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {isPlainObject} from "../../../helpers/is-plain-object";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {isTraversable} from "../../../helpers/is-traversable";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
function isPureArray(map: Map<any, any>): boolean {
let result: boolean = true;
@ -22,44 +22,6 @@ function isPureArray(map: Map<any, any>): boolean {
return result;
}
export const jsonEncode: TwingCallable = (_executionContext, value: any): Promise<string> => {
const _sanitize = (value: any): any => {
if (isTraversable(value) || isPlainObject(value)) {
value = iteratorToMap(value);
}
if (value instanceof Map) {
let sanitizedValue: any;
if (isPureArray(value)) {
value = iteratorToArray(value);
sanitizedValue = [];
for (const key in value) {
sanitizedValue.push(_sanitize(value[key]));
}
}
else {
value = iteratorToHash(value);
sanitizedValue = {};
for (let key in value) {
sanitizedValue[key] = _sanitize(value[key]);
}
}
value = sanitizedValue;
}
return value;
};
return Promise.resolve(JSON.stringify(_sanitize(value)));
}
export const jsonEncodeSynchronously: TwingSynchronousCallable = (_executionContext, value: any): string => {
const _sanitize = (value: any): any => {
if (isTraversable(value) || isPlainObject(value)) {

@ -1,5 +1,5 @@
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Returns the keys of the passed array.
@ -9,23 +9,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<Array<any>>} The keys
*/
export const keys: TwingCallable<[
values: Array<any>
], Array<any>> = (
_executionContext,
values
) => {
let traversable;
if ((values === null) || (values === undefined)) {
traversable = new Map();
} else {
traversable = iteratorToMap(values);
}
return Promise.resolve([...traversable.keys()]);
};
export const keysSynchronously: TwingSynchronousCallable<[
values: Array<any>
], Array<any>> = (

@ -1,6 +1,6 @@
import {getFirstValue} from "../../../helpers/get-first-value";
import {slice, sliceSynchronously} from "./slice";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {sliceSynchronously} from "./slice";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Returns the last element of the item.
@ -10,15 +10,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns The last element of the item
*/
export const last: TwingCallable<[
item: any
]> = (executionContext, item) => {
return slice(executionContext, item, -1, 1, false)
.then((elements) => {
return typeof elements === 'string' ? elements : getFirstValue(elements);
});
};
export const lastSynchronously: TwingSynchronousCallable<[
item: any
]> = (executionContext, item) => {

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Returns the length of a thing.
@ -7,24 +7,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<number>} The length of the thing
*/
export const length: TwingCallable = (_executionContext,thing: any): Promise<number> => {
let length: number;
if ((thing === null) || (thing === undefined)) {
length = 0;
} else if (thing.length !== undefined) {
length = thing.length;
} else if (thing.size !== undefined) {
length = thing.size;
} else if (thing.toString && (typeof thing.toString === 'function')) {
length = thing.toString().length;
} else {
length = 1;
}
return Promise.resolve(length);
};
export const lengthSynchronously: TwingSynchronousCallable = (_executionContext,thing: any): number => {
let length: number;

@ -1,5 +1,5 @@
import type {TwingMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Converts a string to lowercase.
@ -8,10 +8,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns The lowercased string
*/
export const lower: TwingCallable = (_executionContext,string: string | TwingMarkup): Promise<string> => {
return Promise.resolve(string.toString().toLowerCase());
};
export const lowerSynchronously: TwingSynchronousCallable = (_executionContext,string: string | TwingMarkup): string => {
return string.toString().toLowerCase();
};

@ -1,20 +1,5 @@
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const map: TwingCallable<[
map: any,
callback: (...args: Array<any>) => Promise<any>
], Map<any, any>> = async (_executionContext, map, callback) => {
const result: Map<any, any> = new Map();
map = iteratorToMap(map);
for (const [key, value] of map) {
result.set(key, await callback(value, key));
}
return Promise.resolve(result);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const mapSynchronously: TwingSynchronousCallable<[
map: any,

@ -1,7 +1,7 @@
import {mergeIterables} from "../../../helpers/merge-iterables";
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Merges an array with another one.
@ -19,22 +19,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @return {Promise<Map<any, any>>} The merged map
*/
export const merge: TwingCallable = (_executionContext, iterable1: any, source: any): Promise<Map<any, any>> => {
const isIterable1NullOrUndefined = (iterable1 === null) || (iterable1 === undefined);
if (isIterable1NullOrUndefined || (!isTraversable(iterable1) && (typeof iterable1 !== 'object'))) {
return Promise.reject(new Error(`The merge filter only works on arrays or "Traversable", got "${!isIterable1NullOrUndefined ? typeof iterable1 : iterable1}".`));
}
const isSourceNullOrUndefined = (source === null) || (source === undefined);
if (isSourceNullOrUndefined || (!isTraversable(source) && (typeof source !== 'object'))) {
return Promise.reject(new Error(`The merge filter only accepts arrays or "Traversable" as source, got "${!isSourceNullOrUndefined ? typeof source : source}".`));
}
return Promise.resolve(mergeIterables(iteratorToMap(iterable1), iteratorToMap(source)));
};
export const mergeSynchronously: TwingSynchronousCallable = (_executionContext, iterable1: any, source: any): Map<any, any> => {
const isIterable1NullOrUndefined = (iterable1 === null) || (iterable1 === undefined);

@ -1,13 +1,9 @@
import type {TwingMarkup} from "../../../markup";
import {createMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpNl2br = require('locutus/php/strings/nl2br');
export const nl2br: TwingCallable = (_executionContext, ...args: Array<any>): Promise<TwingMarkup> => {
return Promise.resolve(createMarkup(phpNl2br(...args)));
};
export const nl2brSynchronously: TwingSynchronousCallable = (_executionContext, ...args: Array<any>): TwingMarkup => {
return createMarkup(phpNl2br(...args));
};

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpNumberFormat = require('locutus/php/strings/number_format');
@ -16,31 +16,6 @@ const phpNumberFormat = require('locutus/php/strings/number_format');
*
* @returns {Promise<string>} The formatted number
*/
export const numberFormat: TwingCallable = (
executionContext,
number: any,
numberOfDecimals: number | null,
decimalPoint: string | null,
thousandSeparator: string | null
): Promise<string> => {
const {environment} = executionContext;
const {numberFormat} = environment;
if (numberOfDecimals === null) {
numberOfDecimals = numberFormat.numberOfDecimals;
}
if (decimalPoint === null) {
decimalPoint = numberFormat.decimalPoint;
}
if (thousandSeparator === null) {
thousandSeparator = numberFormat.thousandSeparator;
}
return Promise.resolve(phpNumberFormat(number, numberOfDecimals, decimalPoint, thousandSeparator));
};
export const numberFormatSynchronously: TwingSynchronousCallable = (
executionContext,
number: any,

@ -1,5 +1,5 @@
import {createMarkup, TwingMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Marks a variable as being safe.
@ -8,10 +8,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @return {Promise<string>}
*/
export const raw: TwingCallable = (_executionContext, value: string | TwingMarkup | null): Promise<TwingMarkup> => {
return Promise.resolve(createMarkup(value !== null ? value.toString() : ''));
};
export const rawSynchronously: TwingSynchronousCallable = (_executionContext, value: string | TwingMarkup | null): TwingMarkup => {
return createMarkup(value !== null ? value.toString() : '');
};

@ -1,15 +1,5 @@
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const reduce: TwingCallable = (_executionContext, map: any, callback: (accumulator: any, currentValue: any) => any, initial: any): Promise<string> => {
map = iteratorToMap(map);
const values: any[] = [...map.values()];
return Promise.resolve(values.reduce((previousValue: any, currentValue: any): any => {
return (async () => callback(await previousValue, currentValue))();
}, initial));
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const reduceSynchronously: TwingSynchronousCallable = (_executionContext, map: any, callback: (accumulator: any, currentValue: any) => any, initial: any): Promise<string> => {
map = iteratorToMap(map);

@ -1,6 +1,6 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToHash} from "../../../helpers/iterator-to-hash";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpStrtr = require('locutus/php/strings/strtr');
@ -12,29 +12,6 @@ const phpStrtr = require('locutus/php/strings/strtr');
*
* @returns {Promise<string>}
*/
export const replace: TwingCallable = (_executionContext,value: string | null, from: any): Promise<string> => {
const _do = (): string => {
if (isTraversable(from)) {
from = iteratorToHash(from);
}
else if (typeof from !== 'object') {
throw new Error(`The "replace" filter expects an hash or "Iterable" as replace values, got "${typeof from}".`);
}
if (value === null) {
value = '';
}
return phpStrtr(value, from);
};
try {
return Promise.resolve(_do());
} catch (error) {
return Promise.reject(error);
}
};
export const replaceSynchronously: TwingSynchronousCallable = (_executionContext, value: string | null, from: any): string => {
if (isTraversable(from)) {
from = iteratorToHash(from);

@ -1,6 +1,6 @@
import {reverse as reverseHelper} from "../../../helpers/reverse";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Reverses a variable.
@ -10,14 +10,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<string | Map<any, any>>} The reversed input
*/
export const reverse: TwingCallable = (_executionContext, item: any, preserveKeys: boolean): Promise<string | Map<any, any>> => {
if (typeof item === 'string') {
return Promise.resolve([...item].reverse().join(''));
} else {
return Promise.resolve(reverseHelper(iteratorToMap(item as Map<any, any>), preserveKeys));
}
};
export const reverseSynchronously: TwingSynchronousCallable = (_executionContext, item: any, preserveKeys: boolean): string | Map<any, any> => {
if (typeof item === 'string') {
return [...item].reverse().join('');

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpRound = require('locutus/php/math/round');
const phpCeil = require('locutus/php/math/ceil');
@ -13,36 +13,6 @@ const phpFloor = require('locutus/php/math/floor');
*
* @returns {Promise<number>} The rounded number
*/
export const round: TwingCallable = (_executionContext, value: any, precision: number, method: string): Promise<number> => {
const _do = (): number => {
if (method === 'common') {
return phpRound(value, precision);
}
if (method !== 'ceil' && method !== 'floor') {
throw new Error('The round filter only supports the "common", "ceil", and "floor" methods.');
}
const intermediateValue = value * Math.pow(10, precision);
const intermediateDivider = Math.pow(10, precision);
if (method === 'ceil') {
return phpCeil(intermediateValue) / intermediateDivider;
}
else {
return phpFloor(intermediateValue) / intermediateDivider;
}
};
try {
const result = _do();
return Promise.resolve(result);
} catch (error: any) {
return Promise.reject(error);
}
};
export const roundSynchronously: TwingSynchronousCallable = (_executionContext, value: any, precision: number, method: string): number => {
if (method === 'common') {
return phpRound(value, precision);

@ -1,7 +1,7 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {sliceMap} from "../../../helpers/slice-map";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Slices a variable.
@ -14,31 +14,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<string | Map<any, any>>} The sliced variable
*/
export const slice: TwingCallable<[
item: any,
start: number,
length: number | null,
preserveKeys: boolean
], string | Map<any, any>> = (_executionContext, item, start, length, preserveKeys) => {
if (isTraversable(item)) {
const iterableItem = iteratorToMap(item);
if (length === null) {
length = iterableItem.size - start;
}
return Promise.resolve(sliceMap(iterableItem, start, length, preserveKeys));
}
item = '' + (item ? item : '');
if (length === null) {
length = item.length - start;
}
return Promise.resolve(item.substr(start, length));
};
export const sliceSynchronously: TwingSynchronousCallable<[
item: any,
start: number,

@ -1,7 +1,7 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
import {asort, asortSynchronously} from "../../../helpers/asort";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {asortSynchronously} from "../../../helpers/asort";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Sorts an iterable.
@ -12,21 +12,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<Map<any, any>>}
*/
export const sort: TwingCallable<[
iterable: any,
arrow: ((a: any, b: any) => Promise<-1 | 0 | 1>) | null
], Map<any, any>> = async (_executionContext, iterable, arrow)=> {
if (!isTraversable(iterable)) {
return Promise.reject(new Error(`The sort filter only works with iterables, got "${typeof iterable}".`));
}
const map = iteratorToMap(iterable);
await asort(map, arrow || undefined);
return map;
};
export const sortSynchronously: TwingSynchronousCallable<[
iterable: any,
arrow: ((a: any, b: any) => -1 | 0 | 1) | null

@ -1,15 +1,11 @@
import {createMarkup, TwingMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Removes whitespaces between HTML tags.
*
* @return {Promise<TwingMarkup>}
*/
export const spaceless: TwingCallable = (_executionContext, content: string | TwingMarkup): Promise<TwingMarkup> => {
return Promise.resolve(createMarkup(content.toString().replace(/>\s+</g, '><').trim()));
};
export const spacelessSynchronously: TwingSynchronousCallable = (_executionContext, content: string | TwingMarkup): TwingMarkup => {
return createMarkup(content.toString().replace(/>\s+</g, '><').trim());
};

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const explode = require('locutus/php/strings/explode');
@ -25,34 +25,6 @@ const explode = require('locutus/php/strings/explode');
*
* @returns {Promise<Array<string>>} The split string as an array
*/
export const split: TwingCallable = (_executionContext, value: string, delimiter: string, limit: number | null): Promise<Array<string>> => {
let _do = (): Array<string> => {
if (delimiter) {
return !limit ? explode(delimiter, value) : explode(delimiter, value, limit);
}
if (!limit || limit <= 1) {
return value.match(/.{1,1}/ug)!;
}
let length = value.length;
if (length < limit) {
return [value];
}
let r = [];
for (let i = 0; i < length; i += limit) {
r.push(value.substr(i, limit));
}
return r;
};
return Promise.resolve(_do());
};
export const splitSynchronously: TwingSynchronousCallable = (_executionContext, value: string, delimiter: string, limit: number | null): Array<string> => {
if (delimiter) {
return !limit ? explode(delimiter, value) : explode(delimiter, value, limit);

@ -1,11 +1,7 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpStripTags = require('locutus/php/strings/strip_tags');
export const striptags: TwingCallable = (_executionContext, input: string, allowedTags: string): Promise<string> => {
return Promise.resolve(phpStripTags(input, allowedTags));
};
export const striptagsSynchronously: TwingSynchronousCallable = (_executionContext, input: string, allowedTags: string): string => {
return phpStripTags(input, allowedTags);
};

@ -1,5 +1,5 @@
import type {TwingMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpUcwords = require('locutus/php/strings/ucwords');
@ -11,14 +11,6 @@ const phpUcwords = require('locutus/php/strings/ucwords');
*
* @returns The title-cased string
*/
export const title: TwingCallable<[
string: string | TwingMarkup
], string> = (_executionContext, string) => {
const result: string = phpUcwords(string.toString().toLowerCase());
return Promise.resolve(result);
};
export const titleSynchronously: TwingSynchronousCallable<[
string: string | TwingMarkup
], string> = (_executionContext, string) => {

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpTrim = require('locutus/php/strings/trim');
const phpLeftTrim = require('locutus/php/strings/ltrim');
@ -11,31 +11,6 @@ const phpRightTrim = require('locutus/php/strings/rtrim');
*
* @throws TwingErrorRuntime When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
*/
export const trim: TwingCallable = (_executionContext, string: string, characterMask: string | null, side: string): Promise<string> => {
const _do = (): string => {
if (characterMask === null) {
characterMask = " \t\n\r\0\x0B";
}
switch (side) {
case 'both':
return phpTrim(string, characterMask);
case 'left':
return phpLeftTrim(string, characterMask);
case 'right':
return phpRightTrim(string, characterMask);
default:
throw new Error('Trimming side must be "left", "right" or "both".');
}
};
try {
return Promise.resolve(_do());
} catch (error: any) {
return Promise.reject(error);
}
};
export const trimSynchronously: TwingSynchronousCallable = (_executionContext, string: string, characterMask: string | null, side: string): string => {
if (characterMask === null) {
characterMask = " \t\n\r\0\x0B";

@ -1,5 +1,5 @@
import type {TwingMarkup} from "../../../markup";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Converts a string to uppercase.
@ -8,10 +8,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<string>} The uppercased string
*/
export const upper: TwingCallable = (_executionContext, string: string | TwingMarkup): Promise<string> => {
return Promise.resolve(string.toString().toUpperCase());
};
export const upperSynchronously: TwingSynchronousCallable = (_executionContext, string: string | TwingMarkup): string => {
return string.toString().toUpperCase();
};

@ -1,6 +1,6 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToHash} from "../../../helpers/iterator-to-hash";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const phpHttpBuildQuery = require('locutus/php/url/http_build_query');
@ -11,20 +11,6 @@ const phpHttpBuildQuery = require('locutus/php/url/http_build_query');
*
* @returns {Promise<string>} The URL encoded value
*/
export const url_encode: TwingCallable = (_executionContext, url: string | {}): Promise<string> => {
if (typeof url !== 'string') {
if (isTraversable(url)) {
url = iteratorToHash(url);
}
const builtUrl: string = phpHttpBuildQuery(url, '', '&');
return Promise.resolve(builtUrl.replace(/\+/g, '%20'));
}
return Promise.resolve(encodeURIComponent(url));
}
export const urlEncodeSynchronously: TwingSynchronousCallable = (_executionContext, url: string | {}): string => {
if (typeof url !== 'string') {
if (isTraversable(url)) {

@ -1,18 +1,6 @@
import {getConstant as constantHelper} from "../../../helpers/get-constant";
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const constant: TwingCallable<[
name: string,
object: any | null
]> = (
executionContext,
name,
object
): Promise<any> => {
return Promise.resolve(constantHelper(executionContext.context, name, object));
};
export const constantSynchronously: TwingSynchronousCallable<[
name: string,
object: any | null

@ -1,5 +1,5 @@
import {isAMapLike} from "../../../helpers/map-like";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Cycles over a value.
@ -10,29 +10,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns The value at position
*/
export const cycle: TwingCallable<[
value: Map<any, any> | Array<any> | string | boolean | null,
position: number
]> = (_executionContext, value, position) => {
if (!isAMapLike(value) && !Array.isArray(value)) {
return Promise.resolve(value);
}
let values: Array<any>;
let size: number;
if (Array.isArray(value)) {
values = value;
size = value.length;
}
else {
values = [...value.values()];
size = value.size;
}
return Promise.resolve(values[position % size]);
}
export const cycleSynchronously: TwingSynchronousCallable<[
value: Map<any, any> | Array<any> | string | boolean | null,
position: number

@ -1,6 +1,6 @@
import {DateTime, Duration} from "luxon";
import {modifyDate} from "../../../helpers/modify-date";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Converts an input to a DateTime instance.
@ -17,26 +17,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @returns {Promise<DateTime | Duration>}
*/
export const createDateTime = (
defaultTimezone: string,
input: Date | DateTime | number | string | null,
timezone: string | null | false
): Promise<DateTime> => {
return Promise.resolve(createDateTimeSynchronously(defaultTimezone, input, timezone))
}
export const date: TwingCallable = (
executionContext,
date: Date | DateTime | Duration | number | string | null,
timezone: string | null | false
): Promise<DateTime | Duration> => {
if (date instanceof Duration) {
return Promise.resolve(date);
}
return createDateTime(executionContext.environment.timezone, date, timezone);
}
export const createDateTimeSynchronously = (
defaultTimezone: string,
input: Date | DateTime | number | string | null,

@ -1,25 +1,7 @@
import {iterate, iterateSynchronously} from "../../../helpers/iterate";
import {iterateSynchronously} from "../../../helpers/iterate";
import {createMarkup, TwingMarkup} from "../../../markup";
import {varDump} from "../../../helpers/php";
import type {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const dump: TwingCallable<[
...vars: Array<any>
], TwingMarkup> = (executionContext, ...vars) => {
if (vars.length < 1) {
const vars_ = new Map();
return iterate(executionContext.context, (key, value) => {
vars_.set(key, value);
return Promise.resolve();
}).then(() => {
return createMarkup(varDump(vars_));
});
}
return Promise.resolve(createMarkup(varDump(...vars)));
};
import type {TwingSynchronousCallable} from "../../../callable-wrapper";
export const dumpSynchronously: TwingSynchronousCallable<[
...vars: Array<any>

@ -1,11 +1,9 @@
import {isTraversable} from "../../../helpers/is-traversable";
import {isPlainObject} from "../../../helpers/is-plain-object";
import {createContext} from "../../../context";
import {createMarkup, TwingMarkup} from "../../../markup";
import type {TwingSynchronousTemplate, TwingTemplate} from "../../../template";
import type {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {iterableToMap, iteratorToMap} from "../../../helpers/iterator-to-map";
import {mergeIterables} from "../../../helpers/merge-iterables";
import type {TwingSynchronousTemplate} from "../../../template";
import type {TwingSynchronousCallable} from "../../../callable-wrapper";
import {iteratorToMap} from "../../../helpers/iterator-to-map";
/**
* Renders a template.
@ -19,88 +17,6 @@ import {mergeIterables} from "../../../helpers/merge-iterables";
*
* @returns {Promise<TwingMarkup>} The rendered template
*/
export const include: TwingCallable<[
templates: string | TwingTemplate | null | Array<string | TwingTemplate | null>,
variables: Map<string, any>,
withContext: boolean,
ignoreMissing: boolean,
sandboxed: boolean
]> = (
executionContext,
templates,
variables,
withContext,
ignoreMissing,
sandboxed
): Promise<TwingMarkup> => {
const {
template,
environment,
templateLoader,
context,
nodeExecutor,
outputBuffer,
sourceMapRuntime,
strict
} = executionContext;
if (!isPlainObject(variables) && !isTraversable(variables)) {
const isVariablesNullOrUndefined = variables === null || variables === undefined;
return Promise.reject(new Error(`Variables passed to the "include" function or tag must be iterable, got "${!isVariablesNullOrUndefined ? typeof variables : variables}".`));
}
variables = iteratorToMap(variables);
if (withContext) {
variables = mergeIterables(context, variables);
}
if (!Array.isArray(templates)) {
templates = [templates];
}
const resolveTemplate = (templates: Array<string | TwingTemplate | null>): Promise<TwingTemplate | null> => {
return template.loadTemplate(executionContext, templates)
.catch((error) => {
if (!ignoreMissing) {
throw error;
}
else {
return null;
}
});
};
return resolveTemplate(templates)
.then((template) => {
outputBuffer.start();
if (template) {
return template.execute(
environment,
createContext(iterableToMap(variables)),
new Map(),
outputBuffer,
{
nodeExecutor,
sandboxed,
sourceMapRuntime: sourceMapRuntime || undefined,
strict,
templateLoader
}
);
}
else {
return Promise.resolve();
}
})
.then(() => {
const result = outputBuffer.getAndClean();
return createMarkup(result, environment.charset);
});
}
export const includeSynchronously: TwingSynchronousCallable<[
templates: string | TwingSynchronousTemplate | null | Array<string | TwingSynchronousTemplate | null>,
variables: Map<string, any>,

@ -1,18 +1,7 @@
import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {max as phpMax} from "locutus/php/math";
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const max: TwingCallable<[
...values: Array<any>
]> = (_executionContext, ...values) => {
if (values.length === 1) {
values = values[0];
}
return Promise.resolve(phpMax(iteratorToArray(values)));
};
export const maxSynchronously: TwingSynchronousCallable<[
...values: Array<any>
]> = (_executionContext, ...values) => {

@ -1,18 +1,7 @@
import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {min as phpMin} from "locutus/php/math";
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const min: TwingCallable<[
...values: Array<any>
]> = (_executionContext, ...values) => {
if (values.length === 1) {
values = values[0];
}
return Promise.resolve(phpMin(iteratorToArray(values)));
};
export const minSynchronously: TwingSynchronousCallable<[
...values: Array<any>
]> = (_executionContext, ...values) => {

@ -1,7 +1,7 @@
import {iconv} from "../../../helpers/iconv";
import {isTraversable} from "../../../helpers/is-traversable";
import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
const runes = require('runes');
const mt_rand = require('locutus/php/math/mt_rand');
@ -21,72 +21,6 @@ const array_rand = require('locutus/php/array/array_rand');
*
* @returns {Promise<any>} A random value from the given sequence
*/
export const random: TwingCallable = (executionContext, values: any | null, max: number | null): any => {
const {environment} = executionContext;
const {charset} = environment;
let _do = (): any => {
if (values === null) {
return max === null ? mt_rand() : mt_rand(0, max);
}
if (typeof values === 'number') {
let min: number;
if (max === null) {
if (values < 0) {
max = 0;
min = values;
} else {
max = values;
min = 0;
}
} else {
min = values;
}
return mt_rand(min, max);
}
if (typeof values === 'string') {
values = Buffer.from(values);
}
if (Buffer.isBuffer(values)) {
if (values.toString() === '') {
return '';
}
if (charset !== 'UTF-8') {
values = iconv(charset, 'UTF-8', values);
}
// unicode split
values = runes(values.toString());
if (charset !== 'UTF-8') {
values = values.map((value: string) => {
return iconv('UTF-8', charset, Buffer.from(value)).toString();
});
}
} else if (isTraversable(values)) {
values = iteratorToArray(values);
}
if (!Array.isArray(values)) {
return values;
}
if (values.length < 1) {
return Promise.reject(new Error('The random function cannot pick from an empty array.'));
}
return values[array_rand(values, 1)];
};
return Promise.resolve(_do());
}
export const randomSynchronously: TwingSynchronousCallable = (executionContext, values: any | null, max: number | null): any => {
const {environment} = executionContext;
const {charset} = environment;

@ -1,15 +1,5 @@
import {createRange} from "../../../helpers/create-range";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
type Range<V = any> = TwingCallable<[
low: V,
high: V,
step: number
], Map<number, V>>;
export const range: Range = (_executionContext, low, high, step) => {
return Promise.resolve(createRange(low, high, step));
}
import {TwingSynchronousCallable} from "../../../callable-wrapper";
type SynchronousRange<V = any> = TwingSynchronousCallable<[
low: V,

@ -1,5 +1,5 @@
import {createTemplateLoadingError} from "../../../error/loader";
import type {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import type {TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousTemplate} from "../../../template";
/**
@ -11,25 +11,6 @@ import {TwingSynchronousTemplate} from "../../../template";
*
* @return The template source
*/
export const source: TwingCallable<[
name: string,
ignoreMissing: boolean
], string | null> = (executionContext, name, ignoreMissing) => {
const {template} = executionContext;
return template.loadTemplate(executionContext, name)
.catch(() => {
return null;
})
.then((template) => {
if (!ignoreMissing && (template === null)) {
throw createTemplateLoadingError([name]);
}
return template?.source.code || null;
});
};
export const sourceSynchronously: TwingSynchronousCallable<[
name: string,
ignoreMissing: boolean

@ -2,9 +2,9 @@ import {createSynchronousTemplate, TwingSynchronousTemplate} from "../../../temp
import {TwingSynchronousCallable} from "../../../callable-wrapper";
import * as createHash from "create-hash";
import {createSource} from "../../../source";
import {TwingExecutionContext, TwingSynchronousExecutionContext} from "../../../execution-context";
import {TwingSynchronousExecutionContext} from "../../../execution-context";
const getAST = (executionContext: TwingExecutionContext | TwingSynchronousExecutionContext, code: string, name: string | null) => {
const getAST = (executionContext: TwingSynchronousExecutionContext, code: string, name: string | null) => {
const {environment} = executionContext;
const hash: string = createHash("sha256").update(code).digest("hex").toString();

@ -1,20 +1,6 @@
import {getConstant} from "../../../helpers/get-constant";
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isConstant: TwingCallable<[
comparand: any,
constant: any,
object: any | null
], boolean> = (
executionContext,
comparand,
constant,
object
) => {
return Promise.resolve(comparand === getConstant(executionContext.context, constant, object));
};
export const isConstantSynchronously: TwingSynchronousCallable<[
comparand: any,
constant: any,

@ -1,13 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const isDefined: TwingCallable<[
value: any
], boolean> = (
_executionContext,
value
) => {
return Promise.resolve(!!value);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isDefinedSynchronously: TwingSynchronousCallable<[
value: any

@ -1,10 +1,5 @@
import type {TwingCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isDivisibleBy: TwingCallable<[a: any, divisor: any], boolean> = (_executionContext, a, divisor) => {
return Promise.resolve(a % divisor === 0);
};
export const isDivisibleBySynchronously: TwingSynchronousCallable<[a: any, divisor: any], boolean> = (_executionContext, a, divisor) => {
return a % divisor === 0;
};

@ -1,5 +1,5 @@
import {iteratorToArray} from "../../../helpers/iterator-to-array";
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
import { isPlainObject } from "../../../helpers/is-plain-object";
/**
@ -17,35 +17,6 @@ import { isPlainObject } from "../../../helpers/is-plain-object";
*
* @returns {boolean} true if the value is empty, false otherwise
*/
export const isEmpty: TwingCallable<[value: any], boolean> = (executionContext, value) => {
if (value === null || value === undefined) {
return Promise.resolve(true);
}
if (typeof value === 'string') {
return Promise.resolve(value.length < 1);
}
if (typeof value[Symbol.iterator] === 'function') {
return Promise.resolve(value[Symbol.iterator]().next().done === true);
}
if (isPlainObject(value)) {
if (value.hasOwnProperty('toString') && typeof value.toString === 'function') {
return isEmpty(executionContext, value.toString());
}
else {
return Promise.resolve(iteratorToArray(value).length < 1);
}
}
if (typeof value === 'object' && value.toString && typeof value.toString === 'function') {
return isEmpty(executionContext, value.toString());
}
return Promise.resolve(value === false);
};
export const isEmptySynchronously: TwingSynchronousCallable<[value: any], boolean> = (executionContext, value) => {
if (value === null || value === undefined) {
return true;

@ -1,8 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const isEven: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
return Promise.resolve(value % 2 === 0);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isEvenSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
return value % 2 === 0;

@ -1,4 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
import {TwingSynchronousCallable} from "../../../callable-wrapper";
/**
* Checks if a variable is traversable.
@ -14,45 +14,6 @@ import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper
*
* @return {Promise<boolean>} true if the value is traversable
*/
export const isIterable: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
let _do = (): boolean => {
/*
Prevent `(null)[Symbol.iterator]`/`(undefined)[Symbol.iterator]` error,
and return `false` instead.
Note that `value` should only be `undefined` if it's been explicitly
set to that (e.g., in the JavaScript that provided the calling template
with the context). Values that are simply "not defined" will either have
been coerced to `null` or thrown a "does not exist" runtime error before
this function is called (depending on whether `strict_variables` is enabled).
This *does* mean that an explicitly `undefined` value will return `false`
instead of throwing an error if `strict_variables` is enabled, which is
probably unexpected behavior, but short of some major refactoring to allow
an environmental check here, the alternative is to have `undefined`
throw an error even when `strict_variables` is disabled, and that unexpected
behavior seems worse.
*/
if (value === null || value === undefined) {
return false;
}
// for Twig, a string is not traversable
if (typeof value === 'string') {
return false;
}
if (typeof value[Symbol.iterator] === 'function') {
return true;
}
// in PHP objects are not iterable so we have to ensure that the test reflects that
return false;
};
return Promise.resolve(_do());
};
export const isIterableSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
/*
Prevent `(null)[Symbol.iterator]`/`(undefined)[Symbol.iterator]` error,

@ -1,8 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const isNull: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
return Promise.resolve(value === null);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isNullSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
return value === null;

@ -1,8 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const isOdd: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
return Promise.resolve(value % 2 === 1);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isOddSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
return value % 2 === 1;

@ -1,8 +1,4 @@
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
export const isSameAs: TwingCallable<[a: any, comparand: any], boolean> = (_executionContext, a, comparand) => {
return Promise.resolve(a === comparand);
};
import {TwingSynchronousCallable} from "../../../callable-wrapper";
export const isSameAsSynchronously: TwingSynchronousCallable<[a: any, comparand: any], boolean> = (_executionContext, a, comparand) => {
return a === comparand;

@ -1,37 +1,16 @@
import {
TwingCallableWrapperOptions,
TwingCallableArgument,
TwingCallable,
TwingCallableWrapper,
createCallableWrapper,
TwingSynchronousCallableWrapper,
createSynchronousCallableWrapper, TwingSynchronousCallable
} from "./callable-wrapper";
export type TwingFilterOptions = TwingCallableWrapperOptions;
export interface TwingFilter extends TwingCallableWrapper {
}
export interface TwingSynchronousFilter extends TwingSynchronousCallableWrapper {
}
export const createFilter = (
name: string,
callable: TwingCallable,
acceptedArguments: TwingCallableArgument[],
options: TwingFilterOptions = {}
): TwingFilter => {
const callableWrapper = createCallableWrapper(name, callable, acceptedArguments, options);
const filter: TwingFilter = {
...callableWrapper
};
return filter;
};
export const createSynchronousFilter = (
name: string,

@ -1,32 +1,14 @@
import {
TwingCallableWrapperOptions,
TwingCallableArgument,
TwingCallable,
TwingCallableWrapper,
createCallableWrapper,
TwingSynchronousCallableWrapper,
TwingSynchronousCallable, createSynchronousCallableWrapper
} from "./callable-wrapper";
export interface TwingFunction extends TwingCallableWrapper {
}
export interface TwingSynchronousFunction extends TwingSynchronousCallableWrapper {
}
export const createFunction = (
name: string,
callable: TwingCallable,
acceptedArguments: TwingCallableArgument[],
options: TwingCallableWrapperOptions = {}
): TwingFunction => {
const callableWrapper = createCallableWrapper(name, callable, acceptedArguments, options);
return callableWrapper;
};
export const createSynchronousFunction = (
name: string,
callable: TwingSynchronousCallable,

@ -1,5 +1,3 @@
import {sortAsynchronously} from "./sort";
/**
* Sort a map and maintain index association.
*
@ -7,37 +5,6 @@ import {sortAsynchronously} from "./sort";
* @param compareFunction
* @returns
*/
export const asort = async (map: Map<any, any>, compareFunction?: (a: any, b: any) => Promise<-1 | 0 | 1>) => {
const sortedMap = new Map();
const keys: Array<any> = ([] as Array<any>).fill(null, 0, map.size);
const values = [...map.values()];
let sortedValues: Array<any>;
if (compareFunction) {
sortedValues = await sortAsynchronously(values, compareFunction);
}
else {
sortedValues = values.sort();
}
for (const [key, value] of map) {
const index = sortedValues.indexOf(value);
keys[index] = key;
}
for (const key of keys) {
sortedMap.set(key, map.get(key));
}
map.clear();
for (const [key, value] of sortedMap) {
map.set(key, value);
}
}
export const asortSynchronously = (map: Map<any, any>, compareFunction?: (a: any, b: any) => -1 | 0 | 1) => {
const sortedMap = new Map();
const keys: Array<any> = ([] as Array<any>).fill(null, 0, map.size);

@ -8,7 +8,6 @@
import {DateTime} from "luxon";
import {isAMapLike, MapLike} from "./map-like";
import {isAMarkup, TwingMarkup} from "../markup";
import {TwingContext} from "../context";
import {iteratorToMap} from "./iterator-to-map";
type Operand = Buffer | TwingMarkup | DateTime | MapLike<any, any> | string | boolean | number | null | object | Array<any>;
@ -100,8 +99,8 @@ export function compare(
*
*/
function compareToMap(
firstOperand: Map<any, any> | TwingContext<any, any>,
secondOperand: string | object | DateTime | Map<any, any> | TwingContext<any, any>
firstOperand: Map<any, any>,
secondOperand: string | object | DateTime | Map<any, any>
): boolean {
if (firstOperand.size === 0) {
return isAMapLike(secondOperand) && (secondOperand.size === 0);

@ -1,44 +1,11 @@
import {isAMarkup, TwingMarkup} from "../markup";
import {TwingEnvironment, TwingSynchronousEnvironment} from "../environment";
import {TwingSynchronousEnvironment} from "../environment";
import {TwingEscapingStrategy} from "../escaping-strategy";
import {TwingSynchronousTemplate, TwingTemplate} from "../template";
export const escapeValue = (
template: TwingTemplate,
environment: TwingEnvironment,
value: string | boolean | TwingMarkup | null | undefined,
strategy: TwingEscapingStrategy | string,
charset: string | null
): Promise<string | boolean | TwingMarkup> => {
if (typeof value === "boolean") {
return Promise.resolve(value);
}
if (isAMarkup(value)) {
return Promise.resolve(value);
}
let result: string;
if ((value === null) || (value === undefined)) {
result = '';
}
else {
const strategyHandler = environment.escapingStrategyHandlers[strategy];
if (strategyHandler === undefined) {
return Promise.reject(new Error(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`));
}
result = strategyHandler(value.toString(), charset || environment.charset, template.name);
}
return Promise.resolve(result);
}
import {TwingSynchronousTemplate} from "../template";
export const escapeValueSynchronously = (
template: TwingTemplate | TwingSynchronousTemplate,
environment: TwingEnvironment | TwingSynchronousEnvironment,
template: TwingSynchronousTemplate,
environment: TwingSynchronousEnvironment,
value: string | boolean | TwingMarkup | null | undefined,
strategy: TwingEscapingStrategy | string,
charset: string | null

@ -4,10 +4,7 @@ import { isPlainObject } from "./is-plain-object";
import { get } from "./get";
import type { TwingAttributeAccessorCallType } from "../node/expression/attribute-accessor";
import { isBoolean, isFloat } from "./php";
import type {
TwingEnvironment,
TwingSynchronousEnvironment,
} from "../environment";
import type {TwingSynchronousEnvironment} from "../environment";
const isObject = (value: unknown) =>
typeof value === "object" && !!value && !Array.isArray(value);
@ -29,244 +26,6 @@ const isObject = (value: unknown) =>
*
* @throw {TwingErrorRuntime} if the attribute does not exist and Twing is running in strict mode and isDefinedTest is false
*/
export const getAttribute = (
environment: TwingEnvironment,
object: any,
attribute: any,
methodArguments: Map<any, any>,
type: TwingAttributeAccessorCallType,
shouldTestExistence: boolean,
shouldIgnoreStrictCheck: boolean | null,
sandboxed: boolean,
strict: boolean
): Promise<any> => {
const { sandboxPolicy } = environment;
shouldIgnoreStrictCheck =
shouldIgnoreStrictCheck === null ? !strict : shouldIgnoreStrictCheck;
const _do = (): any => {
let message: string;
// ANY_CALL or ARRAY_CALL
if (type !== "method") {
let arrayItem;
if (isBoolean(attribute)) {
arrayItem = attribute ? 1 : 0;
} else if (isFloat(attribute)) {
arrayItem = parseInt(attribute);
} else {
arrayItem = attribute;
}
if (object) {
if (
(isAMapLike(object) && object.has(arrayItem)) ||
(Array.isArray(object) &&
typeof arrayItem === "number" &&
object.length > arrayItem) ||
(isPlainObject(object) && Reflect.has(object, arrayItem))
) {
if (shouldTestExistence) {
return true;
}
if (type !== "array" && sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return get(object, arrayItem);
}
}
if (
type === "array" ||
isAMapLike(object) ||
Array.isArray(object) ||
object === null ||
typeof object !== "object"
) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
// object is null
if (type === "array") {
message = `Impossible to access a key ("${attribute}") on a null variable.`;
} else {
message = `Impossible to access an attribute ("${attribute}") on a null variable.`;
}
} else if (isAMapLike(object)) {
if (object.size < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
} else {
message = `Index "${arrayItem}" is out of bounds for array [${[
...object.values(),
]}].`;
}
} else if (Array.isArray(object)) {
if (object.length < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
} else {
message = `Index "${arrayItem}" is out of bounds for array [${[
...object,
]}].`;
}
} else if (type === "array") {
// object is another kind of object
message = `Impossible to access a key ("${attribute}") on a ${typeof object} variable ("${object.toString()}").`;
} else {
// object is a primitive
message = `Impossible to access an attribute ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
}
// ANY_CALL or METHOD_CALL
if (object === null || !isObject(object) || isAMapLike(object)) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
message = `Impossible to invoke a method ("${attribute}") on a null variable.`;
} else if (isAMapLike(object) || Array.isArray(object)) {
message = `Impossible to invoke a method ("${attribute}") on an array.`;
} else {
message = `Impossible to invoke a method ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
// object property
if (type !== "method") {
if (
Reflect.has(object, attribute) &&
typeof object[attribute] !== "function"
) {
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return get(object, attribute);
}
}
// object method
// precedence: getXxx() > isXxx() > hasXxx()
let methods: Array<string> = [];
for (let property of examineObject(object)) {
let candidate = object[property];
if (typeof candidate === "function") {
methods.push(property);
}
}
methods.sort();
let lcMethods: Array<string> = methods.map((method) => {
return method.toLowerCase();
});
let candidates = new Map();
for (let i = 0; i < methods.length; i++) {
let method: string = methods[i];
let lcName: string = lcMethods[i];
candidates.set(method, method);
candidates.set(lcName, method);
let name: string = "";
if (lcName[0] === "g" && lcName.indexOf("get") === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
} else if (lcName[0] === "i" && lcName.indexOf("is") === 0) {
name = method.substr(2);
lcName = lcName.substr(2);
} else if (lcName[0] === "h" && lcName.indexOf("has") === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
if (lcMethods.includes("is" + lcName)) {
continue;
}
} else {
continue;
}
// skip get() and is() methods (in which case, name is empty)
if (name.length > 0) {
if (!candidates.has(name)) {
candidates.set(name, method);
}
if (!candidates.has(lcName)) {
candidates.set(lcName, method);
}
}
}
let itemAsString: string = attribute as string;
let method: string;
let lcItem: string;
if (candidates.has(attribute)) {
method = candidates.get(attribute);
} else if (candidates.has((lcItem = itemAsString.toLowerCase()))) {
method = candidates.get(lcItem);
} else {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
throw new Error(
`Neither the property "${attribute}" nor one of the methods ${attribute}()" or "get${attribute}()"/"is${attribute}()"/"has${attribute}()" exist and have public access in class "${object.constructor.name}".`
);
}
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkMethodAllowed(object, method);
}
return get(object, method).apply(object, [...methodArguments.values()]);
};
try {
return Promise.resolve(_do());
} catch (e) {
return Promise.reject(e);
}
};
export const getAttributeSynchronously = (
environment: TwingSynchronousEnvironment,
object: any,

@ -8,9 +8,7 @@
*
* @returns {any}
*/
import {TwingContext} from "../context";
export function getConstant(context: TwingContext<any, any> | Map<string, any>, name: string, object: any | null): any {
export function getConstant(context: Map<string, any>, name: string, object: any | null): any {
if (object) {
return object[name];
} else {

@ -1,4 +1,4 @@
import type {TwingFilter, TwingSynchronousFilter} from "../filter";
import type {TwingSynchronousFilter} from "../filter";
/**
* Get a filter by name.
@ -7,7 +7,7 @@ import type {TwingFilter, TwingSynchronousFilter} from "../filter";
*
* @return {TwingFilter|false} A TwingFilter instance or false if the filter does not exist
*/
export const getFilter = <Filter extends TwingFilter | TwingSynchronousFilter>(
export const getFilter = <Filter extends TwingSynchronousFilter>(
filters: Map<string, Filter>,
name: string
): Filter | null => {

@ -1,4 +1,3 @@
import type {TwingFunction} from "../function";
import {TwingSynchronousFunction} from "../function";
/**
@ -7,7 +6,7 @@ import {TwingSynchronousFunction} from "../function";
* @param {string} name function name
* @returns {TwingFunction} A TwingFunction instance or null if the function does not exist
*/
export const getFunction = <Function extends TwingFunction | TwingSynchronousFunction>(
export const getFunction = <Function extends TwingSynchronousFunction>(
functions: Map<string, Function>,
name: string
): Function | null => {

@ -1,4 +1,3 @@
import type {TwingTest} from "../test";
import {TwingSynchronousTest} from "../test";
/**
@ -7,7 +6,7 @@ import {TwingSynchronousTest} from "../test";
* @param {string} name The test name
* @returns {TwingTest} A MyTest instance or null if the test does not exist
*/
export const getTest = <Test extends TwingTest | TwingSynchronousTest>(
export const getTest = <Test extends TwingSynchronousTest>(
tests: Map<string, Test>,
name: string
): Test | null => {

@ -1,7 +1,6 @@
import {TwingContext} from "../context";
import {iteratorToMap} from "./iterator-to-map";
export type MapLike<K extends string, V> = Map<K, V> | TwingContext<K, V>;
export type MapLike<K extends string, V> = Map<K, V>;
export function isAMapLike(candidate: any): candidate is MapLike<any, any> {
return candidate !== null &&

@ -1,6 +1,4 @@
import type {TwingContext} from "../context";
export function mergeIterables<V>(iterable1: TwingContext<any, V> | Map<any, V>, iterable2: TwingContext<any, V> | Map<any, V>): Map<any, V> {
export function mergeIterables<V>(iterable1: Map<any, V>, iterable2: Map<any, V>): Map<any, V> {
let result = new Map();
let index = 0;

@ -1,77 +0,0 @@
type Comparator = (a: any, b: any) => Promise<-1 | 0 | 1>;
export const sortAsynchronously = (array: Array<any>, comparator: Comparator) => {
/**
* return the median value among x, y, and z
*/
const getPivot = async (x: any, y: any, z: any, comparator: Comparator): Promise<any> => {
if (await comparator(x, y) < 0) {
if (await comparator(y, z) < 0) {
return y;
}
else if (await comparator(z, x) < 0) {
return x;
}
else {
return z;
}
}
else if (await comparator(y, z) > 0) {
return y;
}
else if (await comparator(z, x) > 0) {
return x;
}
else {
return z;
}
};
/**
* Asynchronous quick sort.
*
* @see https://gist.github.com/kimamula/fa34190db624239111bbe0deba72a6ab
*
* @param array The array to sort
* @param comparator The comparator function
* @param left The index where the range of elements to be sorted starts
* @param right The index where the range of elements to be sorted ends
*/
const quickSort = async (array: Array<any>, comparator: Comparator, left = 0, right = array.length - 1): Promise<Array<any>> => {
if (left < right) {
let i = left;
let j = right;
let tmp;
const pivot = await getPivot(array[i], array[i + Math.floor((j - i) / 2)], array[j], comparator);
while (true) {
while (await comparator(array[i], pivot) < 0) {
i++;
}
while (await comparator(pivot, array[j]) < 0) {
j--;
}
if (i >= j) {
break;
}
tmp = array[i];
array[i] = array[j];
array[j] = tmp;
i++;
j--;
}
await quickSort(array, comparator, left, i - 1);
await quickSort(array, comparator, j + 1, right);
}
return array;
};
return quickSort(array, comparator);
};

@ -2,22 +2,6 @@ import {createRuntimeError} from "../error/runtime";
import type {TwingSource} from "../source";
import {isATwingError} from "../error";
export function getTraceableMethod<M extends (...args: Array<any>) => Promise<any>>(method: M, location: {
line: number;
column: number;
}, templateSource: TwingSource): M {
return ((...args: Array<any>) => {
return method(...args)
.catch((error) => {
if (!isATwingError(error)) {
throw createRuntimeError(error.message, location, templateSource, error);
}
throw error;
});
}) as typeof method;
}
export function getSynchronousTraceableMethod<M extends (...args: Array<any>) => any>(method: M, location: {
line: number;
column: number;

@ -1,5 +1,4 @@
import type {TwingBaseNode} from "./node";
import type {TwingExecutionContext} from "./execution-context";
import {executeBinaryNodeSynchronously} from "./node-executor/expression/binary";
import {executeTemplateNodeSynchronously} from "./node-executor/template";
import {executePrintNodeSynchronously} from "./node-executor/print";
@ -46,11 +45,6 @@ import {executeCommentNodeSynchronously} from "./node-executor/comment";
import {executeBaseNodeSynchronously} from "./node-executor/base";
import {TwingSynchronousExecutionContext} from "./execution-context";
export type TwingNodeExecutor<Node extends TwingBaseNode = TwingBaseNode> = (
node: Node,
executionContext: TwingExecutionContext
) => Promise<any>;
export type TwingSynchronousNodeExecutor<Node extends TwingBaseNode = TwingBaseNode> = (
node: Node,
executionContext: TwingSynchronousExecutionContext

@ -1,35 +1,9 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingApplyNode} from "../node/apply";
import {getKeyValuePairs} from "../helpers/get-key-value-pairs";
import {createFilterNode} from "../node/expression/call/filter";
import {createConstantNode} from "../node/expression/constant";
export const executeApplyNode: TwingNodeExecutor<TwingApplyNode> = (node, executionContext) => {
const {outputBuffer, nodeExecutor: execute} = executionContext;
const {body, filters} = node.children;
const {line, column} = node;
outputBuffer.start();
return execute(body, executionContext)
.then(async () => {
let content = outputBuffer.getAndClean();
const keyValuePairs = getKeyValuePairs(filters);
while (keyValuePairs.length > 0) {
const {key, value: filterArguments} = keyValuePairs.pop()!;
const filterName = key.attributes.value as string;
const filterNode = createFilterNode(createConstantNode(content, line, column), filterName, filterArguments, line, column);
content = await execute(filterNode, executionContext);
}
outputBuffer.echo(content);
});
};
export const executeApplyNodeSynchronously: TwingSynchronousNodeExecutor<TwingApplyNode> = (node, executionContext) => {
const {outputBuffer, nodeExecutor: execute} = executionContext;
const {body, filters} = node.children;

@ -1,15 +1,4 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
export const executeBaseNode: TwingNodeExecutor = async (node, executionContext) => {
const output: Array<any> = [];
const {nodeExecutor: execute} = executionContext;
for (const [, child] of Object.entries(node.children)) {
output.push(await execute(child, executionContext));
}
return output;
};
import type {TwingSynchronousNodeExecutor} from "../node-executor";
export const executeBaseNodeSynchronously: TwingSynchronousNodeExecutor = (node, executionContext) => {
const output: Array<any> = [];

@ -1,25 +1,6 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingBlockReferenceNode} from "../node/block-reference";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../helpers/traceable-method";
export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNode> = (node, executionContext) => {
const {
template,
context
} = executionContext;
const {name} = node.attributes;
const displayBlock = getTraceableMethod(template.displayBlock, node, template.source);
return displayBlock(
{
...executionContext,
context: context.clone()
},
name,
true,
);
};
import {getSynchronousTraceableMethod} from "../helpers/traceable-method";
export const executeBlockReferenceNodeSynchronously: TwingSynchronousNodeExecutor<TwingBlockReferenceNode> = (node, executionContext) => {
const {

@ -1,39 +1,8 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingCheckSecurityNode} from "../node/check-security";
import type {TwingNode} from "../node";
import {createRuntimeError} from "../error/runtime";
export const executeCheckSecurityNode: TwingNodeExecutor<TwingCheckSecurityNode> = (node, executionContext) => {
const {template, environment, sandboxed} = executionContext;
const {usedTags, usedFunctions, usedFilters} = node.attributes;
if (sandboxed) {
const issue = environment.sandboxPolicy.checkSecurity(
[...usedTags.keys()],
[...usedFilters.keys()],
[...usedFunctions.keys()]
);
if (issue !== null) {
const {type, token} = issue;
let node: TwingNode;
if (type === "tag") {
node = usedTags.get(token)!;
} else if (type === "filter") {
node = usedFilters.get(token)!
} else {
node = usedFunctions.get(token)!;
}
throw createRuntimeError(issue.message, node, template.source);
}
}
return Promise.resolve();
};
export const executeCheckSecurityNodeSynchronously: TwingSynchronousNodeExecutor<TwingCheckSecurityNode> = (node, executionContext) => {
const {template, environment, sandboxed} = executionContext;
const {usedTags, usedFunctions, usedFilters} = node.attributes;

@ -1,33 +1,6 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingCheckToStringNode} from "../node/check-to-string";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../helpers/traceable-method";
export const executeCheckToStringNode: TwingNodeExecutor<TwingCheckToStringNode> = (node, executionContext) => {
const {template, environment, nodeExecutor: execute, sandboxed} = executionContext;
const {value: valueNode} = node.children;
const {sandboxPolicy} = environment;
return execute(valueNode, executionContext)
.then((value) => {
if (sandboxed) {
const assertToStringAllowed = getTraceableMethod((value: any) => {
if ((value !== null) && (typeof value === 'object')) {
try {
sandboxPolicy.checkMethodAllowed(value, 'toString');
} catch (error) {
return Promise.reject(error);
}
}
return Promise.resolve(value);
}, valueNode, template.source)
return assertToStringAllowed(value);
}
return value;
});
};
import {getSynchronousTraceableMethod} from "../helpers/traceable-method";
export const executeCheckToStringNodeSynchronously: TwingSynchronousNodeExecutor<TwingCheckToStringNode> = (node, executionContext) => {
const {template, environment, nodeExecutor: execute, sandboxed} = executionContext;

@ -1,10 +1,6 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingCommentNode} from "../node/comment";
export const executeCommentNode: TwingNodeExecutor<TwingCommentNode> = () => {
return Promise.resolve();
};
export const executeCommentNodeSynchronously: TwingSynchronousNodeExecutor<TwingCommentNode> = () => {
return;
};

@ -1,10 +1,6 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingSynchronousNodeExecutor} from "../node-executor";
import type {TwingConstantNode} from "../node/expression/constant";
export const executeConstantNode: TwingNodeExecutor<TwingConstantNode> = (node) => {
return Promise.resolve(node.attributes.value);
};
export const executeConstantNodeSynchronously: TwingSynchronousNodeExecutor<TwingConstantNode> = (node) => {
return node.attributes.value;
};

@ -1,16 +1,6 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingDeprecatedNode} from "../node/deprecated";
export const executeDeprecatedNode: TwingNodeExecutor<TwingDeprecatedNode> = (node, executionContext) => {
const {template, nodeExecutor: execute} = executionContext;
const {message} = node.children;
return execute(message, executionContext)
.then((message) => {
console.warn(`${message} ("${template.name}" at line ${node.line}, column ${node.column})`);
});
};
export const executeDeprecatedNodeSynchronously: TwingSynchronousNodeExecutor<TwingDeprecatedNode> = (node, executionContext) => {
const {template, nodeExecutor: execute} = executionContext;
const {message: messageNode} = node.children;

@ -1,10 +1,6 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingSynchronousNodeExecutor} from "../node-executor";
import {TwingDoNode} from "../node/do";
export const executeDoNode: TwingNodeExecutor<TwingDoNode> = (node, executionContext) => {
return executionContext.nodeExecutor(node.children.body, executionContext);
};
export const executeDoNodeSynchronously: TwingSynchronousNodeExecutor<TwingDoNode> = (node, executionContext) => {
return executionContext.nodeExecutor(node.children.body, executionContext);
};

@ -1,26 +1,7 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import {type TwingBaseArrayNode} from "../../node/expression/array";
import type {TwingNode} from "../../node";
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
import {getValues} from "../../context";
export const executeArrayNode: TwingNodeExecutor<TwingBaseArrayNode<any>> = async (baseNode, executionContext) => {
const {nodeExecutor: execute} = executionContext;
const keyValuePairs = getKeyValuePairs(baseNode);
const array: Array<any> = [];
for (const {value: valueNode} of keyValuePairs) {
const value = await execute(valueNode, executionContext);
if ((valueNode as TwingNode).type === "spread") {
array.push(...value);
} else {
array.push(value);
}
}
return array;
};
export const executeArrayNodeSynchronously: TwingSynchronousNodeExecutor<TwingBaseArrayNode<any>> = (baseNode, executionContext) => {
const {nodeExecutor: execute} = executionContext;
@ -30,10 +11,8 @@ export const executeArrayNodeSynchronously: TwingSynchronousNodeExecutor<TwingBa
for (const {value: valueNode} of keyValuePairs) {
const value = execute(valueNode, executionContext);
if ((valueNode as TwingNode).type === "spread") {
const values = getValues(value);
array.push(...values);
if ((valueNode as TwingNode).type === "spread") {
array.push(...Object.values(value));
} else {
array.push(value);
}

@ -1,26 +1,6 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingArrowFunctionNode} from "../../node/expression/arrow-function";
export const executeArrowFunctionNode: TwingNodeExecutor<TwingArrowFunctionNode> = (node, executionContext) => {
const {context, nodeExecutor: execute} = executionContext;
const {body, names} = node.children;
const assignmentNodes = Object.values(names.children);
return Promise.resolve((...functionArgs: Array<any>): Promise<any> => {
let index = 0;
for (const assignmentNode of assignmentNodes) {
const {name} = assignmentNode.attributes;
context.set(name, functionArgs[index]);
index++;
}
return execute(body, executionContext);
});
};
export const executeArrowFunctionNodeSynchronously: TwingSynchronousNodeExecutor<TwingArrowFunctionNode> = (node, executionContext) => {
const {context, nodeExecutor: execute} = executionContext;
const {body, names} = node.children;

@ -1,10 +1,6 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingAssignmentNode} from "../../node/expression/assignment";
export const executeAssignmentNode: TwingNodeExecutor<TwingAssignmentNode> = (node) => {
return Promise.resolve(node.attributes.name);
};
export const executeAssignmentNodeSynchronously: TwingSynchronousNodeExecutor<TwingAssignmentNode> = (node) => {
return node.attributes.name;
};

@ -1,33 +1,7 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingAttributeAccessorNode} from "../../node/expression/attribute-accessor";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../../helpers/traceable-method";
import {getAttribute, getAttributeSynchronously} from "../../helpers/get-attribute";
export const executeAttributeAccessorNode: TwingNodeExecutor<TwingAttributeAccessorNode> = (node, executionContext) => {
const {template, sandboxed, environment, nodeExecutor: execute, strict} = executionContext;
const {target, attribute, arguments: methodArguments} = node.children;
const {type, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
return Promise.all([
execute(target, executionContext),
execute(attribute, executionContext),
execute(methodArguments, executionContext)
]).then(([target, attribute, methodArguments]) => {
const traceableGetAttribute = getTraceableMethod(getAttribute, node, template.source);
return traceableGetAttribute(
environment,
target,
attribute,
methodArguments,
type,
shouldTestExistence,
shouldIgnoreStrictCheck || null,
sandboxed,
strict
)
})
};
import {getSynchronousTraceableMethod} from "../../helpers/traceable-method";
import {getAttributeSynchronously} from "../../helpers/get-attribute";
export const executeAttributeAccessorNodeSynchronously: TwingSynchronousNodeExecutor<TwingAttributeAccessorNode> = (node, executionContext) => {
const {template, sandboxed, environment, nodeExecutor: execute, strict} = executionContext;

@ -1,169 +1,13 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingBaseBinaryNode} from "../../node/expression/binary";
import {compare} from "../../helpers/compare";
import {concatenate} from "../../helpers/concatenate";
import {every, everySynchronously, isAMapLike, some, someSynchronously} from "../../helpers/map-like";
import {everySynchronously, isAMapLike, someSynchronously} from "../../helpers/map-like";
import {isIn} from "../../helpers/is-in";
import {parseRegularExpression} from "../../helpers/parse-regular-expression";
import {createRange} from "../../helpers/create-range";
import {createRuntimeError} from "../../error/runtime";
export const executeBinaryNode: TwingNodeExecutor<TwingBaseBinaryNode<any>> = async (node, executionContext) => {
const {left, right} = node.children;
const {nodeExecutor: execute, template} = executionContext;
switch (node.type) {
case "add": {
return await execute(left, executionContext) + await execute(right, executionContext);
}
case "and": {
return !!(await execute(left, executionContext) && await execute(right, executionContext));
}
case "bitwise_and": {
return await execute(left, executionContext) & await execute(right, executionContext);
}
case "bitwise_or": {
return await execute(left, executionContext) | await execute(right, executionContext);
}
case "bitwise_xor": {
return await execute(left, executionContext) ^ await execute(right, executionContext);
}
case "concatenate": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
return concatenate(leftValue, rightValue);
}
case "divide": {
return await execute(left, executionContext) / await execute(right, executionContext);
}
case "divide_and_floor": {
return Math.floor(await execute(left, executionContext) / await execute(right, executionContext));
}
case "ends_with": {
const leftValue = await execute(left, executionContext);
if (typeof leftValue !== "string") {
return false;
}
const rightValue = await execute(right, executionContext);
if (typeof rightValue !== "string") {
return false;
}
return rightValue.length < 1 || leftValue.endsWith(rightValue);
}
case "has_every": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
if (typeof rightValue !== "function") {
return Promise.resolve(true);
}
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
return Promise.resolve(true);
}
return every(leftValue, rightValue);
}
case "has_some": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
if (typeof rightValue !== "function") {
return Promise.resolve(false);
}
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
return Promise.resolve(false);
}
return some(leftValue, rightValue);
}
case "is_equal_to": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
return compare(leftValue, rightValue);
}
case "is_greater_than": {
return await execute(left, executionContext) > await execute(right, executionContext);
}
case "is_greater_than_or_equal_to": {
return await execute(left, executionContext) >= await execute(right, executionContext);
}
case "is_in": {
return isIn(await execute(left, executionContext), await execute(right, executionContext));
}
case "is_less_than": {
return await execute(left, executionContext) < await execute(right, executionContext);
}
case "is_less_than_or_equal_to": {
return await execute(left, executionContext) <= await execute(right, executionContext);
}
case "is_not_equal_to": {
return Promise.resolve(!compare(await execute(left, executionContext), await execute(right, executionContext)))
}
case "is_not_in": {
return Promise.resolve(!isIn(await execute(left, executionContext), await execute(right, executionContext)))
}
case "matches": {
return parseRegularExpression(
await execute(right, executionContext)
).test(
await execute(left, executionContext)
);
}
case "modulo": {
return await execute(left, executionContext) % await execute(right, executionContext);
}
case "multiply": {
return await execute(left, executionContext) * await execute(right, executionContext);
}
case "or": {
return !!(await execute(left, executionContext) || await execute(right, executionContext));
}
case "power": {
return Math.pow(await execute(left, executionContext), await execute(right, executionContext));
}
case "range": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
return createRange(leftValue, rightValue, 1);
}
case "spaceship": {
const leftValue = await execute(left, executionContext);
const rightValue = await execute(right, executionContext);
return compare(leftValue, rightValue) ? 0 : (leftValue < rightValue ? -1 : 1);
}
case "starts_with": {
const leftValue = await execute(left, executionContext);
if (typeof leftValue !== "string") {
return false;
}
const rightValue = await execute(right, executionContext);
if (typeof rightValue !== "string") {
return false;
}
return rightValue.length < 1 || leftValue.startsWith(rightValue);
}
case "subtract": {
return await execute(left, executionContext) - await execute(right, executionContext);
}
}
return Promise.reject(createRuntimeError(`Unrecognized binary node of type "${node.type}"`, node, template.source));
};
export const executeBinaryNodeSynchronously: TwingSynchronousNodeExecutor<TwingBaseBinaryNode<any>> = (node, executionContext): boolean | number | string | Map<number, any> => {
const {left, right} = node.children;
const {nodeExecutor: execute, template} = executionContext;

@ -1,63 +1,7 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingBlockFunctionNode} from "../../node/expression/block-function";
import {TwingSynchronousTemplate, TwingTemplate} from "../../template";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../../helpers/traceable-method";
export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = async (node, executionContext) => {
const {
template,
context,
nodeExecutor: execute,
blocks,
outputBuffer
} = executionContext;
const {template: templateNode, name: blockNameNode} = node.children;
const blockName = await execute(blockNameNode, executionContext);
let resolveTemplate: Promise<TwingTemplate>;
if (templateNode) {
const templateName = await execute(templateNode, executionContext);
const loadTemplate = getTraceableMethod(
template.loadTemplate,
templateNode,
template.source
);
resolveTemplate = loadTemplate(executionContext, templateName);
}
else {
resolveTemplate = Promise.resolve(template)
}
return resolveTemplate
.then<Promise<boolean | string>>((templateOfTheBlock) => {
if (node.attributes.shouldTestExistence) {
const hasBlock = getTraceableMethod(templateOfTheBlock.hasBlock, node, template.source);
return hasBlock({
...executionContext,
context: context.clone()
}, blockName, blocks);
}
else {
const displayBlock = getTraceableMethod(templateOfTheBlock.displayBlock, node, template.source);
let useBlocks = templateNode === undefined;
outputBuffer.start();
return displayBlock({
...executionContext,
context: context.clone()
}, blockName, useBlocks).then<string>(() => {
return outputBuffer.getAndClean();
});
}
});
};
import {TwingSynchronousTemplate} from "../../template";
import {getSynchronousTraceableMethod} from "../../helpers/traceable-method";
export const executeSynchronousBlockFunction: TwingSynchronousNodeExecutor<TwingBlockFunctionNode> = (node, executionContext) => {
const {

@ -1,8 +1,8 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingBaseCallNode} from "../../node/expression/call";
import {TwingCallableArgument, TwingCallableWrapper, TwingSynchronousCallableWrapper} from "../../callable-wrapper";
import {TwingCallableArgument, TwingSynchronousCallableWrapper} from "../../callable-wrapper";
import {createRuntimeError} from "../../error/runtime";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../../helpers/traceable-method";
import {getSynchronousTraceableMethod} from "../../helpers/traceable-method";
import {TwingArrayNode} from "../../node/expression/array";
import {TwingBaseNode} from "../../node";
import {createConstantNode, TwingConstantNode} from "../../node/expression/constant";
@ -11,7 +11,7 @@ import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
import {getTest} from "../../helpers/get-test";
import {getFunction} from "../../helpers/get-function";
import {getFilter} from "../../helpers/get-filter";
import type {TwingSynchronousTemplate, TwingTemplate} from "../../template";
import type {TwingSynchronousTemplate} from "../../template";
const array_merge = require('locutus/php/array/array_merge');
const snakeCase = require('snake-case');
@ -22,7 +22,7 @@ const normalizeName = (name: string) => {
const getArguments = (
node: TwingBaseCallNode<any>,
template: TwingTemplate | TwingSynchronousTemplate,
template: TwingSynchronousTemplate,
argumentsNode: TwingArrayNode,
acceptedArguments: Array<TwingCallableArgument>,
isVariadic: boolean
@ -127,64 +127,6 @@ const getArguments = (
return arguments_;
}
export const executeCallNode: TwingNodeExecutor<TwingBaseCallNode<any>> = async (node, executionContext) => {
const {type} = node;
const {template, environment, nodeExecutor: execute} = executionContext
const {operatorName} = node.attributes;
let callableWrapper: TwingCallableWrapper | null;
switch (type) {
case "filter":
callableWrapper = getFilter(environment.filters, operatorName);
break;
case "function":
callableWrapper = getFunction(environment.functions, operatorName);
break;
// for some reason, using `case "test"` makes the compiler assume that callableWrapper is used
// before it is assigned a value; this is probably a bug of the compiler
default:
callableWrapper = getTest(environment.tests, operatorName);
break;
}
if (callableWrapper === null) {
throw createRuntimeError(`Unknown ${type} "${operatorName}".`, node, template.source);
}
const {operand, arguments: callArguments} = node.children;
const argumentNodes = getArguments(
node,
template,
callArguments,
callableWrapper.acceptedArguments,
callableWrapper.isVariadic
);
const actualArguments: Array<any> = [];
actualArguments.push(...callableWrapper!.nativeArguments);
if (operand) {
actualArguments.push(await execute(operand, executionContext));
}
const providedArguments = await Promise.all([
...argumentNodes.map((node) => execute(node, executionContext))
]);
actualArguments.push(...providedArguments);
const traceableCallable = getTraceableMethod(callableWrapper.callable, node, template.source);
return traceableCallable(executionContext, ...actualArguments).then((value) => {
return value;
});
};
export const executeCallNodeSynchronously: TwingSynchronousNodeExecutor<TwingBaseCallNode<any>> = (node, executionContext) => {
const {type} = node;
const {template, environment, nodeExecutor: execute} = executionContext

@ -1,13 +1,6 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingBaseConditionalNode} from "../../node/expression/conditional";
export const executeConditionalNode: TwingNodeExecutor<TwingBaseConditionalNode<any>> = async (node, executionContext) => {
const {expr1, expr2, expr3} = node.children;
const {nodeExecutor: execute} = executionContext;
return (await execute(expr1, executionContext)) ? execute(expr2, executionContext) : execute(expr3, executionContext);
};
export const executeConditionalNodeSynchronously: TwingSynchronousNodeExecutor<TwingBaseConditionalNode<any>> = (node, executionContext) => {
const {expr1, expr2, expr3} = node.children;
const {nodeExecutor: execute} = executionContext;

@ -1,20 +1,7 @@
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingSynchronousNodeExecutor} from "../../node-executor";
import {TwingEscapeNode} from "../../node/expression/escape";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../../helpers/traceable-method";
import {escapeValue, escapeValueSynchronously} from "../../helpers/escape-value";
export const executeEscapeNode: TwingNodeExecutor<TwingEscapeNode> = (node, executionContext) => {
const {template, environment, nodeExecutor: execute} = executionContext;
const {strategy} = node.attributes;
const {body} = node.children;
return execute(body, executionContext)
.then((value) => {
const traceableEscape = getTraceableMethod(escapeValue, node, template.source);
return traceableEscape(template, environment, value, strategy, null);
});
};
import {getSynchronousTraceableMethod} from "../../helpers/traceable-method";
import {escapeValueSynchronously} from "../../helpers/escape-value";
export const executeEscapeNodeSynchronously: TwingSynchronousNodeExecutor<TwingEscapeNode> = (node, executionContext) => {
const {template, environment, nodeExecutor: execute} = executionContext;

@ -1,31 +1,7 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingHashNode} from "../../node/expression/hash";
import type {TwingNode} from "../../node";
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
import {getEntries} from "../../context";
export const executeHashNode: TwingNodeExecutor<TwingHashNode> = async (node, executionContext) => {
const {nodeExecutor: execute} = executionContext;
const keyValuePairs = getKeyValuePairs(node);
const hash: Map<any, any> = new Map();
for (const {key: keyNode, value: valueNode} of keyValuePairs) {
const [key, value] = await Promise.all([
execute(keyNode, executionContext),
execute(valueNode, executionContext)
]);
if ((valueNode as TwingNode).type === "spread") {
for (const [valueKey, valueValue] of value as Map<any, any>) {
hash.set(valueKey, valueValue);
}
} else {
hash.set(key, value);
}
}
return hash;
};
export const executeHashNodeSynchronously: TwingSynchronousNodeExecutor<TwingHashNode> = (node, executionContext) => {
const {nodeExecutor: execute} = executionContext;
@ -37,7 +13,7 @@ export const executeHashNodeSynchronously: TwingSynchronousNodeExecutor<TwingHas
const value = execute(valueNode, executionContext);
if ((valueNode as TwingNode).type === "spread") {
for (const [valueKey, valueValue] of getEntries(value as Record<any, any>)) {
for (const [valueKey, valueValue] of Object.entries(value)) {
hash.set(valueKey, valueValue);
}
} else {

@ -1,63 +1,12 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import {
TwingSynchronousTemplate,
TwingSynchronousTemplateMacroHandler,
TwingTemplate,
TwingTemplateMacroHandler
} from "../../template";
import {createRuntimeError} from "../../error/runtime";
import type {TwingMethodCallNode} from "../../node/expression/method-call";
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
export const executeMethodCall: TwingNodeExecutor<TwingMethodCallNode> = async (node, executionContext) => {
const {template, aliases, nodeExecutor: execute} = executionContext;
const {methodName, shouldTestExistence} = node.attributes;
const {operand, arguments: methodArguments} = node.children;
if (shouldTestExistence) {
return (aliases.get(operand.attributes.name) as TwingTemplate).hasMacro(methodName);
} else {
const keyValuePairs = getKeyValuePairs(methodArguments);
const macroArguments: Array<any> = [];
for (const {value: valueNode} of keyValuePairs) {
const value = await execute(valueNode, executionContext);
macroArguments.push(value);
}
// by nature, the alias exists - the parser only creates a method call node when the name _is_ an alias.
const macroTemplate = aliases.get(operand.attributes.name)!;
const getHandler = (template: TwingTemplate): Promise<TwingTemplateMacroHandler | null> => {
const macroHandler = template.macroHandlers.get(methodName);
if (macroHandler) {
return Promise.resolve(macroHandler);
} else {
return template.getParent(executionContext)
.then((parent) => {
if (parent) {
return getHandler(parent);
} else {
return null;
}
});
}
};
return getHandler(macroTemplate)
.then((handler) => {
if (handler) {
return handler(executionContext, ...macroArguments);
} else {
throw createRuntimeError(`Macro "${methodName}" is not defined in template "${macroTemplate.name}".`, node, template.source);
}
});
}
};
export const executeMethodCallSynchronously: TwingSynchronousNodeExecutor<TwingMethodCallNode> = (node, executionContext) => {
const {template, aliases, nodeExecutor: execute} = executionContext;
const {methodName, shouldTestExistence} = node.attributes;

@ -1,16 +1,6 @@
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingSynchronousNodeExecutor} from "../../node-executor";
import type {TwingParentFunctionNode} from "../../node/expression/parent-function";
import {getSynchronousTraceableMethod, getTraceableMethod} from "../../helpers/traceable-method";
export const executeParentFunction: TwingNodeExecutor<TwingParentFunctionNode> = (node, executionContext) => {
const {template, outputBuffer} = executionContext;
const {name} = node.attributes;
const displayParentBlock = getTraceableMethod(template.displayParentBlock, node, template.source);
outputBuffer.start();
return displayParentBlock(executionContext, name).then(() => outputBuffer.getAndClean());
};
import {getSynchronousTraceableMethod} from "../../helpers/traceable-method";
export const executeParentFunctionSynchronously: TwingSynchronousNodeExecutor<TwingParentFunctionNode> = (node, executionContext) => {
const {template, outputBuffer} = executionContext;

Some files were not shown because too many files have changed in this diff Show More