use std::io::Write;
use anyhow::{bail, Context, Result};
use serde::Serialize;
use doiget_core::store::{FsStore, Metadata, Store};
use doiget_core::Ref;
use super::resolve_store_root;
pub fn run(input: String) -> Result<()> {
let ref_ = Ref::parse(&input).with_context(|| format!("invalid ref: {input}"))?;
let safekey = ref_.safekey();
let store_root = resolve_store_root()?;
let store = FsStore::new(store_root)?;
let metadata = store
.read(&safekey)
.with_context(|| format!("failed to read store entry for {input}"))?;
let metadata = match metadata {
Some(m) => m,
None => bail!("no entry for {input}"),
};
let item = build_csl_item(safekey.as_str(), &metadata);
let array = vec![item];
let json =
serde_json::to_string_pretty(&array).context("failed to serialize CSL JSON for stdout")?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
writeln!(out, "{json}").context("failed to write CSL JSON to stdout")?;
Ok(())
}
#[derive(Debug, Serialize)]
struct CslItem<'a> {
id: &'a str,
#[serde(rename = "type")]
type_: &'static str,
title: &'a str,
#[serde(skip_serializing_if = "Vec::is_empty")]
author: Vec<CslName>,
#[serde(skip_serializing_if = "Option::is_none")]
issued: Option<CslIssued>,
#[serde(rename = "DOI", skip_serializing_if = "Option::is_none")]
doi: Option<&'a str>,
#[serde(rename = "container-title", skip_serializing_if = "Option::is_none")]
container_title: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
publisher: Option<&'a str>,
#[serde(rename = "ISSN", skip_serializing_if = "Option::is_none")]
issn: Option<&'a str>,
}
#[derive(Debug, Serialize)]
struct CslName {
#[serde(skip_serializing_if = "String::is_empty")]
family: String,
#[serde(skip_serializing_if = "String::is_empty")]
given: String,
}
#[derive(Debug, Serialize)]
struct CslIssued {
#[serde(rename = "date-parts")]
date_parts: Vec<Vec<i32>>,
}
fn build_csl_item<'a>(citation_key: &'a str, m: &'a Metadata) -> CslItem<'a> {
CslItem {
id: citation_key,
type_: match m.type_.as_deref() {
Some("journal-article") => "article-journal",
_ => "manuscript",
},
title: &m.title,
author: m.authors.iter().map(|s| parse_author(s)).collect(),
issued: m.year.map(|y| CslIssued {
date_parts: vec![vec![y]],
}),
doi: m.doi.as_ref().map(|d| d.as_str()),
container_title: m.venue.as_deref(),
publisher: m.publisher.as_deref(),
issn: m.issn.as_deref(),
}
}
fn parse_author(name: &str) -> CslName {
let trimmed = name.trim();
if let Some((family, given)) = trimmed.split_once(',') {
CslName {
family: family.trim().to_string(),
given: given.trim().to_string(),
}
} else if let Some(idx) = trimmed.rfind(char::is_whitespace) {
let (given, family) = trimmed.split_at(idx);
CslName {
family: family.trim().to_string(),
given: given.trim().to_string(),
}
} else {
CslName {
family: trimmed.to_string(),
given: String::new(),
}
}
}