mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-18 22:10:41 -07:00
refactor: remove mock-fs from dev dependency
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { assign } from 'src/util/underscore'
|
||||
import html from './html'
|
||||
import str from './string'
|
||||
import math from './math'
|
||||
@@ -7,6 +6,4 @@ import array from './array'
|
||||
import date from './date'
|
||||
import obj from './object'
|
||||
|
||||
const filters = assign({}, html, str, math, url, date, obj, array)
|
||||
|
||||
export default filters
|
||||
export default { ...html, ...str, ...math, ...url, ...date, ...obj, ...array }
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
if (this.with) {
|
||||
hash[filepath] = evalValue(this.with, scope)
|
||||
}
|
||||
const templates = await this.liquid.getTemplate(filepath, scope.opts.root)
|
||||
const templates = await this.liquid.getTemplate(filepath, scope.opts)
|
||||
scope.push(hash)
|
||||
const html = await this.liquid.renderer.renderTemplates(templates, scope)
|
||||
scope.pop(hash)
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
if (scope.blocks[''] === undefined) {
|
||||
scope.blocks[''] = html
|
||||
}
|
||||
const templates = await this.liquid.getTemplate(layout, scope.opts.root)
|
||||
const templates = await this.liquid.getTemplate(layout, scope.opts)
|
||||
scope.push(hash)
|
||||
scope.blockMode = BlockMode.OUTPUT
|
||||
const partial = await this.liquid.renderer.renderTemplates(templates, scope)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { last, isArray } from '../util/underscore'
|
||||
import { last } from '../util/underscore'
|
||||
import IFS from './ifs'
|
||||
|
||||
function domResolve (root, path) {
|
||||
const base = document.createElement('base')
|
||||
@@ -15,25 +16,17 @@ function domResolve (root, path) {
|
||||
return resolved
|
||||
}
|
||||
|
||||
export function resolve (filepath, root, options) {
|
||||
root = root || options.root
|
||||
if (isArray(root)) {
|
||||
root = root[0]
|
||||
}
|
||||
if (root.length && last(root) !== '/') {
|
||||
root += '/'
|
||||
}
|
||||
function resolve (root, filepath, ext) {
|
||||
if (root.length && last(root) !== '/') root += '/'
|
||||
const url = domResolve(root, filepath)
|
||||
return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => {
|
||||
const last = path.split('/').pop()
|
||||
if (/\.\w+$/.test(last)) {
|
||||
return str
|
||||
}
|
||||
return origin + path + options.extname
|
||||
if (/\.\w+$/.test(last)) return str
|
||||
return origin + path + ext
|
||||
})
|
||||
}
|
||||
|
||||
export async function read (url: string): Promise<string> {
|
||||
async function readFile (url: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onload = () => {
|
||||
@@ -50,3 +43,9 @@ export async function read (url: string): Promise<string> {
|
||||
xhr.send()
|
||||
})
|
||||
}
|
||||
|
||||
async function exists () {
|
||||
return true
|
||||
}
|
||||
|
||||
export default { readFile, resolve, exists } as IFS
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface IFS {
|
||||
exists: (filepath?: string) => Promise<boolean>
|
||||
readFile: (filepath:string) => Promise<string>
|
||||
resolve: (root: string, file: string, ext: string) => string
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import { resolve, extname } from 'path'
|
||||
import { stat, readFile } from 'fs'
|
||||
import IFS from './ifs'
|
||||
|
||||
const statAsync = _.promisify(stat) as (filepath: string) => Promise<object>
|
||||
const readFileAsync = _.promisify(readFile) as (filepath: string, encoding: string) => Promise<string>
|
||||
|
||||
const fs: IFS = {
|
||||
exists: filepath => {
|
||||
return statAsync(filepath).then(() => true).catch(() => false)
|
||||
},
|
||||
readFile: filepath => {
|
||||
return readFileAsync(filepath, 'utf8')
|
||||
},
|
||||
resolve: (root: string, file: string, ext: string) => {
|
||||
if (!extname(file)) file += ext
|
||||
return resolve(root, file)
|
||||
}
|
||||
}
|
||||
|
||||
export default fs
|
||||
+21
-1
@@ -1,3 +1,5 @@
|
||||
import * as _ from './util/underscore'
|
||||
|
||||
export interface LiquidOptions {
|
||||
/** `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`. If an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
|
||||
root?: string | string[]
|
||||
@@ -23,7 +25,11 @@ export interface LiquidOptions {
|
||||
greedy?: boolean
|
||||
}
|
||||
|
||||
export const defaultOptions: LiquidOptions = {
|
||||
export interface NormalizedOptions extends LiquidOptions {
|
||||
root?: string[]
|
||||
}
|
||||
|
||||
export const defaultOptions: NormalizedOptions = {
|
||||
root: ['.'],
|
||||
cache: false,
|
||||
extname: '',
|
||||
@@ -36,3 +42,17 @@ export const defaultOptions: LiquidOptions = {
|
||||
strict_filters: false,
|
||||
strict_variables: false
|
||||
}
|
||||
|
||||
export function normalize (options: LiquidOptions): NormalizedOptions {
|
||||
options = options || {}
|
||||
if (options.hasOwnProperty('root')) {
|
||||
options.root = normalizeStringArray(options.root)
|
||||
}
|
||||
return options as NormalizedOptions
|
||||
}
|
||||
|
||||
function normalizeStringArray (value: string | string[]): string[] {
|
||||
if (_.isArray(value)) return value as string[]
|
||||
if (_.isString(value)) return [value as string]
|
||||
return []
|
||||
}
|
||||
|
||||
+30
-37
@@ -1,6 +1,6 @@
|
||||
import Scope from './scope/scope'
|
||||
import * as Types from './types'
|
||||
import * as template from 'template'
|
||||
import fs from 'src/fs'
|
||||
import * as _ from './util/underscore'
|
||||
import ITemplate from './template/itemplate'
|
||||
import Tokenizer from './parser/tokenizer'
|
||||
@@ -13,19 +13,17 @@ import Value from './template/value'
|
||||
import { isTruthy, isFalsy, evalExp, evalValue } from './render/syntax'
|
||||
import builtinTags from './builtin/tags'
|
||||
import builtinFilters from './builtin/filters'
|
||||
import { LiquidOptions, defaultOptions } from './liquid-options'
|
||||
import { LiquidOptions, NormalizedOptions, defaultOptions, normalize } from './liquid-options'
|
||||
|
||||
export default class Liquid {
|
||||
public options: LiquidOptions
|
||||
public options: NormalizedOptions
|
||||
private cache: object
|
||||
private parser: Parser
|
||||
private renderer: Render
|
||||
private tokenizer: Tokenizer
|
||||
|
||||
constructor (options: LiquidOptions = {}) {
|
||||
options = _.assign({}, defaultOptions, options)
|
||||
options.root = normalizeStringArray(options.root)
|
||||
|
||||
constructor (opts: LiquidOptions = {}) {
|
||||
const options = { ...defaultOptions, ...normalize(opts) }
|
||||
if (options.cache) {
|
||||
this.cache = {}
|
||||
}
|
||||
@@ -42,37 +40,38 @@ export default class Liquid {
|
||||
return this.parser.parse(tokens)
|
||||
}
|
||||
render (tpl: Array<ITemplate>, ctx?: object, opts?: LiquidOptions) {
|
||||
opts = _.assign({}, this.options, opts)
|
||||
const scope = new Scope(ctx, opts)
|
||||
const options = { ...this.options, ...normalize(opts) }
|
||||
const scope = new Scope(ctx, options)
|
||||
return this.renderer.renderTemplates(tpl, scope)
|
||||
}
|
||||
async parseAndRender (html: string, ctx?: object, opts?: LiquidOptions) {
|
||||
const tpl = await this.parse(html)
|
||||
return this.render(tpl, ctx, opts)
|
||||
}
|
||||
async getTemplate (file, root) {
|
||||
const filepath = await template.resolve(file, root, this.options)
|
||||
return this.respectCache(filepath, async () => {
|
||||
const str = await template.read(filepath)
|
||||
return this.parse(str, filepath)
|
||||
})
|
||||
async getTemplate (file, opts?: LiquidOptions) {
|
||||
const options = normalize(opts)
|
||||
const roots = options.root ? [...options.root, ...this.options.root] : this.options.root
|
||||
const paths = roots.map(root => fs.resolve(root, file, this.options.extname))
|
||||
|
||||
for (const filepath of paths) {
|
||||
if (!(await fs.exists(filepath))) continue
|
||||
|
||||
if (this.options.cache && this.cache[filepath]) return this.cache[filepath]
|
||||
const value = this.parse(await fs.readFile(filepath), filepath)
|
||||
if (this.options.cache) this.cache[filepath] = value
|
||||
return value
|
||||
}
|
||||
|
||||
const err = new Error('ENOENT') as any
|
||||
err.message = `ENOENT: Failed to lookup "${file}" in "${roots}"`
|
||||
err.code = 'ENOENT'
|
||||
throw err
|
||||
}
|
||||
async renderFile (file, ctx?: object, opts?: LiquidOptions) {
|
||||
opts = _.assign({}, opts)
|
||||
const templates = await this.getTemplate(file, opts.root)
|
||||
const options = normalize(opts)
|
||||
const templates = await this.getTemplate(file, options)
|
||||
return this.render(templates, ctx, opts)
|
||||
}
|
||||
async respectCache (key, getter) {
|
||||
const cacheEnabled = this.options.cache
|
||||
if (cacheEnabled && this.cache[key]) {
|
||||
return this.cache[key]
|
||||
}
|
||||
const value = await getter()
|
||||
if (cacheEnabled) {
|
||||
this.cache[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
evalValue (str: string, scope: Scope) {
|
||||
return new Value(str, this.options.strict_filters).value(scope)
|
||||
}
|
||||
@@ -85,10 +84,10 @@ export default class Liquid {
|
||||
plugin (plugin) {
|
||||
return plugin.call(this, Liquid)
|
||||
}
|
||||
express (opts: LiquidOptions = {}) {
|
||||
express () {
|
||||
const self = this
|
||||
return function (filePath, ctx, cb) {
|
||||
opts.root = this.root
|
||||
return function (filePath: string, ctx: object, cb: (err: Error, html?: string) => void) {
|
||||
const opts = { root: this.root }
|
||||
self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb)
|
||||
}
|
||||
}
|
||||
@@ -99,9 +98,3 @@ export default class Liquid {
|
||||
static evalValue = evalValue
|
||||
static Types = Types
|
||||
}
|
||||
|
||||
function normalizeStringArray (value) {
|
||||
if (_.isArray(value)) return value
|
||||
if (_.isString(value)) return [value]
|
||||
throw new TypeError('illegal root: ' + value)
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import * as path from 'path'
|
||||
import { anySeries } from '../util/promise'
|
||||
import { stat, readFile } from 'fs'
|
||||
|
||||
export const fs = {
|
||||
stat: _.promisify(stat) as ((filepath: string) => Promise<object>),
|
||||
readFile: _.promisify(readFile) as ((filepath: string, encoding: string) => Promise<string>)
|
||||
}
|
||||
|
||||
export async function resolve (filepath, root, options) {
|
||||
if (!path.extname(filepath)) {
|
||||
filepath += options.extname
|
||||
}
|
||||
root = options.root.concat(root || [])
|
||||
root = _.uniq(root)
|
||||
const paths = root.map(root => path.resolve(root, filepath))
|
||||
return anySeries(paths, async path => {
|
||||
try {
|
||||
await fs.stat(path)
|
||||
return path
|
||||
} catch (e) {
|
||||
e.message = `${e.code}: Failed to lookup ${filepath} in: ${root}`
|
||||
throw e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function read (filepath): Promise<string> {
|
||||
return fs.readFile(filepath, 'utf8')
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { assign } from 'src/util/underscore'
|
||||
import DelimitedToken from 'src/parser/delimited-token'
|
||||
import Token from 'src/parser/token'
|
||||
import TagToken from 'src/parser/tag-token'
|
||||
import { LiquidOptions } from 'src/liquid-options'
|
||||
|
||||
export default function whiteSpaceCtrl (tokens: Token[], options: LiquidOptions) {
|
||||
options = assign({ greedy: true }, options)
|
||||
options = { greedy: true, ...options }
|
||||
let inRaw = false
|
||||
|
||||
tokens.forEach((token: Token, i: number) => {
|
||||
|
||||
+6
-11
@@ -1,21 +1,16 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import * as lexical from '../parser/lexical'
|
||||
import assert from '../util/assert'
|
||||
import { LiquidOptions, defaultOptions } from '../liquid-options'
|
||||
import { NormalizedOptions, defaultOptions } from '../liquid-options'
|
||||
import BlockMode from './block-mode'
|
||||
|
||||
export default class Scope {
|
||||
opts: LiquidOptions
|
||||
opts: NormalizedOptions
|
||||
contexts: Array<object>
|
||||
blocks: object = {}
|
||||
blockMode: BlockMode = BlockMode.OUTPUT
|
||||
constructor (ctx: object = {}, opts: LiquidOptions = defaultOptions) {
|
||||
this.opts = _.assign({
|
||||
dynamicPartials: true,
|
||||
strict_variables: false,
|
||||
strict_filters: false,
|
||||
root: []
|
||||
}, opts)
|
||||
constructor (ctx: object = {}, opts: NormalizedOptions = defaultOptions) {
|
||||
this.opts = { ...defaultOptions, ...opts }
|
||||
this.contexts = [ctx || {}]
|
||||
}
|
||||
getAll () {
|
||||
@@ -43,10 +38,10 @@ export default class Scope {
|
||||
scope = scope[key]
|
||||
})
|
||||
}
|
||||
unshift (ctx: object): any {
|
||||
unshift (ctx: object) {
|
||||
return this.contexts.unshift(ctx)
|
||||
}
|
||||
push (ctx: object): any {
|
||||
push (ctx: object) {
|
||||
return this.contexts.push(ctx)
|
||||
}
|
||||
pop (ctx?: object): object {
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ TokenizationError.prototype.constructor = TokenizationError
|
||||
export class ParseError extends LiquidError {
|
||||
constructor (err, token) {
|
||||
super(err, token)
|
||||
_.assign(this, err)
|
||||
this.message = err.message
|
||||
super.captureStackTrace(this)
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ ParseError.prototype.constructor = ParseError
|
||||
export class RenderError extends LiquidError {
|
||||
constructor (err, tpl) {
|
||||
super(err, tpl.token)
|
||||
_.assign(this, err)
|
||||
this.message = err.message
|
||||
super.captureStackTrace(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ const _date = {
|
||||
return num + d.getDate()
|
||||
},
|
||||
|
||||
// Startday is an integer of which day to start the week measuring from
|
||||
// TODO: that comment was retarted. fix it.
|
||||
getWeekOfYear: function (d, startDay) {
|
||||
// Skip to startDay of this week
|
||||
const now = this.getDayOfYear(d) + (startDay - d.getDay())
|
||||
|
||||
Reference in New Issue
Block a user