Skip to content

Instantly share code, notes, and snippets.

@benphelps
Created July 19, 2024 10:22
Show Gist options
  • Save benphelps/b52069714f97d86a7e4dffbb5892846f to your computer and use it in GitHub Desktop.
Save benphelps/b52069714f97d86a7e4dffbb5892846f to your computer and use it in GitHub Desktop.
A simple libfmt-esque addition to the lua string library.
-- A simple string formatting function that supports named arguments.
-- It is inspired by libfmt (https://github.com/fmtlib/fmt) and Python's str.format() method.
---@param input string The string to format.
---@vararg any The arguments to format the string with.
---@return string formatted The formatted string.
local function fmt(input, ...)
local args = { ... }
local is_table_arg = type(args[1]) == "table" and #args == 1
local index = 1
local function replacer(key)
if key == "" then
key = tostring(index)
index = index + 1
end
local pattern, format_spec = key:match("([^:]*):?(.*)")
if pattern == "" then
key = tostring(index) .. key
index = index + 1
pattern, format_spec = key:match("([^:]*):?(.*)")
end
local keys = {}
for part in string.gmatch(pattern, "[^.]+") do
table.insert(keys, part)
end
local value = is_table_arg and args[1] or args
for _, part in ipairs(keys) do
value = value[tonumber(part) or part]
if type(value) ~= "table" then
break
end
end
if format_spec ~= "" and type(value) ~= "table" then
value = string.format("%" .. format_spec, value)
end
return tostring(value)
end
return (input:gsub("{(.-)}", replacer))
end
---@param self string
string.fmt = function(self, ...)
return fmt(self, ...)
end
---@param self string
getmetatable("").__mod = function(self, ...)
return fmt(self, ...)
end
---@param self string
getmetatable("").__call = function(self, ...)
return fmt(self, ...)
end
return fmt
local fmt = require "fmt"
-- Basic positional arguments
assert(string.fmt("{}, {}", 1, "foo"), "1, foo")
-- Basic positional argument using % operator
assert("{}" % "foo", "foo")
-- Basic formatted positional argument using % operator
assert("{:.2f}" % 1, "1.00")
-- Basic positional arguments using call syntax
assert(("{}, {}")(1, "foo"), "1, foo")
-- Named arguments
assert(string.fmt("{a}, {b}", { a = 1, b = "foo" }), "1, foo")
-- Named formatted arguments
assert(string.fmt("{a:.2f}, {b}", { a = 1, b = "foo" }), "1.00, foo")
-- Nested named arguments
assert(string.fmt("{a.z}, {b.y}", { a = { z = 1 }, b = { y = "foo" } }), "1, foo")
-- Nested formatted named arguments
assert(string.fmt("{a.z:.2f}, {b.y}", { a = { z = 1 }, b = { y = "foo" } }), "1.00, foo")
-- Nested named positional arguments
assert(string.fmt("{a.}, {a.}", { a = { 1, "foo"} }), "1, foo")
-- Mixed positional and named arguments
assert("{a}, {}, {b}, {}" % { a = 1, b = "foo", [1] = "baz", [2] = "bang" }, "1, baz, foo, bang")
-- Mixed formatted positional and named arguments
assert("{a:.2f}, {}, {b}, {}" % { a = 1, b = "foo", [1] = "baz", [2] = "bang" }, "1.00, baz, foo, bang")
-- Named arguments using call syntax
assert(("{a}, {b}"){ a = 1, b = "foo" }, "1, foo")
-- Named formatted arguments using call syntax
assert(("{a:.2f}, {b}"){ a = 1, b = "foo" }, "1.00, foo")
-- Method syntax on string with positional arguments
local test = "{}, {}"
assert(test:fmt(1, "foo"), "1, foo")
-- Method syntax on string with formatted positional arguments
local test_fmt = "{:.2f}, {}"
assert(test_fmt:fmt(1, "foo"), "1.00, foo")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment