Skip to content

Commit

Permalink
feat(cli): add some colors
Browse files Browse the repository at this point in the history
rvcas committed May 2, 2024
1 parent f3fad75 commit 25c076c
Showing 9 changed files with 261 additions and 108 deletions.
28 changes: 28 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,13 +15,16 @@ license = false
eula = false

[dependencies]
arc-swap = "1.7.1"
clap = { version = "4.5.4", features = ["derive"] }
console = "0.15.8"
dirs = "5.0.1"
flate2 = "1.0.28"
http-body-util = "0.1.1"
indoc = "2.0.5"
miette = { version = "7.2.0", features = ["fancy"] }
octocrab = "0.38.0"
once_cell = "1.19.0"
tar = "0.4.40"
thiserror = "1.0.58"
tokio = { version = "1.37.0", features = ["full"] }
3 changes: 2 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::cmd;

use clap::Parser;

#[derive(Parser)]
@@ -25,5 +26,5 @@ impl Cli {
}

async fn install_latest() -> miette::Result<()> {
cmd::install::exec(cmd::install::Args::latest()).await
cmd::install::Args::latest().exec().await
}
190 changes: 112 additions & 78 deletions src/cmd/install.rs
Original file line number Diff line number Diff line change
@@ -33,106 +33,140 @@ impl Args {
no_switch: false,
}
}
}

pub async fn exec(args: Args) -> miette::Result<()> {
println!("{}", BANNER);

let octocrab = octocrab::instance();

let aiken_root = root_dir()?;

let bin_dir = aiken_root.join("bin");
let versions_dir = aiken_root.join("versions");
pub async fn exec(self) -> miette::Result<()> {
println!("{}", BANNER);

let release = match args.release {
Some(tag) => {
println!("aikup: installing aiken ({})", tag);
let ctx = crate::ctx::instance();
let octocrab = octocrab::instance();

octocrab
.repos("aiken-lang", "aiken")
.releases()
.get_by_tag(&tag)
.await
.into_diagnostic()?
}
None => {
println!("aikup: no version specified; installing latest");
let aiken_root = root_dir()?;

octocrab
.repos("aiken-lang", "aiken")
.releases()
.get_latest()
.await
.into_diagnostic()?
}
};

let asset_name = asset_name(&release.tag_name);

let search_result = release
.assets
.into_iter()
.find(|asset| asset.name == asset_name);

let Some(asset) = search_result else {
miette::bail!("aikup: no release found for {}", asset_name);
};
let bin_dir = aiken_root.join("bin");
let versions_dir = aiken_root.join("versions");

let install_dir = versions_dir.join(&release.tag_name);
let src_bin = install_dir.join("aiken");
let release = match self.release {
Some(tag) => {
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.info_text("installing"),
ctx.colors.version_text(&tag).italic().dim()
);

if src_bin.try_exists().into_diagnostic()? {
println!("aikup: already installed aiken ({})", &release.tag_name);
} else {
println!("aikup: downloading aiken ({})", &release.tag_name);
octocrab
.repos("aiken-lang", "aiken")
.releases()
.get_by_tag(&tag)
.await
.into_diagnostic()?
}
None => {
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.warning_text("no version specified;"),
ctx.colors.info_text("installing latest"),
);

octocrab
.repos("aiken-lang", "aiken")
.releases()
.get_latest()
.await
.into_diagnostic()?
}
};

let bytes = octocrab
._get(asset.browser_download_url.to_string())
.await
.into_diagnostic()?
.into_body()
.collect()
.await
.into_diagnostic()?
.to_bytes();
let asset_name = asset_name(&release.tag_name);

let decoder = GzDecoder::new(&bytes[..]);
let search_result = release
.assets
.into_iter()
.find(|asset| asset.name == asset_name);

let mut archive = Archive::new(decoder);
let Some(asset) = search_result else {
miette::bail!("{} no release found for {}", ctx.aikup_label(), asset_name);
};

let install_dir = versions_dir.join(&release.tag_name);
let src_bin = install_dir.join("aiken");

create_dir_all_if_not_exists(&versions_dir).await?;

archive.unpack(&install_dir).into_diagnostic()?;
if src_bin.try_exists().into_diagnostic()? {
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.warning_text("already installed"),
ctx.colors.version_text(&release.tag_name).italic().dim()
);
} else {
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.info_text("downloading"),
ctx.colors.version_text(&release.tag_name).italic().dim()
);

let bytes = octocrab
._get(asset.browser_download_url.to_string())
.await
.into_diagnostic()?
.into_body()
.collect()
.await
.into_diagnostic()?
.to_bytes();

println!("aikup: installed aiken ({})", &release.tag_name);
}
let decoder = GzDecoder::new(&bytes[..]);

if !args.no_switch {
let sym_bin = bin_dir.join("aiken");
let src_bin = install_dir.join("aiken");
let mut archive = Archive::new(decoder);

match tokio::fs::read_link(&sym_bin).await {
Ok(real_path) if real_path == src_bin => {
println!("aikup: already switched to aiken ({})", &release.tag_name);
let install_dir = versions_dir.join(&release.tag_name);

return Ok(());
}
Ok(_) | Err(_) => {
create_dir_all_if_not_exists(&bin_dir).await?;
create_dir_all_if_not_exists(&versions_dir).await?;

remove_file_if_exists(&sym_bin).await?;
archive.unpack(&install_dir).into_diagnostic()?;

symlink(src_bin, sym_bin).await.into_diagnostic()?;
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.success_text("installed"),
ctx.colors.version_text(&release.tag_name).italic().dim()
);
}

println!("aikup: switched to aiken ({})", &release.tag_name);
if !self.no_switch {
let sym_bin = bin_dir.join("aiken");
let src_bin = install_dir.join("aiken");

match tokio::fs::read_link(&sym_bin).await {
Ok(real_path) if real_path == src_bin => {
println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.warning_text("already switched"),
ctx.colors.version_text(&release.tag_name).italic().dim()
);
}
Ok(_) | Err(_) => {
create_dir_all_if_not_exists(&bin_dir).await?;

remove_file_if_exists(&sym_bin).await?;

symlink(src_bin, sym_bin).await.into_diagnostic()?;

println!(
"{} {} {}",
ctx.aikup_label(),
ctx.colors.success_text("switched"),
ctx.colors.version_text(&release.tag_name).italic().dim()
);
}
}
}
}

Ok(())
Ok(())
}
}

fn asset_name(tag_name: &str) -> String {
68 changes: 41 additions & 27 deletions src/cmd/list.rs
Original file line number Diff line number Diff line change
@@ -10,39 +10,53 @@ pub struct Args {
installed: bool,
}

pub async fn exec(args: Args) -> miette::Result<()> {
if args.installed {
let aiken_root = root_dir()?;
let versions_dir = aiken_root.join("versions");
impl Args {
pub async fn exec(self) -> miette::Result<()> {
let ctx = crate::ctx::instance();

let mut entries = tokio::fs::read_dir(&versions_dir).await.into_diagnostic()?;
if self.installed {
let aiken_root = root_dir()?;
let versions_dir = aiken_root.join("versions");

println!("aikup: installed versions");
let mut entries = tokio::fs::read_dir(&versions_dir).await.into_diagnostic()?;

while let Some(entry) = entries.next_entry().await.into_diagnostic()? {
let path = entry.path();
println!(
"{} {}\n",
ctx.aikup_label(),
ctx.colors.info_text("installed versions")
);

if path.is_dir() {
println!("{}", path.file_name().unwrap().to_string_lossy());
}
}
} else {
let octocrab = octocrab::instance();
while let Some(entry) = entries.next_entry().await.into_diagnostic()? {
let path = entry.path();

let releases = octocrab
.repos("aiken-lang", "aiken")
.releases()
.list()
.send()
.await
.into_diagnostic()?;
if path.is_dir() {
let display_name = path.file_name().unwrap().to_string_lossy();

println!("aikup: available versions");

for release in releases {
println!("{}", release.tag_name);
println!("{}", ctx.colors.version_text(display_name).bold());
}
}
} else {
let octocrab = octocrab::instance();

let releases = octocrab
.repos("aiken-lang", "aiken")
.releases()
.list()
.send()
.await
.into_diagnostic()?;

println!(
"{} {}\n",
ctx.aikup_label(),
ctx.colors.info_text("available versions")
);

for release in releases {
println!("{}", ctx.colors.version_text(&release.tag_name).bold());
}
}
}

Ok(())
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@ pub enum Cmd {
impl Cmd {
pub async fn exec(self) -> miette::Result<()> {
match self {
Cmd::Install(args) => install::exec(args).await,
Cmd::List(args) => list::exec(args).await,
Cmd::Install(args) => args.exec().await,
Cmd::List(args) => args.exec().await,
}
}
}
47 changes: 47 additions & 0 deletions src/colors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use console::{Style, StyledObject};

pub struct Colors {
version_text: Style,
warning_text: Style,
success_text: Style,
info_text: Style,
label: Style,
}

impl Default for Colors {
fn default() -> Self {
Self::new()
}
}

impl Colors {
pub fn new() -> Self {
Self {
version_text: Style::new().cyan(),
warning_text: Style::new().yellow(),
success_text: Style::new().green(),
info_text: Style::new().blue(),
label: Style::new().magenta(),
}
}

pub fn version_text<D>(&self, val: D) -> StyledObject<D> {
self.version_text.apply_to(val)
}

pub fn warning_text<D>(&self, val: D) -> StyledObject<D> {
self.warning_text.apply_to(val)
}

pub fn success_text<D>(&self, val: D) -> StyledObject<D> {
self.success_text.apply_to(val)
}

pub fn info_text<D>(&self, val: D) -> StyledObject<D> {
self.info_text.apply_to(val)
}

pub fn label<D>(&self, val: D) -> StyledObject<D> {
self.label.apply_to(val)
}
}
24 changes: 24 additions & 0 deletions src/ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::sync::Arc;

use console::StyledObject;
use once_cell::sync::Lazy;

use crate::colors::Colors;

#[derive(Default)]
pub struct Context {
pub colors: Colors,
}

static STATIC_INSTANCE: Lazy<arc_swap::ArcSwap<Context>> =
Lazy::new(|| arc_swap::ArcSwap::from_pointee(Context::default()));

pub fn instance() -> Arc<Context> {
STATIC_INSTANCE.load().clone()
}

impl Context {
pub fn aikup_label(&self) -> StyledObject<&str> {
self.colors.label("aikup:")
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod cli;
mod cmd;
mod colors;
mod ctx;
mod utils;

pub use cli::Cli;

0 comments on commit 25c076c

Please sign in to comment.