Skip to content

Commit

Permalink
pretty cli output
Browse files Browse the repository at this point in the history
  • Loading branch information
m1guelpf committed Jul 23, 2024
1 parent 78dfaf0 commit eb2e06d
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 33 deletions.
85 changes: 79 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ members = ["crates/*"]
license = "MIT"
edition = "2021"
authors = ["Miguel Piedrafita <[email protected]>"]

[profile.release]
lto = true
strip = true
opt-level = 3
panic = "abort"
2 changes: 1 addition & 1 deletion Dockerfile.cli
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ RUN cargo build --release --bin orbit-cli

FROM --platform=linux/amd64 alpine AS runtime
WORKDIR /orbit-cli
COPY --from=builder /orbit-cli/target/x86_64-unknown-linux-musl/release/orbit-cli /usr/local/bin/orbit
COPY --from=builder /orbit-cli/target/x86_64-unknown-linux-musl/release/orbit /usr/local/bin

ENTRYPOINT ["/usr/local/bin/orbit"]
9 changes: 8 additions & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
[package]
version = "0.1.0"
name = "orbit-cli"
categories = ["command-line-utilities"]

edition.workspace = true
license.workspace = true
authors.workspace = true

[dependencies]
url = "2.5.2"
log = "0.4.22"
anyhow = "1.0.86"
console = "0.15.8"
pin-utils = "0.1.0"
futures-util = "0.3.30"
tokio = { version = "1.38.1", features = ["full"] }
tokio = { version = "=1.29", features = ["full"] }
fern = { version = "0.6.2", features = ["colored"] }
orbit-types = { version = "0.1.0", path = "../types" }
orbit-client = { version = "0.1.0", path = "../client" }
clap = { version = "4.5.9", features = ["derive", "env"] }
78 changes: 53 additions & 25 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]

use anyhow::Result;
use clap::{Parser, Subcommand};
use futures_util::StreamExt;
use orbit_client::Client;
use orbit_types::{Log, Progress, Stage};
use pin_utils::pin_mut;
use url::Url;

mod utils;

#[derive(Debug, Parser)]
#[clap(
name = "orbit",
about = "🪐 Trigger Orbit deploys from the command line.",
version,
author
)]
struct Cli {
/// URL to the Orbit instance.
#[arg(short, long, env = "ORBIT_URL")]
url: Url,

/// Enable debug mode
#[clap(short = 'D', long)]
pub debug: bool,

#[clap(subcommand)]
command: Commands,
}
Expand All @@ -30,37 +44,51 @@ enum Commands {
}

#[tokio::main]
async fn main() {
async fn main() -> Result<()> {
let cli = Cli::parse();

// setup panic hook
utils::set_hook();
utils::logs(cli.debug);

let client = Client::new(cli.url);

match cli.command {
Commands::Deploy { slug, r#ref } => run_deploy(slug, r#ref, &client).await,
if let Err(error) = handle_command(cli.command, &client).await {
log::error!("{error}");
log::debug!("{error:#?}");
std::process::exit(1);
}

utils::clean_term();

Ok(())
}

async fn run_deploy(slug: String, r#ref: Option<String>, client: &Client) {
async fn handle_command(commands: Commands, client: &Client) -> Result<()> {
match commands {
Commands::Deploy { slug, r#ref } => run_deploy(slug, r#ref, client).await,
}
}

async fn run_deploy(slug: String, r#ref: Option<String>, client: &Client) -> Result<()> {
let stream = client.deploy(&slug, r#ref.as_deref());
pin_mut!(stream);

while let Some(event) = stream.next().await {
match event? {
Ok(Progress::Log(log)) => match log {
Log::Info(message) => println!("{message}"),
Log::Error(message) => eprintln!("{message}"),
},
Ok(Progress::Stage(stage)) => match stage {
Stage::Starting => log::info!("Starting deployment"),
Stage::Downloaded => log::info!("Downloaded repository"),
Stage::DepsInstalled => log::info!("Installed dependencies"),
Stage::Deployed => log::info!("Deployed site"),
},
Err(error) => return Err(error.into()),
}
}

stream
.map(|result| match result {
Err(err) => panic!("{err}"),
Ok(event) => event,
})
.for_each(|event| async {
match event {
Ok(Progress::Log(log)) => match log {
Log::Info(message) => println!("{message}"),
Log::Error(message) => eprintln!("{message}"),
},
Ok(Progress::Stage(stage)) => match stage {
Stage::Starting => println!("Starting deployment"),
Stage::Downloaded => println!("Downloaded repository"),
Stage::DepsInstalled => println!("Installed dependencies"),
Stage::Deployed => println!("Deployed site"),
},
Err(error) => eprintln!("{error}"),
}
})
.await;
Ok(())
}
81 changes: 81 additions & 0 deletions crates/cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use fern::colors::{Color, ColoredLevelConfig};
use log::{Level, LevelFilter};

pub fn set_hook() {
// setup a panic hook to easily exit the program on panic
std::panic::set_hook(Box::new(|panic_info| {
// print the panic message
let message = panic_info.payload().downcast_ref::<String>().map_or_else(
|| {
panic_info.payload().downcast_ref::<&str>().map_or_else(
|| format!("{panic_info:?}"),
|message| (*message).to_string(),
)
},
Clone::clone,
);

// add some color
log::error!("{message}");

#[cfg(debug_assertions)]
log::debug!("{panic_info}");

std::process::exit(1);
}));
}

pub fn logs(verbose: bool) {
let colors = ColoredLevelConfig::new()
.info(Color::BrightCyan)
.error(Color::BrightRed)
.warn(Color::BrightYellow)
.debug(Color::BrightWhite);

fern::Dispatch::new()
.format(move |out, message, record| {
let level = record.level();

match level {
Level::Debug => out.finish(format_args!(
"{} [{}]: {}",
colors.color(Level::Debug).to_string().to_lowercase(),
record.target(),
message
)),

level => out.finish(format_args!(
"{}: {}",
colors.color(level).to_string().to_lowercase(),
message
)),
}
})
.level(if verbose {
LevelFilter::Debug
} else {
LevelFilter::Info
})
.chain(
fern::Dispatch::new()
.filter(|metadata| !matches!(metadata.level(), Level::Error | Level::Warn))
.chain(std::io::stdout()),
)
.chain(
fern::Dispatch::new()
.level(log::LevelFilter::Error)
.level(log::LevelFilter::Warn)
.chain(std::io::stderr()),
)
.apply()
.ok();
}

pub fn clean_term() {
let term = console::Term::stdout();

// if the terminal is a tty, clear the screen and reset the cursor
if term.is_term() {
term.show_cursor().ok();
}
}

0 comments on commit eb2e06d

Please sign in to comment.