//------------------------------------------------------------------------------ //! @file ConsoleCompletion.cc //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2017 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "ConsoleMain.hh" #include "ConsoleCompletion.hh" #include "XrdOuc/XrdOucString.hh" #include "XrdOuc/XrdOucTokenizer.hh" #include #include #include #include extern int com_ls(char*); //------------------------------------------------------------------------------ // Helper function to extract the dirname and base name from a absolute or // relative path. For example: // "/a/b/c/d" -> dirname: "/a/b/c/" and basename: "d" // "/a/b/c/d/" -> dirname: "/a/b/c/d/" and basename: "" // "x/y/z" -> dirname: "x/y/" and basename: "z" // "x/y/z/" -> dirname: "x/y/z/" and basename: "" // "" -> dirname: "" and basename: "" //------------------------------------------------------------------------------ void eos_path_split(const std::string& input, std::string& dirname, std::string& basename) { if (input.empty()) { dirname.clear(); basename.clear(); return; } size_t pos = input.rfind('/'); if (pos == std::string::npos) { dirname.clear(); basename = input; return; } dirname = input.substr(0, pos + 1); basename = input.substr(pos + 1); return; } //------------------------------------------------------------------------------ // EOS console custom completion function to be used by the readline library // to provide autocompletion. //------------------------------------------------------------------------------ char** eos_console_completion(const char* text, int start, int end) { char** matches; matches = (char**) 0; // Disable filename completion if our generator finds no matches rl_attempted_completion_over = 1; // If this word is at the start of the line, then it is a command // to complete. Otherwise it is the name of a file in the current // directory. if (start == 0) { rl_completion_append_character = ' '; matches = rl_completion_matches(text, eos_command_generator); return matches; } XrdOucString cmd = rl_line_buffer; if (cmd.beginswith("mkdir ") || cmd.beginswith("rmdir ") || cmd.beginswith("find ") || cmd.beginswith("cd ") || cmd.beginswith("chown ") || cmd.beginswith("chmod ") || cmd.beginswith("attr ") || cmd.beginswith("acl ")) { // dir completion rl_completion_append_character = '\0'; matches = rl_completion_matches(text, eos_dir_generator); } if (cmd.beginswith("rm ") || cmd.beginswith("ls ") || cmd.beginswith("fileinfo ") || cmd.beginswith("cp ")) { // dir/file completion rl_completion_append_character = '\0'; matches = rl_completion_matches(text, eos_all_generator); } return (matches); } //------------------------------------------------------------------------------ // EOS entry (files/directories) generator //------------------------------------------------------------------------------ char* eos_entry_generator(const char* text, int state, bool only_dirs) { static size_t index; static std::vector entries; // If this is a new word to complete, initialize now. This includes // saving the length of TEXT for efficiency, and initializing the index // variable to 0. if (!state) { index = 0; entries.clear(); std::string inarg = text; std::string dirname; std::string basename; eos_path_split(inarg, dirname, basename); if (dirname.empty()) { inarg = gPwd.c_str(); } else { if (dirname.at(0) == '/') { inarg = dirname; } else { inarg = gPwd.c_str() + dirname; } } bool oldsilent = silent; silent = true; XrdOucString comarg = "-F "; comarg += inarg.c_str(); char buffer[4096]; sprintf(buffer, "%s", comarg.c_str()); com_ls((char*) buffer); silent = oldsilent; if (rstdout.c_str()) { XrdOucTokenizer subtokenizer((char*) rstdout.c_str()); do { XrdOucString entry = subtokenizer.GetLine(); if (entry.length()) { if (entry.endswith('\n')) { entry.erase(entry.length() - 1); } if (only_dirs && !entry.endswith('/')) { continue; } if (rl_completion_type == 63) { // ? - list possible completions // When listing completions we need to return the basename of the // candidates if (basename.empty() || ((strncmp(basename.c_str(), entry.c_str(), basename.length()) == 0) && // Exclude exact matches (basename.length() < (size_t)entry.length()))) { entries.push_back(entry.c_str()); } } else if (rl_completion_type == 9) { // TAB - do standard completion // When doing the standard completion we need to return the full path // as given by the user initially (i.e. not including the pwd deduction). if (basename.empty() || (strncmp(basename.c_str(), entry.c_str(), basename.length()) == 0)) { std::string add_path = dirname; add_path += entry.c_str(); entries.push_back(add_path.c_str()); } } } else { break; } } while (1); } } if (index < entries.size()) { return strdup(entries[index++].c_str()); } return ((char*) 0); } //------------------------------------------------------------------------------ // EOS directories generator - similar to the above //------------------------------------------------------------------------------ char* eos_dir_generator(const char* text, int state) { return eos_entry_generator(text, state, true); } //------------------------------------------------------------------------------ // EOS files and directories generator - similar to the above //------------------------------------------------------------------------------ char* eos_all_generator(const char* text, int state) { return eos_entry_generator(text, state); } //------------------------------------------------------------------------------ // Generator function for command completion - similar to the above. //------------------------------------------------------------------------------ char* eos_command_generator(const char* text, int state) { static size_t index; static std::vector completions; // If this is a new word to complete, initialize now. This includes // saving the length of TEXT for efficiency, and initializing the index // variable to 0. if (!state) { int len = strlen(text); completions.clear(); index = 0; char* name; int i = 0; while ((name = commands[i].name)) { if (strncmp(name, text, len) == 0) { completions.push_back(name); } ++i; } } else { ++index; } if (index < completions.size()) { return strdup(completions[index].c_str()); } return ((char*) 0); }