Skip to content

Commit f45dd11

Browse files
committed
feat: wrap the readline functions
Signed-off-by: inhere <[email protected]>
1 parent 1eea697 commit f45dd11

File tree

9 files changed

+543
-4
lines changed

9 files changed

+543
-4
lines changed

example/readline/cb_handler.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
/**
3+
* https://www.php.net/manual/zh/function.readline-callback-handler-install.php
4+
*/
5+
6+
require dirname(__DIR__) . '/../test/bootstrap.php';
7+
8+
/**
9+
* @param int $count
10+
* @param string|null $prompt
11+
*
12+
* @return string
13+
* @link https://www.php.net/manual/zh/function.readline-callback-handler-install.php#123075
14+
*/
15+
function handler_read_demo(int $count, string $prompt = null): string
16+
{
17+
$prev = '';
18+
19+
// 初始化一个 readline 回调接口,然后终端输出提示信息并立即返回
20+
// TIP: 第二次调用这个函数不需要移除上一个回调接口,这个函数将自动覆盖旧的接口
21+
readline_callback_handler_install($prompt ?? " \e[D", function ($input) use (&$prev) {
22+
echo "Input is: $input\n";
23+
$prev .= $input . '|';
24+
});
25+
26+
$str = '';
27+
do {
28+
$r = [STDIN];
29+
// 配合 stream_select() 时回调的特性非常有用,它允许在 IO 与用户输入 间交叉进行
30+
$n = stream_select($r, $w, $e, null);
31+
32+
// TIP: 每输入一个字符都会触发这里
33+
if ($n && in_array(STDIN, $r, true)) {
34+
// 当一个行被接收时读取一个字符并且通知 readline 调用回调函数
35+
readline_callback_read_char();
36+
// vdump(readline_info());
37+
// $str = $prev . readline_info('line_buffer');
38+
$str .= readline_info('line_buffer') . '|';
39+
}
40+
} while (mb_strlen($str) < $count); // use strlen if you need the exact byte count
41+
42+
// 移除上一个安装的回调函数句柄并且恢复终端设置
43+
readline_callback_handler_remove();
44+
45+
echo "prev: $prev\n";
46+
return $str;
47+
}
48+
49+
$str = handler_read_demo(20, 'TEST > ');
50+
51+
echo "str: $str";

example/readline/cb_handler2.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
/**
3+
* https://www.jb51.net/article/212496.htm
4+
*/
5+
6+
use Toolkit\Cli\Util\Readline;
7+
8+
require dirname(__DIR__) . '/../test/bootstrap.php';
9+
10+
// 输出的内容进入这个回调函数中
11+
function rl_callback($ret)
12+
{
13+
global $c, $prompting;
14+
15+
echo "您输入的内容是: $ret\n";
16+
$c++;
17+
18+
readline_add_history($ret);
19+
20+
// 限制了就调用10次,也可以通过命令行输入的内容来判断,比如上面的 exit 那种进行退出
21+
if ($c > 10) {
22+
$prompting = false;
23+
// 移除上一个安装的回调函数句柄并且恢复终端设置
24+
readline_callback_handler_remove();
25+
} else {
26+
// 继续进行递归回调
27+
readline_callback_handler_install("[$c] 输入点什么内容: ", 'rl_callback');
28+
}
29+
}
30+
31+
$c = 1;
32+
$prompting = true;
33+
34+
// 初始化一个 readline 回调接口,然后终端输出提示信息并立即返回,需要等待 readline_callback_read_char() 函数调用后才会进入到回调函数中
35+
readline_callback_handler_install("[$c] 输入点什么内容: ", 'rl_callback');
36+
37+
// 当 $prompting 为 ture 时,一直等待输入信息
38+
while ($prompting) {
39+
$w = null;
40+
$e = null;
41+
$r = [STDIN];
42+
$n = stream_select($r, $w, $e, null);
43+
if ($n && in_array(STDIN, $r, true)) {
44+
// 当一个行被接收时读取一个字符并且通知 readline 调用回调函数
45+
readline_callback_read_char();
46+
}
47+
}
48+
49+
echo "结束,完成所有输入!\n";
50+
// [1] 输入点什么内容: A
51+
// 您输入的内容是: A
52+
// [2] 输入点什么内容: B
53+
// 您输入的内容是: B
54+
// [3] 输入点什么内容: C
55+
// 您输入的内容是: C
56+
// [4] 输入点什么内容: D
57+
// 您输入的内容是: D
58+
// [5] 输入点什么内容: E
59+
// 您输入的内容是: E
60+
// [6] 输入点什么内容: F
61+
// 您输入的内容是: F
62+
// [7] 输入点什么内容: G
63+
// 您输入的内容是: G
64+
// [8] 输入点什么内容: H
65+
// 您输入的内容是: H
66+
// [9] 输入点什么内容: I
67+
// 您输入的内容是: I
68+
// [10] 输入点什么内容: J
69+
// 您输入的内容是: J
70+
// 结束,完成所有输入!
71+
72+
// print_r(readline_list_history());
73+
Readline::setListTmpFile(__DIR__ . '/cb_handler2.txt');
74+
print_r(Readline::listHistory());
75+
// Array
76+
// (
77+
// [0] => A
78+
// [1] => B
79+
// [2] => C
80+
// [3] => D
81+
// [4] => E
82+
// [5] => F
83+
// [6] => G
84+
// [7] => H
85+
// [8] => I
86+
// [9] => J
87+
// )

example/readline/cb_handler2.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
_HiStOrY_V2_
2+
a
3+
b
4+
c
5+
6+
g
7+
dfd
8+
df
9+
10+
sd
11+
s

example/readline/complete.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* @link https://www.php.net/manual/zh/function.readline-completion-function.php
4+
*/
5+
6+
require dirname(__DIR__) . '/../test/bootstrap.php';
7+
8+
function your_callback($input, $index): array
9+
{
10+
// Get info about the current buffer
11+
/*
12+
string(3) "efg" // last words
13+
int(4)
14+
array(6) {
15+
["line_buffer"]=> string(7) "adb efg" // full line
16+
["point"]=> int(7)
17+
["end"]=> int(7)
18+
["library_version"]=> string(16) "EditLine wrapper"
19+
["readline_name"]=> string(0) ""
20+
["attempted_completion_over"]=> int(0)
21+
}
22+
23+
*/
24+
$rlInfo = readline_info();
25+
26+
// Figure out what the entire input is
27+
$full_input = substr($rlInfo['line_buffer'], 0, $rlInfo['end']);
28+
29+
// vdump($input, $index, $full_input, $rlInfo);
30+
// vdump($input, $index, $rlInfo);
31+
32+
$samples = [
33+
'abc',
34+
'tom',
35+
'inhere',
36+
'quit',
37+
'exit',
38+
];
39+
$matches = [];
40+
if (!$input) {
41+
return $samples;
42+
}
43+
44+
// Get all matches based on the entire input buffer
45+
foreach ($samples as $phrase) {
46+
// Only add the end of the input (where this word begins)
47+
// to the matches array
48+
$matches[] = substr($phrase, $index);
49+
}
50+
51+
return $matches;
52+
}
53+
54+
$ok = readline_completion_function('your_callback');
55+
56+
// 使用 Tab 键测试一下吧
57+
// $line = \Toolkit\Cli\Cli::readln('please input> ');
58+
$line = trim(readline("please input> "));
59+
if (!empty($line)) {
60+
readline_add_history($line);
61+
}
62+
63+
echo $line, PHP_EOL; // 当前输入的命令信息
64+
// 如果命令是 exit 或者 quit ,就退出程序执行
65+
if($line === 'exit' || $line === 'quit'){
66+
exit('bye');
67+
}

example/readline/readline.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
require dirname(__DIR__) . '/../test/bootstrap.php';
4+
5+
$line = readline("command:"); // 读取命令行交互信息
6+
if (!empty($line)) {
7+
readline_add_history($line); // 需要手动加入到命令历史记录中
8+
}
9+
echo $line, PHP_EOL; // aaa
10+
11+
$line = readline("command:");
12+
if (!empty($line)) {
13+
readline_add_history($line);
14+
}
15+
16+
readline_write_history($filename = __DIR__ . '/rl_history.txt');
17+
18+
// 命令历史记录列表
19+
// TIP: on READLINE_LIB=libedit not support the function.
20+
// var_dump(readline_list_history());
21+
var_dump(file_get_contents($filename));
22+
// Array
23+
// (
24+
// [0] => aaa
25+
// [1] => bbb
26+
// )
27+
28+
// 当前命令行内部的变量信息
29+
var_dump(readline_info());
30+
// Array
31+
// (
32+
// [line_buffer] => bbb
33+
// [point] => 3
34+
// [end] => 3
35+
// [mark] => 0
36+
// [done] => 1
37+
// [pending_input] => 0
38+
// [prompt] => 请输入命令:
39+
// [terminal_name] => xterm-256color
40+
// [completion_append_character] =>
41+
// [completion_suppress_append] =>
42+
// [library_version] => 7.0
43+
// [readline_name] => other
44+
// [attempted_completion_over] => 0
45+
// )

example/readline/rl_history.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
_HiStOrY_V2_
2+
dfd
3+
faddf

src/Traits/ReadMessageTrait.php

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
namespace Toolkit\Cli\Traits;
1111

12+
use Toolkit\Cli\Cli;
13+
use Toolkit\Cli\Util\Readline;
14+
use function fopen;
15+
use function strip_tags;
1216
use const STDIN;
1317

1418
/**
@@ -35,7 +39,7 @@ trait ReadMessageTrait
3539
public static function read($message = null, bool $nl = false, array $opts = []): string
3640
{
3741
if ($message) {
38-
self::write($message, $nl);
42+
Cli::write($message, $nl);
3943
}
4044

4145
$opts = array_merge([
@@ -61,9 +65,15 @@ public static function read($message = null, bool $nl = false, array $opts = [])
6165
public static function readln($message = null, bool $nl = false, array $opts = []): string
6266
{
6367
if ($message) {
64-
self::write($message, $nl);
68+
Cli::write($message, $nl);
6569
}
6670

71+
// TIP: use readline method, support left and right press.
72+
if (Readline::isSupported()) {
73+
return Readline::readline();
74+
}
75+
76+
// read from input stream
6777
$opts = array_merge([
6878
'length' => 1024,
6979
'stream' => self::$inputStream,
@@ -97,7 +107,7 @@ public static function readRow($message = null, bool $nl = false): string
97107
public static function readSafe($message = null, bool $nl = false, array $opts = []): string
98108
{
99109
if ($message) {
100-
self::write($message, $nl);
110+
Cli::write($message, $nl);
101111
}
102112

103113
$opts = array_merge([
@@ -106,7 +116,9 @@ public static function readSafe($message = null, bool $nl = false, array $opts =
106116
'allowTags' => null,
107117
], $opts);
108118

109-
return trim(fgetss($opts['stream'], $opts['length'], $opts['allowTags']));
119+
// up: fgetss has been DEPRECATED as of PHP 7.3.0
120+
// return trim(fgetss($opts['stream'], $opts['length'], $opts['allowTags']));
121+
return trim(strip_tags(fgets($opts['stream'], $opts['length']), $opts['allowTags']));
110122
}
111123

112124
/**
@@ -137,6 +149,33 @@ public static function readFirst(string $message = '', bool $nl = false): string
137149
return self::readChar($message, $nl);
138150
}
139151

152+
/**
153+
* Read password text.
154+
* NOTICE: only support linux
155+
*
156+
* @param string $prompt
157+
*
158+
* @return string
159+
* @link https://www.php.net/manual/zh/function.readline.php#120729
160+
*/
161+
public static function readPassword(string $prompt = ''): string
162+
{
163+
$termDevice = '/dev/tty';
164+
if ($prompt) {
165+
Cli::write($prompt);
166+
}
167+
168+
$h = fopen($termDevice, 'rb');
169+
if ($h === false) {
170+
// throw new RuntimeException("Failed to open terminal device");
171+
return ''; // probably not running in a terminal.
172+
}
173+
174+
$line = trim((string)fgets($h));
175+
fclose($h);
176+
return $line;
177+
}
178+
140179
/**
141180
* @return false|resource
142181
*/

0 commit comments

Comments
 (0)