pact-plugin-cli 0.2.0

CLI utility for Pact plugins
Documentation
use std::cmp::Ordering;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;

use anyhow::anyhow;
use comfy_table::presets::UTF8_FULL;
use comfy_table::Table;
use itertools::Itertools;
use tracing::trace;

use pact_plugin_driver::plugin_models::PactPluginManifest;
use pact_plugin_driver::repository::fetch_repository_index;

use crate::{ListCommands, resolve_plugin_dir};
use crate::repository::{APP_USER_AGENT, DEFAULT_INDEX};

pub fn list_plugins(command: &ListCommands) -> anyhow::Result<()> {
  match command {
    ListCommands::Installed => list_installed_plugins(),
    ListCommands::Known { show_all_versions } => list_known_plugins(show_all_versions)
  }
}

fn list_known_plugins(show_all_versions: &bool) -> anyhow::Result<()> {
  let runtime = tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .build()?;
  let http_client = reqwest::ClientBuilder::new()
    .user_agent(APP_USER_AGENT)
    .build()?;
  let index = runtime.block_on(fetch_repository_index(&http_client, Some(DEFAULT_INDEX)))?;
  trace!("Result = {:?}", index);
  runtime.shutdown_background();

  let mut table = Table::new();
  if *show_all_versions {
    table
      .load_preset(UTF8_FULL)
      .set_header(vec!["Name", "Version", "Source", "Value"]);

    for entry in index.entries.values() {
      for version in &entry.versions {
        table.add_row(vec![
          entry.name.as_str(),
          version.version.as_str(),
          version.source.name().as_str(),
          version.source.value().as_str()
        ]);
      }
    }
  } else {
    table
      .load_preset(UTF8_FULL)
      .set_header(vec!["Name", "Latest Version", "Num Versions"]);

    for entry in index.entries.values() {
      table.add_row(vec![
        entry.name.as_str(),
        entry.latest_version.as_str(),
        entry.versions.len().to_string().as_str()
      ]);
    }
  }

  println!("{table}");

  Ok(())
}

fn list_installed_plugins() -> anyhow::Result<()> {
  let mut table = Table::new();
  table
    .load_preset(UTF8_FULL)
    .set_header(vec!["Name", "Version", "Interface Version", "Directory", "Status"]);

  for (manifest, _, status) in plugin_list()?.iter().sorted_by(manifest_sort_fn) {
    table.add_row(vec![
      manifest.name.as_str(),
      manifest.version.as_str(),
      manifest.plugin_interface_version.to_string().as_str(),
      manifest.plugin_dir.to_string().as_str(),
      if *status { "enabled" } else { "disabled" }
    ]);
  }

  println!("{table}");

  Ok(())
}

pub fn plugin_list() -> anyhow::Result<Vec<(PactPluginManifest, PathBuf, bool)>> {
  let (_, plugin_dir) = resolve_plugin_dir();
  let dir = PathBuf::from(plugin_dir);
  if dir.exists() {
    let mut plugins = vec![];
    for entry in fs::read_dir(dir)? {
      let path = entry?.path();
      if path.is_dir() {
        let manifest_file = path.join("pact-plugin.json");
        if manifest_file.exists() && manifest_file.is_file() {
          let file = File::open(manifest_file.clone())?;
          let reader = BufReader::new(file);
          let manifest: PactPluginManifest = serde_json::from_reader(reader)?;
          plugins.push((PactPluginManifest {
            plugin_dir: path.display().to_string(),
            ..manifest
          }, manifest_file.clone(), true));
        } else {
          let manifest_file = path.join("pact-plugin.json.disabled");
          if manifest_file.exists() && manifest_file.is_file() {
            let file = File::open(manifest_file.clone())?;
            let reader = BufReader::new(file);
            let manifest: PactPluginManifest = serde_json::from_reader(reader)?;
            plugins.push((PactPluginManifest {
              plugin_dir: path.display().to_string(),
              ..manifest
            }, manifest_file.clone(), false));
          }
        }
      }
    }
    Ok(plugins)
  } else {
    Err(anyhow!("Plugin directory '{}' does not exist!", dir.display()))
  }
}

fn manifest_sort_fn(a: &&(PactPluginManifest, PathBuf, bool), b: &&(PactPluginManifest, PathBuf, bool)) -> Ordering {
  if a.0.name == b.0.name {
    a.0.version.cmp(&b.0.version)
  } else {
    a.0.name.cmp(&b.0.name)
  }
}