better output 0u0
This commit is contained in:
parent
37b9cccc9c
commit
3f40c61d6d
3 changed files with 48 additions and 23 deletions
|
@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Changed
|
### Changed
|
||||||
- Capped help output (`-h`/`--help`) width at 120 characters max
|
- Capped help output (`-h`/`--help`) width at 120 characters max
|
||||||
|
- Output is now sorted by filename - specifically, errors will appear first, followed by files that fif is unable to
|
||||||
|
recommend an extension for, in order of filename, followed by files that fif knows how to rename, again in order
|
||||||
|
of filename.
|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.3.7 - 2021-09-25
|
## v0.3.7 - 2021-09-25
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned and
|
//! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned and
|
||||||
//! whether its MIME type could be inferred.
|
//! whether its MIME type could be inferred.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ use crate::files::mime_extension_lookup;
|
||||||
use crate::String;
|
use crate::String;
|
||||||
|
|
||||||
/// Information about a scanned file.
|
/// Information about a scanned file.
|
||||||
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub struct Findings {
|
pub struct Findings {
|
||||||
/// The location of the scanned file.
|
/// The location of the scanned file.
|
||||||
pub file: PathBuf,
|
pub file: PathBuf,
|
||||||
|
@ -22,6 +23,30 @@ pub struct Findings {
|
||||||
pub mime: Mime,
|
pub mime: Mime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialOrd<Self> for Findings {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Findings {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
// files with no recommended extension should appear first, so that fif outputs the "no known extension for x"
|
||||||
|
// comments before the "mv x y" instructions
|
||||||
|
// since fif doesn't output anything for valid files, the comparison will consider any comparison involving a
|
||||||
|
// valid Findings to be equal, avoiding the (somewhat) expensive call to recommended_extension. after all, since
|
||||||
|
// fif never displays valid files, it really doesn't matter what position they end up in.
|
||||||
|
if self.valid || other.valid {
|
||||||
|
return Ordering::Equal
|
||||||
|
}
|
||||||
|
match(self.recommended_extension(), other.recommended_extension()) {
|
||||||
|
(None, Some(_)) => Ordering::Greater,
|
||||||
|
(Some(_), None) => Ordering::Less,
|
||||||
|
_ => self.file.cmp(&other.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
impl serde::Serialize for Findings {
|
impl serde::Serialize for Findings {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use itertools::Itertools;
|
use itertools::{Either, Itertools};
|
||||||
use snailquote::escape;
|
use snailquote::escape;
|
||||||
|
|
||||||
use crate::findings::ScanError;
|
use crate::findings::ScanError;
|
||||||
|
@ -54,6 +54,21 @@ macro_rules! writablesln {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
type Entries<'a> = [Result<Findings, ScanError<'a>>];
|
type Entries<'a> = [Result<Findings, ScanError<'a>>];
|
||||||
|
|
||||||
|
/// Splits the given [`Entries`] into [`Vec`]s of [`Findings`] and [`ScanError`]s. [`Findings`] are sorted by whether
|
||||||
|
/// or not they have a known extension (unknown extensions coming first), and then by their filenames. [`ScanError`]s
|
||||||
|
/// are sorted such that [`ScanError::File`]s come before [`ScanError::Mime`]s.
|
||||||
|
#[inline]
|
||||||
|
fn sort_entries<'a>(entries: &'a Entries) -> (Vec<&'a Findings>, Vec<&'a ScanError<'a>>) {
|
||||||
|
let (mut findings, mut errors): (Vec<_>, Vec<_>) = entries.iter().partition_map(|entry| match entry {
|
||||||
|
Ok(f) => Either::Left(f),
|
||||||
|
Err(e) => Either::Right(e)
|
||||||
|
});
|
||||||
|
|
||||||
|
findings.sort_unstable();
|
||||||
|
errors.sort_unstable();
|
||||||
|
(findings, errors)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Writable<'a> {
|
pub enum Writable<'a> {
|
||||||
String(&'a str),
|
String(&'a str),
|
||||||
|
@ -131,21 +146,7 @@ pub trait FormatSteps {
|
||||||
fn write_steps<W: Write>(&self, f: &mut W, entries: &Entries) -> io::Result<()> {
|
fn write_steps<W: Write>(&self, f: &mut W, entries: &Entries) -> io::Result<()> {
|
||||||
self.header(f, entries)?;
|
self.header(f, entries)?;
|
||||||
|
|
||||||
// output will be generated in the order:
|
let (findings, errors) = sort_entries(entries);
|
||||||
// - files that couldn't be read
|
|
||||||
// - files with no known mime type
|
|
||||||
// - files with no known extension
|
|
||||||
// - files with a known extension
|
|
||||||
// files that already have a correct extension won't be represented in the output.
|
|
||||||
|
|
||||||
// sort errors so unreadable files appear before files with unknown mimetypes - ScanError impls Ord such that
|
|
||||||
// ScanError::File > ScanError::Mime
|
|
||||||
let errors = entries.iter().filter_map(|e| e.as_ref().err()).sorted_unstable();
|
|
||||||
// sort files so that files with no known extension come before those with known extensions - None > Some("jpg")
|
|
||||||
let findings = entries
|
|
||||||
.iter()
|
|
||||||
.filter_map(|e| e.as_ref().ok())
|
|
||||||
.sorted_unstable_by(|a, b| b.recommended_extension().cmp(&a.recommended_extension()).reverse());
|
|
||||||
|
|
||||||
for error in errors {
|
for error in errors {
|
||||||
match error {
|
match error {
|
||||||
|
@ -338,18 +339,14 @@ pub struct Json;
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
impl Format for Json {
|
impl Format for Json {
|
||||||
fn write_all<W: Write>(&self, f: &mut W, entries: &Entries) -> io::Result<()> {
|
fn write_all<W: Write>(&self, f: &mut W, entries: &Entries) -> io::Result<()> {
|
||||||
use itertools::Either;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
struct SerdeEntries<'a> {
|
struct SerdeEntries<'a> {
|
||||||
errors: &'a Vec<&'a ScanError<'a>>,
|
errors: &'a Vec<&'a ScanError<'a>>,
|
||||||
findings: &'a Vec<&'a Findings>,
|
findings: &'a Vec<&'a Findings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let (errors, findings) = &entries.iter().partition_map(|entry| match entry {
|
let (findings, errors) = &sort_entries(entries);
|
||||||
Err(e) => Either::Left(e),
|
|
||||||
Ok(f) => Either::Right(f),
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = serde_json::to_writer_pretty(f, &SerdeEntries { errors, findings });
|
let result = serde_json::to_writer_pretty(f, &SerdeEntries { errors, findings });
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue