From 4cff1ff674125dc4b543a4d2f3565403580822cc Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Sat, 26 May 2012 18:13:01 +0000 Subject: bbackupquery readline improvements, thanks to Paolo Tosco: Automatically quote filenames including spaces on the readline input. Ignore empty commands, don't generate a parse error message. Close cleanly and quietly when EOF is input (Ctrl+D). Simplify interactive mode code in bbackupquery. --- bin/bbackupquery/BackupQueries.cpp | 13 +++- bin/bbackupquery/BackupQueries.h | 3 +- bin/bbackupquery/CommandCompletion.cpp | 119 ++++++++++++++++++++------------- bin/bbackupquery/bbackupquery.cpp | 102 +++++++++++++++------------- 4 files changed, 142 insertions(+), 95 deletions(-) diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp index f88a0761..1ab46be1 100644 --- a/bin/bbackupquery/BackupQueries.cpp +++ b/bin/bbackupquery/BackupQueries.cpp @@ -359,6 +359,11 @@ static std::string GetTimeString(BackupStoreDirectory::Entry& en, void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel, std::ostream &out) { +#ifdef WIN32 + DWORD n_chars; + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); +#endif + // Generate exclude flags int16_t excludeFlags = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING; if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion; @@ -476,7 +481,9 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, std::string listRootDecoded; if(!ConvertUtf8ToConsole(rListRoot.c_str(), listRootDecoded)) return; - out << listRootDecoded << "/"; + listRootDecoded = listRootDecoded + "/"; + WriteConsole(hOut, listRootDecoded.c_str(), + strlen(listRootDecoded.c_str()), &n_chars, NULL); #else out << rListRoot << "/"; #endif @@ -500,7 +507,11 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, } #endif +#ifdef WIN32 + WriteConsole(hOut, fileName.c_str(), strlen(fileName.c_str()), &n_chars, NULL); +#else out << fileName; +#endif if(!en->GetName().IsEncrypted()) { diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h index 74773ba9..09a1c6e4 100644 --- a/bin/bbackupquery/BackupQueries.h +++ b/bin/bbackupquery/BackupQueries.h @@ -73,9 +73,10 @@ public: // command, but if the command line ends in a space, // e.g. during readline parsing, it can be one greater, // to indicate that we should complete the next item instead. - size_t mArgCount; + size_t mCompleteArgCount; ParsedCommand(const std::string& Command, bool isFromCommandLine); + bool IsEmpty() { return mCmdElements.empty(); } }; void DoCommand(ParsedCommand& rCommand); diff --git a/bin/bbackupquery/CommandCompletion.cpp b/bin/bbackupquery/CommandCompletion.cpp index 6d6189b3..93c4d3fd 100644 --- a/bin/bbackupquery/CommandCompletion.cpp +++ b/bin/bbackupquery/CommandCompletion.cpp @@ -83,9 +83,12 @@ COMPLETION_FUNCTION(None,) #ifdef HAVE_A_FILENAME_COMPLETION_FUNCTION COMPLETION_FUNCTION(Default, - while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), 0)) + int i = 0; + + while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), i)) { completions.push_back(match); + ++i; } ) #else // !HAVE_A_FILENAME_COMPLETION_FUNCTION @@ -177,7 +180,7 @@ std::vector CompleteRemoteFileOrDirectory( std::string searchPrefix; std::string listDir = prefix; - if(rCommand.mArgCount == rCommand.mCmdElements.size()) + if(rCommand.mCompleteArgCount == rCommand.mCmdElements.size()) { // completing an empty name, from the current directory // nothing to change @@ -257,6 +260,8 @@ std::vector CompleteRemoteFileOrDirectory( std::string name = clear.GetClearFilename().c_str(); if(name.compare(0, searchPrefix.length(), searchPrefix) == 0) { + bool dir_added = false; + if(en->IsDir() && (includeFlags & BackupProtocolListDirectory::Flags_Dir) == 0) { @@ -265,7 +270,31 @@ std::vector CompleteRemoteFileOrDirectory( name += "/"; } - if(listDir == "") + #ifdef HAVE_LIBREADLINE + if(strchr(name.c_str(), ' ')) + { + int n_quote = 0; + + for(int k = strlen(rl_line_buffer); k >= 0; k--) + { + if (rl_line_buffer[k] == '\"') { + ++n_quote; + } + } + + dir_added = false; + + if (!(n_quote % 2)) + { + name = "\"" + (listDir == "" ? name : listDir + "/" + name); + dir_added = true; + } + + name = name + "\""; + } + #endif + + if(listDir == "" || dir_added) { completions.push_back(name); } @@ -428,7 +457,7 @@ BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command, : mInOptions(false), mFailed(false), pSpec(NULL), - mArgCount(0) + mCompleteArgCount(0) { mCompleteCommand = Command; @@ -451,77 +480,73 @@ BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command, } // split command into components - const char *c = Command.c_str(); bool inQuoted = false; mInOptions = false; - std::string s; - while(*c != 0) + std::string currentArg; + for (std::string::const_iterator c = Command.begin(); + c != Command.end(); c++) { // Terminating char? if(*c == ((inQuoted)?'"':' ')) { - if(!s.empty()) + if(!currentArg.empty()) { - mCmdElements.push_back(s); + mCmdElements.push_back(currentArg); - // Because we just parsed a space, if this - // wasn't an option word, then we're now - // completing the next (or first) arg - if(!mInOptions) - { - mArgCount++; - } + // Because we just found a space, and the last + // word was not options (otherwise currentArg + // would be empty), we've received a complete + // command or non-option argument. + mCompleteArgCount++; } - s.resize(0); + currentArg.resize(0); inQuoted = false; mInOptions = false; } + // Start of quoted parameter? + else if(currentArg.empty() && *c == '"') + { + inQuoted = true; + } + // Start of options? + else if(currentArg.empty() && *c == '-') + { + mInOptions = true; + } + else if(mInOptions) + { + // Option char + mOptions += *c; + } else { - // No. Start of quoted parameter? - if(s.empty() && *c == '"') - { - inQuoted = true; - } - // Start of options? - else if(s.empty() && *c == '-') - { - mInOptions = true; - } - else - { - if(mInOptions) - { - // Option char - mOptions += *c; - } - else - { - // Normal string char - s += *c; - } - } + // Normal string char, part of current arg + currentArg += *c; } - - ++c; } - if(!s.empty()) + if(!currentArg.empty()) { - mCmdElements.push_back(s); + mCmdElements.push_back(currentArg); + } + + // If there are no commands then there's nothing to do except return + if(mCmdElements.empty()) + { + return; } // Work out which command it is... int cmd = 0; - while(mCmdElements.size() > 0 && commands[cmd].name != 0 && + while(commands[cmd].name != 0 && mCmdElements[0] != commands[cmd].name) { cmd++; } - if(mCmdElements.size() > 0 && commands[cmd].name == 0) + if(commands[cmd].name == 0) { // Check for aliases int a; @@ -536,7 +561,7 @@ BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command, } } - if(mCmdElements.size() == 0 || commands[cmd].name == 0) + if(commands[cmd].name == 0) { mFailed = true; return; diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp index 3d73093f..5493f49c 100644 --- a/bin/bbackupquery/bbackupquery.cpp +++ b/bin/bbackupquery/bbackupquery.cpp @@ -109,8 +109,9 @@ char * completion_generator(const char *text, int state) std::string partialCommand(rl_line_buffer, rl_point); sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand, false)); + int currentArg = sapCmd->mCompleteArgCount; - if(sapCmd->mArgCount == 0) // incomplete command + if(currentArg == 0) // incomplete command { completions = CompleteCommand(*sapCmd, text, *pProtocol, *pConfig, *pQueries); @@ -120,11 +121,12 @@ char * completion_generator(const char *text, int state) completions = CompleteOptions(*sapCmd, text, *pProtocol, *pConfig, *pQueries); } - else if(sapCmd->mArgCount - 1 < MAX_COMPLETION_HANDLERS) - // sapCmd->mArgCount must be at least 1 if we're here + else if(currentArg - 1 < MAX_COMPLETION_HANDLERS) + // currentArg must be at least 1 if we're here { CompletionHandler handler = - sapCmd->pSpec->complete[sapCmd->mArgCount - 1]; + sapCmd->pSpec->complete[currentArg - 1]; + if(handler != NULL) { completions = handler(*sapCmd, text, *pProtocol, @@ -477,13 +479,8 @@ int main(int argc, const char *argv[]) // Get commands from input #ifdef HAVE_LIBREADLINE - if (useReadline) - { -#else - if (false) + if(useReadline) { -#endif - #ifdef HAVE_LIBREADLINE // Must initialise the locale before using editline's // readline(), otherwise cannot enter international characters. if (setlocale(LC_ALL, "") == NULL) @@ -505,57 +502,70 @@ int main(int argc, const char *argv[]) pProtocol = &connection; pConfig = &conf; pQueries = &context; + } + + std::string last_cmd; +#endif - char *last_cmd = 0; - while(!context.Stop()) + std::auto_ptr apGetLine; + if(fileno(stdin) >= 0) + { + apGetLine.reset(new FdGetLine(fileno(stdin))); + } + + while(!context.Stop() && fileno(stdin) >= 0) + { + std::string cmd_str; + + #ifdef HAVE_LIBREADLINE + if(useReadline) { - char *command = readline("query > "); + char *cmd_ptr = readline("query > "); - if(command == NULL) + if(cmd_ptr == NULL) { // Ctrl-D pressed -- terminate now break; } - - BackupQueries::ParsedCommand cmd(command, false); - context.DoCommand(cmd); - if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0) + cmd_str = cmd_ptr; + free(cmd_ptr); + } + else + #endif // HAVE_LIBREADLINE + { + printf("query > "); + fflush(stdout); + + try { - free(command); + cmd_str = apGetLine->GetLine(); } - else + catch(CommonException &e) { - #ifdef HAVE_READLINE_HISTORY - add_history(command); - #else - free(last_cmd); - #endif - last_cmd = command; + if(e.GetSubType() == CommonException::GetLineEOF) + { + break; + } + throw; } } - #ifndef HAVE_READLINE_HISTORY - free(last_cmd); - last_cmd = 0; - #endif - #endif // HAVE_READLINE - } - else // !HAVE_LIBREADLINE || !useReadline - { - // Version for platforms which don't have readline by default - if(fileno(stdin) >= 0) + + BackupQueries::ParsedCommand cmd_parsed(cmd_str, false); + if (cmd_parsed.IsEmpty()) { - FdGetLine getLine(fileno(stdin)); - while(!context.Stop()) - { - printf("query > "); - fflush(stdout); - std::string command(getLine.GetLine()); - BackupQueries::ParsedCommand cmd(command, - false); - context.DoCommand(cmd); - } + continue; + } + + context.DoCommand(cmd_parsed); + + #ifdef HAVE_READLINE_HISTORY + if(last_cmd != cmd_str) + { + add_history(cmd_str.c_str()); + last_cmd = cmd_str; } + #endif // HAVE_READLINE_HISTORY } // Done... stop nicely -- cgit v1.2.3