use std::{
convert::TryFrom as _,
str::{self, FromStr as _},
};
use serde::{
ser::{SerializeStruct as _, Serializer},
Serialize,
};
use radicle_surf::{
file_system,
vcs::git::{Browser, Rev},
};
use crate::{
commit,
error::Error,
object::{Info, ObjectType},
revision::Revision,
};
#[cfg(feature = "syntax")]
use crate::syntax;
pub struct Blob {
pub content: BlobContent,
pub info: Info,
pub path: String,
}
impl Blob {
#[must_use]
pub fn is_binary(&self) -> bool {
self.content == BlobContent::Binary
}
#[must_use]
pub const fn is_html(&self) -> bool {
matches!(self.content, BlobContent::Html(_))
}
}
impl Serialize for Blob {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Blob", 5)?;
state.serialize_field("binary", &self.is_binary())?;
state.serialize_field("html", &self.is_html())?;
state.serialize_field("content", &self.content)?;
state.serialize_field("info", &self.info)?;
state.serialize_field("path", &self.path)?;
state.end()
}
}
#[derive(PartialEq)]
pub enum BlobContent {
Ascii(String),
Html(String),
Binary,
}
impl Serialize for BlobContent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Ascii(content) | Self::Html(content) => serializer.serialize_str(content),
Self::Binary => serializer.serialize_none(),
}
}
}
pub fn blob<P>(
browser: &mut Browser,
maybe_revision: Option<Revision<P>>,
path: &str,
) -> Result<Blob, Error>
where
P: ToString,
{
make_blob(browser, maybe_revision, path, ascii)
}
fn make_blob<P, C>(
browser: &mut Browser,
maybe_revision: Option<Revision<P>>,
path: &str,
content: C,
) -> Result<Blob, Error>
where
P: ToString,
C: FnOnce(&[u8]) -> BlobContent,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
if let Some(revision) = maybe_revision {
browser.rev(revision)?;
}
let root = browser.get_directory()?;
let p = file_system::Path::from_str(path)?;
let file = root
.find_file(p.clone())
.ok_or_else(|| Error::PathNotFound(p.clone()))?;
let mut commit_path = file_system::Path::root();
commit_path.append(p.clone());
let last_commit = browser
.last_commit(commit_path)?
.map(|c| commit::Header::from(&c));
let (_rest, last) = p.split_last();
let content = content(&file.contents);
Ok(Blob {
content,
info: Info {
name: last.to_string(),
object_type: ObjectType::Blob,
last_commit,
},
path: path.to_string(),
})
}
fn ascii(content: &[u8]) -> BlobContent {
match str::from_utf8(content) {
Ok(content) => BlobContent::Ascii(content.to_owned()),
Err(_) => BlobContent::Binary,
}
}
#[cfg(feature = "syntax")]
pub mod highlighting {
use super::*;
pub fn blob<P>(
browser: &mut Browser,
maybe_revision: Option<Revision<P>>,
path: &str,
theme: Option<&str>,
) -> Result<Blob, Error>
where
P: ToString,
{
make_blob(browser, maybe_revision, path, |contents| {
content(path, contents, theme)
})
}
fn content(path: &str, content: &[u8], theme_name: Option<&str>) -> BlobContent {
let content = match str::from_utf8(content) {
Ok(content) => content,
Err(_) => return BlobContent::Binary,
};
match theme_name {
None => BlobContent::Ascii(content.to_owned()),
Some(theme) => syntax::highlight(path, content, theme)
.map_or_else(|| BlobContent::Ascii(content.to_owned()), BlobContent::Html),
}
}
}