Line data Source code
1 : // Copyright (c) 2012-2021 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : #pragma once
20 :
21 : /** \file
22 : * \brief Event dispatch class.
23 : *
24 : * Class used to handle events.
25 : */
26 :
27 :
28 : // self
29 : //
30 : #include "eventdispatcher/dispatcher_base.h"
31 : #include "eventdispatcher/communicator.h"
32 : #include "eventdispatcher/utils.h"
33 :
34 :
35 : // snaplogger lib
36 : //
37 : #include <snaplogger/message.h>
38 :
39 :
40 :
41 : namespace ed
42 : {
43 :
44 :
45 :
46 : /** \brief A template to create a list of messages to dispatch on receival.
47 : *
48 : * Whenever you receive messages, they can automatically get dispatched to
49 : * various functions using the dispatcher.
50 : *
51 : * You define a dispatcher_match array and then add a dispatcher to
52 : * your connection object.
53 : *
54 : * \code
55 : * ed::dispatcher<my_connection>::dispatcher_match const my_messages[] =
56 : * {
57 : * {
58 : * "HELP"
59 : * , &my_connection::msg_help
60 : * //, &dispatcher<my_connection>::dispatcher_match::one_to_one_match -- use default
61 : * },
62 : * {
63 : * "STATUS"
64 : * , &my_connection::msg_status
65 : * //, &dispatcher<my_connection>::dispatcher_match::one_to_one_match -- use default
66 : * },
67 : * ... // other messages
68 : *
69 : * // if you'd like you can end your list with a catch all which
70 : * // generate the UNKNOWN message with the following (not required)
71 : * // if you have that entry, your own process_message() function
72 : * // will not get called
73 : * {
74 : * nullptr
75 : * , &dispatcher<my_connection>::dispatcher_match::msg_reply_with_unknown
76 : * , &dispatcher<my_connection>::dispatcher_match::always_match
77 : * },
78 : * };
79 : * \endcode
80 : *
81 : * In most cases you do not need to specify the matching function. It will
82 : * use the default which is a one to one match. So in the example above,
83 : * for "HELP", only a message with the command set to "HELP" will match.
84 : * When a match is found, the corresponding function (msg_help() here)
85 : * gets called.
86 : *
87 : * Note that "functions" are actually offsets. You will get `this` defined
88 : * as expected when your function gets called. The one drawback is that
89 : * only a function of the connection you attach the dispatcher to can
90 : * be called from the dispatcher. This is because we want to have a
91 : * static table instead of a dynamic one created each time we start
92 : * a process (in a website server, many processes get started again and
93 : * again.)
94 : *
95 : * \note
96 : * The T parameter of this template is often referenced as a "connection"
97 : * because it is expected to be a connection. There is actually no such
98 : * constraint on that object. It just needs to understand the dispatcher
99 : * usage which is to call the dispatch() function whenever a message is
100 : * received. Also it needs to implement any f_execute() function as
101 : * defined in its dispatch_match vector.
102 : *
103 : * \note
104 : * This is documented here because it is a template and we cannot do
105 : * that in the .cpp (at least older versions of doxygen could not.)
106 : *
107 : * \todo
108 : * Transform the dispatcher_match with classes so we can build the
109 : * array safely.
110 : */
111 : template<typename T>
112 8 : class dispatcher
113 : : public dispatcher_base
114 : {
115 : public:
116 : /** \brief A smart pointer of the dispatcher.
117 : *
118 : * Although we expect the array of `dispatcher_match` to be
119 : * statically defined, the `dispatcher`, on the other hand,
120 : * is quite dynamic and needs to be allocated in a smart
121 : * pointer then added to your connection.
122 : */
123 : typedef std::shared_ptr<dispatcher> pointer_t;
124 :
125 : /** \brief This structure is used to define the list of supported messages.
126 : *
127 : * Whenever you create an array of messages, you use this structure.
128 : *
129 : * The structure takes a few parameters as follow:
130 : *
131 : * \li f_expr -- the "expression" to be matched to the command name
132 : * for example "HELP".
133 : * \li f_execute -- the function offset to execute on a match.
134 : * \li f_match -- the function to check whether the expression is a match;
135 : * it has a default of one_to_one_match() which means the
136 : * f_expr string is viewed as a plain string defining the
137 : * message name as is.
138 : *
139 : * The command name is called "f_expr" but some matching functions may
140 : * make use of the "f_expr" parameter as an expression such as a
141 : * regular expression. Such functions will be added here with time, you
142 : * may also have your own, of course. The match function is expected to
143 : * be a static or standalone function.
144 : */
145 : struct dispatcher_match
146 : {
147 : /** \brief Define a vector of dispatcher_match objects.
148 : *
149 : * This function includes an array of dispatcher_match objects.
150 : * Whenever you define dispatcher_match objects, you want to
151 : * use the C++11 syntax to create a vector.
152 : *
153 : * \attention
154 : * We are NOT using a match because the matching may make use
155 : * of complex functions that support things such as complex as
156 : * regular expressions. In other words, the name of the message
157 : * may not just be a simple string.
158 : */
159 : typedef std::vector<dispatcher_match> vector_t;
160 :
161 : /** \brief The execution function.
162 : *
163 : * This type defines the execution function. We give it the message
164 : * on a match. If the command name is not a match, it is ignored.
165 : */
166 : typedef void (T::*execute_func_t)(message & msg);
167 :
168 : /** \brief The match function return types.
169 : *
170 : * Whenever a match function is called, it may return one of:
171 : *
172 : * \li MATCH_FALSE
173 : *
174 : * The function did not match anything. Ignore the corresponding
175 : * function.
176 : *
177 : * \li MATCH_TRUE
178 : *
179 : * This is a match, execute the function. We are done with this list
180 : * of matches.
181 : *
182 : * \li MATCH_CALLBACK
183 : *
184 : * The function is a callback, it gets called and the process
185 : * continues. Since the message parameter is read/write, it is
186 : * a way to tweak the message before other functions get it.
187 : */
188 : enum class match_t
189 : {
190 : MATCH_FALSE,
191 : MATCH_TRUE,
192 : MATCH_CALLBACK
193 : };
194 :
195 : /** \brief The match function.
196 : *
197 : * This type defines the match function. We give it the message
198 : * which has the command name, although specialized matching
199 : * function could test other parameters from the message such
200 : * as the origination of the message.
201 : */
202 : typedef match_t (*match_func_t)(std::string const & expr, message & msg);
203 :
204 : /** \brief The default matching function.
205 : *
206 : * This function checks the command one to one to the expression.
207 : * The word in the expression is compared as is to the command
208 : * name:
209 : *
210 : * \code
211 : * return expr == msg.get_command();
212 : * \endcode
213 : *
214 : * We will add other matching functions with time
215 : * (start_with_match(), regex_match(), etc.)
216 : *
217 : * \note
218 : * It is permissible to use a match function to modify the
219 : * message in some way, however, it is not recommended.
220 : *
221 : * \param[in] expr The expression to compare the command against.
222 : * \param[in] msg The message to match against this expression.
223 : *
224 : * \return MATCH_TRUE if it is a match, MATCH_FALSE otherwise.
225 : */
226 8 : static match_t one_to_one_match(std::string const & expr, message & msg)
227 : {
228 8 : return expr == msg.get_command()
229 8 : ? match_t::MATCH_TRUE
230 8 : : match_t::MATCH_FALSE;
231 : }
232 :
233 : /** \brief Always returns true.
234 : *
235 : * This function always returns true. This is practical to close
236 : * your list of messages and return a specific message. In most
237 : * cases this is used to reply with the UNKNOWN message.
238 : *
239 : * \param[in] expr The expression to compare the command against.
240 : * \param[in] msg The message to match against this expression.
241 : *
242 : * \return Always returns MATCH_TRUE.
243 : */
244 0 : static match_t always_match(std::string const & expr, message & msg)
245 : {
246 0 : snap::NOT_USED(expr, msg);
247 0 : return match_t::MATCH_TRUE;
248 : }
249 :
250 : /** \brief Always returns true.
251 : *
252 : * This function always returns true. This is practical to close
253 : * your list of messages and return a specific message. In most
254 : * cases this is used to reply with the UNKNOWN message.
255 : *
256 : * \param[in] expr The expression to compare the command against.
257 : * \param[in] msg The message to match against this expression.
258 : *
259 : * \return Always returns MATCH_CALLBACK.
260 : */
261 0 : static match_t callback_match(std::string const & expr, message & msg)
262 : {
263 0 : snap::NOT_USED(expr, msg);
264 0 : return match_t::MATCH_CALLBACK;
265 : }
266 :
267 : /** \brief The expression to compare against.
268 : *
269 : * The expression is most often going to be the exact command name
270 : * which will be matched with the one_to_one_match() function.
271 : *
272 : * For other match functions, this would be whatever type of
273 : * expression supported by those other functions.
274 : *
275 : * \note
276 : * Effective C++ doesn't like bare pointers, but there is no real
277 : * reason for us to waste time and memory by having an std::string
278 : * here. It's going to always be a constant pointer anyway.
279 : */
280 : char const * f_expr = nullptr;
281 :
282 : /** \brief The execute function.
283 : *
284 : * This is an offset in your connection class. We do not allow
285 : * std::bind() because we do not want the array of messages to be
286 : * dynamic (that way it is created at compile time and loaded as
287 : * ready/prepared data on load.)
288 : *
289 : * The functions called have `this` defined so you can access
290 : * your connection data and other functions. It requires the
291 : * `&` and the class name to define the pointer, like this:
292 : *
293 : * \code
294 : * &MyClass::my_message_function
295 : * \endcode
296 : *
297 : * The execution is started by calling the run() function.
298 : */
299 : execute_func_t f_execute = nullptr;
300 :
301 : /** \brief The match function.
302 : *
303 : * The match function is used to know whether that command
304 : * dispatch function was found.
305 : *
306 : * By default this parameter is set to one_to_one_match().
307 : * This means the command has to be one to one equal to
308 : * the f_expr string.
309 : *
310 : * The matching is done in the match() function.
311 : */
312 : match_func_t f_match = &::ed::dispatcher<T>::dispatcher_match::one_to_one_match;
313 :
314 : /** \brief Run the execution function if this is a match.
315 : *
316 : * First this function checks whether the command of the message
317 : * in \p msg matches this `dispatcher_match` expression. In
318 : * most cases the match function is going to be
319 : * one_on_one_match() which means it has to be exactly equal.
320 : *
321 : * If it is a match, this function runs your \p connection execution
322 : * function (i.e. the message gets dispatched) and then it returns
323 : * true.
324 : *
325 : * If the message is not a match, then the function returns false
326 : * and only the matching function was called. In this case the
327 : * \p connection does not get used.
328 : *
329 : * When this function returns true, you should not call the
330 : * process_message() function since that was already taken care
331 : * of. The process_message() function should only be called
332 : * if the message was not yet dispatched. When the list of
333 : * matches includes a catch all at the end, the process_message()
334 : * will never be called.
335 : *
336 : * \note
337 : * Note that we do not offer two functions, one to run the match
338 : * function and one to execute the match because you could otherwise
339 : * end up calling the execute function (dispatch) on any
340 : * `dispatcher_match` entry and we did not want that to happen.
341 : *
342 : * \param[in] connection The connection attached to that
343 : * `dispatcher_match`.
344 : * \param[in] msg The message that matched.
345 : *
346 : * \return true if the connection execute function was called.
347 : */
348 8 : bool execute(T * connection, ::ed::message & msg) const
349 : {
350 8 : match_t m(f_match(f_expr == nullptr ? std::string() : f_expr, msg));
351 8 : if(m == match_t::MATCH_TRUE
352 2 : || m == match_t::MATCH_CALLBACK)
353 : {
354 6 : (connection->*f_execute)(msg);
355 6 : if(m == match_t::MATCH_TRUE)
356 : {
357 6 : return true;
358 : }
359 : }
360 :
361 2 : return false;
362 : }
363 :
364 : /** \brief Check whether f_match is one_to_one_match().
365 : *
366 : * This function checks whether the f_match function was defined
367 : * to one_to_one_match()--the default--and if so returns true.
368 : *
369 : * \return true if f_match is the one_to_one_match() function.
370 : */
371 0 : bool match_is_one_to_one_match() const
372 : {
373 0 : return f_match == &ed::dispatcher<T>::dispatcher_match::one_to_one_match;
374 : }
375 :
376 : /** \brief Check whether f_match is always_match().
377 : *
378 : * This function checks whether the f_match function was defined
379 : * to always_match() and if so returns true.
380 : *
381 : * \return true if f_match is the always_match() function.
382 : */
383 0 : bool match_is_always_match() const
384 : {
385 0 : return f_match == &ed::dispatcher<T>::dispatcher_match::always_match;
386 : }
387 :
388 : /** \brief Check whether f_match is callback_match().
389 : *
390 : * This function checks whether the f_match function was defined
391 : * to callback_match() and if so returns true.
392 : *
393 : * \return true if f_match is the callback_match() function.
394 : */
395 0 : bool match_is_callback_match() const
396 : {
397 0 : return f_match == &ed::dispatcher<T>::dispatcher_match::callback_match;
398 : }
399 : };
400 :
401 : private:
402 : /** \brief The connection pointer.
403 : *
404 : * This parameter is set by the constructor. It represents the
405 : * connection this dispatcher was added to (a form of parent of
406 : * this dispatcher object.)
407 : */
408 : T * f_connection = nullptr;
409 :
410 : /** \brief The array of possible matches.
411 : *
412 : * This is the vector of your messages with the corresponding
413 : * match and execute functions. This is used to go through
414 : * the matches and execute (dispatch) as required.
415 : */
416 : typename ed::dispatcher<T>::dispatcher_match::vector_t f_matches = {};
417 :
418 : /** \brief Tell whether messages should be traced or not.
419 : *
420 : * Because your service may accept and send many messages a full
421 : * trace on all of them can really be resource intensive. By default
422 : * the system will not trace anything. By setting this parameter to
423 : * true (call set_trace() for that) you request the SNAP_LOG_TRACE()
424 : * to run on each message received by this dispatcher. This is done
425 : * on entry so whether the message is processed by the dispatcher
426 : * or your own send_message() function, it will trace that message.
427 : */
428 : bool f_trace = false;
429 :
430 : public:
431 :
432 : /** \brief Initialize the dispatcher with your connection and messages.
433 : *
434 : * This function takes a pointer to your connection and an array
435 : * of matches.
436 : *
437 : * Whenever a message is received by one of your connections, the
438 : * dispatch() function gets called which checks the message against
439 : * each entry in this array of \p matches.
440 : *
441 : * \param[in] connection The connection for which this dispatcher is
442 : * created.
443 : * \param[in] matches The array of dispatch keywords and functions.
444 : * \param[in] matches_size The sizeof() of the matches array.
445 : */
446 4 : dispatcher<T>(T * connection, typename ed::dispatcher<T>::dispatcher_match::vector_t matches)
447 : : f_connection(connection)
448 4 : , f_matches(matches)
449 : {
450 4 : }
451 :
452 : // prevent copies
453 : dispatcher<T>(dispatcher<T> const & rhs) = delete;
454 : dispatcher<T> & operator = (dispatcher<T> const & rhs) = delete;
455 :
456 : /** \brief Add a default array of possible matches.
457 : *
458 : * In Snap! a certain number of messages are always exactly the same
459 : * and these can be implemented internally so each daemon doesn't have
460 : * to duplicate that work over and over again. These are there in part
461 : * because the snapcommunicator expects those messages there.
462 : *
463 : * IMPORTANT NOTE: If you add your own version in your dispatcher_match
464 : * vector, then these will be ignored since your version will match first
465 : * and the dispatcher uses the first function only.
466 : *
467 : * This array currently includes:
468 : *
469 : * \li HELP -- msg_help() -- returns the list of all the messages
470 : * \li LOG -- msg_log() -- reconfigure() the logger
471 : * \li QUITTING -- msg_quitting() -- calls stop(true);
472 : * \li READY -- msg_ready() -- calls ready() -- snapcommunicator always
473 : * sends that message so it has to be supported
474 : * \li RESTART -- msg_restart() -- calls restart() -- it is triggered
475 : * when a restart is required (i.e. the library was
476 : * upgraded, a configuration file was updated, etc.)
477 : * \li STOP -- msg_stop() -- calls stop(false);
478 : * \li UNKNOWN -- msg_log_unknown() -- in case we receive a message we
479 : * don't understand
480 : * \li * -- msg_reply_with_unknown() -- the last entry will be a grab
481 : * all pattern which returns the UNKNOWN message automatically
482 : * for you
483 : *
484 : * The msg_...() functions must be declared in your class T. If you
485 : * use the system connection_with_send_message class then they're
486 : * already defined there.
487 : *
488 : * The HELP response is automatically built from the f_matches.f_expr
489 : * strings. However, if the function used to match the expression is
490 : * not one_to_one_match(), then that string doesn't get used.
491 : *
492 : * If any message can't be determine (i.e. the function is not the
493 : * one_to_one_match()) then the user help() function gets called and
494 : * we expect that function to add any dynamic message the daemon
495 : * understands.
496 : *
497 : * The LOG message reconfigures the logger if the is_configure() function
498 : * says it is configured. In any other circumstances, nothing happens.
499 : *
500 : * Note that the UNKNOWN message is understood and just logs the message
501 : * received. This allows us to see that WE sent a message that the receiver
502 : * (not us) does not understand and adjust our code accordingly (i.e. add
503 : * support for that message in that receiver or maybe fix the spelling.)
504 : */
505 0 : void add_communicator_commands()
506 : {
507 : // avoid more than one realloc()
508 : //
509 0 : f_matches.reserve(f_matches.size() + 10);
510 :
511 : {
512 0 : typename ed::dispatcher<T>::dispatcher_match m;
513 0 : m.f_expr = "ALIVE";
514 0 : m.f_execute = &T::msg_alive;
515 : //m.f_match = <default>;
516 0 : f_matches.push_back(m);
517 : }
518 : {
519 0 : typename ed::dispatcher<T>::dispatcher_match m;
520 0 : m.f_expr = "HELP";
521 0 : m.f_execute = &T::msg_help;
522 : //m.f_match = <default>;
523 0 : f_matches.push_back(m);
524 : }
525 : {
526 0 : typename ed::dispatcher<T>::dispatcher_match m;
527 0 : m.f_expr = "LEAK";
528 0 : m.f_execute = &T::msg_leak;
529 : //m.f_match = <default>;
530 0 : f_matches.push_back(m);
531 : }
532 : {
533 0 : typename ed::dispatcher<T>::dispatcher_match m;
534 0 : m.f_expr = "LOG";
535 0 : m.f_execute = &T::msg_log;
536 : //m.f_match = <default>;
537 0 : f_matches.push_back(m);
538 : }
539 : {
540 0 : typename ed::dispatcher<T>::dispatcher_match m;
541 0 : m.f_expr = "QUITTING";
542 0 : m.f_execute = &T::msg_quitting;
543 : //m.f_match = <default>;
544 0 : f_matches.push_back(m);
545 : }
546 : {
547 0 : typename ed::dispatcher<T>::dispatcher_match m;
548 0 : m.f_expr = "READY";
549 0 : m.f_execute = &T::msg_ready;
550 : //m.f_match = <default>;
551 0 : f_matches.push_back(m);
552 : }
553 : {
554 0 : typename ed::dispatcher<T>::dispatcher_match m;
555 0 : m.f_expr = "RESTART";
556 0 : m.f_execute = &T::msg_restart;
557 : //m.f_match = <default>;
558 0 : f_matches.push_back(m);
559 : }
560 : {
561 0 : typename ed::dispatcher<T>::dispatcher_match m;
562 0 : m.f_expr = "STOP";
563 0 : m.f_execute = &T::msg_stop;
564 : //m.f_match = <default>;
565 0 : f_matches.push_back(m);
566 : }
567 : {
568 0 : typename ed::dispatcher<T>::dispatcher_match m;
569 0 : m.f_expr = "UNKNOWN";
570 0 : m.f_execute = &T::msg_log_unknown;
571 : //m.f_match = <default>;
572 0 : f_matches.push_back(m);
573 : }
574 : {
575 0 : typename ed::dispatcher<T>::dispatcher_match m;
576 0 : m.f_expr = nullptr; // any other message
577 0 : m.f_execute = &T::msg_reply_with_unknown;
578 0 : m.f_match = &ed::dispatcher<T>::dispatcher_match::always_match;
579 0 : f_matches.push_back(m);
580 : }
581 0 : }
582 :
583 : typename ed::dispatcher<T>::dispatcher_match::vector_t const & get_matches() const
584 : {
585 : return f_matches;
586 : }
587 :
588 : /** \brief The dispatch function.
589 : *
590 : * This is the function your message system will call whenever
591 : * the system receives a message.
592 : *
593 : * The function returns true if the message was dispatched.
594 : * When that happen, the process_message() function of the
595 : * connection should not be called.
596 : *
597 : * You may not include a message in the array of `dispatch_match`
598 : * if it is too complicated to match or too many variables are
599 : * necessary then you will probably want to use your
600 : * process_message().
601 : *
602 : * By adding a catch-all at the end of your list of matches, you
603 : * can easily have one function called for any message. By default
604 : * the dispatcher environment offers such a match function and
605 : * it also includes a function that sends the UNKNOWN message as
606 : * an immediate reply to a received message.
607 : *
608 : * \param[in] msg The message to be dispatched.
609 : *
610 : * \return true if the message was dispatched, false otherwise.
611 : */
612 6 : virtual bool dispatch(::ed::message & msg) override
613 : {
614 6 : if(f_trace)
615 : {
616 12 : SNAP_LOG_TRACE
617 12 : << "dispatch message \""
618 : << msg.to_message()
619 : << "\"."
620 : << SNAP_LOG_SEND;
621 : }
622 :
623 : // go in order to execute matches
624 : //
625 : // remember that a dispatcher with just a set of well defined command
626 : // names is a special case (albeit frequent) and we can't process
627 : // using a map (a.k.a. fast binary search) as a consequence
628 : //
629 8 : for(auto const & m : f_matches)
630 : {
631 8 : if(m.execute(f_connection, msg))
632 : {
633 6 : return true;
634 : }
635 : }
636 :
637 0 : return false;
638 : }
639 :
640 : /** \brief Set whether the dispatcher should trace your messages or not.
641 : *
642 : * By default, the f_trace flag is set to false. You can change it to
643 : * true while debugging. You should remember to turn it back off once
644 : * you make an official version of your service to avoid the possibly
645 : * huge overhead of sending all those log messages. One way to do so
646 : * is to place the code within #ifdef/#endif as in:
647 : *
648 : * \code
649 : * #ifdef _DEBUG
650 : * my_dispatcher->set_trace();
651 : * #endif
652 : * \endcode
653 : *
654 : * \param[in] trace Set to true to get SNAP_LOG_TRACE() of each message.
655 : */
656 4 : void set_trace(bool trace = true)
657 : {
658 4 : f_trace = trace;
659 4 : }
660 :
661 : /** \brief Retrieve the list of commands.
662 : *
663 : * This function transforms the vector of f_matches in a list of
664 : * commands in a string_list_t.
665 : *
666 : * \param[in,out] commands The place where the list of commands is saved.
667 : *
668 : * \return false if the commands were all determined, true if some need
669 : * help from the user of this dispatcher.
670 : */
671 0 : virtual bool get_commands(string_list_t & commands) override
672 : {
673 0 : bool need_user_help(false);
674 0 : for(auto const & m : f_matches)
675 : {
676 0 : if(m.f_expr == nullptr)
677 : {
678 0 : if(!m.match_is_always_match()
679 0 : && !m.match_is_callback_match())
680 : {
681 : // this is a "special case" where the user has
682 : // a magical function which does not require an
683 : // expression at all (i.e. "hard coded" in a
684 : // function)
685 : //
686 0 : need_user_help = true;
687 : }
688 : //else -- always match is the last entry and that just
689 : // means we can return UNKNOWN on an unknown message
690 : }
691 0 : else if(m.match_is_one_to_one_match())
692 : {
693 : // add the f_expr as is since it represents a command
694 : // as is
695 : //
696 0 : commands.push_back(m.f_expr);
697 : }
698 : else
699 : {
700 : // this is not a one to one match, so possibly a
701 : // full regex or similar
702 : //
703 0 : need_user_help = true;
704 : }
705 : }
706 0 : return need_user_help;
707 : }
708 : };
709 :
710 :
711 :
712 : } // namespace ed
713 : // vim: ts=4 sw=4 et
|