mirror of https://github.com/vitalif/phantomjs
A REPL for PhantomJS
This covers [Issue 252](http://code.google.com/p/phantomjs/issues/detail?id=252) The commit is composed of 12 squashed commits: commit efdc6ba4f143c30a690fd97d92d80fa412e79999 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Mon Feb 27 00:19:36 2012 +0000 Pretty-pringing and Completion Caching done! * This completes pretty-printing for the result of evaluated * expressions in the REPL. * Also, now we cache the "possible completions", to speed things up * a bit (nothing fancy though). * Minor tweaks to the internal doc and the way we "mock" * pretty-printing for QObjects/REPLCompletanle * All tests passing :) commit 1f9ef690e112a535b431fca409b77bb9c09d1c70 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Sun Feb 26 22:35:00 2012 +0000 Moving most of REPL shim JavaScritp code in a separate file. Way easier to work on. commit 02d460a16fee14e7096ae7d899c03902c5b8a9c6 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Sat Feb 25 20:25:18 2012 +0000 Initialisation of the Completions is now done in a pure virtual. This means that every REPLCompletable object will ACTUALLY register completion strings, ONLY if we are running a REPL and that object is ACTUALLY created. Otherwise, why bother? Adding completions for all exposed REPLCompletable objects Also, fixed an issue with _getCompletions() commit 412c3778fb04aa1c7379f8e760afce702b0428dd Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Tue Feb 21 00:49:17 2012 +0000 Few more tweaks to the REPL: - Now 'phantom' is the first QObject with proper completion - No repetition in QObject completions - LVAL of any user expression is now correctly prettified and printed Major things left to do: - Cache completions (using QCache?) - Add completions for the other QObject - When the LVAL of a user expression is a QObject, print what's expected, not the QObject "real" structure commit 46f04713c8165d898055e15478bb31403f8c93f1 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Tue Feb 7 10:13:23 2012 -0800 Pretty-print expressions result Still not done though: there are issues with the NON-Native JS objects. commit 98b2fe67651dc750b62c6fa9cf1d80317fd9ae06 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Fri Feb 3 00:22:52 2012 -0800 Introducing REPLCompletable. This class should be inherited by any JavaScript-exposed QObject, to ensure correct Auto-Completion. Correct auto-completion for QObjects. - Now even QObjects can correctly provide auto-completion, and avoid showing "not for users" methods - The strings used for the auto-completion are stored in a single Index: minimum memory footprint - Still, there is optimization that should be done (when "searching" for the right completion by prefix) - Completion for the objects not set up yet, but now it's just a trivial sequence of "addCompletion('bla')" in their constructors commit 9bd48618154b1530a37b41f4060440184e23253d Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Thu Feb 2 00:20:25 2012 -0800 Changing the way we import Linenoise. Will just import a specific commit, and update manually when needed. commit cfc9bae9fbdab13b01019b34b7cbd565e3153780 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Sun Jan 29 23:22:26 2012 -0800 Made the REPL into a Singleton. With Auto-completion!. Reasons: 1) Needed a pointer to function (i.e. a static method) to be used with Linenoise to provide auto-completions 2) It makes more sense, as it's not like we are going to have 2 REPL running at the same time, are we? There are problems to address: - the enumeration in JS seems to return only the native interface of our objects - the function completions contain argument types of those functions - "private" methods are exposed commit c78bd32e17f8e0e4cc4a0066858de8cc81d33b97 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Sun Jan 29 22:10:20 2012 -0800 Migrating from the original, now [unmantained Linenoise](https://github.com/antirez/linenoise) to the fairly active [tadmarshall fork](https://github.com/tadmarshall/linenoise). Also now the project is imported as a Git Submodule. Having migrated to the latest Linenoise (see prev. commit), now this _SHOULD_ work on Windows too. But, of course, this needs testing. :) commit 43713c5723d7c5ed446ba41ae8d6f8c9feba7f9b Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Tue Jan 24 23:17:06 2012 -0800 Now that the basics work, I'm adding support for REPL history. This is something almost everyone today is accustomed to. Also, now REPL history works! And I found some useful resources to solve pending TODOs. commit 31e5f88b044a5b4a823c67527ef8c245d2ac7863 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Sun Jan 22 20:56:36 2012 -0800 Adding Linenoise Project (https://github.com/antirez/linenoise). For now is included as a drop-in set of files. Later on, if the Linenoise project has frequent updates, we might prefer to do it as a git-submodule. commit 4be9c15c65db4767e482fba0be13f8aab286d5f3 Author: Ivan De Marino <ivan.de.marino@gmail.com> Date: Thu Jan 5 15:31:13 2012 +0000 First simple REPL implementation. - Not complete - Still doesn't handle arrow keys (needed for history)1.5
parent
a0ef8cb898
commit
61a3bf9021
|
@ -38,7 +38,7 @@
|
|||
// File
|
||||
// public:
|
||||
File::File(QFile *openfile, QTextCodec *codec, QObject *parent) :
|
||||
QObject(parent),
|
||||
REPLCompletable(parent),
|
||||
m_file(openfile),
|
||||
m_fileStream(0)
|
||||
{
|
||||
|
@ -180,11 +180,23 @@ void File::close()
|
|||
deleteLater();
|
||||
}
|
||||
|
||||
void File::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'file' object
|
||||
// functions
|
||||
addCompletion("read");
|
||||
addCompletion("write");
|
||||
addCompletion("readLine");
|
||||
addCompletion("writeLine");
|
||||
addCompletion("flush");
|
||||
addCompletion("close");
|
||||
}
|
||||
|
||||
|
||||
// FileSystem
|
||||
// public:
|
||||
FileSystem::FileSystem(QObject *parent) :
|
||||
QObject(parent)
|
||||
FileSystem::FileSystem(QObject *parent)
|
||||
: REPLCompletable(parent)
|
||||
{ }
|
||||
|
||||
// public slots:
|
||||
|
@ -424,3 +436,36 @@ bool FileSystem::_remove(const QString &path) const
|
|||
bool FileSystem::_copy(const QString &source, const QString &destination) const {
|
||||
return QFile(source).copy(destination);
|
||||
}
|
||||
|
||||
void FileSystem::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'fs' object
|
||||
// properties
|
||||
addCompletion("separator");
|
||||
addCompletion("workingDirectory");
|
||||
// functions
|
||||
addCompletion("list");
|
||||
addCompletion("absolute");
|
||||
addCompletion("exists");
|
||||
addCompletion("isDirectory");
|
||||
addCompletion("isFile");
|
||||
addCompletion("isAbsolute");
|
||||
addCompletion("isExecutable");
|
||||
addCompletion("isReadable");
|
||||
addCompletion("isWritable");
|
||||
addCompletion("isLink");
|
||||
addCompletion("changeWorkingDirectory");
|
||||
addCompletion("makeDirectory");
|
||||
addCompletion("makeTree");
|
||||
addCompletion("removeDirectory");
|
||||
addCompletion("removeTree");
|
||||
addCompletion("copyTree");
|
||||
addCompletion("open");
|
||||
addCompletion("read");
|
||||
addCompletion("write");
|
||||
addCompletion("size");
|
||||
addCompletion("remove");
|
||||
addCompletion("copy");
|
||||
addCompletion("move");
|
||||
addCompletion("touch");
|
||||
}
|
||||
|
|
|
@ -30,14 +30,15 @@
|
|||
#ifndef FILESYSTEM_H
|
||||
#define FILESYSTEM_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
#include <QTextCodec>
|
||||
#include <QTextStream>
|
||||
#include <QVariant>
|
||||
|
||||
class File : public QObject
|
||||
#include "replcompletable.h"
|
||||
|
||||
class File : public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -58,13 +59,16 @@ public slots:
|
|||
void flush();
|
||||
void close();
|
||||
|
||||
private:
|
||||
virtual void initCompletions();
|
||||
|
||||
private:
|
||||
QFile *m_file;
|
||||
QTextStream *m_fileStream;
|
||||
};
|
||||
|
||||
|
||||
class FileSystem : public QObject
|
||||
class FileSystem : public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString workingDirectory READ workingDirectory)
|
||||
|
@ -121,6 +125,9 @@ public slots:
|
|||
bool isReadable(const QString &path) const;
|
||||
bool isWritable(const QString &path) const;
|
||||
bool isLink(const QString &path) const;
|
||||
|
||||
private:
|
||||
virtual void initCompletions();
|
||||
};
|
||||
|
||||
#endif // FILESYSTEM_H
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
This project contains the **Linenoise project**, initially released
|
||||
by [Salvatore Sanfilippo](https://github.com/antirez). Here we import a fork
|
||||
by [Tad Marshall](https://github.com/tadmarshall) that lives at
|
||||
[github.com/tadmarshall/linenoise](https://github.com/tadmarshall/linenoise).
|
||||
|
||||
The version of Linenoise included in PhantomJS refers to the commit:
|
||||
|
||||
commit 7946e2c2d08df11dca2b99c5db40360c3d3e9a80
|
||||
Author: Alan T. DeKok <aland@freeradius.org>
|
||||
Date: Wed Oct 26 15:56:52 2011 +0200
|
||||
|
||||
Added character callbacks again
|
||||
|
||||
Some files not needed for PhantomJS are removed.
|
||||
|
||||
|
||||
Linenoise is licensed under the BSD-license.
|
||||
|
||||
Praise to all the developers that contribute to this nice little pearl.
|
|
@ -0,0 +1,9 @@
|
|||
VPATH += $$PWD/src
|
||||
INCLUDEPATH += $$PWD/src
|
||||
|
||||
DEFINES += USE_UTF8
|
||||
|
||||
SOURCES += linenoise.c \
|
||||
utf8.c
|
||||
HEADERS += linenoise.h \
|
||||
utf8.h
|
|
@ -0,0 +1,51 @@
|
|||
# Linenoise
|
||||
|
||||
A minimal, zero-config, BSD licensed, readline replacement.
|
||||
|
||||
News: linenoise now includes minimal completion support, thanks to Pieter Noordhuis (@pnoordhuis).
|
||||
|
||||
News: linenoise is now part of [Android](http://android.git.kernel.org/?p=platform/system/core.git;a=tree;f=liblinenoise;h=56450eaed7f783760e5e6a5993ef75cde2e29dea;hb=HEAD Android)!
|
||||
|
||||
News: Win32 port, many cleanups and fixes from https://github.com/msteveb/linenoise.
|
||||
|
||||
News: added minimal character callbacks, so that the application can catch characters entered by the user, from Alan DeKok.
|
||||
|
||||
## Can a line editing library be 20k lines of code?
|
||||
|
||||
Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
|
||||
|
||||
So what usually happens is either:
|
||||
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh).
|
||||
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance).
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for VT100 alike escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it.
|
||||
|
||||
Since it's so young I guess there are a few bugs, or the lib may not compile or work with some operating system, but it's a matter of a few weeks and eventually we'll get it right, and there will be no excuses for not shipping command line tools without built-in line editing support.
|
||||
|
||||
The library started off at less than 400 lines of code, and is now almost 1500 lines of code. However, it now includes support for UTF-8 and Win32. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
|
||||
|
||||
## Tested with...
|
||||
|
||||
* Linux text only console ($TERM = linux)
|
||||
* Linux KDE terminal application ($TERM = xterm)
|
||||
* Linux xterm ($TERM = xterm)
|
||||
* Mac OS X iTerm ($TERM = xterm)
|
||||
* Mac OS X default Terminal.app ($TERM = xterm)
|
||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
|
||||
* IBM AIX 6.1
|
||||
* FreeBSD xterm ($TERM = xterm)
|
||||
|
||||
Please test it everywhere you can and report back!
|
||||
|
||||
## Let's push this forward!
|
||||
|
||||
Please fork it and add something interesting and send me a pull request. What's especially interesting are fixes, new key bindings, completion.
|
||||
|
||||
Send feedbacks to antirez at gmail
|
|
@ -0,0 +1,90 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#ifndef NO_COMPLETION
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int in_string = 0;
|
||||
static size_t string_start = 0;
|
||||
|
||||
int foundspace(const char *buf, size_t len, char c) {
|
||||
if (in_string) return 0;
|
||||
|
||||
if (len == 0) return 1;
|
||||
|
||||
if (buf[len -1] == c) return 1;
|
||||
|
||||
printf("\r\nSPACE!\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int escapedquote(const char *start)
|
||||
{
|
||||
while (*start) {
|
||||
if (*start == '\\') {
|
||||
if (!start[1]) return 1;
|
||||
start += 2;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int foundquote(const char *buf, size_t len, char c) {
|
||||
if (!in_string) {
|
||||
in_string = 1;
|
||||
string_start = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (buf[string_start] != c) return 0;
|
||||
|
||||
if (escapedquote(buf + string_start)) return 0;
|
||||
|
||||
in_string = 0;
|
||||
printf("\r\nSTRING %s%c\r\n", buf + string_start, buf[string_start]);
|
||||
string_start = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int foundhelp(const char *buf, size_t len, char c) {
|
||||
if (in_string) return 0;
|
||||
|
||||
len = len; /* -Wunused */
|
||||
c = c; /* -Wunused */
|
||||
|
||||
printf("?\r\nHELP: %s\r\n", buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *line;
|
||||
|
||||
#ifndef NO_COMPLETION
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
#endif
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
linenoiseSetCharacterCallback(foundspace, ' ');
|
||||
linenoiseSetCharacterCallback(foundquote, '"');
|
||||
linenoiseSetCharacterCallback(foundquote, '\'');
|
||||
linenoiseSetCharacterCallback(foundhelp, '?');
|
||||
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
printf("echo: '%s'\n", line);
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave("history.txt"); /* Save every new entry */
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,62 @@
|
|||
/* linenoise.h -- guerrilla line editing library against the idea that a
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS 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 __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifndef NO_COMPLETION
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
#endif
|
||||
|
||||
typedef int(linenoiseCharacterCallback)(const char *, size_t, char);
|
||||
void linenoiseSetCharacterCallback(linenoiseCharacterCallback *, char);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseHistoryFree(void);
|
||||
char **linenoiseHistory(int *len);
|
||||
|
||||
#endif /* __LINENOISE_H */
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* UTF-8 utility functions
|
||||
*
|
||||
* (c) 2010 Steve Bennett <steveb@workware.net.au>
|
||||
*
|
||||
* See LICENCE for licence details.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utf8.h"
|
||||
|
||||
#ifdef USE_UTF8
|
||||
int utf8_fromunicode(char *p, unsigned short uc)
|
||||
{
|
||||
if (uc <= 0x7f) {
|
||||
*p = uc;
|
||||
return 1;
|
||||
}
|
||||
else if (uc <= 0x7ff) {
|
||||
*p++ = 0xc0 | ((uc & 0x7c0) >> 6);
|
||||
*p = 0x80 | (uc & 0x3f);
|
||||
return 2;
|
||||
}
|
||||
else {
|
||||
*p++ = 0xe0 | ((uc & 0xf000) >> 12);
|
||||
*p++ = 0x80 | ((uc & 0xfc0) >> 6);
|
||||
*p = 0x80 | (uc & 0x3f);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
int utf8_charlen(int c)
|
||||
{
|
||||
if ((c & 0x80) == 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((c & 0xe0) == 0xc0) {
|
||||
return 2;
|
||||
}
|
||||
if ((c & 0xf0) == 0xe0) {
|
||||
return 3;
|
||||
}
|
||||
if ((c & 0xf8) == 0xf0) {
|
||||
return 4;
|
||||
}
|
||||
/* Invalid sequence */
|
||||
return -1;
|
||||
}
|
||||
|
||||
int utf8_strlen(const char *str, int bytelen)
|
||||
{
|
||||
int charlen = 0;
|
||||
if (bytelen < 0) {
|
||||
bytelen = strlen(str);
|
||||
}
|
||||
while (bytelen) {
|
||||
int c;
|
||||
int l = utf8_tounicode(str, &c);
|
||||
charlen++;
|
||||
str += l;
|
||||
bytelen -= l;
|
||||
}
|
||||
return charlen;
|
||||
}
|
||||
|
||||
int utf8_index(const char *str, int index)
|
||||
{
|
||||
const char *s = str;
|
||||
while (index--) {
|
||||
int c;
|
||||
s += utf8_tounicode(s, &c);
|
||||
}
|
||||
return s - str;
|
||||
}
|
||||
|
||||
int utf8_charequal(const char *s1, const char *s2)
|
||||
{
|
||||
int c1, c2;
|
||||
|
||||
utf8_tounicode(s1, &c1);
|
||||
utf8_tounicode(s2, &c2);
|
||||
|
||||
return c1 == c2;
|
||||
}
|
||||
|
||||
int utf8_tounicode(const char *str, int *uc)
|
||||
{
|
||||
unsigned const char *s = (unsigned const char *)str;
|
||||
|
||||
if (s[0] < 0xc0) {
|
||||
*uc = s[0];
|
||||
return 1;
|
||||
}
|
||||
if (s[0] < 0xe0) {
|
||||
if ((s[1] & 0xc0) == 0x80) {
|
||||
*uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
else if (s[0] < 0xf0) {
|
||||
if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
|
||||
*uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Invalid sequence, so just return the byte */
|
||||
*uc = *s;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef UTF8_UTIL_H
|
||||
#define UTF8_UTIL_H
|
||||
/**
|
||||
* UTF-8 utility functions
|
||||
*
|
||||
* (c) 2010 Steve Bennett <steveb@workware.net.au>
|
||||
*
|
||||
* See LICENCE for licence details.
|
||||
*/
|
||||
|
||||
#ifndef USE_UTF8
|
||||
#include <ctype.h>
|
||||
|
||||
/* No utf-8 support. 1 byte = 1 char */
|
||||
#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B))
|
||||
#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1)
|
||||
#define utf8_index(C, I) (I)
|
||||
#define utf8_charlen(C) 1
|
||||
|
||||
#else
|
||||
/**
|
||||
* Converts the given unicode codepoint (0 - 0xffff) to utf-8
|
||||
* and stores the result at 'p'.
|
||||
*
|
||||
* Returns the number of utf-8 characters (1-3).
|
||||
*/
|
||||
int utf8_fromunicode(char *p, unsigned short uc);
|
||||
|
||||
/**
|
||||
* Returns the length of the utf-8 sequence starting with 'c'.
|
||||
*
|
||||
* Returns 1-4, or -1 if this is not a valid start byte.
|
||||
*
|
||||
* Note that charlen=4 is not supported by the rest of the API.
|
||||
*/
|
||||
int utf8_charlen(int c);
|
||||
|
||||
/**
|
||||
* Returns the number of characters in the utf-8
|
||||
* string of the given byte length.
|
||||
*
|
||||
* Any bytes which are not part of an valid utf-8
|
||||
* sequence are treated as individual characters.
|
||||
*
|
||||
* The string *must* be null terminated.
|
||||
*
|
||||
* Does not support unicode code points > \uffff
|
||||
*/
|
||||
int utf8_strlen(const char *str, int bytelen);
|
||||
|
||||
/**
|
||||
* Returns the byte index of the given character in the utf-8 string.
|
||||
*
|
||||
* The string *must* be null terminated.
|
||||
*
|
||||
* This will return the byte length of a utf-8 string
|
||||
* if given the char length.
|
||||
*/
|
||||
int utf8_index(const char *str, int charindex);
|
||||
|
||||
/**
|
||||
* Returns the unicode codepoint corresponding to the
|
||||
* utf-8 sequence 'str'.
|
||||
*
|
||||
* Stores the result in *uc and returns the number of bytes
|
||||
* consumed.
|
||||
*
|
||||
* If 'str' is null terminated, then an invalid utf-8 sequence
|
||||
* at the end of the string will be returned as individual bytes.
|
||||
*
|
||||
* If it is not null terminated, the length *must* be checked first.
|
||||
*
|
||||
* Does not support unicode code points > \uffff
|
||||
*/
|
||||
int utf8_tounicode(const char *str, int *uc);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -40,12 +40,6 @@ int main(int argc, char** argv)
|
|||
// Registering an alternative Message Handler
|
||||
qInstallMsgHandler(Utils::messageHandler);
|
||||
|
||||
// Check number of parameters passed
|
||||
if (argc < 2) {
|
||||
Utils::showUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
app.setWindowIcon(QIcon(":/phantomjs-icon.png"));
|
||||
|
|
112
src/phantom.cpp
112
src/phantom.cpp
|
@ -41,11 +41,12 @@
|
|||
#include "utils.h"
|
||||
#include "webpage.h"
|
||||
#include "webserver.h"
|
||||
#include "repl.h"
|
||||
|
||||
|
||||
// public:
|
||||
Phantom::Phantom(QObject *parent)
|
||||
: QObject(parent)
|
||||
: REPLCompletable(parent)
|
||||
, m_terminated(false)
|
||||
, m_returnValue(0)
|
||||
, m_filesystem(0)
|
||||
|
@ -79,11 +80,6 @@ Phantom::Phantom(QObject *parent)
|
|||
m_page = new WebPage(this, &m_config, QUrl::fromLocalFile(m_config.scriptFile()));
|
||||
m_pages.append(m_page);
|
||||
|
||||
if (m_config.scriptFile().isEmpty()) {
|
||||
Utils::showUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_config.proxyHost().isEmpty()) {
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
} else {
|
||||
|
@ -126,7 +122,6 @@ Phantom::~Phantom()
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
QStringList Phantom::args() const
|
||||
{
|
||||
return m_config.scriptArgs();
|
||||
|
@ -152,20 +147,24 @@ bool Phantom::execute()
|
|||
if (m_terminated)
|
||||
return false;
|
||||
|
||||
if (m_config.scriptFile().isEmpty())
|
||||
return false;
|
||||
|
||||
if (m_config.debug())
|
||||
{
|
||||
if (!Utils::loadJSForDebug(m_config.scriptFile(), m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), m_config.remoteDebugAutorun())) {
|
||||
m_returnValue = -1;
|
||||
return false;
|
||||
}
|
||||
m_page->showInspector(m_config.remoteDebugPort());
|
||||
if (m_config.scriptFile().isEmpty()) {
|
||||
// REPL mode requested
|
||||
// Create the REPL: it will launch itself, no need to store this variable.
|
||||
REPL::getInstance(m_page->mainFrame(), this);
|
||||
} else {
|
||||
if (!Utils::injectJsInFrame(m_config.scriptFile(), m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), true)) {
|
||||
m_returnValue = -1;
|
||||
return false;
|
||||
// Load the User Script
|
||||
if (m_config.debug()) {
|
||||
// Debug enabled
|
||||
if (!Utils::loadJSForDebug(m_config.scriptFile(), m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), m_config.remoteDebugAutorun())) {
|
||||
m_returnValue = -1;
|
||||
return false;
|
||||
}
|
||||
m_page->showInspector(m_config.remoteDebugPort());
|
||||
} else {
|
||||
if (!Utils::injectJsInFrame(m_config.scriptFile(), m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), true)) {
|
||||
m_returnValue = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,9 +250,9 @@ bool Phantom::injectJs(const QString &jsFilePath)
|
|||
|
||||
void Phantom::exit(int code)
|
||||
{
|
||||
if (m_config.debug())
|
||||
if (m_config.debug()) {
|
||||
Terminal::instance()->cout("Phantom::exit() called but not quitting in debug mode.");
|
||||
else {
|
||||
} else {
|
||||
doExit(code);
|
||||
}
|
||||
}
|
||||
|
@ -263,34 +262,6 @@ void Phantom::debugExit(int code)
|
|||
doExit(code);
|
||||
}
|
||||
|
||||
|
||||
void Phantom::doExit(int code)
|
||||
{
|
||||
if (m_config.debug())
|
||||
{
|
||||
Utils::cleanupFromDebug();
|
||||
}
|
||||
|
||||
m_terminated = true;
|
||||
m_returnValue = code;
|
||||
qDeleteAll(m_pages);
|
||||
m_pages.clear();
|
||||
m_page = 0;
|
||||
QApplication::instance()->exit(code);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Phantom::onInitialized()
|
||||
{
|
||||
// Add 'phantom' object to the global scope
|
||||
m_page->mainFrame()->addToJavaScriptWindowObject("phantom", this);
|
||||
|
||||
// Bootstrap the PhantomJS scope
|
||||
m_page->mainFrame()->evaluateJavaScript(Utils::readResourceFileUtf8(":/bootstrap.js"));
|
||||
}
|
||||
|
||||
|
||||
// private slots:
|
||||
void Phantom::printConsoleMessage(const QString &message, int lineNumber, const QString &source)
|
||||
{
|
||||
|
@ -299,3 +270,44 @@ void Phantom::printConsoleMessage(const QString &message, int lineNumber, const
|
|||
msg = source + ":" + QString::number(lineNumber) + " " + msg;
|
||||
Terminal::instance()->cout(msg);
|
||||
}
|
||||
|
||||
void Phantom::onInitialized()
|
||||
{
|
||||
// Add 'phantom' object to the global scope
|
||||
m_page->mainFrame()->addToJavaScriptWindowObject("phantom", this);
|
||||
|
||||
// Bootstrap the PhantomJS scope
|
||||
m_page->mainFrame()->evaluateJavaScript(Utils::readResourceFileUtf8(":/bootstrap.js"));
|
||||
}
|
||||
|
||||
// private:
|
||||
void Phantom::doExit(int code)
|
||||
{
|
||||
if (m_config.debug())
|
||||
{
|
||||
Utils::cleanupFromDebug();
|
||||
}
|
||||
|
||||
emit aboutToExit(code);
|
||||
m_terminated = true;
|
||||
m_returnValue = code;
|
||||
qDeleteAll(m_pages);
|
||||
m_pages.clear();
|
||||
m_page = 0;
|
||||
QApplication::instance()->exit(code);
|
||||
}
|
||||
|
||||
void Phantom::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'phantom' object
|
||||
// properties
|
||||
addCompletion("args");
|
||||
addCompletion("defaultPageSettings");
|
||||
addCompletion("libraryPath");
|
||||
addCompletion("outputEncoding");
|
||||
addCompletion("scriptName");
|
||||
addCompletion("version");
|
||||
// functions
|
||||
addCompletion("exit");
|
||||
addCompletion("injectJs");
|
||||
}
|
||||
|
|
|
@ -33,14 +33,16 @@
|
|||
|
||||
#include <QtGui>
|
||||
|
||||
class WebPage;
|
||||
class WebServer;
|
||||
#include "csconverter.h"
|
||||
#include "filesystem.h"
|
||||
#include "encoding.h"
|
||||
#include "config.h"
|
||||
#include "replcompletable.h"
|
||||
|
||||
class Phantom: public QObject
|
||||
class WebPage;
|
||||
class WebServer;
|
||||
|
||||
class Phantom: public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList args READ args)
|
||||
|
@ -82,12 +84,16 @@ public slots:
|
|||
void exit(int code = 0);
|
||||
void debugExit(int code = 0);
|
||||
|
||||
signals:
|
||||
void aboutToExit(int code);
|
||||
|
||||
private slots:
|
||||
void printConsoleMessage(const QString &msg, int lineNumber, const QString &source);
|
||||
|
||||
void onInitialized();
|
||||
private:
|
||||
void doExit(int code);
|
||||
virtual void initCompletions();
|
||||
|
||||
Encoding m_scriptFileEnc;
|
||||
WebPage *m_page;
|
||||
|
|
|
@ -21,7 +21,9 @@ HEADERS += csconverter.h \
|
|||
filesystem.h \
|
||||
terminal.h \
|
||||
encoding.h \
|
||||
config.h
|
||||
config.h \
|
||||
repl.h \
|
||||
replcompletable.h
|
||||
|
||||
SOURCES += phantom.cpp \
|
||||
webpage.cpp \
|
||||
|
@ -34,17 +36,21 @@ SOURCES += phantom.cpp \
|
|||
filesystem.cpp \
|
||||
terminal.cpp \
|
||||
encoding.cpp \
|
||||
config.cpp
|
||||
config.cpp \
|
||||
repl.cpp \
|
||||
replcompletable.cpp
|
||||
|
||||
OTHER_FILES += usage.txt \
|
||||
bootstrap.js \
|
||||
configurator.js \
|
||||
modules/fs.js \
|
||||
modules/webpage.js \
|
||||
modules/webserver.js
|
||||
modules/webserver.js \
|
||||
repl.js
|
||||
|
||||
include(gif/gif.pri)
|
||||
include(mongoose/mongoose.pri)
|
||||
include(linenoise/linenoise.pri)
|
||||
|
||||
win32: RC_FILE = phantomjs_win.rc
|
||||
os2: RC_FILE = phantomjs_os2.rc
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
<file>modules/webpage.js</file>
|
||||
<file>modules/webserver.js</file>
|
||||
<file>modules/fs.js</file>
|
||||
<file>repl.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
This file is part of the PhantomJS project from Ofi Labs.
|
||||
|
||||
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.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 "repl.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QTimer>
|
||||
#include <QDir>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "consts.h"
|
||||
#include "terminal.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define PROMPT "phantomjs> "
|
||||
#define HISTORY_FILENAME "phantom_repl_history"
|
||||
|
||||
// Only with word characters, spaces and the dot ('.')
|
||||
// we can still attempt to offer a completion to the user
|
||||
#define REGEXP_NON_COMPLETABLE_CHARS "[^\\w\\s\\.]"
|
||||
|
||||
// JS Code to find possible completions
|
||||
#define JS_RETURN_POSSIBLE_COMPLETIONS "REPL._getCompletions(%1, \"%2\");"
|
||||
|
||||
// JS Code to evaluate User Input and prettify the expression result
|
||||
#define JS_EVAL_USER_INPUT \
|
||||
"try { " \
|
||||
"REPL._lastEval = eval(\"%1\");" \
|
||||
"console.log(JSON.stringify(REPL._lastEval, REPL._expResStringifyReplacer, ' ')); " \
|
||||
"} catch(e) { " \
|
||||
"if (e instanceof TypeError) { " \
|
||||
"console.error(\"'%1' is a cyclic structure\"); " \
|
||||
"} else { " \
|
||||
"console.error(e.message);" \
|
||||
"}" \
|
||||
"} "
|
||||
|
||||
|
||||
// public:
|
||||
bool REPL::instanceExists()
|
||||
{
|
||||
return REPL::getInstance() != NULL;
|
||||
}
|
||||
|
||||
REPL *REPL::getInstance(QWebFrame *webframe, Phantom *parent)
|
||||
{
|
||||
static REPL *singleton = NULL;
|
||||
if (!singleton && webframe && parent) {
|
||||
// This will create the singleton only when all the parameters are given
|
||||
singleton = new REPL(webframe, parent);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
// private:
|
||||
REPL::REPL(QWebFrame *webframe, Phantom *parent)
|
||||
: QObject(parent),
|
||||
m_looping(true)
|
||||
{
|
||||
m_webframe = webframe;
|
||||
m_parentPhantom = parent;
|
||||
m_historyFilepath = QString("%1/%2").arg(
|
||||
QDesktopServices::storageLocation(QDesktopServices::DataLocation),
|
||||
HISTORY_FILENAME).toLocal8Bit();
|
||||
|
||||
// Ensure the location for the history file exists
|
||||
QDir().mkpath(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
|
||||
|
||||
// Listen for Phantom exit(ing)
|
||||
connect(m_parentPhantom, SIGNAL(aboutToExit(int)), this, SLOT(stopLoop(int)));
|
||||
|
||||
// Set the static callback to offer Completions to the User
|
||||
linenoiseSetCompletionCallback(REPL::offerCompletion);
|
||||
|
||||
// Inject REPL utility functions
|
||||
m_webframe->evaluateJavaScript(Utils::readResourceFileUtf8(":/repl.js"));
|
||||
|
||||
// Start the REPL's loop
|
||||
QTimer::singleShot(0, this, SLOT(startLoop()));
|
||||
}
|
||||
|
||||
void REPL::offerCompletion(const char *buf, linenoiseCompletions *lc)
|
||||
{
|
||||
// IF there is a ( or ), then do nothing (we can't complete)
|
||||
QString buffer(buf);
|
||||
int lastIndexOfDot = -1;
|
||||
QString toInspect, toComplete;
|
||||
QRegExp nonCompletableChars(REGEXP_NON_COMPLETABLE_CHARS);
|
||||
|
||||
// If we encounter a non acceptable character (see above)
|
||||
if (buffer.contains(nonCompletableChars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decompose what user typed so far in 2 parts: what toInspect and what toComplete.
|
||||
lastIndexOfDot = buffer.lastIndexOf('.');
|
||||
if (lastIndexOfDot > -1) {
|
||||
toInspect = buffer.left(lastIndexOfDot);
|
||||
toComplete = buffer.right(buffer.length() - lastIndexOfDot -1);
|
||||
} else {
|
||||
// Nothing to inspect: use the global "window" object
|
||||
toInspect = "window";
|
||||
toComplete = buffer;
|
||||
}
|
||||
|
||||
// This will return an array of String with the possible completions
|
||||
QStringList completions = REPL::getInstance()->m_webframe->evaluateJavaScript(
|
||||
QString(JS_RETURN_POSSIBLE_COMPLETIONS).arg(
|
||||
toInspect,
|
||||
toComplete)
|
||||
).toStringList();
|
||||
|
||||
foreach (QString c, completions) {
|
||||
if (lastIndexOfDot > -1) {
|
||||
// Preserve the "toInspect" portion of the string to complete
|
||||
linenoiseAddCompletion(lc, QString("%1.%2").arg(toInspect, c).toLocal8Bit().data());
|
||||
} else {
|
||||
linenoiseAddCompletion(lc, c.toLocal8Bit().data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private slots:
|
||||
void REPL::startLoop()
|
||||
{
|
||||
char *userInput;
|
||||
|
||||
// Load REPL history
|
||||
linenoiseHistoryLoad(m_historyFilepath.data()); //< requires "char *"
|
||||
while(m_looping && (userInput = linenoise(PROMPT)) != NULL) {
|
||||
if (userInput[0] != '\0') {
|
||||
// Send the user input to the main Phantom frame for evaluation
|
||||
m_webframe->evaluateJavaScript(
|
||||
QString(JS_EVAL_USER_INPUT).arg(
|
||||
QString(userInput).replace('"', "\\\"")));
|
||||
|
||||
// Save command in the REPL history
|
||||
linenoiseHistoryAdd(userInput);
|
||||
linenoiseHistorySave(m_historyFilepath.data()); //< requires "char *"
|
||||
}
|
||||
free(userInput);
|
||||
}
|
||||
|
||||
// If still "looping", close Phantom (usually caused by "CTRL+C" / "CTRL+D")
|
||||
if (m_looping) {
|
||||
m_parentPhantom->exit();
|
||||
}
|
||||
}
|
||||
|
||||
void REPL::stopLoop(const int code)
|
||||
{
|
||||
Q_UNUSED(code);
|
||||
m_looping = false;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
This file is part of the PhantomJS project from Ofi Labs.
|
||||
|
||||
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.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 REPL_H
|
||||
#define REPL_H
|
||||
|
||||
#include <QWebFrame>
|
||||
|
||||
#include "phantom.h"
|
||||
|
||||
// Linenoise is a C Library: we need to externalise it's symbols for linkage
|
||||
extern "C" {
|
||||
#include "linenoise.h"
|
||||
}
|
||||
|
||||
/**
|
||||
* REPL. Read–Eval–Print Loop.
|
||||
*
|
||||
* This class realises the REPL functionality within PhantomJS.
|
||||
* It's a Singleton: invoke "REPL::getInstance(QWebFrame *, Phantom *) to
|
||||
* create the first-and-only instance, or no parameter to get the singleton
|
||||
* if previously created.
|
||||
*
|
||||
* It's based the Linenoise library (https://github.com/tadmarshall/linenoise).
|
||||
* More info about REPL: http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop
|
||||
*/
|
||||
class REPL: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static bool instanceExists();
|
||||
static REPL *getInstance(QWebFrame *webframe = NULL, Phantom *parent = NULL);
|
||||
|
||||
private:
|
||||
REPL(QWebFrame *webframe, Phantom *parent);
|
||||
static void offerCompletion(const char *buf, linenoiseCompletions *lc);
|
||||
|
||||
private slots:
|
||||
void startLoop();
|
||||
void stopLoop(const int code);
|
||||
|
||||
private:
|
||||
QWebFrame *m_webframe;
|
||||
Phantom *m_parentPhantom;
|
||||
bool m_looping;
|
||||
QByteArray m_historyFilepath;
|
||||
};
|
||||
|
||||
#endif // REPL_H
|
|
@ -0,0 +1,98 @@
|
|||
/*jslint sloppy: true, nomen: true */
|
||||
|
||||
/*
|
||||
This file is part of the PhantomJS project from Ofi Labs.
|
||||
|
||||
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.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 REPL = REPL || {};
|
||||
|
||||
/**
|
||||
* Return the Completions of the Object, applying the prefix
|
||||
*
|
||||
* @param obj Object to get completions of
|
||||
* @param prefix Limit completions to the one starting with this prefix
|
||||
*/
|
||||
REPL._getCompletions = function (obj, prefix) {
|
||||
var completions = [];
|
||||
|
||||
// If the given object is "(REPL)Completable", just return it's completions
|
||||
if (obj._isCompletable && obj._isCompletable() === true) {
|
||||
completions = obj._getCompletions(prefix || "");
|
||||
} else {
|
||||
// It's a JS Native Object: build the list of completions manually
|
||||
for (k in obj) {
|
||||
if (obj.hasOwnProperty(k) && k.indexOf(prefix || "") === 0) {
|
||||
completions.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
};
|
||||
|
||||
/**
|
||||
* This utility function is used to pretty-print the result of an expression.
|
||||
* @see https://developer.mozilla.org/En/Using_native_JSON#The_replacer_parameter
|
||||
*
|
||||
* @param k Property key name - empty string if it's the object being stringified
|
||||
* @param v Property value
|
||||
*/
|
||||
REPL._expResStringifyReplacer = function (k, v) {
|
||||
var i, iarr,
|
||||
mock = {},
|
||||
funcToStr = "[Function]";
|
||||
|
||||
// If the result of the last evaluated expression is a REPLCompletable object
|
||||
if (k === "" //< only first level of recursive calls
|
||||
&& REPL._lastEval
|
||||
&& REPL._lastEval._isCompletable
|
||||
&& REPL._lastEval._isCompletable() === true) {
|
||||
|
||||
// Get all the completions for the REPLCompletable object we are going to pretty-print
|
||||
iarr = REPL._lastEval._getCompletions("");
|
||||
for (i in iarr) {
|
||||
if (typeof(v[iarr[i]]) !== "undefined") {
|
||||
// add a reference to this "real" property into the mock object
|
||||
mock[iarr[i]] = v[iarr[i]];
|
||||
} else {
|
||||
// add a "function" for this "shimmed" property into the mock object
|
||||
mock[iarr[i]] = funcToStr;
|
||||
}
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
// Else, just act normally
|
||||
if (typeof(v) === "function") {
|
||||
// Normally functions are ignored by JSON.stringify
|
||||
return funcToStr;
|
||||
}
|
||||
|
||||
return v;
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
#include "replcompletable.h"
|
||||
|
||||
// public:
|
||||
REPLCompletable::REPLCompletable(QObject *parent)
|
||||
: QObject(parent),
|
||||
mCompletionsInitialised(false)
|
||||
{ }
|
||||
|
||||
REPLCompletable::~REPLCompletable()
|
||||
{ }
|
||||
|
||||
bool REPLCompletable::_isCompletable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList REPLCompletable::_getCompletions(const QString &prefix)
|
||||
{
|
||||
// First time this method is invoked, initialise the completions (lazy init)
|
||||
if (!mCompletionsInitialised) {
|
||||
initCompletions();
|
||||
mCompletionsInitialised = true;
|
||||
}
|
||||
|
||||
// If no prefix provided?
|
||||
if (prefix.isEmpty()) {
|
||||
// Return all the possible completions
|
||||
return REPLCompletable::getCompletionsIndex()->values(
|
||||
this->metaObject()->className());
|
||||
}
|
||||
|
||||
// make a key to store the (new) completions list
|
||||
QString cacheKey = QString("%1-%2").arg(this->metaObject()->className()).arg(prefix);
|
||||
// If a list of completion withi this key is not already in the cache
|
||||
if (!getCompletionsCache()->contains(cacheKey)) {
|
||||
// Loop over the completions and pick the one that match the given prefix
|
||||
QStringList allCompletions = REPLCompletable::getCompletionsIndex()->values(
|
||||
this->metaObject()->className());
|
||||
QStringList *matchingPrefixCompletions = new QStringList();
|
||||
|
||||
QStringList::iterator i;
|
||||
for (i = allCompletions.begin(); i != allCompletions.end(); ++i) {
|
||||
if (((QString) *i).startsWith(prefix)) {
|
||||
matchingPrefixCompletions->append((QString) *i);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the result in the cache
|
||||
getCompletionsCache()->insert(cacheKey, matchingPrefixCompletions);
|
||||
}
|
||||
|
||||
return *(getCompletionsCache()->object(cacheKey));
|
||||
}
|
||||
|
||||
// protected:
|
||||
void REPLCompletable::addCompletion(const char *completion)
|
||||
{
|
||||
addCompletion(QString(completion));
|
||||
}
|
||||
|
||||
void REPLCompletable::addCompletion(QString completion)
|
||||
{
|
||||
// Accept a completion only if it's unique per MetaObject ClassName
|
||||
if (!REPLCompletable::getCompletionsIndex()->contains(this->metaObject()->className(), completion)) {
|
||||
REPLCompletable::getCompletionsIndex()->insert(this->metaObject()->className(), completion);
|
||||
}
|
||||
}
|
||||
|
||||
// private:
|
||||
QMultiHash<const char *, QString> *REPLCompletable::getCompletionsIndex()
|
||||
{
|
||||
static QMultiHash<const char *, QString> *compIndex = NULL;
|
||||
if (!compIndex) {
|
||||
compIndex = new QMultiHash<const char *, QString>();
|
||||
}
|
||||
return compIndex;
|
||||
}
|
||||
|
||||
QCache<QString, QStringList> *REPLCompletable::getCompletionsCache()
|
||||
{
|
||||
static QCache<QString, QStringList> *compCache = NULL;
|
||||
if (!compCache) {
|
||||
compCache = new QCache<QString, QStringList>();
|
||||
}
|
||||
return compCache;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef REPLCOMPLETABLE_H
|
||||
#define REPLCOMPLETABLE_H
|
||||
|
||||
#include <QMultiHash>
|
||||
#include <QStringList>
|
||||
#include <QCache>
|
||||
|
||||
/**
|
||||
* This subclass of QObject is used by the REPL to better control
|
||||
* what is "shown" of a QObject exposed in the Javascript Runtime.
|
||||
*
|
||||
* By default the JS environment will see all the slots and the Q_INVOKABLE
|
||||
* of a "exposed" QObject. But also some extra QObject specific methods
|
||||
* that, in our case, we prefer not to list during REPL autocompletion
|
||||
* listing or expression result prettyfication.
|
||||
*/
|
||||
class REPLCompletable : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
REPLCompletable(QObject *parent = 0);
|
||||
virtual ~REPLCompletable();
|
||||
|
||||
Q_INVOKABLE bool _isCompletable();
|
||||
Q_INVOKABLE QStringList _getCompletions(const QString &prefixToComplete);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Used by sublcasses to register a possible "completion".
|
||||
*
|
||||
* @param completion Array of characters representing a function/property
|
||||
* that will be listed as possible completion
|
||||
*/
|
||||
void addCompletion(const char *completion);
|
||||
/**
|
||||
* Used by sublcasses to register a possible "completion".
|
||||
*
|
||||
* @param completion String representing a function/property
|
||||
* that will be listed as possible completion
|
||||
*/
|
||||
void addCompletion(QString completion);
|
||||
|
||||
private:
|
||||
/**
|
||||
* This is where subclasses should use REPLCompletable#addCompletion(...)
|
||||
* to declare/register their completions for the REPL.
|
||||
* This ensures that ONLY if a REPL is actually requested by the user,
|
||||
* we bother registering the completion strings.
|
||||
*/
|
||||
virtual void initCompletions() = 0;
|
||||
|
||||
static QMultiHash<const char *, QString> *getCompletionsIndex();
|
||||
static QCache<QString, QStringList> *getCompletionsCache();
|
||||
|
||||
private:
|
||||
bool mCompletionsInitialised;
|
||||
};
|
||||
|
||||
#endif // REPLCOMPLETABLE_H
|
|
@ -58,7 +58,8 @@
|
|||
|
||||
class CustomPage: public QWebPage
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CustomPage(WebPage *parent = 0)
|
||||
: QWebPage(parent)
|
||||
|
@ -103,8 +104,9 @@ private:
|
|||
friend class WebPage;
|
||||
};
|
||||
|
||||
|
||||
WebPage::WebPage(QObject *parent, const Config *config, const QUrl &baseUrl)
|
||||
: QObject(parent)
|
||||
: REPLCompletable(parent)
|
||||
{
|
||||
setObjectName("WebPage");
|
||||
m_webPage = new CustomPage(this);
|
||||
|
@ -631,4 +633,32 @@ void WebPage::sendEvent(const QString &type, const QVariant &arg1, const QVarian
|
|||
}
|
||||
}
|
||||
|
||||
void WebPage::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'webpage' object
|
||||
// properties
|
||||
addCompletion("clipRect");
|
||||
addCompletion("content");
|
||||
addCompletion("libraryPath");
|
||||
addCompletion("settings");
|
||||
addCompletion("viewportSize");
|
||||
// functions
|
||||
addCompletion("evaluate");
|
||||
addCompletion("includeJs");
|
||||
addCompletion("injectJs");
|
||||
addCompletion("open");
|
||||
addCompletion("release");
|
||||
addCompletion("render");
|
||||
addCompletion("sendEvent");
|
||||
addCompletion("uploadFile");
|
||||
// callbacks
|
||||
addCompletion("onAlert");
|
||||
addCompletion("onConsoleMessage");
|
||||
addCompletion("onInitialized");
|
||||
addCompletion("onLoadStarted");
|
||||
addCompletion("onLoadFinished");
|
||||
addCompletion("onResourceRequested");
|
||||
addCompletion("onResourceReceived");
|
||||
}
|
||||
|
||||
#include "webpage.moc"
|
||||
|
|
|
@ -35,13 +35,15 @@
|
|||
#include <QVariantMap>
|
||||
#include <QWebPage>
|
||||
|
||||
#include "replcompletable.h"
|
||||
|
||||
class Config;
|
||||
class CustomPage;
|
||||
class NetworkAccessManager;
|
||||
class QWebInspector;
|
||||
class Phantom;
|
||||
|
||||
class WebPage: public QObject
|
||||
class WebPage: public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString content READ content WRITE setContent)
|
||||
|
@ -106,6 +108,17 @@ signals:
|
|||
private slots:
|
||||
void finish(bool ok);
|
||||
|
||||
private:
|
||||
QImage renderImage();
|
||||
bool renderPdf(const QString &fileName);
|
||||
void applySettings(const QVariantMap &defaultSettings);
|
||||
QString userAgent() const;
|
||||
|
||||
void emitAlert(const QString &msg);
|
||||
void emitConsoleMessage(const QString &msg, int lineNumber, const QString &source);
|
||||
|
||||
virtual void initCompletions();
|
||||
|
||||
private:
|
||||
CustomPage *m_webPage;
|
||||
NetworkAccessManager *m_networkAccessManager;
|
||||
|
@ -116,14 +129,6 @@ private:
|
|||
QString m_libraryPath;
|
||||
QWebInspector* m_inspector;
|
||||
|
||||
QImage renderImage();
|
||||
bool renderPdf(const QString &fileName);
|
||||
void applySettings(const QVariantMap &defaultSettings);
|
||||
QString userAgent() const;
|
||||
|
||||
void emitAlert(const QString &msg);
|
||||
void emitConsoleMessage(const QString &msg, int lineNumber, const QString &source);
|
||||
|
||||
friend class Phantom;
|
||||
friend class CustomPage;
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ static void *callback(mg_event event,
|
|||
}
|
||||
|
||||
WebServer::WebServer(QObject *parent, Config *config)
|
||||
: QObject(parent)
|
||||
: REPLCompletable(parent)
|
||||
, m_config(config)
|
||||
, m_ctx(0)
|
||||
{
|
||||
|
@ -216,11 +216,23 @@ void WebServer::handleRequest(mg_event event, mg_connection *conn, const mg_requ
|
|||
*handled = false;
|
||||
}
|
||||
|
||||
void WebServer::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'webpage' object
|
||||
// properties
|
||||
addCompletion("clipRect");
|
||||
// functions
|
||||
addCompletion("listen");
|
||||
addCompletion("close");
|
||||
// callbacks
|
||||
addCompletion("onNewRequest");
|
||||
}
|
||||
|
||||
|
||||
//BEGIN WebServerResponse
|
||||
|
||||
WebServerResponse::WebServerResponse(mg_connection *conn)
|
||||
: QObject()
|
||||
: REPLCompletable()
|
||||
, m_conn(conn)
|
||||
, m_statusCode(200)
|
||||
, m_headersSent(false)
|
||||
|
@ -389,4 +401,15 @@ void WebServerResponse::setHeaders(const QVariantMap &headers)
|
|||
m_headers = headers;
|
||||
}
|
||||
|
||||
void WebServerResponse::initCompletions()
|
||||
{
|
||||
// Add completion for the Dynamic Properties of the 'webpage' object
|
||||
// properties
|
||||
addCompletion("statusCode");
|
||||
addCompletion("headers");
|
||||
// functions
|
||||
addCompletion("writeHead");
|
||||
addCompletion("write");
|
||||
}
|
||||
|
||||
//END WebServerResponse
|
||||
|
|
|
@ -31,12 +31,12 @@
|
|||
#ifndef WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
///TODO: is this ok, or should it be put into .cpp
|
||||
/// can be done by introducing a WebServerPrivate *d;
|
||||
#include "mongoose.h"
|
||||
#include "replcompletable.h"
|
||||
|
||||
class Config;
|
||||
|
||||
|
@ -47,10 +47,10 @@ class WebServerResponse;
|
|||
*
|
||||
* see also: modules/webserver.js
|
||||
*/
|
||||
class WebServer : public QObject
|
||||
class WebServer : public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString port READ port);
|
||||
Q_PROPERTY(QString port READ port)
|
||||
|
||||
public:
|
||||
WebServer(QObject *parent, Config *config);
|
||||
|
@ -85,6 +85,9 @@ private slots:
|
|||
void handleRequest(mg_event event, mg_connection* conn, const mg_request_info* request,
|
||||
bool* handled);
|
||||
|
||||
private:
|
||||
virtual void initCompletions();
|
||||
|
||||
private:
|
||||
Config *m_config;
|
||||
mg_context *m_ctx;
|
||||
|
@ -95,12 +98,12 @@ private:
|
|||
/**
|
||||
* Outgoing HTTP response to client.
|
||||
*/
|
||||
class WebServerResponse : public QObject
|
||||
class WebServerResponse : public REPLCompletable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int statusCode READ statusCode WRITE setStatusCode);
|
||||
Q_PROPERTY(QVariantMap headers READ headers WRITE setHeaders);
|
||||
Q_PROPERTY(int statusCode READ statusCode WRITE setStatusCode)
|
||||
Q_PROPERTY(QVariantMap headers READ headers WRITE setHeaders)
|
||||
public:
|
||||
WebServerResponse(mg_connection *conn);
|
||||
|
||||
|
@ -135,6 +138,9 @@ public slots:
|
|||
/// set all headers
|
||||
void setHeaders(const QVariantMap &headers);
|
||||
|
||||
private:
|
||||
virtual void initCompletions();
|
||||
|
||||
private:
|
||||
mg_connection *m_conn;
|
||||
int m_statusCode;
|
||||
|
|
Loading…
Reference in New Issue