2015-04-29 19:41:55 +00:00
|
|
|
#include <fc/rpc/cli.hpp>
|
2015-05-04 18:07:22 +00:00
|
|
|
#include <fc/thread/thread.hpp>
|
2015-04-29 19:41:55 +00:00
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-02-20 20:18:47 +00:00
|
|
|
#ifdef HAVE_EDITLINE
|
|
|
|
|
# include "editline.h"
|
2015-05-04 17:36:15 +00:00
|
|
|
# ifdef WIN32
|
|
|
|
|
# include <io.h>
|
|
|
|
|
# endif
|
2015-04-29 19:41:55 +00:00
|
|
|
#endif
|
|
|
|
|
|
2018-10-16 07:30:47 +00:00
|
|
|
#include <boost/regex.hpp>
|
|
|
|
|
|
2015-04-29 19:41:55 +00:00
|
|
|
namespace fc { namespace rpc {
|
|
|
|
|
|
2018-10-25 19:58:22 +00:00
|
|
|
static boost::regex& cli_regex_secret()
|
2018-10-16 07:30:47 +00:00
|
|
|
{
|
2018-10-27 16:06:24 +00:00
|
|
|
static boost::regex regex_expr;
|
|
|
|
|
return regex_expr;
|
2018-10-16 07:30:47 +00:00
|
|
|
}
|
|
|
|
|
|
2015-07-27 13:11:20 +00:00
|
|
|
static std::vector<std::string>& cli_commands()
|
|
|
|
|
{
|
|
|
|
|
static std::vector<std::string>* cmds = new std::vector<std::string>();
|
|
|
|
|
return *cmds;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-30 19:28:12 +00:00
|
|
|
cli::~cli()
|
|
|
|
|
{
|
|
|
|
|
if( _run_complete.valid() )
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant cli::send_call( api_id_type api_id, string method_name, variants args /* = variants() */ )
|
|
|
|
|
{
|
|
|
|
|
FC_ASSERT(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variant cli::send_callback( uint64_t callback_id, variants args /* = variants() */ )
|
|
|
|
|
{
|
|
|
|
|
FC_ASSERT(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cli::send_notice( uint64_t callback_id, variants args /* = variants() */ )
|
|
|
|
|
{
|
|
|
|
|
FC_ASSERT(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cli::start()
|
|
|
|
|
{
|
2015-07-27 13:11:20 +00:00
|
|
|
cli_commands() = get_method_names(0);
|
2015-06-30 19:28:12 +00:00
|
|
|
_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)
|
|
|
|
|
{
|
|
|
|
|
_result_formatters[method] = formatter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cli::set_prompt( const string& prompt )
|
|
|
|
|
{
|
|
|
|
|
_prompt = prompt;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 07:30:47 +00:00
|
|
|
void cli::set_regex_secret( const string& expr )
|
|
|
|
|
{
|
|
|
|
|
cli_regex_secret() = expr;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-30 19:28:12 +00:00
|
|
|
void cli::run()
|
|
|
|
|
{
|
|
|
|
|
while( !_run_complete.canceled() )
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
std::string line;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
getline( _prompt.c_str(), line );
|
|
|
|
|
}
|
|
|
|
|
catch ( const fc::eof_exception& e )
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-10-16 15:52:44 +00:00
|
|
|
|
2015-06-30 19:28:12 +00:00
|
|
|
line += char(EOF);
|
2018-10-16 07:30:47 +00:00
|
|
|
fc::variants args = fc::json::variants_from_string(line);
|
2015-06-30 19:28:12 +00:00
|
|
|
if( args.size() == 0 )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const string& method = args[0].get_string();
|
|
|
|
|
|
|
|
|
|
auto result = receive_call( 0, method, variants( args.begin()+1,args.end() ) );
|
|
|
|
|
auto itr = _result_formatters.find( method );
|
|
|
|
|
if( itr == _result_formatters.end() )
|
|
|
|
|
{
|
|
|
|
|
std::cout << fc::json::to_pretty_string( result ) << "\n";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
std::cout << itr->second( result, args ) << "\n";
|
|
|
|
|
}
|
|
|
|
|
catch ( const fc::exception& e )
|
|
|
|
|
{
|
|
|
|
|
std::cout << e.to_detail_string() << "\n";
|
2018-07-30 14:52:56 +00:00
|
|
|
|
|
|
|
|
if (e.code() == fc::canceled_exception_code)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-06-30 19:28:12 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 20:18:47 +00:00
|
|
|
/****
|
|
|
|
|
* @brief loop through list of commands, attempting to find a match
|
|
|
|
|
* @param token what the user typed
|
2018-03-05 19:37:14 +00:00
|
|
|
* @param match sets to 1 if only 1 match was found
|
|
|
|
|
* @returns the remaining letters of the name of the command or NULL if 1 match not found
|
2018-02-20 20:18:47 +00:00
|
|
|
*/
|
|
|
|
|
static char *my_rl_complete(char *token, int *match)
|
2015-07-27 13:11:20 +00:00
|
|
|
{
|
2018-03-07 07:55:59 +00:00
|
|
|
bool have_one = false;
|
2018-03-06 16:44:00 +00:00
|
|
|
std::string method_name;
|
2015-07-27 13:11:20 +00:00
|
|
|
|
|
|
|
|
auto& cmd = cli_commands();
|
2018-03-13 20:39:40 +00:00
|
|
|
const size_t partlen = strlen (token); /* Part of token */
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2018-03-07 14:44:35 +00:00
|
|
|
for (const std::string& it : cmd)
|
2015-07-27 13:11:20 +00:00
|
|
|
{
|
2018-03-06 16:44:00 +00:00
|
|
|
if (it.compare(0, partlen, token) == 0)
|
|
|
|
|
{
|
2018-03-07 07:55:59 +00:00
|
|
|
if (have_one) {
|
|
|
|
|
// we can only have 1, but we found a second
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
method_name = it;
|
|
|
|
|
have_one = true;
|
|
|
|
|
}
|
2018-03-06 16:44:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2018-03-07 07:55:59 +00:00
|
|
|
if (have_one)
|
2018-03-06 16:44:00 +00:00
|
|
|
{
|
|
|
|
|
*match = 1;
|
|
|
|
|
method_name += " ";
|
2018-03-07 07:55:59 +00:00
|
|
|
return strdup (method_name.c_str() + partlen);
|
2015-07-27 13:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-06 16:44:00 +00:00
|
|
|
return NULL;
|
2015-07-27 13:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
2018-02-20 20:18:47 +00:00
|
|
|
/***
|
|
|
|
|
* @brief return an array of matching commands
|
|
|
|
|
* @param token the incoming text
|
2018-03-07 07:55:59 +00:00
|
|
|
* @param array the resultant array of possible matches
|
2018-02-20 20:18:47 +00:00
|
|
|
* @returns the number of matches
|
|
|
|
|
*/
|
2018-03-07 07:55:59 +00:00
|
|
|
static int cli_completion(char *token, char ***array)
|
2015-07-27 13:11:20 +00:00
|
|
|
{
|
2018-03-06 22:17:09 +00:00
|
|
|
auto& cmd = cli_commands();
|
2018-03-07 07:55:59 +00:00
|
|
|
int num_commands = cmd.size();
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2018-03-07 07:55:59 +00:00
|
|
|
char **copy = (char **) malloc (num_commands * sizeof(char *));
|
|
|
|
|
if (copy == NULL)
|
|
|
|
|
{
|
|
|
|
|
// possible out of memory
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int total_matches = 0;
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2018-03-13 20:39:40 +00:00
|
|
|
const size_t partlen = strlen(token);
|
2018-03-07 07:55:59 +00:00
|
|
|
|
2018-03-07 14:44:35 +00:00
|
|
|
for (const std::string& it : cmd)
|
2018-03-07 07:55:59 +00:00
|
|
|
{
|
|
|
|
|
if ( it.compare(0, partlen, token) == 0)
|
|
|
|
|
{
|
|
|
|
|
copy[total_matches] = strdup ( it.c_str() );
|
|
|
|
|
++total_matches;
|
2018-03-06 22:17:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-03-07 07:55:59 +00:00
|
|
|
*array = copy;
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2018-03-07 07:55:59 +00:00
|
|
|
return total_matches;
|
2015-07-27 13:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-16 07:30:47 +00:00
|
|
|
/***
|
|
|
|
|
* @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)
|
|
|
|
|
{
|
2018-10-30 09:38:59 +00:00
|
|
|
if (nullptr != cli_regex_secret().expression() && boost::regex_match(source, cli_regex_secret()))
|
2018-10-16 07:30:47 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 20:18:47 +00:00
|
|
|
/***
|
|
|
|
|
* @brief Read input from the user
|
|
|
|
|
* @param prompt the prompt to display
|
|
|
|
|
* @param line what the user typed
|
|
|
|
|
*/
|
2015-05-18 17:40:01 +00:00
|
|
|
void cli::getline( const fc::string& prompt, fc::string& line)
|
2015-04-29 19:41:55 +00:00
|
|
|
{
|
|
|
|
|
// getting file descriptor for C++ streams is near impossible
|
|
|
|
|
// so we just assume it's the same as the C stream...
|
2018-02-20 20:18:47 +00:00
|
|
|
#ifdef HAVE_EDITLINE
|
2015-04-29 19:41:55 +00:00
|
|
|
#ifndef WIN32
|
|
|
|
|
if( isatty( fileno( stdin ) ) )
|
|
|
|
|
#else
|
|
|
|
|
// it's implied by
|
|
|
|
|
// https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
|
|
|
|
|
// that this is the proper way to do this on Windows, but I have
|
|
|
|
|
// no access to a Windows compiler and thus,
|
|
|
|
|
// no idea if this actually works
|
|
|
|
|
if( _isatty( _fileno( stdin ) ) )
|
|
|
|
|
#endif
|
|
|
|
|
{
|
2018-02-26 21:54:32 +00:00
|
|
|
rl_set_complete_func(my_rl_complete);
|
|
|
|
|
rl_set_list_possib_func(cli_completion);
|
2018-10-16 07:30:47 +00:00
|
|
|
rl_set_check_secret_func(cli_check_secret);
|
2015-07-27 13:11:20 +00:00
|
|
|
|
2015-05-04 18:07:22 +00:00
|
|
|
static fc::thread getline_thread("getline");
|
|
|
|
|
getline_thread.async( [&](){
|
|
|
|
|
char* line_read = nullptr;
|
|
|
|
|
std::cout.flush(); //readline doesn't use cin, so we must manually flush _out
|
|
|
|
|
line_read = readline(prompt.c_str());
|
|
|
|
|
if( line_read == nullptr )
|
|
|
|
|
FC_THROW_EXCEPTION( fc::eof_exception, "" );
|
|
|
|
|
line = line_read;
|
2018-10-16 07:30:47 +00:00
|
|
|
// we don't need here to add line in editline's history, cause it will be doubled
|
2015-05-04 18:07:22 +00:00
|
|
|
free(line_read);
|
|
|
|
|
}).wait();
|
2015-04-29 19:41:55 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
std::cout << prompt;
|
|
|
|
|
// sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline");
|
|
|
|
|
fc::getline( fc::cin, line );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-30 19:28:12 +00:00
|
|
|
} } // namespace fc::rpc
|