• Subject: Re: string interpolator
• From: "Dimiter \"malkia\" Stanev" <malkia@...>
• Date: Sun, 25 Dec 2011 01:37:12 -0800

-- Somewhat better version, that can work on multiple tables (environments), rather than one... This way one can add, for example, the process environment as table, and expand it there, or some last-minute workaround table for missing keys.
```

local env = {
ver1 = "-alpha",
ver2 = "-beta",
ver = 1,
name = "cairo\${ver\${ver}}", -- cairo\${ver1} -> cairo-alpha
one = {
two = "\${HOME}", -- /Users/<user> (or /home/<user>)
three = "\${ENV.HOME\${ver}}" -- same as above
},
depo = "\${one.two}/\${name}" -- /Users/<user>/cairo-alpha
}

local function split(s, d)
local r, b, e, d = {}, 1, 2, d or '.'
while e ~= 0 do
e = s:find(d, b, true) or 0
b, r[#r+1] = e + 1, s:sub(b, e - 1)
end
return r
end

-- Expands all occurences of \${var} in the string (s) - the first argument
```
-- With values taken from the variarble tables (...) - the rest of the arguments
```local function interpolate(a, ...)
local maxexp = 50 -- max expansion count
local envs = {...}

local function recur(a)
local more
while more ~= 0 do
a, more = a:gsub(
"\$(%b{})",
function(s)
-- Don't expand more than 50 times
maxexp = maxexp - 1
if maxexp == 0 then
error( 'Max expansion count reached' )
end

local s = s:sub(2, -2)
local key = recur(s)
local keys = split(key) or {}
local v = nil

-- Lookup each environment
for _, env in ipairs(envs) do
local val = env
-- Lookup composite keys, like: one.two.three
for _, k in ipairs(keys) do
if val[k] == nil then
-- Lookup as array index (number)
k = tonumber(k)
if k == nil or val[k] == nil then
val = nil
break
end
end
val = val[k]
end

-- Found something
if val ~= nil then
local t = type(val)
if t~="string" and t~="number" then
-- butt-ugly cases require some butt-ugly brackets
```
if t=="table" then val = "" else val = "="..tostring(val) end
```                        return "\$<" .. tostring(s) .. val .. ">"
end
end
end

-- Haven't found anything
error( 'No value for key [' ..
tostring(key) ..
```
((s == key) and '' or '] <- [' .. tostring(s)) .. ']')
```            end)
end
return a
end
return recur(a)
end

-- The Process Environment wrapper
local procenv = setmetatable(
{}, {
__index = function(t, k) return os.getenv(k) end,
```
__newindex = function(...) error("Can't modify process environment") end
```   }
)

-- Attach the process environment ot our test environment
-- under the key "ENV". If the key has been re-used, then
-- it's no longer accessible.
local env = setmetatable(
env, {
__index = function(t, k)
local kenv = "ENV"
if k == kenv and rawget(t, kenv)==nil then
return procenv
end
end
}
)

-- Process environment can be accessed either by ENV
-- Or directly by using environment variable name
print( interpolate(
env.depo,
env,
procenv
-- Let even empty keys go on, but ugly
--       ,setmetatable(
--           {},
--           {
--              __index = function(t,k)
```
-- print( 'Letting nil key ' .. tostring(k) .. ' pass through' )
```--                           return t
--                        end
--           }
--        )
))

```