Add a "deep" option to awful.util.table.clone
[awesome.git] / lib / awful / util.lua.in
blob4c6424d8ec9002ecb383f8369ca6396d2920e2bb
1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local os = os
9 local io = io
10 local assert = assert
11 local load = loadstring or load -- v5.1 - loadstring, v5.2 - load
12 local loadfile = loadfile
13 local debug = debug
14 local pairs = pairs
15 local ipairs = ipairs
16 local type = type
17 local rtable = table
18 local pairs = pairs
19 local string = string
20 local capi =
22 awesome = awesome,
23 mouse = mouse
26 --- Utility module for awful
27 -- awful.util
28 local util = {}
29 util.table = {}
31 util.shell = os.getenv("SHELL") or "/bin/sh"
33 function util.deprecate(see)
34 io.stderr:write("W: awful: function is deprecated")
35 if see then
36 io.stderr:write(", see " .. see)
37 end
38 io.stderr:write("\n")
39 io.stderr:write(debug.traceback())
40 end
42 --- Strip alpha part of color.
43 -- @param color The color.
44 -- @return The color without alpha channel.
45 function util.color_strip_alpha(color)
46 if color:len() == 9 then
47 color = color:sub(1, 7)
48 end
49 return color
50 end
52 --- Make i cycle.
53 -- @param t A length. Must be greater than zero.
54 -- @param i An absolute index to fit into #t.
55 -- @return An integer in (1, t) or nil if t is less than or equal to zero.
56 function util.cycle(t, i)
57 if t < 1 then return end
58 i = i % t
59 if i == 0 then
60 i = t
61 end
62 return i
63 end
65 --- Create a directory
66 -- @param dir The directory.
67 -- @return mkdir return code
68 function util.mkdir(dir)
69 return os.execute("mkdir -p " .. dir)
70 end
72 --- Spawn a program.
73 -- @param cmd The command.
74 -- @param sn Enable startup-notification.
75 -- @return The awesome.spawn return value.
76 function util.spawn(cmd, sn)
77 if cmd and cmd ~= "" then
78 if sn == nil then sn = true end
79 return capi.awesome.spawn(cmd, sn)
80 end
81 end
83 --- Spawn a program using the shell.
84 -- @param cmd The command.
85 function util.spawn_with_shell(cmd)
86 if cmd and cmd ~= "" then
87 cmd = { util.shell, "-c", cmd }
88 return capi.awesome.spawn(cmd, false)
89 end
90 end
92 --- Read a program output and returns its output as a string.
93 -- @param cmd The command to run.
94 -- @return A string with the program output, or the error if one occured.
95 function util.pread(cmd)
96 if cmd and cmd ~= "" then
97 local f, err = io.popen(cmd, 'r')
98 if f then
99 local s = f:read("*all")
100 f:close()
101 return s
102 else
103 return err
108 --- Eval Lua code.
109 -- @return The return value of Lua code.
110 function util.eval(s)
111 return assert(load(s))()
114 local xml_entity_names = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
115 --- Escape a string from XML char.
116 -- Useful to set raw text in textbox.
117 -- @param text Text to escape.
118 -- @return Escape text.
119 function util.escape(text)
120 return text and text:gsub("['&<>\"]", xml_entity_names) or nil
123 local xml_entity_chars = { lt = "<", gt = ">", nbsp = " ", quot = "\"", apos = "'", ndash = "-", mdash = "-", amp = "&" };
124 --- Unescape a string from entities.
125 -- @param text Text to unescape.
126 -- @return Unescaped text.
127 function util.unescape(text)
128 return text and text:gsub("&(%a+);", xml_entity_chars) or nil
131 --- Check if a file is a Lua valid file.
132 -- This is done by loading the content and compiling it with loadfile().
133 -- @param path The file path.
134 -- @return A function if everything is alright, a string with the error
135 -- otherwise.
136 function util.checkfile(path)
137 local f, e = loadfile(path)
138 -- Return function if function, otherwise return error.
139 if f then return f end
140 return e
143 --- Try to restart awesome.
144 -- It checks if the configuration file is valid, and then restart if it's ok.
145 -- If it's not ok, the error will be returned.
146 -- @return Never return if awesome restart, or return a string error.
147 function util.restart()
148 local c = util.checkfile(capi.awesome.conffile)
150 if type(c) ~= "function" then
151 return c
154 capi.awesome.restart()
157 --- Get the user's config or cache dir.
158 -- It first checks XDG_CONFIG_HOME / XDG_CACHE_HOME, but then goes with the
159 -- default paths.
160 -- @param d The directory to get (either "config" or "cache").
161 -- @return A string containing the requested path.
162 function util.getdir(d)
163 if d == "config" then
164 local dir = os.getenv("XDG_CONFIG_HOME")
165 if dir then
166 return dir .. "/awesome"
168 return os.getenv("HOME") .. "/.config/awesome"
169 elseif d == "cache" then
170 local dir = os.getenv("XDG_CACHE_HOME")
171 if dir then
172 return dir .. "/awesome"
174 return os.getenv("HOME").."/.cache/awesome"
178 --- Search for an icon and return the full path.
179 -- It searches for the icon path under the directories given w/the right ext
180 -- @param iconname The name of the icon to search for.
181 -- @param exts Table of image extensions allowed, otherwise { 'png', gif' }
182 -- @param dirs Table of dirs to search, otherwise { '/usr/share/pixmaps/' }
183 -- @param size Optional size. If this is specified, subdirectories <size>x<size>
184 -- of the dirs are searched first
185 function util.geticonpath(iconname, exts, dirs, size)
186 exts = exts or { 'png', 'gif' }
187 dirs = dirs or { '/usr/share/pixmaps/' }
188 for _, d in pairs(dirs) do
189 for _, e in pairs(exts) do
190 local icon
191 if size then
192 icon = string.format("%s%ux%u/%s.%s",
193 d, size, size, iconname, e)
194 if util.file_readable(icon) then
195 return icon
198 icon = d .. iconname .. '.' .. e
199 if util.file_readable(icon) then
200 return icon
206 --- Check if file exists and is readable.
207 -- @param filename The file path
208 -- @return True if file exists and readable.
209 function util.file_readable(filename)
210 local file = io.open(filename)
211 if file then
212 io.close(file)
213 return true
215 return false
218 local function subset_mask_apply(mask, set)
219 local ret = {}
220 for i = 1, #set do
221 if mask[i] then
222 rtable.insert(ret, set[i])
225 return ret
228 local function subset_next(mask)
229 local i = 1
230 while i <= #mask and mask[i] do
231 mask[i] = false
232 i = i + 1
235 if i <= #mask then
236 mask[i] = 1
237 return true
239 return false
242 --- Return all subsets of a specific set.
243 -- This function, giving a set, will return all subset it.
244 -- For example, if we consider a set with value { 10, 15, 34 },
245 -- it will return a table containing 2^n set:
246 -- { }, { 10 }, { 15 }, { 34 }, { 10, 15 }, { 10, 34 }, etc.
247 -- @param set A set.
248 -- @return A table with all subset.
249 function util.subsets(set)
250 local mask = {}
251 local ret = {}
252 for i = 1, #set do mask[i] = false end
254 -- Insert the empty one
255 rtable.insert(ret, {})
257 while subset_next(mask) do
258 rtable.insert(ret, subset_mask_apply(mask, set))
260 return ret
263 -- Return true whether rectangle B is in the right direction
264 -- compared to rectangle A.
265 -- @param dir The direction.
266 -- @param gA The geometric specification for rectangle A.
267 -- @param gB The geometric specification for rectangle B.
268 -- @return True if B is in the direction of A.
269 local function is_in_direction(dir, gA, gB)
270 if dir == "up" then
271 return gA.y > gB.y
272 elseif dir == "down" then
273 return gA.y < gB.y
274 elseif dir == "left" then
275 return gA.x > gB.x
276 elseif dir == "right" then
277 return gA.x < gB.x
279 return false
282 -- Calculate distance between two points.
283 -- i.e: if we want to move to the right, we will take the right border
284 -- of the currently focused screen and the left side of the checked screen.
285 -- @param dir The direction.
286 -- @param gA The first rectangle.
287 -- @param gB The second rectangle.
288 -- @return The distance between the screens.
289 local function calculate_distance(dir, _gA, _gB)
290 local gAx = _gA.x
291 local gAy = _gA.y
292 local gBx = _gB.x
293 local gBy = _gB.y
295 if dir == "up" then
296 gBy = _gB.y + _gB.height
297 elseif dir == "down" then
298 gAy = _gA.y + _gA.height
299 elseif dir == "left" then
300 gBx = _gB.x + _gB.width
301 elseif dir == "right" then
302 gAx = _gA.x + _gA.width
305 return math.sqrt(math.pow(gBx - gAx, 2) + math.pow(gBy - gAy, 2))
308 -- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
309 -- with 'x', 'y', 'width', 'height' keys, the same as client or screen geometries.
310 -- @param dir The direction, can be either "up", "down", "left" or "right".
311 -- @param recttbl A table of rectangle specifications.
312 -- @param cur The current rectangle.
313 -- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
314 function util.get_rectangle_in_direction(dir, recttbl, cur)
315 local dist, dist_min
316 local target = nil
318 -- We check each object
319 for i, rect in ipairs(recttbl) do
320 -- Check geometry to see if object is located in the right direction.
321 if is_in_direction(dir, cur, rect) then
322 -- Calculate distance between current and checked object.
323 dist = calculate_distance(dir, cur, rect)
325 -- If distance is shorter then keep the object.
326 if not target or dist < dist_min then
327 target = i
328 dist_min = dist
332 return target
335 --- Join all tables given as parameters.
336 -- This will iterate all tables and insert all their keys into a new table.
337 -- @param args A list of tables to join
338 -- @return A new table containing all keys from the arguments.
339 function util.table.join(...)
340 local ret = {}
341 for i, t in pairs({...}) do
342 if t then
343 for k, v in pairs(t) do
344 if type(k) == "number" then
345 rtable.insert(ret, v)
346 else
347 ret[k] = v
352 return ret
355 --- Check if a table has an item and return its key.
356 -- @param t The table.
357 -- @param item The item to look for in values of the table.
358 -- @return The key were the item is found, or nil if not found.
359 function util.table.hasitem(t, item)
360 for k, v in pairs(t) do
361 if v == item then
362 return k
367 --- Split a string into multiple lines
368 -- @param text String to wrap.
369 -- @param width Maximum length of each line. Default: 72.
370 -- @param indent Number of spaces added before each wrapped line. Default: 0.
371 -- @return The string with lines wrapped to width.
372 function util.linewrap(text, width, indent)
373 local text = text or ""
374 local width = width or 72
375 local indent = indent or 0
377 local pos = 1
378 return text:gsub("(%s+)()(%S+)()",
379 function(sp, st, word, fi)
380 if fi - pos > width then
381 pos = st
382 return "\n" .. string.rep(" ", indent) .. word
384 end)
387 --- Get a sorted table with all integer keys from a table
388 -- @param t the table for which the keys to get
389 -- @return A table with keys
390 function util.table.keys(t)
391 local keys = { }
392 for k, _ in pairs(t) do
393 rtable.insert(keys, k)
395 rtable.sort(keys, function (a, b)
396 return type(a) == type(b) and a < b or false
397 end)
398 return keys
401 --- Filter a tables keys for certain content types
402 -- @param t The table to retrieve the keys for
403 -- @param ... the types to look for
404 -- @return A filtered table with keys
405 function util.table.keys_filter(t, ...)
406 local keys = util.table.keys(t)
407 local keys_filtered = { }
408 for _, k in pairs(keys) do
409 for _, et in pairs({...}) do
410 if type(t[k]) == et then
411 rtable.insert(keys_filtered, k)
412 break
416 return keys_filtered
419 --- Reverse a table
420 -- @param t the table to reverse
421 -- @return the reversed table
422 function util.table.reverse(t)
423 local tr = { }
424 -- reverse all elements with integer keys
425 for _, v in ipairs(t) do
426 rtable.insert(tr, 1, v)
428 -- add the remaining elements
429 for k, v in pairs(t) do
430 if type(k) ~= "number" then
431 tr[k] = v
434 return tr
437 --- Clone a table
438 -- @param t the table to clone
439 -- @param deep Create a deep clone? (default: true)
440 -- @return a clone of t
441 function util.table.clone(t, deep)
442 local deep = deep == nil and true or deep
443 local c = { }
444 for k, v in pairs(t) do
445 if deep and type(v) == "table" then
446 c[k] = util.table.clone(v)
447 else
448 c[k] = v
451 return c
455 -- Returns an iterator to cycle through, starting from the first element or the
456 -- given index, all elments of a table that match a given criteria.
458 -- @param t the table to iterate
459 -- @param filter a function that returns true to indicate a positive match
460 -- @param start what index to start iterating from. Default is 1 (=> start of
461 -- the table)
462 function util.table.iterate(t, filter, start)
463 local count = 0
464 local index = start or 1
465 local length = #t
467 return function ()
468 while count < length do
469 local item = t[index]
470 index = util.cycle(#t, index + 1)
471 count = count + 1
472 if filter(item) then return item end
477 return util
479 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80