Socket
Socket
Sign inDemoInstall

edge.js

Package Overview
Dependencies
Maintainers
1
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

edge.js - npm Package Compare versions

Comparing version 2.0.2 to 2.1.0

build/src/Edge/globals/index.d.ts

13

build/index.js
"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 });
const Edge_1 = require("./src/Edge");
exports.Edge = Edge_1.Edge;
const globals_1 = __importDefault(require("./src/Edge/globals"));
var utils_1 = require("./src/utils");

@@ -10,2 +22,3 @@ exports.disAllowExpressions = utils_1.disAllowExpressions;

const edge = new Edge_1.Edge();
globals_1.default(edge);
exports.default = edge;

@@ -0,2 +1,8 @@

/**
* @module edge
*/
import { LoaderTemplate } from '../Contracts';
/**
* In memory cache manager to cache pre-compiled templates
*/
export declare class CacheManager {

@@ -6,4 +12,13 @@ private _enabled;

constructor(_enabled: boolean);
/**
* Returns the template and the presenter class 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 returns in noop.
*/
set(absPath: string, payload: LoaderTemplate): void;
}
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* In memory cache manager to cache pre-compiled templates
*/
class CacheManager {

@@ -8,2 +14,7 @@ constructor(_enabled) {

}
/**
* Returns the template and the presenter class from the
* cache. If caching is disabled, then it will
* return undefined.
*/
get(absPath) {

@@ -15,2 +26,6 @@ if (!this._enabled) {

}
/**
* Set's the template path and the payload to the cache. If
* cache is disabled, then this function returns in noop.
*/
set(absPath, payload) {

@@ -17,0 +32,0 @@ if (!this._enabled) {

@@ -0,3 +1,14 @@

/**
* @module edge
*/
import { ParserToken } from 'edge-parser';
import { LoaderContract, TagsContract, LoaderTemplate, CompilerContract } from '../Contracts';
/**
* Compiler compiles the template to a function, which can be invoked at a later
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is
* used under the hood to parse the templates.
*
* Also, the `layouts` are handled natively by the compiler. Before starting
* the parsing process, it will recursively merge the layout sections.
*/
export declare class Compiler implements CompilerContract {

@@ -9,6 +20,44 @@ private _loader;

constructor(_loader: LoaderContract, _tags: TagsContract, _cache?: boolean);
/**
* 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;
/**
* Converts the template content to an [array of lexer tokens]. The method is
* same as the `parser.generateLexerTokens`, plus it will handle the layouts
* and it's sections.
*
* ```
* compiler.generateLexerTokens('<template-path>')
* ```
*/
generateLexerTokens(templatePath: string): ParserToken[];
/**
* Compiles the template contents to a function string, which can be invoked
* later.
*
* When `inline` is set to true, the compiled output **will not have it's own scope** and
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials.
*
* ```js
* compiler.compile('welcome', false)
* // output
*
* {
* template: `function (template, ctx) {
* let out = ''
* out += ''
* return out
* })(template, ctx)`,
* Presenter: class Presenter | undefined
* }
* ```
*/
compile(templatePath: string, inline: boolean): LoaderTemplate;
}

123

build/src/Compiler/index.js
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_error_1 = require("edge-error");

@@ -7,2 +18,10 @@ const edge_parser_1 = require("edge-parser");

const utils_1 = require("../utils");
/**
* Compiler compiles the template to a function, which can be invoked at a later
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is
* used under the hood to parse the templates.
*
* Also, the `layouts` are handled natively by the compiler. Before starting
* the parsing process, it will recursively merge the layout sections.
*/
class Compiler {

@@ -15,7 +34,21 @@ constructor(_loader, _tags, _cache = true) {

}
/**
* Merges sections of base template and parent template tokens
*/
_mergeSections(base, extended, filename, layoutPath) {
/**
* Collection of all sections from the extended tokens
*/
const extendedSections = {};
/**
* Collection of extended set calls as top level nodes. Now since they are hanging
* up in the air, they will be hoisted like `var` statements in Javascript
*/
const extendedSetCalls = [];
extended
.forEach((node) => {
/**
* Ignore new lines, layout tag and empty raw nodes inside the parent
* template
*/
if (utils_1.isBlockToken(node, 'layout') ||

@@ -26,2 +59,5 @@ node.type === 'newline' ||

}
/**
* Collect parent template sections
*/
if (utils_1.isBlockToken(node, 'section')) {

@@ -31,2 +67,5 @@ extendedSections[node.properties.jsArg.trim()] = node;

}
/**
* Collect set calls inside parent templates
*/
if (utils_1.isBlockToken(node, 'set')) {

@@ -36,5 +75,11 @@ extendedSetCalls.push(node);

}
/**
* Everything else if not allowed as top level nodes
*/
const [line, col] = utils_1.getLineAndColumnForToken(node);
throw new edge_error_1.EdgeError(`Template extending the layout can only define @sections as top level nodes`, 'E_UNALLOWED_EXPRESSION', { line, col, filename });
});
/**
* Replace/extend sections inside base tokens list
*/
const finalNodes = base

@@ -51,2 +96,5 @@ .map((node) => {

}
/**
* Concat children when super was called
*/
if (extendedNode.children.length && utils_1.isBlockToken(extendedNode.children[0], 'super')) {

@@ -60,9 +108,25 @@ extendedNode.children = node.children.map((child) => {

});
/**
* 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, filename) {
let templateTokens = parser.generateLexerTokens(content);
const firstToken = templateTokens[0];
/**
* The `layout` is inbuilt feature from core, where we merge the layout
* and parent template sections together
*/
if (utils_1.isBlockToken(firstToken, 'layout')) {
const layoutName = firstToken.properties.jsArg.replace(/'/g, '');
/**
* Making absolute path, so that lexer errors must point to the
* absolute file path
*/
const absPath = this._loader.makePath(layoutName);

@@ -74,2 +138,11 @@ const layoutTokens = this.generateLexerTokens(absPath);

}
/**
* Converts the template content to an [array of lexer tokens]. The method is
* same as the `parser.generateLexerTokens`, plus it will handle the layouts
* and it's sections.
*
* ```
* compiler.generateLexerTokens('<template-path>')
* ```
*/
generateLexerTokens(templatePath) {

@@ -80,4 +153,29 @@ const { template } = this._loader.resolve(templatePath, false);

}
/**
* Compiles the template contents to a function string, which can be invoked
* later.
*
* When `inline` is set to true, the compiled output **will not have it's own scope** and
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials.
*
* ```js
* compiler.compile('welcome', false)
* // output
*
* {
* template: `function (template, ctx) {
* let out = ''
* out += ''
* return out
* })(template, ctx)`,
* Presenter: class Presenter | undefined
* }
* ```
*/
compile(templatePath, inline) {
const absPath = this._loader.makePath(templatePath);
/**
* If template is in the cache, then return it without
* further processing
*/
const cachedResponse = this._cacheManager.get(absPath);

@@ -87,10 +185,29 @@ if (cachedResponse) {

}
/**
* Do not load presenter in inline mode
*/
const loadPresenter = !inline;
/**
* Inline templates are not wrapped inside a function
* call. They share the parent template scope
*/
const wrapAsFunction = !inline;
const parser = new edge_parser_1.Parser(this._tags, {
filename: `${absPath.replace(/\.edge$/, '')}.edge`,
});
/**
* Get a new instance of the parser.
*/
const parser = new edge_parser_1.Parser(this._tags, { filename: absPath });
/**
* Resolve the template and Presenter using the given loader
*/
const { template, Presenter } = this._loader.resolve(absPath, loadPresenter);
/**
* Convert template to AST. The AST will have the layout actions merged (if layout)
* is used.
*/
const templateTokens = this._templateContentToTokens(template, parser, absPath);
/**
* Finally process the ast
*/
const buffer = new edge_parser_1.EdgeBuffer();
buffer.writeStatement(`ctx.set('$filename', '${templatePath.replace(/\.edge$/, '')}.edge');`);
templateTokens.forEach((token) => parser.processLexerToken(token, buffer));

@@ -97,0 +214,0 @@ const payload = {

@@ -0,17 +1,110 @@

/**
* @module edge
*/
import { Macroable } from 'macroable';
import { ContextContract } from '../Contracts';
declare class SafeValue {
value: any;
constructor(value: any);
}
/**
* Context is used at runtime to resolve values for a given
* template.
*
* Also the context can be extended to add `getters` and `methods`. Checkout
* [macroable](https://github.com/poppinss/macroable) for same.
*/
export declare class Context extends Macroable implements ContextContract {
presenter: any;
sharedState: any;
protected static _macros: {};
protected static _getters: {};
protected static macros: {};
protected static getters: {};
/**
* Frames are used to define a inner scope in which values will
* be resolved. The resolve function starts with the deepest
* frame and then resolve the value up until the first
* frame.
*/
private _frames;
constructor(presenter: any, sharedState: any);
/**
* Returns value for a key inside frames. Stops looking for it,
* when value is found inside any frame.
*/
private _getFromFrame;
/**
* Returns a merged copy of the current state. The objects are merged
* in the same order as they are resolved.
*/
private getCurrentState;
/**
* Creates a new frame scope. Think of a scope as a Javacript block
* scope, where variables defined inside the scope are only available
* to that scope.
*
* ```js
* ctx.newFrame()
* ```
*/
newFrame(): void;
/**
* Set key/value pair on the frame object. The value will only be available until
* the `removeFrame` is not called.
*
* ```js
* ctx.setOnFrame('username', 'virk')
*
* // nested values
* ctx.setOnFrame('user.username', 'virk')
* ```
*
* @throws Error if no frame scopes exists.
*/
setOnFrame(key: string, value: any): void;
/**
* Removes the most recent frame/scope. All values set inside the
* frame via `setOnFrame` will be removed.
*/
removeFrame(): void;
/**
* Mark output as safe
*/
safe<T extends any>(value: T): SafeValue;
/**
* Escapes the value to be HTML safe. Only strings are escaped
* and rest all values will be returned as it is.
*/
escape<T>(input: T): T;
/**
* Resolves value for a given key. It will look for the value in different
* locations and continues till the end if `undefined` is returned at
* each step.
*
* The following steps are followed in the same order as defined.
*
* 1. Check for value inside frames.
* 2. Then on the presenter instance.
* 3. Then the presenter `state` object.
* 4. Finally fallback to the sharedState.
*
* @example
* ```js
* ctx.resolve('username')
* ```
*/
resolve(key: string): any;
/**
* Set/Update the value in the context. The value is defined in the following
* order.
*
* 1. If the value already exists on the presenter state, then it will be updated
* 2. If the scope is inside a frame, then will be created/updated on the frame.
* 3. At last, the value is created on the presenter state.
*
* ```js
* ctx.set('username', 'virk')
* ```
*/
set(key: string, value: any): void;
}
export {};
"use strict";
/**
* @module edge
*/
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -6,5 +9,25 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const he_1 = __importDefault(require("he"));
const lodash_1 = require("lodash");
const macroable_1 = require("macroable");
class SafeValue {
constructor(value) {
this.value = value;
}
}
/**
* Context is used at runtime to resolve values for a given
* template.
*
* Also the context can be extended to add `getters` and `methods`. Checkout
* [macroable](https://github.com/poppinss/macroable) for same.
*/
class Context extends macroable_1.Macroable {

@@ -15,4 +38,14 @@ constructor(presenter, sharedState) {

this.sharedState = sharedState;
/**
* Frames are used to define a inner scope in which values will
* be resolved. The resolve function starts with the deepest
* frame and then resolve the value up until the first
* frame.
*/
this._frames = [];
}
/**
* Returns value for a key inside frames. Stops looking for it,
* when value is found inside any frame.
*/
_getFromFrame(key) {

@@ -22,5 +55,34 @@ const frameWithVal = this._frames.find((frame) => frame[key] !== undefined);

}
/**
* Returns a merged copy of the current state. The objects are merged
* in the same order as they are resolved.
*/
getCurrentState() {
return Object.assign({}, this.sharedState, this.presenter.state, ...this._frames);
}
/**
* Creates a new frame scope. Think of a scope as a Javacript block
* scope, where variables defined inside the scope are only available
* to that scope.
*
* ```js
* ctx.newFrame()
* ```
*/
newFrame() {
this._frames.unshift({});
}
/**
* Set key/value pair on the frame object. The value will only be available until
* the `removeFrame` is not called.
*
* ```js
* ctx.setOnFrame('username', 'virk')
*
* // nested values
* ctx.setOnFrame('user.username', 'virk')
* ```
*
* @throws Error if no frame scopes exists.
*/
setOnFrame(key, value) {

@@ -33,10 +95,49 @@ const recentFrame = this._frames[0];

}
/**
* Removes the most recent frame/scope. All values set inside the
* frame via `setOnFrame` will be removed.
*/
removeFrame() {
this._frames.shift();
}
/**
* Mark output as safe
*/
safe(value) {
return new SafeValue(value);
}
/**
* Escapes the value to be HTML safe. Only strings are escaped
* and rest all values will be returned as it is.
*/
escape(input) {
return typeof (input) === 'string' ? he_1.default.escape(input) : input;
return typeof (input) === 'string'
? he_1.default.escape(input)
: (input instanceof SafeValue ? input.value : input);
}
/**
* Resolves value for a given key. It will look for the value in different
* locations and continues till the end if `undefined` is returned at
* each step.
*
* The following steps are followed in the same order as defined.
*
* 1. Check for value inside frames.
* 2. Then on the presenter instance.
* 3. Then the presenter `state` object.
* 4. Finally fallback to the sharedState.
*
* @example
* ```js
* ctx.resolve('username')
* ```
*/
resolve(key) {
if (key === '$state') {
return this.getCurrentState();
}
let value;
/**
* Pull from one of the nested frames
*/
value = this._getFromFrame(key);

@@ -46,2 +147,6 @@ if (value !== undefined) {

}
/**
* Check for value as a property on the presenter
* itself.
*/
value = this.presenter[key];

@@ -51,2 +156,5 @@ if (value !== undefined) {

}
/**
* Otherwise look into presenter state
*/
value = this.presenter.state[key];

@@ -56,6 +164,25 @@ if (value !== undefined) {

}
/**
* Finally fallback to defined globals
*/
value = this.sharedState[key];
return typeof (value) === 'function' ? value.bind(this.sharedState) : value;
}
/**
* Set/Update the value in the context. The value is defined in the following
* order.
*
* 1. If the value already exists on the presenter state, then it will be updated
* 2. If the scope is inside a frame, then will be created/updated on the frame.
* 3. At last, the value is created on the presenter state.
*
* ```js
* ctx.set('username', 'virk')
* ```
*/
set(key, value) {
/**
* If value already exists on the presenter
* state, then mutate it first
*/
if (this.presenter.state[key] !== undefined || !this._frames.length) {

@@ -65,2 +192,5 @@ lodash_1.set(this.presenter.state, key, value);

}
/**
* If frames exists, then set it on frame
*/
this.setOnFrame(key, value);

@@ -70,3 +200,3 @@ }

exports.Context = Context;
Context._macros = {};
Context._getters = {};
Context.macros = {};
Context.getters = {};

@@ -0,4 +1,18 @@

/**
* @module edge
*/
/**
* 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 } from 'edge-lexer';
import { MacroableConstructorContract } from 'macroable';
import { ParseTagDefininationContract } from 'edge-parser';
/**
* The shape in which the loader must resolve the template
*/
export declare type LoaderTemplate = {

@@ -10,12 +24,36 @@ template: string;

};
/**
* Loader contract that every loader must adheres to.
*/
export interface LoaderContract {
/**
* List of mounted disks
*/
mounted: {
[diskName: string]: string;
};
/**
* 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 and optionally the Presenter
*/
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate;
/**
* Make absolute path to a template
*/
makePath(templatePath: string): string;
/**
* Register in memory template and presenter
*/
register(templatePath: string, contents: LoaderTemplate): void;
}
/**
* Shape of runtime context
*/
export interface ContextContract {

@@ -26,2 +64,5 @@ presenter: {

sharedState: any;
safe<T extends any>(value: T): {
value: T;
};
newFrame(): void;

@@ -34,5 +75,12 @@ setOnFrame(key: string, value: any): void;

}
export interface ContextConstructorContract extends MacroableConstructorContract {
/**
* Shape of context constructor
*/
export interface ContextConstructorContract extends MacroableConstructorContract<ContextContract> {
new (presenter: any, sharedState: any): ContextContract;
}
/**
* The final tag must have a tagName along with other properties
* required by lexer and parser
*/
export interface TagContract extends ParseTagDefininationContract {

@@ -42,5 +90,11 @@ tagName: string;

}
/**
* Shape of required tags
*/
export declare type TagsContract = {
[tagName: string]: TagContract;
};
/**
* Shape of the compiler
*/
export interface CompilerContract {

@@ -50,2 +104,5 @@ compile(templatePath: string, inline: boolean): LoaderTemplate;

}
/**
* Shape of the renderer that renders the edge templates
*/
export interface EdgeRendererContract {

@@ -55,2 +112,6 @@ share(locals: any): this;

}
/**
* Shape of options that can be passed to the
* edge constructor
*/
export declare type EdgeOptions = {

@@ -60,2 +121,5 @@ loader?: LoaderContract;

};
/**
* Shape of the main module
*/
export interface EdgeContract {

@@ -62,0 +126,0 @@ loader: LoaderContract;

"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -0,1 +1,4 @@

/**
* @module edge
*/
import { Compiler } from '../Compiler';

@@ -5,16 +8,122 @@ import { TagContract, EdgeOptions, EdgeContract, LoaderTemplate, EdgeRendererContract } from '../Contracts';

private _options;
/**
* Globals are shared with all rendered templates
*/
private _globals;
/**
* List of registered tags. Adding new tags will only impact
* this list
*/
private _tags;
/**
* 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;
/**
* The underlying compiler in use
*/
compiler: Compiler;
constructor(_options?: EdgeOptions);
/**
* 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;
/**
* Mount defaults views directory.
*
* ```
* edge.mount(join(__dirname, 'admin'))
* edge.render('filename')
* ```
*/
mount(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;
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath: string, state?: any): string;
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer(): EdgeRendererContract;
/**
* 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;
}
"use strict";
/**
* @module edge
*/
var __importStar = (this && this.__importStar) || function (mod) {

@@ -10,2 +13,10 @@ if (mod && mod.__esModule) return mod;

Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const Tags = __importStar(require("../Tags"));

@@ -19,5 +30,20 @@ const Loader_1 = require("../Loader");

this._options = _options;
/**
* 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._options.cache);

@@ -34,2 +60,9 @@ Object.keys(Tags).forEach((name) => this.registerTag(Tags[name]));

}
/**
* Un Mount a disk from the loader.
*
* ```js
* edge.unmount('admin')
* ```
*/
unmount(diskName) {

@@ -39,2 +72,11 @@ this.loader.unmount(diskName);

}
/**
* 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) {

@@ -44,2 +86,17 @@ this._globals[name] = value;

}
/**
* 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) {

@@ -52,2 +109,21 @@ if (typeof (tag.run) === 'function') {

}
/**
* 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) {

@@ -57,8 +133,31 @@ this.loader.register(templatePath, contents);

}
/**
* Render a template with optional state
*
* ```ts
* edge.render('welcome', { greeting: 'Hello world' })
* ```
*/
render(templatePath, state) {
return this.getRenderer().render(templatePath, state);
}
/**
* Returns a new instance of edge. The instance
* can be used to define locals.
*/
getRenderer() {
return new Renderer_1.EdgeRenderer(this.compiler, this._globals);
}
/**
* 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) {

@@ -65,0 +164,0 @@ return this.getRenderer().share(data);

@@ -0,15 +1,123 @@

/**
* @module edge
*/
import { LoaderContract, LoaderTemplate } from '../Contracts';
/**
* The job of a loader is to load the template and it's presenter for 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;
/**
* Attempts to load the presenter for a given template. If presenter doesn't exists, it
* will swallow the error.
*
* Also this method will **bypass the `require` cache**, since in production compiled templates
* and their presenters are cached anyways.
*/
private _getPresenterForTemplate;
/**
* 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;
readonly mounted: {
/**
* 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;
};
/**
* 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 from the disk, optionally loads the presenter too. The presenter
* resolution is based on the convention and resolved from the same directory
* as the template.
*
* ## Presenter convention
* - View name - welcome.edge
* - Presenter name - Welcome.presenter.js
*
* ```js
* loader.resolve('welcome', true)
*
* // output
* {
* template: `<h1> Template content </h1>`,
* Presenter: class Presenter | undefined
* }
* ```
*/
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate;
/**
* Register in memory template and Presenter 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;
}
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 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.
*/
const fs_1 = require("fs");

@@ -8,7 +19,27 @@ const utils_1 = require("@poppinss/utils");

const utils_2 = require("../utils");
/**
* The job of a loader is to load the template and it's presenter for 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();
}
/**
* Attempts to load the presenter for a given template. If presenter doesn't exists, it
* will swallow the error.
*
* Also this method will **bypass the `require` cache**, since in production compiled templates
* and their presenters are cached anyways.
*/
_getPresenterForTemplate(templatePath) {

@@ -27,2 +58,6 @@ const presenterPath = templatePath

}
/**
* 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) {

@@ -41,2 +76,16 @@ try {

}
/**
* 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() {

@@ -48,8 +97,39 @@ return Array.from(this._mountedDirs).reduce((obj, [key, value]) => {

}
/**
* 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) {

@@ -60,2 +140,5 @@ if (this._preRegistered.has(templatePath)) {

const [diskName, template] = utils_2.extractDiskAndTemplateName(templatePath);
/**
* Raise exception when disk name is not defined
*/
const mountedDir = this._mountedDirs.get(diskName);

@@ -67,3 +150,25 @@ if (!mountedDir) {

}
/**
* Resolves the template from the disk, optionally loads the presenter too. The presenter
* resolution is based on the convention and resolved from the same directory
* as the template.
*
* ## Presenter convention
* - View name - welcome.edge
* - Presenter name - Welcome.presenter.js
*
* ```js
* loader.resolve('welcome', true)
*
* // output
* {
* template: `<h1> Template content </h1>`,
* Presenter: class Presenter | undefined
* }
* ```
*/
resolve(templatePath, withPresenter) {
/**
* Return from pre-registered one's if exists
*/
if (this._preRegistered.has(templatePath)) {

@@ -73,2 +178,5 @@ const contents = this._preRegistered.get(templatePath);

}
/**
* Make absolute to the file on the disk
*/
templatePath = path_1.isAbsolute(templatePath) ? templatePath : this.makePath(templatePath);

@@ -80,6 +188,29 @@ return {

}
/**
* Register in memory template and Presenter 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 utils_1.Exception('Make sure to define the template content as a string', 500, 'E_MISSING_TEMPLATE_CONTENTS');
}
/**
* Do not overwrite existing template with same template path
*/
if (this._preRegistered.has(templatePath)) {

@@ -86,0 +217,0 @@ throw new utils_1.Exception(`Cannot override previously registered {${templatePath}} template`, 500, 'E_DUPLICATE_TEMPLATE_PATH');

@@ -0,1 +1,11 @@

/**
* @module edge
*/
/**
* The Base presenter is passed to context for reading
* the state values.
*
* However, a custom presenter a do a lot by defining
* custom properties and methods.
*/
export declare class Presenter {

@@ -2,0 +12,0 @@ state: any;

"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
/**
* The Base presenter is passed to context for reading
* the state values.
*
* However, a custom presenter a do a lot by defining
* custom properties and methods.
*/
class Presenter {

@@ -4,0 +22,0 @@ constructor(state) {

import { EdgeRendererContract, CompilerContract } from '../Contracts';
/**
* Renders a given template with it's shared state
*/
export declare class EdgeRenderer implements EdgeRendererContract {

@@ -7,4 +10,11 @@ private _compiler;

constructor(_compiler: CompilerContract, _globals: any);
/**
* Share local variables with the template. They will overwrite the
* globals
*/
share(data: any): this;
/**
* Render the template
*/
render(templatePath: string, state?: any): string;
}
"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 });
const lodash_1 = require("lodash");
const Template_1 = require("../Template");
/**
* Renders a given template with it's shared state
*/
class EdgeRenderer {

@@ -11,2 +22,6 @@ constructor(_compiler, _globals) {

}
/**
* Share local variables with the template. They will overwrite the
* globals
*/
share(data) {

@@ -16,2 +31,5 @@ lodash_1.merge(this._locals, data);

}
/**
* Render the template
*/
render(templatePath, state = {}) {

@@ -18,0 +36,0 @@ const template = new Template_1.Template(this._compiler, this._globals, this._locals);

@@ -0,5 +1,31 @@

/**
* @module edge
*/
/**
* 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;
/**
* Add key/value pair to the object.
*
* ```js
* stringifiedObject.add('username', `'virk'`)
* ```
*/
add(key: any, value: any): void;
/**
* Returns the object alike string back.
*
* ```js
* stringifiedObject.flush()
*
* // returns
* `{ username: 'virk' }`
* ```
*/
flush(): string;
}
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
/**
* 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 {

@@ -7,5 +24,22 @@ constructor() {

}
/**
* Add key/value pair to the object.
*
* ```js
* stringifiedObject.add('username', `'virk'`)
* ```
*/
add(key, value) {
this.obj += this.obj.length ? `, ${key}: ${value}` : `${key}: ${value}`;
}
/**
* Returns the object alike string back.
*
* ```js
* stringifiedObject.flush()
*
* // returns
* `{ username: 'virk' }`
* ```
*/
flush() {

@@ -12,0 +46,0 @@ const obj = `{ ${this.obj} }`;

@@ -0,2 +1,9 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_error_1 = require("edge-error");

@@ -7,2 +18,5 @@ const edge_parser_1 = require("edge-parser");

const utils_1 = require("../utils");
/**
* A list of allowed expressions for the component name
*/
const componentNameAllowedExpressions = [

@@ -17,4 +31,11 @@ edge_parser_1.expressions.Identifier,

];
/**
* Returns the component name and props by parsing the component jsArg expression
*/
function getComponentNameAndProps(expression, parser) {
let name;
/**
* Use the first expression inside the sequence expression as the name
* of the component
*/
if (expression.type === edge_parser_1.expressions.SequenceExpression) {

@@ -26,3 +47,10 @@ name = expression.expressions.shift();

}
/**
* Ensure the component name is a literal value or an expression that
* outputs a literal value
*/
utils_1.allowExpressions(name, componentNameAllowedExpressions, parser.options.filename, `{${parser.stringifyExpression(name)}} is not a valid argument for component name`);
/**
* Parse rest of sequence expressions as an objectified string.
*/
if (expression.type === edge_parser_1.expressions.SequenceExpression) {

@@ -34,7 +62,21 @@ return [

}
/**
* When top level expression is not a sequence expression, then we assume props
* as empty stringified object.
*/
return [parser.stringifyExpression(name), '{}'];
}
/**
* Parses the slot component to fetch it's name and props
*/
function getSlotNameAndProps(expression, 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.generateAcornExpression(expression.properties.jsArg, expression.loc).expression;
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.Literal, edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${expression.properties.jsArg}} is not a valid argument type for the @slot tag`);
/**
* Fetch the slot name
*/
let name;

@@ -47,6 +89,17 @@ if (parsed.type === edge_parser_1.expressions.SequenceExpression) {

}
/**
* Validating the slot name to be a literal value, since slot names cannot be dynamic
*/
utils_1.allowExpressions(name, [edge_parser_1.expressions.Literal], parser.options.filename, 'slot name must be a valid string literal');
/**
* 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) {

@@ -60,4 +113,11 @@ throw new edge_error_1.EdgeError('maximum of 2 arguments are allowed for @slot tag', 'E_MAX_ARGUMENTS', {

utils_1.allowExpressions(parsed.expressions[1], [edge_parser_1.expressions.Identifier], parser.options.filename, `{${parser.stringifyExpression(parsed.expressions[1])}} is not valid prop identifier for @slot tag`);
/**
* 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 = {

@@ -69,4 +129,15 @@ block: true,

const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);
/**
* Check component js props for allowed expressions
*/
utils_1.allowExpressions(parsed, componentNameAllowedExpressions.concat(edge_parser_1.expressions.SequenceExpression), parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @component tag`);
/**
* 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);
/**
* 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 = {};

@@ -76,7 +147,16 @@ token.children.forEach((child, index) => {

let slotProps = null;
/**
* Update the slot name and props when a new slot is detected
*/
if (utils_1.isBlockToken(child, 'slot')) {
[slotName, slotProps] = getSlotNameAndProps(child, parser);
}
/**
* Create a new slot with buffer to process the childs
*/
if (!slots[slotName]) {
slots[slotName] = { buffer: new edge_parser_1.EdgeBuffer(`slot_${index}`), props: slotProps };
/**
* Only start the frame, when there are props in use for a given slot.
*/
if (slotProps) {

@@ -87,2 +167,7 @@ slots[slotName].buffer.writeStatement('ctx.newFrame();');

}
/**
* We must process slot of a component by ourselves (instead of making slot tag)
* process it. This way, we can disallow slots appearing outside the component
* tag
*/
if (utils_1.isBlockToken(child, 'slot')) {

@@ -95,4 +180,11 @@ child.children.forEach((token) => parser.processLexerToken(token, slots[slotName].buffer));

});
/**
* 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.
*/
const obj = new StringifiedObject_1.StringifiedObject();
Object.keys(slots).forEach((slot) => {
/**
* Cleanup the previously started frame scope
*/
if (slots[slot].props) {

@@ -105,4 +197,7 @@ slots[slot].buffer.writeStatement('ctx.removeFrame();');

});
/**
* Write the line to render the component with it's own state
*/
buffer.writeLine(`template.renderWithState(${name}, ${props}, ${obj.flush()})`);
},
};

@@ -0,2 +1,12 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
/**
* Add debugger break point to the compiled template
*
* ```edge
* @debugger
* ```
*/
export declare const debuggerTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Add debugger break point to the compiled template
*
* ```edge
* @debugger
* ```
*/
exports.debuggerTag = {

@@ -7,2 +17,5 @@ block: false,

tagName: 'debugger',
/**
* Compiles else block node to Javascript else statement
*/
compile(_parser, buffer) {

@@ -9,0 +22,0 @@ buffer.writeStatement('debugger;');

@@ -0,2 +1,14 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
/**
* Each tag is used to run a foreach loop on arrays and even objects.
*
* ```edge
* @each(user in users)
* {{ user }} {{ $loop.index }}
* @endeach
* ```
*/
export declare const eachTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
const lodash_1 = require("lodash");
const utils_1 = require("../utils");
/**
* Returns the list to loop over for the each expression
*/
function getLoopList(expression, parser) {
return parser.stringifyExpression(parser.acornToEdgeExpression(expression));
}
/**
* Returns loop item and the index for the each expression
*/
function getLoopItemAndIndex(expression, filename) {
utils_1.allowExpressions(expression, [edge_parser_1.expressions.SequenceExpression, edge_parser_1.expressions.Identifier], filename, `invalid left hand side expression for the @each tag`);
/**
* Return list index from the sequence expression
*/
if (expression.type === 'SequenceExpression') {

@@ -18,2 +38,11 @@ utils_1.allowExpressions(expression.expressions[0], [edge_parser_1.expressions.Identifier], filename, `invalid expression for {value} identifier for the @each tag`);

}
/**
* Each tag is used to run a foreach loop on arrays and even objects.
*
* ```edge
* @each(user in users)
* {{ user }} {{ $loop.index }}
* @endeach
* ```
*/
exports.eachTag = {

@@ -23,9 +52,28 @@ block: true,

tagName: 'each',
/**
* Compile the template
*/
compile(parser, buffer, token) {
/**
* Instead of using `parser.generateEdgeExpression`, we make use of
* `generateAcornExpression`, since we do not want to process
* `Indentifer` expressions as per the normal parsing logic
* and just want to extract it's name.
*/
const parsed = parser.generateAcornExpression(token.properties.jsArg, token.loc).expression;
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.BinaryExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid binary expression for the @each tag`);
/**
* Finding if an else child exists inside the each tag
*/
const elseIndex = token.children.findIndex((child) => utils_1.isBlockToken(child, 'else'));
const elseChild = elseIndex > -1 ? token.children.splice(elseIndex) : [];
/**
* Fetching the item,index and list for the each loop
*/
const [item, index] = getLoopItemAndIndex(parsed.left, parser.options.filename);
const list = getLoopList(parsed.right, parser);
/**
* If there is an else statement, then wrap the loop
* inside the `if` statement first
*/
if (elseIndex > -1) {

@@ -35,14 +83,44 @@ buffer.writeStatement(`if(ctx.size(${list})) {`);

}
/**
* Write the loop statement to the template
*/
buffer.writeStatement(`ctx.loop(${list}, function (${item}, loop) {`);
/**
* Indent block
*/
buffer.indent();
/**
* Start a new context frame. Frame ensures the value inside
* the loop is given priority over top level values. Think
* of it as a Javascript block scope.
*/
buffer.writeStatement('ctx.newFrame();');
/**
* Set key and value pair on the context
*/
buffer.writeStatement(`ctx.setOnFrame('${item}', ${item});`);
buffer.writeStatement(`ctx.setOnFrame('$loop', loop);`);
buffer.writeStatement(`ctx.setOnFrame('${index}', loop.key);`);
/**
* Process all kids
*/
token.children.forEach((child) => {
parser.processLexerToken(child, buffer);
});
/**
* Remove the frame
*/
buffer.writeStatement('ctx.removeFrame();');
/**
* Dedent block
*/
buffer.dedent();
/**
* Close each loop
*/
buffer.writeStatement('});');
/**
* If there is an else statement, then process
* else childs and close the if block
*/
if (elseIndex > -1) {

@@ -54,2 +132,5 @@ elseChild.forEach((child) => parser.processLexerToken(child, buffer));

},
/**
* Add methods to the runtime context for running the loop
*/
run(context) {

@@ -56,0 +137,0 @@ context.macro('loop', function loop(source, callback) {

@@ -0,2 +1,5 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
export declare const elseTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -7,2 +10,5 @@ exports.elseTag = {

tagName: 'else',
/**
* Compiles else block node to Javascript else statement
*/
compile(_parser, buffer) {

@@ -9,0 +15,0 @@ buffer.dedent();

@@ -0,2 +1,16 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
/**
* Else if tag is used to define conditional blocks.
*
* ```edge
* @if(username)
* // If
* @elseif(user.username)
* // Else if
* @endif
* ```
*/
export declare const elseIfTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* Else if tag is used to define conditional blocks.
*
* ```edge
* @if(username)
* // If
* @elseif(user.username)
* // Else if
* @endif
* ```
*/
exports.elseIfTag = {

@@ -9,9 +31,21 @@ block: false,

tagName: 'elseif',
/**
* Compiles the else if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`);
/**
* Dedent block
*/
buffer.dedent();
/**
* Start else block
*/
buffer.writeStatement(`} else if(${parser.stringifyExpression(parsed)}) {`);
/**
* Indent block again
*/
buffer.indent();
},
};

@@ -0,2 +1,13 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
/**
* If tag is used to define conditional blocks.
*
* ```edge
* @if(username)
* @endif
* ```
*/
export declare const ifTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* If tag is used to define conditional blocks.
*
* ```edge
* @if(username)
* @endif
* ```
*/
exports.ifTag = {

@@ -9,11 +28,29 @@ block: true,

tagName: 'if',
/**
* Compiles the if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @if tag`);
/**
* Start if block
*/
buffer.writeStatement(`if(${parser.stringifyExpression(parsed)}) {`);
/**
* Indent upcoming code
*/
buffer.indent();
/**
* Process of all kids recursively
*/
token.children.forEach((child) => parser.processLexerToken(child, buffer));
/**
* Remove identation
*/
buffer.dedent();
/**
* Close if block
*/
buffer.writeStatement('}');
},
};

@@ -0,2 +1,13 @@

/**
* @module edge
*/
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 includeTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
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.includeTag = {

@@ -9,2 +28,5 @@ block: false,

tagName: 'include',
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {

@@ -21,4 +43,9 @@ const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);

], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @include tag`);
/**
* Include template. Since the partials can be a runtime value, we cannot inline
* the content right now and have to defer to runtime to get the value of
* the partial and then process it
*/
buffer.writeLine(`template.renderInline(${parser.stringifyExpression(parsed)})(template, ctx)`);
},
};

@@ -0,1 +1,4 @@

/**
* @module edge
*/
export { ifTag as if } from './If';

@@ -2,0 +5,0 @@ export { elseTag as else } from './Else';

"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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 If_1 = require("./If");

@@ -4,0 +15,0 @@ exports.if = If_1.ifTag;

@@ -0,2 +1,9 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 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 = {

@@ -8,3 +15,5 @@ block: false,

compile() {
// The layouts are handled by the template itself. I am just a way to
// tell lexer to parse me as a block node
},
};

@@ -0,2 +1,9 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 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 = {

@@ -4,0 +11,0 @@ block: true,

@@ -0,2 +1,23 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
const edge_error_1 = require("edge-error");
const utils_1 = 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 = {

@@ -10,5 +39,14 @@ block: false,

tagName: 'set',
/**
* Compiles else block node to Javascript else statement
*/
compile(parser, buffer, token) {
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);
/**
* The set tag only accepts a sequence expression.
*/
utils_1.allowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid key-value pair for the @slot tag`);
/**
* Disallow more than 2 values for the sequence expression
*/
if (parsed.expressions.length > 2) {

@@ -22,5 +60,11 @@ throw new edge_error_1.EdgeError(`maximum of 2 arguments are allowed for the @set tag`, 'E_MAX_ARGUMENTS', {

const [key, value] = parsed.expressions;
/**
* The key has to be a literal value
*/
utils_1.allowExpressions(key, [edge_parser_1.expressions.Literal], parser.options.filename, 'The first argument for @set tag must be a string literal');
/**
* Write statement to mutate the key
*/
buffer.writeStatement(`ctx.set(${key.raw}, ${parser.stringifyExpression(value)});`);
},
};

@@ -0,2 +1,9 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
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 = {

@@ -5,0 +20,0 @@ block: true,

@@ -0,2 +1,12 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 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 = {

@@ -8,3 +18,5 @@ block: false,

compile() {
// The super tag is handled by the compiler itself. I am just a way to
// tell lexer to parse me as an inline node
},
};

@@ -0,2 +1,15 @@

/**
* @module edge
*/
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;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
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 = {

@@ -9,13 +30,31 @@ block: true,

tagName: 'unless',
/**
* Compiles the if block node to a Javascript if statement
*/
compile(parser, buffer, token) {
const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);
utils_1.disAllowExpressions(parsed, [edge_parser_1.expressions.SequenceExpression], parser.options.filename, `{${token.properties.jsArg}} is not a valid argument type for the @unless tag`);
/**
* Start if block
*/
buffer.writeStatement(`if(!${parser.stringifyExpression(parsed)}) {`);
/**
* Indent upcoming code
*/
buffer.indent();
/**
* Process of all kids recursively
*/
token.children.forEach((child) => {
parser.processLexerToken(child, buffer);
});
/**
* Remove identation
*/
buffer.dedent();
/**
* Close if block
*/
buffer.writeStatement('}');
},
};

@@ -0,2 +1,24 @@

/**
* @module edge
*/
import { TagContract } from '../Contracts';
/**
* Yield tag is a shorthand of `if/else` for markup based content.
*
* ```edge
* @yield($slots.main)
* <p> This is the fallback content, when $slots.main is missing </p>
* @endyield
* ```
*
* The longer version of above is following
*
* ```@edge
* @if ($slots.main)
* {{{ $slots.main }}}
* @else
* <p> This is the fallback content, when $slots.main is missing </p>
* @endif
* ```
*/
export declare const yieldTag: TagContract;
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const edge_parser_1 = require("edge-parser");
const utils_1 = require("../utils");
/**
* Yield tag is a shorthand of `if/else` for markup based content.
*
* ```edge
* @yield($slots.main)
* <p> This is the fallback content, when $slots.main is missing </p>
* @endyield
* ```
*
* The longer version of above is following
*
* ```@edge
* @if ($slots.main)
* {{{ $slots.main }}}
* @else
* <p> This is the fallback content, when $slots.main is missing </p>
* @endif
* ```
*/
exports.yieldTag = {

@@ -9,2 +39,5 @@ block: true,

tagName: 'yield',
/**
* Compiles the if block node to a Javascript if statement
*/
compile(parser, buffer, token) {

@@ -14,2 +47,5 @@ const parsed = parser.generateEdgeExpression(token.properties.jsArg, token.loc);

const parsedString = parser.stringifyExpression(parsed);
/**
* Write main content when it's truthy
*/
buffer.writeStatement(`if(${parsedString}) {`);

@@ -19,2 +55,5 @@ buffer.indent();

buffer.dedent();
/**
* Else write fallback
*/
if (!token.properties.selfclosed) {

@@ -21,0 +60,0 @@ buffer.writeStatement('} else {');

@@ -0,9 +1,49 @@

/**
* @module edge
*/
import { CompilerContract } from '../Contracts';
/**
* 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 {
private _compiler;
/**
* 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);
/**
* Render the template inline by sharing the state of the current template.
*
* ```js
* const partialFn = template.renderInline('includes.user')
*
* // render and use output
* partialFn(template, ctx)
* ```
*/
renderInline(templatePath: string): Function;
/**
* Renders the template with custom state. The `sharedState` of the template is still
* passed to this template.
*
* Also a set of custom slots can be passed along. The slots uses the state of the parent
* template.
*
* ```js
* template.renderWithState('components.user', { username: 'virk' }, slotsIfAny)
* ```
*/
renderWithState(template: string, state: any, slots: any): string;
/**
* Render a template with it's state.
*
* ```js
* template.render('welcome', { key: 'value' })
* ```
*/
render(template: string, state: any): string;
}
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const lodash_1 = require("lodash");
const Context_1 = require("../Context");
const Presenter_1 = require("../Presenter");
/**
* 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 {

@@ -11,5 +27,26 @@ constructor(_compiler, globals, locals) {

}
/**
* Render the template inline by sharing the state of the current template.
*
* ```js
* const partialFn = template.renderInline('includes.user')
*
* // render and use output
* partialFn(template, ctx)
* ```
*/
renderInline(templatePath) {
return new Function('template', 'ctx', this._compiler.compile(templatePath, true).template);
}
/**
* Renders the template with custom state. The `sharedState` of the template is still
* passed to this template.
*
* Also a set of custom slots can be passed along. The slots uses the state of the parent
* template.
*
* ```js
* template.renderWithState('components.user', { username: 'virk' }, slotsIfAny)
* ```
*/
renderWithState(template, state, slots) {

@@ -21,2 +58,9 @@ const { template: compiledTemplate, Presenter } = this._compiler.compile(template, false);

}
/**
* Render a template with it's state.
*
* ```js
* template.render('welcome', { key: 'value' })
* ```
*/
render(template, state) {

@@ -23,0 +67,0 @@ const { template: compiledTemplate, Presenter } = this._compiler.compile(template, false);

@@ -0,7 +1,66 @@

/**
* @module edge
*/
import { Parser, expressions as expressionsList, ParserToken, ParserTagToken } from 'edge-parser';
/**
* Validates the expression type to be part of the allowed
* expressions only.
*
* The filename is required to report errors.
*
* ```js
* allowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge')
* ```
*/
export declare function allowExpressions(expression: any, expressions: (keyof typeof expressionsList)[], filename: string, message: string): void;
/**
* Validates the expression type not to be part of the disallowed
* expressions.
*
* The filename is required to report errors.
*
* ```js
* disAllowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge')
* ```
*/
export declare function disAllowExpressions(expression: any, expressions: (keyof typeof expressionsList)[], filename: string, message: string): void;
/**
* 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' }
* ```
*/
export declare function expressionsToStringifyObject(expressions: any[], parser: Parser): string;
/**
* 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']
* ```
*/
export declare function extractDiskAndTemplateName(templatePath: string): [string, string];
/**
* Returns a boolean, telling whether the lexer node is a block node
* or not.
*/
export declare function isBlockToken(token: ParserToken, name: string): token is ParserTagToken;
/**
* Returns line and number for a given AST token
*/
export declare function getLineAndColumnForToken(token: ParserToken): [number, number];
"use strict";
/**
* @module edge
*/
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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.
*/
const path_1 = require("path");

@@ -7,2 +18,12 @@ const edge_error_1 = require("edge-error");

const StringifiedObject_1 = require("../StringifiedObject");
/**
* Validates the expression type to be part of the allowed
* expressions only.
*
* The filename is required to report errors.
*
* ```js
* allowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge')
* ```
*/
function allowExpressions(expression, expressions, filename, message) {

@@ -18,2 +39,12 @@ if (!expressions.includes(expression.type)) {

exports.allowExpressions = allowExpressions;
/**
* Validates the expression type not to be part of the disallowed
* expressions.
*
* The filename is required to report errors.
*
* ```js
* disAllowExpressions('include', 'SequenceExpression', ['Literal', 'Identifier'], 'foo.edge')
* ```
*/
function disAllowExpressions(expression, expressions, filename, message) {

@@ -29,2 +60,17 @@ if (expressions.includes(expression.type)) {

exports.disAllowExpressions = disAllowExpressions;
/**
* 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' }
* ```
*/
function expressionsToStringifyObject(expressions, parser) {

@@ -47,2 +93,16 @@ const objectifyString = new StringifiedObject_1.StringifiedObject();

exports.expressionsToStringifyObject = expressionsToStringifyObject;
/**
* 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']
* ```
*/
function extractDiskAndTemplateName(templatePath) {

@@ -58,2 +118,6 @@ let [disk, ...rest] = templatePath.split('::');

exports.extractDiskAndTemplateName = extractDiskAndTemplateName;
/**
* Returns a boolean, telling whether the lexer node is a block node
* or not.
*/
function isBlockToken(token, name) {

@@ -66,2 +130,5 @@ if (token.type === edge_lexer_1.TagTypes.TAG || token.type === edge_lexer_1.TagTypes.ETAG) {

exports.isBlockToken = isBlockToken;
/**
* Returns line and number for a given AST token
*/
function getLineAndColumnForToken(token) {

@@ -68,0 +135,0 @@ if (token.type === 'newline' || token.type === 'raw') {

36

package.json
{
"name": "edge.js",
"version": "2.0.2",
"version": "2.1.0",
"description": "Template engine",

@@ -35,7 +35,7 @@ "main": "build/index.js",

"devDependencies": {
"@adonisjs/mrm-preset": "^2.1.0",
"@poppinss/dev-utils": "^1.0.1",
"@types/node": "^12.7.5",
"@adonisjs/mrm-preset": "^2.2.4",
"@poppinss/dev-utils": "^1.0.4",
"@types/node": "^13.7.6",
"commitizen": "^4.0.3",
"cz-conventional-changelog": "^3.0.2",
"cz-conventional-changelog": "^3.1.0",
"dedent": "^0.7.0",

@@ -45,13 +45,13 @@ "del-cli": "^3.0.0",

"fs-extra": "^8.1.0",
"husky": "^3.0.5",
"husky": "^4.2.3",
"japa": "^3.0.1",
"mrm": "^1.2.2",
"np": "^5.1.0",
"ts-node": "^8.4.1",
"tslint": "^5.20.0",
"mrm": "^2.1.0",
"np": "^5.2.1",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"tslint-eslint-rules": "^5.4.0",
"typedoc": "^0.15.0",
"typedoc": "^0.16.10",
"typedoc-clarity-theme": "^1.1.0",
"typedoc-plugin-external-module-name": "^2.1.0",
"typescript": "^3.6.3",
"typedoc-plugin-external-module-name": "^3.0.0",
"typescript": "^3.8.2",
"yorkie": "^2.0.0",

@@ -74,11 +74,9 @@ "youch": "^2.0.10"

"dependencies": {
"@poppinss/utils": "^2.0.0",
"debug": "^4.1.1",
"@poppinss/utils": "^2.1.2",
"edge-error": "^1.0.4",
"edge-parser": "^2.1.1",
"edge-parser": "^2.1.2",
"he": "^1.2.0",
"import-fresh": "^3.1.0",
"import-fresh": "^3.2.1",
"lodash": "^4.17.15",
"macroable": "^2.0.2",
"node-exceptions": "^4.0.1"
"macroable": "^4.0.2"
},

@@ -85,0 +83,0 @@ "gitHooks": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc