1use std::{
2 collections::BTreeMap,
3 fmt::{self, Debug},
4 str,
5 sync::Arc,
6};
7
8use crate::{error::ErrorKind, types::NodeHash};
9
10pub(crate) struct Manifest {
11 pub files: BTreeMap<Vec<u8>, ManifestEntry>,
12}
13
14impl Debug for Manifest {
15 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
16 write!(
17 fmt,
18 "Manifest(\nfiles:\n{}\n)",
19 self.files
20 .iter()
21 .map(|(key, value)| format!("{}: {:?}", str::from_utf8(key).unwrap(), value))
22 .collect::<Vec<_>>()
23 .join("\n")
24 )
25 }
26}
27
28#[derive(Debug, Clone, Eq, PartialEq)]
30pub struct ManifestEntry {
31 pub id: NodeHash,
32 pub details: ManifestEntryDetails,
33}
34
35impl ManifestEntry {
36 fn parse(data: &[u8]) -> Result<ManifestEntry, ErrorKind> {
37 let (hash, flags) = data.split_at(40);
38 let id: NodeHash = str::from_utf8(hash).unwrap().parse().unwrap();
39
40 let details = if flags.is_empty() {
41 ManifestEntryDetails::File(FileType::Regular)
42 } else {
43 match flags[0] {
44 b'l' => ManifestEntryDetails::File(FileType::Symlink),
45 b'x' => ManifestEntryDetails::File(FileType::Executable),
46 b't' => ManifestEntryDetails::Tree,
47 unk => return Err(ErrorKind::Manifest(format!("Unknown flag {}", unk))),
48 }
49 };
50
51 Ok(ManifestEntry { id, details })
52 }
53}
54
55impl From<Arc<[u8]>> for Manifest {
56 fn from(value: Arc<[u8]>) -> Self {
57 let mut files = BTreeMap::new();
58 for line in value.split(|&x| x == b'\n') {
59 if line.is_empty() {
60 break;
61 }
62 let mut parts = line.splitn(2, |&x| x == 0);
63 if let (Some(file), Some(rest)) = (parts.next(), parts.next()) {
64 files.insert(file.into(), ManifestEntry::parse(rest).unwrap());
65 } else {
66 panic!("wrong manifest line");
67 }
68 }
69 Manifest { files }
70 }
71}
72
73#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
75pub enum ManifestEntryDetails {
76 File(FileType),
77 Tree,
78}
79
80impl fmt::Display for ManifestEntryDetails {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 match self {
83 ManifestEntryDetails::Tree => write!(f, "tree"),
84 ManifestEntryDetails::File(ft) => write!(f, "{}", ft),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
91pub enum FileType {
92 Regular,
93 Executable,
94 Symlink,
95}
96
97impl fmt::Display for FileType {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 let s = match self {
100 FileType::Symlink => "symlink",
101 FileType::Executable => "executable",
102 FileType::Regular => "regular",
103 };
104 write!(f, "{}", s)
105 }
106}