Skip to content

Commit

Permalink
feat: exported Drop interface for #107
Browse files Browse the repository at this point in the history
Deprecate snake_cased options and APIs, sed #109
  • Loading branch information
harttle committed Feb 27, 2019
1 parent b69c3a3 commit 7bee9fc
Show file tree
Hide file tree
Showing 26 changed files with 226 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Expand Up @@ -18,6 +18,6 @@
"prefer-const": 2,
"no-unused-vars": "off",
"import/export": "off",
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "off", "ignoreRestSiblings": false }]
}
}
24 changes: 12 additions & 12 deletions README.md
Expand Up @@ -24,11 +24,11 @@ This is a liquid implementation for both Node.js and browsers. Website: <http://

Though being compatible with [Ruby Liquid](https://github.com/shopify/liquid) is one of our priorities, there're still certain differences. You may need some configuration to get it compatible in these senarios:

* Dynamic file locating (enabled by default), which means layout/partial name can be an variable in liquidjs. See [#51](https://github.com/harttle/liquidjs/issues/51).
* Dynamic file locating (enabled by default), that means layout/partial names are treated as variables in liquidjs. See [#51](https://github.com/harttle/liquidjs/issues/51).
* Truthy and Falsy. All values except `undefined`, `null`, `false` are truthy, whereas in Ruby Liquid all except `nil` and `false` are truthy. See [#26](https://github.com/harttle/liquidjs/pull/26).
* Number Rendering. Since JavaScript do not distinguish `float` and `integer`, we cannot either convert between them nor render regarding to their type. See [#59](https://github.com/harttle/liquidjs/issues/59).
* [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops) has a `.toLiquid()` alias and and the JavaScript `.toString()` is aliased to `.to_s()`.
* [.to_s()](https://www.rubydoc.info/gems/liquid/Liquid/Drop) uses `JSON.prototype.stringify` as default, rather than Ruby's inspect.
* [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops) is replaced by `.toLiquid()`
* [.to_s()](https://www.rubydoc.info/gems/liquid/Liquid/Drop) is replaced by JavaScript `.toString()`

## TOC

Expand Down Expand Up @@ -182,25 +182,25 @@ Defaults to `["."]`

* `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`.

* `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`.
* `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`.

* `strict_variables` is used to enable strict variable derivation.
* `strictVariables` is used to enable strict variable derivation.
If set to `false`, undefined variables will be rendered as empty string.
Otherwise, undefined variables will cause an exception. Defaults to `false`.

* `trim_tag_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`.
* `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`.

* `trim_tag_left` is similiar to `trim_tag_right`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.
* `trimTagLeft` is similiar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.

* `trim_output_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`.
* `trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`.

* `trim_output_left` is similiar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.
* `trimOutputLeft` is similiar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.

* `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags.
* `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags.

* `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs.
* `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs.

* `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`.
* `greedy` is used to specify whether `trim*Left`/`trim*Right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`.

## Register Filters

Expand Down
9 changes: 7 additions & 2 deletions src/drop/drop.ts
@@ -1,6 +1,11 @@
import { deprecate } from '../util/deprecate'

export abstract class Drop {
abstract valueOf(): any;
valueOf(): any {
return undefined
}

liquid_method_missing (name: string) { // eslint-disable-line
liquidMethodMissing (key: string): string | undefined {
return undefined
}
}
88 changes: 47 additions & 41 deletions src/liquid-options.ts
@@ -1,5 +1,4 @@
/* eslint-disable camelcase */

import { deprecate } from './util/deprecate'
import * as _ from './util/underscore'

export interface LiquidOptions {
Expand All @@ -11,25 +10,25 @@ export interface LiquidOptions {
cache?: boolean
/** `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */
dynamicPartials?: boolean
/** `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. */
strict_filters?: boolean
/** `strict_variables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */
strict_variables?: boolean
/** `trim_tag_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */
trim_tag_right?: boolean
/** `trim_tag_left` is similar to `trim_tag_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trim_tag_left?: boolean
/** ``trim_output_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */
trim_output_right?: boolean
/** `trim_output_left` is similar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trim_output_left?: boolean
/** `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags **/
tag_delimiter_left?: string,
tag_delimiter_right?: string,
/** `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs **/
output_delimiter_left?: string,
output_delimiter_right?: string,
/** `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */
/** `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. */
strictFilters?: boolean
/** `strictVariables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */
strictVariables?: boolean
/** `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */
trimTagRight?: boolean
/** `trimTagLeft` is similar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trimTagLeft?: boolean
/** ``trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */
trimOutputRight?: boolean
/** `trimOutputLeft` is similar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trimOutputLeft?: boolean
/** `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags **/
tagDelimiterLeft?: string,
tagDelimiterRight?: string,
/** `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs **/
outputDelimiterLeft?: string,
outputDelimiterRight?: string,
/** `greedy` is used to specify whether `trim*Left`/`trim*Right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */
greedy?: boolean
}

Expand All @@ -42,16 +41,16 @@ export interface NormalizedFullOptions extends NormalizedOptions {
extname: string
cache: boolean
dynamicPartials: boolean
strict_filters: boolean
strict_variables: boolean
trim_tag_right: boolean
trim_tag_left: boolean
trim_output_right: boolean
trim_output_left: boolean
tag_delimiter_left: string,
tag_delimiter_right: string,
output_delimiter_left: string,
output_delimiter_right: string,
strictFilters: boolean
strictVariables: boolean
trimTagRight: boolean
trimTagLeft: boolean
trimOutputRight: boolean
trimOutputLeft: boolean
tagDelimiterLeft: string,
tagDelimiterRight: string,
outputDelimiterLeft: string,
outputDelimiterRight: string,
greedy: boolean
}

Expand All @@ -60,24 +59,31 @@ const defaultOptions: NormalizedFullOptions = {
cache: false,
extname: '',
dynamicPartials: true,
trim_tag_right: false,
trim_tag_left: false,
trim_output_right: false,
trim_output_left: false,
trimTagRight: false,
trimTagLeft: false,
trimOutputRight: false,
trimOutputLeft: false,
greedy: true,
tag_delimiter_left: '{%',
tag_delimiter_right: '%}',
output_delimiter_left: '{{',
output_delimiter_right: '}}',
strict_filters: false,
strict_variables: false
tagDelimiterLeft: '{%',
tagDelimiterRight: '%}',
outputDelimiterLeft: '{{',
outputDelimiterRight: '}}',
strictFilters: false,
strictVariables: false
}

export function normalize (options?: LiquidOptions): NormalizedOptions {
options = options || {}
if (options.hasOwnProperty('root')) {
options.root = normalizeStringArray(options.root)
}
for (const key of Object.keys(options)) {
if (key.indexOf('_') > -1) {
const newKey = key.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase())
deprecate(`${key} is deprecated, use ${newKey} instead.`, 109)
options[newKey] = options[key]
}
}
return options as NormalizedOptions
}

Expand Down
2 changes: 1 addition & 1 deletion src/liquid.ts
Expand Up @@ -70,7 +70,7 @@ export default class Liquid {
return this.render(templates, ctx, opts)
}
evalValue (str: string, scope: Scope) {
return new Value(str, this.options.strict_filters).value(scope)
return new Value(str, this.options.strictFilters).value(scope)
}
registerFilter (name: string, filter: FilterImpl) {
return Filter.register(name, filter)
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parser.ts
Expand Up @@ -29,7 +29,7 @@ export default class Parser {
return new Tag(token as TagToken, remainTokens, this.liquid)
}
if (token.type === 'output') {
return new Output(token as OutputToken, this.liquid.options.strict_filters)
return new Output(token as OutputToken, this.liquid.options.strictFilters)
}
return new HTML(token)
} catch (e) {
Expand Down
8 changes: 4 additions & 4 deletions src/parser/tokenizer.ts
Expand Up @@ -15,10 +15,10 @@ export default class Tokenizer {
}
tokenize (input: string, file?: string) {
const tokens: Token[] = []
const tagL = this.options.tag_delimiter_left
const tagR = this.options.tag_delimiter_right
const outputL = this.options.output_delimiter_left
const outputR = this.options.output_delimiter_right
const tagL = this.options.tagDelimiterLeft
const tagR = this.options.tagDelimiterRight
const outputL = this.options.outputDelimiterLeft
const outputR = this.options.outputDelimiterRight
let p = 0
let curLine = 1
let state = ParseState.HTML
Expand Down
8 changes: 4 additions & 4 deletions src/parser/whitespace-ctrl.ts
Expand Up @@ -23,14 +23,14 @@ export default function whiteSpaceCtrl (tokens: Token[], options: NormalizedFull

function shouldTrimLeft (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) {
if (inRaw) return false
if (token.type === 'tag') return token.trimLeft || options.trim_tag_left
if (token.type === 'output') return token.trimLeft || options.trim_output_left
if (token.type === 'tag') return token.trimLeft || options.trimTagLeft
if (token.type === 'output') return token.trimLeft || options.trimOutputLeft
}

function shouldTrimRight (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) {
if (inRaw) return false
if (token.type === 'tag') return token.trimRight || options.trim_tag_right
if (token.type === 'output') return token.trimRight || options.trim_output_right
if (token.type === 'tag') return token.trimRight || options.trimTagRight
if (token.type === 'output') return token.trimRight || options.trimOutputRight
}

function trimLeft (token: Token, greedy: boolean) {
Expand Down
41 changes: 16 additions & 25 deletions src/scope/scope.ts
@@ -1,4 +1,5 @@
import * as _ from '../util/underscore'
import { Drop } from '../drop/drop'
import { __assign } from 'tslib'
import assert from '../util/assert'
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
Expand Down Expand Up @@ -27,7 +28,13 @@ export default class Scope {
get (path: string): any {
const paths = this.propertyAccessSeq(path)
const scope = this.findContextFor(paths[0]) || _.last(this.contexts)
return paths.reduce((value, key) => this.readProperty(value, key), scope)
return paths.reduce((value, key) => {
const val = this.readProperty(value, key)
if (_.isNil(val) && this.opts.strictVariables) {
throw new TypeError(`undefined variable: ${key}`)
}
return val
}, scope)
}
set (path: string, v: any): void {
const paths = this.propertyAccessSeq(path)
Expand Down Expand Up @@ -74,26 +81,20 @@ export default class Scope {
return null
}
private readProperty (obj: Context, key: string) {
let val
if (_.isNil(obj)) {
val = obj
} else {
obj = toLiquid(obj)
val = key === 'size' ? readSize(obj) : obj[key]
if (_.isFunction(obj.liquid_method_missing)) {
val = obj.liquid_method_missing!(key)
}
if (_.isNil(obj)) return obj
obj = _.toLiquid(obj)
if (obj instanceof Drop) {
if (_.isFunction(obj[key])) return obj[key]()
if (obj.hasOwnProperty(key)) return obj[key]
return obj.liquidMethodMissing(key)
}
if (_.isNil(val) && this.opts.strict_variables) {
throw new TypeError(`undefined variable: ${key}`)
}
return val
return key === 'size' ? readSize(obj) : obj[key]
}

/*
* Parse property access sequence from access string
* @example
* accessSeq("foo.bar") // ['foo', 'bar']
* accessSeq("foo.bar") // ['foo', 'bar']
* accessSeq("foo['bar']") // ['foo', 'bar']
* accessSeq("foo['b]r']") // ['foo', 'b]r']
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
Expand Down Expand Up @@ -150,16 +151,6 @@ export default class Scope {
}
}

function toLiquid (obj: Context) {
if (_.isFunction(obj.to_liquid)) {
return obj.to_liquid()
}
if (_.isFunction(obj.toLiquid)) {
return obj.toLiquid()
}
return obj
}

function readSize (obj: Context) {
if (!_.isNil(obj.size)) return obj.size
if (_.isArray(obj) || _.isString(obj)) return obj.length
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
@@ -1,2 +1,3 @@
export { AssignScope, CaptureScope, IncrementScope, DecrementScope } from './scope/scopes'
export { ParseError, TokenizationError, RenderBreakError, AssertionError } from './util/error'
export { Drop } from './drop/drop'
7 changes: 7 additions & 0 deletions src/util/deprecate.ts
@@ -0,0 +1,7 @@
const reported:{[key: string]: boolean} = {}

export function deprecate(msg: string, issue: number) {
if (reported[msg]) return
console.warn(msg + ` See: https://github.com/harttle/liquidjs/issues/${issue}`)
reported[msg] = true
}
32 changes: 14 additions & 18 deletions src/util/underscore.ts
@@ -1,5 +1,5 @@
import { deprecate } from './deprecate'
const toStr = Object.prototype.toString
const arrToStr = Array.prototype.toString

/*
* Checks if value is classified as a String primitive or object.
Expand Down Expand Up @@ -28,25 +28,21 @@ export function promisify (fn: any) {

export function stringify (value: any): string {
if (isNil(value)) return ''
if (isFunction(value.to_liquid)) return stringify(value.to_liquid())
if (isFunction(value.toLiquid)) return stringify(value.toLiquid())
if (isFunction(value.to_s)) return value.to_s()
if ([toStr, arrToStr].indexOf(value.toString) > -1) return defaultToString(value)
if (isFunction(value.toString)) return value.toString()
return toStr.call(value)
value = toLiquid(value)
if (isFunction(value.to_s)) {
deprecate('to_s is deprecated, use toString instead.', 109)
return value.to_s()
}
return String(value)
}

function defaultToString (value: any): string {
const cache: any[] = []
return JSON.stringify(value, (key, value) => {
if (isObject(value)) {
if (cache.indexOf(value) !== -1) {
return
}
cache.push(value)
}
return value
})
export function toLiquid (value: any): any {
if (isFunction(value.to_liquid)) {
deprecate('to_liquid is deprecated, use toLiquid instead.', 109)
return toLiquid(value.to_liquid())
}
if (isFunction(value.toLiquid)) return toLiquid(value.toLiquid())
return value
}

export function create<T1 extends object, T2 extends T1 = T1> (proto: T1): T2 {
Expand Down

0 comments on commit 7bee9fc

Please sign in to comment.