-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ -- --* .name Asynchronous callbacks for anything that needs it local C = require("c") local X = require("x") local Conf = require("conf") local ev = require("ev") -- https://github.com/brimworks/lua-ev local Async = {ev = ev} --* fd(int fd, int events, function callback) Raw file descriptor callbacks function Async.fd(fd, evs, cb) ev.IO.new(cb, fd, evs):start(ev.Loop.default) end function Async.idle(cb) local id id = ev.Idle.new(function() id:stop(ev.Loop.default) cb() X.sync() end) id:start(ev.Loop.default) end --* timer(float after, [float repeat], function callback) Calls **callback** after **after** seconds, and then repeats on a **repeat** second loop if it's provided function Async.timer(after, rep, cb) if not cb then rep, cb = cb, rep end if after <= 0 then after = 0.00000001 end ev.Timer.new(cb, after, rep):start(ev.Loop.default) end --* .desc The *spawn** functions return a table with the **pid**, as well as *write*(*str* **data**) and *close*() for stdin local StdinWrapper = { write = function(self, text) C.write(self.stdin, text) return self end, close = function(self) C.close(self.stdin) end } StdinWrapper.__index = StdinWrapper --* table stdin spawn(str program, [str arg, ...], [function callback]) Spawns a program and calls **callback** once it exits. Parameters passed to **callback** are (*str* **stdout**, *str* **stderr**, *int* **exitStatus**) function Async.spawn(...) local args = {...} local cb if #args == 0 then error("Need a program") end if type(args[#args]) == "function" then cb = table.remove(args, #args) end local pid, stdin, stdout, stderr if cb then pid, stdin, stdout, stderr = C.spawn(args) if not pid then return nil, "Failed to spawn" end local childWatcher childWatcher = ev.Child.new(function(loop, child) childWatcher:stop(loop) local out = C.read(stdout) local err = C.read(stderr) C.close(stdin, stdout, stderr) cb(out, err, child:getstatus().exit_status) end, pid, false) childWatcher:start(ev.Loop.default) else pid, stdin, stdout, stderr = C.spawn(args) if not pid then return nil, "Failed to spawn" end end return setmetatable({ pid = pid, stdin = stdin }, StdinWrapper) end --* table stdin spawnShell(str command, [str shell], [function callback]) Runs **command** in a shell instace. The shell is the first of **shell**, **Conf.shell**, **$SHELL**, "/bin/sh". Parameters passed to **callback** are the same as with *spawn* function Async.spawnShell(str, shell, cb) if not cb then cb, shell = shell, cb end return Async.spawn(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, cb) end --* table stdin spawnRead(str program, [str arg, ...], function dataCallback, [function exitCallback]) Spawns a program and calls **dataCallback** when the program writes to stdout or stderr, then **exitCallback** once it exits. Parameters to **dataCallback** are (*str* **stdout**, *str* **stderr**). Paramaters to **exitCallback** are (*int* **exitStatus**) function Async.spawnRead(...) local args = {...} if #args < 2 then error("Need a program and callback") end local outcb = table.remove(args, #args) if type(outcb) ~= "function" then error("Need a callback") end local exitcb if type(args[#args]) == "function" then if #args < 2 then error("Need a program") end outcb, exitcb = table.remove(args, #args), outcb if type(outcb) ~= "function" then error("Need a callback") end end local pid, stdin, stdout, stderr = C.spawn(args) C.setNonBlocking(stdout) C.setNonBlocking(stderr) local stdoutWatcher = ev.IO.new(function(loop, io, revents) local d = C.read(stdout) print(d, #d) if d and #d > 0 then outcb(d, nil) end end, stdout, ev.READ) stdoutWatcher:start(ev.Loop.default) local exitWatcher exitWatcher = ev.Child.new(function(loop, child, revents) local out = C.read(stdout) local err = C.read(stderr) C.close(stdout, stderr, stdin) stdoutWatcher:stop(loop) exitWatcher:stop(loop) outcb(out, err) if exitcb then exitcb(child:getstatus().exit_status) end end, pid, false) exitWatcher:start(ev.Loop.default) return setmetatable({pid = pid, stdin = stdin}, StdinWrapper) end --* table stdin spawnReadShell(str command, [str shell], function dataCallback, [function exitCallback]) Combination of *spawnRead* and *spawnShell* function Async.spawnReadShell(str, shell, outcb, exitcb) if type(shell) == "function" then shell, outcb, exitcb = nil, shell, outcb end return Async.spawnRead(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, outcb, exitcb) end function Async.readFile(path, cb) local stdout = C.getFD(assert(io.open(path, "r"))) C.setNonBlocking(stdout) local chunks = {} local stdoutWatcher stdoutWatcher = ev.IO.new(function() local c = C.read(stdout) if c and #c > 0 then table.insert(chunks, c) else C.close(stdout) stdoutWatcher:stop(ev.Loop.default) cb(table.concat(chunks)) end end, stdout, ev.READ) stdoutWatcher:start(ev.Loop.default) end function Async.loop() ev.Loop.default:loop() end return Async