Skip to content

Commit

Permalink
Merge pull request #212 from ThorstenHans/feat/formatted-output
Browse files Browse the repository at this point in the history
Add support for formatting output of `cloud apps list` and `cloud apps info`
  • Loading branch information
itowlson authored Nov 10, 2024
2 parents a729017 + 3a3b972 commit db9f89c
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 33 deletions.
63 changes: 30 additions & 33 deletions src/commands/apps.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::commands::{client_and_app_id, create_cloud_client, CommonArgs};
use crate::commands::{apps_output::AppInfo, client_and_app_id, create_cloud_client, CommonArgs};
use anyhow::{Context, Result};
use clap::Parser;
use cloud::{CloudClientInterface, DEFAULT_APPLIST_PAGE_SIZE};
use cloud_openapi::models::{AppItem, AppItemPage, ValidationStatus};
use cloud_openapi::models::{AppItem, ValidationStatus};

use super::apps_output::{print_app_info, print_app_list, OutputFormat};

#[derive(Parser, Debug)]
#[clap(about = "Manage applications deployed to Fermyon Cloud")]
Expand All @@ -19,6 +21,9 @@ pub enum AppsCommand {
pub struct ListCommand {
#[clap(flatten)]
common: CommonArgs,
/// Desired output format
#[clap(value_enum, long = "format", default_value = "plain")]
format: OutputFormat,
}

#[derive(Parser, Debug)]
Expand All @@ -35,6 +40,9 @@ pub struct InfoCommand {
pub app: String,
#[clap(flatten)]
common: CommonArgs,
/// Desired output format
#[clap(value_enum, long = "format", default_value = "plain")]
format: OutputFormat,
}

impl AppsCommand {
Expand All @@ -51,19 +59,21 @@ impl ListCommand {
pub async fn run(self) -> Result<()> {
let client = create_cloud_client(self.common.deployment_env_id.as_deref()).await?;
let mut app_list_page = client.list_apps(DEFAULT_APPLIST_PAGE_SIZE, None).await?;
if app_list_page.total_items <= 0 {
eprintln!("No applications found");
} else {
print_app_list(&app_list_page);
let mut page_index = 1;
while !app_list_page.is_last_page {
app_list_page = client
.list_apps(DEFAULT_APPLIST_PAGE_SIZE, Some(page_index))
.await?;
print_app_list(&app_list_page);
page_index += 1;
let mut apps: Vec<String> = vec![];
let mut page_index = 1;
for app in app_list_page.items {
apps.push(app.name.clone());
}
while !app_list_page.is_last_page {
app_list_page = client
.list_apps(DEFAULT_APPLIST_PAGE_SIZE, Some(page_index))
.await?;
for app in app_list_page.items {
apps.push(app.name.clone());
}
page_index += 1;
}
print_app_list(apps, self.format);
Ok(())
}
}
Expand Down Expand Up @@ -92,13 +102,14 @@ impl InfoCommand {

let (current_domain, in_progress_domain) = domains_current_and_in_progress(&app);

println!("Name: {}", &app.name);
print_if_present("Description: ", app.description.as_ref());
print_if_present("URL: https://", current_domain);
if let Some(domain) = in_progress_domain {
println!("Validation for {} is in progress", domain);
};
let info = AppInfo::new(
app.name.clone(),
app.description.clone(),
current_domain.cloned(),
in_progress_domain.is_none(),
);

print_app_info(info, self.format);
Ok(())
}
}
Expand All @@ -116,17 +127,3 @@ fn domains_current_and_in_progress(app: &AppItem) -> (Option<&String>, Option<&S
None => (Some(auto_domain), None),
}
}

fn print_if_present(prefix: &str, value: Option<&String>) {
if let Some(val) = value {
if !val.is_empty() {
println!("{prefix}{val}");
}
}
}

fn print_app_list(page: &AppItemPage) {
for app in &page.items {
println!("{}", app.name);
}
}
82 changes: 82 additions & 0 deletions src/commands/apps_output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::fmt::Display;

use clap::ValueEnum;
use serde::Serialize;

#[derive(Debug, ValueEnum, PartialEq, Clone)]
pub(crate) enum OutputFormat {
Plain,
Json,
}

#[derive(Serialize)]
pub(crate) struct AppInfo {
name: String,
description: String,
url: Option<String>,
#[serde(rename = "domainInfo")]
domain_info: DomainInfo,
}

#[derive(Serialize)]
pub(crate) struct DomainInfo {
domain: Option<String>,
#[serde(rename = "validationFinished")]
validation_finished: bool,
}

impl AppInfo {
pub(crate) fn new(
name: String,
description: Option<String>,
domain: Option<String>,
domain_validation_finished: bool,
) -> Self {
let url = domain.as_ref().map(|d| format!("https://{}", d));
Self {
name,
description: description.unwrap_or_default(),
url,
domain_info: DomainInfo {
domain,
validation_finished: domain_validation_finished,
},
}
}
}

impl Display for AppInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Name: {}", self.name)?;
if !self.description.is_empty() {
writeln!(f, "Description: {}", self.description)?;
}
if let Some(domain) = self.domain_info.domain.as_ref() {
writeln!(f, "URL: https://{}", domain)?;
if !self.domain_info.validation_finished {
writeln!(f, "Validation for {} is in progress", domain)?;
};
}
Ok(())
}
}

pub(crate) fn print_app_list(apps: Vec<String>, format: OutputFormat) {
match format {
OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&apps).unwrap()),
OutputFormat::Plain => {
if apps.is_empty() {
eprintln!("No applications found");
return;
}
println!("{}", apps.join("\n"))
}
}
}

pub(crate) fn print_app_info(app: AppInfo, format: OutputFormat) {
match format {
OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&app).unwrap()),
OutputFormat::Plain => print!("{}", app),
}
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod apps;
pub mod apps_output;
pub mod deploy;
pub mod key_value;
pub mod link;
Expand Down

0 comments on commit db9f89c

Please sign in to comment.