1use super::{Formatter, PathDisplayMode, StreamingFormatter};
2use crate::emoji_mapper;
3use crate::scanner::{FileNode, TreeStats};
4use anyhow::Result;
5use std::io::Write;
6use std::path::Path;
7
8pub struct HexFormatter {
9 pub use_color: bool,
10 pub no_emoji: bool,
11 pub show_ignored: bool,
12 pub path_mode: PathDisplayMode,
13 pub show_filesystems: bool,
14}
15
16impl HexFormatter {
17 pub fn new(
18 use_color: bool,
19 no_emoji: bool,
20 show_ignored: bool,
21 path_mode: PathDisplayMode,
22 show_filesystems: bool,
23 ) -> Self {
24 Self {
25 use_color,
26 no_emoji,
27 show_ignored,
28 path_mode,
29 show_filesystems,
30 }
31 }
32
33 fn get_file_emoji(&self, node: &FileNode) -> &'static str {
36 emoji_mapper::get_file_emoji(node, self.no_emoji)
37 }
38
39 fn format_node(&self, node: &FileNode, root_path: &Path) -> String {
40 let depth_hex = format!("{:x}", node.depth);
41 let perms_hex = format!("{:03x}", node.permissions);
42 let uid_hex = format!("{:04x}", node.uid);
43 let gid_hex = format!("{:04x}", node.gid);
44 let size_hex = if node.is_dir {
45 format!("{:08x}", 0)
46 } else {
47 format!("{:08x}", node.size)
48 };
49 let time_hex = format!(
50 "{:08x}",
51 node.modified
52 .duration_since(std::time::UNIX_EPOCH)
53 .unwrap_or_default()
54 .as_secs()
55 );
56
57 let emoji = self.get_file_emoji(node);
58
59 let fs_indicator = if self.show_filesystems && node.filesystem_type.should_show_by_default()
61 {
62 format!("{} ", node.filesystem_type.to_char())
63 } else {
64 String::new()
65 };
66
67 let name = match self.path_mode {
69 PathDisplayMode::Off => node
70 .path
71 .file_name()
72 .unwrap_or(node.path.as_os_str())
73 .to_string_lossy()
74 .to_string(),
75 PathDisplayMode::Relative => {
76 if node.path == root_path {
77 node.path
78 .file_name()
79 .unwrap_or(node.path.as_os_str())
80 .to_string_lossy()
81 .to_string()
82 } else {
83 node.path
84 .strip_prefix(root_path)
85 .unwrap_or(&node.path)
86 .to_string_lossy()
87 .to_string()
88 }
89 }
90 PathDisplayMode::Full => node.path.display().to_string(),
91 };
92
93 let display_name = if node.permission_denied || node.is_ignored {
95 format!("[{}]", name)
96 } else {
97 name
98 };
99
100 let display_name = if let Some(ref branch) = node.git_branch {
102 format!("{} [{}]", display_name, branch)
103 } else {
104 display_name
105 };
106
107 let display_name_with_search = if let Some(matches) = &node.search_matches {
109 if matches.total_count > 0 {
110 let (line, col) = matches.first_match;
112 let truncated_indicator = if matches.truncated { ",TRUNCATED" } else { "" };
113
114 if matches.total_count > 1 {
115 format!(
116 "{} [SEARCH:L{:x}:C{:x},{:x}x{}]",
117 display_name, line, col, matches.total_count, truncated_indicator
118 )
119 } else {
120 format!("{} [SEARCH:L{:x}:C{:x}]", display_name, line, col)
121 }
122 } else {
123 display_name
124 }
125 } else {
126 display_name
127 };
128
129 if self.use_color {
130 const CYAN: &str = "\x1b[36m";
132 const YELLOW: &str = "\x1b[33m";
133 const MAGENTA: &str = "\x1b[35m";
134 const GREEN: &str = "\x1b[32m";
135 const BLUE: &str = "\x1b[34m";
136 const RESET: &str = "\x1b[0m";
137
138 format!(
139 "{}{}{} {}{}{} {}{} {}{} {}{}{} {}{}{} {}{} {}",
140 CYAN,
141 depth_hex,
142 RESET,
143 YELLOW,
144 perms_hex,
145 RESET,
146 MAGENTA,
147 uid_hex,
148 gid_hex,
149 RESET,
150 GREEN,
151 size_hex,
152 RESET,
153 BLUE,
154 time_hex,
155 RESET,
156 fs_indicator,
157 emoji,
158 display_name_with_search
159 )
160 } else {
161 format!(
162 "{} {} {} {} {} {} {}{} {}",
163 depth_hex,
164 perms_hex,
165 uid_hex,
166 gid_hex,
167 size_hex,
168 time_hex,
169 fs_indicator,
170 emoji,
171 display_name_with_search
172 )
173 }
174 }
175}
176
177impl Formatter for HexFormatter {
178 fn format(
179 &self,
180 writer: &mut dyn Write,
181 nodes: &[FileNode],
182 _stats: &TreeStats,
183 root_path: &Path,
184 ) -> Result<()> {
185 let mut sorted_nodes = nodes.to_vec();
187 sorted_nodes.sort_by(|a, b| a.path.cmp(&b.path));
188
189 for node in &sorted_nodes {
190 writeln!(writer, "{}", self.format_node(node, root_path))?;
191 }
192
193 Ok(())
194 }
195}
196
197impl StreamingFormatter for HexFormatter {
198 fn start_stream(&self, _writer: &mut dyn Write, _root_path: &Path) -> Result<()> {
199 Ok(())
201 }
202
203 fn format_node(&self, writer: &mut dyn Write, node: &FileNode, root_path: &Path) -> Result<()> {
204 writeln!(writer, "{}", self.format_node(node, root_path))?;
205 writer.flush()?; Ok(())
207 }
208
209 fn end_stream(
210 &self,
211 _writer: &mut dyn Write,
212 _stats: &TreeStats,
213 _root_path: &Path,
214 ) -> Result<()> {
215 Ok(())
217 }
218}