refactor: remove mock-fs from dev dependency

This commit is contained in:
harttle
2019-02-19 22:28:27 +08:00
parent f432aade6a
commit 852c6ad453
70 changed files with 711 additions and 795 deletions
+1 -4
View File
@@ -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 }
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+5
View File
@@ -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
}
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
-31
View File
@@ -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 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
-2
View File
@@ -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())