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