Skip to content

Commit

Permalink
refactor: load extension manifests in main
Browse files Browse the repository at this point in the history
  • Loading branch information
ericswanson-dfinity committed Aug 16, 2024
1 parent 7326306 commit afc4fb6
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 119 deletions.
9 changes: 9 additions & 0 deletions src/dfx-core/src/error/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ pub enum ListInstalledExtensionsError {
ExtensionsDirectoryIsNotReadable(#[from] ReadDirError),
}

#[derive(Error, Debug)]
pub enum LoadExtensionManifestsError {
#[error(transparent)]
ListInstalledExtensions(#[from] ListInstalledExtensionsError),

#[error(transparent)]
LoadExtensionManifest(#[from] LoadExtensionManifestError),
}

#[derive(Error, Debug)]
pub enum ConvertExtensionSubcommandIntoClapArgError {
#[error("Extension's subcommand argument '{0}' is missing description.")]
Expand Down
31 changes: 31 additions & 0 deletions src/dfx-core/src/extension/installed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::error::extension::ConvertExtensionIntoClapCommandError;
use crate::extension::manifest::ExtensionManifest;
use crate::extension::ExtensionName;
use clap::Command;
use std::collections::HashMap;

pub type InstalledExtensionList = Vec<ExtensionName>;
pub struct InstalledExtensionManifests(pub HashMap<ExtensionName, ExtensionManifest>);

impl InstalledExtensionManifests {
pub fn as_clap_commands(&self) -> Result<Vec<Command>, ConvertExtensionIntoClapCommandError> {
let commands = self
.0
.values()
.map(|manifest| {
manifest.into_clap_commands().map(|subcommands| {
Command::new(&manifest.name)
.allow_missing_positional(false) // don't accept unknown options
.allow_external_subcommands(false) // don't accept unknown subcommands
.about(&manifest.summary)
.subcommands(subcommands)
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(commands)
}

pub fn contains(&self, extension: &str) -> bool {
self.0.contains_key(extension)
}
}
37 changes: 0 additions & 37 deletions src/dfx-core/src/extension/manager/list.rs

This file was deleted.

52 changes: 48 additions & 4 deletions src/dfx-core/src/extension/manager/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use crate::config::cache::get_cache_path_for_version;
use crate::error::extension::{GetExtensionBinaryError, NewExtensionManagerError};
use crate::error::extension::{
GetExtensionBinaryError, ListInstalledExtensionsError, LoadExtensionManifestsError,
NewExtensionManagerError,
};
use crate::extension::{
installed::{InstalledExtensionList, InstalledExtensionManifests},
manifest::ExtensionManifest,
};
pub use install::InstallOutcome;
use semver::Version;
use std::collections::HashMap;
use std::path::PathBuf;

mod execute;
mod install;
mod list;
mod uninstall;

pub use install::InstallOutcome;

pub struct ExtensionManager {
pub dir: PathBuf,
pub dfx_version: Version,
Expand Down Expand Up @@ -52,4 +58,42 @@ impl ExtensionManager {
pub fn is_extension_installed(&self, extension_name: &str) -> bool {
self.get_extension_directory(extension_name).exists()
}

pub fn list_installed_extensions(
&self,
) -> Result<InstalledExtensionList, ListInstalledExtensionsError> {
if !self.dir.exists() {
return Ok(vec![]);
}
let dir_content = crate::fs::read_dir(&self.dir)?;

let extensions = dir_content
.filter_map(|v| {
let dir_entry = v.ok()?;
if dir_entry.file_type().map_or(false, |e| e.is_dir())
&& !dir_entry.file_name().to_str()?.starts_with(".tmp")
{
let name = dir_entry.file_name().to_string_lossy().to_string();
Some(name)
} else {
None
}
})
.collect();
Ok(extensions)
}

pub fn load_installed_extension_manifests(
&self,
) -> Result<InstalledExtensionManifests, LoadExtensionManifestsError> {
let manifests = self
.list_installed_extensions()?
.into_iter()
.map(|name| {
ExtensionManifest::load(&name, &self.dir)
.map(|manifest| (manifest.name.clone(), manifest))
})
.collect::<Result<HashMap<_, _>, _>>()?;
Ok(InstalledExtensionManifests(manifests))
}
}
59 changes: 30 additions & 29 deletions src/dfx-core/src/extension/manifest/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ impl ExtensionManifest {
}

pub fn into_clap_commands(
self,
&self,
) -> Result<Vec<clap::Command>, ConvertExtensionSubcommandIntoClapCommandError> {
self.subcommands
.unwrap_or_default()
.0
.into_iter()
.map(|(subcmd, opts)| opts.into_clap_command(subcmd))
.collect::<Result<Vec<_>, _>>()
if let Some(sc) = self.subcommands.as_ref() {
sc.0.iter()
.map(|(subcmd, opts)| opts.as_clap_command(subcmd))
.collect::<Result<Vec<_>, _>>()
} else {
Ok(vec![])
}
}
}

Expand Down Expand Up @@ -206,31 +207,31 @@ impl Serialize for ArgNumberOfValues {
}

impl ExtensionSubcommandArgOpts {
pub fn into_clap_arg(
self,
name: String,
pub fn as_clap_arg(
&self,
name: &str,
) -> Result<clap::Arg, ConvertExtensionSubcommandIntoClapArgError> {
let mut arg = clap::Arg::new(name.clone());
if let Some(about) = self.about {
let mut arg = clap::Arg::new(name.to_string());
if let Some(about) = &self.about {
arg = arg.help(about);
} else {
return Err(ConvertExtensionSubcommandIntoClapArgError::ExtensionSubcommandArgMissingDescription(
name,
name.to_string(),
));
}
if let Some(l) = self.long {
if let Some(l) = &self.long {
arg = arg.long(l);
}
if let Some(s) = self.short {
arg = arg.short(s);
if let Some(s) = &self.short {
arg = arg.short(*s);
}
#[allow(deprecated)]
if self.multiple {
arg = arg.num_args(0..);
} else {
arg = match self.values {
ArgNumberOfValues::Number(n) => arg.num_args(n),
ArgNumberOfValues::Range(r) => arg.num_args(r),
arg = match &self.values {
ArgNumberOfValues::Number(n) => arg.num_args(*n),
ArgNumberOfValues::Range(r) => arg.num_args(r.clone()),
ArgNumberOfValues::Unlimited => arg.num_args(0..),
};
}
Expand All @@ -243,25 +244,25 @@ impl ExtensionSubcommandArgOpts {
}

impl ExtensionSubcommandOpts {
pub fn into_clap_command(
self,
name: String,
pub fn as_clap_command(
&self,
name: &str,
) -> Result<clap::Command, ConvertExtensionSubcommandIntoClapCommandError> {
let mut cmd = clap::Command::new(name);
let mut cmd = clap::Command::new(name.to_string());

if let Some(about) = self.about {
if let Some(about) = &self.about {
cmd = cmd.about(about);
}

if let Some(args) = self.args {
if let Some(args) = &self.args {
for (name, opts) in args {
cmd = cmd.arg(opts.into_clap_arg(name)?);
cmd = cmd.arg(opts.as_clap_arg(name)?);
}
}

if let Some(subcommands) = self.subcommands {
for (name, subcommand) in subcommands.0 {
cmd = cmd.subcommand(subcommand.into_clap_command(name)?);
if let Some(subcommands) = &self.subcommands {
for (name, subcommand) in &subcommands.0 {
cmd = cmd.subcommand(subcommand.as_clap_command(name)?);
}
}

Expand Down
44 changes: 2 additions & 42 deletions src/dfx-core/src/extension/mod.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
pub mod catalog;
pub mod installed;
pub mod manager;
pub mod manifest;
pub mod url;

use crate::error::extension::ConvertExtensionIntoClapCommandError;
use crate::extension::{manager::ExtensionManager, manifest::ExtensionManifest};
use clap::Command;
use std::{
fmt::{Display, Formatter},
fs::DirEntry,
};

#[derive(Debug, Default)]
pub struct Extension {
pub name: String,
}

impl From<DirEntry> for Extension {
fn from(entry: DirEntry) -> Self {
let name = entry.file_name().to_string_lossy().to_string();
Extension { name }
}
}

impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}

impl Extension {
pub fn into_clap_command(
self,
manager: &ExtensionManager,
) -> Result<Command, ConvertExtensionIntoClapCommandError> {
let manifest = ExtensionManifest::load(&self.name, &manager.dir)?;
let cmd = Command::new(&self.name)
// don't accept unknown options
.allow_missing_positional(false)
// don't accept unknown subcommands
.allow_external_subcommands(false)
.about(&manifest.summary)
.subcommands(manifest.into_clap_commands()?);
Ok(cmd)
}
}
type ExtensionName = String;
10 changes: 7 additions & 3 deletions src/dfx/src/commands/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ pub struct CompletionOpts {

pub fn exec(env: &dyn Environment, opts: CompletionOpts) -> DfxResult {
let em = env.get_extension_manager();
let installed_extensions = em.installed_extensions_as_clap_commands()?;
let mut command = if installed_extensions.is_empty() {

let commands = em
.load_installed_extension_manifests()?
.as_clap_commands()?;

let mut command = if commands.is_empty() {
CliOpts::command()
} else {
CliOpts::command_for_update().subcommands(&installed_extensions)
CliOpts::command_for_update().subcommands(&commands)
};

generate(
Expand Down
12 changes: 8 additions & 4 deletions src/dfx/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::lib::error::DfxResult;
use crate::lib::logger::{create_root_logger, LoggingMode};
use anyhow::Error;
use clap::{ArgAction, CommandFactory, Parser};
use dfx_core::extension::installed::InstalledExtensionManifests;
use dfx_core::extension::manager::ExtensionManager;
use std::collections::HashMap;
use std::ffi::OsString;
Expand Down Expand Up @@ -110,18 +111,20 @@ fn print_error_and_diagnosis(err: Error, error_diagnosis: Diagnosis) {
}
}

fn get_args_altered_for_extension_run(em: &ExtensionManager) -> DfxResult<Vec<OsString>> {
fn get_args_altered_for_extension_run(
installed: &InstalledExtensionManifests,
) -> DfxResult<Vec<OsString>> {
let mut args = std::env::args_os().collect::<Vec<OsString>>();

let installed_extensions = em.installed_extensions_as_clap_commands()?;
let installed_extensions = installed.as_clap_commands()?;
if !installed_extensions.is_empty() {
let mut app = CliOpts::command_for_update().subcommands(&installed_extensions);
sort_clap_commands(&mut app);
// here clap will display the help message if no subcommand was provided...
let app = app.get_matches();
// ...therefore we can safely unwrap here because we know a subcommand was provided
let subcmd = app.subcommand().unwrap().0;
if em.is_extension_installed(subcmd) {
if installed.contains(subcmd) {
let idx = args.iter().position(|arg| arg == subcmd).unwrap();
args.splice(idx..idx, ["extension", "run"].iter().map(OsString::from));
}
Expand All @@ -131,8 +134,9 @@ fn get_args_altered_for_extension_run(em: &ExtensionManager) -> DfxResult<Vec<Os

fn inner_main() -> DfxResult {
let em = ExtensionManager::new(dfx_version())?;
let installed_extension_manifests = em.load_installed_extension_manifests()?;

let args = get_args_altered_for_extension_run(&em)?;
let args = get_args_altered_for_extension_run(&installed_extension_manifests)?;

let cli_opts = CliOpts::parse_from(args);

Expand Down

0 comments on commit afc4fb6

Please sign in to comment.