1#![allow(dead_code)]
2
3#[cfg(feature = "log")]
4mod log;
5
6#[cfg(feature = "log")]
7pub use crate::log::{initialize_log, write_log};
8
9use std::collections::hash_map;
10use std::ffi::OsString;
11use std::fmt;
12use std::fs::{self, File, OpenOptions};
13use std::hash::{Hash, Hasher};
14use std::io::{self, Read, Seek, SeekFrom, Write};
15use std::path::{Path, PathBuf};
16use std::str::FromStr;
17
18#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
32pub enum WriteMode {
33 AlwaysAppend,
34 AppendOrCreate,
35 CreateOrTruncate,
36 AlwaysCreate,
37 Atomic,
38}
39
40impl From<WriteMode> for OpenOptions {
41 fn from(m: WriteMode) -> OpenOptions {
42 let mut result = OpenOptions::new();
43
44 match m {
45 WriteMode::AlwaysAppend => { result.append(true); },
46 WriteMode::AppendOrCreate => { result.append(true).create(true); },
47 WriteMode::CreateOrTruncate | WriteMode::Atomic => { result.write(true).truncate(true).create(true); },
48 WriteMode::AlwaysCreate => { result.write(true).create_new(true); },
49 }
50
51 result
52 }
53}
54
55pub fn read_bytes_offset(path: &str, from: u64, to: u64) -> Result<Vec<u8>, FileError> {
59 assert!(to >= from);
60
61 match File::open(path) {
62 Err(e) => Err(FileError::from_std(e, path)),
63 Ok(mut f) => match f.seek(SeekFrom::Start(from)) {
64 Err(e) => Err(FileError::from_std(e, path)),
65 Ok(_) => {
66 let mut handle = f.take(to - from);
67 let mut buffer = Vec::with_capacity((to - from) as usize);
68
69 if let Err(e) = handle.read_to_end(&mut buffer) {
70 return Err(FileError::from_std(e, path));
71 }
72
73 Ok(buffer)
74 },
75 },
76 }
77}
78
79pub fn read_bytes(path: &str) -> Result<Vec<u8>, FileError> {
80 fs::read(path).map_err(|e| FileError::from_std(e, path))
81}
82
83pub fn read_string(path: &str) -> Result<String, FileError> {
84 let mut s = String::new();
85
86 match File::open(path) {
87 Err(e) => Err(FileError::from_std(e, path)),
88 Ok(mut f) => match f.read_to_string(&mut s) {
89 Ok(_) => Ok(s),
90 Err(e) => Err(FileError::from_std(e, path)),
91 }
92 }
93}
94
95pub fn write_bytes(path: &str, bytes: &[u8], write_mode: WriteMode) -> Result<(), FileError> {
96 let option: OpenOptions = write_mode.into();
97
98 if let WriteMode::Atomic = write_mode {
99 let tmp_path = format!("{path}_tmp__{:x}", rand::random::<u64>());
102
103 match option.open(&tmp_path) {
104 Ok(mut f) => match f.write_all(bytes) {
105 Ok(_) => match rename(&tmp_path, path) {
106 Ok(_) => Ok(()),
107 Err(e) => {
108 remove_file(&tmp_path)?;
109 Err(e)
110 },
111 },
112 Err(e) => {
113 remove_file(&tmp_path)?;
114 Err(FileError::from_std(e, path))
115 },
116 },
117 Err(e) => Err(FileError::from_std(e, path)),
118 }
119 } else {
120 match option.open(path) {
121 Ok(mut f) => match f.write_all(bytes) {
122 Ok(_) => Ok(()),
123 Err(e) => Err(FileError::from_std(e, path)),
124 },
125 Err(e) => Err(FileError::from_std(e, path)),
126 }
127 }
128}
129
130pub fn write_string(path: &str, s: &str, write_mode: WriteMode) -> Result<(), FileError> {
131 write_bytes(path, s.as_bytes(), write_mode)
132}
133
134pub fn file_name(path: &str) -> Result<String, FileError> {
136 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.file_stem() {
139 None => Ok(String::new()),
140 Some(s) => match s.to_str() {
141 Some(ext) => Ok(ext.to_string()),
142 None => Err(FileError::os_str_err(s.to_os_string())),
143 }
144 }
145}
146
147pub fn extension(path: &str) -> Result<Option<String>, FileError> {
149 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.extension() {
152 None => Ok(None),
153 Some(s) => match s.to_str() {
154 Some(ext) => Ok(Some(ext.to_string())),
155 None => Err(FileError::os_str_err(s.to_os_string())),
156 }
157 }
158}
159
160pub fn basename(path: &str) -> Result<String, FileError> {
162 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.file_name() {
165 None => Ok(String::new()), Some(s) => match s.to_str() {
167 Some(ext) => Ok(ext.to_string()),
168 None => Err(FileError::os_str_err(s.to_os_string())),
169 }
170 }
171}
172
173pub fn join(path: &str, child: &str) -> Result<String, FileError> {
175 let mut path_buf = PathBuf::from_str(path).unwrap(); let child = PathBuf::from_str(child).unwrap(); path_buf.push(child);
179
180 match path_buf.to_str() {
181 Some(result) => Ok(result.to_string()),
182 None => Err(FileError::os_str_err(path_buf.into_os_string())),
183 }
184}
185
186pub fn temp_dir() -> Result<String, FileError> {
187 let temp_dir = std::env::temp_dir();
188
189 match temp_dir.to_str() {
190 Some(result) => Ok(result.to_string()),
191 None => Err(FileError::os_str_err(temp_dir.into_os_string())),
192 }
193}
194
195#[inline]
197pub fn join2(path: &str, child: &str) -> Result<String, FileError> {
198 join(path, child)
199}
200
201pub fn join3(path1: &str, path2: &str, path3: &str) -> Result<String, FileError> {
202 join(
203 path1,
204 &join(path2, path3)?,
205 )
206}
207
208pub fn join4(path1: &str, path2: &str, path3: &str, path4: &str) -> Result<String, FileError> {
209 join(
210 &join(path1, path2)?,
211 &join(path3, path4)?,
212 )
213}
214
215pub fn join5(path1: &str, path2: &str, path3: &str, path4: &str, path5: &str) -> Result<String, FileError> {
216 join(
217 &join(path1, path2)?,
218 &join(path3, &join(path4, path5)?)?,
219 )
220}
221
222pub fn set_extension(path: &str, ext: &str) -> Result<String, FileError> {
224 let mut path_buf = PathBuf::from_str(path).unwrap(); if path_buf.set_extension(ext) {
227 match path_buf.to_str() {
228 Some(result) => Ok(result.to_string()),
229 None => Err(FileError::os_str_err(path_buf.into_os_string())),
230 }
231 } else {
232 Ok(path.to_string())
234 }
235}
236
237pub fn is_dir(path: &str) -> bool {
239 PathBuf::from_str(path).map(|path| path.is_dir()).unwrap_or(false)
240}
241
242pub fn is_symlink(path: &str) -> bool {
244 PathBuf::from_str(path).map(|path| path.is_symlink()).unwrap_or(false)
245}
246
247pub fn exists(path: &str) -> bool {
248 PathBuf::from_str(path).map(|path| path.exists()).unwrap_or(false)
249}
250
251pub fn parent(path: &str) -> Result<String, FileError> {
253 let std_path = Path::new(path);
254
255 std_path.parent().map(
256 |p| p.to_string_lossy().to_string()
257 ).ok_or_else(
258 || FileError::unknown(
259 String::from("function `parent` died"),
260 Some(path.to_string()),
261 )
262 )
263}
264
265pub fn try_create_dir(path: &str) -> Result<(), FileError> {
267 match fs::create_dir(path) {
268 Ok(()) => Ok(()),
269 Err(e) => match e.kind() {
270 io::ErrorKind::AlreadyExists => Ok(()),
271 _ => Err(FileError::from_std(e, path)),
272 },
273 }
274}
275
276pub fn create_dir(path: &str) -> Result<(), FileError> {
277 fs::create_dir(path).map_err(|e| FileError::from_std(e, path))
278}
279
280pub fn create_dir_all(path: &str) -> Result<(), FileError> {
281 fs::create_dir_all(path).map_err(|e| FileError::from_std(e, path))
282}
283
284pub fn rename(from: &str, to: &str) -> Result<(), FileError> {
285 fs::rename(from, to).map_err(|e| FileError::from_std(e, from))
286}
287
288pub fn copy_dir(src: &str, dst: &str) -> Result<(), FileError> {
289 create_dir_all(dst)?;
290
291 for e in read_dir(src, false)? {
293 let new_dst = join(dst, &basename(&e)?)?;
294
295 if is_dir(&e) {
296 create_dir_all(&new_dst)?;
297 copy_dir(&e, &new_dst)?;
298 }
299
300 else {
301 copy_file(&e, &new_dst)?;
302 }
303 }
304
305 Ok(())
306}
307
308pub fn copy_file(src: &str, dst: &str) -> Result<u64, FileError> {
310 std::fs::copy(src, dst).map_err(|e| FileError::from_std(e, src)) }
312
313pub fn last_modified(path: &str) -> Result<u64, FileError> {
315 match fs::metadata(path) {
316 Ok(m) => match m.modified() {
317 Ok(m) => {
318 let mut hasher = hash_map::DefaultHasher::new();
319 m.hash(&mut hasher);
320 let hash = hasher.finish();
321
322 Ok(hash)
323 },
324 Err(e) => Err(FileError::from_std(e, path)),
325 },
326 Err(e) => Err(FileError::from_std(e, path)),
327 }
328}
329
330pub fn file_size(path: &str) -> Result<u64, FileError> {
331 match fs::metadata(path) {
332 Ok(m) => Ok(m.len()),
333 Err(e) => Err(FileError::from_std(e, path)),
334 }
335}
336
337pub fn read_dir(path: &str, sort: bool) -> Result<Vec<String>, FileError> {
338 match fs::read_dir(path) {
339 Err(e) => Err(FileError::from_std(e, path)),
340 Ok(entries) => {
341 let mut result = vec![];
342
343 for entry in entries {
344 match entry {
345 Err(e) => {
346 return Err(FileError::from_std(e, path));
347 },
348 Ok(e) => {
349 if let Some(ee) = e.path().to_str() {
350 result.push(ee.to_string());
351 }
352 },
353 }
354 }
355
356 if sort {
357 result.sort();
358 }
359
360 Ok(result)
361 }
362 }
363}
364
365pub fn remove_file(path: &str) -> Result<(), FileError> {
366 fs::remove_file(path).map_err(|e| FileError::from_std(e, path))
367}
368
369pub fn remove_dir(path: &str) -> Result<(), FileError> {
370 fs::remove_dir(path).map_err(|e| FileError::from_std(e, path))
371}
372
373pub fn remove_dir_all(path: &str) -> Result<(), FileError> {
374 fs::remove_dir_all(path).map_err(|e| FileError::from_std(e, path))
375}
376
377pub fn into_abs_path(path: &str) -> Result<String, FileError> {
378 let std_path = Path::new(path);
379
380 if std_path.is_absolute() {
381 Ok(path.to_string())
382 }
383
384 else {
385 Ok(join(
386 ¤t_dir()?,
387 path,
388 )?)
389 }
390}
391
392pub fn current_dir() -> Result<String, FileError> {
393 let cwd = std::env::current_dir().map_err(|e| FileError::from_std(e, "."))?;
394
395 match cwd.to_str() {
396 Some(cwd) => Ok(cwd.to_string()),
397 None => Err(FileError::os_str_err(cwd.into_os_string())),
398 }
399}
400
401pub fn set_current_dir(path: &str) -> Result<(), FileError> {
402 std::env::set_current_dir(path).map_err(|e| FileError::from_std(e, path))
403}
404
405#[cfg(feature = "diff")]
406pub fn diff(path: &str, base: &str) -> Result<String, FileError> {
407 match pathdiff::diff_paths(path, base) {
408 Some(path) => match path.to_str() {
409 Some(path) => Ok(path.to_string()),
410 None => Err(FileError::os_str_err(path.into_os_string())),
411 },
412 None => Err(FileError::cannot_diff_path(path.to_string(), base.to_string())),
413 }
414}
415
416#[cfg(feature = "diff")]
417pub fn get_relative_path(base: &str, path: &str) -> Result<String, FileError> {
419 Ok(normalize(&diff(
421 &normalize(
423 &into_abs_path(path)?,
424 )?,
425 &normalize(
426 &into_abs_path(base)?,
427 )?,
428 )?)?)
429}
430
431pub fn normalize(path: &str) -> Result<String, FileError> {
432 let mut result = vec![];
433 let path = path.replace("\\", "/");
434
435 for component in path.split("/") {
436 match component {
437 c if c == "." => {},
438
439 c if c == ".." => if result.is_empty() {
445 result.push(c.to_string());
446 } else {
447 let p = result.pop().unwrap().to_string();
448
449 if p == ".." {
450 result.push(p);
451 }
452 },
453
454 c => { result.push(c.to_string()); },
455 }
456 }
457
458 Ok(result.join("/"))
459}
460
461#[derive(Clone, PartialEq)]
462pub struct FileError {
463 pub kind: FileErrorKind,
464 pub given_path: Option<String>,
465}
466
467impl FileError {
468 pub fn from_std(e: io::Error, given_path: &str) -> Self {
469 let kind = match e.kind() {
470 io::ErrorKind::NotFound => FileErrorKind::FileNotFound,
471 io::ErrorKind::PermissionDenied => FileErrorKind::PermissionDenied,
472 io::ErrorKind::AlreadyExists => FileErrorKind::AlreadyExists,
473 e => FileErrorKind::Unknown(format!("unknown error: {e:?}")),
474 };
475
476 FileError {
477 kind,
478 given_path: Some(given_path.to_string()),
479 }
480 }
481
482 pub(crate) fn os_str_err(os_str: OsString) -> Self {
483 FileError {
484 kind: FileErrorKind::OsStrErr(os_str),
485 given_path: None,
486 }
487 }
488
489 pub(crate) fn cannot_diff_path(path: String, base: String) -> Self {
490 FileError {
491 kind: FileErrorKind::CannotDiffPath(path.to_string(), base),
492 given_path: Some(path),
493 }
494 }
495
496 pub fn unknown(msg: String, path: Option<String>) -> Self {
497 FileError {
498 kind: FileErrorKind::Unknown(msg),
499 given_path: path,
500 }
501 }
502
503 pub fn render_error(&self) -> String {
504 let path = self.given_path.as_ref().map(|p| p.to_string()).unwrap_or(String::new());
505
506 match &self.kind {
507 FileErrorKind::FileNotFound => format!(
508 "file not found: `{path}`"
509 ),
510 FileErrorKind::PermissionDenied => format!(
511 "permission denied: `{path}`"
512 ),
513 FileErrorKind::AlreadyExists => format!(
514 "file already exists: `{path}`"
515 ),
516 FileErrorKind::CannotDiffPath(path, base) => format!(
517 "cannot calc diff: `{path}` and `{base}`"
518 ),
519 FileErrorKind::Unknown(msg) => format!(
520 "unknown file error: `{msg}`"
521 ),
522 FileErrorKind::OsStrErr(os_str) => format!(
523 "error converting os_str: `{os_str:?}`"
524 ),
525 }
526 }
527}
528
529impl fmt::Debug for FileError {
530 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
531 write!(fmt, "{}", self.render_error())
532 }
533}
534
535impl fmt::Display for FileError {
536 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
537 write!(fmt, "{}", self.render_error())
538 }
539}
540
541#[derive(Clone, Debug, PartialEq)]
542pub enum FileErrorKind {
543 FileNotFound,
544 PermissionDenied,
545 AlreadyExists,
546 CannotDiffPath(String, String),
547 Unknown(String),
548 OsStrErr(OsString),
549}