File: util.lua

package info (click to toggle)
luakit 2012.09.13-r1-8
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 1,160 kB
  • ctags: 1,276
  • sloc: ansic: 6,086; makefile: 153; ruby: 79; sh: 38
file content (377 lines) | stat: -rw-r--r-- 11,238 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
---------------------------------------------------------------------------
-- @author Mason Larobina <mason.larobina@gmail.com>
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2010 Mason Larobina, 2008 Julien Danjou
---------------------------------------------------------------------------

--- Grab environment we need
local assert = assert
local print = print
local debug = debug
local error = error
local setmetatable = setmetatable
local getmetatable = getmetatable
local io = io
local ipairs = ipairs
local loadstring = loadstring
local os = os
local pairs = pairs
local rstring = string
local rtable = table
local type = type
local tonumber = tonumber
local tostring = tostring
local math = require "math"
local capi = { luakit = luakit }

--- Utility functions for lousy.
module("lousy.util")

table = {}
string = {}

local xml_entity_names = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
local xml_entity_chars = { lt = "<", gt = ">", nbsp = " ", quot = "\"", apos = "'", ndash = "-", mdash = "-", amp = "&" };

--- Escape a string from XML characters.
-- @param text The text to escape.
-- @return A string with all XML characters escaped.
function escape(text)
    return text and text:gsub("['&<>\"]", xml_entity_names) or nil
end

--- Unescape a string from XML entities.
-- @param text The text to un-escape.
-- @return A string with all the XML entities un-escaped.
function unescape(text)
    return text and text:gsub("&(%a+);", xml_entity_chars) or nil
end

--- Create a directory
-- @param dir The directory.
-- @return mkdir return code
function mkdir(dir)
    return os.execute(rstring.format("mkdir -p %q",  dir))
end

--- Eval Lua code.
-- @return The return value of Lua code.
function eval(s)
    return assert(loadstring(s))()
end

--- Check if a file is a Lua valid file.
-- This is done by loading the content and compiling it with loadfile().
-- @param path The file path.
-- @return A function if everything is alright, a string with the error
-- otherwise.
function checkfile(path)
    local f, e = loadfile(path)
    -- Return function if function, otherwise return error.
    if f then return f end
    return e
end

--- Return the difference of one table against another.
-- @param t The original table.
-- @param other The table to perform the difference against.
-- @return All elements in the first table that are not in the other table.
function table.difference(t, other)
    local ret = {}
    for k, v in pairs(t) do
        if type(k) == "number" then
            local found = false
            for _, ov in ipairs(other) do
                if ov == v then
                    found = true
                    break
                end
            end
            if not found then rtable.insert(ret, v) end
        else
            if not other[k] then ret[k] = v end
        end
    end
    return ret
end

--- Join all tables given as parameters.
-- This will iterate all tables and insert all their keys into a new table.
-- @param args A list of tables to join
-- @return A new table containing all keys from the arguments.
function table.join(...)
    local ret = {}
    for _, t in pairs({...}) do
        for k, v in pairs(t) do
            if type(k) == "number" then
                rtable.insert(ret, v)
            else
                ret[k] = v
            end
        end
    end
    return ret
end

--- Check if a table has an item and return its key.
-- @param t The table.
-- @param item The item to look for in values of the table.
-- @return The key were the item is found, or nil if not found.
function table.hasitem(t, item)
    for k, v in pairs(t) do
        if v == item then
            return k
        end
    end
end

--- Get a sorted table with all integer keys from a table
-- @param t the table for which the keys to get
-- @return A table with keys
function table.keys(t)
    local keys = { }
    for k, _ in pairs(t) do
        rtable.insert(keys, k)
    end
    rtable.sort(keys, function (a, b)
        return type(a) == type(b) and a < b or false
    end)
    return keys
end

--- Reverse a table
-- @param t the table to reverse
-- @return the reversed table
function table.reverse(t)
    local tr = { }
    -- reverse all elements with integer keys
    for _, v in ipairs(t) do
        rtable.insert(tr, 1, v)
    end
    -- add the remaining elements
    for k, v in pairs(t) do
        if type(k) ~= "number" then
            tr[k] = v
        end
    end
    return tr
end

--- Clone a table
-- @param t the table to clone
-- @return a clone of t
function table.clone(t)
    local c = { }
    for k, v in pairs(t) do
        c[k] = v
    end
    return c
end

--- Clone table and set metatable
-- @param t the table to clone
-- @return a clone of t with t's metatable
function table.copy(t)
    local c = table.clone(t)
    return setmetatable(c, getmetatable(t))
end

--- Check if two tables are identical.
-- @param a The first table.
-- @param b The second table.
-- @return True if both tables are identical.
function table.isclone(a, b)
    if #a ~= #b then return false end
    for k, v in pairs(a) do
        if a[k] ~= b[k] then return false end
    end
    return true
end

--- Clone a table with all values as array items.
-- @param t the table to clone
-- @return all values in t
function table.values(t)
    local ret = {}
    for _, v in pairs(t) do
        rtable.insert(ret, v)
    end
    return ret
end

--- Convert a table to an array by removing all keys that are not sequential numbers.
-- @param t the table to converts
-- @return a new table with all non-number keys removed
function table.toarray(t)
    local ret = {}
    for k, v in ipairs(t) do
        ret[k] = v
    end
    return ret
end

--- Check if a file exists and is readable.
-- @param f The file path.
-- @return True if the file exists and is readable.
function os.exists(f)
    assert(type(f) == "string", "invalid path")
    fh, err = io.open(f)
    if fh then
        fh:close()
        return f
    end
end

--- Python like string split (source: lua wiki)
-- @param s The string to split.
-- @param pattern The split pattern (I.e. "%s+" to split text by one or more
-- whitespace characters).
-- @param ret The table to insert the split items in to or a new table if nil.
-- @return A table of the string split by the pattern.
function string.split(s, pattern, ret)
    if not pattern then pattern = "%s+" end
    if not ret then ret = {} end
    local pos = 1
    local fstart, fend = rstring.find(s, pattern, pos)
    while fstart do
        rtable.insert(ret, rstring.sub(s, pos, fstart - 1))
        pos = fend + 1
        fstart, fend = rstring.find(s, pattern, pos)
    end
    rtable.insert(ret, rstring.sub(s, pos))
    return ret
end

-- Python like string strip.
-- @param s The string to strip.
-- @param pattern The pattern to strip from the left-most and right-most of the
-- string.
-- @return The inner string segment.
function string.strip(s, pattern)
    local p = pattern or "%s*"
    local sub_start, sub_end

    -- Find start point
    local _, f_end = rstring.find(s, "^"..p)
    if f_end then sub_start = f_end + 1 end

    -- Find end point
    local f_start = rstring.find(s, p.."$")
    if f_start then sub_end = f_start - 1 end

    return rstring.sub(s, sub_start or 1, sub_end or #s)
end

function string.dedent(text, first)
    local min = first and #rstring.match(text, "^(%s*)") or nil
    rstring.gsub(text, "\n(%s*)", function (spaces)
        local len = #spaces
        if not min or len < min then min = len end
    end)
    if min and min > 0 then
        local pat = "\n" .. rstring.rep(" ", min)
        text = rstring.gsub(text, pat, "\n")
    end
    return first and rstring.sub(text, min + 1) or text
end

local function find_file(paths)
    for _, p in ipairs(paths) do
        if os.exists(p) then return p end
    end
    return error(rstring.format("No such file at: \n\t%s\n", rtable.concat(paths, ",\n\t")))
end

--- Search and return the filepath of a file in the current working directory,
-- or $XDG_CONFIG_HOME/luakit/ or /etc/xdg/luakit/.
-- @param f The relative filepath.
-- @return The first valid filepath or an error.
function find_config(f)
    if rstring.match(f, "^/") then return f end
    -- Search locations
    local paths = { "config/"..f, capi.luakit.config_dir.."/"..f, "/etc/xdg/luakit/"..f }
    return find_file(paths)
end

--- Search and return the filepath of a file in the current working directory,
-- in the users $XDG_DATA_HOME/luakit/ or the luakit install dir.
-- @param f The relative filepath.
-- @return The first valid filepath or an error.
function find_data(f)
    if rstring.match(f, "^/") then return f end
    -- Search locations
    local paths = { f, capi.luakit.data_dir.."/"..f, capi.luakit.install_path.."/"..f }
    return find_file(paths)
end

--- Search and return the filepath of a file in the current working directory
-- or in the users $XDG_CACHE_HOME/luakit/
-- @param f The relative filepath.
-- @return The first valid filepath or an error.
function find_cache(f)
    -- Ignore absolute paths
    if rstring.match(f, "^/") then return f end
    -- Search locations
    local paths = { capi.luakit.cache_dir.."/"..f }
    return find_file(paths)
end

--- Recursively traverse widget tree and return all widgets.
-- @param wi The widget.
function recursive_remove(wi)
    local ret = {}
    -- Empty other container widgets
    for _, child in ipairs(wi.children or {}) do
        wi:remove(child)
        rtable.insert(ret, child)
        for _, c in ipairs(recursive_remove(child)) do
            rtable.insert(ret, c)
        end
    end
    return ret
end

--- Convert a number to string independent from locale.
-- @param num A number.
-- @param sigs Signifigant figures (if float).
-- @return The string representation of the number.
function ntos(num, sigs)
    local dec = rstring.sub(tostring(num % 1), 3, 2 + (sigs or 4))
    num = tostring(math.floor(num))
    return (#dec == 0 and num) or (num .. "." .. dec)
end

--- Escape values for SQL queries.
-- In sqlite3: "A string constant is formed by enclosing the string in single
-- quotes ('). A single quote within the string can be encoded by putting two
-- single quotes in a row - as in Pascal."
-- Read: http://sqlite.org/lang_expr.html
function sql_escape(s)
    return "'" .. rstring.gsub(s or "", "'", "''") .. "'"
end

--- Get all hostnames in /etc/hosts
-- @param Force re-load of /etc/hosts
-- @return Table of all hostnames in /etc/hosts
local etc_hosts
function get_etc_hosts(force)
    -- Unless forced return previous hostnames
    if not force and etc_hosts then
        return etc_hosts
    end
    -- Parse /etc/hosts
    local match, find, gsub = rstring.match, rstring.find, rstring.gsub
    local h = { localhost = "localhost" }
    for line in io.lines("/etc/hosts") do
        if not find(line, "^#") then
            local names = match(line, "^%S+%s+(.+)$")
            gsub(names or "", "(%S+)", function (name)
                h[name] = name -- key add removes duplicates
            end)
        end
    end
    etc_hosts = table.values(h)
    return etc_hosts
end

-- vim: et:sw=4:ts=8:sts=4:tw=80