use omgbase_surface::read::{ResolvedRef, resolve_ref};
use serde_json::{Value as Json, json};
use crate::cli::argv::{Opt, expand_dash, parse_args};
use crate::cli::context::Cli;
use crate::cli::help::{render_help_for, wants_help};
use crate::cli::output::{CliError, EXIT_OK, Result};
use super::machine_out;
struct CatOne {
text: String,
json: Json,
capture: Json,
}
pub fn node_text(node: &Json) -> String {
for k in ["raw", "text", "label"] {
if let Some(s) = node.get(k).and_then(Json::as_str) {
return s.to_owned();
}
}
String::new()
}
fn cat_one_remote(cli: &mut Cli, r: &str, resolution: &str) -> Result<CatOne> {
let mut req = serde_json::Map::new();
req.insert("ref".to_owned(), json!(r));
if resolution != "raw" {
req.insert("resolution".to_owned(), json!(resolution));
}
let res = cli.call("read_ref", Json::Object(req))?;
if res.get("kind").and_then(Json::as_str) == Some("document") {
if resolution != "raw" {
warn_doc_resolution(cli, r, resolution);
}
let text = res
.get("content")
.and_then(Json::as_str)
.unwrap_or("")
.to_owned();
let doc = match res {
Json::Object(m) => Json::Object(m.into_iter().filter(|(k, _)| k != "kind").collect()),
other => other,
};
return Ok(CatOne {
capture: Json::String(text.clone()),
text,
json: doc,
});
}
Ok(CatOne {
text: node_text(&res),
capture: res.clone(),
json: res,
})
}
fn warn_doc_resolution(cli: &Cli, r: &str, resolution: &str) {
cli.io.err(&cli.style.warn(&format!(
" --resolution {resolution} ignored for {r}: a document is always its exact bytes (resolutions apply to block refs)"
)));
}
fn cat_one(cli: &mut Cli, r: &str, resolution: &str) -> Result<CatOne> {
if cli.remote_mode() {
return cat_one_remote(cli, r, resolution);
}
let repo = cli.repo()?;
let resolved = resolve_ref(cli.store()?.conn(), &repo.repo_id, r)?
.ok_or_else(|| CliError::engine("doc_missing", format!("no node {r}")))?;
match resolved {
ResolvedRef::Document { doc_id } => {
let res = cli.call("docs_read", json!({ "doc": doc_id }))?;
if resolution != "raw" {
warn_doc_resolution(cli, r, resolution);
}
let text = res
.get("content")
.and_then(Json::as_str)
.unwrap_or("")
.to_owned();
let capture = Json::String(text.clone());
Ok(CatOne {
text,
json: res,
capture,
})
}
ResolvedRef::Block { doc_id, block_id } => {
let node = cli.call(
"nodes_get",
json!({ "id": block_id, "doc": doc_id, "resolution": resolution }),
)?;
Ok(CatOne {
text: node_text(&node),
capture: node.clone(), json: node,
})
}
}
}
pub fn cat(cli: &mut Cli, args: &[String]) -> Result<i32> {
if wants_help(args) {
return render_help_for(cli, "cat");
}
let a = parse_args(args, &[Opt::value("resolution")])?;
let refs = expand_dash(&a.positionals);
if refs.is_empty() {
return Err(CliError::usage(
"cat requires a <node> (or - to read refs from stdin)",
));
}
let resolution = a.value("resolution").unwrap_or("raw").to_owned();
let mut results: Vec<CatOne> = Vec::with_capacity(refs.len());
for r in &refs {
results.push(cat_one(cli, r, &resolution)?);
}
if cli.capturing {
let captured = match results.as_slice() {
[one] => one.capture.clone(),
many => Json::Array(many.iter().map(|r| r.capture.clone()).collect()),
};
cli.capture(&captured);
}
let items: Vec<Json> = results.iter().map(|r| r.json.clone()).collect();
let doc = if items.len() == 1 {
items[0].clone()
} else {
Json::Array(items.clone())
};
if let Some(code) = machine_out(cli, &doc, Some(&items), None) {
return code;
}
for r in &results {
cli.io.out(&r.text);
}
Ok(EXIT_OK)
}