This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+10
View File
@@ -0,0 +1,10 @@
export * from './src/Contracts';
import { Edge } from './src/Edge';
import { safeValue } from './src/Template';
import { GLOBALS } from './src/Edge/globals';
/**
* Default export
*/
declare const edge: Edge;
export default edge;
export { Edge, safeValue, GLOBALS };
+38
View File
@@ -0,0 +1,38 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GLOBALS = exports.safeValue = exports.Edge = void 0;
__exportStar(require("./src/Contracts"), exports);
const Edge_1 = require("./src/Edge");
Object.defineProperty(exports, "Edge", { enumerable: true, get: function () { return Edge_1.Edge; } });
const Template_1 = require("./src/Template");
Object.defineProperty(exports, "safeValue", { enumerable: true, get: function () { return Template_1.safeValue; } });
const globals_1 = require("./src/Edge/globals");
Object.defineProperty(exports, "GLOBALS", { enumerable: true, get: function () { return globals_1.GLOBALS; } });
/**
* Default export
*/
const edge = new Edge_1.Edge();
Object.keys(globals_1.GLOBALS).forEach((key) => edge.global(key, globals_1.GLOBALS[key]));
exports.default = edge;
+28
View File
@@ -0,0 +1,28 @@
import { LoaderTemplate, CacheManagerContract } from '../Contracts';
/**
* In memory cache manager to cache pre-compiled templates.
*/
export declare class CacheManager implements CacheManagerContract {
enabled: boolean;
private cacheStore;
constructor(enabled: boolean);
/**
* Returns a boolean to tell if a template has already been cached
* or not.
*/
has(absPath: string): boolean;
/**
* Returns the template from the cache. If caching is disabled,
* then it will return undefined.
*/
get(absPath: string): undefined | LoaderTemplate;
/**
* Set's the template path and the payload to the cache. If
* cache is disabled, then this function results in a noop.
*/
set(absPath: string, payload: LoaderTemplate): void;
/**
* Delete template from the compiled cache
*/
delete(absPath: string): void;
}
+57
View File
@@ -0,0 +1,57 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheManager = void 0;
/**
* In memory cache manager to cache pre-compiled templates.
*/
class CacheManager {
constructor(enabled) {
this.enabled = enabled;
this.cacheStore = new Map();
}
/**
* Returns a boolean to tell if a template has already been cached
* or not.
*/
has(absPath) {
return this.cacheStore.has(absPath);
}
/**
* Returns the template from the cache. If caching is disabled,
* then it will return undefined.
*/
get(absPath) {
if (!this.enabled) {
return;
}
return this.cacheStore.get(absPath);
}
/**
* Set's the template path and the payload to the cache. If
* cache is disabled, then this function results in a noop.
*/
set(absPath, payload) {
if (!this.enabled) {
return;
}
this.cacheStore.set(absPath, payload);
}
/**
* Delete template from the compiled cache
*/
delete(absPath) {
if (!this.enabled) {
return;
}
this.cacheStore.delete(absPath);
}
}
exports.CacheManager = CacheManager;
+80
View File
@@ -0,0 +1,80 @@
import { Parser } from 'edge-parser';
import { Token } from 'edge-lexer';
import { Processor } from '../Processor';
import { CacheManager } from '../CacheManager';
import { ClaimTagFn, TagsContract, LoaderContract, LoaderTemplate, CompilerOptions, CompilerContract } from '../Contracts';
/**
* Compiler is to used to compile templates using the `edge-parser`. Along with that
* it natively merges the contents of a layout with a parent template.
*/
export declare class Compiler implements CompilerContract {
private loader;
private tags;
private processor;
private options;
private claimTagFn?;
/**
* Caches compiled templates
*/
cacheManager: CacheManager;
/**
* Know if compiler is compiling for the async mode or not
*/
async: boolean;
constructor(loader: LoaderContract, tags: TagsContract, processor: Processor, options?: CompilerOptions);
/**
* Merges sections of base template and parent template tokens
*/
private mergeSections;
/**
* Generates an array of lexer tokens from the template string. Further tokens
* are checked for layouts and if layouts are used, their sections will be
* merged together.
*/
private templateContentToTokens;
/**
* Returns the parser instance for a given template
*/
private getParserFor;
/**
* Returns the parser instance for a given template
*/
private getBufferFor;
/**
* Define a function to claim tags
*/
claimTag(fn: ClaimTagFn): this;
/**
* Converts the template content to an array of lexer tokens. The method is
* same as the `parser.tokenize`, but it also handles layouts natively.
*
* ```
* compiler.tokenize('<template-path>')
* ```
*/
tokenize(templatePath: string, parser?: Parser): Token[];
/**
* Tokenize a raw template
*/
tokenizeRaw(contents: string, templatePath?: string, parser?: Parser): Token[];
/**
* Compiles the template contents to string. The output is same as the `edge-parser`,
* it's just that the compiler uses the loader to load the templates and also
* handles layouts.
*
* ```js
* compiler.compile('welcome')
* ```
*/
compile(templatePath: string, localVariables?: string[], skipCache?: boolean): LoaderTemplate;
/**
* Compiles the template contents to string. The output is same as the `edge-parser`,
* it's just that the compiler uses the loader to load the templates and also
* handles layouts.
*
* ```js
* compiler.compile('welcome')
* ```
*/
compileRaw(contents: string, templatePath?: string): LoaderTemplate;
}
+247
View File
@@ -0,0 +1,247 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Compiler = void 0;
const edge_error_1 = require("edge-error");
const edge_parser_1 = require("edge-parser");
const edge_lexer_1 = require("edge-lexer");
const CacheManager_1 = require("../CacheManager");
/**
* Compiler is to used to compile templates using the `edge-parser`. Along with that
* it natively merges the contents of a layout with a parent template.
*/
class Compiler {
constructor(loader, tags, processor, options = {
cache: true,
async: false,
}) {
this.loader = loader;
this.tags = tags;
this.processor = processor;
this.options = options;
/**
* Caches compiled templates
*/
this.cacheManager = new CacheManager_1.CacheManager(!!this.options.cache);
/**
* Know if compiler is compiling for the async mode or not
*/
this.async = !!this.options.async;
}
/**
* Merges sections of base template and parent template tokens
*/
mergeSections(base, extended) {
/**
* Collection of all sections from the extended tokens
*/
const extendedSections = {};
/**
* Collection of extended set calls as top level nodes. The set
* calls are hoisted just like `var` statements in Javascript.
*/
const extendedSetCalls = [];
extended.forEach((node) => {
/**
* Ignore new lines, comments, layout tag and empty raw nodes inside the parent
* template
*/
if (edge_lexer_1.utils.isTag(node, 'layout') ||
node.type === 'newline' ||
(node.type === 'raw' && !node.value.trim()) ||
node.type === 'comment') {
return;
}
/**
* Collect parent template sections
*/
if (edge_lexer_1.utils.isTag(node, 'section')) {
extendedSections[node.properties.jsArg.trim()] = node;
return;
}
/**
* Collect set calls inside parent templates
*/
if (edge_lexer_1.utils.isTag(node, 'set')) {
extendedSetCalls.push(node);
return;
}
/**
* Everything else is not allowed as top level nodes
*/
const [line, col] = edge_lexer_1.utils.getLineAndColumn(node);
throw new edge_error_1.EdgeError('Template extending a layout can only use "@section" or "@set" tags as top level nodes', 'E_UNALLOWED_EXPRESSION', { line, col, filename: node.filename });
});
/**
* Replace/extend sections inside base tokens list
*/
const finalNodes = base.map((node) => {
if (!edge_lexer_1.utils.isTag(node, 'section')) {
return node;
}
const sectionName = node.properties.jsArg.trim();
const extendedNode = extendedSections[sectionName];
if (!extendedNode) {
return node;
}
/**
* Concat children when super was called
*/
if (extendedNode.children.length) {
if (edge_lexer_1.utils.isTag(extendedNode.children[0], 'super')) {
extendedNode.children.shift();
extendedNode.children = node.children.concat(extendedNode.children);
}
else if (edge_lexer_1.utils.isTag(extendedNode.children[1], 'super')) {
extendedNode.children.shift();
extendedNode.children.shift();
extendedNode.children = node.children.concat(extendedNode.children);
}
}
return extendedNode;
});
/**
* Set calls are hoisted to the top
*/
return [].concat(extendedSetCalls).concat(finalNodes);
}
/**
* Generates an array of lexer tokens from the template string. Further tokens
* are checked for layouts and if layouts are used, their sections will be
* merged together.
*/
templateContentToTokens(content, parser, absPath) {
let templateTokens = parser.tokenize(content, { filename: absPath });
const firstToken = templateTokens[0];
/**
* The `layout` is inbuilt feature from core, where we merge the layout
* and parent template sections together
*/
if (edge_lexer_1.utils.isTag(firstToken, 'layout')) {
const layoutName = firstToken.properties.jsArg.replace(/'|"/g, '');
templateTokens = this.mergeSections(this.tokenize(layoutName, parser), templateTokens);
}
return templateTokens;
}
/**
* Returns the parser instance for a given template
*/
getParserFor(templatePath, localVariables) {
const parser = new edge_parser_1.Parser(this.tags, new edge_parser_1.Stack(), {
claimTag: this.claimTagFn,
async: this.async,
statePropertyName: 'state',
escapeCallPath: ['template', 'escape'],
localVariables: ['$filename', 'state', '$context'],
onTag: (tag) => this.processor.executeTag({ tag, path: templatePath }),
});
/**
* Define local variables on the parser. This is helpful when trying to compile
* a partail and we want to share the local state of the parent template
* with it
*/
if (localVariables) {
localVariables.forEach((localVariable) => parser.stack.defineVariable(localVariable));
}
return parser;
}
/**
* Returns the parser instance for a given template
*/
getBufferFor(templatePath) {
return new edge_parser_1.EdgeBuffer(templatePath, {
outputVar: 'out',
rethrowCallPath: ['template', 'reThrow'],
});
}
/**
* Define a function to claim tags
*/
claimTag(fn) {
this.claimTagFn = fn;
return this;
}
/**
* Converts the template content to an array of lexer tokens. The method is
* same as the `parser.tokenize`, but it also handles layouts natively.
*
* ```
* compiler.tokenize('<template-path>')
* ```
*/
tokenize(templatePath, parser) {
const absPath = this.loader.makePath(templatePath);
let { template } = this.loader.resolve(absPath);
return this.tokenizeRaw(template, absPath, parser);
}
/**
* Tokenize a raw template
*/
tokenizeRaw(contents, templatePath = 'eval.edge', parser) {
contents = this.processor.executeRaw({ path: templatePath, raw: contents });
return this.templateContentToTokens(contents, parser || this.getParserFor(templatePath), templatePath);
}
/**
* Compiles the template contents to string. The output is same as the `edge-parser`,
* it's just that the compiler uses the loader to load the templates and also
* handles layouts.
*
* ```js
* compiler.compile('welcome')
* ```
*/
compile(templatePath, localVariables, skipCache = false) {
const absPath = this.loader.makePath(templatePath);
let cachedResponse = skipCache ? null : this.cacheManager.get(absPath);
/**
* Process the template and cache it
*/
if (!cachedResponse) {
const parser = this.getParserFor(absPath, localVariables);
const buffer = this.getBufferFor(absPath);
/**
* Generate tokens and process them
*/
const templateTokens = this.tokenize(absPath, parser);
templateTokens.forEach((token) => parser.processToken(token, buffer));
const template = buffer.flush();
if (!skipCache) {
this.cacheManager.set(absPath, { template });
}
cachedResponse = { template };
}
const template = this.processor.executeCompiled({
path: absPath,
compiled: cachedResponse.template,
});
return { template };
}
/**
* Compiles the template contents to string. The output is same as the `edge-parser`,
* it's just that the compiler uses the loader to load the templates and also
* handles layouts.
*
* ```js
* compiler.compile('welcome')
* ```
*/
compileRaw(contents, templatePath = 'eval.edge') {
const parser = this.getParserFor(templatePath);
const buffer = this.getBufferFor(templatePath);
const templateTokens = this.tokenizeRaw(contents, templatePath, parser);
templateTokens.forEach((token) => parser.processToken(token, buffer));
const template = this.processor.executeCompiled({
path: templatePath,
compiled: buffer.flush(),
});
return { template };
}
}
exports.Compiler = Compiler;
+47
View File
@@ -0,0 +1,47 @@
import { PropsContract } from '../Contracts';
/**
* Class to ease interactions with component props
*/
export declare class Props implements PropsContract {
constructor(props: any);
/**
* Merges the className attribute with the class attribute
*/
private mergeClassAttributes;
/**
* Find if a key exists inside the props
*/
has(key: string): boolean;
/**
* Get value for a given key
*/
get(key: string, defaultValue?: any): any;
/**
* Returns all the props
*/
all(): any;
/**
* Validate prop value
*/
validate(key: string, validateFn: (key: string, value?: any) => any): void;
/**
* Return values for only the given keys
*/
only(keys: string[]): any;
/**
* Return values except the given keys
*/
except(keys: string[]): any;
/**
* Serialize all props to a string of HTML attributes
*/
serialize(mergeProps?: any, priortizeInline?: boolean): import("../Template").SafeValue;
/**
* Serialize only the given keys to a string of HTML attributes
*/
serializeOnly(keys: string[], mergeProps?: any, priortizeInline?: boolean): import("../Template").SafeValue;
/**
* Serialize except the given keys to a string of HTML attributes
*/
serializeExcept(keys: string[], mergeProps?: any, priortizeInline?: boolean): import("../Template").SafeValue;
}
+120
View File
@@ -0,0 +1,120 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Props = void 0;
const utils_1 = require("@poppinss/utils");
const stringify_attributes_1 = __importDefault(require("stringify-attributes"));
const Template_1 = require("../Template");
/**
* Class to ease interactions with component props
*/
class Props {
constructor(props) {
this[Symbol.for('options')] = { props };
Object.assign(this, props);
}
/**
* Merges the className attribute with the class attribute
*/
mergeClassAttributes(props) {
if (props.className) {
if (!props.class) {
props.class = [];
}
/**
* Normalize class attribute to be an array
*/
if (!Array.isArray(props.class)) {
props.class = [props.class];
}
props.class = props.class.concat(props.className);
props.className = false;
}
return props;
}
/**
* Find if a key exists inside the props
*/
has(key) {
const value = this.get(key);
return value !== undefined && value !== null;
}
/**
* Get value for a given key
*/
get(key, defaultValue) {
return utils_1.lodash.get(this.all(), key, defaultValue);
}
/**
* Returns all the props
*/
all() {
return this[Symbol.for('options')].props;
}
/**
* Validate prop value
*/
validate(key, validateFn) {
const value = this.get(key);
validateFn(key, value);
}
/**
* Return values for only the given keys
*/
only(keys) {
return utils_1.lodash.pick(this.all(), keys);
}
/**
* Return values except the given keys
*/
except(keys) {
return utils_1.lodash.omit(this.all(), keys);
}
/**
* Serialize all props to a string of HTML attributes
*/
serialize(mergeProps, priortizeInline = true) {
/**
* Priortize user attributes when priortizeInline=false
*/
const attributes = priortizeInline
? utils_1.lodash.merge({}, this.all(), mergeProps)
: utils_1.lodash.merge({}, mergeProps, this.all());
return (0, Template_1.safeValue)((0, stringify_attributes_1.default)(this.mergeClassAttributes(attributes)));
}
/**
* Serialize only the given keys to a string of HTML attributes
*/
serializeOnly(keys, mergeProps, priortizeInline = true) {
/**
* Priortize user attributes when priortizeInline=false
*/
const attributes = priortizeInline
? utils_1.lodash.merge({}, this.only(keys), mergeProps)
: utils_1.lodash.merge({}, mergeProps, this.only(keys));
return (0, Template_1.safeValue)((0, stringify_attributes_1.default)(this.mergeClassAttributes(attributes)));
}
/**
* Serialize except the given keys to a string of HTML attributes
*/
serializeExcept(keys, mergeProps, priortizeInline = true) {
/**
* Priortize user attributes when priortizeInline=false
*/
const attributes = priortizeInline
? utils_1.lodash.merge({}, this.except(keys), mergeProps)
: utils_1.lodash.merge({}, mergeProps, this.except(keys));
return (0, Template_1.safeValue)((0, stringify_attributes_1.default)(this.mergeClassAttributes(attributes)));
}
}
exports.Props = Props;
+369
View File
@@ -0,0 +1,369 @@
/**
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Token, TagToken } from 'edge-lexer';
import { MacroableConstructorContract } from 'macroable';
import { ParserTagDefinitionContract, Parser, EdgeBuffer, ClaimTagFn } from 'edge-parser';
/**
* The shape in which the loader must resolve the template
*/
export type LoaderTemplate = {
template: string;
};
/**
* Loader contract that every loader must adheres to.
*/
export interface LoaderContract {
/**
* List of mounted disks
*/
mounted: {
[diskName: string]: string;
};
/**
* List of pre-registered template
*/
templates: {
[templatePath: string]: LoaderTemplate;
};
/**
* Save disk name and dirPath to resolve views
*/
mount(diskName: string, dirPath: string): void;
/**
* Remove disk from the previously saved paths
*/
unmount(diskName: string): void;
/**
* Resolve template contents
*/
resolve(templatePath: string): LoaderTemplate;
/**
* Make absolute path to a template
*/
makePath(templatePath: string): string;
/**
* Register in memory template and presenter
*/
register(templatePath: string, contents: LoaderTemplate): void;
/**
* Remove the pre-registered template
*/
remove(templatePath: string): void;
}
/**
* Shape of template constructor
*/
export interface TemplateConstructorContract extends MacroableConstructorContract<TemplateContract> {
new (compiler: CompilerContract, globals: any, locals: any, processor: ProcessorContract): TemplateContract;
}
/**
* The tag must have a tagName along with other properties
* required by lexer and parser
*/
export interface TagContract extends ParserTagDefinitionContract {
tagName: string;
boot?(template: TemplateConstructorContract): void;
}
/**
* Shape of required tags
*/
export type TagsContract = {
[tagName: string]: TagContract;
};
/**
* Shape of the cache manager
*/
export interface CacheManagerContract {
enabled: boolean;
get(templatePath: string): undefined | LoaderTemplate;
set(templatePath: string, compiledOutput: LoaderTemplate): void;
has(templatePath: string): boolean;
delete(templatePath: string): void;
}
/**
* Compiler constructor options
*/
export type CompilerOptions = {
cache?: boolean;
async?: boolean;
};
/**
* Shape of the compiler
*/
export interface CompilerContract {
cacheManager: CacheManagerContract;
async: boolean;
claimTag(fn: ClaimTagFn): this;
compile(templatePath: string, localVariables?: string[], skipCache?: boolean): LoaderTemplate;
tokenize(templatePath: string, parser?: Parser): Token[];
/**
* Compile the raw string as a template
*/
compileRaw(contents: string, templatePath?: string): LoaderTemplate;
/**
* Tokenize the raw string as a template
*/
tokenizeRaw(contents: string, templatePath?: string, parser?: Parser): Token[];
}
/**
* Shape of the props class passed to the components
*/
export interface PropsContract {
/**
* Find if a key exists inside the props
*/
has(key: string): boolean;
/**
* Return values for only the given keys
*/
only(keys: string[]): {
[key: string]: any;
};
/**
* Return values except the given keys
*/
except(keys: string[]): {
[key: string]: any;
};
/**
* Serialize all props to a string of HTML attributes
*/
serialize(mergeProps?: any): {
value: string;
};
/**
* Serialize only the given keys to a string of HTML attributes
*/
serializeOnly(keys: string[], mergeProps?: any): {
value: string;
};
/**
* Serialize except the given keys to a string of HTML attributes
*/
serializeExcept(keys: string[], mergeProps?: any): {
value: string;
};
}
/**
* Shape of the template contract
*/
export interface TemplateContract {
/**
* Compiles partial
*/
compilePartial(templatePath: string, ...localVariables: string[]): Function;
/**
* Compiles a component
*/
compileComponent(templatePath: string, ...localVariables: string[]): string;
/**
* Returns the state for a component
*/
getComponentState(props: {
[key: string]: any;
}, slots: {
[key: string]: any;
}, caller: {
filename: string;
line: number;
col: number;
}): {
$props: PropsContract & {
[key: string]: any;
};
$slots: {
[key: string]: any;
};
$caller: {
filename: string;
line: number;
col: number;
};
};
/**
* Renders a template to a string
*/
render<T extends Promise<string> | string>(template: string, state: any): T;
renderRaw<T extends Promise<string> | string>(contents: string, state: any, templatePath?: string): T;
/**
* Escape input
*/
escape(input: any): string;
/**
* Rethrow exceptions by pointing back to edge source file and line number
*/
reThrow(error: any, filename: string, line: number): never;
}
/**
* Shape of the renderer that renders the edge templates
*/
export interface EdgeRendererContract {
/**
* Share state with the template and its partials and component
*/
share(locals: any): this;
/**
* Render a template asynchronously
*/
render(templatePath: string, state?: any): Promise<string>;
renderRaw(contents: string, state?: any, templatePath?: string): Promise<string>;
/**
* Render a template synchronously
*/
renderSync(templatePath: string, state?: any): string;
renderRawSync(contents: string, state?: any, templatePath?: string): string;
}
/**
* The processor is used to execute process functions for different
* lifecycles
*/
export interface ProcessorContract {
/**
* Hook into the raw text to modify its contents. Make sure to return the
* new string back or return "void" in case no modifications have been
* performed
*/
process(event: 'raw', handler: (data: {
raw: string;
path: string;
}) => string | void): this;
/**
* Hook into the tag node to modify its properties
*/
process(event: 'tag', handler: (data: {
tag: TagToken;
path: string;
}) => void): this;
/**
* Hook into the compiled template to modify its contents. Make sure to return the
* new string back or return "void" in case no modifications have been
* performed
*/
process(event: 'compiled', handler: (data: {
compiled: string;
path: string;
}) => string | void): this;
/**
* Hook into the compiled output to modify its contents. Make sure to return the
* new string back or return "void" in case no modifications have been
* performed
*/
process(event: 'output', handler: (data: {
output: string;
template: TemplateContract;
state: Record<string, any>;
}) => string | void): this;
}
/**
* Shape of options that can be passed to the
* edge constructor
*/
export type EdgeOptions = {
loader?: LoaderContract;
cache?: boolean;
};
/**
* Shape of the main module
*/
export interface EdgeContract {
/**
* Loader for loading templates. You can also define a custom loader when creating
* a new instance of edge
*/
loader: LoaderContract;
/**
* Compiler to be used for compiling synchronously
*/
compiler: CompilerContract;
/**
* Compiler to be used for compiling asynchronously
*/
asyncCompiler: CompilerContract;
/**
* Processor reference to hook into the compile and the rendering
* phase of templates
*/
processor: ProcessorContract;
/**
* Set of registered globals. One can define custom globals using `edge.global`
* method
*/
GLOBALS: {
[key: string]: any;
};
/**
* A custom set of registered tags. One can define a custom tag using `edge.registerTag`
* method
*/
tags: {
[name: string]: TagContract;
};
/**
* Register a plugin. Plugins are lazily invoked just before the views are rendered. This
* ensures that plugins will receive a fully configured edge instance.
*
* Also plugins are invoked only once. Unless, the `options.recurring` value is set
*/
use<T extends any>(pluginFn: (edge: this, firstRun: boolean, options: T) => void, options?: T): this;
/**
* Register a custom tag
*/
registerTag(tag: TagContract): this;
/**
* Register an inline template
*/
registerTemplate(templatePath: string, contents: LoaderTemplate): this;
/**
* Remove the template registered using the "registerTemplate" method
*/
removeTemplate(templatePath: string): this;
/**
* Register a global value
*/
global(key: string, value: any): this;
/**
* Mount/disk
*/
mount(diskName: string): this;
mount(diskName: string, dirPath: string): this;
/**
* Unmount disk
*/
unmount(diskName: string): this;
/**
* Get access to the underlying template renderer. Each render call
* to edge results in creating an isolated renderer instance.
*/
onRender(callback: (renderer: EdgeRendererContract) => void): this;
/**
* Get a renderer instance to render templates
*/
getRenderer(): EdgeRendererContract;
/**
* Creates a renderer instances and shares the locals with it
*/
share(locals: any): EdgeRendererContract;
/**
* Render a template asynchronously
*/
render(templatePath: string, state?: any): Promise<string>;
renderRaw(contents: string, state?: any, templatePath?: string): Promise<string>;
/**
* Render a template synchronously
*/
renderSync(templatePath: string, state?: any): string;
renderRawSync(contents: string, state?: any, templatePath?: string): string;
}
/**
* Required for someone creating custom tags
*/
export type EdgeBufferContract = EdgeBuffer;
export type ParserContract = Parser;
export type TagTokenContract = TagToken;
export { ClaimTagFn };
+10
View File
@@ -0,0 +1,10 @@
"use strict";
/**
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+62
View File
@@ -0,0 +1,62 @@
import { string } from '@poppinss/utils/build/helpers';
import { safeValue, escape } from '../../Template';
export declare const GLOBALS: {
/**
* Converts new lines to break
*/
nl2br: (value: string | null | undefined) => string | undefined;
/**
* Inspect state
*/
inspect: (value: any) => import("../../Template").SafeValue;
/**
* Truncate a sentence
*/
truncate: (value: string, length?: number, options?: {
completeWords?: boolean;
strict?: boolean;
suffix?: string;
}) => string;
/**
* Raise an exception
*/
raise: (message: string, options?: any) => never;
/**
* Generate an excerpt
*/
excerpt: (value: string, length?: number, options?: {
completeWords?: boolean;
strict?: boolean;
suffix?: string;
}) => string;
/**
* Using `"e"` because, `escape` is a global function in the
* Node.js global namespace and edge parser gives priority
* to it
*/
e: typeof escape;
/**
* Convert javascript data structures to a string. The method is a little
* better over JSON.stringify in handling certain data structures. For
* example: In JSON.stringify, the date is converted to an ISO string
* whereas this method converts it to an actual instance of date
*/
stringify: any;
safe: typeof safeValue;
camelCase: typeof string.camelCase;
snakeCase: typeof string.snakeCase;
dashCase: typeof string.dashCase;
pascalCase: typeof string.pascalCase;
capitalCase: typeof string.capitalCase;
sentenceCase: typeof string.sentenceCase;
dotCase: typeof string.dotCase;
noCase: typeof string.noCase;
titleCase: typeof string.titleCase;
pluralize: typeof string.pluralize;
toSentence: typeof string.toSentence;
prettyBytes: typeof string.prettyBytes;
toBytes: typeof string.toBytes;
prettyMs: typeof string.prettyMs;
toMs: typeof string.toMs;
ordinalize: typeof string.ordinalize;
};
+96
View File
@@ -0,0 +1,96 @@
"use strict";
/*
* edge.js
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GLOBALS = void 0;
const edge_error_1 = require("edge-error");
const js_stringify_1 = __importDefault(require("js-stringify"));
const helpers_1 = require("@poppinss/utils/build/helpers");
const Template_1 = require("../../Template");
exports.GLOBALS = {
/**
* Converts new lines to break
*/
nl2br: (value) => {
if (!value) {
return;
}
return String(value).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br>');
},
/**
* Inspect state
*/
inspect: (value) => {
return (0, Template_1.safeValue)(require('@poppinss/inspect').string.html(value));
},
/**
* Truncate a sentence
*/
truncate: (value, length = 20, options) => {
options = options || {};
return helpers_1.string.truncate(value, length, {
completeWords: options.completeWords !== undefined ? options.completeWords : !options.strict,
suffix: options.suffix,
});
},
/**
* Raise an exception
*/
raise: (message, options) => {
if (!options) {
throw new Error(message);
}
else {
throw new edge_error_1.EdgeError(message, 'E_RUNTIME_EXCEPTION', options);
}
},
/**
* Generate an excerpt
*/
excerpt: (value, length = 20, options) => {
options = options || {};
return helpers_1.string.excerpt(value, length, {
completeWords: options.completeWords !== undefined ? options.completeWords : !options.strict,
suffix: options.suffix,
});
},
/**
* Using `"e"` because, `escape` is a global function in the
* Node.js global namespace and edge parser gives priority
* to it
*/
e: Template_1.escape,
/**
* Convert javascript data structures to a string. The method is a little
* better over JSON.stringify in handling certain data structures. For
* example: In JSON.stringify, the date is converted to an ISO string
* whereas this method converts it to an actual instance of date
*/
stringify: js_stringify_1.default,
safe: Template_1.safeValue,
camelCase: helpers_1.string.camelCase,
snakeCase: helpers_1.string.snakeCase,
dashCase: helpers_1.string.dashCase,
pascalCase: helpers_1.string.pascalCase,
capitalCase: helpers_1.string.capitalCase,
sentenceCase: helpers_1.string.sentenceCase,
dotCase: helpers_1.string.dotCase,
noCase: helpers_1.string.noCase,
titleCase: helpers_1.string.titleCase,
pluralize: helpers_1.string.pluralize,
toSentence: helpers_1.string.toSentence,
prettyBytes: helpers_1.string.prettyBytes,
toBytes: helpers_1.string.toBytes,
prettyMs: helpers_1.string.prettyMs,
toMs: helpers_1.string.toMs,
ordinalize: helpers_1.string.ordinalize,
};
+193
View File
@@ -0,0 +1,193 @@
import { Loader } from '../Loader';
import { Compiler } from '../Compiler';
import { Processor } from '../Processor';
import { TagContract, EdgeOptions, EdgeContract, LoaderTemplate, EdgeRendererContract } from '../Contracts';
/**
* Exposes the API to render templates, register custom tags and globals
*/
export declare class Edge implements EdgeContract {
private options;
private executedPlugins;
/**
* Options passed to the compiler instance
*/
private compilerOptions;
/**
* Options passed to the async compiler instance
*/
private asyncCompilerOptions;
/**
* An array of registered plugins
*/
private plugins;
/**
* Array of registered renderer hooks
*/
private renderCallbacks;
/**
* Reference to the registered processor handlers
*/
processor: Processor;
/**
* Globals are shared with all rendered templates
*/
GLOBALS: {
[key: string]: any;
};
/**
* List of registered tags. Adding new tags will only impact
* this list
*/
tags: {
[name: string]: TagContract;
};
/**
* The loader to load templates. A loader can read and return
* templates from anywhere. The default loader reads files
* from the disk
*/
loader: import("../Contracts").LoaderContract | Loader;
/**
* The underlying compiler in use
*/
compiler: Compiler;
/**
* The underlying compiler in use
*/
asyncCompiler: Compiler;
constructor(options?: EdgeOptions);
/**
* Execute plugins. Since plugins are meant to be called only
* once we empty out the array after first call
*/
private executePlugins;
/**
* Register a plugin. Plugin functions are called once just before
* an attempt to render a view is made.
*/
use<T extends any>(pluginFn: (edge: this, firstRun: boolean, options: T) => void, options?: T): this;
/**
* Mount named directory to use views. Later you can reference
* the views from a named disk as follows.
*
* ```
* edge.mount('admin', join(__dirname, 'admin'))
*
* edge.render('admin::filename')
* ```
*/
mount(diskName: string, dirPath?: string): this;
/**
* Un Mount a disk from the loader.
*
* ```js
* edge.unmount('admin')
* ```
*/
unmount(diskName: string): this;
/**
* Add a new global to the edge globals. The globals are available
* to all the templates.
*
* ```js
* edge.global('username', 'virk')
* edge.global('time', () => new Date().getTime())
* ```
*/
global(name: string, value: any): this;
/**
* Add a new tag to the tags list.
*
* ```ts
* edge.registerTag('svg', {
* block: false,
* seekable: true,
*
* compile (parser, buffer, token) {
* const fileName = token.properties.jsArg.trim()
* buffer.writeRaw(fs.readFileSync(__dirname, 'assets', `${fileName}.svg`), 'utf-8')
* }
* })
* ```
*/
registerTag(tag: TagContract): this;
/**
* Register an in-memory template.
*
* ```ts
* edge.registerTemplate('button', {
* template: `<button class="{{ this.type || 'primary' }}">
* @!yield($slots.main())
* </button>`,
* })
* ```
*
* Later you can use this template
*
* ```edge
* @component('button', type = 'primary')
* Get started
* @endcomponent
* ```
*/
registerTemplate(templatePath: string, contents: LoaderTemplate): this;
/**
* Remove the template registered using the "registerTemplate" method
*/
removeTemplate(templatePath: string): this;
/**
* Get access to the underlying template renderer. Each render call
* to edge results in creating an isolated renderer instance.
*/
onRender(callback: (renderer: EdgeRendererContract) => void): this;
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer(): EdgeRendererContract;
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath: string, state?: any): Promise<string>;
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderSync(templatePath: string, state?: any): string;
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRaw(contents: string, state?: any, templatePath?: string): Promise<string>;
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRawSync(templatePath: string, state?: any): string;
/**
* Share locals with the current view context.
*
* ```js
* const view = edge.getRenderer()
*
* // local state for the current render
* view.share({ foo: 'bar' })
*
* view.render('welcome')
* ```
*/
share(data: any): EdgeRendererContract;
}
+299
View File
@@ -0,0 +1,299 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Edge = void 0;
const Tags = __importStar(require("../Tags"));
const Loader_1 = require("../Loader");
const Compiler_1 = require("../Compiler");
const Template_1 = require("../Template");
const Processor_1 = require("../Processor");
const Renderer_1 = require("../Renderer");
/**
* Exposes the API to render templates, register custom tags and globals
*/
class Edge {
constructor(options = {}) {
this.options = options;
this.executedPlugins = false;
/**
* Options passed to the compiler instance
*/
this.compilerOptions = {
cache: !!this.options.cache,
async: false,
};
/**
* Options passed to the async compiler instance
*/
this.asyncCompilerOptions = {
cache: !!this.options.cache,
async: true,
};
/**
* An array of registered plugins
*/
this.plugins = [];
/**
* Array of registered renderer hooks
*/
this.renderCallbacks = [];
/**
* Reference to the registered processor handlers
*/
this.processor = new Processor_1.Processor();
/**
* Globals are shared with all rendered templates
*/
this.GLOBALS = {};
/**
* List of registered tags. Adding new tags will only impact
* this list
*/
this.tags = {};
/**
* The loader to load templates. A loader can read and return
* templates from anywhere. The default loader reads files
* from the disk
*/
this.loader = this.options.loader || new Loader_1.Loader();
/**
* The underlying compiler in use
*/
this.compiler = new Compiler_1.Compiler(this.loader, this.tags, this.processor, this.compilerOptions);
/**
* The underlying compiler in use
*/
this.asyncCompiler = new Compiler_1.Compiler(this.loader, this.tags, this.processor, this.asyncCompilerOptions);
Object.keys(Tags).forEach((name) => this.registerTag(Tags[name]));
}
/**
* Execute plugins. Since plugins are meant to be called only
* once we empty out the array after first call
*/
executePlugins() {
if (this.executedPlugins) {
this.plugins.forEach(({ fn, options }) => {
if (options && options.recurring) {
fn(this, false, options);
}
});
}
else {
this.executedPlugins = true;
this.plugins.forEach(({ fn, options }) => {
fn(this, true, options);
});
}
}
/**
* Register a plugin. Plugin functions are called once just before
* an attempt to render a view is made.
*/
use(pluginFn, options) {
this.plugins.push({
fn: pluginFn,
options,
});
return this;
}
/**
* Mount named directory to use views. Later you can reference
* the views from a named disk as follows.
*
* ```
* edge.mount('admin', join(__dirname, 'admin'))
*
* edge.render('admin::filename')
* ```
*/
mount(diskName, dirPath) {
if (!dirPath) {
dirPath = diskName;
diskName = 'default';
}
this.loader.mount(diskName, dirPath);
return this;
}
/**
* Un Mount a disk from the loader.
*
* ```js
* edge.unmount('admin')
* ```
*/
unmount(diskName) {
this.loader.unmount(diskName);
return this;
}
/**
* Add a new global to the edge globals. The globals are available
* to all the templates.
*
* ```js
* edge.global('username', 'virk')
* edge.global('time', () => new Date().getTime())
* ```
*/
global(name, value) {
this.GLOBALS[name] = value;
return this;
}
/**
* Add a new tag to the tags list.
*
* ```ts
* edge.registerTag('svg', {
* block: false,
* seekable: true,
*
* compile (parser, buffer, token) {
* const fileName = token.properties.jsArg.trim()
* buffer.writeRaw(fs.readFileSync(__dirname, 'assets', `${fileName}.svg`), 'utf-8')
* }
* })
* ```
*/
registerTag(tag) {
if (typeof tag.boot === 'function') {
tag.boot(Template_1.Template);
}
this.tags[tag.tagName] = tag;
return this;
}
/**
* Register an in-memory template.
*
* ```ts
* edge.registerTemplate('button', {
* template: `<button class="{{ this.type || 'primary' }}">
* @!yield($slots.main())
* </button>`,
* })
* ```
*
* Later you can use this template
*
* ```edge
* @component('button', type = 'primary')
* Get started
* @endcomponent
* ```
*/
registerTemplate(templatePath, contents) {
this.loader.register(templatePath, contents);
return this;
}
/**
* Remove the template registered using the "registerTemplate" method
*/
removeTemplate(templatePath) {
this.loader.remove(templatePath);
this.compiler.cacheManager.delete(templatePath);
this.asyncCompiler.cacheManager.delete(templatePath);
return this;
}
/**
* Get access to the underlying template renderer. Each render call
* to edge results in creating an isolated renderer instance.
*/
onRender(callback) {
this.renderCallbacks.push(callback);
return this;
}
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer() {
this.executePlugins();
const renderer = new Renderer_1.EdgeRenderer(this.compiler, this.asyncCompiler, this.GLOBALS, this.processor);
this.renderCallbacks.forEach((callback) => callback(renderer));
return renderer;
}
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath, state) {
return this.getRenderer().render(templatePath, state);
}
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderSync(templatePath, state) {
return this.getRenderer().renderSync(templatePath, state);
}
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRaw(contents, state, templatePath) {
return this.getRenderer().renderRaw(contents, state, templatePath);
}
/**
* Render a template asynchronously with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
renderRawSync(templatePath, state) {
return this.getRenderer().renderRawSync(templatePath, state);
}
/**
* Share locals with the current view context.
*
* ```js
* const view = edge.getRenderer()
*
* // local state for the current render
* view.share({ foo: 'bar' })
*
* view.render('welcome')
* ```
*/
share(data) {
return this.getRenderer().share(data);
}
}
exports.Edge = Edge;
+147
View File
@@ -0,0 +1,147 @@
/**
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { LoaderContract, LoaderTemplate } from '../Contracts';
/**
* The job of a loader is to load the template from a given path.
* The base loader (shipped with edge) looks for files on the
* file-system and reads them synchronously.
*
* You are free to define your own loaders that implements the [[LoaderContract]] interface.
*/
export declare class Loader implements LoaderContract {
/**
* List of mounted directories
*/
private mountedDirs;
/**
* List of pre-registered (in-memory) templates
*/
private preRegistered;
/**
* Reads the content of a template from the disk. An exception is raised
* when file is missing or if `readFileSync` returns an error.
*/
private readTemplateContents;
/**
* Extracts the disk name and the template name from the template
* path expression.
*
* If `diskName` is missing, it will be set to `default`.
*
* ```
* extractDiskAndTemplateName('users::list')
* // returns ['users', 'list.edge']
*
* extractDiskAndTemplateName('list')
* // returns ['default', 'list.edge']
* ```
*/
private extractDiskAndTemplateName;
/**
* Returns an object of mounted directories with their public
* names.
*
* ```js
* loader.mounted
* // output
*
* {
* default: '/users/virk/code/app/views',
* foo: '/users/virk/code/app/foo',
* }
* ```
*/
get mounted(): {
[key: string]: string;
};
/**
* Returns an object of templates registered as a raw string
*
* ```js
* loader.templates
* // output
*
* {
* 'form.label': { template: '/users/virk/code/app/form/label' }
* }
* ```
*/
get templates(): {
[templatePath: string]: LoaderTemplate;
};
/**
* Mount a directory with a name for resolving views. If name is set
* to `default`, then you can resolve views without prefixing the
* disk name.
*
* ```js
* loader.mount('default', join(__dirname, 'views'))
*
* // mount a named disk
* loader.mount('admin', join(__dirname, 'admin/views'))
* ```
*/
mount(diskName: string, dirPath: string): void;
/**
* Remove the previously mounted dir.
*
* ```js
* loader.unmount('default')
* ```
*/
unmount(diskName: string): void;
/**
* Make path to a given template. The paths are resolved from the root
* of the mounted directory.
*
* ```js
* loader.makePath('welcome') // returns {diskRootPath}/welcome.edge
* loader.makePath('admin::welcome') // returns {adminRootPath}/welcome.edge
* loader.makePath('users.list') // returns {diskRootPath}/users/list.edge
* ```
*
* @throws Error if disk is not mounted and attempting to make path for it.
*/
makePath(templatePath: string): string;
/**
* Resolves the template by reading its contents from the disk
*
* ```js
* loader.resolve('welcome', true)
*
* // output
* {
* template: `<h1> Template content </h1>`,
* }
* ```
*/
resolve(templatePath: string): LoaderTemplate;
/**
* Register in memory template for a given path. This is super helpful
* when distributing components.
*
* ```js
* loader.register('welcome', {
* template: '<h1> Template content </h1>',
* Presenter: class Presenter {
* constructor (state) {
* this.state = state
* }
* }
* })
* ```
*
* @throws Error if template content is empty.
*/
register(templatePath: string, contents: LoaderTemplate): void;
/**
* Remove registered template
*/
remove(templatePath: string): void;
}
+247
View File
@@ -0,0 +1,247 @@
"use strict";
/**
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Loader = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
/**
* The job of a loader is to load the template from a given path.
* The base loader (shipped with edge) looks for files on the
* file-system and reads them synchronously.
*
* You are free to define your own loaders that implements the [[LoaderContract]] interface.
*/
class Loader {
constructor() {
/**
* List of mounted directories
*/
this.mountedDirs = new Map();
/**
* List of pre-registered (in-memory) templates
*/
this.preRegistered = new Map();
}
/**
* Reads the content of a template from the disk. An exception is raised
* when file is missing or if `readFileSync` returns an error.
*/
readTemplateContents(absPath) {
try {
return (0, fs_1.readFileSync)(absPath, 'utf-8');
}
catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Cannot resolve "${absPath}". Make sure the file exists`);
}
else {
throw error;
}
}
}
/**
* Extracts the disk name and the template name from the template
* path expression.
*
* If `diskName` is missing, it will be set to `default`.
*
* ```
* extractDiskAndTemplateName('users::list')
* // returns ['users', 'list.edge']
*
* extractDiskAndTemplateName('list')
* // returns ['default', 'list.edge']
* ```
*/
extractDiskAndTemplateName(templatePath) {
let [disk, ...rest] = templatePath.split('::');
if (!rest.length) {
rest = [disk];
disk = 'default';
}
let [template, ext] = rest.join('::').split('.edge');
/**
* Depreciate dot based path seperators
*/
if (template.indexOf('.') > -1) {
process.emitWarning('DeprecationWarning', 'edge: dot "." based path seperators are depreciated. We recommend using "/" instead');
template = template.replace(/\./g, '/');
}
return [disk, `${template}.${ext || 'edge'}`];
}
/**
* Returns an object of mounted directories with their public
* names.
*
* ```js
* loader.mounted
* // output
*
* {
* default: '/users/virk/code/app/views',
* foo: '/users/virk/code/app/foo',
* }
* ```
*/
get mounted() {
return Array.from(this.mountedDirs).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}
/**
* Returns an object of templates registered as a raw string
*
* ```js
* loader.templates
* // output
*
* {
* 'form.label': { template: '/users/virk/code/app/form/label' }
* }
* ```
*/
get templates() {
return Array.from(this.preRegistered).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}
/**
* Mount a directory with a name for resolving views. If name is set
* to `default`, then you can resolve views without prefixing the
* disk name.
*
* ```js
* loader.mount('default', join(__dirname, 'views'))
*
* // mount a named disk
* loader.mount('admin', join(__dirname, 'admin/views'))
* ```
*/
mount(diskName, dirPath) {
this.mountedDirs.set(diskName, dirPath);
}
/**
* Remove the previously mounted dir.
*
* ```js
* loader.unmount('default')
* ```
*/
unmount(diskName) {
this.mountedDirs.delete(diskName);
}
/**
* Make path to a given template. The paths are resolved from the root
* of the mounted directory.
*
* ```js
* loader.makePath('welcome') // returns {diskRootPath}/welcome.edge
* loader.makePath('admin::welcome') // returns {adminRootPath}/welcome.edge
* loader.makePath('users.list') // returns {diskRootPath}/users/list.edge
* ```
*
* @throws Error if disk is not mounted and attempting to make path for it.
*/
makePath(templatePath) {
/**
* Return the template path as it is, when it is registered
* dynamically
*/
if (this.preRegistered.has(templatePath)) {
return templatePath;
}
/**
* Return absolute path as it is
*/
if ((0, path_1.isAbsolute)(templatePath)) {
return templatePath;
}
/**
* Extract disk name and template path from the expression
*/
const [diskName, template] = this.extractDiskAndTemplateName(templatePath);
/**
* Raise exception when disk name is not registered
*/
const mountedDir = this.mountedDirs.get(diskName);
if (!mountedDir) {
throw new Error(`"${diskName}" namespace is not mounted`);
}
return (0, path_1.join)(mountedDir, template);
}
/**
* Resolves the template by reading its contents from the disk
*
* ```js
* loader.resolve('welcome', true)
*
* // output
* {
* template: `<h1> Template content </h1>`,
* }
* ```
*/
resolve(templatePath) {
/**
* Return from pre-registered one's if exists
*/
if (this.preRegistered.has(templatePath)) {
return this.preRegistered.get(templatePath);
}
/**
* Make absolute to the file on the disk
*/
templatePath = (0, path_1.isAbsolute)(templatePath) ? templatePath : this.makePath(templatePath);
return {
template: this.readTemplateContents(templatePath),
};
}
/**
* Register in memory template for a given path. This is super helpful
* when distributing components.
*
* ```js
* loader.register('welcome', {
* template: '<h1> Template content </h1>',
* Presenter: class Presenter {
* constructor (state) {
* this.state = state
* }
* }
* })
* ```
*
* @throws Error if template content is empty.
*/
register(templatePath, contents) {
/**
* Ensure template content is defined as a string
*/
if (typeof contents.template !== 'string') {
throw new Error('Make sure to define the template content as a string');
}
/**
* Do not overwrite existing template with same template path
*/
if (this.preRegistered.has(templatePath)) {
throw new Error(`Cannot override previously registered "${templatePath}" template`);
}
this.preRegistered.set(templatePath, contents);
}
/**
* Remove registered template
*/
remove(templatePath) {
this.preRegistered.delete(templatePath);
}
}
exports.Loader = Loader;
+58
View File
@@ -0,0 +1,58 @@
import { TagToken } from 'edge-lexer';
import { ProcessorContract, TemplateContract } from '../Contracts';
/**
* Exposes the API to register a set of handlers to process the
* templates output at different stages
*/
export declare class Processor implements ProcessorContract {
private handlers;
/**
* Execute tag handler
*/
executeTag(data: {
tag: TagToken;
path: string;
}): void;
/**
* Execute raw handlers
*/
executeRaw(data: {
raw: string;
path: string;
}): string;
/**
* Execute compiled handlers
*/
executeCompiled(data: {
compiled: string;
path: string;
}): string;
/**
* Execute output handlers
*/
executeOutput(data: {
output: string;
template: TemplateContract;
state: Record<string, any>;
}): string;
/**
* Define a processor function
*/
process(event: 'raw', handler: (data: {
raw: string;
path: string;
}) => string | void): this;
process(event: 'tag', handler: (data: {
tag: TagToken;
path: string;
}) => void): this;
process(event: 'compiled', handler: (data: {
compiled: string;
path: string;
}) => string | void): this;
process(event: 'output', handler: (data: {
output: string;
template: TemplateContract;
state: Record<string, any>;
}) => string | void): this;
}
+88
View File
@@ -0,0 +1,88 @@
"use strict";
/*
* edge-js
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Processor = void 0;
/**
* Exposes the API to register a set of handlers to process the
* templates output at different stages
*/
class Processor {
constructor() {
this.handlers = new Map();
}
/**
* Execute tag handler
*/
executeTag(data) {
const handlers = this.handlers.get('tag');
if (!handlers) {
return;
}
handlers.forEach((handler) => {
handler(data);
});
}
/**
* Execute raw handlers
*/
executeRaw(data) {
const handlers = this.handlers.get('raw');
if (!handlers) {
return data.raw;
}
handlers.forEach((handler) => {
const output = handler(data);
if (output !== undefined) {
data.raw = output;
}
});
return data.raw;
}
/**
* Execute compiled handlers
*/
executeCompiled(data) {
const handlers = this.handlers.get('compiled');
if (!handlers) {
return data.compiled;
}
handlers.forEach((handler) => {
const output = handler(data);
if (output !== undefined) {
data.compiled = output;
}
});
return data.compiled;
}
/**
* Execute output handlers
*/
executeOutput(data) {
const handlers = this.handlers.get('output');
if (!handlers) {
return data.output;
}
handlers.forEach((handler) => {
const output = handler(data);
if (output !== undefined) {
data.output = output;
}
});
return data.output;
}
process(event, handler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event).add(handler);
return this;
}
}
exports.Processor = Processor;
+34
View File
@@ -0,0 +1,34 @@
import { Processor } from '../Processor';
import { EdgeRendererContract, CompilerContract } from '../Contracts';
/**
* Renders a given template with it's shared state
*/
export declare class EdgeRenderer implements EdgeRendererContract {
private compiler;
private asyncCompiler;
private globals;
private processor;
private locals;
constructor(compiler: CompilerContract, asyncCompiler: CompilerContract, globals: any, processor: Processor);
/**
* Share local variables with the template. They will overwrite the
* globals
*/
share(data: any): this;
/**
* Render the template
*/
render(templatePath: string, state?: any): Promise<string>;
/**
* Render the template
*/
renderSync(templatePath: string, state?: any): string;
/**
* Render the template from a raw string
*/
renderRaw(contents: string, state?: any, templatePath?: string): Promise<string>;
/**
* Render the template from a raw string
*/
renderRawSync(contents: string, state?: any, templatePath?: string): string;
}
+58
View File
@@ -0,0 +1,58 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EdgeRenderer = void 0;
const utils_1 = require("@poppinss/utils");
const Template_1 = require("../Template");
/**
* Renders a given template with it's shared state
*/
class EdgeRenderer {
constructor(compiler, asyncCompiler, globals, processor) {
this.compiler = compiler;
this.asyncCompiler = asyncCompiler;
this.globals = globals;
this.processor = processor;
this.locals = {};
}
/**
* Share local variables with the template. They will overwrite the
* globals
*/
share(data) {
utils_1.lodash.merge(this.locals, data);
return this;
}
/**
* Render the template
*/
async render(templatePath, state = {}) {
return new Template_1.Template(this.asyncCompiler, this.globals, this.locals, this.processor).render(templatePath, state);
}
/**
* Render the template
*/
renderSync(templatePath, state = {}) {
return new Template_1.Template(this.compiler, this.globals, this.locals, this.processor).render(templatePath, state);
}
/**
* Render the template from a raw string
*/
async renderRaw(contents, state = {}, templatePath) {
return new Template_1.Template(this.asyncCompiler, this.globals, this.locals, this.processor).renderRaw(contents, state, templatePath);
}
/**
* Render the template from a raw string
*/
renderRawSync(contents, state = {}, templatePath) {
return new Template_1.Template(this.compiler, this.globals, this.locals, this.processor).renderRaw(contents, state, templatePath);
}
}
exports.EdgeRenderer = EdgeRenderer;
+46
View File
@@ -0,0 +1,46 @@
import { Parser } from 'edge-parser';
/**
* This class generates a valid object as a string, which is written to the template
* output. The reason we need a string like object, since we don't want it's
* properties to be evaluated during the object creation, instead it must
* be evaluated when the compiled output is invoked.
*/
export declare class StringifiedObject {
private obj;
addSpread(key: string): void;
/**
* Add key/value pair to the object.
*
* ```js
* stringifiedObject.add('username', `'virk'`)
* ```
*/
add(key: any, value: any, isComputed?: boolean): void;
/**
* Returns the object alike string back.
*
* ```js
* stringifiedObject.flush()
*
* // returns
* `{ username: 'virk' }`
* ```
*/
flush(): string;
/**
* Parses an array of expressions to form an object. Each expression inside the array must
* be `ObjectExpression` or an `AssignmentExpression`, otherwise it will be ignored.
*
* ```js
* (title = 'hello')
* // returns { title: 'hello' }
*
* ({ title: 'hello' })
* // returns { title: 'hello' }
*
* ({ title: 'hello' }, username = 'virk')
* // returns { title: 'hello', username: 'virk' }
* ```
*/
static fromAcornExpressions(expressions: any[], parser: Parser): string;
}
+91
View File
@@ -0,0 +1,91 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StringifiedObject = void 0;
/**
* This class generates a valid object as a string, which is written to the template
* output. The reason we need a string like object, since we don't want it's
* properties to be evaluated during the object creation, instead it must
* be evaluated when the compiled output is invoked.
*/
class StringifiedObject {
constructor() {
this.obj = '';
}
addSpread(key) {
this.obj += this.obj.length ? `, ${key}` : `${key}`;
}
/**
* Add key/value pair to the object.
*
* ```js
* stringifiedObject.add('username', `'virk'`)
* ```
*/
add(key, value, isComputed = false) {
key = isComputed ? `[${key}]` : key;
this.obj += this.obj.length ? `, ${key}: ${value}` : `${key}: ${value}`;
}
/**
* Returns the object alike string back.
*
* ```js
* stringifiedObject.flush()
*
* // returns
* `{ username: 'virk' }`
* ```
*/
flush() {
const obj = `{ ${this.obj} }`;
this.obj = '';
return obj;
}
/**
* Parses an array of expressions to form an object. Each expression inside the array must
* be `ObjectExpression` or an `AssignmentExpression`, otherwise it will be ignored.
*
* ```js
* (title = 'hello')
* // returns { title: 'hello' }
*
* ({ title: 'hello' })
* // returns { title: 'hello' }
*
* ({ title: 'hello' }, username = 'virk')
* // returns { title: 'hello', username: 'virk' }
* ```
*/
static fromAcornExpressions(expressions, parser) {
if (!Array.isArray(expressions)) {
throw new Error('"fromAcornExpressions" expects an array of acorn ast expressions');
}
const objectifyString = new this();
expressions.forEach((arg) => {
if (arg.type === 'ObjectExpression') {
arg.properties.forEach((prop) => {
if (prop.type === 'SpreadElement') {
objectifyString.addSpread(parser.utils.stringify(prop));
}
else {
const key = parser.utils.stringify(prop.key);
const value = parser.utils.stringify(prop.value);
objectifyString.add(key, value, prop.computed);
}
});
}
if (arg.type === 'AssignmentExpression') {
objectifyString.add(arg.left.name, parser.utils.stringify(arg.right));
}
});
return objectifyString.flush();
}
}
exports.StringifiedObject = StringifiedObject;
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* The component tag implementation. It is one of the most complex tags and
* can be used as a reference for creating other tags.
*/
export declare const componentTag: TagContract;
+274
View File
@@ -0,0 +1,274 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.componentTag = void 0;
const edge_error_1 = require("edge-error");
const edge_lexer_1 = require("edge-lexer");
const edge_parser_1 = require("edge-parser");
const StringifiedObject_1 = require("../StringifiedObject");
const utils_1 = require("../utils");
/**
* A list of allowed expressions for the component name
*/
const ALLOWED_EXPRESSION_FOR_COMPONENT_NAME = [
edge_parser_1.expressions.Identifier,
edge_parser_1.expressions.Literal,
edge_parser_1.expressions.LogicalExpression,
edge_parser_1.expressions.MemberExpression,
edge_parser_1.expressions.ConditionalExpression,
edge_parser_1.expressions.CallExpression,
edge_parser_1.expressions.TemplateLiteral,
];
/**
* Returns the component name and props by parsing the component jsArg expression
*/
function getComponentNameAndProps(expression, parser, filename) {
let name;
/**
* Use the first expression inside the sequence expression as the name
* of the component
*/
if (expression.type === edge_parser_1.expressions.SequenceExpression) {
name = expression.expressions.shift();
}
else {
name = expression;
}
/**
* Ensure the component name is a literal value or an expression that
* outputs a literal value
*/
(0, utils_1.isSubsetOf)(name, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME, () => {
(0, utils_1.unallowedExpression)(`"${parser.utils.stringify(name)}" is not a valid argument for component name`, filename, parser.utils.getExpressionLoc(name));
});
/**
* Parse rest of sequence expressions as an objectified string.
*/
if (expression.type === edge_parser_1.expressions.SequenceExpression) {
/**
* We only need to entertain the first expression of the sequence
* expression, as components allows a max of two arguments
*/
const firstSequenceExpression = expression.expressions[0];
if (firstSequenceExpression &&
[edge_parser_1.expressions.ObjectExpression, edge_parser_1.expressions.AssignmentExpression].includes(firstSequenceExpression.type)) {
return [
parser.utils.stringify(name),
StringifiedObject_1.StringifiedObject.fromAcornExpressions([firstSequenceExpression], parser),
];
}
return [parser.utils.stringify(name), parser.utils.stringify(firstSequenceExpression)];
}
/**
* When top level expression is not a sequence expression, then we assume props
* as empty stringified object.
*/
return [parser.utils.stringify(name), '{}'];
}
/**
* Parses the slot component to fetch it's name and props
*/
function getSlotNameAndProps(token, parser) {
/**
* We just generate the acorn AST only, since we don't want parser to transform
* ast to edge statements for a `@slot` tag.
*/
const parsed = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename).expression;
(0, utils_1.isSubsetOf)(parsed, [edge_parser_1.expressions.Literal, edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Fetch the slot name
*/
let name;
if (parsed.type === edge_parser_1.expressions.SequenceExpression) {
name = parsed.expressions[0];
}
else {
name = parsed;
}
/**
* Validating the slot name to be a literal value, since slot names cannot be dynamic
*/
(0, utils_1.isSubsetOf)(name, [edge_parser_1.expressions.Literal], () => {
(0, utils_1.unallowedExpression)('slot name must be a valid string literal', token.filename, parser.utils.getExpressionLoc(name));
});
/**
* Return the slot name with empty props, when the expression is a literal
* value.
*/
if (parsed.type === edge_parser_1.expressions.Literal) {
return [name.raw, null];
}
/**
* Make sure the sequence expression has only 2 arguments in it. Though it doesn't hurt
* the rendering of component, we must not run code with false expectations.
*/
if (parsed.expressions.length > 2) {
throw new edge_error_1.EdgeError('maximum of 2 arguments are allowed for @slot tag', 'E_MAX_ARGUMENTS', {
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename,
});
}
(0, utils_1.isSubsetOf)(parsed.expressions[1], [edge_parser_1.expressions.Identifier], () => {
(0, utils_1.unallowedExpression)(`"${parser.utils.stringify(parsed.expressions[1])}" is not valid prop identifier for @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed.expressions[1]));
});
/**
* Returning the slot name and slot props name
*/
return [name.raw, parsed.expressions[1].name];
}
/**
* The component tag implementation. It is one of the most complex tags and
* can be used as a reference for creating other tags.
*/
exports.componentTag = {
block: true,
seekable: true,
tagName: 'component',
compile(parser, buffer, token) {
const asyncKeyword = parser.asyncMode ? 'async ' : '';
const awaitKeyword = parser.asyncMode ? 'await ' : '';
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* Check component jsProps for allowed expressions
*/
(0, utils_1.isSubsetOf)(parsed, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME.concat(edge_parser_1.expressions.SequenceExpression), () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @component tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Pulling the name and props for the component. The underlying method will
* ensure that the arguments passed to component tag are valid
*/
const [name, props] = getComponentNameAndProps(parsed, parser, token.filename);
/**
* Loop over all the children and set them as part of slots. If no slot
* is defined, then the content will be part of the main slot
*/
const slots = {};
/**
* Main slot collects everything that is out of the named slots
* inside a component
*/
const mainSlot = {
outputVar: 'slot_main',
props: {},
buffer: buffer.create(token.filename, {
outputVar: 'slot_main',
}),
line: -1,
filename: token.filename,
};
let slotsCounter = 0;
/**
* Loop over all the component children
*/
token.children.forEach((child) => {
/**
* If children is not a slot, then add it to the main slot
*/
if (!edge_lexer_1.utils.isTag(child, 'slot')) {
/**
* Ignore first newline inside the unnamed main slot
*/
if (mainSlot.buffer.size === 0 && child.type === 'newline') {
return;
}
parser.processToken(child, mainSlot.buffer);
return;
}
/**
* Fetch slot and props
*/
const [slotName, slotProps] = getSlotNameAndProps(child, parser);
slotsCounter++;
/**
* Create a new slot with buffer to process the children
*/
if (!slots[slotName]) {
/**
* Slot buffer points to the component file name, since slots doesn't
* have their own file names.
*/
slots[slotName] = {
outputVar: `slot_${slotsCounter}`,
buffer: buffer.create(token.filename, {
outputVar: `slot_${slotsCounter}`,
}),
props: slotProps,
line: -1,
filename: token.filename,
};
/**
* Only start the frame, when there are props in use for a given slot.
*/
if (slotProps) {
parser.stack.defineScope();
parser.stack.defineVariable(slotProps);
}
}
/**
* Self process the slot children.
*/
child.children.forEach((grandChildren) => {
parser.processToken(grandChildren, slots[slotName].buffer);
});
/**
* Close the frame after process the slot children
*/
if (slotProps) {
parser.stack.clearScope();
}
});
const obj = new StringifiedObject_1.StringifiedObject();
/**
* Creating a shallow copy of context for the component slots and its children
*/
obj.add('$context', 'Object.assign({}, $context)');
/**
* Add main slot to the stringified object, when main slot
* is not defined otherwise.
*/
if (!slots['main']) {
if (mainSlot.buffer.size) {
mainSlot.buffer.wrap(`${asyncKeyword}function () { const $context = this.$context;`, '}');
obj.add('main', mainSlot.buffer.disableFileAndLineVariables().flush());
}
else {
obj.add('main', 'function () { return "" }');
}
}
/**
* We convert the slots to an objectified string, that is passed to `template.renderWithState`,
* which will pass it to the component as it's local state.
*/
Object.keys(slots).forEach((slotName) => {
if (slots[slotName].buffer.size) {
const fnCall = slots[slotName].props
? `${asyncKeyword}function (${slots[slotName].props}) { const $context = this.$context;`
: `${asyncKeyword}function () { const $context = this.$context;`;
slots[slotName].buffer.wrap(fnCall, '}');
obj.add(slotName, slots[slotName].buffer.disableFileAndLineVariables().flush());
}
else {
obj.add(slotName, 'function () { return "" }');
}
});
const caller = new StringifiedObject_1.StringifiedObject();
caller.add('filename', '$filename');
caller.add('line', '$lineNumber');
caller.add('col', 0);
/**
* Write the line to render the component with it's own state
*/
buffer.outputExpression(`${awaitKeyword}template.compileComponent(${name})(template, template.getComponentState(${props}, ${obj.flush()}, ${caller.flush()}), $context)`, token.filename, token.loc.start.line, false);
},
};
+5
View File
@@ -0,0 +1,5 @@
import { TagContract } from '../Contracts';
/**
* Add debugger break point to the compiled template
*/
export declare const debuggerTag: TagContract;
+26
View File
@@ -0,0 +1,26 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.debuggerTag = void 0;
/**
* Add debugger break point to the compiled template
*/
exports.debuggerTag = {
block: false,
seekable: false,
tagName: 'debugger',
noNewLine: true,
/**
* Compiles `@debugger` tags
*/
compile(_, buffer, token) {
buffer.writeExpression('debugger', token.filename, token.loc.start.line);
},
};
+11
View File
@@ -0,0 +1,11 @@
import { TagContract } from '../Contracts';
/**
* Each tag is used to run a foreach loop on arrays and even objects.
*
* ```edge
* @each((user, index) in users)
* {{ user }} {{ index }}
* @endeach
* ```
*/
export declare const eachTag: TagContract;
+149
View File
@@ -0,0 +1,149 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.eachTag = void 0;
const utils_1 = require("@poppinss/utils");
const edge_lexer_1 = require("edge-lexer");
const edge_parser_1 = require("edge-parser");
const utils_2 = require("../utils");
/**
* Returns the list to loop over for the each binary expression
*/
function getLoopList(rhsExpression, parser, filename) {
return parser.utils.stringify(parser.utils.transformAst(rhsExpression, filename, parser));
}
/**
* Returns loop item and the index for the each binary expression
*/
function getLoopItemAndIndex(lhsExpression, parser, filename) {
/**
* Ensure the LHS content inside `@each()` curly braces is a `SequenceExpression` or
* `Identifier`. Anything else is not allowed.
*
* For example:
*
* - In `@each(user in users)`, `user` is an indentifier
* - In `@each((user, index) in users)`, `(user, index)` is a sequence expression
*/
(0, utils_2.isSubsetOf)(lhsExpression, [edge_parser_1.expressions.SequenceExpression, edge_parser_1.expressions.Identifier], () => {
(0, utils_2.unallowedExpression)(`invalid left hand side "${lhsExpression.type}" expression for the @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression));
});
/**
* Return list index from the sequence expression
*/
if (lhsExpression.type === 'SequenceExpression') {
/**
* First item of the sequence expression must be an idenifier
*/
(0, utils_2.isSubsetOf)(lhsExpression.expressions[0], [edge_parser_1.expressions.Identifier], () => {
(0, utils_2.unallowedExpression)(`"${lhsExpression.expressions[0]}.type" is not allowed as value identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[0]));
});
/**
* Second item of the sequence expression must be an idenifier
*/
(0, utils_2.isSubsetOf)(lhsExpression.expressions[1], [edge_parser_1.expressions.Identifier], () => {
(0, utils_2.unallowedExpression)(`"${lhsExpression.expressions[1]}.type" is not allowed as key identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[1]));
});
return [lhsExpression.expressions[0].name, lhsExpression.expressions[1].name];
}
/**
* There is no key, just the value
*/
return [lhsExpression.name];
}
/**
* Each tag is used to run a foreach loop on arrays and even objects.
*
* ```edge
* @each((user, index) in users)
* {{ user }} {{ index }}
* @endeach
* ```
*/
exports.eachTag = {
block: true,
seekable: true,
tagName: 'each',
/**
* Compile the template
*/
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? 'await ' : '';
const loopFunctionName = parser.asyncMode ? 'loopAsync' : 'loop';
const asyncKeyword = parser.asyncMode ? 'async ' : '';
/**
* We just generate the AST and do not transform it, since the transform
* function attempts to resolve identifiers and we don't want that
*/
const { expression } = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename);
/**
* Each tag only accepts the binary expression or sequence expression. ie `user in users`
*/
(0, utils_2.isSubsetOf)(expression, [edge_parser_1.expressions.BinaryExpression], () => {
(0, utils_2.unallowedExpression)(`"${token.properties.jsArg}" is not valid expression for the @each tag`, token.filename, parser.utils.getExpressionLoc(expression));
});
/**
* Finding if an else child exists inside the each tag
*/
const elseIndex = token.children.findIndex((child) => edge_lexer_1.utils.isTag(child, 'else'));
const elseChildren = elseIndex > -1 ? token.children.splice(elseIndex) : [];
/**
* Fetching the item,index and list for the each loop
*/
const list = getLoopList(expression.right, parser, token.filename);
const [item, index] = getLoopItemAndIndex(expression.left, parser, token.filename);
/**
* If there is an else statement, then wrap the loop inside the `if` statement first
*/
if (elseIndex > -1) {
buffer.writeStatement(`if(template.size(${list})) {`, token.filename, token.loc.start.line);
}
/**
* Write the loop statement to the template
*/
const loopCallbackArgs = (index ? [item, index] : [item]).join(',');
buffer.writeStatement(`${awaitKeyword}template.${loopFunctionName}(${list}, ${asyncKeyword}function (${loopCallbackArgs}) {`, token.filename, token.loc.start.line);
/**
* Start a new parser scope. So that all variable resolutions for the `item`
* are pointing to the local variable and not the template `state`.
*/
parser.stack.defineScope();
parser.stack.defineVariable(item);
index && parser.stack.defineVariable(index);
/**
* Process all children
*/
token.children.forEach((child) => parser.processToken(child, buffer));
/**
* Clear scope
*/
parser.stack.clearScope();
/**
* Close each loop
*/
buffer.writeExpression('})', token.filename, -1);
/**
* If there is an else statement, then process
* else childs and close the if block
*/
if (elseIndex > -1) {
elseChildren.forEach((elseChild) => parser.processToken(elseChild, buffer));
buffer.writeStatement('}', token.filename, -1);
}
},
/**
* Add methods to the template for running the loop
*/
boot(template) {
template.macro('loopAsync', utils_2.asyncEach);
template.macro('loop', utils_2.each);
template.macro('size', utils_1.lodash.size);
},
};
+2
View File
@@ -0,0 +1,2 @@
import { TagContract } from '../Contracts';
export declare const elseTag: TagContract;
+22
View File
@@ -0,0 +1,22 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.elseTag = void 0;
exports.elseTag = {
block: false,
seekable: false,
tagName: 'else',
/**
* Compiles else block node to Javascript else statement
*/
compile(_, buffer, token) {
buffer.writeStatement('} else {', token.filename, -1);
},
};
+7
View File
@@ -0,0 +1,7 @@
import { TagContract } from '../Contracts';
/**
* Else if tag is used to define conditional blocks. We keep `@elseif` tag
* is a inline tag, so that everything between the `if` and the `elseif`
* comes `if` children.
*/
export declare const elseIfTag: TagContract;
+39
View File
@@ -0,0 +1,39 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.elseIfTag = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* Else if tag is used to define conditional blocks. We keep `@elseif` tag
* is a inline tag, so that everything between the `if` and the `elseif`
* comes `if` children.
*/
exports.elseIfTag = {
block: false,
seekable: true,
tagName: 'elseif',
/**
* Compiles the else if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* Disallow sequence expressions
*/
(0, utils_1.isNotSubsetOf)(parsed, [edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Start else if block
*/
buffer.writeStatement(`} else if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
},
};
+5
View File
@@ -0,0 +1,5 @@
import { TagContract } from '../Contracts';
/**
* If tag is used to define conditional blocks.
*/
export declare const ifTag: TagContract;
+45
View File
@@ -0,0 +1,45 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ifTag = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* If tag is used to define conditional blocks.
*/
exports.ifTag = {
block: true,
seekable: true,
tagName: 'if',
/**
* Compiles the if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* Disallow sequence expressions
*/
(0, utils_1.isNotSubsetOf)(parsed, [edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @if tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Start if block
*/
buffer.writeStatement(`if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
/**
* Process of all children recursively
*/
token.children.forEach((child) => parser.processToken(child, buffer));
/**
* Close if block
*/
buffer.writeStatement('}', token.filename, -1);
},
};
+19
View File
@@ -0,0 +1,19 @@
import { Parser } from 'edge-parser';
import { TagContract } from '../Contracts';
/**
* List of expressions allowed for the include tag
*/
export declare const ALLOWED_EXPRESSION: ("Identifier" | "MemberExpression" | "CallExpression" | "Literal" | "TemplateLiteral" | "ConditionalExpression" | "LogicalExpression")[];
/**
* Returns the expression for rendering the partial
*/
export declare function getRenderExpression(parser: Parser, parsedExpression: any): string;
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
export declare const includeTag: TagContract;
+78
View File
@@ -0,0 +1,78 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.includeTag = exports.getRenderExpression = exports.ALLOWED_EXPRESSION = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* List of expressions allowed for the include tag
*/
exports.ALLOWED_EXPRESSION = [
edge_parser_1.expressions.Identifier,
edge_parser_1.expressions.Literal,
edge_parser_1.expressions.LogicalExpression,
edge_parser_1.expressions.MemberExpression,
edge_parser_1.expressions.ConditionalExpression,
edge_parser_1.expressions.CallExpression,
edge_parser_1.expressions.TemplateLiteral,
];
/**
* Returns the expression for rendering the partial
*/
function getRenderExpression(parser, parsedExpression) {
/**
* We need to pass the local variables to the partial render function
*/
const localVariables = parser.stack.list();
/**
* Arguments for the `renderInline` method
*/
const renderArgs = localVariables.length
? [
parser.utils.stringify(parsedExpression),
localVariables.map((localVar) => `"${localVar}"`).join(','),
]
: [parser.utils.stringify(parsedExpression)];
/**
* Arguments for invoking the output function of `renderInline`
*/
const callFnArgs = localVariables.length
? ['template', 'state', '$context', localVariables.map((localVar) => localVar).join(',')]
: ['template', 'state', '$context'];
return `template.compilePartial(${renderArgs.join(',')})(${callFnArgs.join(',')})`;
}
exports.getRenderExpression = getRenderExpression;
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
exports.includeTag = {
block: false,
seekable: true,
tagName: 'include',
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? 'await ' : '';
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* Only mentioned expressions are allowed inside `@include` tag
*/
(0, utils_1.isSubsetOf)(parsed, exports.ALLOWED_EXPRESSION, () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @include tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
buffer.outputExpression(`${awaitKeyword}${getRenderExpression(parser, parsed)}`, token.filename, token.loc.start.line, false);
},
};
+10
View File
@@ -0,0 +1,10 @@
import { TagContract } from '../Contracts';
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
export declare const includeIfTag: TagContract;
+61
View File
@@ -0,0 +1,61 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.includeIfTag = void 0;
const edge_error_1 = require("edge-error");
const edge_parser_1 = require("edge-parser");
const Include_1 = require("./Include");
const utils_1 = require("../utils");
/**
* Include tag is used to include partials in the same scope of the parent
* template.
*
* ```edge
* @include('partials.header')
* ```
*/
exports.includeIfTag = {
block: false,
seekable: true,
tagName: 'includeIf',
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {
const awaitKeyword = parser.asyncMode ? 'await ' : '';
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* The include if only accepts the sequence expression
*/
(0, utils_1.isSubsetOf)(parsed, [edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Disallow more than or less than 2 values for the sequence expression
*/
if (parsed.expressions.length !== 2) {
throw new edge_error_1.EdgeError('@includeIf expects a total of 2 arguments', 'E_ARGUMENTS_MIS_MATCH', {
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename,
});
}
const [conditional, include] = parsed.expressions;
(0, utils_1.isNotSubsetOf)(conditional, [edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`"${conditional.type}" is not a valid 1st argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(conditional));
});
(0, utils_1.isSubsetOf)(include, Include_1.ALLOWED_EXPRESSION, () => {
(0, utils_1.unallowedExpression)(`"${include.type}" is not a valid 2nd argument type for the @includeIf tag`, token.filename, parser.utils.getExpressionLoc(include));
});
buffer.writeStatement(`if (${parser.utils.stringify(conditional)}) {`, token.filename, token.loc.start.line);
buffer.outputExpression(`${awaitKeyword}${(0, Include_1.getRenderExpression)(parser, include)}`, token.filename, token.loc.start.line, false);
buffer.writeStatement('}', token.filename, -1);
},
};
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* The inject tag is used within the components to share values with the
* component caller.
*/
export declare const injectTag: TagContract;
+40
View File
@@ -0,0 +1,40 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.injectTag = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* The inject tag is used within the components to share values with the
* component caller.
*/
exports.injectTag = {
block: false,
seekable: true,
tagName: 'inject',
noNewLine: true,
compile(parser, buffer, token) {
token.properties.jsArg = `(${token.properties.jsArg})`;
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* The inject tag only accepts an object expression.
*/
(0, utils_1.isSubsetOf)(parsed, [edge_parser_1.expressions.ObjectExpression, edge_parser_1.expressions.Identifier], () => {
throw (0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid key-value pair for the @inject tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Ensure $slots are defined before merging shared state
*/
buffer.writeStatement('if (!state.$slots || !state.$slots.$context) {', token.filename, token.loc.start.line);
buffer.writeExpression(`throw new Error('Cannot use "@inject" outside of a component scope')`, token.filename, token.loc.start.line);
buffer.writeStatement('}', token.filename, token.loc.start.line);
buffer.writeExpression(`Object.assign(state.$slots.$context, ${parser.utils.stringify(parsed)})`, token.filename, token.loc.start.line);
},
};
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* Layout tag is used to define parent layout for a given template. The layout
* must appear in the first line of the template itself.
*/
export declare const layoutTag: TagContract;
+25
View File
@@ -0,0 +1,25 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.layoutTag = void 0;
/**
* Layout tag is used to define parent layout for a given template. The layout
* must appear in the first line of the template itself.
*/
exports.layoutTag = {
block: false,
seekable: true,
tagName: 'layout',
noNewLine: true,
compile() {
// The layouts are handled by the template itself. I am just a way to
// tell lexer to parse me as a block node
},
};
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* newError tag to raise exceptions inside your templates. They will point
* back to the exact line:col in the template
*/
export declare const newErrorTag: TagContract;
+47
View File
@@ -0,0 +1,47 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.newErrorTag = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* newError tag to raise exceptions inside your templates. They will point
* back to the exact line:col in the template
*/
exports.newErrorTag = {
block: false,
seekable: true,
tagName: 'newError',
noNewLine: true,
compile(parser, buffer, token) {
const parsed = (0, utils_1.parseJsArg)(parser, token);
let message = '';
let line = token.loc.start.line;
let col = token.loc.start.col;
let filename = '$filename';
if (parsed.type === edge_parser_1.expressions.SequenceExpression) {
message = parser.utils.stringify(parsed.expressions[0]);
filename = parsed.expressions[1] ? parser.utils.stringify(parsed.expressions[1]) : '$filename';
line = parsed.expressions[2]
? parser.utils.stringify(parsed.expressions[2])
: token.loc.start.line;
col = parsed.expressions[3]
? parser.utils.stringify(parsed.expressions[3])
: token.loc.start.col;
}
else {
message = parser.utils.stringify(parsed);
}
/**
* Raise the exception with the correct filename and the line number
*/
buffer.writeStatement(`template.newError(${message}, ${filename}, ${line}, ${col})`, token.filename, token.loc.start.line);
},
};
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* Section tag is used to define the sections on a given template. Sections cannot be
* nested and must appear as top level children inside a component.
*/
export declare const sectionTag: TagContract;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.sectionTag = void 0;
/**
* Section tag is used to define the sections on a given template. Sections cannot be
* nested and must appear as top level children inside a component.
*/
exports.sectionTag = {
block: true,
seekable: true,
tagName: 'section',
compile(parser, buffer, token) {
token.children.forEach((child) => parser.processToken(child, buffer));
},
};
+20
View File
@@ -0,0 +1,20 @@
import { TagContract } from '../Contracts';
/**
* The set tag is used to set runtime values within the template. The value
* is set inside the current scope of the template.
*
* ```edge
* @set('user.username', 'virk')
* <p> {{ user.username }} </p>
* ```
*
* Set it inside the each loop.
*
* ```edge
* @each(user in users)
* @set('age', user.age + 1)
* {{ age }}
* @endeach
* ```
*/
export declare const setTag: TagContract;
+106
View File
@@ -0,0 +1,106 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.setTag = void 0;
const edge_error_1 = require("edge-error");
const edge_parser_1 = require("edge-parser");
const utils_1 = require("@poppinss/utils");
const utils_2 = require("../utils");
/**
* The set tag is used to set runtime values within the template. The value
* is set inside the current scope of the template.
*
* ```edge
* @set('user.username', 'virk')
* <p> {{ user.username }} </p>
* ```
*
* Set it inside the each loop.
*
* ```edge
* @each(user in users)
* @set('age', user.age + 1)
* {{ age }}
* @endeach
* ```
*/
exports.setTag = {
block: false,
seekable: true,
tagName: 'set',
noNewLine: true,
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {
const parsed = (0, utils_2.parseJsArg)(parser, token);
/**
* The set tag only accepts a sequence expression.
*/
(0, utils_2.isSubsetOf)(parsed, [edge_parser_1.expressions.SequenceExpression], () => {
throw (0, utils_2.unallowedExpression)(`"${token.properties.jsArg}" is not a valid key-value pair for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Disallow more than 2 values for the sequence expression
*/
if (parsed.expressions.length < 2 || parsed.expressions.length > 3) {
throw new edge_error_1.EdgeError('@set tag accepts a minimum of 2 or maximum or 3 arguments', 'E_INVALID_ARGUMENTS_COUNT', {
line: parsed.loc.start.line,
col: parsed.loc.start.column,
filename: token.filename,
});
}
/**
* Extract key-value and the collection (if any)
*/
let collection;
let key;
let value;
if (parsed.expressions.length === 3) {
collection = parsed.expressions[0];
key = parsed.expressions[1];
value = parsed.expressions[2];
}
else {
key = parsed.expressions[0];
value = parsed.expressions[1];
}
/**
* The key has to be a literal value
*/
(0, utils_2.isSubsetOf)(key, [edge_parser_1.expressions.Literal], () => {
throw (0, utils_2.unallowedExpression)(`The ${collection ? 'second' : 'first'} argument for @set tag must be a string literal`, token.filename, parser.utils.getExpressionLoc(key));
});
/**
* Mutate the collection when defined
*/
if (collection) {
buffer.writeExpression(`template.setValue(${parser.utils.stringify(collection)}, '${key.value}', ${parser.utils.stringify(value)})`, token.filename, token.loc.start.line);
return;
}
/**
* Write statement to mutate the key. If the variable has already been
* defined, then just update it's value.
*
* We do not allow re-declaring a variable as of now
*/
const expression = parser.stack.has(key.value)
? `${key.value} = ${parser.utils.stringify(value)}`
: `let ${key.value} = ${parser.utils.stringify(value)}`;
buffer.writeExpression(expression, token.filename, token.loc.start.line);
parser.stack.defineVariable(key.value);
},
/**
* Add methods to the template for running the loop
*/
boot(template) {
template.macro('setValue', utils_1.lodash.set);
},
};
+6
View File
@@ -0,0 +1,6 @@
import { TagContract } from '../Contracts';
/**
* Slot tag is used to define the slots of a given component. Slots cannot be
* nested and must appear as top level children inside a component.
*/
export declare const slotTag: TagContract;
+29
View File
@@ -0,0 +1,29 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.slotTag = void 0;
const edge_error_1 = require("edge-error");
/**
* Slot tag is used to define the slots of a given component. Slots cannot be
* nested and must appear as top level children inside a component.
*/
exports.slotTag = {
block: true,
seekable: true,
tagName: 'slot',
noNewLine: true,
compile(_, __, token) {
throw new edge_error_1.EdgeError('@slot tag must appear as top level tag inside the @component tag', 'E_ORPHAN_SLOT_TAG', {
line: token.loc.start.line,
col: token.loc.start.col,
filename: token.filename,
});
},
};
+9
View File
@@ -0,0 +1,9 @@
import { TagContract } from '../Contracts';
/**
* Super tag is used inside sections to inherit the parent section
* content.
*
* The implementation of super tag is handled by the compiler itself, but we need
* the tag to exists, so that the lexer can parse it as a tag.
*/
export declare const superTag: TagContract;
+31
View File
@@ -0,0 +1,31 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.superTag = void 0;
const edge_error_1 = require("edge-error");
/**
* Super tag is used inside sections to inherit the parent section
* content.
*
* The implementation of super tag is handled by the compiler itself, but we need
* the tag to exists, so that the lexer can parse it as a tag.
*/
exports.superTag = {
block: false,
seekable: false,
tagName: 'super',
compile(_, __, token) {
throw new edge_error_1.EdgeError('@super tag must appear as top level tag inside the @section tag', 'E_ORPHAN_SUPER_TAG', {
line: token.loc.start.line,
col: token.loc.start.col,
filename: token.filename,
});
},
};
+12
View File
@@ -0,0 +1,12 @@
import { TagContract } from '../Contracts';
/**
* Inverse of the `if` condition. The term `unless` is more readable and logical
* vs using `@if(!expression)`.
*
* ```edge
* @unless(auth.user)
* <a href="/login"> Login </a>
* @endunless
* ```
*/
export declare const unlessTag: TagContract;
+52
View File
@@ -0,0 +1,52 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.unlessTag = void 0;
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* Inverse of the `if` condition. The term `unless` is more readable and logical
* vs using `@if(!expression)`.
*
* ```edge
* @unless(auth.user)
* <a href="/login"> Login </a>
* @endunless
* ```
*/
exports.unlessTag = {
block: true,
seekable: true,
tagName: 'unless',
/**
* Compiles the if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = (0, utils_1.parseJsArg)(parser, token);
/**
* Disallow sequence expressions
*/
(0, utils_1.isNotSubsetOf)(parsed, [edge_parser_1.expressions.SequenceExpression], () => {
(0, utils_1.unallowedExpression)(`"${token.properties.jsArg}" is not a valid argument type for the @unless tag`, token.filename, parser.utils.getExpressionLoc(parsed));
});
/**
* Start if block
*/
buffer.writeStatement(`if (!${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line);
/**
* Process of all children recursively
*/
token.children.forEach((child) => parser.processToken(child, buffer));
/**
* Close if block
*/
buffer.writeStatement('}', token.filename, -1);
},
};
+16
View File
@@ -0,0 +1,16 @@
export { ifTag as if } from './If';
export { elseTag as else } from './Else';
export { elseIfTag as elseif } from './ElseIf';
export { includeTag as include } from './Include';
export { includeIfTag as includeIf } from './IncludeIf';
export { eachTag as each } from './Each';
export { componentTag as component } from './Component';
export { slotTag as slot } from './Slot';
export { debuggerTag as debugger } from './Debugger';
export { setTag as set } from './Set';
export { unlessTag as unless } from './Unless';
export { layoutTag as layout } from './Layout';
export { sectionTag as section } from './Section';
export { superTag as super } from './Super';
export { injectTag as inject } from './Inject';
export { newErrorTag as newError } from './NewError';
+43
View File
@@ -0,0 +1,43 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.newError = exports.inject = exports.super = exports.section = exports.layout = exports.unless = exports.set = exports.debugger = exports.slot = exports.component = exports.each = exports.includeIf = exports.include = exports.elseif = exports.else = exports.if = void 0;
var If_1 = require("./If");
Object.defineProperty(exports, "if", { enumerable: true, get: function () { return If_1.ifTag; } });
var Else_1 = require("./Else");
Object.defineProperty(exports, "else", { enumerable: true, get: function () { return Else_1.elseTag; } });
var ElseIf_1 = require("./ElseIf");
Object.defineProperty(exports, "elseif", { enumerable: true, get: function () { return ElseIf_1.elseIfTag; } });
var Include_1 = require("./Include");
Object.defineProperty(exports, "include", { enumerable: true, get: function () { return Include_1.includeTag; } });
var IncludeIf_1 = require("./IncludeIf");
Object.defineProperty(exports, "includeIf", { enumerable: true, get: function () { return IncludeIf_1.includeIfTag; } });
var Each_1 = require("./Each");
Object.defineProperty(exports, "each", { enumerable: true, get: function () { return Each_1.eachTag; } });
var Component_1 = require("./Component");
Object.defineProperty(exports, "component", { enumerable: true, get: function () { return Component_1.componentTag; } });
var Slot_1 = require("./Slot");
Object.defineProperty(exports, "slot", { enumerable: true, get: function () { return Slot_1.slotTag; } });
var Debugger_1 = require("./Debugger");
Object.defineProperty(exports, "debugger", { enumerable: true, get: function () { return Debugger_1.debuggerTag; } });
var Set_1 = require("./Set");
Object.defineProperty(exports, "set", { enumerable: true, get: function () { return Set_1.setTag; } });
var Unless_1 = require("./Unless");
Object.defineProperty(exports, "unless", { enumerable: true, get: function () { return Unless_1.unlessTag; } });
var Layout_1 = require("./Layout");
Object.defineProperty(exports, "layout", { enumerable: true, get: function () { return Layout_1.layoutTag; } });
var Section_1 = require("./Section");
Object.defineProperty(exports, "section", { enumerable: true, get: function () { return Section_1.sectionTag; } });
var Super_1 = require("./Super");
Object.defineProperty(exports, "super", { enumerable: true, get: function () { return Super_1.superTag; } });
var Inject_1 = require("./Inject");
Object.defineProperty(exports, "inject", { enumerable: true, get: function () { return Inject_1.injectTag; } });
var NewError_1 = require("./NewError");
Object.defineProperty(exports, "newError", { enumerable: true, get: function () { return NewError_1.newErrorTag; } });
+116
View File
@@ -0,0 +1,116 @@
import { Macroable } from 'macroable';
import { Processor } from '../Processor';
import { CompilerContract, TemplateContract } from '../Contracts';
/**
* An instance of this class passed to the escape
* method ensures that underlying value is never
* escaped.
*/
export declare class SafeValue {
value: any;
constructor(value: any);
}
/**
* Escapes a given string
*/
export declare function escape(input: any): string;
/**
* Mark value as safe and not to be escaped
*/
export declare function safeValue(value: string): SafeValue;
/**
* The template is used to compile and run templates. Also the instance
* of template is passed during runtime to render `dynamic partials`
* and `dynamic components`.
*/
export declare class Template extends Macroable implements TemplateContract {
private compiler;
private processor;
/**
* Required by Macroable
*/
protected static macros: {};
protected static getters: {};
/**
* The shared state is used to hold the globals and locals,
* since it is shared with components too.
*/
private sharedState;
constructor(compiler: CompilerContract, globals: any, locals: any, processor: Processor);
/**
* Wraps template to a function
*/
private wrapToFunction;
/**
* Trims top and bottom new lines from the content
*/
private trimTopBottomNewLines;
/**
* Render a compiled template with state
*/
private renderCompiled;
/**
* Render a partial
*
* ```js
* const partialFn = template.compilePartial('includes/user')
*
* // render and use output
* partialFn(template, state, ctx)
* ```
*/
compilePartial(templatePath: string, ...localVariables: string[]): Function;
/**
* Render a component
*
* ```js
* const componentFn = template.compileComponent('components/button')
*
* // render and use output
* componentFn(template, template.getComponentState(props, slots, caller), ctx)
* ```
*/
compileComponent(templatePath: string, ...localVariables: string[]): string;
/**
* Returns the isolated state for a given component
*/
getComponentState(props: {
[key: string]: any;
}, slots: {
[key: string]: any;
}, caller: {
filename: string;
line: number;
col: number;
}): any;
/**
* Render a template with it's state.
*
* ```js
* template.render('welcome', { key: 'value' })
* ```
*/
render<T extends Promise<string> | string>(template: string, state: any): T;
/**
* Render template from a raw string
*
* ```js
* template.renderRaw('Hello {{ username }}', { username: 'virk' })
* ```
*/
renderRaw<T extends Promise<string> | string>(contents: string, state: any, templatePath?: string): T;
/**
* Escapes the value to be HTML safe. Only strings are escaped
* and rest all values will be returned as it is.
*/
escape(input: any): string;
/**
* Raise an error
*/
newError(errorMessage: string, filename: string, lineNumber: number, column: number): void;
/**
* Rethrows the runtime exception by re-constructing the error message
* to point back to the original filename
*/
reThrow(error: any, filename: string, lineNumber: number): never;
}
+186
View File
@@ -0,0 +1,186 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Template = exports.safeValue = exports.escape = exports.SafeValue = void 0;
const macroable_1 = require("macroable");
const edge_error_1 = require("edge-error");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Props_1 = require("../Component/Props");
/**
* An instance of this class passed to the escape
* method ensures that underlying value is never
* escaped.
*/
class SafeValue {
constructor(value) {
this.value = value;
}
}
exports.SafeValue = SafeValue;
/**
* Escapes a given string
*/
function escape(input) {
return input instanceof SafeValue ? input.value : helpers_1.string.escapeHTML(String(input));
}
exports.escape = escape;
/**
* Mark value as safe and not to be escaped
*/
function safeValue(value) {
return new SafeValue(value);
}
exports.safeValue = safeValue;
/**
* The template is used to compile and run templates. Also the instance
* of template is passed during runtime to render `dynamic partials`
* and `dynamic components`.
*/
class Template extends macroable_1.Macroable {
constructor(compiler, globals, locals, processor) {
super();
this.compiler = compiler;
this.processor = processor;
this.sharedState = utils_1.lodash.merge({}, globals, locals);
}
/**
* Wraps template to a function
*/
wrapToFunction(template, ...localVariables) {
const args = ['template', 'state', '$context'].concat(localVariables);
if (this.compiler.async) {
return new Function('', `return async function template (${args.join(',')}) { ${template} }`)();
}
return new Function('', `return function template (${args.join(',')}) { ${template} }`)();
}
/**
* Trims top and bottom new lines from the content
*/
trimTopBottomNewLines(value) {
return value.replace(/^\n|^\r\n/, '').replace(/\n$|\r\n$/, '');
}
/**
* Render a compiled template with state
*/
renderCompiled(compiledTemplate, state) {
const templateState = Object.assign({}, this.sharedState, state);
const $context = {};
/**
* Process template as a promise.
*/
if (this.compiler.async) {
return this.wrapToFunction(compiledTemplate)(this, templateState, $context).then((output) => {
output = this.trimTopBottomNewLines(output);
return this.processor.executeOutput({ output, template: this, state: templateState });
});
}
const output = this.trimTopBottomNewLines(this.wrapToFunction(compiledTemplate)(this, templateState, $context));
return this.processor.executeOutput({ output, template: this, state: templateState });
}
/**
* Render a partial
*
* ```js
* const partialFn = template.compilePartial('includes/user')
*
* // render and use output
* partialFn(template, state, ctx)
* ```
*/
compilePartial(templatePath, ...localVariables) {
const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables, true);
return this.wrapToFunction(compiledTemplate, ...localVariables);
}
/**
* Render a component
*
* ```js
* const componentFn = template.compileComponent('components/button')
*
* // render and use output
* componentFn(template, template.getComponentState(props, slots, caller), ctx)
* ```
*/
compileComponent(templatePath, ...localVariables) {
const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables);
return this.wrapToFunction(compiledTemplate, ...localVariables);
}
/**
* Returns the isolated state for a given component
*/
getComponentState(props, slots, caller) {
return Object.assign({}, this.sharedState, props, {
$slots: slots,
$caller: caller,
$props: new Props_1.Props(props),
});
}
/**
* Render a template with it's state.
*
* ```js
* template.render('welcome', { key: 'value' })
* ```
*/
render(template, state) {
let { template: compiledTemplate } = this.compiler.compile(template);
return this.renderCompiled(compiledTemplate, state);
}
/**
* Render template from a raw string
*
* ```js
* template.renderRaw('Hello {{ username }}', { username: 'virk' })
* ```
*/
renderRaw(contents, state, templatePath) {
let { template: compiledTemplate } = this.compiler.compileRaw(contents, templatePath);
return this.renderCompiled(compiledTemplate, state);
}
/**
* Escapes the value to be HTML safe. Only strings are escaped
* and rest all values will be returned as it is.
*/
escape(input) {
return escape(input);
}
/**
* Raise an error
*/
newError(errorMessage, filename, lineNumber, column) {
throw new edge_error_1.EdgeError(errorMessage, 'E_RUNTIME_EXCEPTION', {
filename: filename,
line: lineNumber,
col: column,
});
}
/**
* Rethrows the runtime exception by re-constructing the error message
* to point back to the original filename
*/
reThrow(error, filename, lineNumber) {
if (error instanceof edge_error_1.EdgeError) {
throw error;
}
const message = error.message.replace(/state\./, '');
throw new edge_error_1.EdgeError(message, 'E_RUNTIME_EXCEPTION', {
filename: filename,
line: lineNumber,
col: 0,
});
}
}
/**
* Required by Macroable
*/
Template.macros = {};
Template.getters = {};
exports.Template = Template;
+47
View File
@@ -0,0 +1,47 @@
import { TagToken } from 'edge-lexer';
import { expressions as expressionsList, Parser } from 'edge-parser';
type ExpressionList = readonly (keyof typeof expressionsList)[];
/**
* Raise an `E_UNALLOWED_EXPRESSION` exception. Filename and expression is
* required to point the error stack to the correct file
*/
export declare function unallowedExpression(message: string, filename: string, loc: {
line: number;
col: number;
}): void;
/**
* Validates the expression type to be part of the allowed
* expressions only.
*
* The filename is required to report errors.
*
* ```js
* isNotSubsetOf(expression, ['Literal', 'Identifier'], () => {})
* ```
*/
export declare function isSubsetOf(expression: any, expressions: ExpressionList, errorCallback: () => void): void;
/**
* Validates the expression type not to be part of the disallowed
* expressions.
*
* The filename is required to report errors.
*
* ```js
* isNotSubsetOf(expression, 'SequenceExpression', () => {})
* ```
*/
export declare function isNotSubsetOf(expression: any, expressions: ExpressionList, errorCallback: () => void): void;
/**
* Parses the jsArg by generating and transforming its AST
*/
export declare function parseJsArg(parser: Parser, token: TagToken): any;
/**
* Each loop. A soft replacement for `lodash.each` that we were using earlier
*/
export declare function each(collection: any, iteratee: (value: any, key: any) => void): void;
/**
* Async each loop. A soft replacement for `lodash.each` that we were
* using earlier with support for async await
*/
export declare function asyncEach(collection: any, iteratee: (value: any, key: any) => Promise<void>): Promise<void>;
export {};
+112
View File
@@ -0,0 +1,112 @@
"use strict";
/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.asyncEach = exports.each = exports.parseJsArg = exports.isNotSubsetOf = exports.isSubsetOf = exports.unallowedExpression = void 0;
const edge_error_1 = require("edge-error");
/**
* Raise an `E_UNALLOWED_EXPRESSION` exception. Filename and expression is
* required to point the error stack to the correct file
*/
function unallowedExpression(message, filename, loc) {
throw new edge_error_1.EdgeError(message, 'E_UNALLOWED_EXPRESSION', {
line: loc.line,
col: loc.col,
filename: filename,
});
}
exports.unallowedExpression = unallowedExpression;
/**
* Validates the expression type to be part of the allowed
* expressions only.
*
* The filename is required to report errors.
*
* ```js
* isNotSubsetOf(expression, ['Literal', 'Identifier'], () => {})
* ```
*/
function isSubsetOf(expression, expressions, errorCallback) {
if (!expressions.includes(expression.type)) {
errorCallback();
}
}
exports.isSubsetOf = isSubsetOf;
/**
* Validates the expression type not to be part of the disallowed
* expressions.
*
* The filename is required to report errors.
*
* ```js
* isNotSubsetOf(expression, 'SequenceExpression', () => {})
* ```
*/
function isNotSubsetOf(expression, expressions, errorCallback) {
if (expressions.includes(expression.type)) {
errorCallback();
}
}
exports.isNotSubsetOf = isNotSubsetOf;
/**
* Parses the jsArg by generating and transforming its AST
*/
function parseJsArg(parser, token) {
return parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
}
exports.parseJsArg = parseJsArg;
/**
* Each loop. A soft replacement for `lodash.each` that we were using earlier
*/
function each(collection, iteratee) {
if (Array.isArray(collection)) {
for (let [key, value] of collection.entries()) {
iteratee(value, key);
}
return;
}
if (typeof collection === 'string') {
let index = 0;
for (let value of collection) {
iteratee(value, index++);
}
return;
}
if (collection && typeof collection === 'object') {
for (let [key, value] of Object.entries(collection)) {
iteratee(value, key);
}
}
}
exports.each = each;
/**
* Async each loop. A soft replacement for `lodash.each` that we were
* using earlier with support for async await
*/
async function asyncEach(collection, iteratee) {
if (Array.isArray(collection)) {
for (let [key, value] of collection.entries()) {
await iteratee(value, key);
}
return;
}
if (typeof collection === 'string') {
let index = 0;
for (let value of collection) {
await iteratee(value, index++);
}
return;
}
if (collection && typeof collection === 'object') {
for (let [key, value] of Object.entries(collection)) {
await iteratee(value, key);
}
}
}
exports.asyncEach = asyncEach;