Emulate spawn and execFile from node.js's child_process module

This is a rudimentary implementation of the following methods
from [node.js's `child_process` module][1]:

 *  `spawn`
 *  `execFile`

The examples are relevant only for *nix operating systems...

The following methods are Not Yet Implemented™:

 *  `exec`
 *  `fork`

[1]: http://nodejs.org/docs/v0.8.16/api/child_process.html

http://code.google.com/p/phantomjs/issues/detail?id=219
1.9
execjosh 2012-12-28 22:43:25 +09:00
parent 83e8152dd6
commit f52044cd31
10 changed files with 464 additions and 0 deletions

View File

@ -0,0 +1,20 @@
{spawn, execFile} = require "child_process"
child = spawn "ls", ["-lF", "/rooot"]
child.stdout.on "data", (data) ->
console.log "spawnSTDOUT:", JSON.stringify data
child.stderr.on "data", (data) ->
console.log "spawnSTDERR:", JSON.stringify data
child.on "exit", (code) ->
console.log "spawnEXIT:", code
#child.kill "SIGKILL"
execFile "ls", ["-lF", "/usr"], null, (err, stdout, stderr) ->
console.log "execFileSTDOUT:", JSON.stringify stdout
console.log "execFileSTDERR:", JSON.stringify stderr
setTimeout (-> phantom.exit 0), 2000

View File

@ -0,0 +1,27 @@
var spawn = require("child_process").spawn
var execFile = require("child_process").execFile
var child = spawn("ls", ["-lF", "/rooot"])
child.stdout.on("data", function (data) {
console.log("spawnSTDOUT:", JSON.stringify(data))
})
child.stderr.on("data", function (data) {
console.log("spawnSTDERR:", JSON.stringify(data))
})
child.on("exit", function (code) {
console.log("spawnEXIT:", code)
})
//child.kill("SIGKILL")
execFile("ls", ["-lF", "/usr"], null, function (err, stdout, stderr) {
console.log("execFileSTDOUT:", JSON.stringify(stdout))
console.log("execFileSTDERR:", JSON.stringify(stderr))
})
setTimeout(function () {
phantom.exit(0)
}, 2000)

View File

@ -115,6 +115,7 @@ phantom.callback = function(callback) {
// (for future, now both fs and system are loaded anyway)
var nativeExports = {
get fs() { return phantom.createFilesystem(); },
get child_process() { return phantom._createChildProcess(); },
get system() { return phantom.createSystem(); }
};
var extensions = {

136
src/childprocess.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "childprocess.h"
//
// ChildProcessContext
//
ChildProcessContext::ChildProcessContext(QObject *parent)
: QObject(parent)
, m_proc(this)
{
connect(&m_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(_readyReadStandardOutput()));
connect(&m_proc, SIGNAL(readyReadStandardError()), this, SLOT(_readyReadStandardError()));
connect(&m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(_finished(int, QProcess::ExitStatus)));
connect(&m_proc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(_error(QProcess::ProcessError)));
}
ChildProcessContext::~ChildProcessContext()
{
}
// public:
qint64 ChildProcessContext::pid() const
{
Q_PID pid = m_proc.pid();
#if !defined(Q_OS_WIN32) && !defined(Q_OS_WINCE)
return pid;
#else
return pid->dwProcessId;
#endif
}
void ChildProcessContext::kill(const QString &signal)
{
// TODO: it would be nice to be able to handle more signals
if ("SIGKILL" == signal) {
m_proc.kill();
} else {
// Default to "SIGTERM"
m_proc.terminate();
}
}
void ChildProcessContext::_setEncoding(const QString &encoding)
{
m_encoding.setEncoding(encoding);
}
// This is affected by [QTBUG-5990](https://bugreports.qt-project.org/browse/QTBUG-5990).
// `QProcess` doesn't properly handle the situations of `cmd` not existing or
// failing to start...
bool ChildProcessContext::_start(const QString &cmd, const QStringList &args)
{
m_proc.start(cmd, args);
// TODO: Is there a better way to do this???
return m_proc.waitForStarted(1000);
}
// private slots:
void ChildProcessContext::_readyReadStandardOutput()
{
QByteArray bytes = m_proc.readAllStandardOutput();
emit stdoutData(m_encoding.decode(bytes));
}
void ChildProcessContext::_readyReadStandardError()
{
QByteArray bytes = m_proc.readAllStandardError();
emit stderrData(m_encoding.decode(bytes));
}
void ChildProcessContext::_finished(const int exitCode, const QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitStatus)
emit exit(exitCode);
}
void ChildProcessContext::_error(const QProcess::ProcessError error)
{
Q_UNUSED(error)
emit exit(m_proc.exitCode());
}
//
// ChildProcess
//
ChildProcess::ChildProcess(QObject *parent)
: QObject(parent)
{
}
ChildProcess::~ChildProcess()
{
}
// public:
QObject *ChildProcess::_createChildProcessContext()
{
return new ChildProcessContext(this);
}

93
src/childprocess.h Normal file
View File

@ -0,0 +1,93 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CHILDPROCESS_H
#define CHILDPROCESS_H
#include <QObject>
#include <QProcess>
#include "encoding.h"
/**
* This class wraps a QProcess and facilitates emulation of node.js's ChildProcess
*/
class ChildProcessContext : public QObject
{
Q_OBJECT
Q_PROPERTY(qint64 pid READ pid)
public:
explicit ChildProcessContext(QObject *parent = 0);
virtual ~ChildProcessContext();
qint64 pid() const;
Q_INVOKABLE void kill(const QString &signal = "SIGTERM");
Q_INVOKABLE void _setEncoding(const QString &encoding);
Q_INVOKABLE bool _start(const QString &cmd, const QStringList &args);
signals:
void exit(const int code) const;
/**
* For emulating `child.stdout.on("data", function (data) {})`
*/
void stdoutData(const QString &data) const;
/**
* For emulating `child.stderr.on("data", function (data) {})`
*/
void stderrData(const QString &data) const;
private slots:
void _readyReadStandardOutput();
void _readyReadStandardError();
void _error(const QProcess::ProcessError error);
void _finished(const int exitCode, const QProcess::ExitStatus exitStatus);
private:
QProcess m_proc;
Encoding m_encoding;
};
/**
* Helper class for child_process module
*/
class ChildProcess : public QObject
{
Q_OBJECT
public:
explicit ChildProcess(QObject *parent = 0);
virtual ~ChildProcess();
Q_INVOKABLE QObject *_createChildProcessContext();
};
#endif // CHILDPROCESS_H

View File

@ -0,0 +1,165 @@
/*jslint sloppy: true, nomen: true */
/*global exports:true */
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2012 execjosh, http://execjosh.blogspot.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var NOP = function () {}
/**
* spawn(command, [args], [options])
*/
exports.spawn = function (cmd, args, opts) {
var ctx = newContext()
if (null == opts) {
opts = {}
}
opts.encoding = opts.encoding || "utf8"
ctx._setEncoding(opts.encoding)
ctx._start(cmd, args)
return ctx
}
/**
* exec(command, [options], callback)
*/
exports.exec = function (cmd, opts, cb) {
if (null == cb) {
cb = NOP
}
return cb(new Error("NotYetImplemented"))
}
/**
* execFile(file, args, options, callback)
*/
exports.execFile = function (cmd, args, opts, cb) {
var ctx = newContext()
if (null == cb) {
cb = NOP
}
if (null == opts) {
opts = {}
}
opts.encoding = opts.encoding || "utf8"
ctx._setEncoding(opts.encoding)
var stdout = ""
ctx.stdout.on("data", function (chunk) {
stdout += chunk
})
var stderr = ""
ctx.stderr.on("data", function (chunk) {
stderr += chunk
})
ctx.on("exit", function (code) {
return cb(null, stdout, stderr)
})
ctx._start(cmd, args)
return ctx
}
/**
* fork(modulePath, [args], [options])
*/
exports.fork = function (modulePath, args, opts) {
throw new Error("NotYetImplemented")
}
// private
function newContext() {
var ctx = exports._createChildProcessContext()
// TODO: "Buffer" the signals and redispatch them?
ctx.on = function (evt, cb) {
var handler
switch (evt) {
case "exit":
handler = ctx[evt]
break
default:
break
}
// Connect the callback to the signal
if (isFunction(handler)) {
handler.connect(cb)
}
}
ctx.stdout = new FakeReadableStream("stdout")
ctx.stderr = new FakeReadableStream("stderr")
// Emulates `Readable Stream`
function FakeReadableStream(streamName) {
this.on = function (evt, cb) {
switch (evt) {
case 'data':
ctx[streamName + "Data"].connect(cb)
break
default:
break
}
}
}
return ctx
}
function delayCallback() {
var args = 0 < arguments.length ? [].slice.call(arguments, 0) : []
var fn = args.shift()
if (!isFunc(fn)) {
return
}
var that = this
setTimeout(function () {
fn.apply(that, args)
}, 0)
}
function isFunction(o) {
return typeof o === 'function'
}

View File

@ -48,6 +48,7 @@
#include "system.h"
#include "callback.h"
#include "cookiejar.h"
#include "childprocess.h"
static Phantom *phantomInstance = NULL;
@ -58,6 +59,7 @@ Phantom::Phantom(QObject *parent)
, m_returnValue(0)
, m_filesystem(0)
, m_system(0)
, m_childprocess(0)
{
QStringList args = QApplication::arguments();
@ -341,6 +343,15 @@ QObject *Phantom::createSystem()
return m_system;
}
QObject *Phantom::_createChildProcess()
{
if (!m_childprocess) {
m_childprocess = new ChildProcess(this);
}
return m_childprocess;
}
QObject* Phantom::createCallback()
{
return new Callback(this);

View File

@ -39,6 +39,7 @@
#include "config.h"
#include "replcompletable.h"
#include "system.h"
#include "childprocess.h"
class WebPage;
class CustomPage;
@ -102,6 +103,11 @@ public:
bool webdriverMode() const;
/**
* Create `child_process` module instance
*/
Q_INVOKABLE QObject *_createChildProcess();
public slots:
QObject *createWebPage();
QObject *createWebServer();
@ -185,6 +191,7 @@ private:
QVariantMap m_defaultPageSettings;
FileSystem *m_filesystem;
System *m_system;
ChildProcess *m_childprocess;
QList<QPointer<WebPage> > m_pages;
QList<QPointer<WebServer> > m_servers;
Config m_config;

View File

@ -25,6 +25,7 @@ HEADERS += csconverter.h \
terminal.h \
encoding.h \
config.h \
childprocess.h \
repl.h \
replcompletable.h
@ -43,6 +44,7 @@ SOURCES += phantom.cpp \
terminal.cpp \
encoding.cpp \
config.cpp \
childprocess.cpp \
repl.cpp \
replcompletable.cpp
@ -52,6 +54,7 @@ OTHER_FILES += \
modules/fs.js \
modules/webpage.js \
modules/webserver.js \
modules/child_process.js \
repl.js
include(gif/gif.pri)

View File

@ -8,6 +8,7 @@
<file>modules/webserver.js</file>
<file>modules/fs.js</file>
<file>modules/system.js</file>
<file>modules/child_process.js</file>
<file>modules/_coffee-script.js</file>
<file>repl.js</file>