use std::path::Path;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Source {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub urls: Option<Vec<String>>,
}
impl Source {
pub fn try_read(path: &Path) -> anyhow::Result<Self> {
let content = if path.to_string_lossy() == "-" {
std::io::read_to_string(std::io::stdin())
.map_err(|error| anyhow::anyhow!("<stdin> reading: {error}"))
} else {
std::fs::read_to_string(path)
.map_err(|error| anyhow::anyhow!("File {path:?} reading: {error}"))
}?;
Ok(Self {
content: Some(content),
urls: None,
})
}
pub fn try_resolve(&mut self) -> anyhow::Result<()> {
match (self.content.as_ref(), self.urls.as_ref()) {
(Some(_), None) => Ok(()),
(None, Some(urls)) => {
let mut errors = Vec::with_capacity(urls.len());
for url in urls.iter() {
let url_path = PathBuf::from(url);
match Source::try_read(url_path.as_path()) {
Ok(resolved) => {
*self = resolved;
break;
}
Err(error) => errors.push(error),
}
}
if !errors.is_empty() {
anyhow::bail!(
"{}",
errors
.into_iter()
.map(|error| error.to_string())
.collect::<Vec<String>>()
.join("\n")
);
}
Ok(())
}
(Some(_), Some(_)) => anyhow::bail!("Both `content` and `urls` cannot be set."),
(None, None) => anyhow::bail!("Either `content` or `urls` must be set."),
}
}
pub fn take_content(&mut self) -> Option<String> {
self.content.take()
}
pub fn content(&self) -> Option<&str> {
self.content.as_deref()
}
}
impl From<String> for Source {
fn from(content: String) -> Self {
Self {
content: Some(content),
urls: None,
}
}
}
impl From<&Path> for Source {
fn from(path: &Path) -> Self {
Self {
content: None,
urls: Some(vec![path.to_string_lossy().to_string()]),
}
}
}