Skip to content

Commit

Permalink
feat(Drop): liquid_method_missing and to_s
Browse files Browse the repository at this point in the history
close #97
  • Loading branch information
harttle committed Jan 28, 2019
1 parent f38ea63 commit 4dffb27
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 25 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Though being compatible with [Ruby Liquid](https://github.com/shopify/liquid) is
* 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).
* 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).
* Along with [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops), we provide an alias `.toLiquid()` to align with your code styles.
* [.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.

## TOC

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
"types": "src/index.d.ts",
"scripts": {
"lint": "eslint src/ test/ *.js",
"test": "mocha test/unit",
"unit": "mocha test/unit",
"e2e": "mocha test/e2e",
"coverage": "cross-env NODE_ENV=test nyc --reporter=html npm test",
"test": "npm run unit && npm run e2e",
"coverage": "cross-env NODE_ENV=test nyc --reporter=html npm run unit",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"build": "rollup -c && ls -lh dist",
"version": "npm run build && git add -A dist",
Expand Down
30 changes: 20 additions & 10 deletions src/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,10 @@ const Scope = {
if (_.isNil(obj)) {
val = undefined
} else {
if (typeof obj.to_liquid === 'function') {
obj = obj.to_liquid()
} else if (typeof obj.toLiquid === 'function') {
obj = obj.toLiquid()
}

if (key === 'size' && (_.isArray(obj) || _.isString(obj))) {
val = obj.length
} else {
val = obj[key]
obj = toLiquid(obj)
val = key === 'size' ? readSize(obj) : obj[key]
if (_.isFunction(obj.liquid_method_missing)) {
val = obj.liquid_method_missing(key)
}
}
if (_.isNil(val) && this.opts.strict_variables) {
Expand Down Expand Up @@ -138,6 +132,22 @@ const Scope = {
}
}

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

function readSize (obj) {
if (!_.isNil(obj.size)) return obj.size
if (_.isArray(obj) || _.isString(obj)) return obj.length
return obj.size
}

function matchRightBracket (str, begin) {
let stack = 1 // count of '[' - count of ']'
for (let i = begin; i < str.length; i++) {
Expand Down
26 changes: 14 additions & 12 deletions src/util/underscore.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const toStr = Object.prototype.toString
const arrToStr = Array.prototype.toString

/*
* Checks if value is classified as a String primitive or object.
Expand All @@ -9,6 +10,10 @@ export function isString (value) {
return toStr.call(value) === '[object String]'
}

export function isFunction (value) {
return typeof value === 'function'
}

export function promisify (fn) {
return function () {
return new Promise((resolve, reject) => {
Expand All @@ -20,19 +25,16 @@ export function promisify (fn) {
}

export function stringify (value) {
if (isNil(value)) {
return String(value)
}
if (typeof value.to_liquid === 'function') {
return stringify(value.to_liquid())
}
if (typeof value.toLiquid === 'function') {
return stringify(value.toLiquid())
}
if (isString(value) || value instanceof RegExp || value instanceof Date) {
return value.toString()
}
if (isNil(value)) return String(value)
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)
}

function defaultToString (value) {
const cache = []
return JSON.stringify(value, (key, value) => {
if (isObject(value)) {
Expand Down
18 changes: 18 additions & 0 deletions test/unit/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ describe('render', function () {
const tpl = Template.parseValue('obj')
return expect(render.renderValue(tpl, scope)).to.eventually.equal('{"foo":"foo"}')
})
it('should respect to .toString()', async () => {
const scope = scopeFactory({ obj: { toString: () => 'FOO' } })
const tpl = Template.parseValue('obj')
const str = await render.renderValue(tpl, scope)
return expect(str).to.equal('FOO')
})
it('should respect to .to_s()', async () => {
const scope = scopeFactory({ obj: { to_s: () => 'FOO' } })
const tpl = Template.parseValue('obj')
const str = await render.renderValue(tpl, scope)
return expect(str).to.equal('FOO')
})
it('should respect to .liquid_method_missing()', async () => {
const scope = scopeFactory({ obj: { liquid_method_missing: x => x.toUpperCase() } })
const tpl = Template.parseValue('obj.foo')
const str = await render.renderValue(tpl, scope)
return expect(str).to.equal('FOO')
})
})

describe('.evalValue()', function () {
Expand Down

0 comments on commit 4dffb27

Please # to comment.