| 1 | //===-- CommandAlias.cpp --------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Interpreter/CommandAlias.h" |
| 10 | |
| 11 | #include "llvm/ADT/STLExtras.h" |
| 12 | #include "llvm/Support/ErrorHandling.h" |
| 13 | #include "llvm/Support/FormatAdapters.h" |
| 14 | |
| 15 | #include "lldb/Interpreter/CommandInterpreter.h" |
| 16 | #include "lldb/Interpreter/CommandObject.h" |
| 17 | #include "lldb/Interpreter/CommandReturnObject.h" |
| 18 | #include "lldb/Interpreter/Options.h" |
| 19 | #include "lldb/Utility/StreamString.h" |
| 20 | |
| 21 | using namespace lldb; |
| 22 | using namespace lldb_private; |
| 23 | |
| 24 | static llvm::Error |
| 25 | ProcessAliasOptionsArgs(lldb::CommandObjectSP &cmd_obj_sp, |
| 26 | llvm::StringRef options_args, |
| 27 | OptionArgVectorSP &option_arg_vector_sp) { |
| 28 | OptionArgVector *option_arg_vector = option_arg_vector_sp.get(); |
| 29 | |
| 30 | if (options_args.size() < 1) |
| 31 | return llvm::Error::success(); |
| 32 | |
| 33 | Args args(options_args); |
| 34 | std::string options_string(options_args); |
| 35 | // Check to see if the command being aliased can take any command options. |
| 36 | Options *options = cmd_obj_sp->GetOptions(); |
| 37 | if (options) { |
| 38 | // See if any options were specified as part of the alias; if so, handle |
| 39 | // them appropriately. |
| 40 | ExecutionContext exe_ctx = |
| 41 | cmd_obj_sp->GetCommandInterpreter().GetExecutionContext(); |
| 42 | options->NotifyOptionParsingStarting(execution_context: &exe_ctx); |
| 43 | |
| 44 | llvm::Expected<Args> args_or = |
| 45 | options->ParseAlias(args, option_arg_vector, input_line&: options_string); |
| 46 | if (!args_or) |
| 47 | return llvm::createStringError( |
| 48 | S: llvm::formatv(Fmt: "unable to create alias: {0}" , |
| 49 | Vals: llvm::fmt_consume(Item: args_or.takeError()))); |
| 50 | args = std::move(*args_or); |
| 51 | if (llvm::Error error = options->VerifyPartialOptions()) |
| 52 | return error; |
| 53 | } |
| 54 | |
| 55 | if (!options_string.empty()) { |
| 56 | if (cmd_obj_sp->WantsRawCommandString()) { |
| 57 | option_arg_vector->emplace_back(args&: CommandInterpreter::g_argument, args: -1, |
| 58 | args&: options_string); |
| 59 | } else { |
| 60 | for (auto &entry : args.entries()) { |
| 61 | if (!entry.ref().empty()) |
| 62 | option_arg_vector->emplace_back( |
| 63 | args: std::string(CommandInterpreter::g_argument), args: -1, |
| 64 | args: std::string(entry.ref())); |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | return llvm::Error::success(); |
| 70 | } |
| 71 | |
| 72 | CommandAlias::CommandAlias(CommandInterpreter &interpreter, |
| 73 | lldb::CommandObjectSP cmd_sp, |
| 74 | llvm::StringRef options_args, llvm::StringRef name, |
| 75 | llvm::StringRef help, llvm::StringRef syntax, |
| 76 | uint32_t flags) |
| 77 | : CommandObject(interpreter, name, help, syntax, flags), |
| 78 | m_option_string(std::string(options_args)), |
| 79 | m_option_args_sp(new OptionArgVector), |
| 80 | m_is_dashdash_alias(eLazyBoolCalculate), m_did_set_help(false), |
| 81 | m_did_set_help_long(false) { |
| 82 | if (llvm::Error error = |
| 83 | ProcessAliasOptionsArgs(cmd_obj_sp&: cmd_sp, options_args, option_arg_vector_sp&: m_option_args_sp)) { |
| 84 | // FIXME: Find a way to percolate this error up. |
| 85 | LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(error), |
| 86 | "ProcessAliasOptionsArgs failed: {0}" ); |
| 87 | } else { |
| 88 | m_underlying_command_sp = cmd_sp; |
| 89 | for (int i = 0; |
| 90 | auto *cmd_entry = m_underlying_command_sp->GetArgumentEntryAtIndex(idx: i); |
| 91 | i++) { |
| 92 | m_arguments.push_back(x: *cmd_entry); |
| 93 | } |
| 94 | if (!help.empty()) { |
| 95 | StreamString sstr; |
| 96 | StreamString translation_and_help; |
| 97 | GetAliasExpansion(help_string&: sstr); |
| 98 | |
| 99 | translation_and_help.Printf( |
| 100 | format: "(%s) %s" , sstr.GetData(), |
| 101 | GetUnderlyingCommand()->GetHelp().str().c_str()); |
| 102 | SetHelp(translation_and_help.GetString()); |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | bool CommandAlias::WantsRawCommandString() { |
| 108 | if (IsValid()) |
| 109 | return m_underlying_command_sp->WantsRawCommandString(); |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | bool CommandAlias::WantsCompletion() { |
| 114 | if (IsValid()) |
| 115 | return m_underlying_command_sp->WantsCompletion(); |
| 116 | return false; |
| 117 | } |
| 118 | |
| 119 | void CommandAlias::HandleCompletion(CompletionRequest &request) { |
| 120 | if (IsValid()) |
| 121 | m_underlying_command_sp->HandleCompletion(request); |
| 122 | } |
| 123 | |
| 124 | void CommandAlias::HandleArgumentCompletion( |
| 125 | CompletionRequest &request, OptionElementVector &opt_element_vector) { |
| 126 | if (IsValid()) |
| 127 | m_underlying_command_sp->HandleArgumentCompletion(request, |
| 128 | opt_element_vector); |
| 129 | } |
| 130 | |
| 131 | Options *CommandAlias::GetOptions() { |
| 132 | if (IsValid()) |
| 133 | return m_underlying_command_sp->GetOptions(); |
| 134 | return nullptr; |
| 135 | } |
| 136 | |
| 137 | void CommandAlias::Execute(const char *args_string, |
| 138 | CommandReturnObject &result) { |
| 139 | llvm_unreachable("CommandAlias::Execute is not to be called" ); |
| 140 | } |
| 141 | |
| 142 | void CommandAlias::GetAliasExpansion(StreamString &help_string) const { |
| 143 | llvm::StringRef command_name = m_underlying_command_sp->GetCommandName(); |
| 144 | help_string.Printf(format: "'%*s" , (int)command_name.size(), command_name.data()); |
| 145 | |
| 146 | if (!m_option_args_sp) { |
| 147 | help_string.Printf(format: "'" ); |
| 148 | return; |
| 149 | } |
| 150 | |
| 151 | OptionArgVector *options = m_option_args_sp.get(); |
| 152 | std::string opt; |
| 153 | std::string value; |
| 154 | |
| 155 | for (const auto &opt_entry : *options) { |
| 156 | std::tie(args&: opt, args: std::ignore, args&: value) = opt_entry; |
| 157 | if (opt == CommandInterpreter::g_argument) { |
| 158 | help_string.Printf(format: " %s" , value.c_str()); |
| 159 | } else { |
| 160 | help_string.Printf(format: " %s" , opt.c_str()); |
| 161 | if ((value != CommandInterpreter::g_no_argument) |
| 162 | && (value != CommandInterpreter::g_need_argument)) { |
| 163 | help_string.Printf(format: " %s" , value.c_str()); |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | help_string.Printf(format: "'" ); |
| 169 | } |
| 170 | |
| 171 | bool CommandAlias::IsDashDashCommand() { |
| 172 | if (m_is_dashdash_alias != eLazyBoolCalculate) |
| 173 | return (m_is_dashdash_alias == eLazyBoolYes); |
| 174 | m_is_dashdash_alias = eLazyBoolNo; |
| 175 | if (!IsValid()) |
| 176 | return false; |
| 177 | |
| 178 | std::string opt; |
| 179 | std::string value; |
| 180 | |
| 181 | for (const auto &opt_entry : *GetOptionArguments()) { |
| 182 | std::tie(args&: opt, args: std::ignore, args&: value) = opt_entry; |
| 183 | if (opt == CommandInterpreter::g_argument && !value.empty() && |
| 184 | llvm::StringRef(value).ends_with(Suffix: "--" )) { |
| 185 | m_is_dashdash_alias = eLazyBoolYes; |
| 186 | break; |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | // if this is a nested alias, it may be adding arguments on top of an already |
| 191 | // dash-dash alias |
| 192 | if ((m_is_dashdash_alias == eLazyBoolNo) && IsNestedAlias()) |
| 193 | m_is_dashdash_alias = |
| 194 | (GetUnderlyingCommand()->IsDashDashCommand() ? eLazyBoolYes |
| 195 | : eLazyBoolNo); |
| 196 | return (m_is_dashdash_alias == eLazyBoolYes); |
| 197 | } |
| 198 | |
| 199 | bool CommandAlias::IsNestedAlias() { |
| 200 | if (GetUnderlyingCommand()) |
| 201 | return GetUnderlyingCommand()->IsAlias(); |
| 202 | return false; |
| 203 | } |
| 204 | |
| 205 | std::pair<lldb::CommandObjectSP, OptionArgVectorSP> CommandAlias::Desugar() { |
| 206 | auto underlying = GetUnderlyingCommand(); |
| 207 | if (!underlying) |
| 208 | return {nullptr, nullptr}; |
| 209 | |
| 210 | if (underlying->IsAlias()) { |
| 211 | // FIXME: This doesn't work if the original alias fills a slot in the |
| 212 | // underlying alias, since this just appends the two lists. |
| 213 | auto desugared = ((CommandAlias *)underlying.get())->Desugar(); |
| 214 | OptionArgVectorSP options = std::make_shared<OptionArgVector>(); |
| 215 | llvm::append_range(C&: *options, R&: *desugared.second); |
| 216 | llvm::append_range(C&: *options, R&: *GetOptionArguments()); |
| 217 | return {desugared.first, options}; |
| 218 | } |
| 219 | |
| 220 | return {underlying, GetOptionArguments()}; |
| 221 | } |
| 222 | |
| 223 | // allow CommandAlias objects to provide their own help, but fallback to the |
| 224 | // info for the underlying command if no customization has been provided |
| 225 | void CommandAlias::SetHelp(llvm::StringRef str) { |
| 226 | this->CommandObject::SetHelp(str); |
| 227 | m_did_set_help = true; |
| 228 | } |
| 229 | |
| 230 | void CommandAlias::SetHelpLong(llvm::StringRef str) { |
| 231 | this->CommandObject::SetHelpLong(str); |
| 232 | m_did_set_help_long = true; |
| 233 | } |
| 234 | |
| 235 | llvm::StringRef CommandAlias::GetHelp() { |
| 236 | if (!m_cmd_help_short.empty() || m_did_set_help) |
| 237 | return m_cmd_help_short; |
| 238 | if (IsValid()) |
| 239 | return m_underlying_command_sp->GetHelp(); |
| 240 | return llvm::StringRef(); |
| 241 | } |
| 242 | |
| 243 | llvm::StringRef CommandAlias::GetHelpLong() { |
| 244 | if (!m_cmd_help_long.empty() || m_did_set_help_long) |
| 245 | return m_cmd_help_long; |
| 246 | if (IsValid()) |
| 247 | return m_underlying_command_sp->GetHelpLong(); |
| 248 | return llvm::StringRef(); |
| 249 | } |
| 250 | |