/*
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
*/
#ifndef V12_CLI_H_
#define V12_CLI_H_

/*
 * Динамичный, глобальный Common Line Interface.
 * Архитектура библиотеки предоставляет элементы:
 *
 * |CLI_shell:A| --                  |---> <CLI_command : `A` >
 * |    реестр |   \                 /---> <CLICommandContainer: [`B`] >
 *                   <cli::реестр>--> ---> <CLIDeviceCommands : 'S' >
 * |CLI_shell:B| --/
 * |    реестр |-------------------------> <CLICommandInvoke : 'c' >
 *
 *
 * CLI_shell - класс консоли - разбора строки команд, и печати вывода.
 *          Команды в строке ищутся в локальном, и глобальном реестрах команд.
 *          Первая найденая команда, продолжает разбор строки ввода. и исполненяет ее.
 *
 *
 * реестр команд - реализован списком команд CLI_commands. Это динамичный список,
 *      позволяет во время исполнения включить, удалить команду.
 *      Глобальный список пополняется  cli::[un]registerCmd( x )
 *      Локальный список консоли пополняется аналогично shell->[un]registerCmd( x )
 *
 * CLICommand - реализует команду обозначаемую одним словом. Эти команды регистрируются
 *      в глобальном реестре команд, или в конкретной консоли. по первому слову строки ввода
 *      ищется команда, в локальном реестре консоли, и затем глобальном.
 *      найденая команда, продолжает разбор сроки методом cli_process.
 *      Команда долна уметь напечатать о себе подсказку методом cli_help.
 * ---------------------------------------------------------------------------
 *
 * CLICommand предоставляет варианты:
 *
 * CLICommandInvoke - позволяет исполнить метод вида cli_process на стороннем объекте.
 *          Позволяет включать команды в класс-хозяин композицией.
 *
 * CLICommandContainer - динамичный список под-команд CLICommand,
 *              базирующийся на реестре команд.
 *          Если у этой команды имя пустое - то она позволяет удобно подключить
 *              группу команд в реестр.
 *
 * CLIDeviceCommands - команда со статическим списоком команд вида cli::CommandSet<T>
 *          эта абстракция дает готовый парсинг под-команд статического списка
 *              cli_process(.. cli::CommandSet<T> cmds )
 *          так же он предоставляет процессинг последовательностей:
 *               - process_sequence - для слов суб-команд
 *               - process_args - для набора аргументов вида name=value
 *
 *
 * */



#include <hal_types.h>
#include <string.h>
#include <stddef.h>
//#include <c_compat.h>

#include <project-conf.h>

// если задан этот параметр, то CLICommands будет делаться статическим массивом, вместо вектора
//#define CLI_VECTOR_LIMIT 16

// если задан этот параметр, то CLICommand будет разрегистрироваться при удалении
// если система команд статична, десткруктор не будет пользоваться разрегистрацией
//#define CLI_DYNAMICS_CMD

//если задан CLI_CHAR то cli::Char будет опираться на него



#ifndef ASSERT_CLI
#define ASSERT_CLI 0
#endif

#if ASSERT_CLI
#include <cassert>
#define CLI_ASSERT(...) assert(__VA_ARGS__)
#else
#define CLI_ASSERT(...)
#endif



namespace cli{
    // задам свой тип char. чтобы пометь возможность быстро переопределять его
    // например использовать WChar

#ifdef CLI_CHAR
typedef CLI_CHAR Char;
#else
typedef char Char;
#endif

typedef const Char* const_line;
typedef const_line* const_lines;

}; // namespace cli

class CLI_shell;

// этот команда из одного слова <cmd_name>. 
// она регистриуется в общем реестре, или заданной консоли через registerCmd
// и от консоли получает управление в метод cli_process(..) для обработки своих парметров
class CLICommand{
public:
    typedef const cli::Char* const_line;
    typedef const_line* const_lines;

    typedef const_string    const_cmd;
    typedef cli::Char*      cmd_line;

    const_cmd cmd_name;

    CLICommand():cmd_name(nullptr){}
    CLICommand(const_cmd _cmd):cmd_name(_cmd){}
    virtual ~CLICommand();

public:
    //это обработчики для shell
    //* \return > 0- длинна принятой строки
    //*         ==0 - команда не распознана
    //*         <0  - ошибка
    virtual int cli_process(CLI_shell* shell, const_line line);

    //* печатает хелп команды в шелл
    virtual void cli_help(CLI_shell* shell);

    //* печать краткого хелпа команд. выдает только команду
    virtual void cli_brief(CLI_shell* shell);

    // check line starts with cmd_name, and returns line after it, skips spaces
    // \return NULL - line not starts with cmd
    virtual const_line after_cmd(const_line line) const;
    
    bool is_cmd_starts(const_line line) const {return after_cmd(line) != nullptr; }
    bool is_cmd_empty() const { 
        if(cmd_name != nullptr)
            return (cmd_name[0] == '\0');
        else 
            return  true;
    }

public:
    bool registerCmd(CLI_shell* shell = nullptr);
    bool unregisterCmd(CLI_shell* shell = nullptr);
};
typedef class CLICommand CLICommandBase;


#if CLI_VECTOR_LIMIT > 0
// TODO: сделать статическцю версию CLIvector
#else
#include <vector>
#include "system_heap.h"
typedef std::vector< CLICommand*, SystemHeap< CLICommand* > > CLIvector;
#endif

// это список команд. он умеет найти команду по названию cli_process
// или обработать строку от консоли, выбрав команду по первому слову
// список динамический, может меняться в рунтайме
class CLI_commands
    : public CLIvector
{
  public:
    typedef CLIvector inherited;
    typedef CLICommand::cmd_line cmd_line;
    typedef CLICommand::const_line const_line;
    typedef CLICommand             cmd_t;

    CLI_commands();
    CLI_commands(unsigned reserve);
    bool append(cmd_t* cmd);
    bool append(cmd_t& cmd);
    bool remove(cmd_t& cmd);
    cmd_t* lookup(const_line cmd) const;

    void list_brief(CLI_shell* shell);
    void help(CLI_shell* shell);
    // @result - 0 nothig has parsed, causes shell reports error message from cursor
    // @result - -1 , the same reports error message from cursor, but it is also causes
    //               imediate parsing break of current line.
    int cli_process(CLI_shell* shell, const_line line);

    bool is_contains(const cmd_t& cmd) const;
};


// это команда со списком суб-команд
class CLICommandContainer : public CLICommand
{
public:
    typedef CLICommand      inherited;
    typedef CLI_commands    comands_t;
    CLICommandContainer();
    CLICommandContainer(const_cmd _cmd, unsigned reserve = 4);

    comands_t& commands() {return _cmds;}
    const comands_t& commands() const {return _cmds;}

    int cli_process(CLI_shell* shell, const_line line) override;
    void cli_help(CLI_shell* shell) override;
    void cli_brief(CLI_shell* shell) override;
    const_line after_cmd(const_line line) const override;

protected:
    comands_t _cmds;

    // this events from cli_process when line cant mutch sub cmd in _cmds
    virtual int on_no_subcmd(CLI_shell* shell, const_line line);
};



namespace cli{

// method of processing shell line by CLIxxxx class
template <typename T>
using cli_processing = int (T::*)(CLI_shell* shell, CLICommandBase::const_line line);


// TODO: C++11 frovides std::function<>/std::bind<> objects for method invoke objects
//       code can be better with it
class ProcessingFunctor {
public:
    typedef int (*processing_func)(void* self, CLI_shell* shell, CLICommandBase::const_line line);

    ProcessingFunctor(void* obj, processing_func f)
        : _obj(obj), _f(f){}

    template<typename T>
    ProcessingFunctor(void* obj,  cli::cli_processing<T> f)
        : _obj(obj)
    {
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wpmf-conversions\"")
        _f = (processing_func)f;
_Pragma("GCC diagnostic pop")
    }

    int operator()(CLI_shell* shell, CLICommandBase::const_line line){
        return _f(_obj, shell, line);
    }

protected:
    void*           _obj;
    processing_func _f;
};


}


class CLICommandInvoke : public CLICommand {
public:
    typedef CLICommand inherited;

    CLICommandInvoke(const_cmd _cmd, cli::ProcessingFunctor f)
        : inherited(_cmd), help_banner(nullptr), _f(f) {}

    template<typename T>
    CLICommandInvoke(const_cmd _cmd, void* obj, cli::cli_processing<T> f)
        : inherited(_cmd), help_banner(nullptr), _f(obj, f){}

    int cli_process(CLI_shell* shell, const_line line) override;

    // this banner will be printed on cli_help
    const_line      help_banner;
    void cli_help(CLI_shell* shell) override;

protected:
    cli::ProcessingFunctor  _f;
};



namespace cli {

enum {
 BUFFERLEN  = 64,
 MAXTOKEN   = 5+1,
 ENDL       = '\r',
 MAX_CMDS   = 3,
 MAX_FUNS   = 3,
};

//* регистрирует глобальную команду в CLI_shell::cmds
bool registerCmd(CLICommandBase *cmd);
bool unregisterCmd(CLICommandBase *cmd);

int space_len(const char* s);
int space_len(const char* s, unsigned len);
int word_len(const char* s, unsigned len);
//* same, but use <delim> like space - for end of name stop
int word_len(const char* s, unsigned len, char delim);
// TODO need distinguish between name and token: 
//      * token - a word delimited by spases
//      * name - a word delimited spaces|punct
//* word_len + check that after word is space or EOL
//* \return -1 if ccheck fails
int name_len(const char* s, unsigned len);
//* look to EOL or Z
int line_len(const char* s, unsigned len);
//* look for '='
int equal_len(const char* s, unsigned len);
//* \return < 0 - no eol
//* \returm = 0 - Z ended
//* \return >0  - len of EOL token
int is_eol(const char* s);

template<typename T>
inline
bool is_no_eol(const T* s){
    return (is_eol(s) < 0);
}

//* suggests that <name> shorter vs <line>
//* \return = 0 - <name> not starts <line>
//* \return > 0 - len of <name> when one completely starts <line>
//* \return < 0 - len of same prefix in <line> and <name>
int str_starts(const char* __restrict line, const char* __restrict name);

//* same as str_starts, , skip spaces before name, 
//     ensures that mutched prefix <name> in <line> and finishes by EOL, or space
//     or not alphanum char
int name_starts(const char* __restrict line, const char* __restrict name);
//* same as name_starts, but <name> is space-separated list of mutch variants
//*    finishes by EOL, or space, skip spaces before name
int names_starts(const char* __restrict line, const char* __restrict name);

//* check that <line> starts with <name> and skip it, skip spaces too.
//* \return - word after <name>
//* \return NULL - line not starts with <name>
const char* after_name(const char* __restrict line, const char* __restrict name);

//* @return true - if string is at end, or at word '--'.
//                 such arg responses as finish of command arguments sequence
bool is_last_arg(const_line s);

//* \return index of mutched in <names>
//*         , <lend> = position in <line> after mutched
//* \return  = -1 -  if fail mutches
int mutch_names(const char* __restrict line
                , const char** __restrict lend
                , const char* const * __restrict names);

// "off", "on" names set
extern const char* const names_onoff[];
int mutch_onoff(const char* __restrict line
                , const char** __restrict lend
                );
int mutch_bool(const char* __restrict line
                , const char** __restrict lend
                );


// \return > 0 - anount of accepted chars, x = parsed value
int take_number(long& x, const char* s);
// \return = NULL - cant parse value
//           s - s updates to new position after number
const char* pass_number(long& x, const char* s);
const char* pass_number(double& x, const char* s);

// \return = NULL - cant parse value
//           s - s updates to new position after number
const char* pass_number(char mark, long& x, const char* s);
const char* pass_number( char markx, long& x
                        ,char marky, long& y, const char* s);


// просто командна help. используется как глобальная команда
class CLI_Help : public virtual CLICommand{
  public:
    typedef CLICommand inherited;

    CLI_Help(CLI_commands* dst);

    virtual int cli_process(CLI_shell* shell, const_line line);
    //* печатает хелп команды в шелл
    virtual void cli_help(CLI_shell* shell);
};

class CLI_OnOffCommand : public virtual CLICommand
{
    public:
      typedef CLICommand inherited;

      CLI_OnOffCommand();
      CLI_OnOffCommand(const_cmd _cmd);

      virtual void cli_onoff(bool onoff) = 0;

      virtual int cli_process(CLI_shell* shell, const_line line);
      //* печатает хелп команды в шелл
      virtual void cli_help(CLI_shell* shell);

}; // CLI_onoff
typedef CLI_OnOffCommand CLI_onoff;



//-----------------------------------------------------------------------------
// статический сисок команд+действия+хелп. Альтернатива CLI_commands, 
//   только со статической иницализацией
//      Commands Set - static array of commands + handle parsers defs
//      looks like:
//      CommandSet< Tclicommand > Tcmds = {
//          {"cmd1", Tclicommand::cli_cmd1, "cmd1 is a ..." },
//          {"cmd2", Tclicommand::cli_cmd2, "cmd2 is a ..." },
//          {nullptr,}
//      };

template <typename T, typename Tdummy = void* >
struct CmdItem {
    typedef CLICommandBase::const_cmd   const_cmd;
    typedef CLICommandBase::const_line  const_line;

    // последний элемент набора команд имеет name == nullptr
    const_cmd           name;
    cli_processing<T>   proc;
    const_line          help;
    // если proc не определен, используется альтернативный обработчик команды, 
    //  который может использовать dummy
    Tdummy              dummy;

    bool is_empty() const {
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wpmf-conversions\"")
        return ( (uintptr_t)(void*)proc 
                 | (uintptr_t)(const void*)help 
                 | (uintptr_t)(void*)dummy
                 ) == 0;
_Pragma("GCC diagnostic pop")
    }
};

template <typename T>
using CommandsVarDef = struct CmdItem<T>[];
template <typename T>
using CommandsDef = const struct CmdItem<T>[];

template <typename T>
using CommandVarSet = struct CmdItem<T>*;
template <typename T>
using CommandSet = const struct CmdItem<T>*;

// this type use as aliased pointer to local CommandSet
typedef CommandVarSet<CLICommandBase>   BaseVarCommands;
typedef CommandSet<CLICommandBase>      BaseCommands;

template <typename T>
inline int mutch_names(const char* __restrict line
                , const char** __restrict lend
                , CommandSet<T> cmds)
{ return mutch_names<CLICommandBase>(line, lend, (BaseCommands)cmds);}

template <typename T>
inline int process(T* self, CLI_shell* shell
                , const char* __restrict line
                , CommandSet<T> cmds
                )
{ return process<CLICommandBase>((CLICommandBase*)self, shell
                                 , line
                                 , (BaseCommands)cmds
                                );
}


//* \return index mutched in <cmds> by <cmds.name> field.
//*         where <cmds.name> - space-separated list of mutch variants
//*         , <lend> = position in <line> after mutched
//* \return  = -1 -  if fail mutches, and last item empty
//*          = last idx - if last item have some value
template <>
int mutch_names<CLICommandBase>(const char* __restrict line
                , const char** __restrict lend
                , BaseCommands cmds);

//parse line for commands in cmds
template <>
int process<CLICommandBase>(CLICommandBase* self, CLI_shell* shell
                , const char* __restrict line
                , BaseCommands cmds
                );


// это для парсинга занчений имя=>ID
struct CLINameDef{
    typedef CLICommandBase::const_cmd   const_cmd;

    //argument name. 
    const_cmd  name; 
    // this ID passed to arg assigner with accepted value
    int        id;
};

typedef const CLINameDef*  CLINamesDef;

//* \return index mutched in <cmds> by <cmds.name> field.
//*         where <cmds.name> - space-separated list of mutch variants
//*         , <lend> = position in <line> after mutched
//* \return  = -1 -  if fail mutches, and last item empty
//*          = last idx - if last item have some value
int mutch_names(const char* __restrict line
                , const char** __restrict lend
                , CLINamesDef cmds);




}; //namespace cli



//----------------------------------------------------------------------------
// This is a command with sub-commands specified by cli::CommandSet<CLICommand_some_descendant> list
//
//    !!! CommandSet actions MUST points to a single CLICommand descendant`s 
//    valid action invokation can be broken if action`s class layouts differ

class CLIDeviceCommands : public CLICommand 
{
public:
    typedef CLICommand inherited;
    typedef cli::BaseCommands   cmds_t;

    CLIDeviceCommands()
        :inherited(), sub_cmds(nullptr) {}
    CLIDeviceCommands(const_cmd _cmd)
        :inherited(_cmd), sub_cmds(nullptr) {}
    template< typename T>
    CLIDeviceCommands(const_cmd _cmd, cli::CommandSet<T> cmds)
        :inherited(_cmd){ assign(cmds);}

    template< typename T >
    void assign(cli::CommandSet<T> cmds){
        sub_cmds = (cmds_t)cmds;

        // this offset helps to recover valid instance this for calling action method.
        // this offset can appears for complex class with multiple inheritance
        //  when T - is super-class over CLICommandBase.
        // this trick well works on GCC8

        //sub_this = (void*)((u8*) this - sub_type_offset);

_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"")
        sub_type_offset = offsetof(T, cmd_name) - offsetof(CLICommandBase, cmd_name);
_Pragma("GCC diagnostic pop")
    }


    virtual int cli_process(CLI_shell* shell, const_line line);

    // this process calls when CmdItem mutched from sub_cmds, have no proc action
    virtual bool cli_process_dummy(CLI_shell* shell, const cli::CmdItem<CLICommandBase>& cmd);

    //* печатает хелп команды в шелл
    virtual void cli_help(CLI_shell* shell);
    virtual void cli_brief(CLI_shell* shell);

    // check line starts with cmd_name, and returns line after it, skips spaces
    // \return NULL - line not starts with cmd
    virtual const_line after_cmd(const_line line) const;

public:
    int cli_help_cmd(CLI_shell* shell, const_line line);
    
public:
    int cli_process(CLI_shell* shell, const_line line, cmds_t   _cmds);
    template<typename T>
    int cli_process(CLI_shell* shell, const_line line, cli::CommandSet<T> _cmds){
        return cli_process(shell, line, (cmds_t) _cmds);
    }

    //* печатает список команд из перечня команд в шелл
    void print_brief(CLI_shell* shell, cmds_t x);
    //* печатает хелп перечня команд в шелл
    void print_full(CLI_shell* shell, cmds_t x);

    template <typename T>
    void print_brief(CLI_shell* shell, cli::CommandSet<T> x){
        print_brief(shell, (cmds_t)x);
    }
    template <typename T>
    void print_full(CLI_shell* shell, cli::CommandSet<T> x){
        print_full(shell, (cmds_t)x);
    }

public:
    // это обработчики последовательности аргументов-параметров
    // TODO это надо перенести в CLI_Command. только у нее нет invoke_sub

    // this action use for sequence on words processing
    typedef enum { srOK, srBreak } SeqResult;

    template <typename T>
    using SequenceAction = SeqResult (T::*)(const_line line, unsigned wordlen, CLI_shell* shell);
    
    // \return = NULL - cant parse any
    //           s - s updates to new position after all processed words
    const_line process_sequence(CLI_shell* shell, const_line s, SequenceAction<CLICommandBase> act);
    template <typename T>
    const_line process_sequence(CLI_shell* shell, const_line s, SequenceAction<T> act){
        return process_sequence(shell, s, reinterpret_cast< SequenceAction<CLICommandBase> > (act) );
    }


    template <typename T>
    using ArgsAction = SeqResult (T::*)(const_line name, unsigned namelen
                                , const_line val, unsigned vallen, CLI_shell* shell);

    // invoke act on every inte if seq {name=value}*
    // \return = NULL - cant parse any
    //           s - s updates to new position after all processed words
    const_line process_args(CLI_shell* shell, const_line s, const ArgsAction<CLICommandBase> act);

    template <typename T>
    const_line process_args(CLI_shell* shell, const_line s, const ArgsAction<T> act){
        return process_args(shell, s, reinterpret_cast< const ArgsAction<CLICommandBase> >(act) );
    }

protected:
    cmds_t   sub_cmds;
    int      sub_type_offset;

    template<typename F, typename... Args>
    auto invoke_sub(F f, Args ... args){
        u8* asbase = ((u8*)this) - sub_type_offset;
        return ((CLICommandBase*)asbase->*f) (args...);
    }

};




//------------------------------------------------------------------------------
// provide: CLIShellLine, StdPrint SHELL_EOL
#include "cli_line.hpp"
#include <project-cli.h>

#ifndef CLI_SHELLLINE_LIMIT
#define CLI_SHELLLINE_LIMIT 164u
#endif

//provide exception-guarded commands execution
#ifndef CLI_SHELL_EXCEPITONALE
#define CLI_SHELL_EXCEPITONALE 0
#endif


#ifndef SHELL_EOL
#define SHELL_EOL '\n'
#endif


// шелл - оболочка над командной строкой. которая ее разбирает и запускает команды.
//   также шелл  это принтер, в которые команды могут печатать
class CLI_shell
  : public virtual StdPrint
{
public:
    static CLI_commands cmds;
    static cli::CLI_Help help_cmd;

public:
    CLI_shell();
    void init();

    CLI_commands mycmds;
    bool registerCmd(CLICommandBase *cmd);
    bool unregisterCmd(CLICommandBase *cmd);
    CLICommandBase* lookupCmd(CLICommand::const_line cmd);

    void help();

public:
    typedef CLIShellLine  shell_line_t;
    enum {
        cmdlimit = CLI_SHELLLINE_LIMIT, // 164
        EOL = SHELL_EOL, 
    };
    shell_line_t cmdline;      // cmdline buffer

    void clear();
    //* исполняет набранную в cmdline строку
    void processCmd();
    //  отбрасывает из буффера все что перед курсором
    void dropProcessed();
    //* добавляет символ в строку, и выполняет команду на LN в контексте вызвавшего
    void addChar(u8 ch);
    //* добавляет символ в начало строки
    void unwindChar(u8 ch);
    bool is_cursor_in_cmdline( cli::const_line x) const {
        return cmdline.is_in(x);
    }

public:
    const char* cursor;
    unsigned    cursor_len;
    //разбирает строку line. 
    //  в процессе разбора cursor,cursor_len устаанавливаются на line и 
    //  отслеживают позицию парсинга
    int processLine(const char* line, unsigned len = ~0u);

    //* продвигает курсор парсера в командной строке
    const char* acceptChars(int len);
    const char* acceptChars(const char* line);
    // берет из позиии курсора строку (курсор не обязательно может находится в cmdline )
    virtual int getLine(char* line, unsigned limit, char eol = EOL);

public:
    virtual int puts(const char* s);
    // добавляет EOL к концу распечатанного
    virtual int putln(const char* s);
    // берет из буффера символ и проверяет что он ==Y
    // @return true - если из буффера считано y|Y
    virtual bool ask_yes(void);

    virtual void on_fail(const char* s){(void)s;}
};


#endif //V12_CLI_H_
