Skip to content

Commit

Permalink
refactor: adding new global functions
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Mar 12, 2020
1 parent ec07c45 commit 6756713
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 43 deletions.
2 changes: 1 addition & 1 deletion examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ http.createServer((req, res) => {
edge.mount(join(__dirname, './views'))
res.writeHead(200, { 'content-type': 'text/html' })
try {
res.end(edge.render('user', { title: 'Hello' }))
res.end(edge.render('user', { title: 'Hello', username: 'virk' }))
} catch (error) {
new Youch(error, req).toHTML().then((html) => {
res.writeHead(500, { 'content-type': 'text/html' })
Expand Down
3 changes: 2 additions & 1 deletion examples/views/partial.edge
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
{{ inspect($state, 1) }}
<h1>Inside slot: {{ slotTitle() }}</h1>
<h1>Inside slot: {{ props.title }}</h1>
<p>Access to local state {{ username }} </p>
7 changes: 7 additions & 0 deletions examples/views/user.edge
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
</head>
<body>
{{ inspect($state, 1) }}
@set('users', [{}])

{{ users.map(
(user) => {
return user.getUsername()
}
) }}

@component('alert', { title: 'hello world' })
@slot('title', props)
Expand Down
7 changes: 2 additions & 5 deletions examples/views/user.presenter.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
module.exports = class User {
constructor (state) {
constructor (state, sharedState) {
this.state = state
}

slotTitle (ctx) {
return ctx.resolve('props').title.toUpperCase()
this.sharedState = sharedState
}
}
5 changes: 3 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
* file that was distributed with this source code.
*/

export * from './src/Contracts'
import { Edge } from './src/Edge'
import globals from './src/Edge/globals'
export * from './src/Contracts'

const edge = new Edge()
globals(edge)

export default edge
export { Edge }
export default edge
export { safeValue, withCtx } from './src/Context'
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"devDependencies": {
"@adonisjs/mrm-preset": "^2.2.4",
"@poppinss/dev-utils": "^1.0.4",
"@types/lodash": "^4.14.149",
"@types/node": "^13.9.0",
"commitizen": "^4.0.3",
"cz-conventional-changelog": "^3.1.0",
Expand Down
70 changes: 55 additions & 15 deletions src/Context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,27 @@ import { ContextContract } from '../Contracts'
* method ensures that underlying value is never
* escaped.
*/
export class SafeValue {
class SafeValue {
constructor (public value: any) {}
}

/**
* A class to wrap callbacks that can access the `ctx`
*/
class WithCtx {
constructor (private callback: (ctx: ContextContract, ...args: any[]) => any) {
}

/**
* Invoke the callback
*/
public invoke (ctx: ContextContract, bindState: any) {
return (...args: any[]) => {
return this.callback.bind(bindState)(ctx, ...args)
}
}
}

/**
* Context is used at runtime to resolve values for a given
* template.
Expand All @@ -39,12 +56,6 @@ export class Context extends Macroable implements ContextContract {
*/
private frames: any[] = []

/**
* We keep a reference to the last resolved key and use it inside
* the `reThrow` method.
*/
private lastResolvedKey = ''

/**
* Required by Macroable
*/
Expand Down Expand Up @@ -151,6 +162,22 @@ export class Context extends Macroable implements ContextContract {
: (input instanceof SafeValue ? input.value : input)
}

/**
* Transform the resolved value before returning it
* back
*/
private transformValue (value: any, bindState: any) {
if (value instanceof WithCtx) {
return value.invoke(this, bindState)
}

if (typeof (value) === 'function') {
return value.bind(bindState)
}

return value
}

/**
* Resolves value for a given key. It will look for the value in different
* locations and continues till the end if `undefined` is returned at
Expand All @@ -169,8 +196,6 @@ export class Context extends Macroable implements ContextContract {
* ```
*/
public resolve (key: string): any {
this.lastResolvedKey = key

/**
* A special key to return the template current state
*/
Expand Down Expand Up @@ -201,7 +226,7 @@ export class Context extends Macroable implements ContextContract {
*/
value = this.getFromFrame(key)
if (value !== undefined) {
return typeof (value) === 'function' ? value.bind(this) : value
return this.transformValue(value, this)
}

/**
Expand All @@ -210,22 +235,22 @@ export class Context extends Macroable implements ContextContract {
*/
value = this.presenter[key]
if (value !== undefined) {
return typeof (value) === 'function' ? value.bind(this.presenter) : value
return this.transformValue(value, this.presenter)
}

/**
* Otherwise look into presenter state
*/
value = this.presenter.state[key]
if (value !== undefined) {
return typeof (value) === 'function' ? value.bind(this.presenter.state) : value
return this.transformValue(value, this.presenter.state)
}

/**
* Finally fallback to shared globals
*/
value = this.presenter.sharedState[key]
return typeof (value) === 'function' ? value.bind(this.presenter.sharedState) : value
return this.transformValue(value, this.presenter.sharedState)
}

/**
Expand Down Expand Up @@ -272,11 +297,26 @@ export class Context extends Macroable implements ContextContract {
throw error
}

const message = error.message.replace(/ctx\.resolve\(\.\.\.\)/, this.lastResolvedKey)
throw new EdgeError(message, 'E_RUNTIME_EXCEPTION', {
// const message = error.message.replace(/ctx\.resolve\(\.\.\.\)/, this.lastResolvedKey)
throw new EdgeError(error.message, 'E_RUNTIME_EXCEPTION', {
filename: this.$filename,
line: this.$lineNumber,
col: 0,
})
}
}

/**
* Mark value as safe and not to be escaped
*/
export function safeValue (value: string) {
return new SafeValue(value)
}

/**
* Wrap a function that receives the template engine current
* ctx when invoked.
*/
export function withCtx (callback: (ctx: ContextContract, ...args: any[]) => any) {
return new WithCtx(callback)
}
43 changes: 34 additions & 9 deletions src/Edge/globals/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/**
* @module edge
*/

/*
* edge
*
Expand All @@ -12,13 +8,26 @@
*/

import { inspect as utilInspect } from 'util'
import { range } from 'lodash'
import {
size,
last,
first,
range,
groupBy,
truncate,
} from 'lodash'

import { safeValue, withCtx } from '../../Context'
import { EdgeContract, ContextContract } from '../../Contracts'

/**
* Inspect value.
*/
function inspect (ctx: ContextContract, valueToInspect: any, depth: number = 1) {
function inspect (
ctx: ContextContract,
valueToInspect: any,
depth: number = 1,
) {
const inspectedString = `<pre>${utilInspect(valueToInspect, {
showHidden: true,
compact: false,
Expand All @@ -29,7 +38,9 @@ function inspect (ctx: ContextContract, valueToInspect: any, depth: number = 1)
${ctx.resolve('$filename')}
</span>`

return ctx.safe(`<div class="__inspect_output" style="background: #000; color: #fff; padding: 20px; position: relative;">${inspectedString}${filename}</div>`)
return safeValue(
`<div class="__inspect_output" style="background: #000; color: #fff; padding: 20px; position: relative;">${inspectedString}${filename}</div>`,
)
}

/**
Expand All @@ -39,7 +50,21 @@ inspect[Symbol.for('nodejs.util.inspect.custom')] = function customInspect () {
return '[inspect]'
}

/**
* A list of default globals
*/
export default function globals (edge: EdgeContract) {
edge.global('inspect', inspect)
edge.global('range', (_, start: number, end?: number, step?: number) => range(start, end, step))
edge.global('inspect', withCtx(inspect))
edge.global('range', (start: number, end?: number, step?: number) => range(start, end, step))
edge.global('first', first)
edge.global('last', last)
edge.global('groupBy', groupBy)
edge.global('size', size)
edge.global('truncate', truncate)
edge.global('toAnchor', (url: string, title: string = url) => {
return safeValue(`<a href="${url}"> ${title} </a>`)
})
edge.global('style', (url: string, title: string = url) => {
return safeValue(`<a href="${url}"> ${title} </a>`)
})
}
2 changes: 2 additions & 0 deletions src/Presenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ import { PresenterContract } from '../Contracts'
*/
export class Presenter implements PresenterContract {
constructor (public state: any, public sharedState: any) {
this.state = this.state || {}
this.sharedState = this.sharedState || {}
}
}
4 changes: 2 additions & 2 deletions test/compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ test.group('Compiler | Compile', (group) => {
new Context({ state: {}, sharedState: {} }),
)
} catch (error) {
assert.equal(error.message, 'getUserName is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.filename, join(fs.basePath, 'master.edge'))
assert.equal(error.line, 1)
assert.equal(error.col, 0)
Expand Down Expand Up @@ -572,7 +572,7 @@ test.group('Compiler | Compile', (group) => {
const fn = new Function('template', 'ctx', compiler.compile('index.edge', false).template)
fn({}, new Context({ state: {}, sharedState: {} }))
} catch (error) {
assert.equal(error.message, 'getContent is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.filename, join(fs.basePath, 'index.edge'))
assert.equal(error.line, 3)
assert.equal(error.col, 0)
Expand Down
12 changes: 6 additions & 6 deletions test/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getComponentName is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.line, 3)
assert.equal(error.col, 0)
assert.equal(error.filename, join(fs.basePath, 'eval.edge'))
Expand All @@ -167,7 +167,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getColor is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.line, 3)
assert.equal(error.col, 0)
assert.equal(error.filename, join(fs.basePath, 'eval.edge'))
Expand All @@ -192,7 +192,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getColor is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
/**
* Expected to be on line 4. But okay for now
*/
Expand All @@ -218,7 +218,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getColor is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.line, 3)
assert.equal(error.col, 0)
assert.equal(error.filename, join(fs.basePath, 'eval.edge'))
Expand All @@ -244,7 +244,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getColor is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
/**
* Expected to be on line 5. But okay for now
*/
Expand Down Expand Up @@ -299,7 +299,7 @@ test.group('Component | render | errors', (group) => {
try {
template.render('eval.edge', {})
} catch (error) {
assert.equal(error.message, 'getColor is not a function')
assert.equal(error.message, 'ctx.resolve(...) is not a function')
assert.equal(error.line, 2)
assert.equal(error.col, 0)
assert.equal(error.filename, join(fs.basePath, 'button.edge'))
Expand Down
Loading

0 comments on commit 6756713

Please sign in to comment.