Skip to content

Commit e5f3a26

Browse files
committed
Adds Regex Parser CLI tool
Introduces a command-line interface for the RegexParser library. This tool provides functionalities such as parsing, analyzing, highlighting, and validating regular expressions directly from the command line. It supports different output formats and ANSI color codes for improved readability. The CLI tool allows users to quickly test and debug regular expressions.
1 parent 613528b commit e5f3a26

File tree

1 file changed

+125
-51
lines changed

1 file changed

+125
-51
lines changed

bin/regex

Lines changed: 125 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,107 @@ use RegexParser\Exception\LexerException;
1818
use RegexParser\Exception\ParserException;
1919
use RegexParser\Regex;
2020

21+
// ANSI color codes
22+
const RESET = "\033[0m";
23+
const RED = "\033[31m";
24+
const GREEN = "\033[32m";
25+
const YELLOW = "\033[33m";
26+
const BLUE = "\033[34m";
27+
const MAGENTA = "\033[35m";
28+
const CYAN = "\033[36m";
29+
const WHITE = "\033[37m";
30+
const BOLD = "\033[1m";
31+
32+
$options = getopt('q', ['quiet', 'silent', 'ansi', 'no-ansi', 'help']);
33+
$quiet = isset($options['q']) || isset($options['quiet']) || isset($options['silent']);
34+
$ansi = !isset($options['no-ansi']) && (isset($options['ansi']) || (function_exists('posix_isatty') && posix_isatty(STDOUT)));
35+
36+
function color(string $text, string $color): string
37+
{
38+
global $ansi;
39+
return $ansi ? $color . $text . RESET : $text;
40+
}
41+
42+
function success(string $text): string
43+
{
44+
return color($text, GREEN);
45+
}
46+
47+
function error(string $text): string
48+
{
49+
return color($text, RED);
50+
}
51+
52+
function warning(string $text): string
53+
{
54+
return color($text, YELLOW);
55+
}
56+
57+
function info(string $text): string
58+
{
59+
return color($text, BLUE);
60+
}
61+
62+
function bold(string $text): string
63+
{
64+
return color($text, BOLD);
65+
}
66+
67+
function output(string $text): void
68+
{
69+
global $quiet;
70+
if (!$quiet) {
71+
echo $text;
72+
}
73+
}
74+
2175
function showHelp(): void
2276
{
23-
echo "Regex Parser CLI Tool\n\n";
24-
echo "Usage: regex <command> [options] <pattern>\n\n";
25-
echo "Commands:\n";
26-
echo " parse <pattern> [--validate] Parse and recompile a regex pattern\n";
27-
echo " analyze <pattern> Analyze a regex (parse, validate, ReDoS, explain)\n";
28-
echo " highlight <pattern> [--format=auto|cli|html] Highlight regex for display\n";
29-
echo " validate <pattern> Validate a regex pattern\n";
30-
echo " help Show this help\n\n";
31-
echo "Examples:\n";
32-
echo " regex parse '/a+/'\n";
33-
echo " regex analyze '/a+/'\n";
34-
echo " regex highlight '/a+/' --format=cli\n";
35-
echo " regex validate '/a+/'\n";
77+
output(bold("Regex Parser CLI Tool") . "\n\n");
78+
output("Usage: regex <command> [options] <pattern>\n\n");
79+
output(bold("Commands:\n"));
80+
output(" " . info("parse") . " <pattern> [--validate] Parse and recompile a regex pattern\n");
81+
output(" " . info("analyze") . " <pattern> Analyze a regex (parse, validate, ReDoS, explain)\n");
82+
output(" " . info("highlight") . " <pattern> [--format=auto|cli|html] Highlight regex for display\n");
83+
output(" " . info("validate") . " <pattern> Validate a regex pattern\n");
84+
output(" " . info("help") . " Show this help\n\n");
85+
output(bold("Global Options:\n"));
86+
output(" --ansi Force ANSI output\n");
87+
output(" --no-ansi Disable ANSI output\n");
88+
output(" -q, --quiet Suppress output\n");
89+
output(" --silent Same as --quiet\n");
90+
output(" --help Show this help\n\n");
91+
output(bold("Examples:\n"));
92+
output(" regex parse '/a+/'\n");
93+
output(" regex analyze '/a+/'\n");
94+
output(" regex highlight '/a+/' --format=cli\n");
95+
output(" regex validate '/a+/'\n");
96+
output(" regex '/a+/' # Shortcut for highlight\n");
3697
}
3798

38-
if ($argc < 2) {
99+
// Remove processed options from argv
100+
$argv = array_values(array_filter($argv, fn($arg) => !str_starts_with($arg, '-')));
101+
102+
if (isset($options['help'])) {
103+
showHelp();
104+
exit(0);
105+
}
106+
107+
if (count($argv) < 2) {
39108
showHelp();
40109
exit(1);
41110
}
42111

43112
$command = $argv[1];
44113

114+
// Shortcut: if command looks like a regex pattern, assume highlight
115+
if (str_starts_with($command, '/')) {
116+
$pattern = $command;
117+
$command = 'highlight';
118+
} else {
119+
$pattern = $argv[2] ?? '';
120+
}
121+
45122
switch ($command) {
46123
case 'help':
47124
case '--help':
@@ -50,43 +127,41 @@ switch ($command) {
50127
exit(0);
51128

52129
case 'parse':
53-
if ($argc < 3) {
54-
echo "Error: Missing pattern\n";
55-
echo "Usage: regex parse <pattern> [--validate]\n";
130+
if (empty($pattern)) {
131+
output(error("Error: Missing pattern\n"));
132+
output("Usage: regex parse <pattern> [--validate]\n");
56133
exit(1);
57134
}
58-
$pattern = $argv[2];
59135
$validate = in_array('--validate', $argv, true);
60136
$regex = Regex::create();
61137

62138
try {
63139
$ast = $regex->parse($pattern);
64140
$compiled = $ast->accept(new RegexParser\NodeVisitor\CompilerNodeVisitor());
65141

66-
echo "Parsed successfully!\n";
67-
echo "Original: {$pattern}\n";
68-
echo "Recompiled: {$compiled}\n";
142+
output(success("Parsed successfully!\n"));
143+
output("Original: {$pattern}\n");
144+
output("Recompiled: {$compiled}\n");
69145

70146
if ($validate) {
71147
$validation = $regex->validate($pattern);
72-
echo "Validation: " . ($validation->isValid ? 'OK' : 'INVALID') . "\n";
148+
output("Validation: " . ($validation->isValid ? success('OK') : error('INVALID')) . "\n");
73149
if (!$validation->isValid && $validation->error) {
74-
echo "Error: {$validation->error}\n";
150+
output(error("Error: {$validation->error}\n"));
75151
}
76152
}
77153
} catch (LexerException|ParserException $e) {
78-
echo "Error: {$e->getMessage()}\n";
154+
output(error("Error: {$e->getMessage()}\n"));
79155
exit(1);
80156
}
81157
break;
82158

83159
case 'analyze':
84-
if ($argc < 3) {
85-
echo "Error: Missing pattern\n";
86-
echo "Usage: regex analyze <pattern>\n";
160+
if (empty($pattern)) {
161+
output(error("Error: Missing pattern\n"));
162+
output("Usage: regex analyze <pattern>\n");
87163
exit(1);
88164
}
89-
$pattern = $argv[2];
90165
$regex = Regex::create();
91166

92167
try {
@@ -95,32 +170,31 @@ switch ($command) {
95170
$analysis = $regex->analyzeReDoS($pattern);
96171
$explain = $regex->explain($pattern);
97172

98-
echo "Pattern: {$pattern}\n";
99-
echo "Parse: OK\n";
100-
echo "Validation: " . ($validation->isValid ? 'OK' : 'INVALID') . "\n";
173+
output(bold("Pattern: ") . "{$pattern}\n");
174+
output("Parse: " . success("OK") . "\n");
175+
output("Validation: " . ($validation->isValid ? success('OK') : error('INVALID')) . "\n");
101176
if (!$validation->isValid && $validation->error) {
102-
echo "Error: {$validation->error}\n";
177+
output(error("Error: {$validation->error}\n"));
103178
}
104-
echo "ReDoS severity: {$analysis->severity->value}\n";
105-
echo "ReDoS score: {$analysis->score}\n";
179+
output("ReDoS severity: " . warning($analysis->severity->value) . "\n");
180+
output("ReDoS score: {$analysis->score}\n");
106181
if ($analysis->error) {
107-
echo "ReDoS error: {$analysis->error}\n";
182+
output(error("ReDoS error: {$analysis->error}\n"));
108183
}
109-
echo "\nExplanation:\n";
110-
echo $explain . "\n";
184+
output("\n" . bold("Explanation:") . "\n");
185+
output($explain . "\n");
111186
} catch (LexerException|ParserException $e) {
112-
echo "Error: {$e->getMessage()}\n";
187+
output(error("Error: {$e->getMessage()}\n"));
113188
exit(1);
114189
}
115190
break;
116191

117192
case 'highlight':
118-
if ($argc < 3) {
119-
echo "Error: Missing pattern\n";
120-
echo "Usage: regex highlight <pattern> [--format=auto|cli|html]\n";
193+
if (empty($pattern)) {
194+
output(error("Error: Missing pattern\n"));
195+
output("Usage: regex highlight <pattern> [--format=auto|cli|html]\n");
121196
exit(1);
122197
}
123-
$pattern = $argv[2];
124198
$format = 'auto';
125199
foreach ($argv as $arg) {
126200
if (str_starts_with($arg, '--format=')) {
@@ -138,32 +212,32 @@ switch ($command) {
138212
$highlighted = $regex->highlight($pattern);
139213
}
140214

141-
echo $highlighted . "\n";
215+
output($highlighted . "\n");
142216
} catch (LexerException|ParserException $e) {
143-
echo "Error: {$e->getMessage()}\n";
217+
output(error("Error: {$e->getMessage()}\n"));
144218
exit(1);
145219
}
146220
break;
147221

148222
case 'validate':
149-
if ($argc < 3) {
150-
echo "Error: Missing pattern\n";
151-
echo "Usage: regex validate <pattern>\n";
223+
if (empty($pattern)) {
224+
output(error("Error: Missing pattern\n"));
225+
output("Usage: regex validate <pattern>\n");
152226
exit(1);
153227
}
154-
$pattern = $argv[2];
155228
$regex = Regex::create();
156229

157230
$validation = $regex->validate($pattern);
158-
echo ($validation->isValid ? 'OK' : 'INVALID') . "\n";
231+
output($validation->isValid ? success('✓ OK') : error('✗ INVALID'));
232+
output("\n");
159233
if (!$validation->isValid && $validation->error) {
160-
echo "Error: {$validation->error}\n";
234+
output(error("Error: {$validation->error}\n"));
161235
exit(1);
162236
}
163237
break;
164238

165239
default:
166-
echo "Unknown command: {$command}\n\n";
240+
output(error("Unknown command: {$command}\n\n"));
167241
showHelp();
168242
exit(1);
169243
}

0 commit comments

Comments
 (0)