-- -- detoken.lua -- -- Expands tokens. -- -- Copyright (c) 2011-2014 Jason Perkins and the Premake project -- local p = premake p.detoken = {} local detoken = p.detoken -- -- Expand tokens in a value. -- -- @param value -- The value containing the tokens to be expanded. -- @param environ -- An execution environment for any token expansion. This is a list of -- key-value pairs that will be inserted as global variables into the -- token expansion runtime environment. -- @param field -- The definition of the field which stores the value. -- @param basedir -- If provided, path tokens encountered in non-path fields (where -- field.paths is set to false) will be made relative to this location. -- @return -- The value with any contained tokens expanded. -- function detoken.expand(value, environ, field, basedir) function expandtoken(token, e, f) -- fetch the path variable from the action, if needed local varMap = {} if f.pathVars or e.overridePathVars then local action = p.action.current() if action then varMap = action.pathVars or {} end end -- fetch the pathVars from the enviroment. local envMap = e.pathVars or {} -- enable access to the global environment setmetatable(e, {__index = _G}) local isAbs = false local err local result local success -- if the token starts with a !, don't try making it relative. local dontMakeRelative = token:startswith('!') if dontMakeRelative then token = token:sub(2, -1) end -- If this token is in my path variable mapping tables, replace the -- value with the one from the map. This needs to go here because -- I don't want to make the result relative, but I don't want the -- absolute path handling below. local mapped = envMap[token] or varMap[token] if mapped then err = nil result = mapped if type(result) == "function" then success, result = pcall(result, e) if not success then return nil, result end end if (type(result) == "table") then isAbs = result.absolute result = result.token else isAbs = path.isabsolute(result) end else -- convert the token into a function to execute local func func, err = load("return " .. token, nil, 't', e) if not func then return nil, "load error: " .. err end -- run it and get the result success, result = pcall(func) if not success then err = result result = nil else err = nil result = result or "" end if result ~= nil then -- ensure we got a string. result = tostring(result) -- If the result is an absolute path, and it is being inserted into -- a NON-path value, I need to make it relative to the project that -- will contain it. Otherwise I ended up with an absolute path in -- the generated project, and it can no longer be moved around. if path.hasdeferredjoin(result) then result = path.resolvedeferredjoin(result) end isAbs = path.isabsolute(result) if isAbs and not f.paths and basedir and not dontMakeRelative then result = path.getrelative(basedir, result) end end end -- If the result is an absolute path, and it is being inserted into -- a path value, place a special marker at the start of it. After -- all results have been processed, I can look for these markers to -- find the last absolute path expanded. -- -- Example: the value "/home/user/myprj/%{cfg.objdir}" expands to: -- "/home/user/myprj//home/user/myprj/obj/Debug". -- -- By inserting a marker this becomes: -- "/home/user/myprj/[\0]/home/user/myprj/obj/Debug". -- -- I can now trim everything before the marker to get the right -- result, which should always be the last absolute path specified: -- "/home/user/myprj/obj/Debug" if result ~= nil and isAbs and f.paths then result = "\0" .. result end return result, err end function expandvalue(value, e, f) if type(value) ~= "string" then return value end local count repeat value, count = value:gsub("%%{(.-)}", function(token) local result, err = expandtoken(token:gsub("\\", "\\\\"), e, f) if err then error(err .. " in token: " .. token, 0) end if not result then error("Token returned nil, it may not exist: " .. token, 0) end return result end) until count == 0 -- if a path, look for a split out embedded absolute paths if f.paths then local i, j repeat i, j = value:find("\0") if i then value = value:sub(i + 1) end until not i end return value end local expand_cache = {} function recurse(value, e, f) if type(value) == "table" then local res_table = {} for k, v in pairs(value) do if tonumber(k) ~= nil then res_table[k] = recurse(v, e, f) else local nk = recurse(k, e, f) res_table[nk] = recurse(v, e, f) end end return res_table else local res = expand_cache[value] if res == nil then if type(value) == "string" and path.hasdeferredjoin(value) then value = path.resolvedeferredjoin(value) end res = expandvalue(value, e, f) expand_cache[value] = res end return res end end return recurse(value, environ, field or {}) end