-
Notifications
You must be signed in to change notification settings - Fork 1
/
runtime.lua
178 lines (142 loc) · 3.86 KB
/
runtime.lua
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
-- shortcuts
program_pathname = arg[0]
program_name = program_pathname:match("[^/]+$")
-- console messages -------------------------------------------------------------------------------
do
-- print message to STDERR
local function show_msg(kind, msg, ...)
if select("#", ...) > 0 then
msg = msg:format(...)
end
return io.stderr:write(program_name, ": [", kind, "] ", msg, "\n")
end
-- [global] print error message
function perror(msg, ...)
return show_msg("error", msg, ...)
end
-- [global] print warning
function pwarning(msg, ...)
return show_msg("warning", msg, ...)
end
end
-- [global] shell quoting
function Q(s) --> quoted string
s = s:gsub("'+", function(m) return "'" .. string.rep("\\'", m:len()) .. "'" end)
return "'" .. s .. "'"
end
-- error handling ---------------------------------------------------------------------------------
do
-- error reporting helper
local function _fail(err, code)
if math.type(code) == "integer" then
-- returning from os.execute or similar
if err == "exit" then
-- propagate the code, assuming an error message has already been
-- produced by an external program
error(code, 0)
end
if err == "signal" then
err = "interrupted with signal " .. code
end
end
error(err, 0)
end
-- [global] error checker
function just(ok, err, code, ...)
if ok then
return ok, err, code, ...
end
_fail(err, code)
end
-- [global] application error reporter (never returns)
function fail(msg, ...)
if type(msg) == "string" and select("#", ...) > 0 then
msg = msg:format(...)
end
error(msg, 0)
end
-- [global] application runner (never returns)
function run(fn, ...)
local ok, err = pcall(fn, ...)
if ok then
os.exit(true)
end
if math.type(err) == "integer" then
-- exit with this error code, assuming an error message has already been
-- printed out
os.exit(err)
end
perror(tostring(err):gsub("%s+$", ""))
os.exit(false)
end
-- [global] resource handler
function with(resource, cleanup, fn, ...) --> whatever fn returns
local function wrap(ok, ...)
if ok then
just(cleanup(resource, true))
return ...
end
pcall(cleanup, resource)
_fail(...)
end
return wrap(pcall(fn, resource, ...))
end
-- delete file ignoring "file not found" error
local function _remove(fname)
local ok, err, code = os.remove(fname)
if ok or code == 2 then -- ENOENT 2 No such file or directory
return true
end
return ok, err, code
end
-- [global] execute fn with a temporary file name, removing the file in the end
function with_temp_file(fn, ...)
return with(os.tmpname(), _remove, fn, ...)
end
-- remove directory
local function _rm_dir(dir)
return os.execute("rm -rf " .. Q(dir))
end
-- [global] execute fn with a temporary directory name, removing the directory in the end
function with_temp_dir(fn, ...)
-- create temp. directory
local cmd = just(io.popen("mktemp -d"))
local tmp = cmd:read("l")
just(cmd:close())
-- invoke fn
return with(tmp, _rm_dir, fn, ...)
end
end
-- pumping null-delimited data --------------------------------------------------------------------
do
-- pump helper
local function _pump(src, fn)
local tail = ""
local N = 8 * 1024
local s = src:read(N)
while s do -- explicit loop to avoid stack trace on signals
local b, e = 1, s:find("\0", 1, true)
if e then
fn(tail .. s:sub(b, e - 1))
b = e + 1
e = s:find("\0", b, true)
while e do
fn(s:sub(b, e - 1))
b = e + 1
e = s:find("\0", b, true)
end
tail = s:sub(b)
else
tail = tail .. s
end
s = src:read(N)
end
if #tail ~= 0 then -- must never happen
fail("reading command output: missing delimiter on the last line")
end
end
-- [global] feed fn with lines from cmd output, using "\0" symbol as line delimiter
function pump(cmd, fn)
with(just(io.popen(cmd)), io.close, _pump, fn)
end
end