]> granicus.if.org Git - php/commitdiff
- Improved CLI Interactive readline shell (Johannes)
authorJohannes Schlüter <johannes@php.net>
Thu, 20 May 2010 20:55:33 +0000 (20:55 +0000)
committerJohannes Schlüter <johannes@php.net>
Thu, 20 May 2010 20:55:33 +0000 (20:55 +0000)
  . Added cli.pager ini setting to set a pager for output.
  . Added cli.prompt ini settingto configure the shell prompt.
  . Added shortcut #inisetting=value to change ini settings at run-time.
  . Don't terminate shell on fatal errors.

A pager can be a an shell command which will receive the command output on its
STDIN channel

php > #cli.pager=less
php > phpinfo();
(output will appear in the pager)
php > #cli.pager=grep -i readline
php > phpcredits();
Readline => Thies C. Arntzen
php > #cli.pager=
(output appears again direct on the terminal)

A prompt can contain a few escape sequences like

php > #cli.prompt=\e[032m\v \e[031m\b \e[34m\> \e[0m
5.3.99-dev php > //Colorful prompt with version number

A prompt can also contaian PHP code in backticks

php > #cli.prompt=`echo gethostname();` \b \>
guybrush php >

NEWS
sapi/cli/php_cli.c
sapi/cli/php_cli_readline.c
sapi/cli/php_cli_readline.h

diff --git a/NEWS b/NEWS
index fd5de867e4d31cf77a90cd5023954807bde8fa28..866b71c70bf883d1c5ab5fdabbae05fc7e431bbd 100644 (file)
--- a/NEWS
+++ b/NEWS
 - Changed session.entropy_file to default to /dev/urandom or /dev/arandom if
   either is present at compile time. (Rasmus)
 
+- Improved CLI Interactive readline shell (Johannes)
+  . Added cli.pager ini setting to set a pager for output.
+  . Added cli.prompt ini settingto configure the shell prompt.
+  . Added shortcut #inisetting=value to change ini settings at run-time.
+  . Don't terminate shell on fatal errors.
+
 - Removed legacy features:
   . allow_call_time_pass_reference. (Pierrick)
   . define_syslog_variables ini option and its associated function. (Kalle)
index 37564d1372bc17015fe0fc6979e86980c015239d..bf272b44001d8bb757eb0a86f2e8d60fd2170a02 100644 (file)
@@ -132,6 +132,7 @@ static char *php_optarg = NULL;
 static int php_optind = 1;
 #if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)
 static char php_last_char = '\0';
+static FILE *pager_pipe = NULL;
 #endif
 
 static const opt_struct OPTIONS[] = {
@@ -258,7 +259,23 @@ static inline size_t sapi_cli_single_write(const char *str, uint str_length TSRM
 {
 #ifdef PHP_WRITE_STDOUT
        long ret;
+#endif
+
+#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)
+       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);
+       }
+#endif
 
+#ifdef PHP_WRITE_STDOUT
        do {
                ret = write(STDOUT_FILENO, str, str_length);
        } while (ret <= 0 && errno == EAGAIN && sapi_cli_select(STDOUT_FILENO TSRMLS_CC));
@@ -401,7 +418,11 @@ static void sapi_cli_send_header(sapi_header_struct *sapi_header, void *server_c
 
 static int php_cli_startup(sapi_module_struct *sapi_module) /* {{{ */
 {
+#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)
+       if (php_module_startup(sapi_module, &cli_readline_module_entry, 1)==FAILURE) {
+#else
        if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
+#endif
                return FAILURE;
        }
        return SUCCESS;
@@ -1124,7 +1145,7 @@ int main(int argc, char *argv[])
                                char *line;
                                size_t size = 4096, pos = 0, len;
                                char *code = emalloc(size);
-                               char *prompt = "php > ";
+                               char *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
                                char *history_file;
 
                                if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
@@ -1158,6 +1179,27 @@ int main(int argc, char *argv[])
                                        }
 
                                        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);
@@ -1172,15 +1214,19 @@ int main(int argc, char *argv[])
                                        }
 
                                        free(line);
+                                       efree(prompt);
 
                                        if (!cli_is_valid_code(code, pos, &prompt TSRMLS_CC)) {
                                                continue;
                                        }
 
-                                       zend_eval_stringl(code, pos, NULL, "php shell code" TSRMLS_CC);
+                                       zend_try {
+                                               zend_eval_stringl(code, pos, NULL, "php shell code" TSRMLS_CC);
+                                       } zend_end_try();
+
                                        pos = 0;
                                        
-                                       if (php_last_char != '\0' && php_last_char != '\n') {
+                                       if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
                                                sapi_cli_single_write("\n", 1 TSRMLS_CC);
                                        }
 
@@ -1188,11 +1234,17 @@ int main(int argc, char *argv[])
                                                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);
                                exit_status = EG(exit_status);
                                break;
                        }
index 49699c16da6a0a387eaeb5096a93fe635f04a8b1..cba710023d241ef859bb01447ac14fe21f6b0de5 100644 (file)
@@ -44,6 +44,7 @@
 #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>
 #include "zend_highlight.h"
 #include "zend_indent.h"
 
+#include "php_cli_readline.h"
+
+#define DEFAULT_PROMPT "\\b \\> "
+
+ZEND_DECLARE_MODULE_GLOBALS(cli_readline);
+
+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()
+
+static PHP_MINIT_FUNCTION(cli_readline)
+{
+       ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
+       REGISTER_INI_ENTRIES();
+       return SUCCESS;
+}
+
+static PHP_MSHUTDOWN_FUNCTION(cli_readline)
+{
+       UNREGISTER_INI_ENTRIES();
+       return SUCCESS;
+}
+
+static PHP_MINFO_FUNCTION(cli_readline)
+{
+       DISPLAY_INI_ENTRIES();
+}
+
+zend_module_entry cli_readline_module_entry = {
+       STANDARD_MODULE_HEADER,
+       "cli-readline",
+       NULL,
+       PHP_MINIT(cli_readline),
+       PHP_MSHUTDOWN(cli_readline),
+       NULL,
+       NULL,
+       PHP_MINFO(cli_readline),
+       PHP_VERSION,
+       STANDARD_MODULE_PROPERTIES
+};
+
 typedef enum {
        body,
        sstring,
@@ -74,6 +124,75 @@ typedef enum {
        outside,
 } php_code_type;
 
+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;
+}
+
 int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
 {
        int valid_end = 1, last_valid_end;
@@ -206,6 +325,7 @@ int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
                                switch(code[i]) {
                                        case ' ':
                                        case '\t':
+                                       case '\'':
                                                break;
                                        case '\r':
                                        case '\n':
@@ -241,29 +361,29 @@ int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
        switch (code_type) {
                default:
                        if (brace_count) {
-                               *prompt = "php ( ";
+                               *prompt = cli_get_prompt("php", '(' TSRMLS_CC);
                        } else if (brackets_count) {
-                               *prompt = "php { ";
+                               *prompt = cli_get_prompt("php", '{' TSRMLS_CC);
                        } else {
-                               *prompt = "php > ";
+                               *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
                        }
                        break;
                case sstring:
                case sstring_esc:
-                       *prompt = "php ' ";
+                       *prompt = cli_get_prompt("php", '\'' TSRMLS_CC);
                        break;
                case dstring:
                case dstring_esc:
-                       *prompt = "php \" ";
+                       *prompt = cli_get_prompt("php", '"' TSRMLS_CC);
                        break;
                case comment_block:
-                       *prompt = "/*  > ";
+                       *prompt = cli_get_prompt("/* ", '>' TSRMLS_CC);
                        break;
                case heredoc:
-                       *prompt = "<<< > ";
+                       *prompt = cli_get_prompt("<<<", '>' TSRMLS_CC);
                        break;
                case outside:
-                       *prompt = "    > ";
+                       *prompt = cli_get_prompt("   ", '>' TSRMLS_CC);
                        break;
        }
 
@@ -315,6 +435,20 @@ static char *cli_completion_generator_var(const char *text, int textlen, int *st
        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;
@@ -373,6 +507,8 @@ TODO:
        }
        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;
index 4e4a6233bf71d94eb7cf7d76d762f77c61080f41..1b9321a15b838840cdbe027bca6408a80920ccd0 100644 (file)
    | 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
+
+ZEND_EXTERN_MODULE_GLOBALS(cli_readline)
+
+extern zend_module_entry cli_readline_module_entry;
+
+char *cli_get_prompt(char *block, char prompt TSRMLS_DC);
 int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC);
 
 char **cli_code_completion(const char *text, int start, int end);