PageRenderTime 42ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/ext/readline/readline_cli.c

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