use anyhow::Context;
use chrono::SecondsFormat;
use clap::Subcommand;
use nominal::core::{DatasetCreate, DatasetUpdate, NominalClient};
#[derive(Subcommand)]
pub enum DatasetCommands {
List,
Get {
rid: String,
},
Create {
#[arg(short, long)]
name: String,
#[arg(short, long)]
description: Option<String>,
#[arg(short, long = "label", value_name = "LABEL")]
labels: Vec<String>,
#[arg(short, long = "property", value_names = ["KEY", "VALUE"], num_args = 2, action = clap::ArgAction::Append)]
properties: Vec<String>,
},
Update {
rid: String,
#[arg(short, long)]
name: Option<String>,
#[arg(short, long)]
description: Option<String>,
#[arg(
short,
long = "label",
value_name = "LABEL",
conflicts_with = "clear_labels"
)]
labels: Vec<String>,
#[arg(long, conflicts_with = "labels")]
clear_labels: bool,
#[arg(short, long = "property", value_names = ["KEY", "VALUE"], num_args = 2, action = clap::ArgAction::Append, conflicts_with = "clear_properties")]
properties: Vec<String>,
#[arg(long, conflicts_with = "properties")]
clear_properties: bool,
},
Archive {
rid: String,
},
Unarchive {
rid: String,
},
}
pub async fn handle(cmd: DatasetCommands, client: NominalClient) -> anyhow::Result<()> {
match cmd {
DatasetCommands::List => {
let datasets = client
.catalog()
.list_datasets()
.await
.context("Failed to list datasets")?;
for dataset in datasets {
println!("{}", dataset.rid());
}
}
DatasetCommands::Get { rid } => {
let dataset = client
.catalog()
.get_dataset(&rid)
.await
.with_context(|| format!("Failed to get dataset '{rid}'"))?;
print_dataset(&dataset);
}
DatasetCommands::Create {
name,
description,
labels,
properties,
} => {
let mut create = DatasetCreate::new(name);
if let Some(d) = description {
create = create.description(d);
}
if !labels.is_empty() {
create = create.labels(labels);
}
if !properties.is_empty() {
let props: std::collections::HashMap<_, _> = properties
.chunks(2)
.map(|pair| (pair[0].clone(), pair[1].clone()))
.collect();
create = create.properties(props);
}
let dataset = client
.catalog()
.create_dataset(create)
.await
.context("Failed to create dataset")?;
print_dataset(&dataset);
}
DatasetCommands::Update {
rid,
name,
description,
labels,
clear_labels,
properties,
clear_properties,
} => {
let mut update = DatasetUpdate::new();
if let Some(n) = name {
update = update.name(n);
}
if let Some(d) = description {
update = update.description(d);
}
if clear_labels {
update = update.labels([] as [String; 0]);
} else if !labels.is_empty() {
update = update.labels(labels);
}
if clear_properties {
update = update.properties([] as [(String, String); 0]);
} else if !properties.is_empty() {
let props: std::collections::HashMap<_, _> = properties
.chunks(2)
.map(|pair| (pair[0].clone(), pair[1].clone()))
.collect();
update = update.properties(props);
}
let dataset = client
.catalog()
.update_dataset(&rid, update)
.await
.with_context(|| format!("Failed to update dataset '{rid}'"))?;
print_dataset(&dataset);
}
DatasetCommands::Archive { rid } => {
client
.catalog()
.archive_dataset(&rid)
.await
.with_context(|| format!("Failed to archive dataset '{rid}'"))?;
println!("Archived dataset: {rid}");
}
DatasetCommands::Unarchive { rid } => {
client
.catalog()
.unarchive_dataset(&rid)
.await
.with_context(|| format!("Failed to unarchive dataset '{rid}'"))?;
println!("Unarchived dataset: {rid}");
}
}
Ok(())
}
fn print_dataset(dataset: &nominal::core::Dataset) {
println!("RID: {}", dataset.rid());
println!("Name: {}", dataset.name());
if let Some(description) = dataset.description() {
println!("Description: {description}");
}
if !dataset.labels().is_empty() {
println!("Labels: {}", dataset.labels().join(", "));
}
if !dataset.properties().is_empty() {
println!("Properties:");
for (key, value) in dataset.properties() {
println!(" {key}: {value}");
}
}
println!(
"Created: {}",
dataset
.created_at()
.to_rfc3339_opts(SecondsFormat::Nanos, true)
);
println!("URL: {}", dataset.nominal_url());
}