summaryrefslogtreecommitdiff
path: root/ext/readline
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2013-03-14 05:42:27 +0000
committer <>2013-04-03 16:25:08 +0000
commitc4dd7a1a684490673e25aaf4fabec5df138854c4 (patch)
tree4d57c44caae4480efff02b90b9be86f44bf25409 /ext/readline
downloadphp2-c4dd7a1a684490673e25aaf4fabec5df138854c4.tar.gz
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'ext/readline')
-rw-r--r--ext/readline/CREDITS2
-rw-r--r--ext/readline/README.libedit4
-rw-r--r--ext/readline/config.m4124
-rw-r--r--ext/readline/php_readline.h39
-rw-r--r--ext/readline/readline.c650
-rw-r--r--ext/readline/readline_cli.c767
-rw-r--r--ext/readline/readline_cli.h43
-rw-r--r--ext/readline/tests/readline_add_history_001.phpt32
-rw-r--r--ext/readline/tests/readline_callback_handler_install_001.phpt24
-rw-r--r--ext/readline/tests/readline_callback_handler_remove_001.phpt20
-rw-r--r--ext/readline/tests/readline_clear_history_001.phpt16
-rw-r--r--ext/readline/tests/readline_completion_function_001.phpt25
-rw-r--r--ext/readline/tests/readline_info_001.phpt45
-rw-r--r--ext/readline/tests/readline_list_history_001.phpt17
-rw-r--r--ext/readline/tests/readline_read_history_001.phpt30
-rw-r--r--ext/readline/tests/readline_write_history_001.phpt26
16 files changed, 1864 insertions, 0 deletions
diff --git a/ext/readline/CREDITS b/ext/readline/CREDITS
new file mode 100644
index 0000000..02e44a4
--- /dev/null
+++ b/ext/readline/CREDITS
@@ -0,0 +1,2 @@
+Readline
+Thies C. Arntzen
diff --git a/ext/readline/README.libedit b/ext/readline/README.libedit
new file mode 100644
index 0000000..a193710
--- /dev/null
+++ b/ext/readline/README.libedit
@@ -0,0 +1,4 @@
+This library can be built with libedit - non-GPL drop-in readline replacement.
+Libedit can be obtained from http://sourceforge.net/projects/libedit/
+It is taken from NetBSD (http://www.netbsd.org/) CVS repository and modified
+to work as stand-alone library.
diff --git a/ext/readline/config.m4 b/ext/readline/config.m4
new file mode 100644
index 0000000..b1cb0b6
--- /dev/null
+++ b/ext/readline/config.m4
@@ -0,0 +1,124 @@
+dnl
+dnl $Id$
+dnl
+
+PHP_ARG_WITH(libedit,for libedit readline replacement,
+[ --with-libedit[=DIR] Include libedit readline replacement (CLI/CGI only)])
+
+if test "$PHP_LIBEDIT" = "no"; then
+ PHP_ARG_WITH(readline,for readline support,
+ [ --with-readline[=DIR] Include readline support (CLI/CGI only)])
+else
+ dnl "register" the --with-readline option to preven invalid "unknown configure option" warning
+ php_with_readline=no
+fi
+
+if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then
+ for i in $PHP_READLINE /usr/local /usr; do
+ test -f $i/include/readline/readline.h && READLINE_DIR=$i && break
+ done
+
+ if test -z "$READLINE_DIR"; then
+ AC_MSG_ERROR(Please reinstall readline - I cannot find readline.h)
+ fi
+
+ PHP_ADD_INCLUDE($READLINE_DIR/include)
+
+ PHP_READLINE_LIBS=""
+ AC_CHECK_LIB(ncurses, tgetent,
+ [
+ PHP_ADD_LIBRARY(ncurses,,READLINE_SHARED_LIBADD)
+ PHP_READLINE_LIBS="$PHP_READLINE_LIBS -lncurses"
+ ],[
+ AC_CHECK_LIB(termcap, tgetent,
+ [
+ PHP_ADD_LIBRARY(termcap,,READLINE_SHARED_LIBADD)
+ PHP_READLINE_LIBS="$PHP_READLINE_LIBS -ltermcap"
+ ])
+ ])
+
+ PHP_CHECK_LIBRARY(readline, readline,
+ [
+ PHP_ADD_LIBRARY_WITH_PATH(readline, $READLINE_DIR/$PHP_LIBDIR, READLINE_SHARED_LIBADD)
+ ], [
+ AC_MSG_ERROR(readline library not found)
+ ], [
+ -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS
+ ])
+
+ PHP_CHECK_LIBRARY(readline, rl_pending_input,
+ [], [
+ AC_MSG_ERROR([invalid readline installation detected. Try --with-libedit instead.])
+ ], [
+ -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS
+ ])
+
+ PHP_CHECK_LIBRARY(readline, rl_callback_read_char,
+ [
+ AC_DEFINE(HAVE_RL_CALLBACK_READ_CHAR, 1, [ ])
+ ],[],[
+ -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS
+ ])
+
+ PHP_CHECK_LIBRARY(edit, rl_on_new_line,
+ [
+ AC_DEFINE(HAVE_RL_ON_NEW_LINE, 1, [ ])
+ ],[],[
+ -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS
+ ])
+
+ AC_DEFINE(HAVE_LIBREADLINE, 1, [ ])
+
+elif test "$PHP_LIBEDIT" != "no"; then
+
+ for i in $PHP_LIBEDIT /usr/local /usr; do
+ test -f $i/include/editline/readline.h && LIBEDIT_DIR=$i && break
+ done
+
+ if test -z "$LIBEDIT_DIR"; then
+ AC_MSG_ERROR(Please reinstall libedit - I cannot find readline.h)
+ fi
+
+ PHP_ADD_INCLUDE($LIBEDIT_DIR/include)
+
+ AC_CHECK_LIB(ncurses, tgetent,
+ [
+ PHP_ADD_LIBRARY(ncurses,,READLINE_SHARED_LIBADD)
+ ],[
+ AC_CHECK_LIB(termcap, tgetent,
+ [
+ PHP_ADD_LIBRARY(termcap,,READLINE_SHARED_LIBADD)
+ ])
+ ])
+
+ PHP_CHECK_LIBRARY(edit, readline,
+ [
+ PHP_ADD_LIBRARY_WITH_PATH(edit, $LIBEDIT_DIR/$PHP_LIBDIR, READLINE_SHARED_LIBADD)
+ ], [
+ AC_MSG_ERROR(edit library required by readline not found)
+ ], [
+ -L$READLINE_DIR/$PHP_LIBDIR
+ ])
+
+ PHP_CHECK_LIBRARY(edit, rl_callback_read_char,
+ [
+ AC_DEFINE(HAVE_RL_CALLBACK_READ_CHAR, 1, [ ])
+ ],[],[
+ -L$READLINE_DIR/$PHP_LIBDIR
+ ])
+
+ PHP_CHECK_LIBRARY(edit, rl_on_new_line,
+ [
+ AC_DEFINE(HAVE_RL_ON_NEW_LINE, 1, [ ])
+ ],[],[
+ -L$READLINE_DIR/$PHP_LIBDIR
+ ])
+
+ AC_DEFINE(HAVE_LIBEDIT, 1, [ ])
+fi
+
+if test "$PHP_READLINE" != "no" || test "$PHP_LIBEDIT" != "no"; then
+ AC_CHECK_FUNCS([rl_completion_matches])
+ PHP_NEW_EXTENSION(readline, readline.c readline_cli.c, $ext_shared, cli)
+ PHP_SUBST(READLINE_SHARED_LIBADD)
+fi
diff --git a/ext/readline/php_readline.h b/ext/readline/php_readline.h
new file mode 100644
index 0000000..a9f8287
--- /dev/null
+++ b/ext/readline/php_readline.h
@@ -0,0 +1,39 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Thies C. Arntzen <thies@thieso.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_READLINE_H
+#define PHP_READLINE_H
+
+#if HAVE_LIBREADLINE || HAVE_LIBEDIT
+#ifdef ZTS
+#warning Readline module will *NEVER* be thread-safe
+#endif
+
+extern zend_module_entry readline_module_entry;
+#define phpext_readline_ptr &readline_module_entry
+
+#else
+
+#define phpext_readline_ptr NULL
+
+#endif /* HAVE_LIBREADLINE */
+
+#endif /* PHP_READLINE_H */
+
diff --git a/ext/readline/readline.c b/ext/readline/readline.c
new file mode 100644
index 0000000..3148f80
--- /dev/null
+++ b/ext/readline/readline.c
@@ -0,0 +1,650 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Thies C. Arntzen <thies@thieso.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* {{{ includes & prototypes */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_readline.h"
+#include "readline_cli.h"
+
+#if HAVE_LIBREADLINE || HAVE_LIBEDIT
+
+#ifndef HAVE_RL_COMPLETION_MATCHES
+#define rl_completion_matches completion_matches
+#endif
+
+#ifdef HAVE_LIBEDIT
+#include <editline/readline.h>
+#else
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+PHP_FUNCTION(readline);
+PHP_FUNCTION(readline_add_history);
+PHP_FUNCTION(readline_info);
+PHP_FUNCTION(readline_clear_history);
+#ifndef HAVE_LIBEDIT
+PHP_FUNCTION(readline_list_history);
+#endif
+PHP_FUNCTION(readline_read_history);
+PHP_FUNCTION(readline_write_history);
+PHP_FUNCTION(readline_completion_function);
+
+#if HAVE_RL_CALLBACK_READ_CHAR
+PHP_FUNCTION(readline_callback_handler_install);
+PHP_FUNCTION(readline_callback_read_char);
+PHP_FUNCTION(readline_callback_handler_remove);
+PHP_FUNCTION(readline_redisplay);
+PHP_FUNCTION(readline_on_new_line);
+
+static zval *_prepped_callback = NULL;
+
+#endif
+
+static zval *_readline_completion = NULL;
+static zval _readline_array;
+
+PHP_MINIT_FUNCTION(readline);
+PHP_MSHUTDOWN_FUNCTION(readline);
+PHP_RSHUTDOWN_FUNCTION(readline);
+PHP_MINFO_FUNCTION(readline);
+
+/* }}} */
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline, 0, 0, 0)
+ ZEND_ARG_INFO(0, prompt)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_info, 0, 0, 0)
+ ZEND_ARG_INFO(0, varname)
+ ZEND_ARG_INFO(0, newvalue)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_add_history, 0, 0, 1)
+ ZEND_ARG_INFO(0, prompt)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_readline_clear_history, 0)
+ZEND_END_ARG_INFO()
+
+#ifndef HAVE_LIBEDIT
+ZEND_BEGIN_ARG_INFO(arginfo_readline_list_history, 0)
+ZEND_END_ARG_INFO()
+#endif
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_read_history, 0, 0, 0)
+ ZEND_ARG_INFO(0, filename)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_write_history, 0, 0, 0)
+ ZEND_ARG_INFO(0, filename)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_completion_function, 0, 0, 1)
+ ZEND_ARG_INFO(0, funcname)
+ZEND_END_ARG_INFO()
+
+#if HAVE_RL_CALLBACK_READ_CHAR
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_callback_handler_install, 0, 0, 2)
+ ZEND_ARG_INFO(0, prompt)
+ ZEND_ARG_INFO(0, callback)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_read_char, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_readline_callback_handler_remove, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_readline_redisplay, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_readline_on_new_line, 0)
+ZEND_END_ARG_INFO()
+#endif
+/* }}} */
+
+/* {{{ module stuff */
+static const zend_function_entry php_readline_functions[] = {
+ PHP_FE(readline, arginfo_readline)
+ PHP_FE(readline_info, arginfo_readline_info)
+ PHP_FE(readline_add_history, arginfo_readline_add_history)
+ PHP_FE(readline_clear_history, arginfo_readline_clear_history)
+#ifndef HAVE_LIBEDIT
+ PHP_FE(readline_list_history, arginfo_readline_list_history)
+#endif
+ PHP_FE(readline_read_history, arginfo_readline_read_history)
+ PHP_FE(readline_write_history, arginfo_readline_write_history)
+ PHP_FE(readline_completion_function,arginfo_readline_completion_function)
+#if HAVE_RL_CALLBACK_READ_CHAR
+ PHP_FE(readline_callback_handler_install, arginfo_readline_callback_handler_install)
+ PHP_FE(readline_callback_read_char, arginfo_readline_callback_read_char)
+ PHP_FE(readline_callback_handler_remove, arginfo_readline_callback_handler_remove)
+ PHP_FE(readline_redisplay, arginfo_readline_redisplay)
+#endif
+#if HAVE_RL_ON_NEW_LINE
+ PHP_FE(readline_on_new_line, arginfo_readline_on_new_line)
+#endif
+ PHP_FE_END
+};
+
+zend_module_entry readline_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "readline",
+ php_readline_functions,
+ PHP_MINIT(readline),
+ PHP_MSHUTDOWN(readline),
+ NULL,
+ PHP_RSHUTDOWN(readline),
+ PHP_MINFO(readline),
+ PHP_VERSION,
+ STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_READLINE
+ZEND_GET_MODULE(readline)
+#endif
+
+PHP_MINIT_FUNCTION(readline)
+{
+ using_history();
+ return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU);
+}
+
+PHP_MSHUTDOWN_FUNCTION(readline)
+{
+ return PHP_MSHUTDOWN(cli_readline)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
+}
+
+PHP_RSHUTDOWN_FUNCTION(readline)
+{
+ if (_readline_completion) {
+ zval_dtor(_readline_completion);
+ FREE_ZVAL(_readline_completion);
+ }
+#if HAVE_RL_CALLBACK_READ_CHAR
+ if (_prepped_callback) {
+ rl_callback_handler_remove();
+ zval_ptr_dtor(&_prepped_callback);
+ _prepped_callback = 0;
+ }
+#endif
+
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(readline)
+{
+ PHP_MINFO(cli_readline)(ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU);
+}
+
+/* }}} */
+
+/* {{{ proto string readline([string prompt])
+ Reads a line */
+PHP_FUNCTION(readline)
+{
+ char *prompt = NULL;
+ int prompt_len;
+ char *result;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &prompt, &prompt_len)) {
+ RETURN_FALSE;
+ }
+
+ result = readline(prompt);
+
+ if (! result) {
+ RETURN_FALSE;
+ } else {
+ RETVAL_STRING(result,1);
+ free(result);
+ }
+}
+
+/* }}} */
+
+#define SAFE_STRING(s) ((s)?(char*)(s):"")
+
+/* {{{ proto mixed readline_info([string varname [, string newvalue]])
+ Gets/sets various internal readline variables. */
+PHP_FUNCTION(readline_info)
+{
+ char *what = NULL;
+ zval **value = NULL;
+ int what_len, oldval;
+ char *oldstr;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sZ", &what, &what_len, &value) == FAILURE) {
+ return;
+ }
+
+ if (!what) {
+ array_init(return_value);
+ add_assoc_string(return_value,"line_buffer",SAFE_STRING(rl_line_buffer),1);
+ add_assoc_long(return_value,"point",rl_point);
+ add_assoc_long(return_value,"end",rl_end);
+#ifdef HAVE_LIBREADLINE
+ add_assoc_long(return_value,"mark",rl_mark);
+ add_assoc_long(return_value,"done",rl_done);
+ add_assoc_long(return_value,"pending_input",rl_pending_input);
+ add_assoc_string(return_value,"prompt",SAFE_STRING(rl_prompt),1);
+ add_assoc_string(return_value,"terminal_name",(char *)SAFE_STRING(rl_terminal_name),1);
+#endif
+#if HAVE_ERASE_EMPTY_LINE
+ add_assoc_long(return_value,"erase_empty_line",rl_erase_empty_line);
+#endif
+ add_assoc_string(return_value,"library_version",(char *)SAFE_STRING(rl_library_version),1);
+ add_assoc_string(return_value,"readline_name",(char *)SAFE_STRING(rl_readline_name),1);
+ } else {
+ if (!strcasecmp(what,"line_buffer")) {
+ oldstr = rl_line_buffer;
+ if (value) {
+ /* XXX if (rl_line_buffer) free(rl_line_buffer); */
+ convert_to_string_ex(value);
+ rl_line_buffer = strdup(Z_STRVAL_PP(value));
+ }
+ RETVAL_STRING(SAFE_STRING(oldstr),1);
+ } else if (!strcasecmp(what, "point")) {
+ RETVAL_LONG(rl_point);
+ } else if (!strcasecmp(what, "end")) {
+ RETVAL_LONG(rl_end);
+#ifdef HAVE_LIBREADLINE
+ } else if (!strcasecmp(what, "mark")) {
+ RETVAL_LONG(rl_mark);
+ } else if (!strcasecmp(what, "done")) {
+ oldval = rl_done;
+ if (value) {
+ convert_to_long_ex(value);
+ rl_done = Z_LVAL_PP(value);
+ }
+ RETVAL_LONG(oldval);
+ } else if (!strcasecmp(what, "pending_input")) {
+ oldval = rl_pending_input;
+ if (value) {
+ convert_to_string_ex(value);
+ rl_pending_input = Z_STRVAL_PP(value)[0];
+ }
+ RETVAL_LONG(oldval);
+ } else if (!strcasecmp(what, "prompt")) {
+ RETVAL_STRING(SAFE_STRING(rl_prompt),1);
+ } else if (!strcasecmp(what, "terminal_name")) {
+ RETVAL_STRING((char *)SAFE_STRING(rl_terminal_name),1);
+#endif
+#if HAVE_ERASE_EMPTY_LINE
+ } else if (!strcasecmp(what, "erase_empty_line")) {
+ oldval = rl_erase_empty_line;
+ if (value) {
+ convert_to_long_ex(value);
+ rl_erase_empty_line = Z_LVAL_PP(value);
+ }
+ RETVAL_LONG(oldval);
+#endif
+ } else if (!strcasecmp(what,"library_version")) {
+ RETVAL_STRING((char *)SAFE_STRING(rl_library_version),1);
+ } else if (!strcasecmp(what, "readline_name")) {
+ oldstr = (char*)rl_readline_name;
+ if (value) {
+ /* XXX if (rl_readline_name) free(rl_readline_name); */
+ convert_to_string_ex(value);
+ rl_readline_name = strdup(Z_STRVAL_PP(value));;
+ }
+ RETVAL_STRING(SAFE_STRING(oldstr),1);
+ }
+ }
+}
+
+/* }}} */
+/* {{{ proto bool readline_add_history(string prompt)
+ Adds a line to the history */
+PHP_FUNCTION(readline_add_history)
+{
+ char *arg;
+ int arg_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
+ return;
+ }
+
+ add_history(arg);
+
+ RETURN_TRUE;
+}
+
+/* }}} */
+/* {{{ proto bool readline_clear_history(void)
+ Clears the history */
+PHP_FUNCTION(readline_clear_history)
+{
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ clear_history();
+
+ RETURN_TRUE;
+}
+
+/* }}} */
+/* {{{ proto array readline_list_history(void)
+ Lists the history */
+#ifndef HAVE_LIBEDIT
+PHP_FUNCTION(readline_list_history)
+{
+ HIST_ENTRY **history;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ history = history_list();
+
+ array_init(return_value);
+
+ if (history) {
+ int i;
+ for (i = 0; history[i]; i++) {
+ add_next_index_string(return_value,history[i]->line,1);
+ }
+ }
+}
+#endif
+/* }}} */
+/* {{{ proto bool readline_read_history([string filename])
+ Reads the history */
+PHP_FUNCTION(readline_read_history)
+{
+ char *arg = NULL;
+ int arg_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
+ return;
+ }
+
+ if (php_check_open_basedir(arg TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ /* XXX from & to NYI */
+ if (read_history(arg)) {
+ RETURN_FALSE;
+ } else {
+ RETURN_TRUE;
+ }
+}
+
+/* }}} */
+/* {{{ proto bool readline_write_history([string filename])
+ Writes the history */
+PHP_FUNCTION(readline_write_history)
+{
+ char *arg = NULL;
+ int arg_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|p", &arg, &arg_len) == FAILURE) {
+ return;
+ }
+
+ if (php_check_open_basedir(arg TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ if (write_history(arg)) {
+ RETURN_FALSE;
+ } else {
+ RETURN_TRUE;
+ }
+}
+
+/* }}} */
+/* {{{ proto bool readline_completion_function(string funcname)
+ Readline completion function? */
+
+static char *_readline_command_generator(const char *text, int state)
+{
+ HashTable *myht = Z_ARRVAL(_readline_array);
+ zval **entry;
+
+ if (!state) {
+ zend_hash_internal_pointer_reset(myht);
+ }
+
+ while (zend_hash_get_current_data(myht, (void **)&entry) == SUCCESS) {
+ zend_hash_move_forward(myht);
+
+ convert_to_string_ex(entry);
+ if (strncmp (Z_STRVAL_PP(entry), text, strlen(text)) == 0) {
+ return (strdup(Z_STRVAL_PP(entry)));
+ }
+ }
+
+ return NULL;
+}
+
+static zval *_readline_string_zval(const char *str)
+{
+ zval *ret;
+ int len;
+
+ MAKE_STD_ZVAL(ret);
+
+ if (str) {
+ len = strlen(str);
+ ZVAL_STRINGL(ret, (char*)str, len, 1);
+ } else {
+ ZVAL_NULL(ret);
+ }
+
+ return ret;
+}
+
+static zval *_readline_long_zval(long l)
+{
+ zval *ret;
+ MAKE_STD_ZVAL(ret);
+
+ Z_TYPE_P(ret) = IS_LONG;
+ Z_LVAL_P(ret) = l;
+ return ret;
+}
+
+static char **_readline_completion_cb(const char *text, int start, int end)
+{
+ zval *params[3];
+ int i;
+ char **matches = NULL;
+ TSRMLS_FETCH();
+
+ params[0]=_readline_string_zval(text);
+ params[1]=_readline_long_zval(start);
+ params[2]=_readline_long_zval(end);
+
+ if (call_user_function(CG(function_table), NULL, _readline_completion, &_readline_array, 3, params TSRMLS_CC) == SUCCESS) {
+ if (Z_TYPE(_readline_array) == IS_ARRAY) {
+ if (zend_hash_num_elements(Z_ARRVAL(_readline_array))) {
+ matches = rl_completion_matches(text,_readline_command_generator);
+ } else {
+ matches = malloc(sizeof(char *) * 2);
+ if (!matches) {
+ return NULL;
+ }
+ matches[0] = strdup("");
+ matches[1] = '\0';
+ }
+ }
+ }
+
+ for (i = 0; i < 3; i++) {
+ zval_ptr_dtor(&params[i]);
+ }
+ zval_dtor(&_readline_array);
+
+ return matches;
+}
+
+PHP_FUNCTION(readline_completion_function)
+{
+ zval *arg = NULL;
+ char *name = NULL;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg)) {
+ RETURN_FALSE;
+ }
+
+ if (!zend_is_callable(arg, 0, &name TSRMLS_CC)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not callable", name);
+ efree(name);
+ RETURN_FALSE;
+ }
+ efree(name);
+
+ if (_readline_completion) {
+ zval_dtor(_readline_completion);
+ FREE_ZVAL(_readline_completion);
+ }
+
+ MAKE_STD_ZVAL(_readline_completion);
+ *_readline_completion = *arg;
+ zval_copy_ctor(_readline_completion);
+
+ rl_attempted_completion_function = _readline_completion_cb;
+ if (rl_attempted_completion_function == NULL) {
+ efree(name);
+ RETURN_FALSE;
+ }
+ RETURN_TRUE;
+}
+
+/* }}} */
+
+#if HAVE_RL_CALLBACK_READ_CHAR
+
+static void php_rl_callback_handler(char *the_line)
+{
+ zval *params[1];
+ zval dummy;
+ TSRMLS_FETCH();
+
+ ZVAL_NULL(&dummy);
+
+ params[0] = _readline_string_zval(the_line);
+
+ call_user_function(CG(function_table), NULL, _prepped_callback, &dummy, 1, params TSRMLS_CC);
+
+ zval_ptr_dtor(&params[0]);
+ zval_dtor(&dummy);
+}
+
+/* {{{ proto void readline_callback_handler_install(string prompt, mixed callback)
+ Initializes the readline callback interface and terminal, prints the prompt and returns immediately */
+PHP_FUNCTION(readline_callback_handler_install)
+{
+ zval *callback;
+ char *name = NULL;
+ char *prompt;
+ int prompt_len;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &prompt, &prompt_len, &callback)) {
+ return;
+ }
+
+ if (!zend_is_callable(callback, 0, &name TSRMLS_CC)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not callable", name);
+ efree(name);
+ RETURN_FALSE;
+ }
+ efree(name);
+
+ if (_prepped_callback) {
+ rl_callback_handler_remove();
+ zval_dtor(_prepped_callback);
+ FREE_ZVAL(_prepped_callback);
+ }
+
+ ALLOC_ZVAL(_prepped_callback);
+ MAKE_COPY_ZVAL(&callback, _prepped_callback);
+
+ rl_callback_handler_install(prompt, php_rl_callback_handler);
+
+ RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto void readline_callback_read_char()
+ Informs the readline callback interface that a character is ready for input */
+PHP_FUNCTION(readline_callback_read_char)
+{
+ if (_prepped_callback) {
+ rl_callback_read_char();
+ }
+}
+/* }}} */
+
+/* {{{ proto bool readline_callback_handler_remove()
+ Removes a previously installed callback handler and restores terminal settings */
+PHP_FUNCTION(readline_callback_handler_remove)
+{
+ if (_prepped_callback) {
+ rl_callback_handler_remove();
+ zval_dtor(_prepped_callback);
+ FREE_ZVAL(_prepped_callback);
+ _prepped_callback = 0;
+ RETURN_TRUE;
+ }
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto void readline_redisplay(void)
+ Ask readline to redraw the display */
+PHP_FUNCTION(readline_redisplay)
+{
+ rl_redisplay();
+}
+/* }}} */
+
+#endif
+
+#if HAVE_RL_ON_NEW_LINE
+/* {{{ proto void readline_on_new_line(void)
+ Inform readline that the cursor has moved to a new line */
+PHP_FUNCTION(readline_on_new_line)
+{
+ rl_on_new_line();
+}
+/* }}} */
+
+#endif
+
+
+#endif /* HAVE_LIBREADLINE */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
new file mode 100644
index 0000000..f4ce12b
--- /dev/null
+++ b/ext/readline/readline_cli.c
@@ -0,0 +1,767 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Marcus Boerger <helly@php.net> |
+ | Johannes Schlueter <johannes@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+
+#ifndef HAVE_RL_COMPLETION_MATCHES
+#define rl_completion_matches completion_matches
+#endif
+
+#include "php_globals.h"
+#include "php_variables.h"
+#include "zend_hash.h"
+#include "zend_modules.h"
+
+#include "SAPI.h"
+
+#if HAVE_SETLOCALE
+#include <locale.h>
+#endif
+#include "zend.h"
+#include "zend_extensions.h"
+#include "php_ini.h"
+#include "php_globals.h"
+#include "php_main.h"
+#include "fopen_wrappers.h"
+#include "ext/standard/php_standard.h"
+#include "ext/standard/php_smart_str.h"
+
+#ifdef __riscos__
+#include <unixlib/local.h>
+#endif
+
+#if HAVE_LIBEDIT
+#include <editline/readline.h>
+#else
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include "zend_compile.h"
+#include "zend_execute.h"
+#include "zend_highlight.h"
+#include "zend_indent.h"
+#include "zend_exceptions.h"
+
+#include "sapi/cli/cli.h"
+#include "readline_cli.h"
+
+#ifdef COMPILE_DL_READLINE
+#include <dlfcn.h>
+#endif
+
+#ifndef RTLD_DEFAULT
+#define RTLD_DEFAULT NULL
+#endif
+
+#define DEFAULT_PROMPT "\\b \\> "
+
+ZEND_DECLARE_MODULE_GLOBALS(cli_readline);
+
+static char php_last_char = '\0';
+static FILE *pager_pipe = NULL;
+
+static size_t readline_shell_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */
+{
+ if (CLIR_G(prompt_str)) {
+ smart_str_appendl(CLIR_G(prompt_str), str, str_length);
+ return str_length;
+ }
+
+ if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
+ pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
+ }
+ if (pager_pipe) {
+ return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
+ }
+
+ return -1;
+}
+/* }}} */
+
+static int readline_shell_ub_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */
+{
+ php_last_char = str[str_length-1];
+ return -1;
+}
+/* }}} */
+
+static void cli_readline_init_globals(zend_cli_readline_globals *rg TSRMLS_DC)
+{
+ rg->pager = NULL;
+ rg->prompt = NULL;
+ rg->prompt_str = NULL;
+}
+
+PHP_INI_BEGIN()
+ STD_PHP_INI_ENTRY("cli.pager", "", PHP_INI_ALL, OnUpdateString, pager, zend_cli_readline_globals, cli_readline_globals)
+ STD_PHP_INI_ENTRY("cli.prompt", DEFAULT_PROMPT, PHP_INI_ALL, OnUpdateString, prompt, zend_cli_readline_globals, cli_readline_globals)
+PHP_INI_END()
+
+
+
+typedef enum {
+ body,
+ sstring,
+ dstring,
+ sstring_esc,
+ dstring_esc,
+ comment_line,
+ comment_block,
+ heredoc_start,
+ heredoc,
+ outside,
+} php_code_type;
+
+static char *cli_get_prompt(char *block, char prompt TSRMLS_DC) /* {{{ */
+{
+ smart_str retval = {0};
+ char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
+
+ do {
+ if (*prompt_spec == '\\') {
+ switch (prompt_spec[1]) {
+ case '\\':
+ smart_str_appendc(&retval, '\\');
+ prompt_spec++;
+ break;
+ case 'n':
+ smart_str_appendc(&retval, '\n');
+ prompt_spec++;
+ break;
+ case 't':
+ smart_str_appendc(&retval, '\t');
+ prompt_spec++;
+ break;
+ case 'e':
+ smart_str_appendc(&retval, '\033');
+ prompt_spec++;
+ break;
+
+
+ case 'v':
+ smart_str_appends(&retval, PHP_VERSION);
+ prompt_spec++;
+ break;
+ case 'b':
+ smart_str_appends(&retval, block);
+ prompt_spec++;
+ break;
+ case '>':
+ smart_str_appendc(&retval, prompt);
+ prompt_spec++;
+ break;
+ case '`':
+ smart_str_appendc(&retval, '`');
+ prompt_spec++;
+ break;
+ default:
+ smart_str_appendc(&retval, '\\');
+ break;
+ }
+ } else if (*prompt_spec == '`') {
+ char *prompt_end = strstr(prompt_spec + 1, "`");
+ char *code;
+
+ if (prompt_end) {
+ code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
+
+ CLIR_G(prompt_str) = &retval;
+ zend_try {
+ zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code" TSRMLS_CC);
+ } zend_end_try();
+ CLIR_G(prompt_str) = NULL;
+ efree(code);
+ prompt_spec = prompt_end;
+ }
+ } else {
+ smart_str_appendc(&retval, *prompt_spec);
+ }
+ } while (++prompt_spec && *prompt_spec);
+ smart_str_0(&retval);
+ return retval.c;
+}
+/* }}} */
+
+static int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
+{
+ int valid_end = 1, last_valid_end;
+ int brackets_count = 0;
+ int brace_count = 0;
+ int i;
+ php_code_type code_type = body;
+ char *heredoc_tag;
+ int heredoc_len;
+
+ for (i = 0; i < len; ++i) {
+ switch(code_type) {
+ default:
+ switch(code[i]) {
+ case '{':
+ brackets_count++;
+ valid_end = 0;
+ break;
+ case '}':
+ if (brackets_count > 0) {
+ brackets_count--;
+ }
+ valid_end = brackets_count ? 0 : 1;
+ break;
+ case '(':
+ brace_count++;
+ valid_end = 0;
+ break;
+ case ')':
+ if (brace_count > 0) {
+ brace_count--;
+ }
+ valid_end = 0;
+ break;
+ case ';':
+ valid_end = brace_count == 0 && brackets_count == 0;
+ break;
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ break;
+ case '\'':
+ code_type = sstring;
+ break;
+ case '"':
+ code_type = dstring;
+ break;
+ case '#':
+ code_type = comment_line;
+ break;
+ case '/':
+ if (code[i+1] == '/') {
+ i++;
+ code_type = comment_line;
+ break;
+ }
+ if (code[i+1] == '*') {
+ last_valid_end = valid_end;
+ valid_end = 0;
+ code_type = comment_block;
+ i++;
+ break;
+ }
+ valid_end = 0;
+ break;
+ case '%':
+ if (!CG(asp_tags)) {
+ valid_end = 0;
+ break;
+ }
+ /* no break */
+ case '?':
+ if (code[i+1] == '>') {
+ i++;
+ code_type = outside;
+ break;
+ }
+ valid_end = 0;
+ break;
+ case '<':
+ valid_end = 0;
+ if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
+ i += 2;
+ code_type = heredoc_start;
+ heredoc_len = 0;
+ }
+ break;
+ default:
+ valid_end = 0;
+ break;
+ }
+ break;
+ case sstring:
+ if (code[i] == '\\') {
+ code_type = sstring_esc;
+ } else {
+ if (code[i] == '\'') {
+ code_type = body;
+ }
+ }
+ break;
+ case sstring_esc:
+ code_type = sstring;
+ break;
+ case dstring:
+ if (code[i] == '\\') {
+ code_type = dstring_esc;
+ } else {
+ if (code[i] == '"') {
+ code_type = body;
+ }
+ }
+ break;
+ case dstring_esc:
+ code_type = dstring;
+ break;
+ case comment_line:
+ if (code[i] == '\n') {
+ code_type = body;
+ }
+ break;
+ case comment_block:
+ if (code[i-1] == '*' && code[i] == '/') {
+ code_type = body;
+ valid_end = last_valid_end;
+ }
+ break;
+ case heredoc_start:
+ switch(code[i]) {
+ case ' ':
+ case '\t':
+ case '\'':
+ break;
+ case '\r':
+ case '\n':
+ code_type = heredoc;
+ break;
+ default:
+ if (!heredoc_len) {
+ heredoc_tag = code+i;
+ }
+ heredoc_len++;
+ break;
+ }
+ break;
+ case heredoc:
+ if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
+ code_type = body;
+ } else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
+ code_type = body;
+ valid_end = 1;
+ }
+ break;
+ case outside:
+ if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
+ || (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
+ || (i > 3 && !strncmp(code+i-4, "<?php", 5))
+ ) {
+ code_type = body;
+ }
+ break;
+ }
+ }
+
+ switch (code_type) {
+ default:
+ if (brace_count) {
+ *prompt = cli_get_prompt("php", '(' TSRMLS_CC);
+ } else if (brackets_count) {
+ *prompt = cli_get_prompt("php", '{' TSRMLS_CC);
+ } else {
+ *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
+ }
+ break;
+ case sstring:
+ case sstring_esc:
+ *prompt = cli_get_prompt("php", '\'' TSRMLS_CC);
+ break;
+ case dstring:
+ case dstring_esc:
+ *prompt = cli_get_prompt("php", '"' TSRMLS_CC);
+ break;
+ case comment_block:
+ *prompt = cli_get_prompt("/* ", '>' TSRMLS_CC);
+ break;
+ case heredoc:
+ *prompt = cli_get_prompt("<<<", '>' TSRMLS_CC);
+ break;
+ case outside:
+ *prompt = cli_get_prompt(" ", '>' TSRMLS_CC);
+ break;
+ }
+
+ if (!valid_end || brackets_count) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+/* }}} */
+
+static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC) /* {{{ */
+{
+ char *name;
+ ulong number;
+
+ if (!(*state % 2)) {
+ zend_hash_internal_pointer_reset(ht);
+ (*state)++;
+ }
+ while(zend_hash_has_more_elements(ht) == SUCCESS) {
+ zend_hash_get_current_key(ht, &name, &number, 0);
+ if (!textlen || !strncmp(name, text, textlen)) {
+ if (pData) {
+ zend_hash_get_current_data(ht, pData);
+ }
+ zend_hash_move_forward(ht);
+ return name;
+ }
+ if (zend_hash_move_forward(ht) == FAILURE) {
+ break;
+ }
+ }
+ (*state)++;
+ return NULL;
+} /* }}} */
+
+static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
+{
+ char *retval, *tmp;
+
+ tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);
+ if (retval) {
+ retval = malloc(strlen(tmp) + 2);
+ retval[0] = '$';
+ strcpy(&retval[1], tmp);
+ rl_completion_append_character = '\0';
+ }
+ return retval;
+} /* }}} */
+
+static char *cli_completion_generator_ini(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
+{
+ char *retval, *tmp;
+
+ tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL TSRMLS_CC);
+ if (retval) {
+ retval = malloc(strlen(tmp) + 2);
+ retval[0] = '#';
+ strcpy(&retval[1], tmp);
+ rl_completion_append_character = '=';
+ }
+ return retval;
+} /* }}} */
+
+static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
+{
+ zend_function *func;
+ char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
+ if (retval) {
+ rl_completion_append_character = '(';
+ retval = strdup(func->common.function_name);
+ }
+
+ return retval;
+} /* }}} */
+
+static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
+{
+ zend_class_entry **pce;
+ char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);
+ if (retval) {
+ rl_completion_append_character = '\0';
+ retval = strdup((*pce)->name);
+ }
+
+ return retval;
+} /* }}} */
+
+static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
+{
+ zend_class_entry **pce;
+ char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);
+ if (retval) {
+ rl_completion_append_character = '\0';
+ retval = strdup(retval);
+ }
+
+ return retval;
+} /* }}} */
+
+static int cli_completion_state;
+
+static char *cli_completion_generator(const char *text, int index) /* {{{ */
+{
+/*
+TODO:
+- constants
+- maybe array keys
+- language constructs and other things outside a hashtable (echo, try, function, class, ...)
+- object/class members
+
+- future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
+*/
+ char *retval = NULL;
+ int textlen = strlen(text);
+ TSRMLS_FETCH();
+
+ if (!index) {
+ cli_completion_state = 0;
+ }
+ if (text[0] == '$') {
+ retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);
+ } else if (text[0] == '#') {
+ retval = cli_completion_generator_ini(text, textlen, &cli_completion_state TSRMLS_CC);
+ } else {
+ char *lc_text, *class_name, *class_name_end;
+ int class_name_len;
+ zend_class_entry **pce = NULL;
+
+ class_name_end = strstr(text, "::");
+ if (class_name_end) {
+ class_name_len = class_name_end - text;
+ class_name = zend_str_tolower_dup(text, class_name_len);
+ class_name[class_name_len] = '\0'; /* not done automatically */
+ if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {
+ efree(class_name);
+ return NULL;
+ }
+ lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
+ textlen -= (class_name_len + 2);
+ } else {
+ lc_text = zend_str_tolower_dup(text, textlen);
+ }
+
+ switch (cli_completion_state) {
+ case 0:
+ case 1:
+ retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);
+ if (retval) {
+ break;
+ }
+ case 2:
+ case 3:
+ retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);
+ if (retval || pce) {
+ break;
+ }
+ case 4:
+ case 5:
+ retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);
+ break;
+ default:
+ break;
+ }
+ efree(lc_text);
+ if (class_name_end) {
+ efree(class_name);
+ }
+ if (pce && retval) {
+ int len = class_name_len + 2 + strlen(retval) + 1;
+ char *tmp = malloc(len);
+
+ snprintf(tmp, len, "%s::%s", (*pce)->name, retval);
+ free(retval);
+ retval = tmp;
+ }
+ }
+
+ return retval;
+} /* }}} */
+
+static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
+{
+ return rl_completion_matches(text, cli_completion_generator);
+}
+/* }}} */
+
+static int readline_shell_run(TSRMLS_D) /* {{{ */
+{
+ char *line;
+ size_t size = 4096, pos = 0, len;
+ char *code = emalloc(size);
+ char *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
+ char *history_file;
+
+ if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
+ zend_file_handle *prepend_file_p;
+ zend_file_handle prepend_file = {0};
+
+ prepend_file.filename = PG(auto_prepend_file);
+ prepend_file.opened_path = NULL;
+ prepend_file.free_filename = 0;
+ prepend_file.type = ZEND_HANDLE_FILENAME;
+ prepend_file_p = &prepend_file;
+
+ zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 1, prepend_file_p);
+ }
+
+ history_file = tilde_expand("~/.php_history");
+ rl_attempted_completion_function = cli_code_completion;
+ rl_special_prefixes = "$";
+ read_history(history_file);
+
+ EG(exit_status) = 0;
+ while ((line = readline(prompt)) != NULL) {
+ if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
+ free(line);
+ break;
+ }
+
+ if (!pos && !*line) {
+ free(line);
+ continue;
+ }
+
+ len = strlen(line);
+
+ if (line[0] == '#') {
+ char *param = strstr(&line[1], "=");
+ if (param) {
+ char *cmd;
+ uint cmd_len;
+ param++;
+ cmd_len = param - &line[1] - 1;
+ cmd = estrndup(&line[1], cmd_len);
+
+ zend_alter_ini_entry_ex(cmd, cmd_len + 1, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC);
+ efree(cmd);
+ add_history(line);
+
+ efree(prompt);
+ /* TODO: This might be wrong! */
+ prompt = cli_get_prompt("php", '>' TSRMLS_CC);
+ continue;
+ }
+ }
+
+ if (pos + len + 2 > size) {
+ size = pos + len + 2;
+ code = erealloc(code, size);
+ }
+ memcpy(&code[pos], line, len);
+ pos += len;
+ code[pos] = '\n';
+ code[++pos] = '\0';
+
+ if (*line) {
+ add_history(line);
+ }
+
+ free(line);
+ efree(prompt);
+
+ if (!cli_is_valid_code(code, pos, &prompt TSRMLS_CC)) {
+ continue;
+ }
+
+ zend_try {
+ zend_eval_stringl(code, pos, NULL, "php shell code" TSRMLS_CC);
+ } zend_end_try();
+
+ pos = 0;
+
+ if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
+ readline_shell_write("\n", 1 TSRMLS_CC);
+ }
+
+ if (EG(exception)) {
+ zend_exception_error(EG(exception), E_WARNING TSRMLS_CC);
+ }
+
+ if (pager_pipe) {
+ fclose(pager_pipe);
+ pager_pipe = NULL;
+ }
+
+ php_last_char = '\0';
+ }
+ write_history(history_file);
+ free(history_file);
+ efree(code);
+ efree(prompt);
+ return EG(exit_status);
+}
+/* }}} */
+
+/*
+#ifdef COMPILE_DL_READLINE
+This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
+extensions. If that is being changed dlsym() should only be used when building
+this extension sharedto offer compatibility.
+*/
+#define GET_SHELL_CB(cb) \
+ do { \
+ (cb) = NULL; \
+ cli_shell_callbacks_t *(*get_callbacks)(); \
+ get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
+ if (get_callbacks) { \
+ (cb) = get_callbacks(); \
+ } \
+ } while(0)
+/*#else
+#define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
+#endif*/
+
+PHP_MINIT_FUNCTION(cli_readline)
+{
+ cli_shell_callbacks_t *cb;
+
+ ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
+ REGISTER_INI_ENTRIES();
+
+#if HAVE_LIBEDIT
+ REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
+#else
+ REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
+#endif
+
+ GET_SHELL_CB(cb);
+ if (cb) {
+ cb->cli_shell_write = readline_shell_write;
+ cb->cli_shell_ub_write = readline_shell_ub_write;
+ cb->cli_shell_run = readline_shell_run;
+ }
+
+ return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(cli_readline)
+{
+ cli_shell_callbacks_t *cb;
+
+ UNREGISTER_INI_ENTRIES();
+
+ GET_SHELL_CB(cb);
+ if (cb) {
+ cb->cli_shell_write = NULL;
+ cb->cli_shell_ub_write = NULL;
+ cb->cli_shell_run = NULL;
+ }
+
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(cli_readline)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "Readline Support", "enabled");
+ php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
+ php_info_print_table_end();
+
+ DISPLAY_INI_ENTRIES();
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/ext/readline/readline_cli.h b/ext/readline/readline_cli.h
new file mode 100644
index 0000000..d9deb8a
--- /dev/null
+++ b/ext/readline/readline_cli.h
@@ -0,0 +1,43 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Marcus Boerger <helly@php.net> |
+ | Johannes Schlueter <johannes@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "ext/standard/php_smart_str.h"
+
+ZEND_BEGIN_MODULE_GLOBALS(cli_readline)
+ char *pager;
+ char *prompt;
+ smart_str *prompt_str;
+ZEND_END_MODULE_GLOBALS(cli_readline)
+
+#ifdef ZTS
+# define CLIR_G(v) TSRMG(cli_readline_globals_id, zend_cli_readline_globals *, v)
+#else
+# define CLIR_G(v) (cli_readline_globals.v)
+#endif
+
+extern PHP_MINIT_FUNCTION(cli_readline);
+extern PHP_MSHUTDOWN_FUNCTION(cli_readline);
+extern PHP_MINFO_FUNCTION(cli_readline);
+
+ZEND_EXTERN_MODULE_GLOBALS(cli_readline)
+
+
diff --git a/ext/readline/tests/readline_add_history_001.phpt b/ext/readline/tests/readline_add_history_001.phpt
new file mode 100644
index 0000000..a4bd524
--- /dev/null
+++ b/ext/readline/tests/readline_add_history_001.phpt
@@ -0,0 +1,32 @@
+--TEST--
+readline_add_history(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_list_history')) die("skip"); ?>
+--FILE--
+<?php
+
+var_dump(readline_add_history('foo'));
+var_dump(readline_list_history());
+var_dump(readline_add_history(NULL));
+var_dump(readline_list_history());
+var_dump(readline_clear_history());
+var_dump(readline_add_history());
+
+?>
+--EXPECTF--
+bool(true)
+array(1) {
+ [0]=>
+ string(3) "foo"
+}
+bool(true)
+array(2) {
+ [0]=>
+ string(3) "foo"
+ [1]=>
+ string(0) ""
+}
+bool(true)
+
+Warning: readline_add_history() expects exactly 1 parameter, 0 given in %s on line %d
+NULL
diff --git a/ext/readline/tests/readline_callback_handler_install_001.phpt b/ext/readline/tests/readline_callback_handler_install_001.phpt
new file mode 100644
index 0000000..8bf1d61
--- /dev/null
+++ b/ext/readline/tests/readline_callback_handler_install_001.phpt
@@ -0,0 +1,24 @@
+--TEST--
+readline_callback_handler_install(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_callback_handler_install')) die("skip"); ?>
+--FILE--
+<?php
+
+function foo() {
+ readline_callback_handler_remove();
+}
+
+var_dump(readline_callback_handler_install('testing: ', 'foo'));
+var_dump(readline_callback_handler_install('testing: ', 'foobar!'));
+var_dump(readline_callback_handler_install('testing: '));
+
+?>
+--EXPECTF--
+testing: bool(true)
+
+Warning: readline_callback_handler_install(): foobar! is not callable in %s on line %d
+bool(false)
+
+Warning: readline_callback_handler_install() expects exactly 2 parameters, 1 given in %s on line %d
+NULL
diff --git a/ext/readline/tests/readline_callback_handler_remove_001.phpt b/ext/readline/tests/readline_callback_handler_remove_001.phpt
new file mode 100644
index 0000000..768041a
--- /dev/null
+++ b/ext/readline/tests/readline_callback_handler_remove_001.phpt
@@ -0,0 +1,20 @@
+--TEST--
+readline_callback_handler_remove(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_callback_handler_remove')) die("skip"); ?>
+--FILE--
+<?php
+
+var_dump(readline_callback_handler_remove());
+var_dump(readline_callback_handler_install('testing: ', 'foo'));
+
+function foo() { }
+var_dump(readline_callback_handler_install('testing: ', 'foo'));
+var_dump(readline_callback_handler_remove());
+
+?>
+--EXPECT--
+bool(false)
+testing: bool(true)
+testing: bool(true)
+bool(true)
diff --git a/ext/readline/tests/readline_clear_history_001.phpt b/ext/readline/tests/readline_clear_history_001.phpt
new file mode 100644
index 0000000..5df1ae7
--- /dev/null
+++ b/ext/readline/tests/readline_clear_history_001.phpt
@@ -0,0 +1,16 @@
+--TEST--
+readline_clear_history(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline")) die("skip"); ?>
+--FILE--
+<?php
+
+var_dump(readline_clear_history());
+var_dump(readline_clear_history(1));
+
+?>
+--EXPECTF--
+bool(true)
+
+Warning: readline_clear_history() expects exactly 0 parameters, 1 given in %s on line %d
+NULL
diff --git a/ext/readline/tests/readline_completion_function_001.phpt b/ext/readline/tests/readline_completion_function_001.phpt
new file mode 100644
index 0000000..828636c
--- /dev/null
+++ b/ext/readline/tests/readline_completion_function_001.phpt
@@ -0,0 +1,25 @@
+--TEST--
+readline_completion_function(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline")) die("skip"); ?>
+--FILE--
+<?php
+
+function foo() { }
+
+$data = array(
+ 'foo',
+ 'strtolower',
+ 1,
+ 1.1231
+);
+
+foreach ($data as $callback) {
+ readline_completion_function($callback);
+}
+
+?>
+--EXPECTF--
+Warning: readline_completion_function(): 1 is not callable in %s on line %d
+
+Warning: readline_completion_function(): 1.1231 is not callable in %s on line %d
diff --git a/ext/readline/tests/readline_info_001.phpt b/ext/readline/tests/readline_info_001.phpt
new file mode 100644
index 0000000..c7bc92a
--- /dev/null
+++ b/ext/readline/tests/readline_info_001.phpt
@@ -0,0 +1,45 @@
+--TEST--
+readline_info(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline")) die("skip"); ?>
+--FILE--
+<?php
+
+var_dump(readline_info());
+var_dump(readline_info(1));
+var_dump(readline_info(1,1));
+var_dump(readline_info('line_buffer'));
+var_dump(readline_info('readline_name'));
+var_dump(readline_info('readline_name', 1));
+var_dump(readline_info('readline_name'));
+
+?>
+--EXPECTF--
+array(10) {
+ ["line_buffer"]=>
+ string(0) ""
+ ["point"]=>
+ int(0)
+ ["end"]=>
+ int(0)
+ ["mark"]=>
+ int(0)
+ ["done"]=>
+ int(0)
+ ["pending_input"]=>
+ int(0)
+ ["prompt"]=>
+ string(0) ""
+ ["terminal_name"]=>
+ string(0) ""
+ ["library_version"]=>
+ string(%d) "%s"
+ ["readline_name"]=>
+ string(5) "other"
+}
+NULL
+NULL
+string(0) ""
+string(5) "other"
+string(5) "other"
+string(1) "1"
diff --git a/ext/readline/tests/readline_list_history_001.phpt b/ext/readline/tests/readline_list_history_001.phpt
new file mode 100644
index 0000000..2f6f952
--- /dev/null
+++ b/ext/readline/tests/readline_list_history_001.phpt
@@ -0,0 +1,17 @@
+--TEST--
+readline_list_history(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_list_history')) die("skip"); ?>
+--FILE--
+<?php
+
+var_dump(readline_list_history());
+var_dump(readline_list_history(1));
+
+?>
+--EXPECTF--
+array(0) {
+}
+
+Warning: readline_list_history() expects exactly 0 parameters, 1 given in %s on line %d
+NULL
diff --git a/ext/readline/tests/readline_read_history_001.phpt b/ext/readline/tests/readline_read_history_001.phpt
new file mode 100644
index 0000000..fcdb1ae
--- /dev/null
+++ b/ext/readline/tests/readline_read_history_001.phpt
@@ -0,0 +1,30 @@
+--TEST--
+readline_read_history(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_list_history')) die("skip"); ?>
+--FILE--
+<?php
+
+$name = tempnam('/tmp', 'readline.tmp');
+
+readline_add_history("foo\n");
+
+var_dump(readline_write_history($name));
+
+var_dump(readline_clear_history());
+
+var_dump(readline_read_history($name));
+
+var_dump(readline_list_history());
+
+unlink($name);
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [0]=>
+ string(3) "foo"
+}
diff --git a/ext/readline/tests/readline_write_history_001.phpt b/ext/readline/tests/readline_write_history_001.phpt
new file mode 100644
index 0000000..fc0ae32
--- /dev/null
+++ b/ext/readline/tests/readline_write_history_001.phpt
@@ -0,0 +1,26 @@
+--TEST--
+readline_write_history(): Basic test
+--SKIPIF--
+<?php if (!extension_loaded("readline") || !function_exists('readline_add_history')) die("skip"); ?>
+--FILE--
+<?php
+
+$name = tempnam('/tmp', 'readline.tmp');
+
+readline_add_history('foo');
+readline_add_history('');
+readline_add_history(1);
+readline_add_history(NULL);
+readline_write_history($name);
+
+var_dump(file_get_contents($name));
+
+unlink($name);
+
+?>
+--EXPECT--
+string(8) "foo
+
+1
+
+"