1-- 2-- path.lua 3-- Path manipulation functions. 4-- Copyright (c) 2002-2010 Jason Perkins and the Premake project 5-- 6 7 8-- 9-- Retrieve the filename portion of a path, without any extension. 10-- 11 12 function path.getbasename(p) 13 local name = path.getname(p) 14 local i = name:findlast(".", true) 15 if (i) then 16 return name:sub(1, i - 1) 17 else 18 return name 19 end 20 end 21 22-- 23-- Retrieve the path, without any extension. 24-- 25 26 function path.removeext(name) 27 local i = name:findlast(".", true) 28 if (i) then 29 return name:sub(1, i - 1) 30 else 31 return name 32 end 33 end 34 35-- 36-- Retrieve the directory portion of a path, or an empty string if 37-- the path does not include a directory. 38-- 39 40 function path.getdirectory(p) 41 local i = p:findlast("/", true) 42 if (i) then 43 if i > 1 then i = i - 1 end 44 return p:sub(1, i) 45 else 46 return "." 47 end 48 end 49 50 51-- 52-- Retrieve the drive letter, if a Windows path. 53-- 54 55 function path.getdrive(p) 56 local ch1 = p:sub(1,1) 57 local ch2 = p:sub(2,2) 58 if ch2 == ":" then 59 return ch1 60 end 61 end 62 63 64 65-- 66-- Retrieve the file extension. 67-- 68 69 function path.getextension(p) 70 local i = p:findlast(".", true) 71 if (i) then 72 return p:sub(i) 73 else 74 return "" 75 end 76 end 77 78 79 80-- 81-- Retrieve the filename portion of a path. 82-- 83 84 function path.getname(p) 85 local i = p:findlast("[/\\]") 86 if (i) then 87 return p:sub(i + 1) 88 else 89 return p 90 end 91 end 92 93 94 95-- 96-- Returns the common base directory of two paths. 97-- 98 99 function path.getcommonbasedir(a, b) 100 a = path.getdirectory(a)..'/' 101 b = path.getdirectory(b)..'/' 102 103 -- find the common leading directories 104 local idx = 0 105 while (true) do 106 local tst = a:find('/', idx + 1, true) 107 if tst then 108 if a:sub(1,tst) == b:sub(1,tst) then 109 idx = tst 110 else 111 break 112 end 113 else 114 break 115 end 116 end 117 -- idx is the index of the last sep before path string 'a' ran out or didn't match. 118 local result = '' 119 if idx > 1 then 120 result = a:sub(1, idx - 1) -- Remove the trailing slash to be consistent with other functions. 121 end 122 return result 123 end 124 125 126-- 127-- Returns true if the filename has a particular extension. 128-- 129-- @param fname 130-- The file name to test. 131-- @param extensions 132-- The extension(s) to test. Maybe be a string or table. 133-- 134 135 function path.hasextension(fname, extensions) 136 local fext = path.getextension(fname):lower() 137 if type(extensions) == "table" then 138 for _, extension in pairs(extensions) do 139 if fext == extension then 140 return true 141 end 142 end 143 return false 144 else 145 return (fext == extensions) 146 end 147 end 148 149-- 150-- Returns true if the filename represents a C/C++ source code file. This check 151-- is used to prevent passing non-code files to the compiler in makefiles. It is 152-- not foolproof, but it has held up well. I'm open to better suggestions. 153-- 154 155 function path.iscfile(fname) 156 return path.hasextension(fname, { ".c", ".m" }) 157 end 158 159 function path.iscppfile(fname) 160 return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c", ".m", ".mm" }) 161 end 162 163 function path.iscxfile(fname) 164 return path.hasextension(fname, ".cx") 165 end 166 167 function path.isobjcfile(fname) 168 return path.hasextension(fname, { ".m", ".mm" }) 169 end 170 171 function path.iscppheader(fname) 172 return path.hasextension(fname, { ".h", ".hh", ".hpp", ".hxx" }) 173 end 174 175 function path.isappxmanifest(fname) 176 return path.hasextension(fname, ".appxmanifest") 177 end 178 179 function path.isandroidbuildfile(fname) 180 return path.getname(fname) == "AndroidManifest.xml" 181 end 182 183 function path.isnatvis(fname) 184 return path.hasextension(fname, ".natvis") 185 end 186 187 function path.isasmfile(fname) 188 return path.hasextension(fname, { ".asm", ".s", ".S" }) 189 end 190 191 function path.isvalafile(fname) 192 return path.hasextension(fname, ".vala") 193 end 194 195 function path.isswiftfile(fname) 196 return path.hasextension(fname, ".swift") 197 end 198 199 function path.issourcefile(fname) 200 return path.iscfile(fname) 201 or path.iscppfile(fname) 202 or path.iscxfile(fname) 203 or path.isasmfile(fname) 204 or path.isvalafile(fname) 205 or path.isswiftfile(fname) 206 end 207 208 function path.issourcefilevs(fname) 209 return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c" }) 210 or path.iscxfile(fname) 211 end 212 213-- 214-- Returns true if the filename represents a compiled object file. This check 215-- is used to support object files in the "files" list for archiving. 216-- 217 218 function path.isobjectfile(fname) 219 return path.hasextension(fname, { ".o", ".obj" }) 220 end 221 222-- 223-- Returns true if the filename represents a Windows resource file. This check 224-- is used to prevent passing non-resources to the compiler in makefiles. 225-- 226 227 function path.isresourcefile(fname) 228 return path.hasextension(fname, ".rc") 229 end 230 231 232-- 233-- Returns true if the filename represents a Windows image file. 234-- 235 236 function path.isimagefile(fname) 237 local extensions = { ".png" } 238 local ext = path.getextension(fname):lower() 239 return table.contains(extensions, ext) 240 end 241 242-- 243-- Join one or more pieces of a path together into a single path. 244-- 245-- @param ... 246-- One or more path strings. 247-- @return 248-- The joined path. 249-- 250 251 function path.join(...) 252 local arg={...} 253 local numargs = select("#", ...) 254 if numargs == 0 then 255 return ""; 256 end 257 258 local allparts = {} 259 for i = numargs, 1, -1 do 260 local part = select(i, ...) 261 if part and #part > 0 and part ~= "." then 262 -- trim off trailing slashes 263 while part:endswith("/") do 264 part = part:sub(1, -2) 265 end 266 267 table.insert(allparts, 1, part) 268 if path.isabsolute(part) then 269 break 270 end 271 end 272 end 273 274 return table.concat(allparts, "/") 275 end 276 277 278-- 279-- Takes a path which is relative to one location and makes it relative 280-- to another location instead. 281-- 282 283 function path.rebase(p, oldbase, newbase) 284 p = path.getabsolute(path.join(oldbase, p)) 285 p = path.getrelative(newbase, p) 286 return p 287 end 288 289 290-- 291-- Convert the separators in a path from one form to another. If `sep` 292-- is nil, then a platform-specific separator is used. 293-- 294 295 function path.translate(p, sep) 296 if (type(p) == "table") then 297 local result = { } 298 for _, value in ipairs(p) do 299 table.insert(result, path.translate(value)) 300 end 301 return result 302 else 303 if (not sep) then 304 if (os.is("windows")) then 305 sep = "\\" 306 else 307 sep = "/" 308 end 309 end 310 local result = p:gsub("[/\\]", sep) 311 return result 312 end 313 end 314 315 316-- 317-- Converts from a simple wildcard syntax, where * is "match any" 318-- and ** is "match recursive", to the corresponding Lua pattern. 319-- 320-- @param pattern 321-- The wildcard pattern to convert. 322-- @returns 323-- The corresponding Lua pattern. 324-- 325 326 function path.wildcards(pattern) 327 -- Escape characters that have special meanings in Lua patterns 328 pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1") 329 330 -- Replace wildcard patterns with special placeholders so I don't 331 -- have competing star replacements to worry about 332 pattern = pattern:gsub("%*%*", "\001") 333 pattern = pattern:gsub("%*", "\002") 334 335 -- Replace the placeholders with their Lua patterns 336 pattern = pattern:gsub("\001", ".*") 337 pattern = pattern:gsub("\002", "[^/]*") 338 339 return pattern 340 end 341 342-- 343-- remove any dot ("./", "../") patterns from the start of the path 344-- 345 function path.trimdots(p) 346 local changed 347 repeat 348 changed = true 349 if p:startswith("./") then 350 p = p:sub(3) 351 elseif p:startswith("../") then 352 p = p:sub(4) 353 else 354 changed = false 355 end 356 until not changed 357 358 return p 359 end 360 361-- 362-- Takes a path which is relative to one location and makes it relative 363-- to another location instead. 364-- 365 366 function path.rebase(p, oldbase, newbase) 367 p = path.getabsolute(path.join(oldbase, p)) 368 p = path.getrelative(newbase, p) 369 return p 370 end 371 372 373 374-- 375-- Replace the file extension. 376-- 377 378 function path.replaceextension(p, newext) 379 local ext = path.getextension(p) 380 381 if not ext then 382 return p 383 end 384 385 if #newext > 0 and not newext:findlast(".", true) then 386 newext = "."..newext 387 end 388 389 return p:match("^(.*)"..ext.."$")..newext 390 end 391