Add awful.util.spawn_with_line_callback

This new function spawns a program, similarly to awful.spawn, but captures its
output. On each line of output on stdout / stderr, a Lua function is called with
this line. There are different callbacks for stdout and stderr. When both stdout
and stderr are closed, another callback function is called. The intention for
this last callback is "the program is done", because most programs should only
close their output when they exit.

Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2015-09-02 22:18:33 +02:00
parent 1beda989e5
commit 0e20fef2bd

View file

@ -22,6 +22,8 @@ local pairs = pairs
local string = string
local lgi = require("lgi")
local Pango = lgi.Pango
local Gio = lgi.Gio
local GLib = lgi.GLib
local capi =
{
awesome = awesome,
@ -110,6 +112,94 @@ function util.spawn_with_shell(cmd, sn)
end
end
--- Spawn a program and asynchronously capture its output line by line.
-- @tparam string|table cmd The command.
-- @tparam[opt] function stdout_callback Function that is called with each line of
-- output on stdout, e.g. `stdout_callback(line)`.
-- @tparam[opt] function stderr_callback Function that is called with each line of
-- output on stderr, e.g. `stderr_callback(line)`.
-- @tparam[opt] function done_callback Function to call when no more output is
-- produced.
-- @treturn[1] Integer the PID of the forked process.
-- @treturn[2] string Error message.
function util.spawn_with_line_callback(cmd, stdout_callback, stderr_callback, done_callback)
local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil
local pid, sn_id, stdin, stdout, stderr = capi.awesome.spawn(cmd, false, false, have_stdout, have_stderr)
if type(pid) == "string" then
-- Error
return pid
end
local done_before = false
local function step_done()
if have_stdout and have_stderr and not done_before then
done_before = true
return
end
done_callback()
end
if have_stdout then
util.read_lines(Gio.UnixInputStream.new(stdout, true),
stdout_callback, step_done, true)
end
if have_stderr then
util.read_lines(Gio.UnixInputStream.new(stderr, true),
stderr_callback, step_done, true)
end
assert(stdin == nil)
end
--- Read lines from a Gio input stream
-- @tparam Gio.InputStream input_stream The input stream to read from.
-- @tparam function line_callback Function that is called with each line
-- read, e.g. `line_callback(line_from_stream)`.
-- @tparam[opt] function done_callback Function that is called when the
-- operation finishes (e.g. due to end of file).
-- @tparam[opt=false] boolean close Should the stream be closed after end-of-file?
function util.read_lines(input_stream, line_callback, done_callback, close)
local stream = Gio.DataInputStream.new(input_stream)
local function done()
if close then
stream:close()
end
if done_callback then
xpcall(done_callback, function(err)
print(debug.traceback("Error while calling done_callback:"
.. tostring(err), 2))
end)
end
end
local start_read, finish_read
start_read = function()
stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
end
finish_read = function(obj, res)
local line, length = obj:read_line_finish(res)
if type(length) ~= "number" then
-- Error
print("Error in awful.util.read_lines:", tostring(length))
done()
elseif #line ~= length then
-- End of file
done()
else
-- Read a line
xpcall(function()
-- This needs tostring() for older lgi versions which returned
-- "GLib.Bytes" instead of Lua strings (I guess)
line_callback(tostring(line))
end, function(err)
print(debug.traceback("Error while calling line_callback: "
.. tostring(err), 2))
end)
-- Read the next line
start_read()
end
end
start_read()
end
--- Read a program output and returns its output as a string.
-- @param cmd The command to run.
-- @return A string with the program output, or the error if one occured.