Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ bytesize = "2"
chrono = { version = "0.4", default-features = false, features = ["clock"] }
clap = { version = "4.6", features = ["cargo", "derive", "wrap_help"] }
clap_complete = "4.6"
comfy-table = { version = "7.2", features = ["custom_styling"] }
crc32fast = "1.4"
criterion = { version = "0.8", features = ["html_reports"] }
crossterm = "0.29"
Expand Down
1 change: 1 addition & 0 deletions rust/crates/sift_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ arrow-schema = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
clap_complete = { workspace = true }
comfy-table = { workspace = true }
crossterm = { workspace = true }
csv = { workspace = true }
dirs = { workspace = true }
Expand Down
16 changes: 13 additions & 3 deletions rust/crates/sift_cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,16 +887,26 @@ pub enum OutputFormats {
}

#[derive(clap::Args)]
pub struct GetAssetArgs {
pub struct GetArgs {
/// Filter option for filtering search with CEL expression
#[arg(long)]
pub filter: Option<String>,

/// Caps returned results to set number
#[arg(long, default_value = "50")]
pub limit: Option<u32>,
#[arg(long, default_value_t = 50)]
pub limit: u32,

/// Orders results as a comma-separated list of "FIELD_NAME[ desc]"
#[arg(long, default_value = "modified_date desc")]
pub order_by: String,

/// Determines the output format
#[arg(long, value_enum)]
pub output_format: Option<OutputFormats>,
}

#[derive(clap::Args)]
pub struct GetAssetArgs {
#[command(flatten)]
pub common: GetArgs,
}
25 changes: 8 additions & 17 deletions rust/crates/sift_cli/src/cmd/get/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,27 @@ use crate::{
api::create_grpc_channel,
app_uri::normalize_app_uri,
explore_url::build_explore_url,
table::new_table,
tty::{Output, hyperlink, link_style, stdout_is_tty},
},
};

const ASSET_ID_CHAR_LENGTH_WHITESPACE_LENGTH: usize = 38;

pub async fn run(ctx: Context, args: GetAssetArgs) -> Result<ExitCode> {
let grpc_channel = create_grpc_channel(&ctx)?;

let ListAssetsResponse { assets, .. } = AssetServiceClient::new(grpc_channel)
.list_assets(ListAssetsRequest {
filter: args.filter.unwrap_or_default(),
order_by: "modified_date desc".to_string(),
page_size: args.limit.unwrap_or_default(),
filter: args.common.filter.unwrap_or_default(),
order_by: args.common.order_by,
page_size: args.common.limit,
..Default::default()
})
.await
.context("failed to list assets")?
.into_inner();
let app_uri = ctx.app_uri.as_deref().and_then(normalize_app_uri);
let mut output = Output::new();
match args.output_format {
match args.common.output_format {
Some(OutputFormats::Json) => {
output.line(serde_json::to_string_pretty(&assets).context("failed to encode assets")?);
}
Expand All @@ -43,24 +42,16 @@ pub async fn run(ctx: Context, args: GetAssetArgs) -> Result<ExitCode> {
output.line("no assets found");
} else {
let linkify = stdout_is_tty();
let mut table = new_table(vec!["ID", "Name"]);

output.line(format_args!(
"{:<width$}{}",
"ID",
"Name",
width = ASSET_ID_CHAR_LENGTH_WHITESPACE_LENGTH,
));
for asset in &assets {
let name = match build_explore_url(app_uri, &asset.name, None) {
Some(url) if linkify => hyperlink(&link_style(&asset.name), &url),
_ => asset.name.clone(),
};
output.line(format_args!(
"{:<width$}{name}",
asset.asset_id,
width = ASSET_ID_CHAR_LENGTH_WHITESPACE_LENGTH,
));
table.add_row(vec![asset.asset_id.clone(), name]);
}
output.line(table);
output.tip(match app_uri {
Some(host) => format!(
"View an asset in Explore at {host}/explore?method=single&assets=<NAME>"
Expand Down
1 change: 1 addition & 0 deletions rust/crates/sift_cli/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pub mod channel;
pub mod explore_url;
pub mod job;
pub mod progress;
pub mod table;
pub mod tty;
13 changes: 13 additions & 0 deletions rust/crates/sift_cli/src/util/table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use comfy_table::{ContentArrangement, Row, Table, presets::ASCII_FULL_CONDENSED};

pub fn new_table<H: Into<Row>>(headers: H) -> Table {
let mut table = Table::new();
table
.load_preset(ASCII_FULL_CONDENSED)
.set_content_arrangement(ContentArrangement::Disabled)
.set_header(headers);
table
}

#[cfg(test)]
mod test;
75 changes: 75 additions & 0 deletions rust/crates/sift_cli/src/util/table/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use super::new_table;
use crate::util::tty::{hyperlink, link_style};

fn visible(line: &str) -> String {
let mut out = String::new();
let mut chars = line.chars().peekable();

while let Some(c) = chars.next() {
if c != '\x1b' {
out.push(c);
continue;
}
match chars.next() {
Some(']') => {
while let Some(c) = chars.next() {
if c == '\x07' {
break;
}
if c == '\x1b' {
chars.next();
break;
}
}
}
Some('[') => {
for c in chars.by_ref() {
if c.is_ascii_alphabetic() {
break;
}
}
}
_ => {}
}
}
out
}

#[test]
fn borders_stay_aligned_when_a_cell_holds_a_styled_hyperlink() {
let mut table = new_table(vec!["ID", "Name"]);
table.add_row(vec![
"c6a9e2b8-0000-4000-8000-1234567890ab".to_string(),
hyperlink(
&link_style("engine"),
"https://app.siftstack.com/explore?method=single&assets=engine",
),
]);
table.add_row(vec![
"7f13aa20-1111-4000-8000-abcdefabcdef".to_string(),
"avionics-bench-3".to_string(),
]);

let rendered = table.to_string();
let widths: Vec<usize> = rendered
.lines()
.map(|line| visible(line).chars().count())
.collect();

assert!(!widths.is_empty(), "table rendered no lines");
assert!(
widths.iter().all(|w| *w == widths[0]),
"every line should have the same visible width, got {widths:?}:\n{}",
rendered.replace('\x1b', "<ESC>")
);
}

#[test]
fn hyperlink_cell_keeps_its_escape_sequence_intact() {
let url = "https://app.siftstack.com/explore?method=single&assets=engine";
let mut table = new_table(vec!["Name"]);
table.add_row(vec![hyperlink("engine", url)]);

let rendered = table.to_string();
assert!(rendered.contains(&format!("\x1b]8;;{url}\x1b\\engine\x1b]8;;\x1b\\")));
}
Loading