add `#![warn(missing_docs)]` to `check.rs`
[ouch.git] / src / check.rs
blobb5b5f46f36017b58e41b1432ada8e1977946fa85
1 //! Checks for errors.
3 #![warn(missing_docs)]
5 use std::{
6     ops::ControlFlow,
7     path::{Path, PathBuf},
8 };
10 use crate::{
11     error::FinalError,
12     extension,
13     extension::Extension,
14     info,
15     utils::{pretty_format_list_of_paths, try_infer_extension, user_wants_to_continue, EscapedPathDisplay},
16     warning, QuestionAction, QuestionPolicy, Result,
19 /// Check, for each file, if the mime type matches the detected extensions.
20 ///
21 /// In case the file doesn't has any extensions, try to infer the format.
22 ///
23 /// TODO: maybe the name of this should be "magic numbers" or "file signature",
24 /// and not MIME.
25 pub fn check_mime_type(
26     files: &[PathBuf],
27     formats: &mut [Vec<Extension>],
28     question_policy: QuestionPolicy,
29 ) -> Result<ControlFlow<()>> {
30     for (path, format) in files.iter().zip(formats.iter_mut()) {
31         if format.is_empty() {
32             // File with no extension
33             // Try to detect it automatically and prompt the user about it
34             if let Some(detected_format) = try_infer_extension(path) {
35                 // Inferring the file extension can have unpredicted consequences (e.g. the user just
36                 // mistyped, ...) which we should always inform the user about.
37                 info!(
38                     accessible,
39                     "Detected file: `{}` extension as `{}`",
40                     path.display(),
41                     detected_format
42                 );
43                 if user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? {
44                     format.push(detected_format);
45                 } else {
46                     return Ok(ControlFlow::Break(()));
47                 }
48             }
49         } else if let Some(detected_format) = try_infer_extension(path) {
50             // File ending with extension
51             // Try to detect the extension and warn the user if it differs from the written one
52             let outer_ext = format.iter().next_back().unwrap();
53             if !outer_ext
54                 .compression_formats
55                 .ends_with(detected_format.compression_formats)
56             {
57                 warning!(
58                     "The file extension: `{}` differ from the detected extension: `{}`",
59                     outer_ext,
60                     detected_format
61                 );
62                 if !user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? {
63                     return Ok(ControlFlow::Break(()));
64                 }
65             }
66         } else {
67             // NOTE: If this actually produces no false positives, we can upgrade it in the future
68             // to a warning and ask the user if he wants to continue decompressing.
69             info!(accessible, "Could not detect the extension of `{}`", path.display());
70         }
71     }
72     Ok(ControlFlow::Continue(()))
75 /// In the context of listing archives, this function checks if `ouch` was told to list
76 /// the contents of a compressed file that is not an archive
77 pub fn check_for_non_archive_formats(files: &[PathBuf], formats: &[Vec<Extension>]) -> Result<()> {
78     let mut not_archives = files
79         .iter()
80         .zip(formats)
81         .filter(|(_, formats)| !formats.first().map(Extension::is_archive).unwrap_or(false))
82         .map(|(path, _)| path)
83         .peekable();
85     if not_archives.peek().is_some() {
86         let not_archives: Vec<_> = not_archives.collect();
87         let error = FinalError::with_title("Cannot list archive contents")
88             .detail("Only archives can have their contents listed")
89             .detail(format!(
90                 "Files are not archives: {}",
91                 pretty_format_list_of_paths(&not_archives)
92             ));
94         return Err(error.into());
95     }
97     Ok(())
100 /// Show error if archive format is not the first format in the chain.
101 pub fn check_archive_formats_position(formats: &[extension::Extension], output_path: &Path) -> Result<()> {
102     if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) {
103         let error = FinalError::with_title(format!(
104             "Cannot compress to '{}'.",
105             EscapedPathDisplay::new(output_path)
106         ))
107         .detail(format!("Found the format '{format}' in an incorrect position."))
108         .detail(format!(
109             "'{format}' can only be used at the start of the file extension."
110         ))
111         .hint(format!(
112             "If you wish to compress multiple files, start the extension with '{format}'."
113         ))
114         .hint(format!(
115             "Otherwise, remove the last '{}' from '{}'.",
116             format,
117             EscapedPathDisplay::new(output_path)
118         ));
120         return Err(error.into());
121     }
122     Ok(())