Skip to content

Commit e3402e5

Browse files
authored
refs #4561 fixed bugs handling escape chars in regex subst replacement strings (#4563)
refs #4562 fixed support for \a and \e in Qore strings refs #4557 fixed message API, updated qdp
1 parent d7a0d9e commit e3402e5

File tree

16 files changed

+504
-101
lines changed

16 files changed

+504
-101
lines changed

bin/qdp

Lines changed: 192 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@
3737
%requires Util
3838
%requires Logger
3939

40+
%try-module linenoise
41+
%define NoLinenoise
42+
%endtry
43+
4044
%exec-class QdpCmd
4145

4246
class QdpCmd {
4347
public {
48+
static bool ix;
49+
4450
#! program options
4551
const Opts = {
4652
"listconnections": "c,list-connections",
@@ -65,12 +71,18 @@ class QdpCmd {
6571
"field-update": \QdpCmd::fieldUpdate(),
6672
"fupdate": \QdpCmd::fieldUpdate(),
6773
"info": \QdpCmd::getInfo(),
74+
"interactive": \QdpCmd::interactive(),
75+
"ix": \QdpCmd::interactive(),
6876
"ldetails": \QdpCmd::listChildDetails(),
6977
"levents": \QdpCmd::listEvents(),
7078
"list": \QdpCmd::listChildren(),
7179
"list-details": \QdpCmd::listChildDetails(),
7280
"list-events": \QdpCmd::listEvents(),
7381
"listen": \QdpCmd::listen(),
82+
"lmessages": \QdpCmd::listMessages(),
83+
"lmsgs": \QdpCmd::listMessages(),
84+
"message": \QdpCmd::message(),
85+
"msg": \QdpCmd::message(),
7486
"pcreate": \QdpCmd::providerCreate(),
7587
"pdelete": \QdpCmd::providerDelete(),
7688
"provider-create": \QdpCmd::providerCreate(),
@@ -81,6 +93,9 @@ class QdpCmd {
8193
"response": \QdpCmd::response(),
8294
"rsearch": \QdpCmd::doRequestSearch(),
8395
"search": \QdpCmd::search(),
96+
"send-message": \QdpCmd::sendMessage(),
97+
"smessage": \QdpCmd::sendMessage(),
98+
"smsg": \QdpCmd::sendMessage(),
8499
"update": \QdpCmd::update(),
85100
"upsert": \QdpCmd::upsert(),
86101
};
@@ -284,6 +299,59 @@ class QdpCmd {
284299
}
285300
}
286301

302+
static interactive(AbstractDataProvider provider) {
303+
provider.checkObservable();
304+
provider.checkMessages();
305+
MyObserver observer();
306+
Observable observable = cast<Observable>(provider);
307+
308+
ix = True;
309+
310+
# print a message
311+
stdout.printf("listening to events from %y; enter <message id>: <message body> to send a message, 'help' for "
312+
"help\n", provider.getName());
313+
observable.registerObserver(observer);
314+
315+
InputHelper input_helper(provider);
316+
while (True) {
317+
string input = input_helper.get();
318+
#printf("input: %y\n", input);
319+
bool quit;
320+
switch (input) {
321+
case "exit":
322+
case "quit":
323+
quit = True;
324+
break;
325+
}
326+
if (quit) {
327+
break;
328+
}
329+
# check for msg ID
330+
(*string msgid, *string msg) = (input =~ x/([^:]+):(.*)/);
331+
if (!msgid) {
332+
stdout.printf("cannot parse input %y; enter <message id>: <message body> to send a message, "
333+
"'help' for help\n", input);
334+
continue;
335+
}
336+
337+
auto v = parse_to_qore_value(msg);
338+
printf("> sending %y -> %y\n", msgid, v);
339+
provider.sendMessage(msgid, v);
340+
}
341+
ix = False;
342+
printf("stopped\n");
343+
}
344+
345+
static listMessages(AbstractDataProvider provider) {
346+
hash<string, AbstractDataProviderType> messages = provider.getMessageTypes();
347+
map printf(" - %s\n", $1), keys messages;
348+
}
349+
350+
static message(AbstractDataProvider provider) {
351+
string message = QdpCmd::getString("message", True);
352+
QdpCmd::showType(provider.getMessageType(message));
353+
}
354+
287355
static listen(AbstractDataProvider provider) {
288356
provider.checkObservable();
289357
MyObserver observer();
@@ -363,6 +431,13 @@ class QdpCmd {
363431
map printf("%y\n", $1), i;
364432
}
365433

434+
static sendMessage(AbstractDataProvider provider) {
435+
string message_id = QdpCmd::getString("message_id", True);
436+
auto val = QdpCmd::getAny("data");
437+
*hash<auto> send_options = QdpCmd::getHash("send options");
438+
provider.sendMessage(message_id, val, send_options);
439+
}
440+
366441
static update(AbstractDataProvider provider) {
367442
hash<auto> set = QdpCmd::getHash("update 'set'", True);
368443
*hash<auto> where_cond = QdpCmd::getHash("update 'where'", True);
@@ -481,6 +556,14 @@ class QdpCmd {
481556
}
482557
}
483558

559+
static auto getAny(string action) {
560+
auto arg = shift ARGV;
561+
if (exists arg) {
562+
arg = parse_to_qore_value(arg);
563+
}
564+
return arg;
565+
}
566+
484567
private AbstractDataProvider getDataProvider(string name) {
485568
if (name =~ /{([^}]*)}/) {
486569
try {
@@ -572,17 +655,25 @@ class QdpCmd {
572655
errors [<code>]
573656
lists all error replies
574657
event <event>
575-
shows the event type (if supported)
658+
shows the event type
576659
events
577660
list all supported event types
578661
info
579662
show information about the data provider
663+
interactive|ix
664+
listen to events generated by an observable data provider and send response messages
580665
list
581666
list child data provider names
582667
list-details|ldetails
583668
list child data providers with details
669+
list-events|levents
670+
list event types
671+
list-messages|lmessages|lmsgs
672+
list message types
584673
listen
585674
listen to events generated by an observable data provider
675+
message|msg <message>
676+
shows the message type
586677
provider-create|pcreate <name> <desc hash> [<option hash>]
587678
create a new data provider
588679
provider-delete|pdelete <name>
@@ -597,6 +688,8 @@ class QdpCmd {
597688
executes a request and then a search on the results (if supported)
598689
search [search criteria] (ex: search name=\"my name\")
599690
search for record(s) matching the given criteria
691+
send-message|send-msg|smsg <message id> [<message payload>] [<send options>]
692+
sends a message
600693
update <set criteria> <match criteria> (ex update name=other id=123)
601694
update the given records with the given information
602695
upsert <record> (ex update id=123,name=\"my name\")
@@ -671,7 +764,18 @@ class Term {
671764
class MyObserver inherits Observer {
672765
update(string event_id, hash<auto> data_) {
673766
string eventstr = sprintf("%N", data_);
674-
stdout.printf("< %s received event %y:\n< %s\n", now_us().format("YYYY-MM-DD HH:mm:SS.xx"), event_id, eventstr);
767+
string msg = sprintf("< %s received event %y:\n< %s\n", now_us().format("YYYY-MM-DD HH:mm:SS.xx"), event_id,
768+
eventstr);
769+
%ifndef NoLinenoise
770+
msg =~ s/\n/\015\012/g;
771+
%endif
772+
if (QdpCmd::ix) {
773+
stdout.print("\r");
774+
}
775+
stdout.print(msg);
776+
if (QdpCmd::ix) {
777+
stdout.print("> ");
778+
}
675779
stdout.sync();
676780
}
677781
}
@@ -684,8 +788,94 @@ class ConsoleAppender inherits LoggerAppenderWithLayout {
684788
processEventImpl(int type, auto params) {
685789
switch (type) {
686790
case EVENT_LOG:
791+
%ifndef NoLinenoise
792+
params =~ s/\n/\r\n/g;
793+
%endif
687794
print(params);
688795
break;
689796
}
690797
}
691798
}
799+
800+
class InputHelper {
801+
public {
802+
# message map
803+
hash<string, AbstractDataProviderType> mmap;
804+
805+
const Prompt = "> ";
806+
const HistoryFile = ".qdp";
807+
808+
const Commands = {
809+
"help": True,
810+
"history": True,
811+
"exit": True,
812+
"quit": True,
813+
};
814+
}
815+
816+
constructor(AbstractDataProvider provider) {
817+
mmap = provider.getMessageTypes();
818+
819+
%ifndef NoLinenoise
820+
Linenoise::history_set_max_len(100);
821+
Linenoise::set_callback(\completion());
822+
823+
try {
824+
Linenoise::history_load(HistoryFile);
825+
} catch (hash<ExceptionInfo> ex) {
826+
printf("cannot load history: %s: %s\n", ex.err, ex.desc);
827+
}
828+
%endif
829+
}
830+
831+
destructor() {
832+
%ifndef NoLinenoise
833+
Linenoise::history_save(HistoryFile);
834+
%endif
835+
}
836+
837+
string get() {
838+
string line;
839+
while (True) {
840+
%ifndef NoLinenoise
841+
*string line0 = Linenoise::line(Prompt);
842+
if (!exists line0) {
843+
line = "exit";
844+
break;
845+
}
846+
line = line0;
847+
Linenoise::history_add(line);
848+
%else
849+
stdout.print(Prompt);
850+
stdout.sync();
851+
line = trim(stdin.readLine());
852+
%endif
853+
854+
if (line == 'help' || line == '?') {
855+
printf("commands:\n help\n quit\n history\n");
856+
continue;
857+
}
858+
%ifndef NoLinenoise
859+
if (line == 'history') {
860+
map printf("%s\n", $1), Linenoise::history();
861+
continue;
862+
}
863+
%endif
864+
865+
if (!line.val()) {
866+
continue;
867+
}
868+
869+
break;
870+
}
871+
872+
return line;
873+
}
874+
875+
softlist<string> completion(string str) {
876+
list<string> rv = ();
877+
rv += map $1, keys Commands, $1.equalPartial(str);
878+
rv += map $1 + ": ", keys mmap, $1.equalPartial(str);
879+
return rv;
880+
}
881+
}

doxygen/lang/185_qore_regex.dox.tmpl

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
@section qore_regex_intro Qore Regular Expression Introduction
66

7-
Regular expression functionality in %Qore is provided by <a href="http://www.pcre.org">PCRE: Perl-Compatible Regular Expression library</a>.
7+
Regular expression functionality in %Qore is provided by
8+
<a href="http://www.pcre.org">PCRE: Perl-Compatible Regular Expression library</a>.
89

9-
Using this library, %Qore implements regular expression pattern matching using very simple syntax with semantics similar
10-
to those of <a href="http://www.perl.org">Perl 5</a>.
11-
One difference between %Qore and Perl to keep in mind is that @ref qore_regex_backreferences "backreferences" in %Qore
12-
are referenced as \c $1, \c $2, \c $3, etc which differs from Perl's syntax (which uses numbered backslashes instead).
10+
Using this library, %Qore implements regular expression pattern matching using very simple syntax with semantics
11+
similar to those of <a href="http://www.perl.org">Perl 5</a>.
12+
One difference between %Qore and Perl to keep in mind is that @ref qore_regex_backreferences "backreferences" in
13+
%Qore are referenced as \c $1, \c $2, \c $3, etc which differs from Perl's syntax (which uses numbered backslashes
14+
instead).
1315

1416
@par Examples:
1517
@code{.py}
@@ -40,15 +42,18 @@
4042

4143
@section qore_regex_operators Qore Regular Expression Operators
4244

43-
The following is a list of operators based on regular expressions (or similar to regular expressions in the case of the transliteration operator).
45+
The following is a list of operators based on regular expressions (or similar to regular expressions in the case
46+
of the transliteration operator).
4447

4548
<b>Regular Expression Operators</b>
4649
|!Operator|!Description
4750
|@ref regex_match_operator|Returns @ref True "True" if the regular expression matches a string
4851
|@ref regex_no_match_operator|Returns @ref True "True" if the regular expression does not match a string
4952
|@ref regex_subst_operator|Substitutes text in a string based on matching a regular expression
50-
|@ref regex_extract_operator|Returns a list of substrings in a string based on matching patterns defined by a regular expression
51-
|@ref transliteration_operator|Not a regular expression operator; transliterates one or more characters to other characters in a string
53+
|@ref regex_extract_operator|Returns a list of substrings in a string based on matching patterns defined by a \
54+
regular expression
55+
|@ref transliteration_operator|Not a regular expression operator; transliterates one or more characters to other \
56+
characters in a string
5257

5358
See the table below for valid regular expression options.
5459

@@ -57,25 +62,58 @@
5762
<b>Regular Expression Options</b>
5863
|!Option|!Description
5964
|\c i|Ignores case when matching
60-
|\c m|makes start-of-line (<tt>^</tt>) or end-of-line (<tt>$</tt>) match after or before any newline in the subject string
65+
|\c m|makes start-of-line (<tt>^</tt>) or end-of-line (<tt>$</tt>) match after or before any newline in the \
66+
subject string
6167
|\c s|makes a dot (<tt>.</tt>) match a newline character
6268
|\c x|ignores whitespace characters and enables comments prefixed by <tt>#</tt>
6369
|\c u|extends Posix character matching to Unicode characters
64-
|\c g|makes global substitutions or global extractions (only applicable with the substitution and extraction operators)
70+
|\c g|makes global substitutions or global extractions (only applicable with the substitution and extraction \
71+
operators)
6572

6673
@section qore_regex_functions Qore Regular Expression Functions
6774

68-
The following is a list of functions providing regular expression functionality where the pattern may be given at run-time:
75+
The following is a list of functions providing regular expression functionality where the pattern may be given at
76+
run-time:
6977

7078
<b>Regular Expression Functions</b>
7179
|!Function|!Description
7280
|regex()|Returns @ref True "True" if the regular expression matches a string
7381
|regex_subst()|Substitutes a pattern in a string based on regular expressions and returns the new string
74-
|regex_extract()|Returns a list of substrings in a string based on matching patterns defined by a regular expression
82+
|regex_extract()|Returns a list of substrings in a string based on matching patterns defined by a regular \
83+
expression
7584

76-
@subsection qore_regex_backreferences Qore Regular Expression Backreferences
85+
@section qore_regex_escape_patterns Qore Regular Expression Escape Codes
7786

78-
Qore uses <tt>$</tt><i>num</i> for backreferences in regular expression substitution expressions. The first backreference is \c $1, the second $2, and so on.
87+
Escape characters in the pattern string are processed by the <a href="http://www.pcre.org">PCRE</a> library
88+
similar to how Perl5 handles escape characters.
89+
90+
@subsection qore_regex_escape_replacement_string Qore Regular Expression Replacement String Escape Codes
91+
92+
Regular expression substitution expressions have the following pattern:
93+
- <tt>s/</tt><i>&lt;pattern&gt;</i><tt>/</tt><i>&lt;replacement&gt;</i><tt>/</tt>
94+
95+
The escape codes in the following table are supported in the replacement string.
96+
97+
<b>Regular Expression Replacement String Escape Codes</b>
98+
|!Escape|!ASCII|!Decimal|!Octal|!Hex|!Description
99+
|\\a|\c BEL|\c 7|\c 007|\c 07|alarm or bell
100+
|\\b|\c BS|\c 8|\c 010|\c 08|backspace
101+
|\\e|\c ESC|\c 27|\c 033|\c 1B|escape character
102+
|\\f|\c FF|\c 12|\c 014|\c 0C|form feed
103+
|\\n|\c LF|\c 10|\c 012|\c 0A|line feed
104+
|\\r|\c CR|\c 13|\c 015|\c 0D|carriage return
105+
|\\t|\c HT|\c 9|\c 011|\c 09|horizontal tab
106+
|\\v|\c VT|\c 11|\c 013|\c 0B|vertical tab
107+
|\\$|\c $|\c 36|\c 044|\c 24|a literal dollar sign character
108+
|\\\\|\c \\|\c 134|\c 092|\c 5C|a literal backslash character
109+
|\\[0-7][0-7][0-7]|-|-|-|-|the ASCII character represented by the octal code
110+
111+
Otherwise any backslashes in the replacement string will be copied literally to the output string.
112+
113+
@section qore_regex_backreferences Qore Regular Expression Backreferences
114+
115+
Qore uses <tt>$</tt><i>num</i> for backreferences in regular expression substitution expressions. The first
116+
backreference is \c $1, the second $2, and so on.
79117

80118
@par Example
81119
@code{.py}
@@ -86,5 +124,4 @@
86124
# remove parentheses from string at the beginning of the line
87125
str =~ s/^\((.*)\)/$1/;
88126
@endcode
89-
90127
*/

0 commit comments

Comments
 (0)