Annotation of embedaddon/php/ext/readline/readline_cli.c, revision 1.1.1.1
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
5: | Copyright (c) 1997-2012 The PHP Group |
6: +----------------------------------------------------------------------+
7: | This source file is subject to version 3.01 of the PHP license, |
8: | that is bundled with this package in the file LICENSE, and is |
9: | available through the world-wide-web at the following url: |
10: | http://www.php.net/license/3_01.txt |
11: | If you did not receive a copy of the PHP license and are unable to |
12: | obtain it through the world-wide-web, please send a note to |
13: | license@php.net so we can mail you a copy immediately. |
14: +----------------------------------------------------------------------+
15: | Author: Marcus Boerger <helly@php.net> |
16: | Johannes Schlueter <johannes@php.net> |
17: +----------------------------------------------------------------------+
18: */
19:
20: /* $Id$ */
21:
22: #include "php.h"
23:
24: #ifndef HAVE_RL_COMPLETION_MATCHES
25: #define rl_completion_matches completion_matches
26: #endif
27:
28: #include "php_globals.h"
29: #include "php_variables.h"
30: #include "zend_hash.h"
31: #include "zend_modules.h"
32:
33: #include "SAPI.h"
34:
35: #if HAVE_SETLOCALE
36: #include <locale.h>
37: #endif
38: #include "zend.h"
39: #include "zend_extensions.h"
40: #include "php_ini.h"
41: #include "php_globals.h"
42: #include "php_main.h"
43: #include "fopen_wrappers.h"
44: #include "ext/standard/php_standard.h"
45: #include "ext/standard/php_smart_str.h"
46:
47: #ifdef __riscos__
48: #include <unixlib/local.h>
49: #endif
50:
51: #if HAVE_LIBEDIT
52: #include <editline/readline.h>
53: #else
54: #include <readline/readline.h>
55: #include <readline/history.h>
56: #endif
57:
58: #include "zend_compile.h"
59: #include "zend_execute.h"
60: #include "zend_highlight.h"
61: #include "zend_indent.h"
62: #include "zend_exceptions.h"
63:
64: #include "sapi/cli/cli.h"
65: #include "readline_cli.h"
66:
67: #ifdef COMPILE_DL_READLINE
68: #include <dlfcn.h>
69: #endif
70:
71: #ifndef RTLD_DEFAULT
72: #define RTLD_DEFAULT NULL
73: #endif
74:
75: #define DEFAULT_PROMPT "\\b \\> "
76:
77: ZEND_DECLARE_MODULE_GLOBALS(cli_readline);
78:
79: static char php_last_char = '\0';
80: static FILE *pager_pipe = NULL;
81:
82: static size_t readline_shell_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */
83: {
84: if (CLIR_G(prompt_str)) {
85: smart_str_appendl(CLIR_G(prompt_str), str, str_length);
86: return str_length;
87: }
88:
89: if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
90: pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
91: }
92: if (pager_pipe) {
93: return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
94: }
95:
96: return -1;
97: }
98: /* }}} */
99:
100: static int readline_shell_ub_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */
101: {
102: php_last_char = str[str_length-1];
103: return -1;
104: }
105: /* }}} */
106:
107: static void cli_readline_init_globals(zend_cli_readline_globals *rg TSRMLS_DC)
108: {
109: rg->pager = NULL;
110: rg->prompt = NULL;
111: rg->prompt_str = NULL;
112: }
113:
114: PHP_INI_BEGIN()
115: STD_PHP_INI_ENTRY("cli.pager", "", PHP_INI_ALL, OnUpdateString, pager, zend_cli_readline_globals, cli_readline_globals)
116: STD_PHP_INI_ENTRY("cli.prompt", DEFAULT_PROMPT, PHP_INI_ALL, OnUpdateString, prompt, zend_cli_readline_globals, cli_readline_globals)
117: PHP_INI_END()
118:
119:
120:
121: typedef enum {
122: body,
123: sstring,
124: dstring,
125: sstring_esc,
126: dstring_esc,
127: comment_line,
128: comment_block,
129: heredoc_start,
130: heredoc,
131: outside,
132: } php_code_type;
133:
134: static char *cli_get_prompt(char *block, char prompt TSRMLS_DC) /* {{{ */
135: {
136: smart_str retval = {0};
137: char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
138:
139: do {
140: if (*prompt_spec == '\\') {
141: switch (prompt_spec[1]) {
142: case '\\':
143: smart_str_appendc(&retval, '\\');
144: prompt_spec++;
145: break;
146: case 'n':
147: smart_str_appendc(&retval, '\n');
148: prompt_spec++;
149: break;
150: case 't':
151: smart_str_appendc(&retval, '\t');
152: prompt_spec++;
153: break;
154: case 'e':
155: smart_str_appendc(&retval, '\033');
156: prompt_spec++;
157: break;
158:
159:
160: case 'v':
161: smart_str_appends(&retval, PHP_VERSION);
162: prompt_spec++;
163: break;
164: case 'b':
165: smart_str_appends(&retval, block);
166: prompt_spec++;
167: break;
168: case '>':
169: smart_str_appendc(&retval, prompt);
170: prompt_spec++;
171: break;
172: case '`':
173: smart_str_appendc(&retval, '`');
174: prompt_spec++;
175: break;
176: default:
177: smart_str_appendc(&retval, '\\');
178: break;
179: }
180: } else if (*prompt_spec == '`') {
181: char *prompt_end = strstr(prompt_spec + 1, "`");
182: char *code;
183:
184: if (prompt_end) {
185: code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
186:
187: CLIR_G(prompt_str) = &retval;
188: zend_try {
189: zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code" TSRMLS_CC);
190: } zend_end_try();
191: CLIR_G(prompt_str) = NULL;
192: efree(code);
193: prompt_spec = prompt_end;
194: }
195: } else {
196: smart_str_appendc(&retval, *prompt_spec);
197: }
198: } while (++prompt_spec && *prompt_spec);
199: smart_str_0(&retval);
200: return retval.c;
201: }
202: /* }}} */
203:
204: static int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
205: {
206: int valid_end = 1, last_valid_end;
207: int brackets_count = 0;
208: int brace_count = 0;
209: int i;
210: php_code_type code_type = body;
211: char *heredoc_tag;
212: int heredoc_len;
213:
214: for (i = 0; i < len; ++i) {
215: switch(code_type) {
216: default:
217: switch(code[i]) {
218: case '{':
219: brackets_count++;
220: valid_end = 0;
221: break;
222: case '}':
223: if (brackets_count > 0) {
224: brackets_count--;
225: }
226: valid_end = brackets_count ? 0 : 1;
227: break;
228: case '(':
229: brace_count++;
230: valid_end = 0;
231: break;
232: case ')':
233: if (brace_count > 0) {
234: brace_count--;
235: }
236: valid_end = 0;
237: break;
238: case ';':
239: valid_end = brace_count == 0 && brackets_count == 0;
240: break;
241: case ' ':
242: case '\r':
243: case '\n':
244: case '\t':
245: break;
246: case '\'':
247: code_type = sstring;
248: break;
249: case '"':
250: code_type = dstring;
251: break;
252: case '#':
253: code_type = comment_line;
254: break;
255: case '/':
256: if (code[i+1] == '/') {
257: i++;
258: code_type = comment_line;
259: break;
260: }
261: if (code[i+1] == '*') {
262: last_valid_end = valid_end;
263: valid_end = 0;
264: code_type = comment_block;
265: i++;
266: break;
267: }
268: valid_end = 0;
269: break;
270: case '%':
271: if (!CG(asp_tags)) {
272: valid_end = 0;
273: break;
274: }
275: /* no break */
276: case '?':
277: if (code[i+1] == '>') {
278: i++;
279: code_type = outside;
280: break;
281: }
282: valid_end = 0;
283: break;
284: case '<':
285: valid_end = 0;
286: if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
287: i += 2;
288: code_type = heredoc_start;
289: heredoc_len = 0;
290: }
291: break;
292: default:
293: valid_end = 0;
294: break;
295: }
296: break;
297: case sstring:
298: if (code[i] == '\\') {
299: code_type = sstring_esc;
300: } else {
301: if (code[i] == '\'') {
302: code_type = body;
303: }
304: }
305: break;
306: case sstring_esc:
307: code_type = sstring;
308: break;
309: case dstring:
310: if (code[i] == '\\') {
311: code_type = dstring_esc;
312: } else {
313: if (code[i] == '"') {
314: code_type = body;
315: }
316: }
317: break;
318: case dstring_esc:
319: code_type = dstring;
320: break;
321: case comment_line:
322: if (code[i] == '\n') {
323: code_type = body;
324: }
325: break;
326: case comment_block:
327: if (code[i-1] == '*' && code[i] == '/') {
328: code_type = body;
329: valid_end = last_valid_end;
330: }
331: break;
332: case heredoc_start:
333: switch(code[i]) {
334: case ' ':
335: case '\t':
336: case '\'':
337: break;
338: case '\r':
339: case '\n':
340: code_type = heredoc;
341: break;
342: default:
343: if (!heredoc_len) {
344: heredoc_tag = code+i;
345: }
346: heredoc_len++;
347: break;
348: }
349: break;
350: case heredoc:
351: if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
352: code_type = body;
353: } else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
354: code_type = body;
355: valid_end = 1;
356: }
357: break;
358: case outside:
359: if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
360: || (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
361: || (i > 3 && !strncmp(code+i-4, "<?php", 5))
362: ) {
363: code_type = body;
364: }
365: break;
366: }
367: }
368:
369: switch (code_type) {
370: default:
371: if (brace_count) {
372: *prompt = cli_get_prompt("php", '(' TSRMLS_CC);
373: } else if (brackets_count) {
374: *prompt = cli_get_prompt("php", '{' TSRMLS_CC);
375: } else {
376: *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
377: }
378: break;
379: case sstring:
380: case sstring_esc:
381: *prompt = cli_get_prompt("php", '\'' TSRMLS_CC);
382: break;
383: case dstring:
384: case dstring_esc:
385: *prompt = cli_get_prompt("php", '"' TSRMLS_CC);
386: break;
387: case comment_block:
388: *prompt = cli_get_prompt("/* ", '>' TSRMLS_CC);
389: break;
390: case heredoc:
391: *prompt = cli_get_prompt("<<<", '>' TSRMLS_CC);
392: break;
393: case outside:
394: *prompt = cli_get_prompt(" ", '>' TSRMLS_CC);
395: break;
396: }
397:
398: if (!valid_end || brackets_count) {
399: return 0;
400: } else {
401: return 1;
402: }
403: }
404: /* }}} */
405:
406: static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC) /* {{{ */
407: {
408: char *name;
409: ulong number;
410:
411: if (!(*state % 2)) {
412: zend_hash_internal_pointer_reset(ht);
413: (*state)++;
414: }
415: while(zend_hash_has_more_elements(ht) == SUCCESS) {
416: zend_hash_get_current_key(ht, &name, &number, 0);
417: if (!textlen || !strncmp(name, text, textlen)) {
418: if (pData) {
419: zend_hash_get_current_data(ht, pData);
420: }
421: zend_hash_move_forward(ht);
422: return name;
423: }
424: if (zend_hash_move_forward(ht) == FAILURE) {
425: break;
426: }
427: }
428: (*state)++;
429: return NULL;
430: } /* }}} */
431:
432: static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
433: {
434: char *retval, *tmp;
435:
436: tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);
437: if (retval) {
438: retval = malloc(strlen(tmp) + 2);
439: retval[0] = '$';
440: strcpy(&retval[1], tmp);
441: rl_completion_append_character = '\0';
442: }
443: return retval;
444: } /* }}} */
445:
446: static char *cli_completion_generator_ini(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
447: {
448: char *retval, *tmp;
449:
450: tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL TSRMLS_CC);
451: if (retval) {
452: retval = malloc(strlen(tmp) + 2);
453: retval[0] = '#';
454: strcpy(&retval[1], tmp);
455: rl_completion_append_character = '=';
456: }
457: return retval;
458: } /* }}} */
459:
460: static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
461: {
462: zend_function *func;
463: char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
464: if (retval) {
465: rl_completion_append_character = '(';
466: retval = strdup(func->common.function_name);
467: }
468:
469: return retval;
470: } /* }}} */
471:
472: static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
473: {
474: zend_class_entry **pce;
475: char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);
476: if (retval) {
477: rl_completion_append_character = '\0';
478: retval = strdup((*pce)->name);
479: }
480:
481: return retval;
482: } /* }}} */
483:
484: static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
485: {
486: zend_class_entry **pce;
487: char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);
488: if (retval) {
489: rl_completion_append_character = '\0';
490: retval = strdup(retval);
491: }
492:
493: return retval;
494: } /* }}} */
495:
496: static int cli_completion_state;
497:
498: static char *cli_completion_generator(const char *text, int index) /* {{{ */
499: {
500: /*
501: TODO:
502: - constants
503: - maybe array keys
504: - language constructs and other things outside a hashtable (echo, try, function, class, ...)
505: - object/class members
506:
507: - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
508: */
509: char *retval = NULL;
510: int textlen = strlen(text);
511: TSRMLS_FETCH();
512:
513: if (!index) {
514: cli_completion_state = 0;
515: }
516: if (text[0] == '$') {
517: retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);
518: } else if (text[0] == '#') {
519: retval = cli_completion_generator_ini(text, textlen, &cli_completion_state TSRMLS_CC);
520: } else {
521: char *lc_text, *class_name, *class_name_end;
522: int class_name_len;
523: zend_class_entry **pce = NULL;
524:
525: class_name_end = strstr(text, "::");
526: if (class_name_end) {
527: class_name_len = class_name_end - text;
528: class_name = zend_str_tolower_dup(text, class_name_len);
529: class_name[class_name_len] = '\0'; /* not done automatically */
530: if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {
531: efree(class_name);
532: return NULL;
533: }
534: lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
535: textlen -= (class_name_len + 2);
536: } else {
537: lc_text = zend_str_tolower_dup(text, textlen);
538: }
539:
540: switch (cli_completion_state) {
541: case 0:
542: case 1:
543: retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);
544: if (retval) {
545: break;
546: }
547: case 2:
548: case 3:
549: retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);
550: if (retval || pce) {
551: break;
552: }
553: case 4:
554: case 5:
555: retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);
556: break;
557: default:
558: break;
559: }
560: efree(lc_text);
561: if (class_name_end) {
562: efree(class_name);
563: }
564: if (pce && retval) {
565: int len = class_name_len + 2 + strlen(retval) + 1;
566: char *tmp = malloc(len);
567:
568: snprintf(tmp, len, "%s::%s", (*pce)->name, retval);
569: free(retval);
570: retval = tmp;
571: }
572: }
573:
574: return retval;
575: } /* }}} */
576:
577: static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
578: {
579: return rl_completion_matches(text, cli_completion_generator);
580: }
581: /* }}} */
582:
583: static int readline_shell_run(TSRMLS_D) /* {{{ */
584: {
585: char *line;
586: size_t size = 4096, pos = 0, len;
587: char *code = emalloc(size);
588: char *prompt = cli_get_prompt("php", '>' TSRMLS_CC);
589: char *history_file;
590:
591: if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
592: zend_file_handle *prepend_file_p;
593: zend_file_handle prepend_file = {0};
594:
595: prepend_file.filename = PG(auto_prepend_file);
596: prepend_file.opened_path = NULL;
597: prepend_file.free_filename = 0;
598: prepend_file.type = ZEND_HANDLE_FILENAME;
599: prepend_file_p = &prepend_file;
600:
601: zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 1, prepend_file_p);
602: }
603:
604: history_file = tilde_expand("~/.php_history");
605: rl_attempted_completion_function = cli_code_completion;
606: rl_special_prefixes = "$";
607: read_history(history_file);
608:
609: EG(exit_status) = 0;
610: while ((line = readline(prompt)) != NULL) {
611: if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
612: free(line);
613: break;
614: }
615:
616: if (!pos && !*line) {
617: free(line);
618: continue;
619: }
620:
621: len = strlen(line);
622:
623: if (line[0] == '#') {
624: char *param = strstr(&line[1], "=");
625: if (param) {
626: char *cmd;
627: uint cmd_len;
628: param++;
629: cmd_len = param - &line[1] - 1;
630: cmd = estrndup(&line[1], cmd_len);
631:
632: zend_alter_ini_entry_ex(cmd, cmd_len + 1, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC);
633: efree(cmd);
634: add_history(line);
635:
636: efree(prompt);
637: /* TODO: This might be wrong! */
638: prompt = cli_get_prompt("php", '>' TSRMLS_CC);
639: continue;
640: }
641: }
642:
643: if (pos + len + 2 > size) {
644: size = pos + len + 2;
645: code = erealloc(code, size);
646: }
647: memcpy(&code[pos], line, len);
648: pos += len;
649: code[pos] = '\n';
650: code[++pos] = '\0';
651:
652: if (*line) {
653: add_history(line);
654: }
655:
656: free(line);
657: efree(prompt);
658:
659: if (!cli_is_valid_code(code, pos, &prompt TSRMLS_CC)) {
660: continue;
661: }
662:
663: zend_try {
664: zend_eval_stringl(code, pos, NULL, "php shell code" TSRMLS_CC);
665: } zend_end_try();
666:
667: pos = 0;
668:
669: if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
670: readline_shell_write("\n", 1 TSRMLS_CC);
671: }
672:
673: if (EG(exception)) {
674: zend_exception_error(EG(exception), E_WARNING TSRMLS_CC);
675: }
676:
677: if (pager_pipe) {
678: fclose(pager_pipe);
679: pager_pipe = NULL;
680: }
681:
682: php_last_char = '\0';
683: }
684: write_history(history_file);
685: free(history_file);
686: efree(code);
687: efree(prompt);
688: return EG(exit_status);
689: }
690: /* }}} */
691:
692: /*
693: #ifdef COMPILE_DL_READLINE
694: This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
695: extensions. If that is being changed dlsym() should only be used when building
696: this extension sharedto offer compatibility.
697: */
698: #define GET_SHELL_CB(cb) \
699: do { \
700: (cb) = NULL; \
701: cli_shell_callbacks_t *(*get_callbacks)(); \
702: get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
703: if (get_callbacks) { \
704: (cb) = get_callbacks(); \
705: } \
706: } while(0)
707: /*#else
708: #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
709: #endif*/
710:
711: PHP_MINIT_FUNCTION(cli_readline)
712: {
713: cli_shell_callbacks_t *cb;
714:
715: ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
716: REGISTER_INI_ENTRIES();
717:
718: #if HAVE_LIBEDIT
719: REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
720: #else
721: REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
722: #endif
723:
724: GET_SHELL_CB(cb);
725: if (cb) {
726: cb->cli_shell_write = readline_shell_write;
727: cb->cli_shell_ub_write = readline_shell_ub_write;
728: cb->cli_shell_run = readline_shell_run;
729: }
730:
731: return SUCCESS;
732: }
733:
734: PHP_MSHUTDOWN_FUNCTION(cli_readline)
735: {
736: cli_shell_callbacks_t *cb;
737:
738: UNREGISTER_INI_ENTRIES();
739:
740: GET_SHELL_CB(cb);
741: if (cb) {
742: cb->cli_shell_write = NULL;
743: cb->cli_shell_ub_write = NULL;
744: cb->cli_shell_run = NULL;
745: }
746:
747: return SUCCESS;
748: }
749:
750: PHP_MINFO_FUNCTION(cli_readline)
751: {
752: php_info_print_table_start();
753: php_info_print_table_header(2, "Readline Support", "enabled");
754: php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
755: php_info_print_table_end();
756:
757: DISPLAY_INI_ENTRIES();
758: }
759:
760: /*
761: * Local variables:
762: * tab-width: 4
763: * c-basic-offset: 4
764: * End:
765: * vim600: sw=4 ts=4 fdm=marker
766: * vim<600: sw=4 ts=4
767: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>