use std::io::{BufRead, Write};
use std::sync::Arc;
use anyhow::{Context, Result};
use doiget_core::source::FetchContext;
use doiget_core::sources::crossref::CrossrefSource;
use doiget_core::{RateLimits, ResolveResult};
use super::output::OutputMode;
fn build_context() -> Result<FetchContext> {
let session_id = crate::commands::fetch::new_session_id();
let log_path = crate::commands::fetch::resolve_log_path()?;
let http = Arc::new(crate::commands::fetch::build_http_client()?);
let rate_limiter = Arc::new(doiget_core::rate_limiter::RateLimiter::new(
RateLimits::HARD_CODED,
));
let log = Arc::new(
doiget_core::provenance::ProvenanceLog::open(log_path, session_id.clone())
.context("failed to open provenance log for citation resolution")?,
);
Ok(FetchContext {
http,
rate_limiter,
log,
session_id,
cache_root: None,
})
}
fn build_crossref_source() -> Result<CrossrefSource> {
let contact_email =
std::env::var("DOIGET_CONTACT_EMAIL").unwrap_or_else(|_| "doiget@localhost".to_string());
match std::env::var("DOIGET_CROSSREF_BASE").ok() {
Some(base_str) => {
let base = url::Url::parse(&base_str).context("invalid DOIGET_CROSSREF_BASE")?;
Ok(CrossrefSource::with_base(base, contact_email))
}
None => Ok(CrossrefSource::new(contact_email)),
}
}
pub async fn run(query: String, limit: u8, mode: OutputMode) -> Result<()> {
if mode == OutputMode::Quiet || mode == OutputMode::Mcp {
return Ok(());
}
let ctx = build_context()?;
let source = build_crossref_source()?;
let candidates = source
.resolve_citation(&query, limit, &ctx)
.await
.context("failed to resolve citation via Crossref")?;
let result = ResolveResult { query, candidates };
let s = serde_json::to_string_pretty(&result)?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
writeln!(out, "{s}").context("failed to write resolution result to stdout")?;
Ok(())
}
pub async fn run_batch(limit: u8, mode: OutputMode) -> Result<()> {
let queries: Vec<String> = {
let stdin = std::io::stdin();
stdin
.lock()
.lines()
.collect::<std::io::Result<_>>()
.context("failed to read lines from stdin")?
};
let ctx = build_context()?;
let source = build_crossref_source()?;
for raw in queries {
let query = raw.trim().to_string();
if query.is_empty() {
continue;
}
let candidates = source
.resolve_citation(&query, limit, &ctx)
.await
.context("failed to resolve citation in batch via Crossref")?;
if mode != OutputMode::Quiet {
let result = ResolveResult { query, candidates };
let s = serde_json::to_string(&result)?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
writeln!(out, "{s}").context("failed to write batch resolution result to stdout")?;
out.flush().context("failed to flush stdout")?;
}
}
Ok(())
}