From 17963c9082cb3a391e7f8df0dba8582a599400ac Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 5 Sep 2016 14:49:51 +0300 Subject: [PATCH] Add. Protected formatter. --- lua/log/formatter/pformat.lua | 91 ++++++++++++++++++++++++++++++++ rockspecs/lua-log-scm-0.rockspec | 2 + test/test_basic.lua | 71 +++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 lua/log/formatter/pformat.lua diff --git a/lua/log/formatter/pformat.lua b/lua/log/formatter/pformat.lua new file mode 100644 index 0000000..e79028a --- /dev/null +++ b/lua/log/formatter/pformat.lua @@ -0,0 +1,91 @@ +local lpeg = require "lpeg" +local table = require "table" +local string = require "string" + +local unpack = unpack or table.unpack + +local HAS_A_FORMAT = pcall(string.format, '%a' , 10) + +local P, C, Cs, Ct, Cp, S, R = lpeg.P, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cp, lpeg.S, lpeg.R + +local any = P(1) +local empty = P(0) + +local esc = P'%%' +local flags = S'-+ #0' +local digit = R'09' + +local fsym = S('cdiouxXeEfgGqs' .. (HAS_A_FORMAT and 'aA' or '')) +local width = digit * digit + digit +local precision = P'.' * (digit * digit + digit) +local format = (flags + empty) * (width + empty) * (precision + empty) * (P'.' + empty) +local valid_format = P'%' * format * fsym +local valid_format_capture = Cs(valid_format) + +local any_fsym = any - (flags + digit + P'.') +local any_format = P'%' * (flags + digit + P'.')^0 * any_fsym + +local types = { + c = 'number'; + d = 'number'; + i = 'number'; + o = 'number'; + u = 'number'; + x = 'number'; + X = 'number'; + a = 'number'; + A = 'number'; + e = 'number'; + E = 'number'; + f = 'number'; + g = 'number'; + G = 'number'; + q = 'string'; + s = 'string'; +} + +local function safe_format(protect_only_args, fmt, ...) + local n, args = 0, {...} + + local function fix_fmt(f) + local fmt = valid_format_capture:match(f) + + if not fmt then + if protect_only_args then return end + return '%' .. f + end + + local typ = string.sub(fmt, -1) + + n = n + 1 + + if types[typ] ~= type( args[n] ) then + args[n], fmt = tostring(args[n]), '%s' + end + + return fmt + end + + local pattern = Cs((esc + any_format / fix_fmt + any) ^ 0) + fmt = pattern:match(fmt) + + return string.format(fmt, unpack(args, 1, n)) +end + +local function buld_formatter(protect_only_args, no_warning) + return function(...) + local ok, msg = pcall(string.format, ...) + if not ok then + local err = msg + msg = safe_format(protect_only_args, ...) + if not no_warning then + msg = msg .. ' - ' .. 'WARNING: Error formatting log message: ' .. err + end + end + return msg + end +end + +return { + new = buld_formatter +} diff --git a/rockspecs/lua-log-scm-0.rockspec b/rockspecs/lua-log-scm-0.rockspec index ee8f3ce..194a495 100644 --- a/rockspecs/lua-log-scm-0.rockspec +++ b/rockspecs/lua-log-scm-0.rockspec @@ -16,6 +16,7 @@ description = { dependencies = { "lua >= 5.1", "date >= 2.0", + -- "lpeg >= 0.10.0", -- "llthread >= 1.2", -- "luasocket >= 2.0.1", -- "lzmq >= 0.4.2", @@ -35,6 +36,7 @@ build = { ["log.formatter.concat" ] = "lua/log/formatter/concat.lua", ["log.formatter.default" ] = "lua/log/formatter/default.lua", ["log.formatter.format" ] = "lua/log/formatter/format.lua", + ["log.formatter.pformat" ] = "lua/log/formatter/pformat.lua", ["log.formatter.mix" ] = "lua/log/formatter/mix.lua", ["log.logformat.default" ] = "lua/log/logformat/default.lua", ["log.logformat.proxy" ] = "lua/log/logformat/proxy.lua", diff --git a/test/test_basic.lua b/test/test_basic.lua index 2378d20..3d3b692 100644 --- a/test/test_basic.lua +++ b/test/test_basic.lua @@ -268,4 +268,75 @@ end end +local _ENV = TEST_CASE'protected formatter' do + +function test_do_not_raise_error_nil_argument() + local formatter = require "log.formatter.pformat".new() + + local msg + assert_pass(function() msg = formatter("%s", nil) end) + assert_string(msg) + + local expected = tostring(nil) + assert_equal(expected, string.sub(msg,1, #expected)) + + assert_match('Error formatting log message', msg) +end + +function test_do_not_raise_error_unknown_format() + local formatter = require "log.formatter.pformat".new() + + local msg + assert_pass(function() msg = formatter("%t", 10) end) + assert_string(msg) + + local expected = "%t" + assert_equal(expected, string.sub(msg,1, #expected)) + + assert_match('Error formatting log message', msg) +end + +function test_do_not_raise_error_and_no_warning_nil_argument() + local formatter = require "log.formatter.pformat".new(nil, true) + + local msg + assert_pass(function() msg = formatter("%t", nil) end) + assert_string(msg) + + local expected = '%t' + assert_equal(expected, msg) +end + +function test_do_not_raise_error_and_no_warning_unknown_format() + local formatter = require "log.formatter.pformat".new(nil, true) + + local msg + assert_pass(function() msg = formatter("%t", 10) end) + assert_string(msg) + + local expected = '%t' + assert_equal(expected, msg) +end + +function test_do_not_raise_error_nil_argument_2() + local formatter = require "log.formatter.pformat".new(true) + + local msg + assert_pass(function() msg = formatter("%s", nil) end) + assert_string(msg) + + local expected = tostring(nil) + assert_equal(expected, string.sub(msg,1, #expected)) + + assert_match('Error formatting log message', msg) +end + +function test_raise_error_unknown_format() + local formatter = require "log.formatter.pformat".new(true) + + local msg = assert_error(function() msg = formatter("%t", 10) end) +end + +end + if not HAS_RUNNER then lunit.run() end