1--
2-- premake.lua
3-- High-level helper functions for the project exporters.
4-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
5--
6
7	local p = premake
8
9
10
11-- Store captured output text for later testing
12
13	local _captured
14
15-- The string escaping function.
16
17	local _esc = function(v) return v end
18
19-- The output settings and defaults
20
21	local _eol = "\n"
22	local _indentString = "\t"
23	local _indentLevel = 0
24
25-- Set up the global configuration scope. There can be only one.
26
27	global("root")
28
29
30
31---
32-- Capture and store everything sent through the output stream functions
33-- premake.w(), premake.x(), and premake.out(). Retrieve the captured
34-- text using the premake.captured() function.
35--
36-- @param fn
37--    A function to execute. Any output calls made during the execution
38--    of the function will be captured.
39-- @return
40--    The captured output.
41---
42
43	function premake.capture(fn)
44		-- start a new capture without forgetting the old one
45		local old = _captured
46		_captured = buffered.new()
47
48		-- capture
49		fn()
50
51		-- build the result
52		local captured = p.captured()
53
54		-- free the capture buffer.
55		buffered.close(_captured)
56
57		-- restore the old capture and done
58		_captured = old
59		return captured
60	end
61
62
63
64--
65-- Returns the captured text and stops capturing.
66--
67
68	function premake.captured()
69		if _captured then
70			return buffered.tostring(_captured)
71		else
72			return ""
73		end
74	end
75
76
77
78---
79-- Set the output stream end-of-line sequence.
80--
81-- @param s
82--    The string to use to mark line ends, or nil to keep the existing
83--    EOL sequence.
84-- @return
85--    The new EOL sequence.
86---
87
88	function premake.eol(s)
89		_eol = s or _eol
90		return _eol
91	end
92
93
94
95---
96-- Handle escaping of strings for various outputs.
97--
98-- @param value
99--    If this is a string: escape it and return the new value. If it is an
100--    array, return a new array of escaped values.
101-- @return
102--    If the input was a single string, returns the escaped version. If it
103--    was an array, returns an corresponding array of escaped strings.
104---
105
106	function premake.esc(value)
107		if type(value) == "table" then
108			local result = {}
109			local n = #value
110			for i = 1, n do
111				table.insert(result, p.esc(value[i]))
112			end
113			return result
114		end
115
116		return _esc(value or "")
117	end
118
119
120
121---
122-- Set a new string escaping function.
123--
124-- @param func
125--    The new escaping function, which should take a single string argument
126--    and return the escaped version of that string. If nil, uses a default
127--    no-op function.
128---
129
130	function premake.escaper(func)
131		_esc = func
132		if not _esc then
133			_esc = function (value) return value end
134		end
135	end
136
137
138
139--
140-- Returns a boolean if the file was modified
141-- Open a file for output, and call a function to actually do the writing.
142-- Used by the actions to generate workspace and project files.
143--
144-- @param obj
145--    A workspace or project object; will be passed to the callback function.
146-- @param ext
147--    An optional extension for the generated file, with the leading dot.
148-- @param callback
149--    The function responsible for writing the file, should take a workspace
150--    or project as a parameters.
151--
152
153	function premake.generate(obj, ext, callback)
154		local output = p.capture(function ()
155			_indentLevel = 0
156			callback(obj)
157			_indentLevel = 0
158		end)
159
160		local fn = p.filename(obj, ext)
161
162		-- make sure output folder exists.
163		local dir = path.getdirectory(fn)
164		local ok, err = os.mkdir(dir)
165		if not ok then
166			error(err, 0)
167		end
168
169		local f, err = os.writefile_ifnotequal(output, fn);
170
171		if (f == 0) then
172			return false -- file not modified
173		elseif (f < 0) then
174			error(err, 0)
175		elseif (f > 0) then
176			printf("Generated %s...", path.getrelative(os.getcwd(), fn))
177			return true -- file modified
178		end
179	end
180
181
182
183--
184-- Marks a file as modified without changing its contents
185--
186-- @param obj
187--    A workspace or project object; will be passed to the callback function.
188-- @param ext
189--    An optional extension for the generated file, with the leading dot.
190--
191
192	function premake.touch(obj, ext)
193		local fn = premake.filename(obj, ext)
194
195		-- make sure output folder exists.
196		local dir = path.getdirectory(fn)
197		local ok, err = os.mkdir(dir)
198		if not ok then
199			error(err, 0)
200		end
201
202		local f, err = os.touchfile(fn);
203
204		if (f == 0) then
205			return false -- file marked as modified
206		elseif (f < 0) then
207			error(err, 0)
208		elseif (f > 0) then
209			return true -- file created
210		end
211	end
212
213
214---
215-- Returns the full path a file generated from any of the project
216-- objects (project, workspace, rule).
217--
218-- @param obj
219--    The project object being generated.
220-- @param ext
221--    An optional extension for the generated file, with the leading dot.
222---
223
224function premake.filename(obj, ext)
225	local fname = obj.location or obj.basedir
226	if ext and not ext:startswith(".") then
227		fname = path.join(fname, ext)
228	else
229		fname = path.join(fname, obj.filename)
230		if ext then
231			fname = fname .. ext
232		end
233	end
234	return path.getabsolute(fname)
235end
236
237
238
239---
240-- Sets the output indentation parameters.
241--
242-- @param s
243--    The indentation string.
244-- @param i
245--    The new indentation level, or nil to reset to zero.
246---
247
248	function premake.indent(s, i)
249		_indentString = s or "\t"
250		_indentLevel = i or 0
251	end
252
253
254
255---
256-- Write a simple, unformatted string to the output stream, with no indentation
257-- or end of line sequence.
258---
259
260	function premake.out(s)
261		if not _captured then
262			io.write(s)
263		else
264			buffered.write(_captured, s)
265		end
266	end
267
268
269
270---
271-- Write a simple, unformatted string to the output stream, with no indentation,
272-- and append the current EOL sequence.
273---
274
275	function premake.outln(s)
276		p.out(s)
277		p.out(_eol or "\n")
278	end
279
280
281
282---
283-- Write a formatted string to the exported file, after decreasing the
284-- indentation level by one.
285--
286-- @param i
287--    If set to a number, the indentation level will be decreased by
288--    this amount. If nil, the indentation level is decremented and
289--    no output is written. Otherwise, pass to premake.w() as the
290--    formatting string, followed by any additional arguments.
291---
292
293	function premake.pop(i, ...)
294		if i == nil or type(i) == "number" then
295			_indentLevel = _indentLevel - (i or 1)
296		else
297			_indentLevel = _indentLevel - 1
298			p.w(i, ...)
299		end
300	end
301
302
303
304---
305-- Write a formatted string to the exported file, and increase the
306-- indentation level by one.
307--
308-- @param i
309--    If set to a number, the indentation level will be increased by
310--    this amount. If nil, the indentation level is incremented and
311--    no output is written. Otherwise, pass to premake.w() as the
312--    formatting string, followed by any additional arguments.
313---
314
315	function premake.push(i, ...)
316		if i == nil or type(i) == "number" then
317			_indentLevel = _indentLevel + (i or 1)
318		else
319			p.w(i, ...)
320			_indentLevel = _indentLevel + 1
321		end
322	end
323
324
325
326---
327-- Wrap the provided value in double quotes if it contains spaces, or
328-- if it contains a shell variable of the form $(...).
329---
330
331	function premake.quoted(value)
332		local q = value:find(" ", 1, true)
333		if not q then
334			q = value:find("$%(.-%)", 1)
335		end
336		if q then
337			value = '"' .. value .. '"'
338		end
339		return value
340	end
341
342
343
344--
345-- Output a UTF-8 BOM to the exported file.
346--
347
348	function p.utf8()
349		p.out('\239\187\191')
350	end
351
352
353
354---
355-- Write a formatted string to the exported file, at the current
356-- level of indentation, and appends an end of line sequence.
357-- This gets called quite a lot, hence the very short name.
358---
359
360	function premake.w(...)
361		if select("#", ...) > 0 then
362			p.outln(string.rep(_indentString or "\t", _indentLevel) .. string.format(...))
363		else
364			p.outln('');
365		end
366	end
367
368
369
370---
371-- Write a formatted string to the exported file, after passing all
372-- arguments (except for the first, which is the formatting string)
373-- through premake.esc().
374---
375
376	function premake.x(msg, ...)
377		local arg = {...}
378		for i = 1, #arg do
379			arg[i] = p.esc(arg[i])
380		end
381		p.w(msg, table.unpack(arg))
382	end
383
384
385
386---
387-- Write a opening XML element for a UTF-8 encoded file. Used by
388-- several different files for different actions, so makes sense
389-- to have a common call for it.
390--
391-- @param upper
392--    If true, the encoding is written in uppercase.
393---
394
395	function premake.xmlUtf8(upper)
396		local encoding = iif(upper, "UTF-8", "utf-8")
397		p.w('<?xml version="1.0" encoding="%s"?>', encoding)
398	end
399
400
401
402--
403-- These are the output shortcuts that I used before switching to the
404-- indentation-aware calls above. They are still in use all over the
405-- place, including lots of community code, so let's keep them around.
406--
407-- @param i
408--    This will either be a printf-style formatting string suitable
409--    for passing to string.format(), OR an integer number indicating
410--    the desired level of indentation. If the latter, the formatting
411--    string should be the next argument in the list.
412-- @param ...
413--    The values necessary to fill out the formatting string tokens.
414--
415
416	function _p(i, ...)
417		if type(i) == "number" then
418			_indentLevel = i
419			p.w(...)
420		else
421			_indentLevel = 0
422			p.w(i, ...)
423		end
424	end
425
426	function _x(i, ...)
427		local arg = {...}
428		for i = 2, #arg do
429			arg[i] = p.esc(arg[i])
430		end
431		_p(i, table.unpack(arg))
432	end
433