blob: bb07b38bf953a65b175f80303b6a2dac7f86384f [file] [log] [blame] [edit]
// Copyright 2022 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.
use std::{fmt::Write, path::PathBuf, process::ExitStatus};
use crate::{Error, Ffx, FfxCommandLine};
use anyhow::Result;
use async_trait::async_trait;
use ffx_config::EnvironmentContext;
/// Where the command was discovered
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FfxToolSource {
/// built directly into the executable
BuiltIn,
/// discovered in a development tree or workspace tree
Workspace,
/// discovered in the currently active SDK
Sdk,
}
/// Information about a tool for use in help output
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct FfxToolInfo {
/// Where the tool was found
pub source: FfxToolSource,
/// The short name of the tool
pub name: String,
/// A longer one-line description of the functionality of the tool
pub description: String,
/// A path to the executable for the tool, if there is one
pub path: Option<PathBuf>,
}
impl FfxToolInfo {
fn write_description(&self, out: &mut String) {
crate::describe::write_description(out, &self.name, &self.description)
}
}
impl From<&argh::CommandInfo> for FfxToolInfo {
fn from(info: &argh::CommandInfo) -> Self {
let source = FfxToolSource::BuiltIn;
let name = info.name.to_owned();
let description = info.description.to_owned();
let path = None;
FfxToolInfo { source, name, description, path }
}
}
#[async_trait(?Send)]
pub trait ToolRunner {
fn forces_stdout_log(&self) -> bool;
async fn run(self: Box<Self>) -> Result<ExitStatus, Error>;
}
/// Implements discovering and loading the subtools a particular ffx binary
/// is capable of running.
pub trait ToolSuite: Sized {
/// Initializes the tool suite from the ffx invocation's environment.
fn from_env(app: &Ffx, env: &EnvironmentContext) -> Result<Self, Error>;
/// Lists commands that should be available no matter how and where this tool
/// is invoked.
fn global_command_list() -> &'static [&'static argh::CommandInfo];
/// Lists all commands reachable from the current context. Defaults to just
/// the same set of commands as in [`Self::global_command_list`].
fn command_list(&self) -> Vec<FfxToolInfo> {
Self::global_command_list().iter().copied().map(|cmd| cmd.into()).collect()
}
/// Parses the given command line information into a runnable command
/// object.
fn try_from_args(
&self,
cmd: &FfxCommandLine,
args: &[&str],
) -> Result<Option<Box<dyn ToolRunner>>, Error>;
/// Parses the given command line into a command, then returns a redacted string usable in
/// analytics. See [`FromArgs::redact_arg_values`] for the kind of output to expect.
fn redact_arg_values(&self, cmd: &FfxCommandLine, args: &[&str]) -> Result<Vec<String>, Error>;
/// Parses the given command line information into a runnable command
/// object, exiting and printing the early exit output if help is requested
/// or an error occurs.
fn from_args(&self, cmd: &FfxCommandLine, args: &[&str]) -> Option<Box<dyn ToolRunner>> {
self.try_from_args(cmd, args).unwrap_or_else(|early_exit| {
print!("{}", early_exit);
std::process::exit(early_exit.exit_code())
})
}
/// Prints out a list of the commands this suite has available
fn print_command_list(&self, w: &mut impl Write) -> Result<(), std::fmt::Error> {
let mut built_in = None;
let mut workspace = None;
let mut sdk = None;
for cmd in &self.command_list() {
use FfxToolSource::*;
let kind = match cmd.source {
BuiltIn => built_in.get_or_insert_with(String::new),
Workspace => workspace.get_or_insert_with(String::new),
Sdk => sdk.get_or_insert_with(String::new),
};
cmd.write_description(kind);
}
if let Some(built_in) = built_in {
writeln!(w, "Built-in Commands:\n{built_in}\n")?;
}
if let Some(workspace) = workspace {
writeln!(w, "Workspace Commands:\n{workspace}\n")?;
}
if let Some(sdk) = sdk {
writeln!(w, "SDK Commands:\n{sdk}\n")?;
}
Ok(())
}
/// Finds the given tool by name in the available command list
fn find_tool_by_name(&self, name: &str) -> Option<FfxToolInfo> {
self.command_list().into_iter().find(|cmd| cmd.name == name)
}
}