Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
const { isNull, isBoolean, isNumber, isString, isArray, isObject, isEmpty, fromPairs, keys, map, repeat } = require('lodash')
const { parse: parseLua } = require('luaparse')

const formatLuaString = (string, singleQuote) => (singleQuote ? `'${string.replace(/'/g, "\\'")}'` : `"${string.replace(/"/g, '\\"')}"`)
/**
* @link https://github.com/fstirlitz/luaparse/blob/0f525b152516bc8afa34564de2423b039aa83bc1/luaparse.js#L1414
* @param {string} id
* @returns {boolean}
*/
function isKeyword(id) {
switch (id.length) {
case 2:
return 'do' === id || 'if' === id || 'in' === id || 'or' === id;
case 3:
return 'and' === id || 'end' === id || 'for' === id || 'not' === id;
case 4:
// TODO: configure labels "goto"
return 'else' === id || 'then' === id || 'goto' === id;
case 5:
return 'break' === id || 'local' === id || 'until' === id || 'while' === id;
case 6:
return 'elseif' === id || 'repeat' === id || 'return' === id;
case 8:
return 'function' === id;
}
return false;
}

const formatLuaString = (string, singleQuote, multilineString) => {
string = string.replace(/\\/g, "\\\\")
if (multilineString) {
if(string.includes("]]")) throw new SyntaxError("Multiline string can't include ']]'.") // TODO: provide more information about the error
return `[[${string}]]`
} else {
string = string.replace(/\n/g, "\\n")
return (singleQuote ? `'${string.replace(/'/g, "\\'")}'` : `"${string.replace(/"/g, '\\"')}"`)
}
}

const valueKeys = { false: 'false', true: 'true', null: 'nil' }

const formatLuaKey = (string, singleQuote) =>
valueKeys[string] ? `[${valueKeys[string]}]` : string.match(/^[a-zA-Z_][a-zA-Z_0-9]*$/) ? string : `[${formatLuaString(string, singleQuote)}]`
valueKeys[string] ? `[${valueKeys[string]}]` : (string.match(/^[a-zA-Z_][a-zA-Z_0-9]*$/) && !isKeyword(string)) ? string : `[${formatLuaString(string, singleQuote)}]`

const format = (value, options = { eol: '\n', singleQuote: true, spaces: 2 }) => {
const format = (value, options = { eol: '\n', singleQuote: true, multilineString: false, spaces: 2 }) => {
options = options || {}
const eol = (options.eol = isString(options.eol) ? options.eol : '\n')
options.singleQuote = isBoolean(options.singleQuote) ? options.singleQuote : true
Expand All @@ -22,7 +55,7 @@ const format = (value, options = { eol: '\n', singleQuote: true, spaces: 2 }) =>
return value.toString()
}
if (isString(value)) {
return formatLuaString(value, options.singleQuote)
return formatLuaString(value, options.singleQuote, options.multilineString)
}
if (isArray(value)) {
if (isEmpty(value)) {
Expand All @@ -43,11 +76,11 @@ const format = (value, options = { eol: '\n', singleQuote: true, spaces: 2 }) =>
const spaces = isNumber(options.spaces) ? repeat(' ', options.spaces * (i + 1)) : repeat(options.spaces, i + 1)
const spacesEnd = isNumber(options.spaces) ? repeat(' ', options.spaces * i) : repeat(options.spaces, i)
return `{${eol}${keys(value)
.map(key => `${spaces}${formatLuaKey(key, options.singleQuote)} = ${rec(value[key], i + 1)},`)
.map(key => `${spaces}${formatLuaKey(key, options.singleQuote, false)} = ${rec(value[key], i + 1)},`)
.join(eol)}${eol}${spacesEnd}}`
}
return `{${keys(value)
.map(key => `${formatLuaKey(key, options.singleQuote)}=${rec(value[key], i + 1)},`)
.map(key => `${formatLuaKey(key, options.singleQuote, false)}=${rec(value[key], i + 1)},`)
.join('')}}`
}
throw new Error(`can't format ${typeof value}`)
Expand Down
65 changes: 64 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const json = {
_float: 3.14,
_big: 1e123,
_string: "...'...'...",
_multilinestring: '1st line\n2nd line\n3rd line',
_backslash: {
_single: '\\',
_double: '\\\\',
_followed: '\\test',
},
_array0: [],
_array1: [1],
_array2: [1, 2],
Expand All @@ -29,6 +35,27 @@ const json = {
false: 4,
true: 5,
},
_keywords: {
do: 'keyword',
if: 'keyword',
in: 'keyword',
or: 'keyword',
and: 'keyword',
end: 'keyword',
for: 'keyword',
not: 'keyword',
else: 'keyword',
then: 'keyword',
goto: 'keyword',
break: 'keyword',
local: 'keyword',
until: 'keyword',
while: 'keyword',
elseif: 'keyword',
repeat: 'keyword',
return: 'keyword',
function: 'keyword'
}
}

const lua = `return {
Expand All @@ -41,6 +68,12 @@ const lua = `return {
_float = 3.14,
_big = 1e+123,
_string = '...\\'...\\'...',
_multilinestring = '1st line\\n2nd line\\n3rd line',
_backslash = {
_single = '\\\\',
_double = '\\\\\\\\',
_followed = '\\\\test',
},
_array0 = {},
_array1 = {
1,
Expand All @@ -62,9 +95,30 @@ const lua = `return {
[false] = 4,
[true] = 5,
},
_keywords = {
['do'] = 'keyword',
['if'] = 'keyword',
['in'] = 'keyword',
['or'] = 'keyword',
['and'] = 'keyword',
['end'] = 'keyword',
['for'] = 'keyword',
['not'] = 'keyword',
['else'] = 'keyword',
['then'] = 'keyword',
['goto'] = 'keyword',
['break'] = 'keyword',
['local'] = 'keyword',
['until'] = 'keyword',
['while'] = 'keyword',
['elseif'] = 'keyword',
['repeat'] = 'keyword',
['return'] = 'keyword',
['function'] = 'keyword',
},
}`

const luaMinified = `return{_null=nil,_false=false,_true=true,_zero=0,_one=1,_negative=-1,_float=3.14,_big=1e+123,_string='...\\'...\\'...',_array0={},_array1={1,},_array2={1,2,},_object0={},_object1={_nested={_value=1,},},_object2={['a b']=1,['...\\'...\\'...']=2,[nil]=3,[false]=4,[true]=5,},}`
const luaMinified = `return{_null=nil,_false=false,_true=true,_zero=0,_one=1,_negative=-1,_float=3.14,_big=1e+123,_string='...\\'...\\'...',_multilinestring='1st line\\n2nd line\\n3rd line',_backslash={_single='\\\\',_double='\\\\\\\\',_followed='\\\\test',},_array0={},_array1={1,},_array2={1,2,},_object0={},_object1={_nested={_value=1,},},_object2={['a b']=1,['...\\'...\\'...']=2,[nil]=3,[false]=4,[true]=5,},_keywords={['do']='keyword',['if']='keyword',['in']='keyword',['or']='keyword',['and']='keyword',['end']='keyword',['for']='keyword',['not']='keyword',['else']='keyword',['then']='keyword',['goto']='keyword',['break']='keyword',['local']='keyword',['until']='keyword',['while']='keyword',['elseif']='keyword',['repeat']='keyword',['return']='keyword',['function']='keyword',},}`

equal(format(json), lua)
deepEqual(parse(lua), json)
Expand All @@ -77,3 +131,12 @@ jsonDoubleQuote._object2 = mapKeys(jsonDoubleQuote._object2, (_, key) => key.rep
const luaDoubleQuote = lua.replace(/'/g, '"')
equal(format(jsonDoubleQuote, { singleQuote: false }), luaDoubleQuote)
deepEqual(parse(luaDoubleQuote), jsonDoubleQuote)

const jsonMultiline = { "test": "1st line\n2nd line\n3rd line" }
const luaMultiline = `return {
test = [[1st line
2nd line
3rd line]],
}`
equal(format(jsonMultiline, { multilineString: true }), luaMultiline)
deepEqual(parse(luaMultiline), jsonMultiline)