lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


-- 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
                     return tostring(val)
                  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
--           }
--        )
    ))