nix_nar/
debug.rs

1//! Debugging utilities.
2
3use std::io::Read;
4
5use camino::Utf8PathBuf;
6
7use crate::{Content, Decoder};
8
9#[derive(Debug)]
10pub enum ReadContent {
11    Directory,
12    Symlink {
13        target: Utf8PathBuf,
14    },
15    File {
16        executable: bool,
17        size: u64,
18        offset: u64,
19        data: Vec<u8>,
20    },
21}
22
23/// Pretty print the content of the given [`Decoder`]. The output of
24/// this function is unstable.
25///
26/// Example output:
27///
28/// ```text
29/// ROOT
30/// ├── 01-an-empty-file: executable=false, size=0, offset=240, data=''
31/// ├── 02-some-dir
32/// │   ├── link-to-an-empty-file -> ../01-an-empty-file
33/// │   ├── more-depth
34/// │   │   ├── deep-empty-file: executable=false, size=0, offset=944, data=''
35/// │   ├── small-file: executable=false, size=21, offset=1168, data='This is a test file.\n'
36/// ├── 03-executable-file: executable=true, size=0, offset=1456, data=''
37/// ```
38///
39/// # Panics
40///
41/// Panics if file data contains invalid UTF-8.
42// Takes owned Decoder for backward compatibility with existing API.
43#[allow(clippy::needless_pass_by_value)]
44pub fn pretty_print_nar_content<R: Read>(dec: Decoder<R>) -> String {
45    use ReadContent as RC;
46    let mut res = vec![];
47    for (path, content) in decode_nar(&dec) {
48        let path = match path {
49            None => "ROOT".into(),
50            Some(path) => {
51                let path = path.to_string();
52                let components: Vec<&str> =
53                    path.split(std::path::MAIN_SEPARATOR).collect();
54                match components.last() {
55                    Some(basename) => {
56                        let depth = components.len();
57                        let mut indented = String::new();
58                        for _ in 0..depth - 1 {
59                            indented.push_str("│   ");
60                        }
61                        indented.push_str("├── ");
62                        indented.push_str(basename);
63                        indented
64                    }
65                    None => String::new(),
66                }
67            }
68        };
69        res.push(match content {
70            RC::Directory => path,
71            RC::Symlink { target } => format!("{path} -> {target}"),
72            RC::File {
73                executable,
74                size,
75                offset,
76                data,
77            } => format!(
78                "{path}: executable={executable}, size={size}, offset={offset}, data='{}'",
79                std::str::from_utf8(&data)
80                    .unwrap()
81                    .escape_default()
82            ),
83        });
84    }
85    res.join("\n")
86}
87
88fn decode_nar<R: Read>(dec: &Decoder<R>) -> Vec<(Option<Utf8PathBuf>, ReadContent)> {
89    let mut res = vec![];
90    for entry in dec.entries().unwrap() {
91        let entry = entry.unwrap();
92        let read_content = match entry.content {
93            Content::Symlink { target } => ReadContent::Symlink { target },
94            Content::Directory => ReadContent::Directory,
95            Content::File {
96                executable,
97                size,
98                offset,
99                mut data,
100            } => {
101                let mut read_data: Vec<u8> = vec![];
102                data.read_to_end(&mut read_data).unwrap();
103                ReadContent::File {
104                    executable,
105                    size,
106                    offset,
107                    data: read_data,
108                }
109            }
110        };
111        res.push((entry.path, read_content));
112    }
113    res
114}