Skip to content

Commit

Permalink
feat: add envs command
Browse files Browse the repository at this point in the history
  • Loading branch information
ysndr committed Apr 17, 2024
1 parent f8b315c commit d680066
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 0 deletions.
182 changes: 182 additions & 0 deletions cli/flox/src/commands/envs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use std::collections::BTreeSet;
use std::fmt::Display;
use std::path::Path;

use anyhow::Result;
use bpaf::Bpaf;
use crossterm::style::Stylize;
use flox_rust_sdk::flox::Flox;
use flox_rust_sdk::models::env_registry::{
env_registry_path,
read_environment_registry,
EnvRegistry,
};
use flox_rust_sdk::models::environment::DotFlox;
use serde_json::json;
use tracing::instrument;

use super::UninitializedEnvironment;
use crate::commands::activated_environments;
use crate::subcommand_metric;
use crate::utils::message;

#[derive(Bpaf, Debug, Clone)]
pub struct Envs {
#[bpaf(long)]
active: bool,
#[bpaf(long)]
json: bool,
}

impl Envs {
/// List all environments
///
/// If `--json` is passed, dispatch to [Self::handle_json]
///
/// If `--active` is passed, print only the active environments
/// Always prints headers and formats the output.
#[instrument(name = "envs")]
pub fn handle(self, flox: Flox) -> Result<()> {
subcommand_metric!("envs");

let active = activated_environments();

if self.json {
return self.handle_json(flox);
}

if self.active {
if active.last_active().is_none() {
message::plain("No active environments");
return Ok(());
}

message::plain("Active environments:");
println!("{}", DisplayEnvironments::new(active.iter(), true));
return Ok(());
}

let env_registry = read_environment_registry(env_registry_path(&flox))?.unwrap_or_default();
let registered = get_registered_environments(&env_registry);
let inactive = get_inactive_environments(registered, active.iter())?;

if active.iter().next().is_none() && inactive.is_empty() {
message::plain("No environments known to Flox");
}

if active.iter().next().is_some() {
message::plain("Active environments:");
println!("{}", DisplayEnvironments::new(active.iter(), true));
}

if !inactive.is_empty() {
message::plain("Inactive environments:");
println!("{}", DisplayEnvironments::new(inactive.iter(), false));
}

Ok(())
}

/// Print the list of environments in JSON format
fn handle_json(&self, flox: Flox) -> Result<()> {
let active = activated_environments();

if self.active {
println!("{:#}", json!(active));
return Ok(());
}

let env_registry = read_environment_registry(env_registry_path(&flox))?.unwrap_or_default();
let registered = get_registered_environments(&env_registry);
let inactive = get_inactive_environments(registered, active.iter())?;

println!(
"{:#}",
json!({
"active": active,
"inactive": inactive,
})
);

Ok(())
}
}

struct DisplayEnvironments<'a> {
envs: Vec<&'a UninitializedEnvironment>,
format_active: bool,
}

impl<'a> DisplayEnvironments<'a> {
fn new(
envs: impl IntoIterator<Item = &'a UninitializedEnvironment>,
format_active: bool,
) -> Self {
Self {
envs: envs.into_iter().collect(),
format_active,
}
}
}

impl<'a> Display for DisplayEnvironments<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let widest = self
.envs
.iter()
.map(|env| env.name().as_ref().len())
.max()
.unwrap_or(0);

let mut envs = self.envs.iter();

if self.format_active {
let Some(first) = envs.next() else {
return Ok(());
};
let first_formatted =
format!("{:<widest$} {}", first.name(), format_path(first.path())).bold();
writeln!(f, "{first_formatted}")?;
}

for env in envs {
write!(f, "{:<widest$} {}", env.name(), format_path(env.path()))?;
}

Ok(())
}
}

fn format_path(path: Option<&Path>) -> String {
path.map(|p| p.display().to_string())
.unwrap_or_else(|| "(remote)".to_string())
}

fn get_registered_environments(
registry: &EnvRegistry,
) -> impl Iterator<Item = UninitializedEnvironment> + '_ {
registry.entries.iter().filter_map(|entry| {
Some(UninitializedEnvironment::DotFlox(DotFlox {
path: entry.path.clone(),
pointer: entry.latest_env()?.pointer.clone(),
}))
})
}

/// Get the list of environments that are not active
fn get_inactive_environments<'a>(
available: impl IntoIterator<Item = UninitializedEnvironment>,
active: impl IntoIterator<Item = &'a UninitializedEnvironment>,
) -> Result<BTreeSet<UninitializedEnvironment>> {
// let active = activated_environments();

let inactive = {
let mut available = BTreeSet::from_iter(available);
for active in active {
available.remove(active);
}
available
};

Ok(inactive)
}
4 changes: 4 additions & 0 deletions cli/flox/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod auth;
mod containerize;
mod delete;
mod edit;
mod envs;
mod general;
mod init;
mod install;
Expand Down Expand Up @@ -719,6 +720,9 @@ enum AdditionalCommands {
/// View and set configuration options
#[bpaf(command, hide, footer("Run 'man flox-config' for more details."))]
Config(#[bpaf(external(general::config_args))] general::ConfigArgs),

#[bpaf(command, hide, footer("Run 'man flox-envs' for more details."))]
Envs(#[bpaf(external(envs::envs))] envs::Envs),
}

impl AdditionalCommands {
Expand Down

0 comments on commit d680066

Please sign in to comment.