`cd_into_same_dir_as`: allow the proper IO error to bubble up and add docs
[ouch.git] / src / utils.rs
blob5116ccb2e29ca44ae06833e0160ec2668cbfbcb2
1 use std::{
2     cmp, env,
3     ffi::OsStr,
4     fs,
5     path::{Path, PathBuf},
6 };
8 use crate::{dialogs::Confirmation, info, oof};
10 pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
11     if !path.exists() {
12         fs::create_dir_all(path)?;
13         info!("directory {} created.", to_utf(path));
14     }
15     Ok(())
18 /// Changes the process' current directory to the directory that contains the
19 /// file pointed to by `filename` and returns the directory that the process
20 /// was in before this function was called.
21 pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
22     let previous_location = env::current_dir()?;
24     let parent = filename.parent().ok_or(crate::Error::CompressingRootFolder)?;
26     env::set_current_dir(parent)?;
28     Ok(previous_location)
31 pub fn user_wants_to_overwrite(path: &Path, flags: &oof::Flags) -> crate::Result<bool> {
32     match (flags.is_present("yes"), flags.is_present("no")) {
33         (true, true) => {
34             unreachable!("This should've been cutted out in the ~/src/cli.rs filter flags function.")
35         }
36         (true, _) => return Ok(true),
37         (_, true) => return Ok(false),
38         _ => {}
39     }
41     let file_path_str = to_utf(path);
43     const OVERWRITE_CONFIRMATION_QUESTION: Confirmation =
44         Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
46     OVERWRITE_CONFIRMATION_QUESTION.ask(Some(&file_path_str))
49 pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
50     let text = format!("{:?}", os_str.as_ref());
51     text.trim_matches('"').to_string()
54 pub struct Bytes {
55     bytes: f64,
58 /// Module with a list of bright colors.
59 #[allow(dead_code)]
60 #[cfg(target_family = "unix")]
61 pub mod colors {
62     pub const fn reset() -> &'static str {
63         "\u{1b}[39m"
64     }
65     pub const fn black() -> &'static str {
66         "\u{1b}[38;5;8m"
67     }
68     pub const fn blue() -> &'static str {
69         "\u{1b}[38;5;12m"
70     }
71     pub const fn cyan() -> &'static str {
72         "\u{1b}[38;5;14m"
73     }
74     pub const fn green() -> &'static str {
75         "\u{1b}[38;5;10m"
76     }
77     pub const fn magenta() -> &'static str {
78         "\u{1b}[38;5;13m"
79     }
80     pub const fn red() -> &'static str {
81         "\u{1b}[38;5;9m"
82     }
83     pub const fn white() -> &'static str {
84         "\u{1b}[38;5;15m"
85     }
86     pub const fn yellow() -> &'static str {
87         "\u{1b}[38;5;11m"
88     }
90 // Windows does not support ANSI escape codes
91 #[allow(dead_code, non_upper_case_globals)]
92 #[cfg(not(target_family = "unix"))]
93 pub mod colors {
94     pub fn empty() -> &'static str {
95         ""
96     }
97     pub const reset: fn() -> &'static str = empty;
98     pub const black: fn() -> &'static str = empty;
99     pub const blue: fn() -> &'static str = empty;
100     pub const cyan: fn() -> &'static str = empty;
101     pub const green: fn() -> &'static str = empty;
102     pub const magenta: fn() -> &'static str = empty;
103     pub const red: fn() -> &'static str = empty;
104     pub const white: fn() -> &'static str = empty;
105     pub const yellow: fn() -> &'static str = empty;
108 impl Bytes {
109     const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
111     pub fn new(bytes: u64) -> Self {
112         Self { bytes: bytes as f64 }
113     }
116 impl std::fmt::Display for Bytes {
117     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118         let num = self.bytes;
119         debug_assert!(num >= 0.0);
120         if num < 1_f64 {
121             return write!(f, "{} B", num);
122         }
123         let delimiter = 1000_f64;
124         let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4);
126         write!(f, "{:.2} ", num / delimiter.powi(exponent))?;
127         write!(f, "{}B", Bytes::UNIT_PREFIXES[exponent as usize])
128     }
131 #[cfg(test)]
132 mod tests {
133     use super::*;
135     #[test]
136     fn test_pretty_bytes_formatting() {
137         fn format_bytes(bytes: u64) -> String {
138             format!("{}", Bytes::new(bytes))
139         }
140         let b = 1;
141         let kb = b * 1000;
142         let mb = kb * 1000;
143         let gb = mb * 1000;
145         assert_eq!("0 B", format_bytes(0)); // This is weird
146         assert_eq!("1.00 B", format_bytes(b));
147         assert_eq!("999.00 B", format_bytes(b * 999));
148         assert_eq!("12.00 MB", format_bytes(mb * 12));
149         assert_eq!("123.00 MB", format_bytes(mb * 123));
150         assert_eq!("5.50 MB", format_bytes(mb * 5 + kb * 500));
151         assert_eq!("7.54 GB", format_bytes(gb * 7 + 540 * mb));
152         assert_eq!("1.20 TB", format_bytes(gb * 1200));
154         // bytes
155         assert_eq!("234.00 B", format_bytes(234));
156         assert_eq!("999.00 B", format_bytes(999));
157         // kilobytes
158         assert_eq!("2.23 kB", format_bytes(2234));
159         assert_eq!("62.50 kB", format_bytes(62500));
160         assert_eq!("329.99 kB", format_bytes(329990));
161         // megabytes
162         assert_eq!("2.75 MB", format_bytes(2750000));
163         assert_eq!("55.00 MB", format_bytes(55000000));
164         assert_eq!("987.65 MB", format_bytes(987654321));
165         // gigabytes
166         assert_eq!("5.28 GB", format_bytes(5280000000));
167         assert_eq!("95.20 GB", format_bytes(95200000000));
168         assert_eq!("302.00 GB", format_bytes(302000000000));
169     }