hg_parser/
manifest.rs

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/// Manifest entry for file. Contains revision hash and file metainformation.
29#[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/// File meta information.
74#[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/// File type. Can be regular, executable, or symlink.
90#[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}