diff options
-rw-r--r-- | script/cli/check.lua | 4 | ||||
-rw-r--r-- | script/cli/doc.lua | 6 | ||||
-rw-r--r-- | script/cli/doc2md.lua | 6 | ||||
-rw-r--r-- | script/client.lua | 6 | ||||
-rw-r--r-- | script/config/loader.lua | 4 | ||||
-rw-r--r-- | script/core/command/jsonToLua.lua | 5 | ||||
-rw-r--r-- | script/json-beautify.lua | 112 | ||||
-rw-r--r-- | script/json.lua | 115 | ||||
-rw-r--r-- | script/jsonc.lua | 235 | ||||
-rw-r--r-- | script/library.lua | 7 | ||||
-rw-r--r-- | script/service/service.lua | 3 |
11 files changed, 196 insertions, 307 deletions
diff --git a/script/cli/check.lua b/script/cli/check.lua index 0addd4d0..37b6ad15 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -4,7 +4,7 @@ local ws = require 'workspace' local files = require 'files' local diag = require 'provider.diagnostic' local util = require 'utility' -local json = require 'json-beautify' +local jsonb = require 'json-beautify' local lang = require 'language' local define = require 'proto.define' local config = require 'config.config' @@ -93,7 +93,7 @@ if count == 0 then print(lang.script('CLI_CHECK_SUCCESS')) else local outpath = LOGPATH .. '/check.json' - util.saveFile(outpath, json.beautify(results)) + util.saveFile(outpath, jsonb.beautify(results)) print(lang.script('CLI_CHECK_RESULTS', count, outpath)) end diff --git a/script/cli/doc.lua b/script/cli/doc.lua index 6d5fe596..7ba40c78 100644 --- a/script/cli/doc.lua +++ b/script/cli/doc.lua @@ -3,7 +3,7 @@ local furi = require 'file-uri' local ws = require 'workspace' local files = require 'files' local util = require 'utility' -local json = require 'json-beautify' +local jsonb = require 'json-beautify' local lang = require 'language' local define = require 'proto.define' local config = require 'config.config' @@ -258,7 +258,7 @@ lclient():start(function (client) end) local outpath = LOGPATH .. '/doc.json' -json.supportSparseArray = true -util.saveFile(outpath, json.beautify(results)) +jsonb.supportSparseArray = true +util.saveFile(outpath, jsonb.beautify(results)) require 'cli.doc2md' diff --git a/script/cli/doc2md.lua b/script/cli/doc2md.lua index 70941d0c..8c0956f1 100644 --- a/script/cli/doc2md.lua +++ b/script/cli/doc2md.lua @@ -1,13 +1,15 @@ -- This is an example of how to process the generated `doc.json` file. -- You can use it to generate a markdown file or a html file. -local json = require 'json' +local jsonc = require 'jsonc' local util = require 'utility' local markdown = require 'provider.markdown' -local doc = json.decode(util.loadFile(LOGPATH .. '/doc.json')) +local doc = jsonc.decode_jsonc(util.loadFile(LOGPATH .. '/doc.json')) local md = markdown() +assert(type(doc) == 'table') + for _, class in ipairs(doc) do md:add('md', '# ' .. class.name) md:emptyLine() diff --git a/script/client.lua b/script/client.lua index 14320edf..9acb8ec5 100644 --- a/script/client.lua +++ b/script/client.lua @@ -6,7 +6,7 @@ local proto = require 'proto' local define = require 'proto.define' local config = require 'config' local converter = require 'proto.converter' -local json = require 'json-beautify' +local jsonb = require 'json-beautify' local await = require 'await' local scope = require 'workspace.scope' local inspect = require 'inspect' @@ -243,7 +243,7 @@ local function tryModifySpecifiedConfig(uri, finalChanges) if not path then return false end - util.saveFile(path, json.beautify(scp:get('lastLocalConfig'), { indent = ' ' })) + util.saveFile(path, jsonb.beautify(scp:get('lastLocalConfig'), { indent = ' ' })) return true end @@ -274,7 +274,7 @@ local function tryModifyRC(uri, finalChanges, create) if not suc then return false end - util.saveFile(path, json.beautify(rc, { indent = ' ' })) + util.saveFile(path, jsonb.beautify(rc, { indent = ' ' })) return true end diff --git a/script/config/loader.lua b/script/config/loader.lua index 5cc7139f..8a53d073 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -30,7 +30,7 @@ function m.loadRCConfig(uri, filename) scp:set('lastRCConfig', nil) return nil end - local suc, res = pcall(jsonc.decode, buf) + local suc, res = pcall(jsonc.decode_jsonc, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return scp:get('lastRCConfig') @@ -61,7 +61,7 @@ function m.loadLocalConfig(uri, filename) end local firstChar = buf:match '%S' if firstChar == '{' then - local suc, res = pcall(jsonc.decode, buf) + local suc, res = pcall(jsonc.decode_jsonc, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return scp:get('lastLocalConfig') diff --git a/script/core/command/jsonToLua.lua b/script/core/command/jsonToLua.lua index da1aeb4b..f09a3681 100644 --- a/script/core/command/jsonToLua.lua +++ b/script/core/command/jsonToLua.lua @@ -1,11 +1,12 @@ local files = require 'files' -local json = require 'jsonc' local util = require 'utility' local proto = require 'proto' local define = require 'proto.define' local lang = require 'language' local converter = require 'proto.converter' local guide = require 'parser.guide' +local json = require 'json' +local jsonc = require 'jsonc' ---@async return function (data) @@ -17,7 +18,7 @@ return function (data) local start = guide.positionToOffset(state, data.start) local finish = guide.positionToOffset(state, data.finish) local jsonStr = text:sub(start + 1, finish) - local suc, res = pcall(json.decode, jsonStr:match '[%{%[].+') + local suc, res = pcall(jsonc.decode_jsonc, jsonStr:match '[%{%[].+') if not suc or res == json.null then proto.notify('window/showMessage', { type = define.MessageType.Warning, diff --git a/script/json-beautify.lua b/script/json-beautify.lua index ede0e75b..f86a7625 100644 --- a/script/json-beautify.lua +++ b/script/json-beautify.lua @@ -5,23 +5,36 @@ local error = error local table_concat = table.concat local table_sort = table.sort local string_rep = string.rep -local math_type = math.type local setmetatable = setmetatable -local getmetatable = getmetatable -local statusMark -local statusQue +local math_type + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then + local math_floor = math.floor + function math_type(v) + if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then + return "integer" + end + return "float" + end +else + math_type = math.type +end + +local statusVisited +local statusBuilder local statusDep local statusOpt local defaultOpt = { newline = "\n", - indent = " ", + indent = " ", + depth = 0, } defaultOpt.__index = defaultOpt local function encode_newline() - statusQue[#statusQue+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) + statusBuilder[#statusBuilder+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) end local encode_map = {} @@ -32,96 +45,125 @@ end local function encode(v) local res = encode_map[type(v)](v) - statusQue[#statusQue+1] = res + statusBuilder[#statusBuilder+1] = res end function encode_map.string(v) - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(v) + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(v) return '"' end function encode_map.table(t) local first_val = next(t) if first_val == nil then - if getmetatable(t) == json.object then + if json.isObject(t) then return "{}" else return "[]" end end - if statusMark[t] then + if statusVisited[t] then error("circular reference") end - statusMark[t] = true + statusVisited[t] = true if type(first_val) == 'string' then local key = {} for k in next, t do if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end key[#key+1] = k end table_sort(key) - statusQue[#statusQue+1] = "{" + statusBuilder[#statusBuilder+1] = "{" statusDep = statusDep + 1 encode_newline() local k = key[1] - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(k) - statusQue[#statusQue+1] = '": ' + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(k) + statusBuilder[#statusBuilder+1] = '": ' encode(t[k]) for i = 2, #key do local k = key[i] - statusQue[#statusQue+1] = "," + statusBuilder[#statusBuilder+1] = "," encode_newline() - statusQue[#statusQue+1] = '"' - statusQue[#statusQue+1] = encode_string(k) - statusQue[#statusQue+1] = '": ' + statusBuilder[#statusBuilder+1] = '"' + statusBuilder[#statusBuilder+1] = encode_string(k) + statusBuilder[#statusBuilder+1] = '": ' encode(t[k]) end statusDep = statusDep - 1 encode_newline() - statusMark[t] = nil + statusVisited[t] = nil return "}" - else + elseif json.supportSparseArray then local max = 0 for k in next, t do if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end if max < k then max = k end end - statusQue[#statusQue+1] = "[" + statusBuilder[#statusBuilder+1] = "[" statusDep = statusDep + 1 encode_newline() encode(t[1]) for i = 2, max do - statusQue[#statusQue+1] = "," + statusBuilder[#statusBuilder+1] = "," encode_newline() encode(t[i]) end statusDep = statusDep - 1 encode_newline() - statusMark[t] = nil + statusVisited[t] = nil + return "]" + else + if t[1] == nil then + error("invalid table: sparse array is not supported") + end + statusBuilder[#statusBuilder+1] = "[" + statusDep = statusDep + 1 + encode_newline() + encode(t[1]) + local count = 2 + while t[count] ~= nil do + statusBuilder[#statusBuilder+1] = "," + encode_newline() + encode(t[count]) + count = count + 1 + end + if next(t, count-1) ~= nil then + local k = next(t, count-1) + if type(k) == "number" then + error("invalid table: sparse array is not supported") + else + error("invalid table: mixed or invalid key types: "..k) + end + end + statusDep = statusDep - 1 + encode_newline() + statusVisited[t] = nil return "]" end end +local function beautify_option(option) + return setmetatable(option or {}, defaultOpt) +end + local function beautify(v, option) - if type(v) == "string" then - v = json.decode(v) - end - statusMark = {} - statusQue = {} - statusDep = 0 - statusOpt = option and setmetatable(option, defaultOpt) or defaultOpt + statusVisited = {} + statusBuilder = {} + statusOpt = beautify_option(option) + statusDep = statusOpt.depth encode(v) - return table_concat(statusQue) + return table_concat(statusBuilder) end json.beautify = beautify +json.beautify_option = beautify_option return json diff --git a/script/json.lua b/script/json.lua index 39d06ab9..899d8c36 100644 --- a/script/json.lua +++ b/script/json.lua @@ -3,7 +3,6 @@ local next = next local error = error local tonumber = tonumber local tostring = tostring -local utf8_char = utf8.char local table_concat = table.concat local table_sort = table.sort local string_char = string.char @@ -13,17 +12,72 @@ local string_match = string.match local string_gsub = string.gsub local string_sub = string.sub local string_format = string.format -local math_type = math.type local setmetatable = setmetatable local getmetatable = getmetatable local huge = math.huge local tiny = -huge +local utf8_char +local math_type + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then + local math_floor = math.floor + function utf8_char(c) + if c <= 0x7f then + return string_char(c) + elseif c <= 0x7ff then + return string_char(math_floor(c / 64) + 192, c % 64 + 128) + elseif c <= 0xffff then + return string_char( + math_floor(c / 4096) + 224, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + elseif c <= 0x10ffff then + return string_char( + math_floor(c / 262144) + 240, + math_floor(c % 262144 / 4096) + 128, + math_floor(c % 4096 / 64) + 128, + c % 64 + 128 + ) + end + error(string_format("invalid UTF-8 code '%x'", c)) + end + function math_type(v) + if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then + return "integer" + end + return "float" + end +else + utf8_char = utf8.char + math_type = math.type +end + local json = {} -json.object = {} json.supportSparseArray = true +local objectMt = {} + +function json.createEmptyObject() + return setmetatable({}, objectMt) +end + +function json.isObject(t) + if t[1] ~= nil then + return false + end + return next(t) ~= nil or getmetatable(t) == objectMt +end + +if debug and debug.upvalueid then + -- Generate a lightuserdata + json.null = debug.upvalueid(json.createEmptyObject, 1) +else + json.null = function() end +end + -- json.encode -- local statusVisited local statusBuilder @@ -65,7 +119,7 @@ encode_map["nil"] = function () end local function encode_string(v) - return string_gsub(v, '[\0-\31\\"]', encode_escape_map) + return string_gsub(v, '[%z\1-\31\\"]', encode_escape_map) end function encode_map.string(v) @@ -93,6 +147,9 @@ function encode_map.number(v) if v ~= v or v <= tiny or v >= huge then error("unexpected number value '" .. tostring(v) .. "'") end + if math_type(v) == "integer" then + return string_format('%d', v) + end return convertreal(v) end @@ -107,7 +164,7 @@ end function encode_map.table(t) local first_val = next(t) if first_val == nil then - if getmetatable(t) == json.object then + if getmetatable(t) == objectMt then return "{}" else return "[]" @@ -121,7 +178,7 @@ function encode_map.table(t) local keys = {} for k in next, t do if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end keys[#keys+1] = k end @@ -140,21 +197,16 @@ function encode_map.table(t) end statusVisited[t] = nil return "}" - else - local count = 0 + elseif json.supportSparseArray then local max = 0 for k in next, t do if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") + error("invalid table: mixed or invalid key types: "..k) end - count = count + 1 if max < k then max = k end end - if not json.supportSparseArray and count ~= max then - error("sparse array are not supported") - end statusBuilder[#statusBuilder+1] = "[" encode(t[1]) for i = 2, max do @@ -163,6 +215,28 @@ function encode_map.table(t) end statusVisited[t] = nil return "]" + else + if t[1] == nil then + error("invalid table: sparse array is not supported") + end + statusBuilder[#statusBuilder+1] = "[" + encode(t[1]) + local count = 2 + while t[count] ~= nil do + statusBuilder[#statusBuilder+1] = "," + encode(t[count]) + count = count + 1 + end + if next(t, count-1) ~= nil then + local k = next(t, count-1) + if type(k) == "number" then + error("invalid table: sparse array is not supported") + else + error("invalid table: mixed or invalid key types: "..k) + end + end + statusVisited[t] = nil + return "]" end end @@ -213,7 +287,7 @@ local function find_line() end local function decode_error(msg) - error(string_format("ERROR: %s at line %d col %d", msg, find_line())) + error(string_format("ERROR: %s at line %d col %d", msg, find_line()), 2) end local function get_word() @@ -258,7 +332,7 @@ local function decode_string() local has_escape = false local i = statusPos + 1 while true do - i = string_find(statusBuf, '["\\\0-\31]', i) + i = string_find(statusBuf, '[%z\1-\31\\"]', i) if not i then decode_error "expected closing quote for string" end @@ -369,10 +443,10 @@ end local function decode_array() statusPos = statusPos + 1 - local res = {} if consume_byte "^[ \t\r\n]*%]" then - return res + return {} end + local res = {} statusTop = statusTop + 1 statusAry[statusTop] = true statusRef[statusTop] = res @@ -381,10 +455,10 @@ end local function decode_object() statusPos = statusPos + 1 - local res = {} if consume_byte "^[ \t\r\n]*}" then - return setmetatable(res, json.object) + return json.createEmptyObject() end + local res = {} statusTop = statusTop + 1 statusAry[statusTop] = false statusRef[statusTop] = res @@ -472,7 +546,4 @@ function json.decode(str) return res end --- Generate a lightuserdata -json.null = debug.upvalueid(decode, 1) - return json diff --git a/script/jsonc.lua b/script/jsonc.lua index 0361d99b..267ab31d 100644 --- a/script/jsonc.lua +++ b/script/jsonc.lua @@ -2,24 +2,15 @@ local type = type local next = next local error = error local tonumber = tonumber -local tostring = tostring -local table_concat = table.concat -local table_sort = table.sort local string_char = string.char local string_byte = string.byte local string_find = string.find local string_match = string.match local string_gsub = string.gsub local string_sub = string.sub -local string_rep = string.rep local string_format = string.format -local setmetatable = setmetatable -local getmetatable = getmetatable -local huge = math.huge -local tiny = -huge local utf8_char -local math_type if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then local math_floor = math.floor @@ -42,57 +33,13 @@ if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then c % 64 + 128 ) end - error(string.format("invalid UTF-8 code '%x'", c)) - end - function math_type(v) - if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then - return "integer" - end - return "float" + error(string_format("invalid UTF-8 code '%x'", c)) end else utf8_char = utf8.char - math_type = math.type -end - -local json = {} - -json.supportSparseArray = true - -local objectMt = {} - -function json.createEmptyObject() - return setmetatable({}, objectMt) -end - -function json.isObject(t) - if t[1] ~= nil then - return false - end - return next(t) ~= nil or getmetatable(t) == objectMt end -if debug and debug.upvalueid then - -- Generate a lightuserdata - json.null = debug.upvalueid(json.createEmptyObject, 1) -else - json.null = function() end -end - --- json.encode -- - -local statusVisited -local statusBuilder -local statusDep -local statusOpt - -local defaultOpt = { - newline = "", - indent = "", -} -defaultOpt.__index = defaultOpt - -local encode_map = {} +local json = require "json" local encode_escape_map = { [ "\"" ] = "\\\"", @@ -112,182 +59,6 @@ for k, v in next, encode_escape_map do decode_escape_set[string_byte(v, 2)] = true end -for i = 0, 31 do - local c = string_char(i) - if not encode_escape_map[c] then - encode_escape_map[c] = string_format("\\u%04x", i) - end -end - -encode_map["nil"] = function () - return "null" -end - -local function encode_string(v) - return string_gsub(v, '[%z\1-\31\\"]', encode_escape_map) -end - -local function convertreal(v) - local g = string_format('%.16g', v) - if tonumber(g) == v then - return g - end - return string_format('%.17g', v) -end - -if string_match(tostring(1/2), "%p") == "," then - local _convertreal = convertreal - function convertreal(v) - return string_gsub(_convertreal(v), ',', '.') - end -end - -function encode_map.number(v) - if v ~= v or v <= tiny or v >= huge then - error("unexpected number value '" .. tostring(v) .. "'") - end - if math_type(v) == "integer" then - return string_format('%d', v) - end - return convertreal(v) -end - -function encode_map.boolean(v) - if v then - return "true" - else - return "false" - end -end - -local function encode_unexpected(v) - if v == json.null then - return "null" - else - error("unexpected type '"..type(v).."'") - end -end -encode_map[ "function" ] = encode_unexpected -encode_map[ "userdata" ] = encode_unexpected -encode_map[ "thread" ] = encode_unexpected - -local function encode_newline() - statusBuilder[#statusBuilder+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep) -end - -local function encode(v) - local res = encode_map[type(v)](v) - statusBuilder[#statusBuilder+1] = res -end - -function encode_map.string(v) - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(v) - return '"' -end - -function encode_map.table(t) - local first_val = next(t) - if first_val == nil then - if getmetatable(t) == objectMt then - return "{}" - else - return "[]" - end - end - if statusVisited[t] then - error("circular reference") - end - statusVisited[t] = true - if type(first_val) == 'string' then - local key = {} - for k in next, t do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - key[#key+1] = k - end - table_sort(key) - statusBuilder[#statusBuilder+1] = "{" - statusDep = statusDep + 1 - encode_newline() - local k = key[1] - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(k) - statusBuilder[#statusBuilder+1] = '": ' - encode(t[k]) - for i = 2, #key do - local k = key[i] - statusBuilder[#statusBuilder+1] = "," - encode_newline() - statusBuilder[#statusBuilder+1] = '"' - statusBuilder[#statusBuilder+1] = encode_string(k) - statusBuilder[#statusBuilder+1] = '": ' - encode(t[k]) - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "}" - elseif json.supportSparseArray then - local max = 0 - for k in next, t do - if math_type(k) ~= "integer" or k <= 0 then - error("invalid table: mixed or invalid key types") - end - if max < k then - max = k - end - end - statusBuilder[#statusBuilder+1] = "[" - statusDep = statusDep + 1 - encode_newline() - encode(t[1]) - for i = 2, max do - statusBuilder[#statusBuilder+1] = "," - encode_newline() - encode(t[i]) - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "]" - else - if t[1] == nil then - error("invalid table: mixed or invalid key types") - end - statusBuilder[#statusBuilder+1] = "[" - statusDep = statusDep + 1 - encode_newline() - encode(t[1]) - local count = 2 - while t[count] ~= nil do - statusBuilder[#statusBuilder+1] = "," - encode_newline() - encode(t[count]) - count = count + 1 - end - if next(t, count-1) ~= nil then - error("invalid table: mixed or invalid key types") - end - statusDep = statusDep - 1 - encode_newline() - statusVisited[t] = nil - return "]" - end -end - -function json.encode(v, option) - statusVisited = {} - statusBuilder = {} - statusDep = 0 - statusOpt = option and setmetatable(option, defaultOpt) or defaultOpt - encode(v) - return table_concat(statusBuilder) -end - --- json.decode -- - local statusBuf local statusPos local statusTop @@ -580,7 +351,7 @@ local function decode_item() end end -function json.decode(str) +function json.decode_jsonc(str) if type(str) ~= "string" then error("expected argument of type string, got " .. type(str)) end diff --git a/script/library.lua b/script/library.lua index b160f603..7ff735cc 100644 --- a/script/library.lua +++ b/script/library.lua @@ -9,13 +9,12 @@ local fsu = require 'fs-utility' local define = require "proto.define" local files = require 'files' local await = require 'await' -local timer = require 'timer' local encoder = require 'encoder' local ws = require 'workspace.workspace' local scope = require 'workspace.scope' local inspect = require 'inspect' +local jsonb = require 'json-beautify' local jsonc = require 'jsonc' -local json = require 'json' local m = {} @@ -284,7 +283,7 @@ local function loadSingle3rdConfigFromJson(libraryDir) return nil end - local suc, cfg = xpcall(jsonc.decode, function (err) + local suc, cfg = xpcall(jsonc.decode_jsonc, function (err) log.error('Decode config.json failed at:', libraryDir:string(), err) end, configText) if not suc then @@ -339,7 +338,7 @@ local function loadSingle3rdConfig(libraryDir) if not cfg then return end - local jsonbuf = json.beautify(cfg) + local jsonbuf = jsonb.beautify(cfg) client.requestMessage('Info', lang.script.WINDOW_CONFIG_LUA_DEPRECATED, { lang.script.WINDOW_CONVERT_CONFIG_LUA, }, function (action, index) diff --git a/script/service/service.lua b/script/service/service.lua index e15dfbd1..54620bd0 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -13,6 +13,9 @@ local time = require 'bee.time' local fw = require 'filewatch' local furi = require 'file-uri' +require 'jsonc' +require 'json-beautify' + ---@class service local m = {} m.type = 'service' |