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
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2022 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+78
View File
@@ -0,0 +1,78 @@
<div align="center"><img src="https://res.cloudinary.com/adonis-js/image/upload/v1620150474/edge-banner_tzmnox.jpg" width="600px"></div>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of contents
- [Table of contents](#table-of-contents)
- [Maintainers](#maintainers)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Edge
> A template for Node.js
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] [![synk-image]][synk-url]
Edge is a logical and batteries included template engine for Node.js. It can render any text based format, whether is **HTML**, **Markdown** or **plain text** files.
## Usage
Install the package from the npm registry.
```sh
npm i edge.js
# yarn
yarn add edge.js
```
And use it as follows
```js
const { join } = require('path')
// CommonJS
const { Edge } = require('edge.js')
// Typescript import
// import { Edge } from 'edge.js'
const edge = new Edge({ cache: false })
edge.mount(join(__dirname, 'views'))
const html = await edge.render('welcome', {
greeting: 'Hello world'
})
console.log(html)
```
Next create the `views/welcome.edge` file.
```edge
<p> {{ greeting }} </p>
```
Edge was created to be used inside the AdonisJS framework. However it is a framework agnostic library and can be used standalone as well.
The documentation is written on the [AdonisJS website](https://docs.adonisjs.com/guides/views/rendering). In AdonisJS docs, we refer the `edge` variable as `view`.
<br />
<hr>
![](https://cdn.jsdelivr.net/gh/thetutlage/static/sponsorkit/sponsors.png)
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/edge-js/edge/test.yml?style=for-the-badge
[gh-workflow-url]: https://github.com/edge-js/edge/actions/workflows/test.yml "Github action"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[license-image]: https://img.shields.io/npm/l/edge.js?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
[npm-image]: https://img.shields.io/npm/v/edge.js.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/edge.js 'npm'
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/edge-js/edge?label=Synk%20Vulnerabilities&style=for-the-badge
[synk-url]: https://snyk.io/test/github/edge-js/edge?targetFile=package.json "synk"
+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;
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2022 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+40
View File
@@ -0,0 +1,40 @@
# edge-error
> Create errors with custom stack trace pointing to a ".edge" file
[![github-actions-image]][github-actions-url] [![npm-image]][npm-url] [![license-image]][license-url] [![typescript-image]][typescript-url]
The package extends the native Error class and adds support for pushing an error stack frame pointing to a ".edge" template file.
## Usage
Install the package from the npm packages registry.
```bash
npm i edge-error
# yarn
yarn add edge-error
```
Then use it as follows
```js
import { EdgeError } from 'edge-error'
throw new EdgeError('message', 'status', {
line: 1,
col: 2,
filename: 'absolute/path/to/index.edge'
})
```
[github-actions-image]: https://img.shields.io/github/workflow/status/edge-js/error/test?style=for-the-badge
[github-actions-url]: https://github.com/edge-js/error/actions/workflows/test.yml "github-actions"
[npm-image]: https://img.shields.io/npm/v/edge-error.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/edge-error "npm"
[license-image]: https://img.shields.io/npm/l/edge-error?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md "license"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
+9
View File
@@ -0,0 +1,9 @@
import { ExceptionOptions } from './types';
export declare class EdgeError extends Error {
message: string;
code: string;
line: number;
col: number;
filename: string;
constructor(message: string, code: string, options: ExceptionOptions);
}
+22
View File
@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EdgeError = void 0;
class EdgeError extends Error {
message;
code;
line;
col;
filename;
constructor(message, code, options) {
super(message);
this.message = message;
this.code = code;
this.line = options.line;
this.col = options.col;
this.filename = options.filename;
const stack = this.stack.split('\n');
stack.splice(1, 0, ` at anonymous (${this.filename}:${this.line}:${this.col})`);
this.stack = stack.join('\n');
}
}
exports.EdgeError = EdgeError;
+5
View File
@@ -0,0 +1,5 @@
export declare type ExceptionOptions = {
line: number;
col: number;
filename: string;
};
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+107
View File
@@ -0,0 +1,107 @@
{
"name": "edge-error",
"version": "3.0.0",
"description": "Create errors with custom stack trace pointing to the .edge template file",
"main": "build/index.js",
"files": [
"build/index.d.ts",
"build/index.js",
"build/types.d.ts",
"build/types.js"
],
"exports": {
".": "./build/index.js",
"./types": "./build/types.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "npm run vscode:test",
"vscode:test": "node --require=@adonisjs/require-ts/build/register bin/test.ts",
"clean": "del-cli build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"lint": "eslint . --ext=.ts",
"release": "np",
"version": "npm run build",
"sync-labels": "github-label-sync --labels .github/labels.js edge-js/error",
"format": "prettier --write .",
"prepublishOnly": "npm run build"
},
"keywords": [
"edge-error",
"edge.js"
],
"author": "virk",
"license": "MIT",
"devDependencies": {
"@adonisjs/require-ts": "^2.0.12",
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@japa/assert": "^1.3.6",
"@japa/run-failed-tests": "^1.1.0",
"@japa/runner": "^2.2.1",
"@japa/spec-reporter": "^1.3.1",
"@types/node": "^18.7.18",
"del-cli": "^5.0.0",
"eslint": "^8.23.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^4.2.1",
"github-label-sync": "^2.2.0",
"husky": "^8.0.1",
"np": "^7.6.2",
"prettier": "^2.7.1",
"typescript": "^4.8.3"
},
"directories": {
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/edge-js/edge-error.git"
},
"bugs": {
"url": "https://github.com/edge-js/edge-error/issues"
},
"homepage": "https://github.com/edge-js/edge-error#readme",
"np": {
"contents": ".",
"message": "chore(release): %s",
"anyBranch": false
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}
+150
View File
@@ -0,0 +1,150 @@
{
"name": "edge.js",
"version": "5.5.1",
"description": "Template engine",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"pretest": "npm run lint",
"test": "node -r @adonisjs/require-ts/build/register ./bin/test.ts",
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"commit": "git-cz",
"clean": "del-cli build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"lint": "eslint . --ext=.ts",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"format": "prettier --write .",
"sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json edge-js/edge",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/edge.git"
},
"author": "virk",
"license": "MIT",
"bugs": {
"url": "https://github.com/poppinss/edge/issues"
},
"homepage": "https://github.com/poppinss/edge#readme",
"devDependencies": {
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.12",
"@japa/assert": "^1.3.4",
"@japa/run-failed-tests": "^1.0.7",
"@japa/runner": "^2.0.9",
"@japa/spec-reporter": "^1.1.12",
"@poppinss/dev-utils": "^2.0.3",
"@types/node": "^18.7.14",
"chai": "^4.3.6",
"commitizen": "^4.2.5",
"cz-conventional-changelog": "^3.3.0",
"dedent-js": "^1.0.1",
"del-cli": "^5.0.0",
"doctoc": "^2.2.0",
"eslint": "^8.23.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-adonis": "^2.1.0",
"eslint-plugin-prettier": "^4.2.1",
"github-label-sync": "^2.2.0",
"husky": "^8.0.1",
"mrm": "^4.1.0",
"np": "^7.6.2",
"prettier": "^2.7.1",
"typescript": "^5.0.2"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"dependencies": {
"@poppinss/inspect": "^1.0.1",
"@poppinss/utils": "^5.0.0",
"edge-error": "^3.0.0",
"edge-lexer": "^5.0.2",
"edge-parser": "^8.2.1",
"js-stringify": "^1.0.2",
"macroable": "^7.0.1",
"stringify-attributes": "^2.0.0"
},
"husky": {
"hooks": {
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"directories": {
"example": "examples",
"test": "test"
},
"keywords": [
"template",
"mustache",
"edge"
],
"publishConfig": {
"access": "public",
"tag": "latest"
},
"np": {
"contents": ".",
"anyBranch": false
},
"mrmConfig": {
"core": false,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "14.15.4",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": true
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}