]>
Dogcows Code - chaz/yoink/blob - build/utility.lua
3 -- A pseudo-random collection of useful Lua functions.
5 -- Copyright (c) 2011, Charles McGarvey
6 -- Distributable under the terms and conditions of the 2-clause BSD
7 -- license; see the file COPYING for a complete text of the license.
11 logfile
= "config.log"
15 -- Sometimes it is useful to iterate over an array with gaps or sections of
16 -- nil values, skipping over such empty sections. This function, like the
17 -- standard ipairs, creates an iterator, except the iterator will look n
18 -- indices past any nil key it finds. The argument n should be given as
19 -- the largest possible number of consecutive nils; it defaults to 4. This
20 -- function can typically serve as a drop-in replacement for ipairs.
21 local function npairs(t
, n
)
27 if v
~= nil then return i
, v
end
34 -- Makes a copy of an object. For most objects, this just returns the
35 -- object that was passed. For tables, a new table will be created and the
36 -- contents of the original object will either be assigned or copied over,
37 -- depending on the mode. The mode enables deep copies; it is a string
38 -- which may be empty or contain any combination of subsets of "k", "v",
39 -- and "m" for copying keys, values and metatables, respectively. The
40 -- default behavior is to deep copy only the values. The subcopy argument
41 -- is the function to be called when making subcopies during a deep copy.
42 -- It may also be a table with keys equal to one or more of the copy modes
43 -- and values set as the subcopy function to use for the type of object
44 -- represented by the copy mode character identifier.
45 local function copy(object
, mode
, subcopy
)
46 if type(object
) ~= "table" then return object
end
48 local copykey
= function(object
) return object
end
49 local copyvalue
, copymetatable
= copykey
, copykey
50 if type(subcopy
) == "table"
52 if mode
:find("k") and type(subcopy
.k
) == "function"
56 if mode
:find("v") and type(subcopy
.v
) == "function"
60 if mode
:find("m") and type(subcopy
.m
) == "function"
62 copymetatable
= subcopy
.m
65 if type(subcopy
) ~= "function" then subcopy
= copy
end
66 if mode
:find("k") then copykey
= subcopy
end
67 if mode
:find("v") then copyvalue
= subcopy
end
68 if mode
:find("m") then copymetatable
= subcopy
end
71 for key
,value
in pairs(object
) do
72 new
[copykey(key
, mode
)] = copyvalue(value
, mode
)
74 return setmetatable(new
, copymetatable(getmetatable(object
), mode
))
79 -- Like the standard table.concat function in all respects except values
80 -- are transformed by an optional function given; the function defaults to
81 -- tostring, so tables with objects for which the concatenate operator
82 -- cannot apply do not cause an error to be thrown.
83 local function concat(t
, s
, i
, j
, f
)
85 return table.concat(copy(t
, "v", f
), s
, i
, j
)
89 -- Escape the argument, preparing it for passage through a shell parse.
90 local function escape(str
)
91 return string.format("%q", tostring(str
))
95 -- Run the test command with the given arguments. If the test passes,
96 -- returns true; false if the test fails.
97 local function test(...)
98 return os
.execute(string.format("test %s", concat(arg
, " ", 1, #arg
, escape
))) == 0
103 -- Format a string with two pieces of text; the first bit of text is
104 -- printed as-is, and the next bit of text is printed left-justified with
105 -- an indent. The second bit of text is pushed to the right if the first
106 -- bit of text is too big.
107 local function align(text1
, text2
, indent
)
111 local numSpaces
= math
.max(indent
- #text1
- 1, 0)
112 for i
= 0, numSpaces
do text2
= " " .. text2
end
113 return text1
.. text2
118 -- Remove the whitespace surrounding a string.
119 local function trim(str
)
120 return (str
:gsub("^%s*(.-)%s*$", "%1"))
124 -- Trim the string and convert all sequences of whitespace to a single
126 local function compact(str
)
127 return trim(str
:gsub("%s+", " "))
132 -- Execute a command and return its output or nil if the command failed to
133 -- run. Use capture to specify which file descriptor to return. The exit
134 -- code of the command is also returned.
135 local function exec(command
, capture
)
136 capture
= capture
or ""
137 local tmpname = os
.tmpname()
138 local full
= string.format("%s %s>%q", command
, tostring(capture
), tmpname)
139 local code
= os
.execute(full
)
140 local fd
= io
.open(tmpname)
142 if fd
then output
= fd
:read("*a") fd
:close() end
146 local fd
= io
.open(M
.logfile
, "a")
147 fd
:write("\n# ", command
, "\n", output
, "# exit: ", code
, "\n")
150 return trim(output
), code
154 -- Try to execute a command and return true if the command finished
155 -- successfully (with an exit code of zero).
156 local function try_run(command
, dir
)
157 if type(dir
) == "string" then dir
= string.format("cd %q && ", dir
) else dir
= "" end
158 local output
, code
= exec(string.format("%s%s", dir
, command
))
163 -- Look for a command from a list that can complete successfully (with exit
164 -- code zero) given some arguments. Returns nil and an error string if
165 -- none were successful.
166 local function find_command(commands
, args
, dir
)
167 if type(commands
) ~= "table" then commands
= {commands
} end
168 if type(args
) ~= "string" then args
= "" end
169 local paths
= os
.getenv("PATH")
171 for _
,command
in npairs(commands
)
173 if command
:byte() == 0x2F
175 if test("-x", command
)
177 if try_run(command
.. " " .. args
, dir
) then return command
end
181 for path
in paths
:gmatch("([^:]*)")
183 local command
= path
.. "/" .. command
184 if test("-x", command
)
186 if try_run(command
.. " " .. args
, dir
) then return command
end
191 if found
then return nil, "command failed" end
192 return nil, "command not found"
195 M
.find_command
= find_command
198 local function make_tempdir(template
)
199 local dir
, code
= exec(string.format("mktemp -d ${TMPDIR:-/tmp}/%q", template
))
200 if code
== 0 then return dir
end
202 M
.make_tempdir
= make_tempdir
205 local function pkgfind(libs
, libdir
)
206 local cmd
= "pkg-config"
207 if type(libdir
) == "string" and libdir
~= ""
209 cmd
= string.format("PKG_CONFIG_PATH=%s/pkgconfig:$PKG_CONFIG_PATH %s", libdir
, cmd
)
211 local function invoke(package
) return exec(cmd
.. " " .. package
) end
212 local packages
, missing
= {}, {}
213 for _
,list
in ipairs(libs
:split("%S+"))
215 list
= list
:split("[^|]+")
217 for _
,package
in ipairs(list
)
219 local flags
,code
= invoke(package
)
222 table.insert(packages
, package
)
227 if not found
then table.insert(missing
, list
[1]) end
229 if #missing
== 0 then return concat(packages
, " ") end
230 return nil, "One or more required packages are missing: " .. concat(missing
, ", ") .. ". Please install them."
234 -- A wrapper function for a call out to pkg-config. The first argument is
235 -- a string signifying what kind of flags to get, either CFLAGS, LIBS, or
236 -- LDFLAGS. The second argument is the list of libraries. Finally, the
237 -- last argument can be given as a path to libdir; it is used to add a new
238 -- path to PKG_CONFIG_PATH when searching for "pc" files.
239 local function pkgconfig(what
, libs
, libdir
)
240 local cmd
= "pkg-config"
241 if type(libdir
) == "string" and libdir
~= ""
243 cmd
= string.format("PKG_CONFIG_PATH=%s/pkgconfig:$PKG_CONFIG_PATH %s", libdir
, cmd
)
245 if what
== "CFLAGS" or what
== "CXXFLAGS"
247 return exec(cmd
.. " --cflags " .. libs
)
248 elseif what
== "LIBS"
250 return exec(cmd
.. " --libs-only-l " .. libs
)
251 elseif what
== "LDFLAGS"
253 local output
, code
= exec(cmd
.. " --libs " .. libs
)
254 output
= output
:gsub("%s%-l%S*", ""):gsub("^%-l%S*%s*", "")
257 return exec(cmd
.. " " .. libs
)
259 M
.pkgconfig
= pkgconfig
262 -- Escape special characters of a pattern string.
263 local function escapePattern(str
)
264 str
= str
:gsub("%%", "%%%%")
265 str
= str
:gsub("%^", "%%^")
266 str
= str
:gsub("%$", "%%$")
267 str
= str
:gsub("%(", "%%(")
268 str
= str
:gsub("%)", "%%)")
269 str
= str
:gsub("%.", "%%.")
270 str
= str
:gsub("%[", "%%[")
271 str
= str
:gsub("%]", "%%[")
272 str
= str
:gsub("%*", "%%*")
273 str
= str
:gsub("%+", "%%+")
274 str
= str
:gsub("%-", "%%-")
275 str
= str
:gsub("%?", "%%?")
279 -- A basic split function for strings. Give a pattern of a sequence which
280 -- should be matched and returns a table of matches.
281 function string:split(pattern
)
283 self
:gsub(pattern
, function(seq
) table.insert(t
, seq
) end)
287 -- Truncates a string to a certain length, appending an ellipsis if any
288 -- part of the string had to be chopped.
289 function string:truncate(length
)
290 if length
< #self
then return self
:sub(1, length
- 3) .. "..." end
295 -- Append a word (i.e. flag) to the end of the string.
296 function string:appendFlag(...)
297 for _
,flag
in ipairs(arg
)
299 if self
== "" then self
= flag
300 else self
= string.format("%s %s", self
, flag
) end
305 -- Set the flag by appending it to the end of the string unless it already
306 -- exists somewhere else in the string. Note: The command line isn't
307 -- parsed; a simple search is used to determined if a flag is set.
308 function string:setFlag(...)
309 for _
,flag
in ipairs(arg
)
311 local escaped
= escapePattern(flag
)
312 if not self
:match(escaped
) then self
= self
:appendFlag(flag
) end
317 -- Remove all matching flags from a string. Note: The command line isn't
318 -- parsed; a simple search and replace is used to determine where a flag is
320 function string:unsetFlag(...)
321 for _
,flag
in ipairs(arg
)
323 flag
= escapePattern(flag
)
324 self
= self
:gsub("^"..flag
.."$", "")
325 self
= self
:gsub("^"..flag
.."%s", "")
326 self
= self
:gsub("%s"..flag
.."$", "")
327 self
= self
:gsub("%s"..flag
, "")
332 -- Replace all matching flags from a string with a new flag. The old flag
333 -- is parsed as a pattern, just like a substitution. Each flag matching
334 -- the pattern is replaced by the new flag, but if no replacements are
335 -- made, the new flag is appended to the string.
336 function string:replaceFlag(old
, new
)
338 self
, count
= self
:gsub(old
, new
)
339 if count
< 1 then self
= self
:appendFlag(new
) end
345 local colorCount
= exec("tput colors")
346 local isTTY
= test("-t", 1)
348 -- The logging facility is exported by way of this printer generator.
349 -- Pass a format and attribute string and get a function to print to
350 -- stdout. Attributes will only be used if stdout is a terminal with
351 -- at least eight colors. All arguments are optional; the default
352 -- format mimics the return print function regarding only the first
354 local function printer(format, attrib
)
355 format = tostring(format or "%s\n")
356 if type(attrib
) == "string" and isTTY
and 8 <= tonumber(colorCount
)
359 io
.write(string.format("\e[%sm%s\e[0m", attrib
, string.format(format, ...)))
363 io
.write(string.format(format, ...))
371 -- Symbolize a name; that is, convert a string such that it would make a
372 -- good filename. The string is converted to lower case and series of
373 -- non-alphanumeric characters are converted to hyphens.
374 local function symbolize(name
)
375 return name
:lower():gsub("%W+", "-")
377 M
.symbolize
= symbolize
379 -- Take a standard version number with three parts and separate the parts
380 -- into major, minor, and revision components.
381 local function splitVersion(version
)
382 local vmajor
, vminor
, vrevis
= version
:match("^(%d*)%.?(%d*)%.?(%d*)")
383 vmajor
= tonumber(vmajor
) or 0
384 vminor
= tonumber(vminor
) or 0
385 vrevis
= tonumber(vrevis
) or 0
386 return vmajor
, vminor
, vrevis
388 M
.splitVersion
= splitVersion
391 -- Create a new class type. Pass one or more parent classes for
393 local function class(...)
394 local c
= {__init
= function() end}
396 for _
,super
in ipairs(arg
)
398 for key
,value
in pairs(super
) do c
[key
] = value
end
399 table.insert(s
, super
)
404 function m
:__call(...)
405 local o
= setmetatable({}, c
) o
:__init(...) return o
408 local function recurse(a
, b
)
409 if a
== b
then return true end
410 for _
,super
in ipairs(a
.__super
)
412 if recurse(super
, b
) then return true end
416 return recurse(getmetatable(self
), c
)
418 return setmetatable(c
, m
)
This page took 0.046966 seconds and 4 git commands to generate.