Browse files

- 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.

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 >
  • Loading branch information...
1 parent 31f62db commit 61db5cf98a91916c23ae4adf3754b2ef94a4fbb2 @johannes johannes committed May 20, 2010
Showing with 224 additions and 11 deletions.
  1. +6 −0 NEWS
  2. +55 −3 sapi/cli/php_cli.c
  3. +144 −8 sapi/cli/php_cli_readline.c
  4. +19 −0 sapi/cli/php_cli_readline.h
View
6 NEWS
@@ -47,6 +47,12 @@
- 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)
View
58 sapi/cli/php_cli.c
@@ -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,27 +1214,37 @@ 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);
}
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);
exit_status = EG(exit_status);
break;
}
View
152 sapi/cli/php_cli_readline.c
@@ -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>
@@ -61,6 +62,55 @@
#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 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
}
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;
View
19 sapi/cli/php_cli_readline.h
@@ -13,13 +13,32 @@
| 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);

0 comments on commit 61db5cf

Please sign in to comment.