| /* |
| * Copyright (c) 2020 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include "Commands.h" |
| |
| #include "Command.h" |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <sstream> |
| #include <string> |
| |
| #include <lib/support/Base64.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CodeUtils.h> |
| |
| #include "../clusters/JsonParser.h" |
| |
| namespace { |
| |
| char kInteractiveModeName[] = ""; |
| constexpr size_t kInteractiveModeArgumentsMaxLength = 32; |
| constexpr const char * kOptionalArgumentPrefix = "--"; |
| constexpr const char * kJsonClusterKey = "cluster"; |
| constexpr const char * kJsonCommandKey = "command"; |
| constexpr const char * kJsonCommandSpecifierKey = "command_specifier"; |
| constexpr const char * kJsonArgumentsKey = "arguments"; |
| |
| bool GetArgumentsFromJson(Command * command, Json::Value & value, bool optional, std::vector<std::string> & outArgs) |
| { |
| auto memberNames = value.getMemberNames(); |
| |
| std::vector<std::string> args; |
| for (size_t i = 0; i < command->GetArgumentsCount(); i++) |
| { |
| auto argName = command->GetArgumentName(i); |
| auto memberNamesIterator = memberNames.begin(); |
| while (memberNamesIterator != memberNames.end()) |
| { |
| auto memberName = *memberNamesIterator; |
| if (strcasecmp(argName, memberName.c_str()) != 0) |
| { |
| memberNamesIterator++; |
| continue; |
| } |
| |
| if (command->GetArgumentIsOptional(i) != optional) |
| { |
| memberNamesIterator = memberNames.erase(memberNamesIterator); |
| continue; |
| } |
| |
| if (optional) |
| { |
| args.push_back(std::string(kOptionalArgumentPrefix) + argName); |
| } |
| |
| auto argValue = value[memberName].asString(); |
| args.push_back(std::move(argValue)); |
| memberNamesIterator = memberNames.erase(memberNamesIterator); |
| break; |
| } |
| } |
| |
| if (memberNames.size()) |
| { |
| auto memberName = memberNames.front(); |
| ChipLogError(chipTool, "The argument \"\%s\" is not supported.", memberName.c_str()); |
| return false; |
| } |
| |
| outArgs = args; |
| return true; |
| }; |
| |
| // Check for arguments with a starting '"' but no ending '"': those |
| // would indicate that people are using double-quoting, not single |
| // quoting, on arguments with spaces. |
| static void DetectAndLogMismatchedDoubleQuotes(int argc, char ** argv) |
| { |
| for (int curArg = 0; curArg < argc; ++curArg) |
| { |
| char * arg = argv[curArg]; |
| if (!arg) |
| { |
| continue; |
| } |
| |
| auto len = strlen(arg); |
| if (len == 0) |
| { |
| continue; |
| } |
| |
| if (arg[0] == '"' && arg[len - 1] != '"') |
| { |
| ChipLogError(chipTool, |
| "Mismatched '\"' detected in argument: '%s'. Use single quotes to delimit arguments with spaces " |
| "in them: 'x y', not \"x y\".", |
| arg); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| void Commands::Register(const char * commandSetName, commands_list commandsList, const char * helpText, bool isCluster) |
| { |
| VerifyOrDieWithMsg(isCluster || helpText != nullptr, chipTool, "Non-cluster command sets must have help text"); |
| mCommandSets[commandSetName].isCluster = isCluster; |
| mCommandSets[commandSetName].helpText = helpText; |
| for (auto & command : commandsList) |
| { |
| mCommandSets[commandSetName].commands.push_back(std::move(command)); |
| } |
| } |
| |
| int Commands::Run(int argc, char ** argv) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| err = chip::Platform::MemoryInit(); |
| VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Memory failure: %s", chip::ErrorStr(err))); |
| |
| #ifdef CONFIG_USE_LOCAL_STORAGE |
| err = mStorage.Init(); |
| VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err))); |
| |
| chip::Logging::SetLogFilter(mStorage.GetLoggingLevel()); |
| #endif // CONFIG_USE_LOCAL_STORAGE |
| |
| err = RunCommand(argc, argv); |
| VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(chipTool, "Run command failure: %s", chip::ErrorStr(err))); |
| |
| exit: |
| return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| |
| int Commands::RunInteractive(const char * command, const chip::Optional<char *> & storageDirectory) |
| { |
| std::vector<std::string> arguments; |
| VerifyOrReturnValue(DecodeArgumentsFromInteractiveMode(command, arguments), EXIT_FAILURE); |
| |
| if (arguments.size() > (kInteractiveModeArgumentsMaxLength - 1 /* for interactive mode name */)) |
| { |
| ChipLogError(chipTool, "Too many arguments. Ignoring."); |
| arguments.resize(kInteractiveModeArgumentsMaxLength - 1); |
| } |
| |
| int argc = 0; |
| char * argv[kInteractiveModeArgumentsMaxLength] = {}; |
| argv[argc++] = kInteractiveModeName; |
| |
| std::string commandStr; |
| for (auto & arg : arguments) |
| { |
| argv[argc] = new char[arg.size() + 1]; |
| strcpy(argv[argc++], arg.c_str()); |
| commandStr += arg; |
| commandStr += " "; |
| } |
| |
| ChipLogProgress(chipTool, "Command: %s", commandStr.c_str()); |
| auto err = RunCommand(argc, argv, true, storageDirectory); |
| |
| // Do not delete arg[0] |
| for (auto i = 1; i < argc; i++) |
| { |
| delete[] argv[i]; |
| } |
| |
| return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| |
| CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, |
| const chip::Optional<char *> & interactiveStorageDirectory) |
| { |
| Command * command = nullptr; |
| |
| if (argc <= 1) |
| { |
| ChipLogError(chipTool, "Missing cluster or command set name"); |
| ShowCommandSets(argv[0]); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| auto commandSetIter = GetCommandSet(argv[1]); |
| if (commandSetIter == mCommandSets.end()) |
| { |
| ChipLogError(chipTool, "Unknown cluster or command set: %s", argv[1]); |
| ShowCommandSets(argv[0]); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| auto & commandList = commandSetIter->second.commands; |
| auto * helpText = commandSetIter->second.helpText; |
| |
| if (argc <= 2) |
| { |
| ChipLogError(chipTool, "Missing command name"); |
| ShowCommandSet(argv[0], argv[1], commandList, helpText); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| bool isGlobalCommand = IsGlobalCommand(argv[2]); |
| if (!isGlobalCommand) |
| { |
| command = GetCommand(commandList, argv[2]); |
| if (command == nullptr) |
| { |
| ChipLogError(chipTool, "Unknown command: %s", argv[2]); |
| ShowCommandSet(argv[0], argv[1], commandList, helpText); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| } |
| else if (IsEventCommand(argv[2])) |
| { |
| if (argc <= 3) |
| { |
| ChipLogError(chipTool, "Missing event name"); |
| ShowClusterEvents(argv[0], argv[1], argv[2], commandList); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| command = GetGlobalCommand(commandList, argv[2], argv[3]); |
| if (command == nullptr) |
| { |
| ChipLogError(chipTool, "Unknown event: %s", argv[3]); |
| ShowClusterEvents(argv[0], argv[1], argv[2], commandList); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| } |
| else |
| { |
| if (argc <= 3) |
| { |
| ChipLogError(chipTool, "Missing attribute name"); |
| ShowClusterAttributes(argv[0], argv[1], argv[2], commandList); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| command = GetGlobalCommand(commandList, argv[2], argv[3]); |
| if (command == nullptr) |
| { |
| ChipLogError(chipTool, "Unknown attribute: %s", argv[3]); |
| ShowClusterAttributes(argv[0], argv[1], argv[2], commandList); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| } |
| |
| int argumentsPosition = isGlobalCommand ? 4 : 3; |
| if (!command->InitArguments(argc - argumentsPosition, &argv[argumentsPosition])) |
| { |
| if (interactive) |
| { |
| DetectAndLogMismatchedDoubleQuotes(argc - argumentsPosition, &argv[argumentsPosition]); |
| } |
| ShowCommand(argv[0], argv[1], command); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| if (interactive) |
| { |
| return command->RunAsInteractive(interactiveStorageDirectory); |
| } |
| |
| // Now that the command is initialized, get our storage from it as needed |
| // and set up our loging level. |
| #ifdef CONFIG_USE_LOCAL_STORAGE |
| CHIP_ERROR err = mStorage.Init(nullptr, command->GetStorageDirectory().ValueOr(nullptr)); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err)); |
| return err; |
| } |
| |
| chip::Logging::SetLogFilter(mStorage.GetLoggingLevel()); |
| #endif // CONFIG_USE_LOCAL_STORAGE |
| |
| return command->Run(); |
| } |
| |
| Commands::CommandSetMap::iterator Commands::GetCommandSet(std::string commandSetName) |
| { |
| for (auto & commandSet : mCommandSets) |
| { |
| std::string key(commandSet.first); |
| std::transform(key.begin(), key.end(), key.begin(), ::tolower); |
| if (key.compare(commandSetName) == 0) |
| { |
| return mCommandSets.find(commandSet.first); |
| } |
| } |
| |
| return mCommandSets.end(); |
| } |
| |
| Command * Commands::GetCommand(CommandsVector & commands, std::string commandName) |
| { |
| for (auto & command : commands) |
| { |
| if (commandName.compare(command->GetName()) == 0) |
| { |
| return command.get(); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| Command * Commands::GetGlobalCommand(CommandsVector & commands, std::string commandName, std::string attributeName) |
| { |
| for (auto & command : commands) |
| { |
| if (commandName.compare(command->GetName()) == 0 && attributeName.compare(command->GetAttribute()) == 0) |
| { |
| return command.get(); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool Commands::IsAttributeCommand(std::string commandName) const |
| { |
| return commandName.compare("read") == 0 || commandName.compare("write") == 0 || commandName.compare("force-write") == 0 || |
| commandName.compare("subscribe") == 0; |
| } |
| |
| bool Commands::IsEventCommand(std::string commandName) const |
| { |
| return commandName.compare("read-event") == 0 || commandName.compare("subscribe-event") == 0; |
| } |
| |
| bool Commands::IsGlobalCommand(std::string commandName) const |
| { |
| return IsAttributeCommand(commandName) || IsEventCommand(commandName); |
| } |
| |
| void Commands::ShowCommandSetOverview(std::string commandSetName, const CommandSet & commandSet) |
| { |
| std::transform(commandSetName.begin(), commandSetName.end(), commandSetName.begin(), |
| [](unsigned char c) { return std::tolower(c); }); |
| fprintf(stderr, " | * %-82s|\n", commandSetName.c_str()); |
| ShowHelpText(commandSet.helpText); |
| } |
| |
| void Commands::ShowCommandSets(std::string executable) |
| { |
| fprintf(stderr, "Usage:\n"); |
| fprintf(stderr, " %s cluster_name command_name [param1 param2 ...]\n", executable.c_str()); |
| fprintf(stderr, "or:\n"); |
| fprintf(stderr, " %s command_set_name command_name [param1 param2 ...]\n", executable.c_str()); |
| fprintf(stderr, "\n"); |
| // Table of clusters |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, " | Clusters: |\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| for (auto & commandSet : mCommandSets) |
| { |
| if (commandSet.second.isCluster) |
| { |
| ShowCommandSetOverview(commandSet.first, commandSet.second); |
| } |
| } |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, "\n"); |
| |
| // Table of command sets |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, " | Command sets: |\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| for (auto & commandSet : mCommandSets) |
| { |
| if (!commandSet.second.isCluster) |
| { |
| ShowCommandSetOverview(commandSet.first, commandSet.second); |
| } |
| } |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| } |
| |
| void Commands::ShowCommandSet(std::string executable, std::string commandSetName, CommandsVector & commands, const char * helpText) |
| { |
| fprintf(stderr, "Usage:\n"); |
| fprintf(stderr, " %s %s command_name [param1 param2 ...]\n", executable.c_str(), commandSetName.c_str()); |
| |
| if (helpText) |
| { |
| fprintf(stderr, "\n%s\n", helpText); |
| } |
| |
| fprintf(stderr, "\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, " | Commands: |\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| bool readCommand = false; |
| bool writeCommand = false; |
| bool writeOverrideCommand = false; |
| bool subscribeCommand = false; |
| bool readEventCommand = false; |
| bool subscribeEventCommand = false; |
| for (auto & command : commands) |
| { |
| bool shouldPrint = true; |
| |
| if (IsGlobalCommand(command->GetName())) |
| { |
| if (strcmp(command->GetName(), "read") == 0 && !readCommand) |
| { |
| readCommand = true; |
| } |
| else if (strcmp(command->GetName(), "write") == 0 && !writeCommand) |
| { |
| writeCommand = true; |
| } |
| else if (strcmp(command->GetName(), "force-write") == 0 && !writeOverrideCommand) |
| { |
| writeOverrideCommand = true; |
| } |
| else if (strcmp(command->GetName(), "subscribe") == 0 && !subscribeCommand) |
| { |
| subscribeCommand = true; |
| } |
| else if (strcmp(command->GetName(), "read-event") == 0 && !readEventCommand) |
| { |
| readEventCommand = true; |
| } |
| else if (strcmp(command->GetName(), "subscribe-event") == 0 && !subscribeEventCommand) |
| { |
| subscribeEventCommand = true; |
| } |
| else |
| { |
| shouldPrint = false; |
| } |
| } |
| |
| if (shouldPrint) |
| { |
| fprintf(stderr, " | * %-82s|\n", command->GetName()); |
| ShowHelpText(command->GetHelpText()); |
| } |
| } |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| } |
| |
| void Commands::ShowClusterAttributes(std::string executable, std::string clusterName, std::string commandName, |
| CommandsVector & commands) |
| { |
| fprintf(stderr, "Usage:\n"); |
| fprintf(stderr, " %s %s %s attribute-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(), |
| commandName.c_str()); |
| fprintf(stderr, "\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, " | Attributes: |\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| for (auto & command : commands) |
| { |
| if (commandName.compare(command->GetName()) == 0) |
| { |
| fprintf(stderr, " | * %-82s|\n", command->GetAttribute()); |
| } |
| } |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| } |
| |
| void Commands::ShowClusterEvents(std::string executable, std::string clusterName, std::string commandName, |
| CommandsVector & commands) |
| { |
| fprintf(stderr, "Usage:\n"); |
| fprintf(stderr, " %s %s %s event-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(), commandName.c_str()); |
| fprintf(stderr, "\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| fprintf(stderr, " | Events: |\n"); |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| for (auto & command : commands) |
| { |
| if (commandName.compare(command->GetName()) == 0) |
| { |
| fprintf(stderr, " | * %-82s|\n", command->GetEvent()); |
| } |
| } |
| fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); |
| } |
| |
| void Commands::ShowCommand(std::string executable, std::string clusterName, Command * command) |
| { |
| fprintf(stderr, "Usage:\n"); |
| |
| std::string arguments; |
| std::string description; |
| arguments += command->GetName(); |
| |
| if (command->GetReadOnlyGlobalCommandArgument()) |
| { |
| arguments += ' '; |
| arguments += command->GetReadOnlyGlobalCommandArgument(); |
| } |
| |
| size_t argumentsCount = command->GetArgumentsCount(); |
| for (size_t i = 0; i < argumentsCount; i++) |
| { |
| std::string arg; |
| bool isOptional = command->GetArgumentIsOptional(i); |
| if (isOptional) |
| { |
| arg += "[--"; |
| } |
| arg += command->GetArgumentName(i); |
| if (isOptional) |
| { |
| arg += "]"; |
| } |
| arguments += " "; |
| arguments += arg; |
| |
| const char * argDescription = command->GetArgumentDescription(i); |
| if ((argDescription != nullptr) && (strlen(argDescription) > 0)) |
| { |
| description += "\n"; |
| description += arg; |
| description += ":\n "; |
| description += argDescription; |
| description += "\n"; |
| } |
| } |
| fprintf(stderr, " %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str()); |
| |
| if (command->GetHelpText()) |
| { |
| fprintf(stderr, "\n%s\n", command->GetHelpText()); |
| } |
| |
| if (description.size() > 0) |
| { |
| fprintf(stderr, "%s\n", description.c_str()); |
| } |
| } |
| |
| bool Commands::DecodeArgumentsFromInteractiveMode(const char * command, std::vector<std::string> & args) |
| { |
| // Remote clients may not know the ordering of arguments, so instead of a strict ordering arguments can |
| // be passed in as a json payload encoded in base64 and are reordered on the fly. |
| return IsJsonString(command) ? DecodeArgumentsFromBase64EncodedJson(command, args) |
| : DecodeArgumentsFromStringStream(command, args); |
| } |
| |
| bool Commands::DecodeArgumentsFromBase64EncodedJson(const char * json, std::vector<std::string> & args) |
| { |
| Json::Value jsonValue; |
| bool parsed = JsonParser::ParseCustomArgument(json, json + kJsonStringPrefixLen, jsonValue); |
| VerifyOrReturnValue(parsed, false, ChipLogError(chipTool, "Error while parsing json.")); |
| VerifyOrReturnValue(jsonValue.isObject(), false, ChipLogError(chipTool, "Unexpected json type.")); |
| VerifyOrReturnValue(jsonValue.isMember(kJsonClusterKey), false, |
| ChipLogError(chipTool, "'%s' key not found in json.", kJsonClusterKey)); |
| VerifyOrReturnValue(jsonValue.isMember(kJsonCommandKey), false, |
| ChipLogError(chipTool, "'%s' key not found in json.", kJsonCommandKey)); |
| VerifyOrReturnValue(jsonValue.isMember(kJsonArgumentsKey), false, |
| ChipLogError(chipTool, "'%s' key not found in json.", kJsonArgumentsKey)); |
| VerifyOrReturnValue(IsBase64String(jsonValue[kJsonArgumentsKey].asString().c_str()), false, |
| ChipLogError(chipTool, "'arguments' is not a base64 string.")); |
| |
| auto clusterName = jsonValue[kJsonClusterKey].asString(); |
| auto commandName = jsonValue[kJsonCommandKey].asString(); |
| auto arguments = jsonValue[kJsonArgumentsKey].asString(); |
| |
| auto clusterIter = GetCommandSet(clusterName); |
| VerifyOrReturnValue(clusterIter != mCommandSets.end(), false, |
| ChipLogError(chipTool, "Cluster '%s' is not supported.", clusterName.c_str())); |
| |
| auto & commandList = clusterIter->second.commands; |
| |
| auto command = GetCommand(commandList, commandName); |
| |
| if (jsonValue.isMember(kJsonCommandSpecifierKey) && IsGlobalCommand(commandName)) |
| { |
| auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); |
| command = GetGlobalCommand(commandList, commandName, commandSpecifierName); |
| } |
| VerifyOrReturnValue(nullptr != command, false, ChipLogError(chipTool, "Unknown command.")); |
| |
| auto encodedData = arguments.c_str(); |
| encodedData += kBase64StringPrefixLen; |
| |
| size_t encodedDataSize = strlen(encodedData); |
| size_t expectedMaxDecodedSize = BASE64_MAX_DECODED_LEN(encodedDataSize); |
| |
| chip::Platform::ScopedMemoryBuffer<uint8_t> decodedData; |
| VerifyOrReturnValue(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), false); |
| |
| size_t decodedDataSize = chip::Base64Decode(encodedData, static_cast<uint16_t>(encodedDataSize), decodedData.Get()); |
| VerifyOrReturnValue(decodedDataSize != 0, false, ChipLogError(chipTool, "Error while decoding base64 data.")); |
| |
| decodedData.Get()[decodedDataSize] = '\0'; |
| |
| Json::Value jsonArguments; |
| bool parsedArguments = JsonParser::ParseCustomArgument(encodedData, chip::Uint8::to_char(decodedData.Get()), jsonArguments); |
| VerifyOrReturnValue(parsedArguments, false, ChipLogError(chipTool, "Error while parsing json.")); |
| VerifyOrReturnValue(jsonArguments.isObject(), false, ChipLogError(chipTool, "Unexpected json type, expects and object.")); |
| |
| std::vector<std::string> mandatoryArguments; |
| std::vector<std::string> optionalArguments; |
| VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, false /* addOptional */, mandatoryArguments), false); |
| VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, true /* addOptional */, optionalArguments), false); |
| |
| args.push_back(std::move(clusterName)); |
| args.push_back(std::move(commandName)); |
| if (jsonValue.isMember(kJsonCommandSpecifierKey)) |
| { |
| auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); |
| args.push_back(std::move(commandSpecifierName)); |
| } |
| args.insert(args.end(), mandatoryArguments.begin(), mandatoryArguments.end()); |
| args.insert(args.end(), optionalArguments.begin(), optionalArguments.end()); |
| |
| return true; |
| } |
| |
| bool Commands::DecodeArgumentsFromStringStream(const char * command, std::vector<std::string> & args) |
| { |
| std::string arg; |
| std::stringstream ss(command); |
| while (ss >> std::quoted(arg, '\'')) |
| { |
| args.push_back(std::move(arg)); |
| } |
| |
| return true; |
| } |
| |
| void Commands::ShowHelpText(const char * helpText) |
| { |
| if (helpText == nullptr) |
| { |
| return; |
| } |
| |
| // We leave 82 chars for command/cluster names. The help text starts |
| // two chars further to the right, so there are 80 chars left |
| // for it. |
| if (strlen(helpText) > 80) |
| { |
| // Add "..." at the end to indicate truncation, and only |
| // show the first 77 chars, since that's what will fit. |
| fprintf(stderr, " | - %.77s...|\n", helpText); |
| } |
| else |
| { |
| fprintf(stderr, " | - %-80s|\n", helpText); |
| } |
| } |