Repository: sharkdp/fd
Pull request #668 (2020-10-16 14:18:25)

Support colorless help
Closes: #600 


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 80ea0a7..58eb9f2 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -9,6 +9,11 @@ assignees: ''
 
 **Describe the bug you encountered:**
 
+<!--
+Please check out the troubleshooting section first:
+  https://github.com/sharkdp/fd#troubleshooting
+-->
+
 
 **Describe what you expected to happen:**
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4eaf010..c66220a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 - Improved the usability of the time-based options, see #624 and #645 (@gorogoroumaru)
 - Add new `--prune` flag, see #535 (@reima)
+- Add support for exact sizes in `--size` filter, see #669 (pull #696)
 
 ## Bugfixes
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5ef632e..8a2313e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,6 +10,24 @@ We welcome any form of contribution:
 **Note**: Before you take the time to open a pull request, please open a ticket first. This will
 give us the chance to discuss any potential changes first.
 
+## Add an entry to the changelog
+
+If your contribution changes the behavior of `fd` (as opposed to a typo-fix
+in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file
+and describe your changes. This makes the release process much easier and
+therefore helps to get your changes into a new `fd` release faster.
+
+The top of the `CHANGELOG` contains a *"unreleased"* section with a few
+subsections (Features, Bugfixes, …). Please add your entry to the subsection
+that best describes your change.
+
+Entries follow this format:
+```
+- Short description of what has been changed, see #123 (@user)
+```
+Here, `#123` is the number of the original issue and/or your pull request.
+Please replace `@user` by your GitHub username.
+
 ## Important links
 
   * [Open issues](https://github.com/sharkdp/fd/issues)
diff --git a/doc/fd.1 b/doc/fd.1
index 41158e1..3579d90 100644
--- a/doc/fd.1
+++ b/doc/fd.1
@@ -57,7 +57,7 @@ pattern contains an uppercase character (smart case).
 Perform a glob-based search instead of a regular expression search.
 .TP
 .B \-\-regex
-Perform a regular-expression based seach (default). This can be used to override --glob.
+Perform a regular-expression based search (default). This can be used to override --glob.
 .TP
 .B \-F, \-\-fixed\-strings
 Treat the pattern as a literal string instead of a regular expression.
@@ -179,6 +179,8 @@ Limit results based on the size of files using the format
 file size must be greater than or equal to this
 .IP '-'
 file size must be less than or equal to this
+.P
+If neither '+' nor '-' is specified, file size must be exactly equal to this.
 .IP 'NUM'
 The numeric size (e.g. 500)
 .IP 'UNIT'
diff --git a/src/app.rs b/src/app.rs
index df30f48..685f184 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,10 +1,17 @@
 use clap::{crate_version, App, AppSettings, Arg};
 
 pub fn build_app() -> App<'static, 'static> {
+    let clap_color_setting =
+        if std::env::var_os("NO_COLOR").is_none() {
+            AppSettings::ColoredHelp
+        } else {
+            AppSettings::ColorNever
+        };
+
     let mut app = App::new("fd")
         .version(crate_version!())
         .usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
-        .setting(AppSettings::ColoredHelp)
+        .setting(clap_color_setting)
         .setting(AppSettings::DeriveDisplayOrder)
         .after_help(
             "Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
@@ -390,7 +397,8 @@ pub fn build_app() -> App<'static, 'static> {
                 .long_help(
                     "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n   \
                         '+': file size must be greater than or equal to this\n   \
-                        '-': file size must be less than or equal to this\n   \
+                        '-': file size must be less than or equal to this\n\
+                     If neither '+' nor '-' is specified, file size must be exactly equal to this.\n   \
                         'NUM':  The numeric size (e.g. 500)\n   \
                         'UNIT': The units for NUM. They are not case-sensitive.\n\
                      Allowed unit values:\n    \
diff --git a/src/filter/size.rs b/src/filter/size.rs
index 08f6c68..09714e7 100644
--- a/src/filter/size.rs
+++ b/src/filter/size.rs
@@ -2,13 +2,14 @@ use lazy_static::lazy_static;
 use regex::Regex;
 
 lazy_static! {
-    static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap();
+    static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap();
 }
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum SizeFilter {
     Max(u64),
     Min(u64),
+    Equals(u64),
 }
 
 // SI prefixes (powers of 10)
@@ -49,16 +50,19 @@ impl SizeFilter {
         };
 
         let size = quantity * multiplier;
-        Some(match limit_kind {
-            "+" => SizeFilter::Min(size),
-            _ => SizeFilter::Max(size),
-        })
+        match limit_kind {
+            "+" => Some(SizeFilter::Min(size)),
+            "-" => Some(SizeFilter::Max(size)),
+            "" => Some(SizeFilter::Equals(size)),
+            _ => None
+        }
     }
 
     pub fn is_within(&self, size: u64) -> bool {
         match *self {
             SizeFilter::Max(limit) => size <= limit,
             SizeFilter::Min(limit) => size >= limit,
+            SizeFilter::Equals(limit) => size == limit,
         }
     }
 }
@@ -170,7 +174,6 @@ mod tests {
 
     // Invalid parse data
     gen_size_filter_failure! {
-        ensure_missing_symbol_returns_none: "10M",
         ensure_missing_number_returns_none: "+g",
         ensure_missing_unit_returns_none: "+18",
         ensure_bad_format_returns_none_1: "$10M",
diff --git a/src/main.rs b/src/main.rs
index b17b5a2..4f52a44 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,6 +43,12 @@ use crate::regex_helper::pattern_has_uppercase_char;
 #[global_allocator]
 static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
 
+// vivid --color-mode 8-bit generate molokai
+const DEFAULT_LS_COLORS: &str = "
+ow=0:or=0;38;5;16;48;5;203:no=0:ex=1;38;5;203:cd=0;38;5;203;48;5;236:mi=0;38;5;16;48;5;203:*~=0;38;5;243:st=0:pi=0;38;5;16;48;5;81:fi=0:di=0;38;5;81:so=0;38;5;16;48;5;203:bd=0;38;5;81;48;5;236:tw=0:ln=0;38;5;203:*.m=0;38;5;48:*.o=0;38;5;243:*.z=4;38;5;203:*.a=1;38;5;203:*.r=0;38;5;48:*.c=0;38;5;48:*.d=0;38;5;48:*.t=0;38;5;48:*.h=0;38;5;48:*.p=0;38;5;48:*.cc=0;38;5;48:*.ll=0;38;5;48:*.jl=0;38;5;48:*css=0;38;5;48:*.md=0;38;5;185:*.gz=4;38;5;203:*.nb=0;38;5;48:*.mn=0;38;5;48:*.go=0;38;5;48:*.xz=4;38;5;203:*.so=1;38;5;203:*.rb=0;38;5;48:*.pm=0;38;5;48:*.bc=0;38;5;243:*.py=0;38;5;48:*.as=0;38;5;48:*.pl=0;38;5;48:*.rs=0;38;5;48:*.sh=0;38;5;48:*.7z=4;38;5;203:*.ps=0;38;5;186:*.cs=0;38;5;48:*.el=0;38;5;48:*.rm=0;38;5;208:*.hs=0;38;5;48:*.td=0;38;5;48:*.ui=0;38;5;149:*.ex=0;38;5;48:*.js=0;38;5;48:*.cp=0;38;5;48:*.cr=0;38;5;48:*.la=0;38;5;243:*.kt=0;38;5;48:*.ml=0;38;5;48:*.vb=0;38;5;48:*.gv=0;38;5;48:*.lo=0;38;5;243:*.hi=0;38;5;243:*.ts=0;38;5;48:*.ko=1;38;5;203:*.hh=0;38;5;48:*.pp=0;38;5;48:*.di=0;38;5;48:*.bz=4;38;5;203:*.fs=0;38;5;48:*.png=0;38;5;208:*.zsh=0;38;5;48:*.mpg=0;38;5;208:*.pid=0;38;5;243:*.xmp=0;38;5;149:*.iso=4;38;5;203:*.m4v=0;38;5;208:*.dot=0;38;5;48:*.ods=0;38;5;186:*.inc=0;38;5;48:*.sxw=0;38;5;186:*.aif=0;38;5;208:*.git=0;38;5;243:*.gvy=0;38;5;48:*.tbz=4;38;5;203:*.log=0;38;5;243:*.txt=0;38;5;185:*.ico=0;38;5;208:*.csx=0;38;5;48:*.vob=0;38;5;208:*.pgm=0;38;5;208:*.pps=0;38;5;186:*.ics=0;38;5;186:*.img=4;38;5;203:*.fon=0;38;5;208:*.hpp=0;38;5;48:*.bsh=0;38;5;48:*.sql=0;38;5;48:*TODO=1:*.php=0;38;5;48:*.pkg=4;38;5;203:*.ps1=0;38;5;48:*.csv=0;38;5;185:*.ilg=0;38;5;243:*.ini=0;38;5;149:*.pyc=0;38;5;243:*.psd=0;38;5;208:*.htc=0;38;5;48:*.swp=0;38;5;243:*.mli=0;38;5;48:*hgrc=0;38;5;149:*.bst=0;38;5;149:*.ipp=0;38;5;48:*.fsi=0;38;5;48:*.tcl=0;38;5;48:*.exs=0;38;5;48:*.out=0;38;5;243:*.jar=4;38;5;203:*.xls=0;38;5;186:*.ppm=0;38;5;208:*.apk=4;38;5;203:*.aux=0;38;5;243:*.rpm=4;38;5;203:*.dll=1;38;5;203:*.eps=0;38;5;208:*.exe=1;38;5;203:*.doc=0;38;5;186:*.wma=0;38;5;208:*.deb=4;38;5;203:*.pod=0;38;5;48:*.ind=0;38;5;243:*.nix=0;38;5;149:*.lua=0;38;5;48:*.epp=0;38;5;48:*.dpr=0;38;5;48:*.htm=0;38;5;185:*.ogg=0;38;5;208:*.bin=4;38;5;203:*.otf=0;38;5;208:*.yml=0;38;5;149:*.pro=0;38;5;149:*.cxx=0;38;5;48:*.tex=0;38;5;48:*.fnt=0;38;5;208:*.erl=0;38;5;48:*.sty=0;38;5;243:*.bag=4;38;5;203:*.rst=0;38;5;185:*.pdf=0;38;5;186:*.pbm=0;38;5;208:*.xcf=0;38;5;208:*.clj=0;38;5;48:*.gif=0;38;5;208:*.rar=4;38;5;203:*.elm=0;38;5;48:*.bib=0;38;5;149:*.tsx=0;38;5;48:*.dmg=4;38;5;203:*.tmp=0;38;5;243:*.bcf=0;38;5;243:*.mkv=0;38;5;208:*.svg=0;38;5;208:*.cpp=0;38;5;48:*.vim=0;38;5;48:*.bmp=0;38;5;208:*.ltx=0;38;5;48:*.fls=0;38;5;243:*.flv=0;38;5;208:*.wav=0;38;5;208:*.m4a=0;38;5;208:*.mid=0;38;5;208:*.hxx=0;38;5;48:*.pas=0;38;5;48:*.wmv=0;38;5;208:*.tif=0;38;5;208:*.kex=0;38;5;186:*.mp4=0;38;5;208:*.bak=0;38;5;243:*.xlr=0;38;5;186:*.dox=0;38;5;149:*.swf=0;38;5;208:*.tar=4;38;5;203:*.tgz=4;38;5;203:*.cfg=0;38;5;149:*.xml=0;
+38;5;185:*.jpg=0;38;5;208:*.mir=0;38;5;48:*.sxi=0;38;5;186:*.bz2=4;38;5;203:*.odt=0;38;5;186:*.mov=0;38;5;208:*.toc=0;38;5;243:*.bat=1;38;5;203:*.asa=0;38;5;48:*.awk=0;38;5;48:*.sbt=0;38;5;48:*.vcd=4;38;5;203:*.kts=0;38;5;48:*.arj=4;38;5;203:*.blg=0;38;5;243:*.c++=0;38;5;48:*.odp=0;38;5;186:*.bbl=0;38;5;243:*.idx=0;38;5;243:*.com=1;38;5;203:*.mp3=0;38;5;208:*.avi=0;38;5;208:*.def=0;38;5;48:*.cgi=0;38;5;48:*.zip=4;38;5;203:*.ttf=0;38;5;208:*.ppt=0;38;5;186:*.tml=0;38;5;149:*.fsx=0;38;5;48:*.h++=0;38;5;48:*.rtf=0;38;5;186:*.inl=0;38;5;48:*.yaml=0;38;5;149:*.html=0;38;5;185:*.mpeg=0;38;5;208:*.java=0;38;5;48:*.hgrc=0;38;5;149:*.orig=0;38;5;243:*.conf=0;38;5;149:*.dart=0;38;5;48:*.psm1=0;38;5;48:*.rlib=0;38;5;243:*.fish=0;38;5;48:*.bash=0;38;5;48:*.make=0;38;5;149:*.docx=0;38;5;186:*.json=0;38;5;149:*.psd1=0;38;5;48:*.lisp=0;38;5;48:*.tbz2=4;38;5;203:*.diff=0;38;5;48:*.epub=0;38;5;186:*.xlsx=0;38;5;186:*.pptx=0;38;5;186:*.toml=0;38;5;149:*.h264=0;38;5;208:*.purs=0;38;5;48:*.flac=0;38;5;208:*.tiff=0;38;5;208:*.jpeg=0;38;5;208:*.lock=0;38;5;243:*.less=0;38;5;48:*.dyn_o=0;38;5;243:*.scala=0;38;5;48:*.mdown=0;38;5;185:*.shtml=0;38;5;185:*.class=0;38;5;243:*.cache=0;38;5;243:*.cmake=0;38;5;149:*passwd=0;38;5;149:*.swift=0;38;5;48:*shadow=0;38;5;149:*.xhtml=0;38;5;185:*.patch=0;38;5;48:*.cabal=0;38;5;48:*README=0;38;5;16;48;5;186:*.toast=4;38;5;203:*.ipynb=0;38;5;48:*COPYING=0;38;5;249:*.gradle=0;38;5;48:*.matlab=0;38;5;48:*.config=0;38;5;149:*LICENSE=0;38;5;249:*.dyn_hi=0;38;5;243:*.flake8=0;38;5;149:*.groovy=0;38;5;48:*INSTALL=0;38;5;16;48;5;186:*TODO.md=1:*.ignore=0;38;5;149:*Doxyfile=0;38;5;149:*TODO.txt=1:*setup.py=0;38;5;149:*Makefile=0;38;5;149:*.gemspec=0;38;5;149:*.desktop=0;38;5;149:*.rgignore=0;38;5;149:*.markdown=0;38;5;185:*COPYRIGHT=0;38;5;249:*configure=0;38;5;149:*.DS_Store=0;38;5;243:*.kdevelop=0;38;5;149:*.fdignore=0;38;5;149:*README.md=0;38;5;16;48;5;186:*.cmake.in=0;38;5;149:*SConscript=0;38;5;149:*CODEOWNERS=0;38;5;149:*.localized=0;38;5;243:*.gitignore=0;38;5;149:*Dockerfile=0;38;5;149:*.gitconfig=0;38;5;149:*INSTALL.md=0;38;5;16;48;5;186:*README.txt=0;38;5;16;48;5;186:*SConstruct=0;38;5;149:*.scons_opt=0;38;5;243:*.travis.yml=0;38;5;186:*.gitmodules=0;38;5;149:*.synctex.gz=0;38;5;243:*LICENSE-MIT=0;38;5;249:*MANIFEST.in=0;38;5;149:*Makefile.in=0;38;5;243:*Makefile.am=0;38;5;149:*INSTALL.txt=0;38;5;16;48;5;186:*configure.ac=0;38;5;149:*.applescript=0;38;5;48:*appveyor.yml=0;38;5;186:*.fdb_latexmk=0;38;5;243:*CONTRIBUTORS=0;38;5;16;48;5;186:*.clang-format=0;38;5;149:*LICENSE-APACHE=0;38;5;249:*CMakeLists.txt=0;38;5;149:*CMakeCache.txt=0;38;5;243:*.gitattributes=0;38;5;149:*CONTRIBUTORS.md=0;38;5;16;48;5;186:*.sconsign.dblite=0;38;5;243:*requirements.txt=0;38;5;149:*CONTRIBUTORS.txt=0;38;5;16;48;5;186:*package-lock.json=0;38;5;243:*.CFUserTextEncoding=0;38;5;243
+";
+
 fn run() -> Result<ExitCode> {
     let matches = app::build_app().get_matches_from(env::args_os());
 
@@ -170,7 +176,7 @@ fn run() -> Result<ExitCode> {
     let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
 
     let ls_colors = if colored_output {
-        Some(LsColors::from_env().unwrap_or_default())
+        Some(LsColors::from_env().unwrap_or(LsColors::from_string(DEFAULT_LS_COLORS)))
     } else {
         None
     };
@@ -399,11 +405,11 @@ fn run() -> Result<ExitCode> {
         exclude_patterns: matches
             .values_of("exclude")
             .map(|v| v.map(|p| String::from("!") + p).collect())
-            .unwrap_or_else(|| vec![]),
+            .unwrap_or_else(Vec::new),
         ignore_files: matches
             .values_of("ignore-file")
             .map(|vs| vs.map(PathBuf::from).collect())
-            .unwrap_or_else(|| vec![]),
+            .unwrap_or_else(Vec::new),
         size_constraints: size_limits,
         time_constraints,
         #[cfg(unix)]
diff --git a/src/output.rs b/src/output.rs
index d2737c6..7f4c5e0 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -4,7 +4,6 @@ use std::process;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 
-use ansi_term;
 use lscolors::{LsColors, Style};
 
 use crate::exit_codes::ExitCode;


Test patch:
diff --git a/tests/tests.rs b/tests/tests.rs
index 9a375f3..616189e 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1387,6 +1387,9 @@ fn test_size() {
 
     // Zero sized files.
     te.assert_output(&["", "--size", "-0B"], "0_bytes.foo");
+    te.assert_output(&["", "--size", "0B"], "0_bytes.foo");
+    te.assert_output(&["", "--size=0B"], "0_bytes.foo");
+    te.assert_output(&["", "-S", "0B"], "0_bytes.foo");
 
     // Files with 2 bytes or more.
     te.assert_output(
@@ -1403,6 +1406,9 @@ fn test_size() {
     // Files with size between 1 byte and 11 bytes.
     te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo");
 
+    // Files with size equal 11 bytes.
+    te.assert_output(&["", "--size", "11B"], "11_bytes.foo");
+
     // Files with size between 1 byte and 30 bytes.
     te.assert_output(
         &["", "--size", "+1B", "--size", "-30B"],
@@ -1434,6 +1440,7 @@ fn test_size() {
 
     // Files with size equal 4 kibibytes.
     te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
+    te.assert_output(&["", "--size", "4ki"], "4_kibibytes.foo");
 }
 
 #[cfg(test)]