mirror of
https://gitlab.com/nightlycommit/twing.git
synced 2025-01-18 08:46:50 +02:00
Merge branch 'issue-465' into 'main'
Resolve issue #465 Closes #465 See merge request nightlycommit/twing!611
This commit is contained in:
commit
7e02a05e4d
@ -18,12 +18,12 @@ export {createTemplateLoadingError} from "./lib/error/loader";
|
||||
export type {
|
||||
TwingFilesystemLoader, TwingFilesystemLoaderFilesystem, TwingFilesystemLoaderFilesystemStats
|
||||
} from "./lib/loader/filesystem";
|
||||
export type {TwingArrayLoader} from "./lib/loader/array";
|
||||
export type {TwingArrayLoader, TwingSynchronousArrayLoader} from "./lib/loader/array";
|
||||
export type {TwingChainLoader} from "./lib/loader/chain";
|
||||
export type {TwingLoader} from "./lib/loader";
|
||||
export type {TwingLoader, TwingSynchronousLoader} from "./lib/loader";
|
||||
|
||||
export {createFilesystemLoader} from "./lib/loader/filesystem";
|
||||
export {createArrayLoader} from "./lib/loader/array";
|
||||
export {createFilesystemLoader, createSynchronousFilesystemLoader} from "./lib/loader/filesystem";
|
||||
export {createArrayLoader, createSynchronousArrayLoader} from "./lib/loader/array";
|
||||
export {createChainLoader} from "./lib/loader/chain";
|
||||
|
||||
// markup
|
||||
@ -233,7 +233,7 @@ export {createEmbedNode} from "./lib/node/include/embed";
|
||||
export {createIncludeNode} from "./lib/node/include/include";
|
||||
|
||||
// node executors
|
||||
export {executeNode, type TwingNodeExecutor} from "./lib/node-executor";
|
||||
export {executeNode, executeNodeSynchronously, type TwingNodeExecutor, type TwingSynchronousNodeExecutor} from "./lib/node-executor";
|
||||
|
||||
// tag handlers
|
||||
export type {TwingTagHandler, TwingTokenParser} from "./lib/tag-handler";
|
||||
@ -299,7 +299,7 @@ export interface TwingTemplate {
|
||||
render: import("./lib/template").TwingTemplate["render"];
|
||||
}
|
||||
|
||||
export {createEnvironment} from "./lib/environment";
|
||||
export {createEnvironment, createSynchronousEnvironment} from "./lib/environment";
|
||||
export {createExtensionSet} from "./lib/extension-set";
|
||||
export {createFilter} from "./lib/filter";
|
||||
export {createFunction} from "./lib/function";
|
||||
|
@ -27,3 +27,31 @@ export interface TwingCache {
|
||||
*/
|
||||
getTimestamp: (key: string) => Promise<number>;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousCache {
|
||||
/**
|
||||
* Writes a template AST to the cache.
|
||||
*
|
||||
* @param key The cache key
|
||||
* @param content The template AST
|
||||
*/
|
||||
write: (key: string, content: TwingTemplateNode) => void;
|
||||
|
||||
/**
|
||||
* Loads a template AST from the cache.
|
||||
*
|
||||
* @param key The cache key
|
||||
*
|
||||
* @returns The template AST
|
||||
*/
|
||||
load: (key: string) => TwingTemplateNode | null;
|
||||
|
||||
/**
|
||||
* Returns the modification timestamp of a key.
|
||||
*
|
||||
* @param {string} key The cache key
|
||||
*
|
||||
* @returns The modification timestamp
|
||||
*/
|
||||
getTimestamp: (key: string) => number;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type {TwingExecutionContext} from "./execution-context";
|
||||
import {TwingExecutionContext, 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 = {
|
||||
name: string;
|
||||
@ -29,6 +30,10 @@ export interface TwingCallableWrapper {
|
||||
nativeArguments: Array<string>;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousCallableWrapper extends Omit<TwingCallableWrapper, "callable"> {
|
||||
readonly callable: TwingSynchronousCallable;
|
||||
}
|
||||
|
||||
export const createCallableWrapper = (
|
||||
name: string,
|
||||
callable: TwingCallable,
|
||||
@ -37,7 +42,48 @@ export const createCallableWrapper = (
|
||||
): TwingCallableWrapper => {
|
||||
let nativeArguments: Array<string> = [];
|
||||
|
||||
const callableWrapper: TwingCallableWrapper = {
|
||||
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,
|
||||
acceptedArguments: Array<TwingCallableArgument>,
|
||||
options: TwingCallableWrapperOptions
|
||||
): TwingSynchronousCallableWrapper => {
|
||||
let nativeArguments: Array<string> = [];
|
||||
|
||||
const callableWrapper = {
|
||||
get callable() {
|
||||
return callable;
|
||||
},
|
||||
|
@ -1,13 +1,13 @@
|
||||
export interface TwingContext<K, V> {
|
||||
export interface TwingContext<K extends string, V> {
|
||||
readonly size: number;
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[K, V]>;
|
||||
[Symbol.iterator](): IterableIterator<[string, V]>;
|
||||
|
||||
clone(): TwingContext<K, V>;
|
||||
|
||||
delete(key: K): boolean;
|
||||
|
||||
entries(): IterableIterator<[K, V]>;
|
||||
entries(): IterableIterator<[string, V]>;
|
||||
|
||||
get(key: K): V | undefined;
|
||||
|
||||
@ -61,3 +61,13 @@ export const createContext = <K extends string, V>(
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
export type TwingContext2 = Map<string, any>;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {TwingTagHandler} from "./tag-handler";
|
||||
import {TwingNodeVisitor} from "./node-visitor";
|
||||
import {createExtensionSet} from "./extension-set";
|
||||
import {TwingFilter} from "./filter";
|
||||
import {TwingFilter, TwingSynchronousFilter} from "./filter";
|
||||
import {createParser, TwingParser, TwingParserOptions} from "./parser";
|
||||
import {TwingLoader} from "./loader";
|
||||
import {TwingTest} from "./test";
|
||||
import {TwingFunction} from "./function";
|
||||
import {TwingLoader, TwingSynchronousLoader} from "./loader";
|
||||
import {TwingSynchronousTest, TwingTest} from "./test";
|
||||
import {TwingFunction, TwingSynchronousFunction} from "./function";
|
||||
import {TwingOperator} from "./operator";
|
||||
import {TwingEscapingStrategy, TwingEscapingStrategyHandler} from "./escaping-strategy";
|
||||
import {createHtmlEscapingStrategyHandler} from "./escaping-stragegy/html";
|
||||
@ -15,19 +15,19 @@ 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} from "./extension";
|
||||
import {TwingExtension, 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 {TwingTemplate} from "./template";
|
||||
import {TwingSynchronousTemplate, TwingTemplate} from "./template";
|
||||
import {Settings as DateTimeSettings} from "luxon";
|
||||
import {createLexer, type TwingLexer} from "./lexer";
|
||||
import {TwingCache} from "./cache";
|
||||
import {createCoreExtension} from "./extension/core";
|
||||
import {TwingCache, TwingSynchronousCache} from "./cache";
|
||||
import {createCoreExtension, createSynchronousCoreExtension} from "./extension/core";
|
||||
import {createAutoEscapeNode, createTemplateLoadingError, type TwingContext} from "../lib";
|
||||
import {createTemplateLoader} from "./template-loader";
|
||||
import {createContext} from "./context";
|
||||
import {createSynchronousTemplateLoader, createTemplateLoader} from "./template-loader";
|
||||
import {createContext, TwingContext2} from "./context";
|
||||
import {iterableToMap} from "./helpers/iterator-to-map";
|
||||
|
||||
export type TwingNumberFormat = {
|
||||
@ -48,7 +48,7 @@ export type TwingEnvironmentOptions = {
|
||||
* The persistent cache instance.
|
||||
*/
|
||||
cache?: TwingCache;
|
||||
|
||||
|
||||
/**
|
||||
* The default charset. Defaults to "UTF-8".
|
||||
*/
|
||||
@ -62,6 +62,13 @@ export type TwingEnvironmentOptions = {
|
||||
timezone?: string;
|
||||
};
|
||||
|
||||
export type TwingSynchronousEnvironmentOptions = Omit<TwingEnvironmentOptions, "cache"> & {
|
||||
/**
|
||||
* The persistent cache instance.
|
||||
*/
|
||||
cache?: TwingSynchronousCache;
|
||||
};
|
||||
|
||||
export interface TwingEnvironment {
|
||||
readonly cache: TwingCache | null;
|
||||
readonly charset: string;
|
||||
@ -72,7 +79,7 @@ export interface TwingEnvironment {
|
||||
readonly filters: Map<string, TwingFilter>;
|
||||
readonly functions: Map<string, TwingFunction>;
|
||||
readonly globals: TwingContext<string, any>;
|
||||
readonly loader: TwingLoader;
|
||||
readonly loader: TwingLoader | TwingSynchronousLoader;
|
||||
readonly sandboxPolicy: TwingSandboxSecurityPolicy;
|
||||
readonly tests: Map<string, TwingTest>;
|
||||
readonly timezone: string;
|
||||
@ -149,6 +156,93 @@ export interface TwingEnvironment {
|
||||
tokenize(source: TwingSource): TwingTokenStream;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousEnvironment {
|
||||
readonly cache: TwingSynchronousCache | null;
|
||||
readonly charset: string;
|
||||
readonly dateFormat: string;
|
||||
readonly dateIntervalFormat: string;
|
||||
readonly escapingStrategyHandlers: Record<TwingEscapingStrategy, TwingEscapingStrategyHandler>;
|
||||
readonly numberFormat: TwingNumberFormat;
|
||||
readonly filters: Map<string, TwingSynchronousFilter>;
|
||||
readonly functions: Map<string, TwingSynchronousFunction>;
|
||||
readonly globals: TwingContext2;
|
||||
readonly loader: TwingSynchronousLoader;
|
||||
readonly sandboxPolicy: TwingSandboxSecurityPolicy;
|
||||
readonly tests: Map<string, TwingSynchronousTest>;
|
||||
readonly timezone: string;
|
||||
|
||||
/**
|
||||
* Convenient method...
|
||||
*
|
||||
* @param extension
|
||||
*/
|
||||
addExtension(extension: TwingSynchronousExtension): void;
|
||||
|
||||
addFilter(filter: TwingSynchronousFilter): void;
|
||||
|
||||
addFunction(aFunction: TwingSynchronousFunction): void;
|
||||
|
||||
addNodeVisitor(visitor: TwingNodeVisitor): void;
|
||||
|
||||
addOperator(operator: TwingOperator): void;
|
||||
|
||||
addTagHandler(parser: TwingTagHandler): void;
|
||||
|
||||
addTest(test: TwingSynchronousTest): 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): TwingSynchronousTemplate;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}): 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;
|
||||
}): {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link TwingEnvironment} backed by the passed loader.
|
||||
*
|
||||
@ -156,7 +250,7 @@ export interface TwingEnvironment {
|
||||
* @param options
|
||||
*/
|
||||
export const createEnvironment = (
|
||||
loader: TwingLoader,
|
||||
loader: TwingLoader | TwingSynchronousLoader,
|
||||
options?: TwingEnvironmentOptions
|
||||
): TwingEnvironment => {
|
||||
const cssEscapingStrategy = createCssEscapingStrategyHandler();
|
||||
@ -172,7 +266,7 @@ export const createEnvironment = (
|
||||
js: jsEscapingStrategy,
|
||||
url: urlEscapingStrategy
|
||||
};
|
||||
const extensionSet = createExtensionSet();
|
||||
const extensionSet = createExtensionSet<TwingExtension>();
|
||||
|
||||
extensionSet.addExtension(createCoreExtension());
|
||||
|
||||
@ -299,7 +393,7 @@ export const createEnvironment = (
|
||||
},
|
||||
renderWithSourceMap: (name, context, options) => {
|
||||
const sourceMapRuntime = createSourceMapRuntime();
|
||||
|
||||
|
||||
return environment.loadTemplate(name)
|
||||
.then((template) => {
|
||||
return template.render(environment, context, {
|
||||
@ -335,3 +429,181 @@ export const createEnvironment = (
|
||||
|
||||
return environment;
|
||||
};
|
||||
|
||||
export const createSynchronousEnvironment = (
|
||||
loader: TwingSynchronousLoader,
|
||||
options?: TwingSynchronousEnvironmentOptions
|
||||
): TwingSynchronousEnvironment => {
|
||||
const cssEscapingStrategy = createCssEscapingStrategyHandler();
|
||||
const htmlEscapingStrategy = createHtmlEscapingStrategyHandler();
|
||||
const htmlAttributeEscapingStrategy = createHtmlAttributeEscapingStrategyHandler();
|
||||
const jsEscapingStrategy = createJsEscapingStrategyHandler();
|
||||
const urlEscapingStrategy = createUrlEscapingStrategyHandler();
|
||||
|
||||
const escapingStrategyHandlers: Record<TwingEscapingStrategy, TwingEscapingStrategyHandler> = {
|
||||
css: cssEscapingStrategy,
|
||||
html: htmlEscapingStrategy,
|
||||
html_attr: htmlAttributeEscapingStrategy,
|
||||
js: jsEscapingStrategy,
|
||||
url: urlEscapingStrategy
|
||||
};
|
||||
const extensionSet = createExtensionSet<TwingSynchronousExtension>();
|
||||
|
||||
extensionSet.addExtension(createSynchronousCoreExtension());
|
||||
|
||||
const cache: TwingSynchronousCache | null = options?.cache || null;
|
||||
const charset = options?.charset || 'UTF-8';
|
||||
const dateFormat = options?.dateFormat || 'F j, Y H:i';
|
||||
const dateIntervalFormat = options?.dateIntervalFormat || '%d days';
|
||||
const numberFormat: TwingNumberFormat = options?.numberFormat || {
|
||||
decimalPoint: '.',
|
||||
numberOfDecimals: 0,
|
||||
thousandSeparator: ','
|
||||
};
|
||||
const sandboxPolicy = options?.sandboxPolicy || createSandboxSecurityPolicy();
|
||||
const globals = new Map(Object.entries(options?.globals || {}));
|
||||
|
||||
let lexer: TwingLexer;
|
||||
let parser: TwingParser;
|
||||
|
||||
const environment: TwingSynchronousEnvironment = {
|
||||
get cache() {
|
||||
return cache;
|
||||
},
|
||||
get charset() {
|
||||
return charset;
|
||||
},
|
||||
get dateFormat() {
|
||||
return dateFormat;
|
||||
},
|
||||
get dateIntervalFormat() {
|
||||
return dateIntervalFormat;
|
||||
},
|
||||
get escapingStrategyHandlers() {
|
||||
return escapingStrategyHandlers;
|
||||
},
|
||||
get filters() {
|
||||
return extensionSet.filters;
|
||||
},
|
||||
get functions() {
|
||||
return extensionSet.functions;
|
||||
},
|
||||
get globals() {
|
||||
return globals;
|
||||
},
|
||||
get loader() {
|
||||
return loader;
|
||||
},
|
||||
get numberFormat() {
|
||||
return numberFormat;
|
||||
},
|
||||
get sandboxPolicy() {
|
||||
return sandboxPolicy;
|
||||
},
|
||||
get tests() {
|
||||
return extensionSet.tests;
|
||||
},
|
||||
get timezone() {
|
||||
return options?.timezone || DateTimeSettings.defaultZoneName
|
||||
},
|
||||
addExtension: extensionSet.addExtension,
|
||||
addFilter: extensionSet.addFilter,
|
||||
addFunction: extensionSet.addFunction,
|
||||
addNodeVisitor: extensionSet.addNodeVisitor,
|
||||
addOperator: extensionSet.addOperator,
|
||||
addTagHandler: extensionSet.addTagHandler,
|
||||
addTest: extensionSet.addTest,
|
||||
loadTemplate: (name, from = null) => {
|
||||
const templateLoader = createSynchronousTemplateLoader(environment);
|
||||
|
||||
const template = templateLoader(name, from);
|
||||
|
||||
if (template === null) {
|
||||
throw createTemplateLoadingError([name]);
|
||||
}
|
||||
|
||||
return template;
|
||||
},
|
||||
registerEscapingStrategy: (handler, name) => {
|
||||
escapingStrategyHandlers[name] = handler;
|
||||
},
|
||||
parse: (stream, parserOptions) => {
|
||||
if (!parser) {
|
||||
const visitors = extensionSet.nodeVisitors;
|
||||
|
||||
if (options?.autoEscapingStrategy) {
|
||||
const strategy = options.autoEscapingStrategy;
|
||||
|
||||
visitors.unshift({
|
||||
enterNode: (node) => {
|
||||
return node;
|
||||
},
|
||||
leaveNode: (node) => {
|
||||
if (node.type === "template") {
|
||||
node.children.body = createAutoEscapeNode(strategy, node.children.body, node.line, node.column);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
parser = createParser(
|
||||
extensionSet.unaryOperators,
|
||||
extensionSet.binaryOperators,
|
||||
extensionSet.tagHandlers,
|
||||
extensionSet.nodeVisitors,
|
||||
extensionSet.filters,
|
||||
extensionSet.functions,
|
||||
extensionSet.tests,
|
||||
parserOptions || options?.parserOptions || {
|
||||
strict: true,
|
||||
level: 3
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return parser.parse(stream);
|
||||
},
|
||||
render: (name, data, options) => {
|
||||
const template = environment.loadTemplate(name);
|
||||
const context: TwingContext2 = new Map(Object.entries(data));
|
||||
|
||||
return template.render(environment, context, options);
|
||||
},
|
||||
renderWithSourceMap: (name, data, options) => {
|
||||
const sourceMapRuntime = createSourceMapRuntime();
|
||||
|
||||
const context: TwingContext2 = new Map(Object.entries(data));
|
||||
const template = environment.loadTemplate(name);
|
||||
const output = template.render(environment, context, {
|
||||
...options,
|
||||
sourceMapRuntime
|
||||
});
|
||||
|
||||
const {sourceMap} = sourceMapRuntime;
|
||||
|
||||
return {
|
||||
data: output,
|
||||
sourceMap
|
||||
};
|
||||
},
|
||||
tokenize: (source: TwingSource): TwingTokenStream => {
|
||||
const level = options?.parserOptions?.level || 3;
|
||||
|
||||
if (!lexer) {
|
||||
lexer = createLexer(
|
||||
level,
|
||||
extensionSet.binaryOperators,
|
||||
extensionSet.unaryOperators
|
||||
);
|
||||
}
|
||||
|
||||
const stream = lexer.tokenizeSource(source);
|
||||
|
||||
return createTokenStream(stream.toAst(), stream.source);
|
||||
}
|
||||
};
|
||||
|
||||
return environment;
|
||||
};
|
||||
|
@ -1,10 +1,13 @@
|
||||
import type {TwingTemplate, TwingTemplateAliases, TwingTemplateBlockMap} from "./template";
|
||||
import type {TwingContext} from "./context";
|
||||
import type {TwingContext, TwingContext2} from "./context";
|
||||
import type {TwingOutputBuffer} from "./output-buffer";
|
||||
import type {TwingSourceMapRuntime} from "./source-map-runtime";
|
||||
import type {TwingEnvironment} from "./environment";
|
||||
import type {TwingEnvironment, TwingSynchronousEnvironment} from "./environment";
|
||||
import type {TwingNodeExecutor} from "./node-executor";
|
||||
import type {TwingTemplateLoader} from "./template-loader";
|
||||
import {TwingSynchronousNodeExecutor} from "./node-executor";
|
||||
import {TwingSynchronousTemplate, TwingSynchronousTemplateAliases, TwingSynchronousTemplateBlockMap} from "./template";
|
||||
import {TwingSynchronousTemplateLoader} from "./template-loader";
|
||||
|
||||
export type TwingExecutionContext = {
|
||||
aliases: TwingTemplateAliases;
|
||||
@ -19,3 +22,17 @@ export type TwingExecutionContext = {
|
||||
template: TwingTemplate;
|
||||
templateLoader: TwingTemplateLoader;
|
||||
};
|
||||
|
||||
export type TwingSynchronousExecutionContext = {
|
||||
aliases: TwingSynchronousTemplateAliases;
|
||||
blocks: TwingSynchronousTemplateBlockMap;
|
||||
context: TwingContext2;
|
||||
environment: TwingSynchronousEnvironment;
|
||||
nodeExecutor: TwingSynchronousNodeExecutor;
|
||||
outputBuffer: TwingOutputBuffer;
|
||||
sandboxed: boolean;
|
||||
sourceMapRuntime?: TwingSourceMapRuntime;
|
||||
strict: boolean;
|
||||
template: TwingSynchronousTemplate;
|
||||
templateLoader: TwingSynchronousTemplateLoader;
|
||||
};
|
||||
|
@ -1,25 +1,22 @@
|
||||
import {TwingTagHandler} from "./tag-handler";
|
||||
import {TwingFilter} from "./filter";
|
||||
import {TwingFunction} from "./function";
|
||||
import {TwingNodeVisitor} from "./node-visitor";
|
||||
import {TwingTest} from "./test";
|
||||
import {TwingOperator} from "./operator";
|
||||
import type {TwingExtension} from "./extension";
|
||||
import type {TwingExtension, TwingSynchronousExtension} from "./extension";
|
||||
|
||||
export interface TwingExtensionSet {
|
||||
export interface TwingExtensionSet<Extension extends TwingExtension | TwingSynchronousExtension> {
|
||||
readonly binaryOperators: Array<TwingOperator>;
|
||||
readonly filters: Map<string, TwingFilter>;
|
||||
readonly functions: Map<string, TwingFunction>;
|
||||
readonly filters: Map<string, Extension["filters"][number]>;
|
||||
readonly functions: Map<string, Extension["functions"][number]>;
|
||||
readonly nodeVisitors: Array<TwingNodeVisitor>;
|
||||
readonly tagHandlers: Array<TwingTagHandler>;
|
||||
readonly tests: Map<string, TwingTest>;
|
||||
readonly tests: Map<string, Extension["tests"][number]>;
|
||||
readonly unaryOperators: Array<TwingOperator>;
|
||||
|
||||
addExtension(extension: TwingExtension): void;
|
||||
addExtension(extension: Extension): void;
|
||||
|
||||
addFilter(filter: TwingFilter): void;
|
||||
addFilter(filter: Extension["filters"][number]): void;
|
||||
|
||||
addFunction(twingFunction: TwingFunction): void;
|
||||
addFunction(twingFunction: Extension["functions"][number]): void;
|
||||
|
||||
addNodeVisitor(visitor: TwingNodeVisitor): void;
|
||||
|
||||
@ -27,19 +24,19 @@ export interface TwingExtensionSet {
|
||||
|
||||
addTagHandler(tagHandler: TwingTagHandler): void;
|
||||
|
||||
addTest(test: TwingTest): void;
|
||||
addTest(test: Extension["tests"][number]): void;
|
||||
}
|
||||
|
||||
export const createExtensionSet = (): TwingExtensionSet => {
|
||||
export const createExtensionSet = <Extension extends TwingExtension | TwingSynchronousExtension> (): TwingExtensionSet<Extension> => {
|
||||
const binaryOperators: Array<TwingOperator> = [];
|
||||
const filters: Map<string, TwingFilter> = new Map();
|
||||
const functions: Map<string, TwingFunction> = new Map();
|
||||
const filters: Map<string, Extension["filters"][number]> = new Map();
|
||||
const functions: Map<string, Extension["functions"][number]> = new Map();
|
||||
const nodeVisitors: Array<TwingNodeVisitor> = [];
|
||||
const tagHandlers: Array<TwingTagHandler> = [];
|
||||
const tests: Map<string, TwingTest> = new Map();
|
||||
const tests: Map<string, Extension["tests"][number]> = new Map();
|
||||
const unaryOperators: Array<TwingOperator> = [];
|
||||
|
||||
const extensionSet: TwingExtensionSet = {
|
||||
const extensionSet: TwingExtensionSet<Extension> = {
|
||||
get binaryOperators() {
|
||||
return binaryOperators;
|
||||
},
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {TwingTagHandler} from "./tag-handler";
|
||||
import {TwingNodeVisitor} from "./node-visitor";
|
||||
import {TwingFilter} from "./filter";
|
||||
import {TwingFunction} from "./function";
|
||||
import {TwingTest} from "./test";
|
||||
import {TwingFilter, TwingSynchronousFilter} from "./filter";
|
||||
import {TwingFunction, TwingSynchronousFunction} from "./function";
|
||||
import {TwingSynchronousTest, TwingTest} from "./test";
|
||||
import {TwingOperator} from "./operator";
|
||||
|
||||
export interface TwingExtension {
|
||||
@ -48,3 +48,47 @@ export interface TwingExtension {
|
||||
*/
|
||||
readonly tests: Array<TwingTest>;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousExtension {
|
||||
/**
|
||||
* Returns a list of filters to add to the existing list.
|
||||
*
|
||||
* @return Array<TwingSynchronousFilter>
|
||||
*/
|
||||
readonly filters: Array<TwingSynchronousFilter>;
|
||||
|
||||
/**
|
||||
* Returns a list of functions to add to the existing list.
|
||||
*
|
||||
* @return Array<TwingSynchronousFunction>
|
||||
*/
|
||||
readonly functions: Array<TwingSynchronousFunction>;
|
||||
|
||||
/**
|
||||
* 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<TwingSynchronousTest>;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {TwingExtension} from "../extension";
|
||||
import type {TwingExtension, TwingSynchronousExtension} from "../extension";
|
||||
import {createAndNode} from "../node/expression/binary/and";
|
||||
import {createIsInNode} from "../node/expression/binary/is-in";
|
||||
import {createIsGreaterThanNode} from "../node/expression/binary/is-greater-than";
|
||||
@ -6,7 +6,7 @@ import {createIsLessThanNode} from "../node/expression/binary/is-less-than";
|
||||
import {createNotNode} from "../node/expression/unary/not";
|
||||
import {createNegativeNode} from "../node/expression/unary/negative";
|
||||
import {createPositiveNode} from "../node/expression/unary/positive";
|
||||
import {createFunction} from "../function";
|
||||
import {createFunction, createSynchronousFunction} from "../function";
|
||||
import {createConcatenateNode} from "../node/expression/binary/concatenate";
|
||||
import {createMultiplyNode} from "../node/expression/binary/multiply";
|
||||
import {createDivideNode} from "../node/expression/binary/divide";
|
||||
@ -27,77 +27,178 @@ import {createIsNotInNode} from "../node/expression/binary/is-not-in";
|
||||
import {createNullishCoalescingNode} from "../node/expression/nullish-coalescing";
|
||||
import {TwingBaseExpressionNode} from "../node/expression";
|
||||
import {createPowerNode} from "../node/expression/binary/power";
|
||||
import {createTest} from "../test";
|
||||
import {createSynchronousTest, createTest} from "../test";
|
||||
import {createMatchesNode} from "../node/expression/binary/matches";
|
||||
import {createStartsWithNode} from "../node/expression/binary/starts-with";
|
||||
import {createEndsWithNode} from "../node/expression/binary/ends-with";
|
||||
import {createFilter} from "../filter";
|
||||
import {createOperator} from "../operator";
|
||||
import {isEven} from "./core/tests/is-even";
|
||||
import {isOdd} from "./core/tests/is-odd";
|
||||
import {isSameAs} from "./core/tests/is-same-as";
|
||||
import {isNull} from "./core/tests/is-null";
|
||||
import {isDivisibleBy} from "./core/tests/is-divisible-by";
|
||||
import {min} from "./core/functions/min";
|
||||
import {max} from "./core/functions/max";
|
||||
import {date} from "./core/filters/date";
|
||||
import {dateModify} from "./core/filters/date-modify";
|
||||
import {format} from "./core/filters/format";
|
||||
import {replace} from "./core/filters/replace";
|
||||
import {numberFormat} from "./core/filters/number_format";
|
||||
import {abs} from "./core/filters/abs";
|
||||
import {url_encode} from "./core/filters/url_encode";
|
||||
import {jsonEncode} from "./core/filters/json-encode";
|
||||
import {convertEncoding} from "./core/filters/convert-encoding";
|
||||
import {title} from "./core/filters/title";
|
||||
import {capitalize} from "./core/filters/capitalize";
|
||||
import {upper} from "./core/filters/upper";
|
||||
import {lower} from "./core/filters/lower";
|
||||
import {striptags} from "./core/filters/striptags";
|
||||
import {trim} from "./core/filters/trim";
|
||||
import {nl2br} from "./core/filters/nl2br";
|
||||
import {raw} from "./core/filters/raw";
|
||||
import {join} from "./core/filters/join";
|
||||
import {split} from "./core/filters/split";
|
||||
import {sort} from "./core/filters/sort";
|
||||
import {merge as mergeFilter} from "./core/filters/merge";
|
||||
import {batch} from "./core/filters/batch";
|
||||
import {reverse as reverseFilter} from "./core/filters/reverse";
|
||||
import {length} from "./core/filters/length";
|
||||
import {slice as sliceFilter} from "./core/filters/slice";
|
||||
import {first as firstFilter} from "./core/filters/first";
|
||||
import {last} from "./core/filters/last";
|
||||
import {defaultFilter} from "./core/filters/default";
|
||||
import {escape} from "./core/filters/escape";
|
||||
import {round} from "./core/filters/round";
|
||||
import {include} from "./core/functions/include";
|
||||
import {keys} from "./core/filters/keys";
|
||||
import {spaceless} from "./core/filters/spaceless";
|
||||
import {column} from "./core/filters/column";
|
||||
import {filter} from "./core/filters/filter";
|
||||
import {map} from "./core/filters/map";
|
||||
import {reduce} from "./core/filters/reduce";
|
||||
import {range} from "./core/functions/range";
|
||||
import {constant} from "./core/functions/constant";
|
||||
import {cycle} from "./core/functions/cycle";
|
||||
import {random} from "./core/functions/random";
|
||||
import {source} from "./core/functions/source";
|
||||
import {templateFromString} from "./core/functions/template-from-string";
|
||||
import {dump} from "./core/functions/dump";
|
||||
import {isEmpty} from "./core/tests/is-empty";
|
||||
import {isIterable} from "./core/tests/is-iterable";
|
||||
import {date as dateFunction} from "./core/functions/date";
|
||||
import {isDefined} from "./core/tests/is-defined";
|
||||
import {isConstant} from "./core/tests/is-constant";
|
||||
import {createFilter, createSynchronousFilter} from "../filter";
|
||||
import {createOperator, TwingOperator} from "../operator";
|
||||
import {isEven, isEvenSynchronously} from "./core/tests/is-even";
|
||||
import {isOdd, isOddSynchronously} from "./core/tests/is-odd";
|
||||
import {isSameAs, isSameAsSynchronously} from "./core/tests/is-same-as";
|
||||
import {isNull, isNullSynchronously} from "./core/tests/is-null";
|
||||
import {isDivisibleBy, isDivisibleBySynchronously} from "./core/tests/is-divisible-by";
|
||||
import {min, minSynchronously} from "./core/functions/min";
|
||||
import {max, maxSynchronously} from "./core/functions/max";
|
||||
import {date, dateFilterSynchronously} from "./core/filters/date";
|
||||
import {dateModify, dateModifySynchronously} from "./core/filters/date-modify";
|
||||
import {format, formatSynchronously} from "./core/filters/format";
|
||||
import {replace, replaceSynchronously} from "./core/filters/replace";
|
||||
import {numberFormat, numberFormatSynchronously} from "./core/filters/number_format";
|
||||
import {abs, absSynchronously} from "./core/filters/abs";
|
||||
import {url_encode, urlEncodeSynchronously} from "./core/filters/url_encode";
|
||||
import {jsonEncode, jsonEncodeSynchronously} from "./core/filters/json-encode";
|
||||
import {convertEncoding, convertEncodingSynchronously} from "./core/filters/convert-encoding";
|
||||
import {title, titleSynchronously} from "./core/filters/title";
|
||||
import {capitalize, capitalizeSynchronously} from "./core/filters/capitalize";
|
||||
import {upper, upperSynchronously} from "./core/filters/upper";
|
||||
import {lower, lowerSynchronously} from "./core/filters/lower";
|
||||
import {striptags, striptagsSynchronously} from "./core/filters/striptags";
|
||||
import {trim, trimSynchronously} from "./core/filters/trim";
|
||||
import {nl2br, nl2brSynchronously} from "./core/filters/nl2br";
|
||||
import {raw, rawSynchronously} from "./core/filters/raw";
|
||||
import {join, joinSynchronously} from "./core/filters/join";
|
||||
import {split, splitSynchronously} from "./core/filters/split";
|
||||
import {sort, sortSynchronously} from "./core/filters/sort";
|
||||
import {merge as mergeFilter, mergeSynchronously} from "./core/filters/merge";
|
||||
import {batch, batchSynchronously} from "./core/filters/batch";
|
||||
import {reverse as reverseFilter, reverseSynchronously} from "./core/filters/reverse";
|
||||
import {length, lengthSynchronously} from "./core/filters/length";
|
||||
import {slice as sliceFilter, sliceSynchronously} from "./core/filters/slice";
|
||||
import {first as firstFilter, firstSynchronously} from "./core/filters/first";
|
||||
import {last, lastSynchronously} from "./core/filters/last";
|
||||
import {defaultFilter, defaultFilterSynchronously} from "./core/filters/default";
|
||||
import {escape, escapeSynchronously} from "./core/filters/escape";
|
||||
import {round, roundSynchronously} from "./core/filters/round";
|
||||
import {include, includeSynchronously} from "./core/functions/include";
|
||||
import {keys, keysSynchronously} from "./core/filters/keys";
|
||||
import {spaceless, spacelessSynchronously} from "./core/filters/spaceless";
|
||||
import {column, columnSynchronously} from "./core/filters/column";
|
||||
import {filter, filterSynchronously} from "./core/filters/filter";
|
||||
import {map, mapSynchronously} from "./core/filters/map";
|
||||
import {reduce, reduceSynchronously} from "./core/filters/reduce";
|
||||
import {range, rangeSynchronously} from "./core/functions/range";
|
||||
import {constant, constantSynchronously} from "./core/functions/constant";
|
||||
import {cycle, cycleSynchronously} from "./core/functions/cycle";
|
||||
import {random, randomSynchronously} from "./core/functions/random";
|
||||
import {source, sourceSynchronously} from "./core/functions/source";
|
||||
import {templateFromString, templateFromStringSynchronously} from "./core/functions/template-from-string";
|
||||
import {dump, dumpSynchronously} from "./core/functions/dump";
|
||||
import {isEmpty, isEmptySynchronously} from "./core/tests/is-empty";
|
||||
import {isIterable, isIterableSynchronously} from "./core/tests/is-iterable";
|
||||
import {date as dateFunction, dateSynchronously} from "./core/functions/date";
|
||||
import {isDefined, isDefinedSynchronously} from "./core/tests/is-defined";
|
||||
import {isConstant, isConstantSynchronously} from "./core/tests/is-constant";
|
||||
import {createSpaceshipNode} from "../node/expression/binary/spaceship";
|
||||
import {createHasEveryNode} from "../node/expression/binary/has-every";
|
||||
import {createHasSomeNode} from "../node/expression/binary/has-some";
|
||||
|
||||
const getOperators = (): Array<TwingOperator> => {
|
||||
return [
|
||||
createOperator('not', "UNARY", 50, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createNotNode(operands[0], line, column);
|
||||
}),
|
||||
createOperator('-', "UNARY", 500, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createNegativeNode(operands[0], line, column);
|
||||
}),
|
||||
createOperator('+', "UNARY", 500, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createPositiveNode(operands[0], line, column);
|
||||
}),
|
||||
createOperator('or', "BINARY", 10, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createOrNode(operands, line, column);
|
||||
}),
|
||||
createOperator('and', "BINARY", 15, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createAndNode(operands, line, column);
|
||||
}),
|
||||
createOperator('b-or', "BINARY", 16, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createBitwiseOrNode(operands, line, column);
|
||||
}),
|
||||
createOperator('b-xor', "BINARY", 17, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createBitwiseXorNode(operands, line, column);
|
||||
}),
|
||||
createOperator('b-and', "BINARY", 18, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createBitwiseAndNode(operands, line, column);
|
||||
}),
|
||||
createOperator('==', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsEqualNode(operands, line, column);
|
||||
}),
|
||||
createOperator('!=', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsNotEqualToNode(operands, line, column);
|
||||
}),
|
||||
createOperator('<=>', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createSpaceshipNode(operands, line, column);
|
||||
}),
|
||||
createOperator('<', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsLessThanNode(operands, line, column);
|
||||
}),
|
||||
createOperator('<=', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsLessThanOrEqualToNode(operands, line, column);
|
||||
}),
|
||||
createOperator('>', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsGreaterThanNode(operands, line, column);
|
||||
}),
|
||||
createOperator('>=', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsGreaterThanOrEqualToNode(operands, line, column);
|
||||
}),
|
||||
createOperator('not in', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsNotInNode(operands, line, column);
|
||||
}),
|
||||
createOperator('in', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createIsInNode(operands, line, column);
|
||||
}),
|
||||
createOperator('matches', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createMatchesNode(operands, line, column);
|
||||
}),
|
||||
createOperator('starts with', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createStartsWithNode(operands, line, column);
|
||||
}),
|
||||
createOperator('ends with', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createEndsWithNode(operands, line, column);
|
||||
}),
|
||||
createOperator('has some', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createHasSomeNode(operands, line, column);
|
||||
}, "LEFT", 3),
|
||||
createOperator('has every', "BINARY", 20, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createHasEveryNode(operands, line, column);
|
||||
}, "LEFT", 3),
|
||||
createOperator('..', "BINARY", 25, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createRangeNode(operands, line, column);
|
||||
}),
|
||||
createOperator('+', "BINARY", 30, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createAddNode(operands, line, column);
|
||||
}),
|
||||
createOperator('-', "BINARY", 30, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createSubtractNode(operands, line, column);
|
||||
}),
|
||||
createOperator('~', "BINARY", 40, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createConcatenateNode(operands, line, column);
|
||||
}),
|
||||
createOperator('*', "BINARY", 60, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createMultiplyNode(operands, line, column);
|
||||
}),
|
||||
createOperator('/', "BINARY", 60, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createDivideNode(operands, line, column);
|
||||
}),
|
||||
createOperator('//', "BINARY", 60, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createDivideAndFloorNode(operands, line, column);
|
||||
}),
|
||||
createOperator('%', "BINARY", 60, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createModuloNode(operands, line, column);
|
||||
}),
|
||||
createOperator('**', "BINARY", 200, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createPowerNode(operands, line, column);
|
||||
}, "RIGHT"),
|
||||
createOperator('??', "BINARY", 300, (operands: [TwingBaseExpressionNode, TwingBaseExpressionNode], line: number, column: number) => {
|
||||
return createNullishCoalescingNode(operands, line, column);
|
||||
}, "RIGHT")
|
||||
];
|
||||
};
|
||||
|
||||
export const createCoreExtension = (): TwingExtension => {
|
||||
return {
|
||||
get filters() {
|
||||
const escapeFilters = ['escape', 'e'].map((name) => {
|
||||
return createFilter(name, escape, [
|
||||
return createFilter(name, (escape), [
|
||||
{
|
||||
name: 'strategy',
|
||||
defaultValue: null
|
||||
@ -131,7 +232,7 @@ export const createCoreExtension = (): TwingExtension => {
|
||||
name: 'name'
|
||||
}
|
||||
]),
|
||||
createFilter('convert_encoding', convertEncoding, [
|
||||
createFilter('convert_encoding', (convertEncoding), [
|
||||
{
|
||||
name: 'to'
|
||||
},
|
||||
@ -531,3 +632,345 @@ export const createCoreExtension = (): TwingExtension => {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const createSynchronousCoreExtension = (): TwingSynchronousExtension => {
|
||||
return {
|
||||
get filters() {
|
||||
const escapeFilters = ['escape', 'e'].map((name) => {
|
||||
return createSynchronousFilter(name, escapeSynchronously, [
|
||||
{
|
||||
name: 'strategy',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'charset',
|
||||
defaultValue: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
return [
|
||||
...escapeFilters,
|
||||
createSynchronousFilter('abs', absSynchronously, []),
|
||||
createSynchronousFilter('batch', batchSynchronously, [
|
||||
{
|
||||
name: 'size'
|
||||
},
|
||||
{
|
||||
name: 'fill',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'preserve_keys',
|
||||
defaultValue: true
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('capitalize', capitalizeSynchronously, []),
|
||||
createSynchronousFilter('column', columnSynchronously, [
|
||||
{
|
||||
name: 'name'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('convert_encoding', convertEncodingSynchronously, [
|
||||
{
|
||||
name: 'to'
|
||||
},
|
||||
{
|
||||
name: 'from'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('date', dateFilterSynchronously, [
|
||||
{
|
||||
name: 'format',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'timezone',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('date_modify', dateModifySynchronously, [
|
||||
{
|
||||
name: 'modifier'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('default', defaultFilterSynchronously, [
|
||||
{
|
||||
name: 'default',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('filter', filterSynchronously, [
|
||||
{
|
||||
name: 'array'
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('first', firstSynchronously, []),
|
||||
createSynchronousFilter('format', formatSynchronously, [], {
|
||||
is_variadic: true
|
||||
}),
|
||||
createSynchronousFilter('join', joinSynchronously, [
|
||||
{
|
||||
name: 'glue',
|
||||
defaultValue: ''
|
||||
},
|
||||
{
|
||||
name: 'and',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('json_encode', jsonEncodeSynchronously, [
|
||||
{
|
||||
name: 'options',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('keys', keysSynchronously, []),
|
||||
createSynchronousFilter('last', lastSynchronously, []),
|
||||
createSynchronousFilter('length', lengthSynchronously, []),
|
||||
createSynchronousFilter('lower', lowerSynchronously, []),
|
||||
createSynchronousFilter('map', mapSynchronously, [
|
||||
{
|
||||
name: 'arrow'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('merge', mergeSynchronously, [
|
||||
{
|
||||
name: 'source'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('nl2br', nl2brSynchronously, []),
|
||||
createSynchronousFilter('number_format', numberFormatSynchronously, [
|
||||
{
|
||||
name: 'decimal',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'decimal_point',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'thousand_sep',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('raw', rawSynchronously, []),
|
||||
createSynchronousFilter('reduce', reduceSynchronously, [
|
||||
{
|
||||
name: 'arrow'
|
||||
},
|
||||
{
|
||||
name: 'initial',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('replace', replaceSynchronously, [
|
||||
{
|
||||
name: 'from'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('reverse', reverseSynchronously, [
|
||||
{
|
||||
name: 'preserve_keys',
|
||||
defaultValue: false
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('round', roundSynchronously, [
|
||||
{
|
||||
name: 'precision',
|
||||
defaultValue: 0
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
defaultValue: 'common'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('slice', sliceSynchronously, [
|
||||
{
|
||||
name: 'start'
|
||||
},
|
||||
{
|
||||
name: 'length',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'preserve_keys',
|
||||
defaultValue: false
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('sort', sortSynchronously, [{
|
||||
name: 'arrow',
|
||||
defaultValue: null
|
||||
}]),
|
||||
createSynchronousFilter('spaceless', spacelessSynchronously, []),
|
||||
createSynchronousFilter('split', splitSynchronously, [
|
||||
{
|
||||
name: 'delimiter'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('striptags', striptagsSynchronously, [
|
||||
{
|
||||
name: 'allowable_tags',
|
||||
defaultValue: ''
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('title', titleSynchronously, []),
|
||||
createSynchronousFilter('trim', trimSynchronously, [
|
||||
{
|
||||
name: 'character_mask',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'side',
|
||||
defaultValue: 'both'
|
||||
}
|
||||
]),
|
||||
createSynchronousFilter('upper', upperSynchronously, []),
|
||||
createSynchronousFilter('url_encode', urlEncodeSynchronously, []),
|
||||
];
|
||||
},
|
||||
get functions() {
|
||||
return [
|
||||
createSynchronousFunction('constant', constantSynchronously, [
|
||||
{name: 'name'},
|
||||
{name: 'object', defaultValue: null}
|
||||
]),
|
||||
createSynchronousFunction('cycle', cycleSynchronously, [
|
||||
{
|
||||
name: 'values'
|
||||
},
|
||||
{
|
||||
name: 'position'
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('date', dateSynchronously, [
|
||||
{
|
||||
name: 'date',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'timezone',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('dump', dumpSynchronously, [], {
|
||||
is_variadic: true
|
||||
}),
|
||||
createSynchronousFunction('include', includeSynchronously, [
|
||||
{
|
||||
name: 'template'
|
||||
},
|
||||
{
|
||||
name: 'variables',
|
||||
defaultValue: {}
|
||||
},
|
||||
{
|
||||
name: 'with_context',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'ignore_missing',
|
||||
defaultValue: false
|
||||
},
|
||||
{
|
||||
name: 'sandboxed',
|
||||
defaultValue: false
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('max', maxSynchronously, [], {
|
||||
is_variadic: true
|
||||
}),
|
||||
createSynchronousFunction('min', minSynchronously, [], {
|
||||
is_variadic: true
|
||||
}),
|
||||
createSynchronousFunction('random', randomSynchronously, [
|
||||
{
|
||||
name: 'values',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
name: 'max',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('range', rangeSynchronously, [
|
||||
{
|
||||
name: 'low'
|
||||
},
|
||||
{
|
||||
name: 'high'
|
||||
},
|
||||
{
|
||||
name: 'step',
|
||||
defaultValue: 1
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('source', sourceSynchronously, [
|
||||
{
|
||||
name: 'name'
|
||||
},
|
||||
{
|
||||
name: 'ignore_missing',
|
||||
defaultValue: false
|
||||
}
|
||||
]),
|
||||
createSynchronousFunction('template_from_string', templateFromStringSynchronously, [
|
||||
{
|
||||
name: 'template'
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
defaultValue: null
|
||||
}
|
||||
])
|
||||
];
|
||||
},
|
||||
get nodeVisitors() {
|
||||
return [];
|
||||
},
|
||||
get operators() {
|
||||
return getOperators();
|
||||
},
|
||||
get tagHandlers() {
|
||||
return [];
|
||||
},
|
||||
get tests() {
|
||||
return [
|
||||
createSynchronousTest('constant', isConstantSynchronously, [
|
||||
{
|
||||
name: 'constant'
|
||||
},
|
||||
{
|
||||
name: 'object',
|
||||
defaultValue: null
|
||||
}
|
||||
]),
|
||||
createSynchronousTest('divisible by', isDivisibleBySynchronously, [
|
||||
{
|
||||
name: 'divisor'
|
||||
}
|
||||
]),
|
||||
createSynchronousTest('defined', isDefinedSynchronously, []),
|
||||
createSynchronousTest('empty', isEmptySynchronously, []),
|
||||
createSynchronousTest('even', isEvenSynchronously, []),
|
||||
createSynchronousTest('iterable', isIterableSynchronously, []),
|
||||
createSynchronousTest('none', isNullSynchronously, []),
|
||||
createSynchronousTest('null', isNullSynchronously, []),
|
||||
createSynchronousTest('odd', isOddSynchronously, []),
|
||||
createSynchronousTest('same as', isSameAsSynchronously, [
|
||||
{
|
||||
name: 'comparand'
|
||||
}
|
||||
]),
|
||||
];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1,12 +1,15 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Return the absolute value of a number.
|
||||
*
|
||||
* @param _executionContext
|
||||
* @param x
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
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} from "../../../helpers/chunk";
|
||||
import {chunk, chunkSynchronously} from "../../../helpers/chunk";
|
||||
import {fillMap} from "../../../helpers/fill-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Batches item.
|
||||
@ -35,3 +35,25 @@ export const batch: TwingCallable<[
|
||||
return chunks;
|
||||
});
|
||||
};
|
||||
|
||||
export const batchSynchronously: TwingSynchronousCallable<[
|
||||
items: Array<any>,
|
||||
size: number,
|
||||
fill: any,
|
||||
preserveKeys: boolean
|
||||
], Array<Map<any, any>>> = (_executionContext, items, size, fill, preserveKeys) => {
|
||||
if ((items === null) || (items === undefined)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const chunks = chunkSynchronously(items, size, preserveKeys);
|
||||
|
||||
if (fill !== null && chunks.length) {
|
||||
const last = chunks.length - 1;
|
||||
const lastChunk: Map<any, any> = chunks[last];
|
||||
|
||||
fillMap(lastChunk, size, fill);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type {TwingMarkup} from "../../../markup";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const words: (value: string) => string = require('capitalize');
|
||||
|
||||
@ -19,3 +20,13 @@ export const capitalize: TwingCallable<[
|
||||
|
||||
return Promise.resolve(words(string.toString()));
|
||||
};
|
||||
|
||||
export const capitalizeSynchronously: TwingSynchronousCallable<[
|
||||
string: string | TwingMarkup
|
||||
], string> = (_executionContext, string) => {
|
||||
if ((string === null) || (string === undefined) || string === '') {
|
||||
return string;
|
||||
}
|
||||
|
||||
return words(string.toString());
|
||||
};
|
||||
|
@ -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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Return the values from a single column in the input array.
|
||||
@ -34,3 +34,27 @@ export const column: TwingCallable = (_executionContext, thing: any, columnKey:
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
export const columnSynchronously: TwingSynchronousCallable = (_executionContext, thing: any, columnKey: any): Array<any> => {
|
||||
let map: Map<any, any>;
|
||||
|
||||
if (!isTraversable(thing) || isPlainObject(thing)) {
|
||||
throw 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 result;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {iconv} from "../../../helpers/iconv";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const convertEncoding: TwingCallable<[
|
||||
value: string | Buffer,
|
||||
@ -8,3 +8,11 @@ export const convertEncoding: TwingCallable<[
|
||||
], Buffer> = (_executionContext, value, to, from) => {
|
||||
return Promise.resolve(iconv(from, to, Buffer.from(value)));
|
||||
};
|
||||
|
||||
export const convertEncodingSynchronously: TwingSynchronousCallable<[
|
||||
value: string | Buffer,
|
||||
to: string,
|
||||
from: string
|
||||
], Buffer> = (_executionContext, value, to, from) => {
|
||||
return iconv(from, to, Buffer.from(value));
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {DateTime} from "luxon";
|
||||
import {createDate} from "../functions/date";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {createDateTime, createDateTimeSynchronously} from "../functions/date";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Returns a new date object modified.
|
||||
@ -23,21 +23,46 @@ export const dateModify: TwingCallable = (
|
||||
const {environment} = executionContext;
|
||||
const {timezone: defaultTimezone} = environment;
|
||||
|
||||
return createDate(defaultTimezone, date, null)
|
||||
return createDateTime(defaultTimezone, date, null)
|
||||
.then((dateTime) => {
|
||||
let regExp = new RegExp(/(\+|-)([0-9])(.*)/);
|
||||
let parts = regExp.exec(modifier)!;
|
||||
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 operator: string = parts[1];
|
||||
let operand: number = Number.parseInt(parts[2]);
|
||||
let unit: string = parts[3].trim();
|
||||
|
||||
let duration: any = {};
|
||||
let duration: any = {};
|
||||
|
||||
duration[unit] = operator === '-' ? -operand : operand;
|
||||
duration[unit] = operator === '-' ? -operand : operand;
|
||||
|
||||
dateTime = dateTime.plus(duration);
|
||||
dateTime = dateTime.plus(duration);
|
||||
|
||||
return dateTime;
|
||||
return dateTime;
|
||||
});
|
||||
};
|
||||
|
||||
export const dateModifySynchronously: TwingSynchronousCallable = (
|
||||
executionContext,
|
||||
date: Date | DateTime | string,
|
||||
modifier: string
|
||||
): DateTime => {
|
||||
const {environment} = executionContext;
|
||||
const {timezone: defaultTimezone} = environment;
|
||||
|
||||
let dateTime = createDateTimeSynchronously(defaultTimezone, date, null);
|
||||
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;
|
||||
};
|
||||
|
@ -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} from "../functions/date";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {date as createDate, dateSynchronously as createDateSynchronously} from "../functions/date";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Converts a date to the given format.
|
||||
@ -44,3 +44,29 @@ export const date: TwingCallable = (
|
||||
return Promise.resolve(formatDateTime(date, format));
|
||||
});
|
||||
};
|
||||
|
||||
export const dateFilterSynchronously: TwingSynchronousCallable = (
|
||||
executionContext,
|
||||
date: DateTime | Duration | string,
|
||||
format: string | null,
|
||||
timezone: string | null | false
|
||||
): string => {
|
||||
const {environment} = executionContext;
|
||||
const {dateFormat, dateIntervalFormat} = environment;
|
||||
|
||||
const durationOrDateTime = createDateSynchronously(executionContext, date, timezone);
|
||||
|
||||
if (durationOrDateTime instanceof Duration) {
|
||||
if (format === null) {
|
||||
format = dateIntervalFormat;
|
||||
}
|
||||
|
||||
return formatDuration(durationOrDateTime, format);
|
||||
}
|
||||
|
||||
if (format === null) {
|
||||
format = dateFormat;
|
||||
}
|
||||
|
||||
return formatDateTime(durationOrDateTime, format);
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {isEmpty} from "../tests/is-empty";
|
||||
import {isEmpty, isEmptySynchronously} from "../tests/is-empty";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const defaultFilter: TwingCallable<[
|
||||
value: any,
|
||||
@ -15,3 +16,14 @@ export const defaultFilter: TwingCallable<[
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const defaultFilterSynchronously: TwingSynchronousCallable<[
|
||||
value: any,
|
||||
defaultValue: any | null
|
||||
]> = (executionContext, value, defaultValue) => {
|
||||
if (isEmptySynchronously(executionContext, value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
import {escapeValue} from "../../../helpers/escape-value";
|
||||
import {escapeValue, escapeValueSynchronously} from "../../../helpers/escape-value";
|
||||
import {TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const escape: TwingCallable<[
|
||||
value: string | TwingMarkup | null,
|
||||
@ -26,3 +27,27 @@ export const escape: TwingCallable<[
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
export const escapeSynchronously: TwingSynchronousCallable<[
|
||||
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
|
||||
const escapedValue = escapeValueSynchronously(template, environment, value, strategy, environment.charset);
|
||||
|
||||
if (typeof escapedValue === "string") {
|
||||
return createMarkup(escapedValue, environment.charset);
|
||||
}
|
||||
|
||||
return escapedValue;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
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();
|
||||
@ -14,3 +14,17 @@ export const filter: TwingCallable = async (_executionContext, map: any, callbac
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
export const filterSynchronously: TwingSynchronousCallable = (_executionContext, map: any, callback: (...args: Array<any>) => boolean): Map<any, any> => {
|
||||
const result: Map<any, any> = new Map();
|
||||
|
||||
map = iteratorToMap(map);
|
||||
|
||||
for (const [key, value] of map) {
|
||||
if (callback(value)) {
|
||||
result.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {getFirstValue} from "../../../helpers/get-first-value";
|
||||
import {slice} from "./slice";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {slice, sliceSynchronously} from "./slice";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Returns the first element of the item.
|
||||
@ -15,6 +15,14 @@ export const first: TwingCallable<[
|
||||
]> = (executionContext, item) => {
|
||||
return slice(executionContext, item, 0, 1, false)
|
||||
.then((elements) => {
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
});
|
||||
}
|
||||
|
||||
export const firstSynchronously: TwingSynchronousCallable<[
|
||||
item: any
|
||||
]> = (executionContext, item) => {
|
||||
const elements = sliceSynchronously(executionContext, item, 0, 1, false);
|
||||
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const sprintf = require('locutus/php/strings/sprintf');
|
||||
|
||||
@ -7,3 +7,9 @@ export const format: TwingCallable = (_executionContext, ...args: any[]): Promis
|
||||
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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Joins the values to a string.
|
||||
@ -64,3 +64,42 @@ export const join: TwingCallable<[
|
||||
|
||||
return Promise.resolve(_do());
|
||||
};
|
||||
|
||||
export const joinSynchronously: TwingSynchronousCallable<[
|
||||
value: any,
|
||||
glue: string,
|
||||
and: string | null
|
||||
], string> = (_executionContext, value, glue, and) => {
|
||||
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 '';
|
||||
};
|
||||
|
@ -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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
function isPureArray(map: Map<any, any>): boolean {
|
||||
let result: boolean = true;
|
||||
@ -59,3 +59,41 @@ export const jsonEncode: TwingCallable = (_executionContext, value: any): Promis
|
||||
|
||||
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)) {
|
||||
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 JSON.stringify(_sanitize(value));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Returns the keys of the passed array.
|
||||
@ -25,3 +25,20 @@ export const keys: TwingCallable<[
|
||||
|
||||
return Promise.resolve([...traversable.keys()]);
|
||||
};
|
||||
|
||||
export const keysSynchronously: TwingSynchronousCallable<[
|
||||
values: Array<any>
|
||||
], Array<any>> = (
|
||||
_executionContext,
|
||||
values
|
||||
) => {
|
||||
let traversable;
|
||||
|
||||
if ((values === null) || (values === undefined)) {
|
||||
traversable = new Map();
|
||||
} else {
|
||||
traversable = iteratorToMap(values);
|
||||
}
|
||||
|
||||
return [...traversable.keys()];
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {getFirstValue} from "../../../helpers/get-first-value";
|
||||
import {slice} from "./slice";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {slice, sliceSynchronously} from "./slice";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Returns the last element of the item.
|
||||
@ -15,6 +15,14 @@ export const last: TwingCallable<[
|
||||
]> = (executionContext, item) => {
|
||||
return slice(executionContext, item, -1, 1, false)
|
||||
.then((elements) => {
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
});
|
||||
};
|
||||
|
||||
export const lastSynchronously: TwingSynchronousCallable<[
|
||||
item: any
|
||||
]> = (executionContext, item) => {
|
||||
const elements = sliceSynchronously(executionContext, item, -1, 1, false)
|
||||
|
||||
return typeof elements === 'string' ? elements : getFirstValue(elements);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Returns the length of a thing.
|
||||
@ -24,3 +24,21 @@ export const length: TwingCallable = (_executionContext,thing: any): Promise<num
|
||||
|
||||
return Promise.resolve(length);
|
||||
};
|
||||
|
||||
export const lengthSynchronously: TwingSynchronousCallable = (_executionContext,thing: any): 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 length;
|
||||
};
|
||||
|
@ -1,13 +1,17 @@
|
||||
import type {TwingMarkup} from "../../../markup";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Converts a string to lowercase.
|
||||
*
|
||||
* @param {string | TwingMarkup} string A string
|
||||
*
|
||||
* @returns {Promise<string>} The lowercased string
|
||||
* @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,5 +1,5 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const map: TwingCallable<[
|
||||
map: any,
|
||||
@ -15,3 +15,18 @@ export const map: TwingCallable<[
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
export const mapSynchronously: TwingSynchronousCallable<[
|
||||
map: any,
|
||||
callback: (...args: Array<any>) => any
|
||||
], Map<any, any>> = (_executionContext, map, callback) => {
|
||||
const result: Map<any, any> = new Map();
|
||||
|
||||
map = iteratorToMap(map);
|
||||
|
||||
for (const [key, value] of map) {
|
||||
result.set(key, callback(value, key));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Merges an array with another one.
|
||||
@ -34,3 +34,19 @@ export const merge: TwingCallable = (_executionContext, iterable1: any, 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);
|
||||
|
||||
if (isIterable1NullOrUndefined || (!isTraversable(iterable1) && (typeof iterable1 !== 'object'))) {
|
||||
throw 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'))) {
|
||||
throw new Error(`The merge filter only accepts arrays or "Traversable" as source, got "${!isSourceNullOrUndefined ? typeof source : source}".`);
|
||||
}
|
||||
|
||||
return mergeIterables(iteratorToMap(iterable1), iteratorToMap(source));
|
||||
};
|
||||
|
@ -1,9 +1,13 @@
|
||||
import type {TwingMarkup} from "../../../markup";
|
||||
import {createMarkup} from "../../../markup";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, 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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpNumberFormat = require('locutus/php/strings/number_format');
|
||||
|
||||
@ -40,3 +40,28 @@ export const numberFormat: TwingCallable = (
|
||||
|
||||
return Promise.resolve(phpNumberFormat(number, numberOfDecimals, decimalPoint, thousandSeparator));
|
||||
};
|
||||
|
||||
export const numberFormatSynchronously: TwingSynchronousCallable = (
|
||||
executionContext,
|
||||
number: any,
|
||||
numberOfDecimals: number | null,
|
||||
decimalPoint: string | null,
|
||||
thousandSeparator: string | null
|
||||
): 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 phpNumberFormat(number, numberOfDecimals, decimalPoint, thousandSeparator);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Marks a variable as being safe.
|
||||
@ -11,3 +11,7 @@ import {TwingCallable} from "../../../callable-wrapper";
|
||||
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,5 +1,5 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
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);
|
||||
@ -10,3 +10,13 @@ export const reduce: TwingCallable = (_executionContext, map: any, callback: (ac
|
||||
return (async () => callback(await previousValue, currentValue))();
|
||||
}, initial));
|
||||
};
|
||||
|
||||
export const reduceSynchronously: TwingSynchronousCallable = (_executionContext, map: any, callback: (accumulator: any, currentValue: any) => any, initial: any): Promise<string> => {
|
||||
map = iteratorToMap(map);
|
||||
|
||||
const values: any[] = [...map.values()];
|
||||
|
||||
return values.reduce((previousValue: any, currentValue: any): any => {
|
||||
return (() => callback(previousValue, currentValue))();
|
||||
}, initial);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {iteratorToHash} from "../../../helpers/iterator-to-hash";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpStrtr = require('locutus/php/strings/strtr');
|
||||
|
||||
@ -14,18 +14,19 @@ const phpStrtr = require('locutus/php/strings/strtr');
|
||||
*/
|
||||
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 (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 = '';
|
||||
}
|
||||
if (value === null) {
|
||||
value = '';
|
||||
}
|
||||
|
||||
return phpStrtr(value, from);
|
||||
};
|
||||
return phpStrtr(value, from);
|
||||
};
|
||||
|
||||
try {
|
||||
return Promise.resolve(_do());
|
||||
@ -33,3 +34,18 @@ export const replace: TwingCallable = (_executionContext,value: string | null, f
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const replaceSynchronously: TwingSynchronousCallable = (_executionContext, value: string | null, from: any): 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);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {reverse as reverseHelper} from "../../../helpers/reverse";
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const esrever = require('esrever');
|
||||
|
||||
@ -19,3 +19,11 @@ export const reverse: TwingCallable = (_executionContext, item: any, preserveKey
|
||||
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 esrever.reverse(item);
|
||||
} else {
|
||||
return reverseHelper(iteratorToMap(item as Map<any, any>), preserveKeys);
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpRound = require('locutus/php/math/round');
|
||||
const phpCeil = require('locutus/php/math/ceil');
|
||||
@ -15,23 +15,24 @@ const phpFloor = require('locutus/php/math/floor');
|
||||
*/
|
||||
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 === 'common') {
|
||||
return phpRound(value, precision);
|
||||
}
|
||||
|
||||
if (method !== 'ceil' && method !== 'floor') {
|
||||
throw new Error('The round filter only supports the "common", "ceil", and "floor" methods.');
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
};
|
||||
if (method === 'ceil') {
|
||||
return phpCeil(intermediateValue) / intermediateDivider;
|
||||
}
|
||||
else {
|
||||
return phpFloor(intermediateValue) / intermediateDivider;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const result = _do();
|
||||
@ -41,3 +42,23 @@ export const round: TwingCallable = (_executionContext, value: any, precision: n
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const roundSynchronously: TwingSynchronousCallable = (_executionContext, value: any, precision: number, method: string): 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;
|
||||
}
|
||||
};
|
||||
|
@ -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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Slices a variable.
|
||||
@ -38,3 +38,28 @@ export const slice: TwingCallable<[
|
||||
|
||||
return Promise.resolve(item.substr(start, length));
|
||||
};
|
||||
|
||||
export const sliceSynchronously: TwingSynchronousCallable<[
|
||||
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 sliceMap(iterableItem, start, length, preserveKeys);
|
||||
}
|
||||
|
||||
item = '' + (item ? item : '');
|
||||
|
||||
if (length === null) {
|
||||
length = item.length - start;
|
||||
}
|
||||
|
||||
return item.substr(start, length);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {asort} from "../../../helpers/asort";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {asort, asortSynchronously} from "../../../helpers/asort";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Sorts an iterable.
|
||||
@ -26,3 +26,18 @@ export const sort: TwingCallable<[
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
export const sortSynchronously: TwingSynchronousCallable<[
|
||||
iterable: any,
|
||||
arrow: ((a: any, b: any) => -1 | 0 | 1) | null
|
||||
], Map<any, any>> = (_executionContext, iterable, arrow)=> {
|
||||
if (!isTraversable(iterable)) {
|
||||
throw new Error(`The sort filter only works with iterables, got "${typeof iterable}".`);
|
||||
}
|
||||
|
||||
const map = iteratorToMap(iterable);
|
||||
|
||||
asortSynchronously(map, arrow || undefined);
|
||||
|
||||
return map;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Removes whitespaces between HTML tags.
|
||||
@ -9,3 +9,7 @@ import {TwingCallable} from "../../../callable-wrapper";
|
||||
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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const explode = require('locutus/php/strings/explode');
|
||||
|
||||
@ -27,28 +27,52 @@ const explode = require('locutus/php/strings/explode');
|
||||
*/
|
||||
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 (delimiter) {
|
||||
return !limit ? explode(delimiter, value) : explode(delimiter, value, limit);
|
||||
}
|
||||
|
||||
if (!limit || limit <= 1) {
|
||||
return value.match(/.{1,1}/ug)!;
|
||||
}
|
||||
if (!limit || limit <= 1) {
|
||||
return value.match(/.{1,1}/ug)!;
|
||||
}
|
||||
|
||||
let length = value.length;
|
||||
let length = value.length;
|
||||
|
||||
if (length < limit) {
|
||||
return [value];
|
||||
}
|
||||
if (length < limit) {
|
||||
return [value];
|
||||
}
|
||||
|
||||
let r = [];
|
||||
let r = [];
|
||||
|
||||
for (let i = 0; i < length; i += limit) {
|
||||
r.push(value.substr(i, limit));
|
||||
}
|
||||
for (let i = 0; i < length; i += limit) {
|
||||
r.push(value.substr(i, limit));
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -1,7 +1,11 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, 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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpUcwords = require('locutus/php/strings/ucwords');
|
||||
|
||||
@ -18,3 +18,11 @@ export const title: TwingCallable<[
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
export const titleSynchronously: TwingSynchronousCallable<[
|
||||
string: string | TwingMarkup
|
||||
], string> = (_executionContext, string) => {
|
||||
const result: string = phpUcwords(string.toString().toLowerCase());
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpTrim = require('locutus/php/strings/trim');
|
||||
const phpLeftTrim = require('locutus/php/strings/ltrim');
|
||||
@ -13,21 +13,21 @@ const phpRightTrim = require('locutus/php/strings/rtrim');
|
||||
*/
|
||||
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";
|
||||
}
|
||||
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".');
|
||||
}
|
||||
};
|
||||
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());
|
||||
@ -35,3 +35,21 @@ export const trim: TwingCallable = (_executionContext, string: string, character
|
||||
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";
|
||||
}
|
||||
|
||||
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".');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type {TwingMarkup} from "../../../markup";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Converts a string to uppercase.
|
||||
@ -11,3 +11,7 @@ import {TwingCallable} from "../../../callable-wrapper";
|
||||
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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpHttpBuildQuery = require('locutus/php/url/http_build_query');
|
||||
|
||||
@ -24,3 +24,17 @@ export const url_encode: TwingCallable = (_executionContext, url: string | {}):
|
||||
|
||||
return Promise.resolve(encodeURIComponent(url));
|
||||
}
|
||||
|
||||
export const urlEncodeSynchronously: TwingSynchronousCallable = (_executionContext, url: string | {}): string => {
|
||||
if (typeof url !== 'string') {
|
||||
if (isTraversable(url)) {
|
||||
url = iteratorToHash(url);
|
||||
}
|
||||
|
||||
const builtUrl: string = phpHttpBuildQuery(url, '', '&');
|
||||
|
||||
return builtUrl.replace(/\+/g, '%20');
|
||||
}
|
||||
|
||||
return encodeURIComponent(url);
|
||||
}
|
||||
|
@ -1,5 +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,
|
||||
@ -11,3 +12,14 @@ export const constant: TwingCallable<[
|
||||
): Promise<any> => {
|
||||
return Promise.resolve(constantHelper(executionContext.context, name, object));
|
||||
};
|
||||
|
||||
export const constantSynchronously: TwingSynchronousCallable<[
|
||||
name: string,
|
||||
object: any | null
|
||||
]> = (
|
||||
executionContext,
|
||||
name,
|
||||
object
|
||||
): Promise<any> => {
|
||||
return constantHelper(executionContext.context, name, object);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {isAMapLike} from "../../../helpers/map-like";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Cycles over a value.
|
||||
@ -32,3 +32,26 @@ export const cycle: TwingCallable<[
|
||||
|
||||
return Promise.resolve(values[position % size]);
|
||||
}
|
||||
|
||||
export const cycleSynchronously: TwingSynchronousCallable<[
|
||||
value: Map<any, any> | Array<any> | string | boolean | null,
|
||||
position: number
|
||||
]> = (_executionContext, value, position) => {
|
||||
if (!isAMapLike(value) && !Array.isArray(value)) {
|
||||
return 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 values[position % size];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {DateTime, Duration} from "luxon";
|
||||
import {modifyDate} from "../../../helpers/modify-date";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Converts an input to a DateTime instance.
|
||||
@ -17,74 +17,74 @@ import {TwingCallable} from "../../../callable-wrapper";
|
||||
*
|
||||
* @returns {Promise<DateTime | Duration>}
|
||||
*/
|
||||
export const createDate = (
|
||||
export const createDateTime = (
|
||||
defaultTimezone: string,
|
||||
input: Date | DateTime | number | string | null,
|
||||
timezone: string | null | false
|
||||
): Promise<DateTime> => {
|
||||
const _do = (): DateTime => {
|
||||
let result: DateTime;
|
||||
let result: DateTime;
|
||||
|
||||
if (input === null) {
|
||||
if (input === null) {
|
||||
result = DateTime.local();
|
||||
}
|
||||
else if (typeof input === 'number') {
|
||||
result = DateTime.fromMillis(input * 1000);
|
||||
}
|
||||
else if (typeof input === 'string') {
|
||||
if (input === 'now') {
|
||||
result = DateTime.local();
|
||||
}
|
||||
else if (typeof input === 'number') {
|
||||
result = DateTime.fromMillis(input * 1000);
|
||||
}
|
||||
else if (typeof input === 'string') {
|
||||
if (input === 'now') {
|
||||
result = DateTime.local();
|
||||
}
|
||||
else {
|
||||
result = DateTime.fromISO(input, {
|
||||
else {
|
||||
result = DateTime.fromISO(input, {
|
||||
setZone: true
|
||||
});
|
||||
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromRFC2822(input, {
|
||||
setZone: true
|
||||
});
|
||||
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromRFC2822(input, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromSQL(input, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid && /^-{0,1}\d+$/.test(input)) {
|
||||
result = DateTime.fromMillis(Number.parseInt(input) * 1000, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
result = modifyDate(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (input instanceof DateTime) {
|
||||
result = input;
|
||||
}
|
||||
else {
|
||||
result = DateTime.fromJSDate(input);
|
||||
}
|
||||
|
||||
if (!result || !result.isValid) {
|
||||
throw new Error(`Failed to parse date "${input}".`);
|
||||
}
|
||||
|
||||
// now let's apply timezone
|
||||
// determine the timezone
|
||||
if (timezone !== false) {
|
||||
if (timezone === null) {
|
||||
timezone = defaultTimezone;
|
||||
}
|
||||
|
||||
result = result.setZone(timezone);
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromSQL(input, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid && /^-{0,1}\d+$/.test(input)) {
|
||||
result = DateTime.fromMillis(Number.parseInt(input) * 1000, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
result = modifyDate(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (input instanceof DateTime) {
|
||||
result = input;
|
||||
}
|
||||
else {
|
||||
result = DateTime.fromJSDate(input);
|
||||
}
|
||||
|
||||
if (!result || !result.isValid) {
|
||||
throw new Error(`Failed to parse date "${input}".`);
|
||||
}
|
||||
|
||||
// now let's apply timezone
|
||||
// determine the timezone
|
||||
if (timezone !== false) {
|
||||
if (timezone === null) {
|
||||
timezone = defaultTimezone;
|
||||
}
|
||||
|
||||
return result;
|
||||
result = result.setZone(timezone);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
try {
|
||||
@ -103,5 +103,86 @@ export const date: TwingCallable = (
|
||||
return Promise.resolve(date);
|
||||
}
|
||||
|
||||
return createDate(executionContext.environment.timezone, date, timezone);
|
||||
return createDateTime(executionContext.environment.timezone, date, timezone);
|
||||
}
|
||||
|
||||
export const createDateTimeSynchronously = (
|
||||
defaultTimezone: string,
|
||||
input: Date | DateTime | number | string | null,
|
||||
timezone: string | null | false
|
||||
): DateTime => {
|
||||
let result: DateTime;
|
||||
|
||||
if (input === null) {
|
||||
result = DateTime.local();
|
||||
}
|
||||
else if (typeof input === 'number') {
|
||||
result = DateTime.fromMillis(input * 1000);
|
||||
}
|
||||
else if (typeof input === 'string') {
|
||||
if (input === 'now') {
|
||||
result = DateTime.local();
|
||||
}
|
||||
else {
|
||||
result = DateTime.fromISO(input, {
|
||||
setZone: true
|
||||
});
|
||||
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromRFC2822(input, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
result = DateTime.fromSQL(input, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid && /^-{0,1}\d+$/.test(input)) {
|
||||
result = DateTime.fromMillis(Number.parseInt(input) * 1000, {
|
||||
setZone: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
result = modifyDate(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (input instanceof DateTime) {
|
||||
result = input;
|
||||
}
|
||||
else {
|
||||
result = DateTime.fromJSDate(input);
|
||||
}
|
||||
|
||||
if (!result || !result.isValid) {
|
||||
throw new Error(`Failed to parse date "${input}".`);
|
||||
}
|
||||
|
||||
// now let's apply timezone
|
||||
// determine the timezone
|
||||
if (timezone !== false) {
|
||||
if (timezone === null) {
|
||||
timezone = defaultTimezone;
|
||||
}
|
||||
|
||||
result = result.setZone(timezone);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const dateSynchronously: TwingSynchronousCallable = (
|
||||
executionContext,
|
||||
date: Date | DateTime | Duration | number | string | null,
|
||||
timezone: string | null | false
|
||||
): DateTime | Duration => {
|
||||
if (date instanceof Duration) {
|
||||
return date;
|
||||
}
|
||||
|
||||
return createDateTimeSynchronously(executionContext.environment.timezone, date, timezone);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {iterate} from "../../../helpers/iterate";
|
||||
import {iterate, iterateSynchronously} from "../../../helpers/iterate";
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
import {varDump} from "../../../helpers/php";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
import type {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const dump: TwingCallable<[
|
||||
...vars: Array<any>
|
||||
@ -20,3 +20,20 @@ export const dump: TwingCallable<[
|
||||
|
||||
return Promise.resolve(createMarkup(varDump(...vars)));
|
||||
};
|
||||
|
||||
export const dumpSynchronously: TwingSynchronousCallable<[
|
||||
...vars: Array<any>
|
||||
], TwingMarkup> = (executionContext, ...vars) => {
|
||||
if (vars.length < 1) {
|
||||
const vars_ = new Map();
|
||||
|
||||
iterateSynchronously(executionContext.context, (key, value) => {
|
||||
vars_.set(key, value);
|
||||
});
|
||||
|
||||
return createMarkup(varDump(vars_));
|
||||
}
|
||||
|
||||
return createMarkup(varDump(...vars));
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {mergeIterables} from "../../../helpers/merge-iterables";
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {isPlainObject} from "../../../helpers/is-plain-object";
|
||||
import {createContext} from "../../../context";
|
||||
import {createContext, TwingContext2} from "../../../context";
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
import type {TwingTemplate} from "../../../template";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
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";
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
@ -78,7 +78,7 @@ export const include: TwingCallable<[
|
||||
if (template) {
|
||||
return template.execute(
|
||||
environment,
|
||||
createContext(variables),
|
||||
createContext(iterableToMap(variables)),
|
||||
new Map(),
|
||||
outputBuffer,
|
||||
{
|
||||
@ -100,3 +100,85 @@ export const include: TwingCallable<[
|
||||
return createMarkup(result, environment.charset);
|
||||
});
|
||||
}
|
||||
|
||||
export const includeSynchronously: TwingSynchronousCallable<[
|
||||
templates: string | TwingSynchronousTemplate | null | Array<string | TwingSynchronousTemplate | null>,
|
||||
variables: TwingContext2,
|
||||
withContext: boolean,
|
||||
ignoreMissing: boolean,
|
||||
sandboxed: boolean
|
||||
]> = (
|
||||
executionContext,
|
||||
templates,
|
||||
variables,
|
||||
withContext,
|
||||
ignoreMissing,
|
||||
sandboxed
|
||||
): TwingMarkup => {
|
||||
const {
|
||||
template,
|
||||
environment,
|
||||
templateLoader,
|
||||
context,
|
||||
nodeExecutor,
|
||||
outputBuffer,
|
||||
sourceMapRuntime,
|
||||
strict
|
||||
} = executionContext;
|
||||
|
||||
if (!isPlainObject(variables) && !isTraversable(variables)) {
|
||||
const isVariablesNullOrUndefined = variables === null || variables === undefined;
|
||||
|
||||
throw new Error(`Variables passed to the "include" function or tag must be iterable, got "${!isVariablesNullOrUndefined ? typeof variables : variables}".`);
|
||||
}
|
||||
|
||||
variables = iteratorToMap(variables);
|
||||
|
||||
if (withContext) {
|
||||
variables = new Map([
|
||||
...context.entries(),
|
||||
...variables.entries()
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Array.isArray(templates)) {
|
||||
templates = [templates];
|
||||
}
|
||||
|
||||
const resolveTemplate = (templates: Array<string | TwingSynchronousTemplate | null>): TwingSynchronousTemplate | null => {
|
||||
try {
|
||||
return template.loadTemplate(executionContext, templates);
|
||||
} catch (error) {
|
||||
if (!ignoreMissing) {
|
||||
throw error;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resolvedTemplate = resolveTemplate(templates);
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
if (resolvedTemplate) {
|
||||
resolvedTemplate.execute(
|
||||
environment,
|
||||
variables,
|
||||
new Map(),
|
||||
outputBuffer,
|
||||
{
|
||||
nodeExecutor,
|
||||
sandboxed,
|
||||
sourceMapRuntime: sourceMapRuntime || undefined,
|
||||
strict,
|
||||
templateLoader
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const result = outputBuffer.getAndClean();
|
||||
|
||||
return createMarkup(result, environment.charset);
|
||||
}
|
||||
|
@ -1,6 +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>
|
||||
@ -11,3 +12,13 @@ export const max: TwingCallable<[
|
||||
|
||||
return Promise.resolve(phpMax(iteratorToArray(values)));
|
||||
};
|
||||
|
||||
export const maxSynchronously: TwingSynchronousCallable<[
|
||||
...values: Array<any>
|
||||
]> = (_executionContext, ...values) => {
|
||||
if (values.length === 1) {
|
||||
values = values[0];
|
||||
}
|
||||
|
||||
return phpMax(iteratorToArray(values));
|
||||
};
|
||||
|
@ -1,6 +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>
|
||||
@ -11,3 +12,13 @@ export const min: TwingCallable<[
|
||||
|
||||
return Promise.resolve(phpMin(iteratorToArray(values)));
|
||||
};
|
||||
|
||||
export const minSynchronously: TwingSynchronousCallable<[
|
||||
...values: Array<any>
|
||||
]> = (_executionContext, ...values) => {
|
||||
if (values.length === 1) {
|
||||
values = values[0];
|
||||
}
|
||||
|
||||
return phpMin(iteratorToArray(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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const runes = require('runes');
|
||||
const mt_rand = require('locutus/php/math/mt_rand');
|
||||
@ -86,3 +86,68 @@ export const random: TwingCallable = (executionContext, values: any | null, max:
|
||||
|
||||
return Promise.resolve(_do());
|
||||
}
|
||||
|
||||
export const randomSynchronously: TwingSynchronousCallable = (executionContext, values: any | null, max: number | null): any => {
|
||||
const {environment} = executionContext;
|
||||
const {charset} = environment;
|
||||
|
||||
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) {
|
||||
throw new Error('The random function cannot pick from an empty array.');
|
||||
}
|
||||
|
||||
return values[array_rand(values, 1)];
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {createRange} from "../../../helpers/create-range";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
type Range<V = any> = TwingCallable<[
|
||||
low: V,
|
||||
@ -10,3 +10,13 @@ type Range<V = any> = TwingCallable<[
|
||||
export const range: Range = (_executionContext, low, high, step) => {
|
||||
return Promise.resolve(createRange(low, high, step));
|
||||
}
|
||||
|
||||
type SynchronousRange<V = any> = TwingSynchronousCallable<[
|
||||
low: V,
|
||||
high: V,
|
||||
step: number
|
||||
], Map<number, V>>;
|
||||
|
||||
export const rangeSynchronously: SynchronousRange = (_executionContext, low, high, step) => {
|
||||
return createRange(low, high, step);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {createTemplateLoadingError} from "../../../error/loader";
|
||||
import type {TwingCallable} from "../../../callable-wrapper";
|
||||
import type {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
import {TwingSynchronousTemplate} from "../../../template";
|
||||
|
||||
/**
|
||||
* Returns a template content without rendering it.
|
||||
@ -8,7 +9,7 @@ import type {TwingCallable} from "../../../callable-wrapper";
|
||||
* @param name The template name
|
||||
* @param ignoreMissing Whether to ignore missing templates or not
|
||||
*
|
||||
* @return {Promise<string>} The template source
|
||||
* @return The template source
|
||||
*/
|
||||
export const source: TwingCallable<[
|
||||
name: string,
|
||||
@ -28,3 +29,25 @@ export const source: TwingCallable<[
|
||||
return template?.source.code || null;
|
||||
});
|
||||
};
|
||||
|
||||
export const sourceSynchronously: TwingSynchronousCallable<[
|
||||
name: string,
|
||||
ignoreMissing: boolean
|
||||
], string | null> = (executionContext, name, ignoreMissing) => {
|
||||
const {template} = executionContext;
|
||||
|
||||
let loadedTemplate: TwingSynchronousTemplate | null;
|
||||
|
||||
try {
|
||||
loadedTemplate = template.loadTemplate(executionContext, name)
|
||||
}
|
||||
catch (error) {
|
||||
loadedTemplate = null;
|
||||
}
|
||||
|
||||
if (!ignoreMissing && (loadedTemplate === null)) {
|
||||
throw createTemplateLoadingError([name]);
|
||||
}
|
||||
|
||||
return loadedTemplate?.source.code || null;
|
||||
};
|
||||
|
@ -1,22 +1,10 @@
|
||||
import {createTemplate, TwingTemplate} from "../../../template";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {createSynchronousTemplate, createTemplate, TwingSynchronousTemplate, TwingTemplate} from "../../../template";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
import * as createHash from "create-hash";
|
||||
import {createSource} from "../../../source";
|
||||
import {TwingExecutionContext, TwingSynchronousExecutionContext} from "../../../execution-context";
|
||||
|
||||
/**
|
||||
* Loads a template from a string.
|
||||
*
|
||||
* <pre>
|
||||
* {{ include(template_from_string("Hello {{ name }}")) }}
|
||||
* </pre>
|
||||
*
|
||||
* @param executionContext
|
||||
* @param code
|
||||
* @param name An optional name for the template to be used in error messages
|
||||
*
|
||||
* @returns {Promise<TwingTemplate>}
|
||||
*/
|
||||
export const templateFromString: TwingCallable = (executionContext, code: string, name: string | null): Promise<TwingTemplate> => {
|
||||
const getAST = (executionContext: TwingExecutionContext | TwingSynchronousExecutionContext, code: string, name: string | null) => {
|
||||
const {environment} = executionContext;
|
||||
|
||||
const hash: string = createHash("sha256").update(code).digest("hex").toString();
|
||||
@ -28,8 +16,28 @@ export const templateFromString: TwingCallable = (executionContext, code: string
|
||||
name = `__string_template__${hash}`;
|
||||
}
|
||||
|
||||
const ast = environment.parse(environment.tokenize(createSource(name, code)));
|
||||
const template = createTemplate(ast);
|
||||
return environment.parse(environment.tokenize(createSource(name, code)));
|
||||
};
|
||||
|
||||
return Promise.resolve(template);
|
||||
/**
|
||||
* Loads a template from a string.
|
||||
*
|
||||
* <pre>
|
||||
* {{ include(template_from_string("Hello {{ name }}")) }}
|
||||
* </pre>
|
||||
*
|
||||
* @param executionContext
|
||||
* @param code
|
||||
* @param name An optional name for the template to be used in error messages
|
||||
*/
|
||||
export const templateFromString: TwingCallable = (executionContext, code: string, name: string | null): Promise<TwingTemplate> => {
|
||||
const ast = getAST(executionContext, code, name);
|
||||
|
||||
return Promise.resolve(createTemplate(ast));
|
||||
}
|
||||
|
||||
export const templateFromStringSynchronously: TwingSynchronousCallable = (executionContext, code: string, name: string | null): TwingSynchronousTemplate => {
|
||||
const ast = getAST(executionContext, code, name);
|
||||
|
||||
return createSynchronousTemplate(ast);
|
||||
}
|
||||
|
@ -1,5 +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,
|
||||
@ -13,3 +14,16 @@ export const isConstant: TwingCallable<[
|
||||
) => {
|
||||
return Promise.resolve(comparand === getConstant(executionContext.context, constant, object));
|
||||
};
|
||||
|
||||
export const isConstantSynchronously: TwingSynchronousCallable<[
|
||||
comparand: any,
|
||||
constant: any,
|
||||
object: any | null
|
||||
], boolean> = (
|
||||
executionContext,
|
||||
comparand,
|
||||
constant,
|
||||
object
|
||||
) => {
|
||||
return comparand === getConstant(executionContext.context, constant, object);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const isDefined: TwingCallable<[
|
||||
value: any
|
||||
@ -8,3 +8,12 @@ export const isDefined: TwingCallable<[
|
||||
) => {
|
||||
return Promise.resolve(!!value);
|
||||
};
|
||||
|
||||
export const isDefinedSynchronously: TwingSynchronousCallable<[
|
||||
value: any
|
||||
], boolean> = (
|
||||
_executionContext,
|
||||
value
|
||||
) => {
|
||||
return !!value;
|
||||
};
|
||||
|
@ -1,5 +1,10 @@
|
||||
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} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
const isPlainObject = require('is-plain-object');
|
||||
|
||||
@ -46,3 +46,32 @@ export const isEmpty: TwingCallable<[value: any], boolean> = (executionContext,
|
||||
|
||||
return Promise.resolve(value === false);
|
||||
};
|
||||
|
||||
export const isEmptySynchronously: TwingSynchronousCallable<[value: any], boolean> = (executionContext, value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value.length < 1;
|
||||
}
|
||||
|
||||
if (typeof value[Symbol.iterator] === 'function') {
|
||||
return value[Symbol.iterator]().next().done === true;
|
||||
}
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
if (value.hasOwnProperty('toString') && typeof value.toString === 'function') {
|
||||
return isEmptySynchronously(executionContext, value.toString());
|
||||
}
|
||||
else {
|
||||
return iteratorToArray(value).length < 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value.toString && typeof value.toString === 'function') {
|
||||
return isEmptySynchronously(executionContext, value.toString());
|
||||
}
|
||||
|
||||
return value === false;
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const isEven: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return Promise.resolve(value % 2 === 0);
|
||||
};
|
||||
|
||||
export const isEvenSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return value % 2 === 0;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
* Checks if a variable is traversable.
|
||||
@ -52,3 +52,38 @@ export const isIterable: TwingCallable<[value: any], boolean> = (_executionConte
|
||||
|
||||
return Promise.resolve(_do());
|
||||
};
|
||||
|
||||
export const isIterableSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
/*
|
||||
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;
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const isNull: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return Promise.resolve(value === null);
|
||||
};
|
||||
|
||||
export const isNullSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return value === null;
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const isOdd: TwingCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return Promise.resolve(value % 2 === 1);
|
||||
};
|
||||
|
||||
export const isOddSynchronously: TwingSynchronousCallable<[value: any], boolean> = (_executionContext, value) => {
|
||||
return value % 2 === 1;
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
import {TwingCallable, TwingSynchronousCallable} from "../../../callable-wrapper";
|
||||
|
||||
export const isSameAs: TwingCallable<[a: any, comparand: any], boolean> = (_executionContext, a, comparand) => {
|
||||
return Promise.resolve(a === comparand);
|
||||
};
|
||||
|
||||
export const isSameAsSynchronously: TwingSynchronousCallable<[a: any, comparand: any], boolean> = (_executionContext, a, comparand) => {
|
||||
return a === comparand;
|
||||
};
|
||||
|
@ -1,7 +1,11 @@
|
||||
import {
|
||||
TwingCallableWrapperOptions,
|
||||
TwingCallableArgument,
|
||||
TwingCallable, TwingCallableWrapper, createCallableWrapper
|
||||
TwingCallable,
|
||||
TwingCallableWrapper,
|
||||
createCallableWrapper,
|
||||
TwingSynchronousCallableWrapper,
|
||||
createSynchronousCallableWrapper, TwingSynchronousCallable
|
||||
} from "./callable-wrapper";
|
||||
|
||||
export type TwingFilterOptions = TwingCallableWrapperOptions;
|
||||
@ -10,9 +14,13 @@ export interface TwingFilter extends TwingCallableWrapper {
|
||||
|
||||
}
|
||||
|
||||
export const createFilter = <Callable extends TwingCallable>(
|
||||
export interface TwingSynchronousFilter extends TwingSynchronousCallableWrapper {
|
||||
|
||||
}
|
||||
|
||||
export const createFilter = (
|
||||
name: string,
|
||||
callable: Callable,
|
||||
callable: TwingCallable,
|
||||
acceptedArguments: TwingCallableArgument[],
|
||||
options: TwingFilterOptions = {}
|
||||
): TwingFilter => {
|
||||
@ -24,3 +32,18 @@ export const createFilter = <Callable extends TwingCallable>(
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
export const createSynchronousFilter = (
|
||||
name: string,
|
||||
callable: TwingSynchronousCallable,
|
||||
acceptedArguments: TwingCallableArgument[],
|
||||
options: TwingFilterOptions = {}
|
||||
): TwingSynchronousFilter => {
|
||||
const callableWrapper = createSynchronousCallableWrapper(name, callable, acceptedArguments, options);
|
||||
|
||||
const filter: TwingSynchronousFilter = {
|
||||
...callableWrapper
|
||||
};
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
@ -1,16 +1,24 @@
|
||||
import {
|
||||
TwingCallableWrapperOptions,
|
||||
TwingCallableArgument,
|
||||
TwingCallable, TwingCallableWrapper, createCallableWrapper
|
||||
TwingCallable,
|
||||
TwingCallableWrapper,
|
||||
createCallableWrapper,
|
||||
TwingSynchronousCallableWrapper,
|
||||
TwingSynchronousCallable, createSynchronousCallableWrapper
|
||||
} from "./callable-wrapper";
|
||||
|
||||
export interface TwingFunction extends TwingCallableWrapper {
|
||||
|
||||
}
|
||||
|
||||
export const createFunction = <Callable extends TwingCallable>(
|
||||
export interface TwingSynchronousFunction extends TwingSynchronousCallableWrapper {
|
||||
|
||||
}
|
||||
|
||||
export const createFunction = (
|
||||
name: string,
|
||||
callable: Callable,
|
||||
callable: TwingCallable,
|
||||
acceptedArguments: TwingCallableArgument[],
|
||||
options: TwingCallableWrapperOptions = {}
|
||||
): TwingFunction => {
|
||||
@ -18,3 +26,14 @@ export const createFunction = <Callable extends TwingCallable>(
|
||||
|
||||
return callableWrapper;
|
||||
};
|
||||
|
||||
export const createSynchronousFunction = (
|
||||
name: string,
|
||||
callable: TwingSynchronousCallable,
|
||||
acceptedArguments: TwingCallableArgument[],
|
||||
options: TwingCallableWrapperOptions = {}
|
||||
): TwingSynchronousFunction => {
|
||||
const callableWrapper = createSynchronousCallableWrapper(name, callable, acceptedArguments, options);
|
||||
|
||||
return callableWrapper;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ export const asort = async (map: Map<any, any>, compareFunction?: (a: any, b: an
|
||||
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) {
|
||||
@ -20,7 +20,38 @@ export const asort = async (map: Map<any, any>, compareFunction?: (a: any, b: an
|
||||
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);
|
||||
const values = [...map.values()];
|
||||
|
||||
let sortedValues: Array<any>;
|
||||
|
||||
if (compareFunction) {
|
||||
sortedValues = values.sort(compareFunction);
|
||||
}
|
||||
else {
|
||||
sortedValues = values.sort();
|
||||
}
|
||||
|
||||
for (const [key, value] of map) {
|
||||
const index = sortedValues.indexOf(value);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {iterate} from "./iterate";
|
||||
import {iterate, iterateSynchronously} from "./iterate";
|
||||
|
||||
/**
|
||||
* Split an hash into chunks.
|
||||
@ -34,3 +34,36 @@ export async function chunk(hash: any, size: number, preserveKeys: boolean): Pro
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an hash into chunks, synchronously.
|
||||
*
|
||||
* @param {*} hash
|
||||
* @param {number} size
|
||||
* @param {boolean} preserveKeys
|
||||
*/
|
||||
export function chunkSynchronously(hash: any, size: number, preserveKeys: boolean): Array<Map<any, any>> {
|
||||
let result: Array<Map<any, any>> = [];
|
||||
let count = 0;
|
||||
let currentMap: Map<any, any> | null;
|
||||
|
||||
iterateSynchronously(hash, (key: any, value: any) => {
|
||||
if (!currentMap) {
|
||||
currentMap = new Map();
|
||||
|
||||
result.push(currentMap);
|
||||
}
|
||||
|
||||
currentMap.set(preserveKeys ? key : count, value);
|
||||
|
||||
count++;
|
||||
|
||||
if (count >= size) {
|
||||
count = 0;
|
||||
currentMap = null;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {isAMarkup, TwingMarkup} from "../markup";
|
||||
import {TwingEnvironment} from "../environment";
|
||||
import {TwingEnvironment, TwingSynchronousEnvironment} from "../environment";
|
||||
import {TwingEscapingStrategy} from "../escaping-strategy";
|
||||
import {TwingTemplate} from "../template";
|
||||
import {TwingSynchronousTemplate, TwingTemplate} from "../template";
|
||||
|
||||
export const escapeValue = (
|
||||
template: TwingTemplate,
|
||||
@ -35,3 +35,36 @@ export const escapeValue = (
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
export const escapeValueSynchronously = (
|
||||
template: TwingTemplate | TwingSynchronousTemplate,
|
||||
environment: TwingEnvironment | TwingSynchronousEnvironment,
|
||||
value: string | boolean | TwingMarkup | null | undefined,
|
||||
strategy: TwingEscapingStrategy | string,
|
||||
charset: string | null
|
||||
): string | boolean | TwingMarkup => {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isAMarkup(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let result: string;
|
||||
|
||||
if ((value === null) || (value === undefined)) {
|
||||
result = '';
|
||||
}
|
||||
else {
|
||||
const strategyHandler = environment.escapingStrategyHandlers[strategy];
|
||||
|
||||
if (strategyHandler === undefined) {
|
||||
throw new Error(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`);
|
||||
}
|
||||
|
||||
result = strategyHandler(value.toString(), charset || environment.charset, template.name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -4,7 +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} from "../environment";
|
||||
import type {TwingEnvironment, TwingSynchronousEnvironment} from "../environment";
|
||||
|
||||
const isObject = require('isobject');
|
||||
|
||||
@ -37,7 +37,7 @@ export const getAttribute = (
|
||||
strict: boolean
|
||||
): Promise<any> => {
|
||||
const {sandboxPolicy} = environment;
|
||||
|
||||
|
||||
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !strict : shouldIgnoreStrictCheck;
|
||||
|
||||
const _do = (): any => {
|
||||
@ -75,10 +75,10 @@ export const getAttribute = (
|
||||
}
|
||||
}
|
||||
|
||||
if ((type === "array")
|
||||
|| (isAMapLike(object))
|
||||
if ((type === "array")
|
||||
|| (isAMapLike(object))
|
||||
|| (Array.isArray(object))
|
||||
|| (object === null)
|
||||
|| (object === null)
|
||||
|| (typeof object !== 'object')
|
||||
) {
|
||||
if (shouldTestExistence) {
|
||||
@ -88,7 +88,7 @@ export const getAttribute = (
|
||||
if (shouldIgnoreStrictCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (object === null) {
|
||||
// object is null
|
||||
if (type === "array") {
|
||||
@ -265,3 +265,236 @@ export const getAttribute = (
|
||||
return Promise.reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const getAttributeSynchronously = (
|
||||
environment: TwingSynchronousEnvironment,
|
||||
object: any,
|
||||
attribute: any,
|
||||
methodArguments: Map<any, any>,
|
||||
type: TwingAttributeAccessorCallType,
|
||||
shouldTestExistence: boolean,
|
||||
shouldIgnoreStrictCheck: boolean | null,
|
||||
sandboxed: boolean,
|
||||
strict: boolean
|
||||
): any => {
|
||||
const {sandboxPolicy} = environment;
|
||||
|
||||
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !strict : shouldIgnoreStrictCheck;
|
||||
|
||||
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()]);
|
||||
};
|
||||
|
@ -8,9 +8,9 @@
|
||||
*
|
||||
* @returns {any}
|
||||
*/
|
||||
import {TwingContext} from "../context";
|
||||
import {TwingContext, TwingContext2} from "../context";
|
||||
|
||||
export function getConstant(context: TwingContext<any, any>, name: string, object: any | null): any {
|
||||
export function getConstant(context: TwingContext<any, any> | TwingContext2, name: string, object: any | null): any {
|
||||
if (object) {
|
||||
return object[name];
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {TwingContext} from "../context";
|
||||
import type {TwingContext, TwingContext2} from "../context";
|
||||
|
||||
export const getContextValue = (
|
||||
charset: string,
|
||||
@ -46,3 +46,59 @@ export const getContextValue = (
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
export const getContextValueSynchronously = (
|
||||
charset: string,
|
||||
templateName: string,
|
||||
isStrictVariables: boolean,
|
||||
context: TwingContext2,
|
||||
globals: TwingContext2,
|
||||
name: string,
|
||||
isAlwaysDefined: boolean,
|
||||
shouldIgnoreStrictCheck: boolean,
|
||||
shouldTestExistence: boolean
|
||||
): any => {
|
||||
const specialNames = new Map<string, any>([
|
||||
['_self', templateName],
|
||||
['_context', context],
|
||||
['_charset', charset]
|
||||
]);
|
||||
|
||||
const isSpecial = () => {
|
||||
return specialNames.has(name);
|
||||
};
|
||||
|
||||
let result: any;
|
||||
|
||||
if (shouldTestExistence) {
|
||||
if (isSpecial()) {
|
||||
result = true;
|
||||
} else {
|
||||
result = context.has(name) || globals.has(name);
|
||||
}
|
||||
} else if (isSpecial()) {
|
||||
result = specialNames.get(name);
|
||||
} else if (isAlwaysDefined) {
|
||||
result = context.get(name);
|
||||
|
||||
if (result === undefined) {
|
||||
result = globals.get(name);
|
||||
}
|
||||
} else {
|
||||
if (shouldIgnoreStrictCheck || !isStrictVariables) {
|
||||
result = context.has(name) ? context.get(name) : (globals.has(name) ? globals.get(name) : null);
|
||||
} else {
|
||||
result = context.get(name);
|
||||
|
||||
if (result === undefined) {
|
||||
result = globals.get(name);
|
||||
}
|
||||
|
||||
if (result === undefined) {
|
||||
throw new Error(`Variable "${name}" does not exist.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {TwingFilter} from "../filter";
|
||||
import type {TwingFilter, TwingSynchronousFilter} from "../filter";
|
||||
|
||||
/**
|
||||
* Get a filter by name.
|
||||
@ -7,10 +7,10 @@ import type {TwingFilter} from "../filter";
|
||||
*
|
||||
* @return {TwingFilter|false} A TwingFilter instance or false if the filter does not exist
|
||||
*/
|
||||
export const getFilter = (
|
||||
filters: Map<string, TwingFilter>,
|
||||
export const getFilter = <Filter extends TwingFilter | TwingSynchronousFilter>(
|
||||
filters: Map<string, Filter>,
|
||||
name: string
|
||||
): TwingFilter | null => {
|
||||
): Filter | null => {
|
||||
const result = filters.get(name);
|
||||
|
||||
if (result) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type {TwingFunction} from "../function";
|
||||
import {TwingSynchronousFunction} from "../function";
|
||||
|
||||
/**
|
||||
* Get a function by name.
|
||||
@ -6,10 +7,10 @@ import type {TwingFunction} from "../function";
|
||||
* @param {string} name function name
|
||||
* @returns {TwingFunction} A TwingFunction instance or null if the function does not exist
|
||||
*/
|
||||
export const getFunction = (
|
||||
functions: Map<string, TwingFunction>,
|
||||
export const getFunction = <Function extends TwingFunction | TwingSynchronousFunction>(
|
||||
functions: Map<string, Function>,
|
||||
name: string
|
||||
): TwingFunction | null => {
|
||||
): Function | null => {
|
||||
const result = functions.get(name);
|
||||
|
||||
if (result) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type {TwingTest} from "../test";
|
||||
import {TwingSynchronousTest} from "../test";
|
||||
|
||||
/**
|
||||
* Gets a test by name.
|
||||
@ -6,10 +7,10 @@ import type {TwingTest} from "../test";
|
||||
* @param {string} name The test name
|
||||
* @returns {TwingTest} A MyTest instance or null if the test does not exist
|
||||
*/
|
||||
export const getTest = (
|
||||
tests: Map<string, TwingTest>,
|
||||
export const getTest = <Test extends TwingTest | TwingSynchronousTest>(
|
||||
tests: Map<string, Test>,
|
||||
name: string
|
||||
): TwingTest | null => {
|
||||
): Test | null => {
|
||||
const result = tests.get(name);
|
||||
|
||||
if (result) {
|
||||
|
@ -31,13 +31,6 @@ export function isIn(value: number | string | object | TwingMarkup, compare: str
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (typeof compare === 'object') {
|
||||
for (const key in compare) {
|
||||
if (compareHelper((compare as any)[key], value)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,5 +1,5 @@
|
||||
const _isPlainObject = require('is-plain-object');
|
||||
|
||||
export function isPlainObject(thing: any) {
|
||||
export function isPlainObject(thing: any): thing is Record<string, any> {
|
||||
return _isPlainObject(thing);
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import {isPlainObject} from "./is-plain-object";
|
||||
|
||||
/**
|
||||
* Check that an object is traversable in the sense of PHP,
|
||||
* i.e. implements PHP Traversable interface
|
||||
@ -6,6 +8,10 @@
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isTraversable(value: any) {
|
||||
if (isPlainObject(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((value !== null) && (value !== undefined)) {
|
||||
if (typeof value === 'string') {
|
||||
return false;
|
||||
|
@ -1,4 +1,5 @@
|
||||
export type IterateCallback = (key: any, value: any) => Promise<void>;
|
||||
export type SynchronousIterateCallback = (key: any, value: any) => void;
|
||||
|
||||
/**
|
||||
* Executes the provided function once for each element of an iterable.
|
||||
@ -35,3 +36,34 @@ export const iterate = async (iterable: any, callback: IterateCallback): Promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const iterateSynchronously = (iterable: any, callback: SynchronousIterateCallback): void => {
|
||||
// todo: maybe useless when we pass records instead of TwingContext
|
||||
if (iterable.entries) {
|
||||
for (const [key, value] of iterable.entries()) {
|
||||
callback(key, value);
|
||||
}
|
||||
}
|
||||
else if (typeof iterable[Symbol.iterator] === 'function') {
|
||||
let i: number = 0;
|
||||
|
||||
for (let value of iterable) {
|
||||
callback(i++, value);
|
||||
}
|
||||
}
|
||||
// todo: check why this is not covered anymore
|
||||
// else if (typeof iterable['next'] === 'function') {
|
||||
// let i: number = 0;
|
||||
// let next: any;
|
||||
//
|
||||
// while ((next = iterable.next()) && !next.done) {
|
||||
// callback(i++, next.value)
|
||||
// }
|
||||
// }
|
||||
else {
|
||||
for (const key in iterable) {
|
||||
callback(key, iterable[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function iteratorToHash(value: any) {
|
||||
let result: any;
|
||||
export function iteratorToHash(value: any): Record<any, any> {
|
||||
let result: Record<any, any>;
|
||||
|
||||
if (value.entries) {
|
||||
result = {};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {TwingContext} from "../context";
|
||||
import {iteratorToMap} from "./iterator-to-map";
|
||||
|
||||
export type MapLike<K, V> = Map<K, V> | TwingContext<K, V>;
|
||||
export type MapLike<K extends string, V> = Map<K, V> | TwingContext<K, V>;
|
||||
|
||||
export function isAMapLike(candidate: any): candidate is MapLike<any, any> {
|
||||
return candidate !== null &&
|
||||
@ -30,6 +30,23 @@ export const every = async (
|
||||
return true;
|
||||
};
|
||||
|
||||
export const everySynchronously = (
|
||||
iterable: MapLike<any, any> | Array<any>,
|
||||
comparator: (value: any, key: any) => boolean
|
||||
): boolean => {
|
||||
if (Array.isArray(iterable)) {
|
||||
iterable = iteratorToMap(iterable);
|
||||
}
|
||||
|
||||
for (const [key, value] of iterable) {
|
||||
if (comparator(value, key) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const some = async (
|
||||
iterable: MapLike<any, any> | Array<any>,
|
||||
comparator: (value: any, key: any) => Promise<boolean>
|
||||
@ -45,4 +62,21 @@ export const some = async (
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
export const someSynchronously = (
|
||||
iterable: MapLike<any, any> | Array<any>,
|
||||
comparator: (value: any, key: any) => boolean
|
||||
): boolean => {
|
||||
if (Array.isArray(iterable)) {
|
||||
iterable = iteratorToMap(iterable);
|
||||
}
|
||||
|
||||
for (const [key, value] of iterable) {
|
||||
if (comparator(value, key) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
@ -17,3 +17,20 @@ export function getTraceableMethod<M extends (...args: Array<any>) => Promise<an
|
||||
});
|
||||
}) as typeof method;
|
||||
}
|
||||
|
||||
export function getSynchronousTraceableMethod<M extends (...args: Array<any>) => any>(method: M, location: {
|
||||
line: number;
|
||||
column: number;
|
||||
}, templateSource: TwingSource): M {
|
||||
return ((...args: Array<any>) => {
|
||||
try {
|
||||
return method(...args);
|
||||
} catch (error) {
|
||||
if (!isATwingError(error as Error)) {
|
||||
throw createRuntimeError((error as Error).message, location, templateSource, (error as Error));
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}) as typeof method;
|
||||
}
|
||||
|
@ -44,3 +44,46 @@ export interface TwingLoader {
|
||||
*/
|
||||
exists: (name: string, from: string | null) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousLoader {
|
||||
/**
|
||||
* Returns the source for a given template logical name.
|
||||
*
|
||||
* @param {string} name The template logical name
|
||||
* @param {TwingSource} from The source that initiated the template loading
|
||||
*/
|
||||
getSource: (name: string, from: string | null) => TwingSource | null;
|
||||
|
||||
/**
|
||||
* Resolve a template FQN from its name and the name of the template that initiated the loading.
|
||||
*
|
||||
* @param {string} name The name of the template to load
|
||||
* @param {TwingSource} from The source that initiated the template loading
|
||||
*
|
||||
* @returns The cache key
|
||||
*/
|
||||
resolve: (name: string, from: string | null) => string | null;
|
||||
|
||||
/**
|
||||
* Returns true if the template is still fresh.
|
||||
*
|
||||
* @param {string} name The template name
|
||||
* @param {number} time Timestamp of the last modification time of the cached template
|
||||
* @param {TwingSource} from The source that initiated the template loading
|
||||
*
|
||||
* @returns true if the template is fresh, false otherwise; null if it does not exist
|
||||
*
|
||||
* @throws TwingErrorLoader When name is not found
|
||||
*/
|
||||
isFresh: (name: string, time: number, from: string | null) => boolean | null;
|
||||
|
||||
/**
|
||||
* Check if we have the source code of a template, given its name.
|
||||
*
|
||||
* @param {string} name The name of the template to check if we can load
|
||||
* @param {TwingSource} from The source that initiated the template loading
|
||||
*
|
||||
* @returns If the template source code is handled by this loader or not
|
||||
*/
|
||||
exists: (name: string, from: string | null) => boolean;
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
import type {TwingLoader} from "../loader";
|
||||
import type {TwingLoader, TwingSynchronousLoader} from "../loader";
|
||||
import {createSource} from "../source";
|
||||
|
||||
export interface TwingArrayLoader extends TwingLoader {
|
||||
setTemplate(name: string, template: string): void;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousArrayLoader extends TwingSynchronousLoader {
|
||||
setTemplate(name: string, template: string): void;
|
||||
}
|
||||
|
||||
export const createArrayLoader = (
|
||||
templates: Record<string, string>
|
||||
): TwingArrayLoader => {
|
||||
@ -42,3 +46,36 @@ export const createArrayLoader = (
|
||||
|
||||
return loader;
|
||||
};
|
||||
|
||||
export const createSynchronousArrayLoader = (
|
||||
templates: Record<string, string>
|
||||
): TwingSynchronousArrayLoader => {
|
||||
const loader: TwingSynchronousArrayLoader = {
|
||||
setTemplate: (name, template) => {
|
||||
templates[name] = template;
|
||||
},
|
||||
getSource: (name, from) => {
|
||||
if (loader.exists(name, from)) {
|
||||
return createSource(name, templates[name]);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
exists(name) {
|
||||
return templates[name] !== undefined;
|
||||
},
|
||||
resolve: (name, from) => {
|
||||
if (loader.exists(name, from)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
isFresh: () => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return loader;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {TwingLoader} from "../loader";
|
||||
import type {TwingLoader, TwingSynchronousLoader} from "../loader";
|
||||
import type {TwingSource} from "../source";
|
||||
import {join, isAbsolute, dirname, normalize} from "path";
|
||||
import {createSource} from "../source";
|
||||
@ -13,9 +13,9 @@ export interface TwingFilesystemLoaderFilesystemStats {
|
||||
|
||||
export interface TwingFilesystemLoaderFilesystem {
|
||||
stat(
|
||||
path: string,
|
||||
path: string,
|
||||
callback: (
|
||||
error: Error | null,
|
||||
error: Error | null,
|
||||
stats: TwingFilesystemLoaderFilesystemStats | null
|
||||
) => void
|
||||
): void;
|
||||
@ -23,6 +23,12 @@ export interface TwingFilesystemLoaderFilesystem {
|
||||
readFile(path: string, callback: (error: Error | null, data: Buffer | null) => void): void;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousFilesystemLoaderFilesystem {
|
||||
statSync(path: string): TwingFilesystemLoaderFilesystemStats | null;
|
||||
|
||||
readFileSync(path: string): Buffer | null;
|
||||
}
|
||||
|
||||
export interface TwingFilesystemLoader extends TwingLoader {
|
||||
/**
|
||||
* Adds a path where templates are stored.
|
||||
@ -41,17 +47,36 @@ export interface TwingFilesystemLoader extends TwingLoader {
|
||||
prependPath(path: string, namespace?: string | null): void;
|
||||
}
|
||||
|
||||
export interface TwingSynchronousFilesystemLoader extends TwingSynchronousLoader {
|
||||
/**
|
||||
* Adds a path where templates are stored.
|
||||
*
|
||||
* @param path A path where to look for templates
|
||||
* @param namespace A path namespace
|
||||
*/
|
||||
addPath(path: string, namespace?: string | null): void;
|
||||
|
||||
/**
|
||||
* Prepends a path where templates are stored.
|
||||
*
|
||||
* @param path A path where to look for templates
|
||||
* @param namespace A path namespace
|
||||
*/
|
||||
prependPath(path: string, namespace?: string | null): void;
|
||||
}
|
||||
|
||||
export const createFilesystemLoader = (
|
||||
filesystem: TwingFilesystemLoaderFilesystem
|
||||
): TwingFilesystemLoader => {
|
||||
const namespacedPaths: Map<string | null, Array<string>> = new Map();
|
||||
|
||||
|
||||
const stat = (path: string): Promise<TwingFilesystemLoaderFilesystemStats | null> => {
|
||||
return new Promise((resolve) => {
|
||||
filesystem.stat(path, (error, stats) => {
|
||||
if (error) {
|
||||
resolve(null);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
resolve(stats);
|
||||
}
|
||||
});
|
||||
@ -73,13 +98,14 @@ export const createFilesystemLoader = (
|
||||
// * if not found yet, resolve from "from"
|
||||
const resolve = (name: string, from: string | null): Promise<string | null> => {
|
||||
name = normalize(from ? resolvePathFromSource(name, from) : name);
|
||||
|
||||
|
||||
const findTemplateInPath = async (path: string): Promise<string | null> => {
|
||||
const stats = await stat(path);
|
||||
|
||||
|
||||
if (stats && stats.isFile()) {
|
||||
return Promise.resolve(path);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
@ -89,10 +115,11 @@ export const createFilesystemLoader = (
|
||||
.then((templatePath) => {
|
||||
if (templatePath) {
|
||||
return templatePath;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// then, search for the template from its namespaced name
|
||||
const [namespace, shortname] = parseName(name);
|
||||
|
||||
|
||||
const paths = namespacedPaths.get(namespace) || ['.'];
|
||||
|
||||
const findTemplateInPathAtIndex = async (index: number): Promise<string | null> => {
|
||||
@ -102,11 +129,13 @@ export const createFilesystemLoader = (
|
||||
|
||||
if (templatePath) {
|
||||
return Promise.resolve(templatePath);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// let's continue searching
|
||||
return findTemplateInPathAtIndex(index + 1);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
@ -151,7 +180,8 @@ export const createFilesystemLoader = (
|
||||
|
||||
if (!namespacePaths) {
|
||||
namespacedPaths.set(namespace!, [path]);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
namespacePaths.unshift(path);
|
||||
}
|
||||
}
|
||||
@ -170,12 +200,14 @@ export const createFilesystemLoader = (
|
||||
.then((path) => {
|
||||
if (path === null) {
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return new Promise<TwingSource | null>((resolve, reject) => {
|
||||
filesystem.readFile(path, (error, data) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
resolve(createSource(path, data!.toString()));
|
||||
}
|
||||
});
|
||||
@ -188,7 +220,8 @@ export const createFilesystemLoader = (
|
||||
.then((path) => {
|
||||
if (path === null) {
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return stat(path)
|
||||
.then((stats) => {
|
||||
return stats!.mtime.getTime() <= time
|
||||
@ -199,3 +232,154 @@ export const createFilesystemLoader = (
|
||||
prependPath
|
||||
};
|
||||
};
|
||||
|
||||
export const createSynchronousFilesystemLoader = (
|
||||
filesystem: TwingSynchronousFilesystemLoaderFilesystem
|
||||
): TwingSynchronousFilesystemLoader => {
|
||||
const namespacedPaths: Map<string | null, Array<string>> = new Map();
|
||||
|
||||
const stat = (path: string): TwingFilesystemLoaderFilesystemStats | null => {
|
||||
try {
|
||||
return filesystem.statSync(path);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const resolvePathFromSource = (name: string, from: string): string => {
|
||||
if (name && !isAbsolute(name) && name.startsWith('.')) {
|
||||
name = join(dirname(from), name);
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
// todo: rework
|
||||
// * if no slash, resolve from "from"
|
||||
// * if contains a slash, extract namespace and check if registered:
|
||||
// * if so, resolve from namespace
|
||||
// * if not found yet, resolve from "from"
|
||||
const resolve = (name: string, from: string | null): string | null => {
|
||||
name = normalize(from ? resolvePathFromSource(name, from) : name);
|
||||
|
||||
const findTemplateInPath = (path: string): string | null => {
|
||||
const stats = stat(path);
|
||||
|
||||
if (stats && stats.isFile()) {
|
||||
return path;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// first search for the template from its fully qualified name
|
||||
const templatePath = findTemplateInPath(name);
|
||||
|
||||
if (templatePath) {
|
||||
return templatePath;
|
||||
}
|
||||
else {
|
||||
// then, search for the template from its namespaced name
|
||||
const [namespace, shortname] = parseName(name);
|
||||
|
||||
const paths = namespacedPaths.get(namespace) || ['.'];
|
||||
|
||||
const findTemplateInPathAtIndex = (index: number): string | null => {
|
||||
if (index < paths.length) {
|
||||
const path = paths[index];
|
||||
const templatePath = findTemplateInPath(join(path, shortname));
|
||||
|
||||
if (templatePath) {
|
||||
return templatePath;
|
||||
}
|
||||
else {
|
||||
// let's continue searching
|
||||
return findTemplateInPathAtIndex(index + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return findTemplateInPathAtIndex(0);
|
||||
}
|
||||
};
|
||||
|
||||
const parseName = (name: string): [string | null, string] => {
|
||||
// only non-relative names can be namespace references
|
||||
if (name[0] !== '.') {
|
||||
const position = name.indexOf('/');
|
||||
|
||||
if (position >= 0) {
|
||||
const namespace = name.substring(0, position);
|
||||
const shortname = name.substring(position + 1);
|
||||
|
||||
return [namespace, shortname];
|
||||
}
|
||||
}
|
||||
|
||||
return [null, name];
|
||||
};
|
||||
|
||||
const addPath: TwingFilesystemLoader["addPath"] = (path, namespace = null) => {
|
||||
let namespacePaths = namespacedPaths.get(namespace);
|
||||
|
||||
if (!namespacePaths) {
|
||||
namespacePaths = [];
|
||||
|
||||
namespacedPaths.set(namespace, namespacePaths);
|
||||
}
|
||||
|
||||
namespacePaths.push(rtrim(path, '\/\\\\'));
|
||||
}
|
||||
|
||||
const prependPath: TwingFilesystemLoader["prependPath"] = (path, namespace = null) => {
|
||||
path = rtrim(path, '\/\\\\');
|
||||
|
||||
const namespacePaths = namespacedPaths.get(namespace);
|
||||
|
||||
if (!namespacePaths) {
|
||||
namespacedPaths.set(namespace!, [path]);
|
||||
}
|
||||
else {
|
||||
namespacePaths.unshift(path);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addPath,
|
||||
exists: (name, from) => {
|
||||
const path = resolve(name, from);
|
||||
|
||||
return path !== null;
|
||||
},
|
||||
resolve,
|
||||
getSource: (name, from) => {
|
||||
const path = resolve(name, from);
|
||||
|
||||
if (path === null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
const data = filesystem.readFileSync(path);
|
||||
|
||||
return createSource(path, data!.toString());
|
||||
}
|
||||
},
|
||||
isFresh: (name, time, from) => {
|
||||
const path = resolve(name, from);
|
||||
|
||||
if (path === null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
const stats = stat(path);
|
||||
|
||||
return stats!.mtime.getTime() <= time
|
||||
}
|
||||
},
|
||||
prependPath
|
||||
};
|
||||
};
|
||||
|
@ -1,51 +1,63 @@
|
||||
import type {TwingBaseNode} from "./node";
|
||||
import type {TwingExecutionContext} from "./execution-context";
|
||||
import {executeBinaryNode} from "./node-executor/expression/binary";
|
||||
import {executeTemplateNode} from "./node-executor/template";
|
||||
import {executePrintNode} from "./node-executor/print";
|
||||
import {executeTextNode} from "./node-executor/text";
|
||||
import {executeCallNode} from "./node-executor/expression/call";
|
||||
import {executeMethodCall} from "./node-executor/expression/method-call";
|
||||
import {executeAssignmentNode} from "./node-executor/expression/assignment";
|
||||
import {executeImportNode} from "./node-executor/import";
|
||||
import {executeParentFunction} from "./node-executor/expression/parent-function";
|
||||
import {executeBlockFunction} from "./node-executor/expression/block-function";
|
||||
import {executeBlockReferenceNode} from "./node-executor/block-reference";
|
||||
import {executeUnaryNode} from "./node-executor/expression/unary";
|
||||
import {executeArrayNode} from "./node-executor/expression/array";
|
||||
import {executeHashNode} from "./node-executor/expression/hash";
|
||||
import {executeAttributeAccessorNode} from "./node-executor/expression/attribute-accessor";
|
||||
import {executeNameNode} from "./node-executor/expression/name";
|
||||
import {executeSetNode} from "./node-executor/set";
|
||||
import {executeIfNode} from "./node-executor/if";
|
||||
import {executeForNode} from "./node-executor/for";
|
||||
import {executeForLoopNode} from "./node-executor/for-loop";
|
||||
import {executeCheckToStringNode} from "./node-executor/check-to-string";
|
||||
import {executeConditionalNode} from "./node-executor/expression/conditional";
|
||||
import {executeEmbedNode} from "./node-executor/include/embed";
|
||||
import {executeIncludeNode} from "./node-executor/include/include";
|
||||
import {executeWithNode} from "./node-executor/with";
|
||||
import {executeSpacelessNode} from "./node-executor/spaceless";
|
||||
import {executeApplyNode} from "./node-executor/apply";
|
||||
import {executeEscapeNode} from "./node-executor/expression/escape";
|
||||
import {executeArrowFunctionNode} from "./node-executor/expression/arrow-function";
|
||||
import {executeSandboxNode} from "./node-executor/sandbox";
|
||||
import {executeDoNode} from "./node-executor/do";
|
||||
import {executeDeprecatedNode} from "./node-executor/deprecated";
|
||||
import {executeSpreadNode} from "./node-executor/expression/spread";
|
||||
import {executeCheckSecurityNode} from "./node-executor/check-security";
|
||||
import {executeFlushNode} from "./node-executor/flush";
|
||||
import {executeBinaryNode, executeBinaryNodeSynchronously} from "./node-executor/expression/binary";
|
||||
import {executeTemplateNode, executeTemplateNodeSynchronously} from "./node-executor/template";
|
||||
import {executePrintNode, executePrintNodeSynchronously} from "./node-executor/print";
|
||||
import {executeTextNode, executeTextNodeSynchronously} from "./node-executor/text";
|
||||
import {executeCallNode, executeCallNodeSynchronously} from "./node-executor/expression/call";
|
||||
import {executeMethodCall, executeMethodCallSynchronously} from "./node-executor/expression/method-call";
|
||||
import {executeAssignmentNode, executeAssignmentNodeSynchronously} from "./node-executor/expression/assignment";
|
||||
import {executeImportNode, executeImportNodeSynchronously} from "./node-executor/import";
|
||||
import {executeParentFunction, executeParentFunctionSynchronously} from "./node-executor/expression/parent-function";
|
||||
import {executeBlockFunction, executeSynchronousBlockFunction} from "./node-executor/expression/block-function";
|
||||
import {executeBlockReferenceNode, executeBlockReferenceNodeSynchronously} from "./node-executor/block-reference";
|
||||
import {executeUnaryNode, executeUnaryNodeSynchronously} from "./node-executor/expression/unary";
|
||||
import {executeArrayNode, executeArrayNodeSynchronously} from "./node-executor/expression/array";
|
||||
import {executeHashNode, executeHashNodeSynchronously} from "./node-executor/expression/hash";
|
||||
import {
|
||||
executeAttributeAccessorNode,
|
||||
executeAttributeAccessorNodeSynchronously
|
||||
} from "./node-executor/expression/attribute-accessor";
|
||||
import {executeNameNode, executeNameNodeSynchronously} from "./node-executor/expression/name";
|
||||
import {executeSetNode, executeSetNodeSynchronously} from "./node-executor/set";
|
||||
import {executeIfNode, executeIfNodeSynchronously} from "./node-executor/if";
|
||||
import {executeForNode, executeForNodeSynchronously} from "./node-executor/for";
|
||||
import {executeForLoopNode, executeForLoopNodeSynchronously} from "./node-executor/for-loop";
|
||||
import {executeCheckToStringNode, executeCheckToStringNodeSynchronously} from "./node-executor/check-to-string";
|
||||
import {executeConditionalNode, executeConditionalNodeSynchronously} from "./node-executor/expression/conditional";
|
||||
import {executeEmbedNode, executeEmbedNodeSynchronously} from "./node-executor/include/embed";
|
||||
import {executeIncludeNode, executeIncludeNodeSynchronously} from "./node-executor/include/include";
|
||||
import {executeWithNode, executeWithNodeSynchronously} from "./node-executor/with";
|
||||
import {executeSpacelessNode, executeSpacelessNodeSynchronously} from "./node-executor/spaceless";
|
||||
import {executeApplyNode, executeApplyNodeSynchronously} from "./node-executor/apply";
|
||||
import {executeEscapeNode, executeEscapeNodeSynchronously} from "./node-executor/expression/escape";
|
||||
import {
|
||||
executeArrowFunctionNode,
|
||||
executeArrowFunctionNodeSynchronously
|
||||
} from "./node-executor/expression/arrow-function";
|
||||
import {executeSandboxNode, executeSandboxNodeSynchronously} from "./node-executor/sandbox";
|
||||
import {executeDoNode, executeDoNodeSynchronously} from "./node-executor/do";
|
||||
import {executeDeprecatedNode, executeDeprecatedNodeSynchronously} from "./node-executor/deprecated";
|
||||
import {executeSpreadNode, executeSpreadNodeSynchronously} from "./node-executor/expression/spread";
|
||||
import {executeCheckSecurityNode, executeCheckSecurityNodeSynchronously} from "./node-executor/check-security";
|
||||
import {executeFlushNode, executeFlushNodeSynchronously} from "./node-executor/flush";
|
||||
import {createRuntimeError} from "./error/runtime";
|
||||
import {executeConstantNode} from "./node-executor/constant";
|
||||
import {executeLineNode} from "./node-executor/line";
|
||||
import {executeCommentNode} from "./node-executor/comment";
|
||||
import {executeBaseNode} from "./node-executor/base";
|
||||
import {executeConstantNode, executeConstantNodeSynchronously} from "./node-executor/constant";
|
||||
import {executeLineNode, executeLineNodeSynchronously} from "./node-executor/line";
|
||||
import {executeCommentNode, executeCommentNodeSynchronously} from "./node-executor/comment";
|
||||
import {executeBaseNode, 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
|
||||
) => any;
|
||||
|
||||
const binaryNodeTypes = ["add", "and", "bitwise_and", "bitwise_or", "bitwise_xor", "concatenate", "divide", "divide_and_floor", "ends_with", "has_every", "has_some", "is_equal_to", "is_greater_than", "is_greater_than_or_equal_to", "is_in", "is_less_than", "is_less_than_or_equal_to", "is_not_equal_to", "is_not_in", "matches", "modulo", "multiply", "or", "power", "range", "spaceship", "starts_with", "subtract"];
|
||||
|
||||
const isABinaryNode = (node: TwingBaseNode): boolean => {
|
||||
@ -202,3 +214,136 @@ export const executeNode: TwingNodeExecutor = (node, executionContext) => {
|
||||
|
||||
return executor(node, executionContext);
|
||||
};
|
||||
|
||||
export const executeNodeSynchronously: TwingSynchronousNodeExecutor = (node, executionContext) => {
|
||||
let executor: TwingSynchronousNodeExecutor<any>;
|
||||
|
||||
if (isABinaryNode(node)) {
|
||||
executor = executeBinaryNodeSynchronously;
|
||||
}
|
||||
else if (isACallNode(node)) {
|
||||
executor = executeCallNodeSynchronously;
|
||||
}
|
||||
else if (isAUnaryNode(node)) {
|
||||
executor = executeUnaryNodeSynchronously;
|
||||
}
|
||||
else if (node.type === null) {
|
||||
executor = executeBaseNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "apply") {
|
||||
executor = executeApplyNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "array") {
|
||||
executor = executeArrayNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "arrow_function") {
|
||||
executor = executeArrowFunctionNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "assignment") {
|
||||
executor = executeAssignmentNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "attribute_accessor") {
|
||||
executor = executeAttributeAccessorNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "block_function") {
|
||||
executor = executeSynchronousBlockFunction;
|
||||
}
|
||||
else if (node.type === "block_reference") {
|
||||
executor = executeBlockReferenceNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "check_security") {
|
||||
executor = executeCheckSecurityNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "check_to_string") {
|
||||
executor = executeCheckToStringNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "comment") {
|
||||
executor = executeCommentNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "conditional") {
|
||||
executor = executeConditionalNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "constant") {
|
||||
executor = executeConstantNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "deprecated") {
|
||||
executor = executeDeprecatedNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "do") {
|
||||
executor = executeDoNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "embed") {
|
||||
executor = executeEmbedNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "escape") {
|
||||
executor = executeEscapeNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "flush") {
|
||||
executor = executeFlushNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "for") {
|
||||
executor = executeForNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "for_loop") {
|
||||
executor = executeForLoopNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "hash") {
|
||||
executor = executeHashNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "if") {
|
||||
executor = executeIfNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "import") {
|
||||
executor = executeImportNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "include") {
|
||||
executor = executeIncludeNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "line") {
|
||||
executor = executeLineNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "method_call") {
|
||||
executor = executeMethodCallSynchronously;
|
||||
}
|
||||
else if (node.type === "name") {
|
||||
executor = executeNameNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "nullish_coalescing") {
|
||||
executor = executeConditionalNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "parent_function") {
|
||||
executor = executeParentFunctionSynchronously;
|
||||
}
|
||||
else if (node.type === "print") {
|
||||
executor = executePrintNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "sandbox") {
|
||||
executor = executeSandboxNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "set") {
|
||||
executor = executeSetNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "spaceless") {
|
||||
executor = executeSpacelessNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "spread") {
|
||||
executor = executeSpreadNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "template") {
|
||||
executor = executeTemplateNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "text") {
|
||||
executor = executeTextNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "verbatim") {
|
||||
executor = executeTextNodeSynchronously;
|
||||
}
|
||||
else if (node.type === "with") {
|
||||
executor = executeWithNodeSynchronously;
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`Unrecognized node of type "${node.type}"`, node, executionContext.template.source);
|
||||
}
|
||||
|
||||
return executor(node, executionContext);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
import {TwingApplyNode} from "../node/apply";
|
||||
import {getKeyValuePairs} from "../helpers/get-key-value-pairs";
|
||||
import {createFilterNode} from "../node/expression/call/filter";
|
||||
@ -29,3 +29,28 @@ export const executeApplyNode: TwingNodeExecutor<TwingApplyNode> = (node, execut
|
||||
outputBuffer.echo(content);
|
||||
});
|
||||
};
|
||||
|
||||
export const executeApplyNodeSynchronously: TwingSynchronousNodeExecutor<TwingApplyNode> = (node, executionContext) => {
|
||||
const {outputBuffer, nodeExecutor: execute} = executionContext;
|
||||
const {body, filters} = node.children;
|
||||
const {line, column} = node;
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
execute(body, executionContext)
|
||||
|
||||
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 = execute(filterNode, executionContext);
|
||||
}
|
||||
|
||||
outputBuffer.echo(content);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
|
||||
export const executeBaseNode: TwingNodeExecutor = async (node, executionContext) => {
|
||||
const output: Array<any> = [];
|
||||
@ -10,3 +10,14 @@ export const executeBaseNode: TwingNodeExecutor = async (node, executionContext)
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
export const executeBaseNodeSynchronously: TwingSynchronousNodeExecutor = (node, executionContext) => {
|
||||
const output: Array<any> = [];
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
for (const [, child] of Object.entries(node.children)) {
|
||||
output.push(execute(child, executionContext));
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
import type {TwingBlockReferenceNode} from "../node/block-reference";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
import {getSynchronousTraceableMethod, getTraceableMethod} from "../helpers/traceable-method";
|
||||
|
||||
export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNode> = (node, executionContext) => {
|
||||
const {
|
||||
@ -20,3 +20,24 @@ export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNod
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
export const executeBlockReferenceNodeSynchronously: TwingSynchronousNodeExecutor<TwingBlockReferenceNode> = (node, executionContext) => {
|
||||
const {
|
||||
template,
|
||||
context
|
||||
} = executionContext;
|
||||
const {name} = node.attributes;
|
||||
|
||||
const displayBlock = getSynchronousTraceableMethod(template.displayBlock, node, template.source);
|
||||
|
||||
return displayBlock(
|
||||
{
|
||||
...executionContext,
|
||||
// todo: was context: context.clone()
|
||||
// context: context.clone()
|
||||
context: new Map(context.entries())
|
||||
},
|
||||
name,
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
import {TwingCheckSecurityNode} from "../node/check-security";
|
||||
import type {TwingNode} from "../node";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
@ -33,3 +33,32 @@ export const executeCheckSecurityNode: TwingNodeExecutor<TwingCheckSecurityNode>
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export const executeCheckSecurityNodeSynchronously: TwingSynchronousNodeExecutor<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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
import {TwingCheckToStringNode} from "../node/check-to-string";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
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) {
|
||||
@ -28,3 +28,25 @@ export const executeCheckToStringNode: TwingNodeExecutor<TwingCheckToStringNode>
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
export const executeCheckToStringNodeSynchronously: TwingSynchronousNodeExecutor<TwingCheckToStringNode> = (node, executionContext) => {
|
||||
const {template, environment, nodeExecutor: execute, sandboxed} = executionContext;
|
||||
const {value: valueNode} = node.children;
|
||||
const {sandboxPolicy} = environment;
|
||||
|
||||
const value = execute(valueNode, executionContext);
|
||||
|
||||
if (sandboxed) {
|
||||
const assertToStringAllowed = getSynchronousTraceableMethod((value: any) => {
|
||||
if ((value !== null) && (typeof value === 'object')) {
|
||||
sandboxPolicy.checkMethodAllowed(value, 'toString');
|
||||
}
|
||||
|
||||
return value;
|
||||
}, valueNode, template.source)
|
||||
|
||||
return assertToStringAllowed(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
@ -1,6 +1,11 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingNodeExecutor, 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,6 +1,10 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingNodeExecutor, 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,4 +1,4 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../node-executor";
|
||||
import {TwingDeprecatedNode} from "../node/deprecated";
|
||||
|
||||
export const executeDeprecatedNode: TwingNodeExecutor<TwingDeprecatedNode> = (node, executionContext) => {
|
||||
@ -10,3 +10,12 @@ export const executeDeprecatedNode: TwingNodeExecutor<TwingDeprecatedNode> = (no
|
||||
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;
|
||||
|
||||
const message = execute(messageNode, executionContext);
|
||||
|
||||
console.warn(`${message} ("${template.name}" at line ${node.line}, column ${node.column})`);
|
||||
};
|
||||
|
@ -1,6 +1,10 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingNodeExecutor, 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,7 +1,8 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingNodeExecutor, 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;
|
||||
@ -20,3 +21,23 @@ export const executeArrayNode: TwingNodeExecutor<TwingBaseArrayNode<any>> = asyn
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
export const executeArrayNodeSynchronously: TwingSynchronousNodeExecutor<TwingBaseArrayNode<any>> = (baseNode, executionContext) => {
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
const keyValuePairs = getKeyValuePairs(baseNode);
|
||||
const array: Array<any> = [];
|
||||
|
||||
for (const {value: valueNode} of keyValuePairs) {
|
||||
const value = execute(valueNode, executionContext);
|
||||
|
||||
if ((valueNode as TwingNode).type === "spread") {
|
||||
const values = getValues(value);
|
||||
|
||||
array.push(...values);
|
||||
} else {
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
|
||||
import {TwingArrowFunctionNode} from "../../node/expression/arrow-function";
|
||||
|
||||
export const executeArrowFunctionNode: TwingNodeExecutor<TwingArrowFunctionNode> = (node, executionContext) => {
|
||||
@ -20,3 +20,23 @@ export const executeArrowFunctionNode: TwingNodeExecutor<TwingArrowFunctionNode>
|
||||
return execute(body, executionContext);
|
||||
});
|
||||
};
|
||||
|
||||
export const executeArrowFunctionNodeSynchronously: TwingSynchronousNodeExecutor<TwingArrowFunctionNode> = (node, executionContext) => {
|
||||
const {context, nodeExecutor: execute} = executionContext;
|
||||
const {body, names} = node.children;
|
||||
const assignmentNodes = Object.values(names.children);
|
||||
|
||||
return (...functionArgs: Array<any>): any => {
|
||||
let index = 0;
|
||||
|
||||
for (const assignmentNode of assignmentNodes) {
|
||||
const {name} = assignmentNode.attributes;
|
||||
|
||||
context.set(name, functionArgs[index]);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return execute(body, executionContext);
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,10 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingNodeExecutor, 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,7 +1,7 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingNodeExecutor, TwingSynchronousNodeExecutor} from "../../node-executor";
|
||||
import {TwingAttributeAccessorNode} from "../../node/expression/attribute-accessor";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
import {getAttribute} from "../../helpers/get-attribute";
|
||||
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;
|
||||
@ -28,3 +28,28 @@ export const executeAttributeAccessorNode: TwingNodeExecutor<TwingAttributeAcces
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
export const executeAttributeAccessorNodeSynchronously: TwingSynchronousNodeExecutor<TwingAttributeAccessorNode> = (node, executionContext) => {
|
||||
const {template, sandboxed, environment, nodeExecutor: execute, strict} = executionContext;
|
||||
const {target: targetNode, attribute: attributeNode, arguments: argumentsNode} = node.children;
|
||||
const {type, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
|
||||
|
||||
|
||||
const target = execute(targetNode, executionContext);
|
||||
const attribute = execute(attributeNode, executionContext);
|
||||
const methodArguments = execute(argumentsNode, executionContext);
|
||||
|
||||
const traceableGetAttribute = getSynchronousTraceableMethod(getAttributeSynchronously, node, template.source);
|
||||
|
||||
return traceableGetAttribute(
|
||||
environment,
|
||||
target,
|
||||
attribute,
|
||||
methodArguments,
|
||||
type,
|
||||
shouldTestExistence,
|
||||
shouldIgnoreStrictCheck || null,
|
||||
sandboxed,
|
||||
strict
|
||||
);
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user