1use f00_core::{Entry, EntryKind, IndicatorStyle};
2
3pub fn format_permissions(entry: &Entry) -> String {
7 #[cfg(unix)]
8 {
9 format_unix(entry)
10 }
11 #[cfg(not(unix))]
12 {
13 format_fallback(entry)
14 }
15}
16
17#[cfg(unix)]
18fn format_unix(entry: &Entry) -> String {
19 let mode = entry.mode;
20 let file_type = match entry.kind {
21 EntryKind::Directory => 'd',
22 EntryKind::Symlink => 'l',
23 EntryKind::File => '-',
24 EntryKind::Other => match mode & 0o170000 {
25 0o140000 => 's',
26 0o010000 => 'p',
27 0o060000 => 'b',
28 0o020000 => 'c',
29 _ => '-',
30 },
31 };
32
33 let mut s = String::with_capacity(10);
34 s.push(file_type);
35 s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
37 s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
38 s.push(suid_bit(mode, 0o100, 0o4000));
39 s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
41 s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
42 s.push(sgid_bit(mode, 0o010, 0o2000));
43 s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
45 s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
46 s.push(sticky_bit(mode, 0o001, 0o1000));
47 s
48}
49
50#[cfg(unix)]
51fn suid_bit(mode: u32, exec: u32, special: u32) -> char {
52 match (mode & exec != 0, mode & special != 0) {
53 (true, true) => 's',
54 (false, true) => 'S',
55 (true, false) => 'x',
56 (false, false) => '-',
57 }
58}
59
60#[cfg(unix)]
61fn sgid_bit(mode: u32, exec: u32, special: u32) -> char {
62 suid_bit(mode, exec, special)
63}
64
65#[cfg(unix)]
66fn sticky_bit(mode: u32, exec: u32, special: u32) -> char {
67 match (mode & exec != 0, mode & special != 0) {
68 (true, true) => 't',
69 (false, true) => 'T',
70 (true, false) => 'x',
71 (false, false) => '-',
72 }
73}
74
75#[cfg(not(unix))]
76fn format_fallback(entry: &Entry) -> String {
77 let t = match entry.kind {
78 EntryKind::Directory => 'd',
79 EntryKind::Symlink => 'l',
80 EntryKind::File => '-',
81 EntryKind::Other => '-',
82 };
83 let w = if entry.readonly { '-' } else { 'w' };
84 format!("{t}r{w}xr{w}xr{w}x")
85}
86
87pub fn classify_suffix(entry: &Entry, style: IndicatorStyle) -> &'static str {
89 if matches!(style, IndicatorStyle::None) || entry.is_dir_header {
90 return "";
91 }
92 match entry.kind {
93 EntryKind::Directory => "/",
94 EntryKind::Symlink => {
95 if matches!(style, IndicatorStyle::Slash) {
96 ""
97 } else {
98 "@"
99 }
100 }
101 EntryKind::File if is_executable(entry) => {
102 if matches!(style, IndicatorStyle::Classify) {
103 "*"
104 } else {
105 ""
106 }
107 }
108 EntryKind::Other => {
109 #[cfg(unix)]
110 {
111 match entry.mode & 0o170000 {
112 0o010000 if !matches!(style, IndicatorStyle::Slash) => "|", 0o140000 if !matches!(style, IndicatorStyle::Slash) => "=", _ => "",
115 }
116 }
117 #[cfg(not(unix))]
118 {
119 ""
120 }
121 }
122 _ => "",
123 }
124}
125
126pub fn classify_suffix_bool(entry: &Entry, enabled: bool) -> &'static str {
128 classify_suffix(
129 entry,
130 if enabled {
131 IndicatorStyle::Classify
132 } else {
133 IndicatorStyle::None
134 },
135 )
136}
137
138fn is_executable(entry: &Entry) -> bool {
139 #[cfg(unix)]
140 {
141 entry.mode & 0o111 != 0
142 }
143 #[cfg(not(unix))]
144 {
145 let _ = entry;
146 false
147 }
148}