blob: 37af4062d46217a67ec5fe8c6b158e566e2d77a9 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/console/verbs_settings.h"
#include <ctype.h>
#include <algorithm>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/execution_scope.h"
#include "src/developer/debug/zxdb/client/filter.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/adapters.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_parser.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/format_settings.h"
#include "src/developer/debug/zxdb/console/input_location_parser.h"
#include "src/developer/debug/zxdb/console/nouns.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/string_util.h"
#include "src/developer/debug/zxdb/console/verbs.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kValueOnlySwitch = 1;
// TODO(brettw) Some of this is very repetitive. The Check*() functions and the big block in
// GetSettingContext() duplicate getting scopes and checking stuff.
//
// It would be nice if this was expressed in more of a table form to eliminate this code. The
// Level enum could also be removed if the setting store description could be added to the
// SettingContext.
// Struct to represents all the context needed to correctly reason about the settings commands.
struct SettingContext {
enum class Level {
kGlobal,
kTarget,
kThread,
kBreakpoint,
kFilter,
};
// The SettingStore we're using. This will either be the one the user specified or the implicit
// one deteched for the value. It will be null if the name is empty and there was no explicitly
// specified one.
SettingStore* store = nullptr;
// Set when the user has explicitly set the setting store ("thread 1 set ..."). False means the
// store was determined implicitly based on the name.
bool explicit_store = false;
std::string name;
SettingValue value;
// At what level the setting was applied.
Level level = Level::kGlobal;
// What kind of operation this is for set commands.
ParsedSetCommand::Operation op = ParsedSetCommand::kAssign;
};
// Fixes up user typing for a setting name by lower-casing.
std::string CanonicalizeSettingName(const std::string& input) {
std::string output;
output.reserve(input.size());
for (char c : input)
output.push_back(tolower(c));
return output;
}
bool CheckGlobal(ConsoleContext* context, const Command& cmd, const std::string& setting_name,
SettingContext* out) {
if (!context->session()->system().settings().schema()->HasSetting(setting_name))
return false;
out->store = &context->session()->system().settings();
out->level = SettingContext::Level::kGlobal;
return true;
}
bool CheckTarget(ConsoleContext* context, const Command& cmd, const std::string& setting_name,
SettingContext* out) {
if (!cmd.target()->settings().schema()->HasSetting(setting_name))
return false;
out->store = &cmd.target()->settings();
out->level = SettingContext::Level::kTarget;
return true;
}
bool CheckThread(ConsoleContext* context, const Command& cmd, const std::string& setting_name,
SettingContext* out) {
if (!cmd.thread() || !cmd.thread()->settings().schema()->HasSetting(setting_name))
return false;
out->store = &cmd.thread()->settings();
out->level = SettingContext::Level::kThread;
return true;
}
// Applies the hierarchical rules for getting/setting a setting and fills the give SettingContext.
// It takes into account the noun overrides.
Err GetSettingContext(ConsoleContext* context, const Command& cmd, const std::string& setting_name,
SettingContext* out) {
if (!cmd.target())
return Err("No process found. Please file a bug with a repro.");
out->name = CanonicalizeSettingName(setting_name);
// Handle noun overrides for getting/setting on specific objects.
// TODO(brettw) see above for TODO about reducing this duplication.
if (cmd.HasNoun(Noun::kThread)) {
out->store = cmd.thread() ? &cmd.thread()->settings() : nullptr;
out->explicit_store = true;
out->level = SettingContext::Level::kThread;
} else if (cmd.HasNoun(Noun::kProcess)) {
out->store = &cmd.target()->settings();
out->explicit_store = true;
out->level = SettingContext::Level::kTarget;
} else if (cmd.HasNoun(Noun::kGlobal)) {
out->store = &context->session()->system().settings();
out->explicit_store = true;
out->level = SettingContext::Level::kGlobal;
} else if (cmd.HasNoun(Noun::kBreakpoint)) {
out->store = cmd.breakpoint() ? &cmd.breakpoint()->settings() : nullptr;
out->explicit_store = true;
out->level = SettingContext::Level::kBreakpoint;
} else if (cmd.HasNoun(Noun::kFilter)) {
out->store = cmd.filter() ? &cmd.filter()->settings() : nullptr;
out->explicit_store = true;
out->level = SettingContext::Level::kFilter;
}
// When no name is specified, there's no implicit setting store to look up.
if (out->name.empty())
return Err();
if (out->store) {
// Found an explicitly requested setting store.
if (cmd.verb() == Verb::kSet) {
// Use the generic definition from the schama (if any).
if (const SettingSchema::Record* record = out->store->schema()->GetSetting(out->name))
out->value = record->default_value;
} else if (cmd.verb() == Verb::kGet) {
// Use the specific value from the store.
out->value = out->store->GetValue(out->name);
}
return Err();
}
// Didn't found an explicit specified store, so lookup in the current context. Since the
// settings can be duplicated on different levels, we need to search in the order that makes
// sense for the command.
out->explicit_store = false;
// This array encodes the order we search from the most global to most specific store. This is
// not the same as the SettingStore fallback because "set" searches from global->specific ("get"
// does the reverse).
using CheckFunction =
bool (*)(ConsoleContext*, const Command&, const std::string&, SettingContext*);
const CheckFunction kGlobalToSpecific[] = {
&CheckGlobal,
&CheckTarget,
&CheckThread,
};
if (cmd.verb() == Verb::kSet) {
// When setting, choose the most global context the setting can apply to.
for (const auto cur : kGlobalToSpecific) {
if (cur(context, cmd, out->name, out)) {
// Just get the default value so the type is set properly.
out->value = out->store->schema()->GetSetting(out->name)->default_value;
return Err();
}
}
} else if (cmd.verb() == Verb::kGet) {
// When getting, choose the most specific context the setting can apply to.
for (const auto cur : Reversed(kGlobalToSpecific)) {
if (cur(context, cmd, out->name, out)) {
// Getting additionally requires that the setting be non-null. We want to find the first
// one that might apply.
out->value = out->store->GetValue(out->name);
if (!out->value.is_null())
return Err();
}
}
}
return Err("Could not find setting \"%s\".", out->name.data());
}
// get ---------------------------------------------------------------------------------------------
const char kGetShortHelp[] = "get: Prints setting values.";
const char kGetHelp[] =
R"([ <object> ] get [ --value-only ] [ <setting-name> ]
Prints setting values.
Settings are hierarchical for processes and threads. This means that
thread settings will inherit from the process if they don't exist on
the thread, and process settings will inherit from the global settings
if they don't exist on the process.
get
By itself, "get" will show the global settings and the ones for the
current thread and process.
Example:
get
get <setting-name>
Prints the value of the named setting. It will search the current
thread, followed by the current process, and then global settings.
Examples:
get build-dirs
get show-stdout
<object> get
Prints all settings from a specific object.
Examples:
process 1 thread 1 get
thread get // Uses the current thread.
filter 2 get
global get // Explicitly requests only global values
// (processes and threads may override).
<object> get <setting-name>
Prints a named setting from a specific object.
Examples:
filter 2 get pattern
filter get pattern // Uses the current filter.
Options
--value-only
Displays only the value of the setting without any help text or
formatting.
)";
// Outputs the general settings in the following order: System -> Target -> Thread
Err CompleteSettingsToOutput(const Command& cmd, ConsoleContext* context, OutputBuffer* out) {
out->Append(OutputBuffer(Syntax::kHeading, "Global\n"));
out->Append(FormatSettingStore(context, context->session()->system().settings()));
out->Append("\n");
if (Target* target = cmd.target(); target && !target->settings().schema()->empty()) {
auto title = fxl::StringPrintf("Process %d\n", context->IdForTarget(target));
out->Append(OutputBuffer(Syntax::kHeading, std::move(title)));
out->Append(FormatSettingStore(context, target->settings()));
out->Append("\n");
}
if (Thread* thread = cmd.thread(); thread && !thread->settings().schema()->empty()) {
auto title = fxl::StringPrintf("Thread %d\n", context->IdForThread(thread));
out->Append(OutputBuffer(Syntax::kHeading, std::move(title)));
out->Append(FormatSettingStore(context, thread->settings()));
out->Append("\n");
}
return Err();
}
Err DoGet(ConsoleContext* console_context, const Command& cmd) {
std::string input_name;
if (!cmd.args().empty()) {
if (cmd.args().size() > 1)
return Err("Expected only one setting name");
input_name = cmd.args()[0];
}
Err err;
SettingContext setting_context;
if (err = GetSettingContext(console_context, cmd, input_name, &setting_context); err.has_error())
return err;
// Use setting_context.name from here on down instead of input_name because it's canonicalized.
OutputBuffer out;
if (!setting_context.name.empty()) {
// Getting one setting.
if (setting_context.value.is_null()) {
err = Err("Could not find setting %s", setting_context.name.c_str());
} else if (cmd.HasSwitch(kValueOnlySwitch)) {
out = FormatSettingValue(console_context, setting_context.value);
} else {
const SettingSchema::Record* record =
setting_context.store->schema()->GetSetting(setting_context.name);
FX_DCHECK(record); // Should always succeed if GetSettingContext did.
out = FormatSetting(console_context, setting_context.name, record->description,
setting_context.value);
}
} else if (setting_context.explicit_store) {
// Getting all settings on one object. The object may not be valid (e.g. "thread get" when
// there's no current thread).
if (setting_context.store) {
out.Append(FormatSettingStore(console_context, *setting_context.store));
out.Append("\n");
} else {
err = Err("No current object of this type.");
}
} else {
// Implcitly getting all settings, show the general global/process/thread ones.
err = CompleteSettingsToOutput(cmd, console_context, &out);
}
if (err.has_error())
return err;
Console::get()->Output(out);
return Err();
}
// set ---------------------------------------------------------------------------------------------
const char kSetShortHelp[] = "set: Set a setting value.";
const char kSetHelp[] =
R"([ <object> ] set <setting-name> [ <modification-type> ] <value>*
Sets the value of a setting.
See which settings are available, their names and current values with
the "get" command (see "help get").
As a special-case, the syntax "set <setting-name> =" (with no value) will
clear the setting back to its default value. This can also be used for thread-
or process-specific settings to case a fallback to the global value.
Arguments
<object>
The object that the setting is on. If unspecified, it will search
the current thread, process, and then the global settings for a
match to set.
<setting_name>
The setting that will modified. Case-insensitive.
<modification-type>
Operator that indicates how to mutate a list. For non-lists only = (the
default) is supported:
= Replace the current contents (the default).
+= Append the given value to the list.
-= Search for the given value and remove it.
<value>
The value(s) to set, add, or remove. Multiple values for list settings
are whitespace separated. You can "quote" strings to include literal
spaces, and backslash-escape literal quotes.
Setting Types
Each setting has a particular type.
bool
"0", "false" -> false
"1", "true" -> true
integer
Any string convertible to integer.
string
Strings can be "quoted" to include whitespace. Otherwise whitespace is
used as a delimiter.
list
A list of one or more string separated by whitespace. Strings can be
"quoted" to include literal whitespace.
Examples
[zxdb] set show-stdout true
Set global show-stdout = true
[zxdb] pr 3 set vector-format double
Set process 3 vector-format = double
[zxdb] get build-dirs
• /home/me/build
• /home/me/other/out
[zxdb] set build-dirs += /tmp/build
Set global build-dirs =
• /home/me/build
• /home/me/other/out
• /tmp/build
[zxdb] set build-dirs = /other/build/location /other/build2
Set global build-dirs build-dirs =
• /other/build/location
• /other/build2
[zxdb] set build-dirs =
Set global build-dirs = <empty>
)";
Err SetBool(SettingStore* store, const std::string& setting_name, const std::string& value) {
std::optional<bool> bool_val = StringToBool(value);
if (!bool_val)
return Err("%s expects a boolean. See \"help set\" for valid values.", setting_name.data());
store->SetBool(setting_name, *bool_val);
return Err();
}
Err SetInt(SettingStore* store, const std::string& setting_name, const std::string& value) {
int out;
Err err = StringToInt(value, &out);
if (err.has_error()) {
return Err("%s expects a valid int: %s", setting_name.data(), err.msg().data());
}
return store->SetInt(setting_name, out);
}
Err SetList(const SettingContext& setting_context, const std::vector<std::string>& values,
SettingStore* store) {
if (setting_context.op == ParsedSetCommand::kAssign)
return store->SetList(setting_context.name, values);
if (setting_context.op == ParsedSetCommand::kAppend) {
auto list = store->GetList(setting_context.name);
list.insert(list.end(), values.begin(), values.end());
return store->SetList(setting_context.name, list);
}
if (setting_context.op == ParsedSetCommand::kRemove) {
// Search for the elements to remove.
auto list = store->GetList(setting_context.name);
for (const std::string& value : values) {
auto first_to_remove = std::remove(list.begin(), list.end(), value);
if (first_to_remove == list.end()) {
// Item not found.
return Err("Could not find \"" + value + "\" to remove from setting list.");
}
list.erase(first_to_remove, list.end());
}
return store->SetList(setting_context.name, list);
}
FX_NOTREACHED();
return Err();
}
Err SetExecutionScope(ConsoleContext* console_context, const SettingContext& setting_context,
const std::string& scope_str, SettingStore* store) {
ErrOr<ExecutionScope> scope_or = ParseExecutionScope(console_context, scope_str);
if (scope_or.has_error())
return scope_or.err();
return store->SetExecutionScope(setting_context.name, scope_or.value());
}
Err SetInputLocations(const Frame* optional_frame, const SettingContext& setting_context,
const std::string& input, SettingStore* store) {
std::vector<InputLocation> locs;
if (Err err = ParseLocalInputLocation(optional_frame, input, &locs); err.has_error())
return err;
return store->SetInputLocations(setting_context.name, std::move(locs));
}
// Will run the sets against the correct SettingStore:
// |setting_context| represents the required context needed to reason about the command.
// for user feedback.
// |out| is the resultant setting, which is used for user feedback.
Err SetSetting(ConsoleContext* console_context, const Frame* optional_frame,
const SettingContext& setting_context, const std::vector<std::string>& values,
SettingStore* store, SettingValue* out) {
Err err;
if (setting_context.op != ParsedSetCommand::kAssign && !setting_context.value.is_list())
return Err("Appending/removing only works for list options.");
if (values.empty()) {
if (err = store->ClearValue(setting_context.name); err.has_error())
return err;
// When clearing, report the new value is the default reported by the store.
*out = store->GetValue(setting_context.name);
return Err();
}
switch (setting_context.value.type()) {
case SettingType::kBoolean:
err = SetBool(store, setting_context.name, values[0]);
break;
case SettingType::kInteger:
err = SetInt(store, setting_context.name, values[0]);
break;
case SettingType::kString:
err = store->SetString(setting_context.name, values[0]);
break;
case SettingType::kList:
err = SetList(setting_context, values, store);
break;
case SettingType::kExecutionScope:
err = SetExecutionScope(console_context, setting_context, values[0], store);
break;
case SettingType::kInputLocations:
err = SetInputLocations(optional_frame, setting_context, values[0], store);
break;
case SettingType::kNull:
return Err("Could not find setting %s.", setting_context.name.c_str());
}
if (!err.ok())
return err;
*out = store->GetValue(setting_context.name);
return Err();
}
OutputBuffer FormatSetFeedback(ConsoleContext* console_context,
const SettingContext& setting_context,
const std::string& setting_name, const Command& cmd,
const SettingValue& value) {
OutputBuffer out;
std::string message;
switch (setting_context.level) {
case SettingContext::Level::kGlobal:
out.Append("Set global");
break;
case SettingContext::Level::kTarget:
out.Append("Set process ");
out.Append(Syntax::kSpecial, std::to_string(console_context->IdForTarget(cmd.target())));
break;
case SettingContext::Level::kThread:
out.Append("Set process ");
out.Append(Syntax::kSpecial, std::to_string(console_context->IdForTarget(cmd.target())));
out.Append(" thread ");
out.Append(Syntax::kSpecial, std::to_string(console_context->IdForThread(cmd.thread())));
break;
case SettingContext::Level::kBreakpoint:
out.Append("Set breakpoint ");
out.Append(Syntax::kSpecial,
std::to_string(console_context->IdForBreakpoint(cmd.breakpoint())));
break;
case SettingContext::Level::kFilter:
out.Append("Set filter ");
out.Append(Syntax::kSpecial, std::to_string(console_context->IdForFilter(cmd.filter())));
break;
default:
FX_NOTREACHED() << "Should not receive a default setting.";
}
out.Append(" ");
out.Append(Syntax::kVariable, setting_name);
out.Append(" = ");
if (value.is_list() && !value.get_list().empty())
out.Append("\n"); // Put list items on their own lines.
out.Append(FormatSettingShort(console_context, setting_name, value, 2));
return out;
}
Err DoSet(ConsoleContext* console_context, const Command& cmd) {
// The command parser will provide everything as one argument.
if (cmd.args().size() != 1) {
return Err(
"Expected a setting and a new value.\n"
" • Type \"help set\" for usage.\n"
" • Type \"get\" to list all settings and their current values.\n"
" • Type \"get <setting-name>\" for documentation on a setting.");
}
ErrOr<ParsedSetCommand> parsed = ParseSetCommand(cmd.args()[0]);
if (parsed.has_error())
return parsed.err();
// See where this setting would be stored.
SettingContext setting_context;
Err err = GetSettingContext(console_context, cmd, parsed.value().name, &setting_context);
if (err.has_error())
return err;
setting_context.op = parsed.value().op;
// Validate that the operations makes sense.
if (parsed.value().op != ParsedSetCommand::kAssign && !setting_context.value.is_list())
return Err("List modification (+=, -=) used on a non-list option.");
if (parsed.value().values.size() > 1u && !setting_context.value.is_list()) {
// When the value is a non-list, assume input with spaces is a single literal. This allows
// input like:
// bp set scope process 1
// without quoting "process 1" which is much more intuitive. The potentially surprising side
// effect of this rule is that if two things are quoted (triggering this condition), the quotes
// will be literally included in the value, while if only one thing is quoted, the quotes will
// br trimmed. But this edge case is not worth adding extra complexity here.
parsed.value().values.clear();
parsed.value().values.push_back(parsed.value().raw_value);
}
SettingValue out_value; // Used for showing the new value.
err = SetSetting(console_context, cmd.frame(), setting_context, parsed.value().values,
setting_context.store, &out_value);
if (!err.ok())
return err;
// Be sure to use the canonicalized name in the setting_context for the output.
Console::get()->Output(
FormatSetFeedback(console_context, setting_context, setting_context.name, cmd, out_value));
return Err();
}
// Equivalend to isalpha with no C local complexity.
bool IsSettingNameAlpha(const char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
// Setting names are only letters and hyphens.
//
// The "name" is annoying to parse because it can't end in a "-" and we have to differentiate that
// from a "name-=..." command which requires lookahead. As a result, this takes the full string
// and the index of the character to check.
bool IsSettingNameChar(const std::string& input, size_t i) {
if (IsSettingNameAlpha(input[i]))
return true; // Normal letter.
if (input[i] == '-') {
// Disambiguate internal hyphens from "-=".
if (i + 1 < input.size() && IsSettingNameAlpha(input[i + 1]))
return true; // More name to come.
}
return false;
}
void CompleteSet(const Command& cmd, const std::string& prefix,
std::vector<std::string>* completions) {
// Find the end of the setting name. The argument is a whole expression as one string, so if we're
// past the setting name we're past the part we know how to complete.
size_t cur;
for (cur = 0; cur < prefix.size() && IsSettingNameChar(prefix, cur); ++cur)
;
if (cur == prefix.size()) {
for (const auto& [name, _] : System::GetSchema()->settings()) {
if (!name.compare(0, prefix.size(), prefix)) {
completions->push_back(name);
}
}
for (const auto& [name, _] : Target::GetSchema()->settings()) {
if (!name.compare(0, prefix.size(), prefix)) {
completions->push_back(name);
}
}
for (const auto& [name, _] : Thread::GetSchema()->settings()) {
if (!name.compare(0, prefix.size(), prefix)) {
completions->push_back(name);
}
}
}
// TODO: We need to refactor parsing a bit if we want to complete options.
}
} // namespace
// Grammar:
// command := <name> [ <whitespace> ] [ <operator> <whitespace> ] [ <value> * ]
// name := ('A' - 'Z') | ('a' - 'z') | '-' // See IsSettingNameChar() for qualifications.
// operator := "=" | '+=' | '-='
//
// A "value" is a possibly quoted string parsed as a command token. The whole thing can't be parsed
// as a command because we want to handle "name value" as well as "name = value" and "name=value".
ErrOr<ParsedSetCommand> ParseSetCommand(const std::string& input) {
ParsedSetCommand result;
size_t cur = 0;
// Name.
while (cur < input.size() && IsSettingNameChar(input, cur)) {
result.name.push_back(input[cur]);
++cur;
}
if (result.name.empty())
return Err("Expected a setting name to set.");
// Require whitespace or an operator after the name to prevent "name$" from being valid.
bool has_name_terminator = false;
// Whitespace.
while (cur < input.size() && isspace(input[cur])) {
has_name_terminator = true;
++cur;
}
// Special-case the error message for no value to be more helpful. To clear the value we require
// "=" so the command is more explicit ("set foo" may be typed by somebody who doesn't know what
// they're doing).
if (cur == input.size())
return Err("Expecting a value to set. Use \"set setting-name setting-value\".");
// Optional operator.
if (cur < input.size() && input[cur] == '=') {
has_name_terminator = true;
result.op = ParsedSetCommand::kAssign;
++cur;
} else if (cur < input.size() - 1 && input[cur] == '+' && input[cur + 1] == '=') {
has_name_terminator = true;
result.op = ParsedSetCommand::kAppend;
cur += 2;
} else if (cur < input.size() - 1 && input[cur] == '-' && input[cur + 1] == '=') {
has_name_terminator = true;
result.op = ParsedSetCommand::kRemove;
cur += 2;
}
// Check for the name terminator.
if (!has_name_terminator)
return Err("Invalid setting name.");
// Whitespace.
while (cur < input.size() && isspace(input[cur]))
++cur;
// Save the original raw value (trimming whitespace from the right).
result.raw_value = input.substr(cur);
while (!result.raw_value.empty() && isspace(result.raw_value.back()))
result.raw_value.resize(result.raw_value.size() - 1);
// Value(s) parsed as a command token. This handles the various types of escaping and quoting
// supported by the interactive command line.
//
// The value can be omitted for sets which clears the value to the default.
std::vector<CommandToken> value_tokens;
if (Err err = TokenizeCommand(input.substr(cur), &value_tokens); err.has_error())
return err;
if (result.op != ParsedSetCommand::kAssign && value_tokens.empty())
return Err("Expected a value to add/remove.");
for (const auto& token : value_tokens)
result.values.push_back(std::move(token.str));
return result;
}
ErrOr<ExecutionScope> ParseExecutionScope(ConsoleContext* console_context,
const std::string& input) {
// Use the command parser to get the scope.
Command parsed;
if (Err err = ParseCommand(input, &parsed); err.has_error())
return err;
const char kScopeHelp[] =
"Scope not valid, expecting \"global\", \"process\", or \"thread\" only.";
// We accept the "global", "process", and "thread" nouns, and no verbs or switches.
if (parsed.verb() != Verb::kNone)
return Err(kScopeHelp);
if (parsed.ValidateNouns({Noun::kGlobal, Noun::kProcess, Noun::kThread}).has_error())
return Err(kScopeHelp);
if (!parsed.args().empty())
return Err(kScopeHelp);
// Convert the nouns to objects.
if (Err err = console_context->FillOutCommand(&parsed); err.has_error())
return err;
// Valid, convert to scope based on what nouns were specified. The thread or process could have
// failed to resolve to objects if there is no current one.
if (parsed.HasNoun(Noun::kThread)) {
if (!parsed.thread())
return Err("There is no current thread to use for the scope.");
return ExecutionScope(parsed.thread());
}
if (parsed.HasNoun(Noun::kProcess)) {
if (!parsed.target())
return Err("There is no current process to use for the scope.");
return ExecutionScope(parsed.target());
}
return ExecutionScope();
}
void AppendSettingsVerbs(std::map<Verb, VerbRecord>* verbs) {
VerbRecord get(&DoGet, {"get"}, kGetShortHelp, kGetHelp, CommandGroup::kGeneral);
get.switches.emplace_back(kValueOnlySwitch, false, "value-only");
(*verbs)[Verb::kGet] = std::move(get);
VerbRecord set(&DoSet, &CompleteSet, {"set"}, kSetShortHelp, kSetHelp, CommandGroup::kGeneral);
set.param_type = VerbRecord::kOneParam;
(*verbs)[Verb::kSet] = std::move(set);
}
} // namespace zxdb