updated to editline changes, brought all updates
This commit is contained in:
parent
1e1a8cb30a
commit
8fe703c35a
4 changed files with 190 additions and 70 deletions
|
|
@ -25,18 +25,22 @@ namespace fc { namespace rpc {
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
void cancel();
|
||||||
void wait();
|
void wait();
|
||||||
void format_result( const string& method, std::function<string(variant,const variants&)> formatter);
|
void format_result( const string& method, std::function<string(variant,const variants&)> formatter);
|
||||||
|
|
||||||
virtual void getline( const fc::string& prompt, fc::string& line );
|
virtual void getline( const std::string& prompt, std::string& line );
|
||||||
|
|
||||||
void set_prompt( const string& prompt );
|
void set_prompt( const string& prompt );
|
||||||
|
|
||||||
|
void set_regex_secret( const string& expr );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
std::string _prompt = ">>>";
|
std::string _prompt = ">>>";
|
||||||
std::map<string,std::function<string(variant,const variants&)> > _result_formatters;
|
std::map<string,std::function<string(variant,const variants&)> > _result_formatters;
|
||||||
fc::future<void> _run_complete;
|
fc::future<void> _run_complete;
|
||||||
|
fc::thread* _getline_thread = nullptr; ///< Wait for user input in this thread
|
||||||
};
|
};
|
||||||
} }
|
} }
|
||||||
|
|
@ -112,6 +112,11 @@ namespace fc {
|
||||||
*/
|
*/
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send signal to underlying native thread. Only for Linux and macOS
|
||||||
|
*/
|
||||||
|
void signal(int);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true unless quit() has been called.
|
* @return true unless quit() has been called.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
214
src/rpc/cli.cpp
214
src/rpc/cli.cpp
|
|
@ -9,13 +9,22 @@
|
||||||
|
|
||||||
#ifdef HAVE_EDITLINE
|
#ifdef HAVE_EDITLINE
|
||||||
# include "editline.h"
|
# include "editline.h"
|
||||||
|
# include <signal.h>
|
||||||
# ifdef WIN32
|
# ifdef WIN32
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
namespace fc { namespace rpc {
|
namespace fc { namespace rpc {
|
||||||
|
|
||||||
|
static boost::regex& cli_regex_secret()
|
||||||
|
{
|
||||||
|
static boost::regex regex_expr;
|
||||||
|
return regex_expr;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<std::string>& cli_commands()
|
static std::vector<std::string>& cli_commands()
|
||||||
{
|
{
|
||||||
static std::vector<std::string>* cmds = new std::vector<std::string>();
|
static std::vector<std::string>* cmds = new std::vector<std::string>();
|
||||||
|
|
@ -45,23 +54,6 @@ void cli::send_notice( uint64_t callback_id, variants args /* = variants() */ )
|
||||||
FC_ASSERT(false);
|
FC_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cli::start()
|
|
||||||
{
|
|
||||||
cli_commands() = get_method_names(0);
|
|
||||||
_run_complete = fc::async( [&](){ run(); } );
|
|
||||||
}
|
|
||||||
|
|
||||||
void cli::stop()
|
|
||||||
{
|
|
||||||
_run_complete.cancel();
|
|
||||||
_run_complete.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cli::wait()
|
|
||||||
{
|
|
||||||
_run_complete.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cli::format_result( const string& method, std::function<string(variant,const variants&)> formatter)
|
void cli::format_result( const string& method, std::function<string(variant,const variants&)> formatter)
|
||||||
{
|
{
|
||||||
_result_formatters[method] = formatter;
|
_result_formatters[method] = formatter;
|
||||||
|
|
@ -72,6 +64,11 @@ void cli::set_prompt( const string& prompt )
|
||||||
_prompt = prompt;
|
_prompt = prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cli::set_regex_secret( const string& expr )
|
||||||
|
{
|
||||||
|
cli_regex_secret() = expr;
|
||||||
|
}
|
||||||
|
|
||||||
void cli::run()
|
void cli::run()
|
||||||
{
|
{
|
||||||
while( !_run_complete.canceled() )
|
while( !_run_complete.canceled() )
|
||||||
|
|
@ -85,11 +82,17 @@ void cli::run()
|
||||||
}
|
}
|
||||||
catch ( const fc::eof_exception& e )
|
catch ( const fc::eof_exception& e )
|
||||||
{
|
{
|
||||||
|
_getline_thread = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::cout << line << "\n";
|
catch ( const fc::canceled_exception& e )
|
||||||
|
{
|
||||||
|
_getline_thread = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
line += char(EOF);
|
line += char(EOF);
|
||||||
fc::variants args = fc::json::variants_from_string(line);;
|
fc::variants args = fc::json::variants_from_string(line);
|
||||||
if( args.size() == 0 )
|
if( args.size() == 0 )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -106,17 +109,16 @@ void cli::run()
|
||||||
}
|
}
|
||||||
catch ( const fc::exception& e )
|
catch ( const fc::exception& e )
|
||||||
{
|
{
|
||||||
std::cout << e.to_detail_string() << "\n";
|
|
||||||
|
|
||||||
if (e.code() == fc::canceled_exception_code)
|
if (e.code() == fc::canceled_exception_code)
|
||||||
{
|
{
|
||||||
|
_getline_thread = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
std::cout << e.to_detail_string() << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/****
|
/****
|
||||||
* @brief loop through list of commands, attempting to find a match
|
* @brief loop through list of commands, attempting to find a match
|
||||||
* @param token what the user typed
|
* @param token what the user typed
|
||||||
|
|
@ -125,36 +127,52 @@ void cli::run()
|
||||||
*/
|
*/
|
||||||
static char *my_rl_complete(char *token, int *match)
|
static char *my_rl_complete(char *token, int *match)
|
||||||
{
|
{
|
||||||
bool have_one = false;
|
const auto& cmds = cli_commands();
|
||||||
std::string method_name;
|
const size_t partlen = strlen (token); /* Part of token */
|
||||||
|
|
||||||
auto& cmd = cli_commands();
|
std::vector<std::reference_wrapper<const std::string>> matched_cmds;
|
||||||
int partlen = strlen (token); /* Part of token */
|
for( const std::string& it : cmds )
|
||||||
|
|
||||||
for (const std::string& it : cmd)
|
|
||||||
{
|
{
|
||||||
if( it.compare(0, partlen, token) == 0 )
|
if( it.compare(0, partlen, token) == 0 )
|
||||||
{
|
{
|
||||||
if (have_one) {
|
matched_cmds.push_back( it );
|
||||||
// we can only have 1, but we found a second
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
method_name = it;
|
|
||||||
have_one = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (have_one)
|
if( matched_cmds.size() == 0 )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
const std::string& first_matched_cmd = matched_cmds[0];
|
||||||
|
if( matched_cmds.size() == 1 )
|
||||||
{
|
{
|
||||||
*match = 1;
|
*match = 1;
|
||||||
method_name += " ";
|
std::string matched_cmd = first_matched_cmd + " ";
|
||||||
return strdup (method_name.c_str() + partlen);
|
return strdup( matched_cmd.c_str() + partlen );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t first_cmd_len = first_matched_cmd.size();
|
||||||
|
size_t matched_len = partlen;
|
||||||
|
for( ; matched_len < first_cmd_len; ++matched_len )
|
||||||
|
{
|
||||||
|
char next_char = first_matched_cmd[matched_len];
|
||||||
|
bool end = false;
|
||||||
|
for( const std::string& s : matched_cmds )
|
||||||
|
{
|
||||||
|
if( s.size() <= matched_len || s[matched_len] != next_char )
|
||||||
|
{
|
||||||
|
end = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( end )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( matched_len == partlen )
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
std::string matched_cmd_part = first_matched_cmd.substr( partlen, matched_len - partlen );
|
||||||
|
return strdup( matched_cmd_part.c_str() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
|
@ -176,7 +194,7 @@ static int cli_completion(char *token, char ***array)
|
||||||
}
|
}
|
||||||
int total_matches = 0;
|
int total_matches = 0;
|
||||||
|
|
||||||
int partlen = strlen(token);
|
const size_t partlen = strlen(token);
|
||||||
|
|
||||||
for (const std::string& it : cmd)
|
for (const std::string& it : cmd)
|
||||||
{
|
{
|
||||||
|
|
@ -191,12 +209,98 @@ static int cli_completion(char *token, char ***array)
|
||||||
return total_matches;
|
return total_matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @brief regex match for secret information
|
||||||
|
* @param source the incoming text source
|
||||||
|
* @returns integer 1 in event of regex match for secret information, otherwise 0
|
||||||
|
*/
|
||||||
|
static int cli_check_secret(const char *source)
|
||||||
|
{
|
||||||
|
if (!cli_regex_secret().empty() && boost::regex_match(source, cli_regex_secret()))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Indicates whether CLI is quitting after got a SIGINT signal.
|
||||||
|
* In order to be used by editline which is C-style, this is a global variable.
|
||||||
|
*/
|
||||||
|
static int cli_quitting = false;
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
/**
|
||||||
|
* Get next character from stdin, or EOF if got a SIGINT signal
|
||||||
|
*/
|
||||||
|
static int interruptible_getc(void)
|
||||||
|
{
|
||||||
|
if( cli_quitting )
|
||||||
|
return EOF;
|
||||||
|
|
||||||
|
int r;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
r = read(0, &c, 1); // read from stdin, will return -1 on SIGINT
|
||||||
|
|
||||||
|
if( r == -1 && errno == EINTR )
|
||||||
|
cli_quitting = true;
|
||||||
|
|
||||||
|
return r == 1 && !cli_quitting ? c : EOF;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void cli::start()
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef HAVE_EDITLINE
|
||||||
|
el_hist_size = 256;
|
||||||
|
|
||||||
|
rl_set_complete_func(my_rl_complete);
|
||||||
|
rl_set_list_possib_func(cli_completion);
|
||||||
|
//rl_set_check_secret_func(cli_check_secret);
|
||||||
|
rl_set_getc_func(interruptible_getc);
|
||||||
|
|
||||||
|
static fc::thread getline_thread("getline");
|
||||||
|
_getline_thread = &getline_thread;
|
||||||
|
|
||||||
|
cli_quitting = false;
|
||||||
|
|
||||||
|
cli_commands() = get_method_names(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_run_complete = fc::async( [this](){ run(); } );
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli::cancel()
|
||||||
|
{
|
||||||
|
_run_complete.cancel();
|
||||||
|
#ifdef HAVE_EDITLINE
|
||||||
|
cli_quitting = true;
|
||||||
|
if( _getline_thread )
|
||||||
|
{
|
||||||
|
_getline_thread->signal(SIGINT);
|
||||||
|
_getline_thread = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli::stop()
|
||||||
|
{
|
||||||
|
cancel();
|
||||||
|
_run_complete.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli::wait()
|
||||||
|
{
|
||||||
|
_run_complete.wait();
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* @brief Read input from the user
|
* @brief Read input from the user
|
||||||
* @param prompt the prompt to display
|
* @param prompt the prompt to display
|
||||||
* @param line what the user typed
|
* @param line what the user typed
|
||||||
*/
|
*/
|
||||||
void cli::getline( const fc::string& prompt, fc::string& line)
|
void cli::getline( const std::string& prompt, std::string& line)
|
||||||
{
|
{
|
||||||
// getting file descriptor for C++ streams is near impossible
|
// getting file descriptor for C++ streams is near impossible
|
||||||
// so we just assume it's the same as the C stream...
|
// so we just assume it's the same as the C stream...
|
||||||
|
|
@ -212,37 +316,35 @@ void cli::getline( const fc::string& prompt, fc::string& line)
|
||||||
if( _isatty( _fileno( stdin ) ) )
|
if( _isatty( _fileno( stdin ) ) )
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
rl_set_complete_func(my_rl_complete);
|
if( _getline_thread )
|
||||||
rl_set_list_possib_func(cli_completion);
|
{
|
||||||
jjkkljk;
|
_getline_thread->async( [&prompt,&line](){
|
||||||
static fc::thread getline_thread("getline");
|
|
||||||
getline_thread.async( [&](){
|
|
||||||
char* line_read = nullptr;
|
char* line_read = nullptr;
|
||||||
std::cout.flush(); //readline doesn't use cin, so we must manually flush _out
|
std::cout.flush(); //readline doesn't use cin, so we must manually flush _out
|
||||||
line_read = readline(prompt.c_str());
|
line_read = readline(prompt.c_str());
|
||||||
if( line_read == nullptr )
|
if( line_read == nullptr )
|
||||||
FC_THROW_EXCEPTION( fc::eof_exception, "" );
|
FC_THROW_EXCEPTION( fc::eof_exception, "" );
|
||||||
line = line_read;
|
line = line_read;
|
||||||
try
|
// we don't need here to add line in editline's history, cause it will be doubled
|
||||||
{
|
if (cli_check_secret(line_read)) {
|
||||||
if (*line_read)
|
|
||||||
add_history(line_read);
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
free(line_read);
|
free(line_read);
|
||||||
throw;
|
el_no_echo = 1;
|
||||||
|
line_read = readline("Enter password: ");
|
||||||
|
el_no_echo = 0;
|
||||||
|
if( line_read == nullptr )
|
||||||
|
FC_THROW_EXCEPTION( fc::eof_exception, "" );
|
||||||
|
line = line + ' ' + line_read;
|
||||||
}
|
}
|
||||||
free(line_read);
|
free(line_read);
|
||||||
}).wait();
|
}).wait();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
std::cout << prompt;
|
std::cout << prompt;
|
||||||
// sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline");
|
// sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline");
|
||||||
fc::getline( fc::cin, line );
|
fc::getline( fc::cin, line );
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,16 @@ namespace fc {
|
||||||
}
|
}
|
||||||
|
|
||||||
void thread::debug( const fc::string& d ) { /*my->debug(d);*/ }
|
void thread::debug( const fc::string& d ) { /*my->debug(d);*/ }
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
#include <signal.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void thread::signal(int sig)
|
||||||
|
{
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
pthread_kill( my->boost_thread->native_handle(), sig );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
void thread::quit()
|
void thread::quit()
|
||||||
{
|
{
|
||||||
//if quitting from a different thread, start quit task on thread.
|
//if quitting from a different thread, start quit task on thread.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue