local path = require("tools.lua.path") local process = {} local function parse_elapsed_seconds(s) if not s then return nil end local parts = {} for part in tostring(s):gmatch("[^:]+") do parts[#parts + 1] = tonumber(part) end if #parts == 3 then return parts[1] * 3600 + parts[2] * 60 + parts[3] elseif #parts == 2 then return parts[1] * 60 + parts[2] elseif #parts == 1 then return parts[1] end return nil end local function normalize_status(a, b, c) if type(a) == "boolean" then return a and 0 or (c or 1) end if type(a) == "number" then if a == 0 then return 0 end return a end return 1 end function process.capture(cmd) local f = assert(io.popen(cmd)) local out = f:read("*a") local a, b, c = f:close() return out, normalize_status(a, b, c) end function process.run(cmd) local a, b, c = os.execute(cmd) return normalize_status(a, b, c) end function process.with_cwd(root, cmd) if path.is_windows then return "cd /d " .. path.shell_quote(root) .. " && " .. cmd end return "cd " .. path.shell_quote(root) .. " && " .. cmd end function process.env_prefix(env) local keys = {} for k, _ in pairs(env or {}) do keys[#keys + 1] = k end table.sort(keys) if #keys == 0 then return "" end if path.is_windows then local parts = {} for _, k in ipairs(keys) do parts[#parts + 1] = "set " .. k .. "=" .. tostring(env[k]) .. "&&" end return table.concat(parts, " ") .. " " end local parts = {} for _, k in ipairs(keys) do parts[#parts + 1] = k .. "=" .. path.shell_quote(env[k]) end return table.concat(parts, " ") .. " " end function process.command_exists(name) local cmd if path.is_windows then cmd = "where " .. path.shell_quote(name) .. " >NUL 2>NUL" else cmd = "command -v " .. path.shell_quote(name) .. " >/dev/null 2>&1" end return process.run(cmd) == 0 end function process.uname() if path.is_windows then return "Windows" end local out = process.capture("uname -s 2>/dev/null") return (out or ""):match("([^\r\n]+)") or "Unix" end function process.read_time_v(pathname) if not pathname or not path.exists(pathname) then return {} end local text = path.read_file(pathname) local out = {} out.user_seconds = tonumber(text:match("User time %(seconds%):%s*([%d%.]+)")) out.system_seconds = tonumber(text:match("System time %(seconds%):%s*([%d%.]+)")) out.max_rss_kib = tonumber(text:match("Maximum resident set size %(kbytes%):%s*(%d+)")) out.elapsed = text:match("Elapsed %(wall clock%) time.-%):%s*([^\n]+)") out.elapsed_seconds = parse_elapsed_seconds(out.elapsed) if not out.max_rss_kib then local rss_bytes = tonumber(text:match("maximum resident set size:%s*(%d+)")) if rss_bytes then out.max_rss_kib = math.floor(rss_bytes / 1024) end end return out end process.parse_elapsed_seconds = parse_elapsed_seconds return process