118 lines
3.1 KiB
Rust
118 lines
3.1 KiB
Rust
use anyhow::{Context, Result};
|
|
use clap::Args;
|
|
use pile_config::{Label, objectpath::ObjectPath};
|
|
use pile_dataset::Datasets;
|
|
use pile_toolbox::cancelabletask::{CancelFlag, CancelableTaskError};
|
|
use pile_value::{extract::traits::ExtractState, value::PileValue};
|
|
use std::path::PathBuf;
|
|
|
|
use crate::{CliCmd, GlobalContext};
|
|
|
|
#[derive(Debug, Args)]
|
|
pub struct ItemCommand {
|
|
/// Source name (as defined in pile.toml)
|
|
source: String,
|
|
|
|
/// Item key within the source
|
|
key: String,
|
|
|
|
/// If present, extract a specific field
|
|
#[arg(long, short = 'p')]
|
|
path: Option<String>,
|
|
|
|
/// If present, print the schema fields instead of item data
|
|
#[arg(long)]
|
|
schema: bool,
|
|
|
|
#[arg(long, short = 'x')]
|
|
exclude: Vec<String>,
|
|
|
|
/// Path to dataset config
|
|
#[arg(long, short = 'c', default_value = "./pile.toml")]
|
|
config: PathBuf,
|
|
|
|
/// Working directory root
|
|
#[arg(long, default_value = "./.pile")]
|
|
workdir: PathBuf,
|
|
}
|
|
|
|
impl CliCmd for ItemCommand {
|
|
#[expect(clippy::print_stdout)]
|
|
#[expect(clippy::unwrap_used)]
|
|
async fn run(
|
|
self,
|
|
_ctx: GlobalContext,
|
|
_flag: CancelFlag,
|
|
) -> Result<i32, CancelableTaskError<anyhow::Error>> {
|
|
let source = Label::new(&self.source)
|
|
.ok_or_else(|| anyhow::anyhow!("invalid source name {:?}", self.source))?;
|
|
|
|
let ds = Datasets::open(&self.config, &self.workdir)
|
|
.await
|
|
.with_context(|| format!("while opening dataset for {}", self.config.display()))?;
|
|
|
|
let state = ExtractState { ignore_mime: false };
|
|
|
|
let item = ds.get(&source, &self.key).await.ok_or_else(|| {
|
|
anyhow::anyhow!("{:?} not found in source {:?}", self.key, self.source)
|
|
})?;
|
|
let pv = PileValue::Item(item);
|
|
|
|
if self.schema {
|
|
let mut map = serde_json::Map::new();
|
|
for (name, spec) in &ds.config.schema {
|
|
if self.exclude.contains(&name.to_string()) {
|
|
continue;
|
|
}
|
|
|
|
let mut value = None;
|
|
for path in &spec.path {
|
|
let v = pv
|
|
.query(&state, path)
|
|
.await
|
|
.with_context(|| format!("while extracting field {name}"))?;
|
|
if let Some(v) = v
|
|
&& !matches!(v, PileValue::Null)
|
|
{
|
|
let j = v
|
|
.to_json(&state)
|
|
.await
|
|
.with_context(|| format!("while extracting field {name}"))?;
|
|
value = Some(j);
|
|
break;
|
|
}
|
|
}
|
|
map.insert(name.to_string(), value.unwrap_or(serde_json::Value::Null));
|
|
}
|
|
let json = serde_json::to_string_pretty(&serde_json::Value::Object(map)).unwrap();
|
|
println!("{json}");
|
|
return Ok(0);
|
|
}
|
|
|
|
let json = if let Some(path_str) = self.path {
|
|
let path: ObjectPath = path_str
|
|
.parse()
|
|
.with_context(|| format!("invalid path {path_str:?}"))?;
|
|
|
|
let v = pv
|
|
.query(&state, &path)
|
|
.await
|
|
.with_context(|| format!("while extracting {}", self.key))?
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("{:?} not found in source {:?}", self.key, self.source)
|
|
})?;
|
|
v.to_json(&state)
|
|
.await
|
|
.with_context(|| format!("while extracting {}", self.key))?
|
|
} else {
|
|
pv.to_json(&state)
|
|
.await
|
|
.with_context(|| format!("while extracting {}", self.key))?
|
|
};
|
|
|
|
let json = serde_json::to_string_pretty(&json).unwrap();
|
|
println!("{json}");
|
|
return Ok(0);
|
|
}
|
|
}
|