use std::{convert::TryFrom as _, str::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,
};
pub struct Tree {
pub path: String,
pub entries: Vec<TreeEntry>,
pub info: Info,
}
impl Serialize for Tree {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Tree", 3)?;
state.serialize_field("path", &self.path)?;
state.serialize_field("entries", &self.entries)?;
state.serialize_field("info", &self.info)?;
state.end()
}
}
pub struct TreeEntry {
pub info: Info,
pub path: String,
}
impl Serialize for TreeEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Tree", 2)?;
state.serialize_field("path", &self.path)?;
state.serialize_field("info", &self.info)?;
state.end()
}
}
pub fn tree<P>(
browser: &mut Browser<'_>,
maybe_revision: Option<Revision<P>>,
maybe_prefix: Option<String>,
) -> Result<Tree, Error>
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
let prefix = maybe_prefix.unwrap_or_default();
if let Some(revision) = maybe_revision {
browser.rev(revision)?;
}
let path = if prefix == "/" || prefix.is_empty() {
file_system::Path::root()
} else {
file_system::Path::from_str(&prefix)?
};
let root_dir = browser.get_directory()?;
let prefix_dir = if path.is_root() {
root_dir
} else {
root_dir
.find_directory(path.clone())
.ok_or_else(|| Error::PathNotFound(path.clone()))?
};
let mut prefix_contents = prefix_dir.list_directory();
prefix_contents.sort();
let entries_results: Result<Vec<TreeEntry>, Error> = prefix_contents
.iter()
.map(|(label, system_type)| {
let entry_path = if path.is_root() {
file_system::Path::new(label.clone())
} else {
let mut p = path.clone();
p.push(label.clone());
p
};
let mut commit_path = file_system::Path::root();
commit_path.append(entry_path.clone());
let info = Info {
name: label.to_string(),
object_type: match system_type {
file_system::SystemType::Directory => ObjectType::Tree,
file_system::SystemType::File => ObjectType::Blob,
},
last_commit: None,
};
Ok(TreeEntry {
info,
path: entry_path.to_string(),
})
})
.collect();
let mut entries = entries_results?;
entries.sort_by(|a, b| a.info.object_type.cmp(&b.info.object_type));
let last_commit = if path.is_root() {
Some(commit::Header::from(browser.get().first()))
} else {
None
};
let name = if path.is_root() {
"".into()
} else {
let (_first, last) = path.split_last();
last.to_string()
};
let info = Info {
name,
object_type: ObjectType::Tree,
last_commit,
};
Ok(Tree {
path: prefix,
entries,
info,
})
}