diff --git a/CHANGELOG.md b/CHANGELOG.md index dce04659..93921bfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) fo [#360](https://github.com/lunarmodules/Penlight/pull/360) - feat: `permute.list_table` generate table with different sets of values [#360](https://github.com/lunarmodules/Penlight/pull/360) + - feat: deprecation functionality `utils.raise_deprecation` [#361](https://github.com/lunarmodules/Penlight/pull/361) ## 1.9.1 (2020-09-27) diff --git a/lua/pl/utils.lua b/lua/pl/utils.lua index 097fd8dd..8eb24c8d 100644 --- a/lua/pl/utils.lua +++ b/lua/pl/utils.lua @@ -557,6 +557,7 @@ local function _string_lambda(f) end end + --- an anonymous function as a string. This string is either of the form -- '|args| expression' or is a function of one argument, '_' -- @param lf function as a string @@ -587,6 +588,7 @@ function utils.bind1 (fn,p) return function(...) return fn(p,...) end end + --- bind the second argument of the function to a value. -- @param fn a function of at least two values (may be an operator string) -- @param p a value @@ -605,6 +607,78 @@ function utils.bind2 (fn,p) return function(x,...) return fn(x,p,...) end end + + + +--- Deprecation +-- @section deprecation + + +--- A deprecation warning function, to be overridden. +-- An application can override this function to support proper output of +-- deprecation warnings. The warnings can be generated from libraries or +-- functions by calling `utils.raise_deprecation`. By default this function +-- doesn't do anything. +-- +-- Note: only applications should override this function, libraries should not. +-- @string msg the message to display/log +-- @string trace the traceback from where the deprecated element was invoked +-- @usage +-- function utils.deprecation_warning(msg, trace) +-- io.stderr:write(msg .. "\n" .. trace .."\n") +-- end +function utils.deprecation_warning(msg, trace) + -- this does nothing by default +end + + +--- raises a deprecation warning. +-- For options see the usage example below. +-- +-- Note: the `opts.version_deprecated` field is the last version in which +-- a feature or option was NOT YET deprecated! Because when writing the code it +-- is quite often not known in what version the code will land. But the last +-- released version is usually known. +-- @param opts options table +-- @see utils.deprecation_warning +-- @usage +-- function stringx.islower(str) +-- deprecation_warning { +-- source = "Penlight " .. utils._VERSION, -- optional +-- message = "function 'islower' was renamed to 'is_lower'" -- required +-- version_removed = "2.0.0", -- optional +-- version_deprecated = "1.2.3", -- optional +-- } +-- return stringx.is_lower(str) +-- end +-- -- output: "[Penlight 1.9.2] function 'islower' was renamed to 'is_lower' (deprecated after 1.2.3, scheduled for removal in 2.0.0)" +function utils.raise_deprecation(opts) + utils.assert_arg(1, opts, "table") + if type(opts.message) ~= "string" then + error("field 'message' of the options table must be a string", 2) + end + local trace = debug.traceback("", 2):match("[\n%s]*(.-)$") + local msg + if opts.version_deprecated and opts.version_removed then + msg = (" (deprecated after %s, scheduled for removal in %s)"):format( + tostring(opts.version_deprecated), tostring(opts.version_removed)) + elseif opts.version_deprecated then + msg = (" (deprecated after %s)"):format(tostring(opts.version_deprecated)) + elseif opts.version_removed then + msg = (" (scheduled for removal in %s)"):format(tostring(opts.version_removed)) + else + msg = "" + end + + msg = opts.message .. msg + + if opts.source then + msg = "[" .. opts.source .."] " .. msg + end + + utils.deprecation_warning(msg, trace) +end + return utils diff --git a/spec/utils-deprecate_spec.lua b/spec/utils-deprecate_spec.lua new file mode 100644 index 00000000..b236cb06 --- /dev/null +++ b/spec/utils-deprecate_spec.lua @@ -0,0 +1,102 @@ +local utils = require("pl.utils") + +describe("pl.utils", function () + + local old_fn, last_msg, last_trace + + before_each(function() + old_fn = utils.deprecation_warning + last_msg = nil + last_trace = nil + utils.deprecation_warning = function(msg, trace) + last_msg = msg + last_trace = trace + end + end) + + + after_each(function() + utils.deprecation_warning = old_fn + end) + + + + describe("raise_deprecation", function () + + it("requires the opts table", function() + assert.has.error(function() utils.raise_deprecation(nil) end, + "argument 1 expected a 'table', got a 'nil'") + end) + + + it("requires the opts.message field", function() + assert.has.error(function() utils.raise_deprecation({}) end, + "field 'message' of the options table must be a string") + end) + + + it("should output the message", function () + utils.raise_deprecation { + message = "hello world" + } + assert.equal("hello world", last_msg) + end) + + + it("should output the deprecated version", function () + utils.raise_deprecation { + message = "hello world", + version_deprecated = "2.0.0", + } + assert.equal("hello world (deprecated after 2.0.0)", last_msg) + end) + + + it("should output the removal version", function () + utils.raise_deprecation { + message = "hello world", + version_removed = "3.0.0", + } + assert.equal("hello world (scheduled for removal in 3.0.0)", last_msg) + end) + + + it("should output the deprecated and removal versions", function () + utils.raise_deprecation { + message = "hello world", + version_deprecated = "2.0.0", + version_removed = "3.0.0", + } + assert.equal("hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) + end) + + + it("should output the application/module name", function () + utils.raise_deprecation { + source = "MyApp 1.2.3", + message = "hello world", + version_deprecated = "2.0.0", + version_removed = "3.0.0", + } + assert.equal("[MyApp 1.2.3] hello world (deprecated after 2.0.0, scheduled for removal in 3.0.0)", last_msg) + end) + + + it("should add a stracktrace", function () + local function my_function_name() + utils.raise_deprecation { + source = "MyApp 1.2.3", + message = "hello world", + version_deprecated = "2.0.0", + version_removed = "3.0.0", + } + end + my_function_name() + + assert.Not.match("raise_deprecation", last_trace) + assert.match("my_function_name", last_trace) + end) + + end) + +end)