diff --git a/README.md b/README.md index dd95bd89..f90875f2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Why use ugrep? - ugrep is fast, user-friendly, and equipped with a ton of new features that users wanted -- includes an interactive TUI with built-in help, Google-like search with AND/OR/NOT patterns, fuzzy search, searches (nested) zip/7z/tar/pax/cpio archives, tarballs and compressed files gz/Z/bz/bz2/lzma/xz/lz4/zstd/brotli, search and hexdump binary files, search documents such as PDF, doc, docx, and output in JSON, XML, CSV or your own customized format +- includes an interactive TUI with built-in help, Google-like search with AND/OR/NOT patterns, fuzzy search, search (nested) zip/7z/tar/pax/cpio archives, tarballs and compressed files gz/Z/bz/bz2/lzma/xz/lz4/zstd/brotli, search and hexdump binary files, search documents such as PDF, doc, docx, and output in JSON, XML, CSV or your own customized format - Unicode extended regex pattern syntax with multi-line pattern matching without requiring special command-line options @@ -4325,15 +4325,15 @@ in markdown: --filter=COMMANDS Filter files through the specified COMMANDS first before searching. COMMANDS is a comma-separated list of `exts:command - [option ...]', where `exts' is a comma-separated list of filename + arguments', where `exts' is a comma-separated list of filename extensions and `command' is a filter utility. Files matching one - of `exts' are filtered. When `exts' is a `*', all files are - filtered. One or more `option' separated by spacing may be - specified, which are passed verbatim to the command. A `%' as - `option' expands into the pathname to search. For example, + of `exts' are filtered. A `*' matches any file. The specified + `command' may include arguments separated by spaces. An argument + may be quoted to include spacing, commas or a `%'. A `%' argument + expands into the pathname to search. For example, --filter='pdf:pdftotext % -' searches PDF files. The `%' expands into a `-' when searching standard input. When a `%' is not - specified, a filter utility should read from standard input and + specified, the filter command should read from standard input and write to standard output. Option --label=.ext may be used to specify extension `ext' when searching standard input. This option may be repeated. @@ -5459,7 +5459,7 @@ in markdown: - ugrep 7.0.4 November 12, 2024 UGREP(1) + ugrep 7.1.0 November 19, 2024 UGREP(1) 🔝 [Back to table of contents](#toc) diff --git a/bin/win32/ug.exe b/bin/win32/ug.exe index 9fe9919a..1ca1af8c 100755 Binary files a/bin/win32/ug.exe and b/bin/win32/ug.exe differ diff --git a/bin/win32/ugrep.exe b/bin/win32/ugrep.exe index 9fe9919a..1ca1af8c 100755 Binary files a/bin/win32/ugrep.exe and b/bin/win32/ugrep.exe differ diff --git a/bin/win64/ug.exe b/bin/win64/ug.exe index 78cae816..36f8942a 100755 Binary files a/bin/win64/ug.exe and b/bin/win64/ug.exe differ diff --git a/bin/win64/ugrep.exe b/bin/win64/ugrep.exe index 78cae816..36f8942a 100755 Binary files a/bin/win64/ugrep.exe and b/bin/win64/ugrep.exe differ diff --git a/configure b/configure index 9854cbb0..1e574943 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72 for ugrep 7.0.4. +# Generated by GNU Autoconf 2.72 for ugrep 7.1.0. # # Report bugs to . # @@ -606,8 +606,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='ugrep' PACKAGE_TARNAME='ugrep' -PACKAGE_VERSION='7.0.4' -PACKAGE_STRING='ugrep 7.0.4' +PACKAGE_VERSION='7.1.0' +PACKAGE_STRING='ugrep 7.1.0' PACKAGE_BUGREPORT='https://github.com/Genivia/ugrep/issues' PACKAGE_URL='https://ugrep.com' @@ -1382,7 +1382,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures ugrep 7.0.4 to adapt to many kinds of systems. +'configure' configures ugrep 7.1.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1453,7 +1453,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of ugrep 7.0.4:";; + short | recursive ) echo "Configuration of ugrep 7.1.0:";; esac cat <<\_ACEOF @@ -1630,7 +1630,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -ugrep configure 7.0.4 +ugrep configure 7.1.0 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. @@ -2184,7 +2184,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by ugrep $as_me 7.0.4, which was +It was created by ugrep $as_me 7.1.0, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -3694,7 +3694,7 @@ fi # Define the identity of the package. PACKAGE='ugrep' - VERSION='7.0.4' + VERSION='7.1.0' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -11790,7 +11790,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by ugrep $as_me 7.0.4, which was +This file was extended by ugrep $as_me 7.1.0, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -11863,7 +11863,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -ugrep config.status 7.0.4 +ugrep config.status 7.1.0 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 2d0f86e2..43c37d20 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([ugrep],[7.0.4],[https://github.com/Genivia/ugrep/issues],[ugrep],[https://ugrep.com]) +AC_INIT([ugrep],[7.1.0],[https://github.com/Genivia/ugrep/issues],[ugrep],[https://ugrep.com]) AM_INIT_AUTOMAKE([foreign subdir-objects dist-xz no-dist-gzip]) AC_CONFIG_HEADERS([config.h]) AC_COPYRIGHT([Copyright (C) 2019-2024 Robert van Engelen, Genivia Inc.]) diff --git a/makemake.sh b/makemake.sh index 776c3a56..ffa95629 100755 --- a/makemake.sh +++ b/makemake.sh @@ -55,6 +55,7 @@ autoconf automake touch config.h.in ./configure +make echo OK diff --git a/man/ug.1 b/man/ug.1 index 904bcaeb..8f9feea7 100644 --- a/man/ug.1 +++ b/man/ug.1 @@ -1,4 +1,4 @@ -.TH UGREP "1" "November 12, 2024" "ugrep 7.0.4" "User Commands" +.TH UGREP "1" "November 19, 2024" "ugrep 7.1.0" "User Commands" .SH NAME \fBugrep\fR, \fBug\fR -- file pattern searcher .SH SYNOPSIS @@ -385,15 +385,15 @@ patterns; thus nothing is matched. This option may be repeated. .TP \fB\-\-filter\fR=\fICOMMANDS\fR Filter files through the specified COMMANDS first before searching. -COMMANDS is a comma\-separated list of `exts:command [option ...]', +COMMANDS is a comma\-separated list of `exts:command arguments', where `exts' is a comma\-separated list of filename extensions and `command' is a filter utility. Files matching one of `exts' are -filtered. When `exts' is a `*', all files are filtered. One or -more `option' separated by spacing may be specified, which are -passed verbatim to the command. A `%' as `option' expands into the -pathname to search. For example, \fB\-\-filter\fR='pdf:pdftotext % \-' +filtered. A `*' matches any file. The specified `command' may +include arguments separated by spaces. An argument may be quoted +to include spacing, commas or a `%'. A `%' argument expands into +the pathname to search. For example, \fB\-\-filter\fR='pdf:pdftotext % \-' searches PDF files. The `%' expands into a `\-' when searching -standard input. When a `%' is not specified, a filter utility +standard input. When a `%' is not specified, the filter command should read from standard input and write to standard output. Option \fB\-\-label\fR=.ext may be used to specify extension `ext' when searching standard input. This option may be repeated. diff --git a/man/ugrep-indexer.1 b/man/ugrep-indexer.1 index 62504652..8d380340 100644 --- a/man/ugrep-indexer.1 +++ b/man/ugrep-indexer.1 @@ -1,4 +1,4 @@ -.TH UGREP-INDEXER "1" "November 12, 2024" "ugrep-indexer 7.0.4" "User Commands" +.TH UGREP-INDEXER "1" "November 19, 2024" "ugrep-indexer 7.1.0" "User Commands" .SH NAME \fBugrep-indexer\fR -- file indexer to accelerate recursive searching .SH SYNOPSIS diff --git a/man/ugrep.1 b/man/ugrep.1 index 904bcaeb..8f9feea7 100644 --- a/man/ugrep.1 +++ b/man/ugrep.1 @@ -1,4 +1,4 @@ -.TH UGREP "1" "November 12, 2024" "ugrep 7.0.4" "User Commands" +.TH UGREP "1" "November 19, 2024" "ugrep 7.1.0" "User Commands" .SH NAME \fBugrep\fR, \fBug\fR -- file pattern searcher .SH SYNOPSIS @@ -385,15 +385,15 @@ patterns; thus nothing is matched. This option may be repeated. .TP \fB\-\-filter\fR=\fICOMMANDS\fR Filter files through the specified COMMANDS first before searching. -COMMANDS is a comma\-separated list of `exts:command [option ...]', +COMMANDS is a comma\-separated list of `exts:command arguments', where `exts' is a comma\-separated list of filename extensions and `command' is a filter utility. Files matching one of `exts' are -filtered. When `exts' is a `*', all files are filtered. One or -more `option' separated by spacing may be specified, which are -passed verbatim to the command. A `%' as `option' expands into the -pathname to search. For example, \fB\-\-filter\fR='pdf:pdftotext % \-' +filtered. A `*' matches any file. The specified `command' may +include arguments separated by spaces. An argument may be quoted +to include spacing, commas or a `%'. A `%' argument expands into +the pathname to search. For example, \fB\-\-filter\fR='pdf:pdftotext % \-' searches PDF files. The `%' expands into a `\-' when searching -standard input. When a `%' is not specified, a filter utility +standard input. When a `%' is not specified, the filter command should read from standard input and write to standard output. Option \fB\-\-label\fR=.ext may be used to specify extension `ext' when searching standard input. This option may be repeated. diff --git a/src/cnf.hpp b/src/cnf.hpp index affc7155..10a741d3 100644 --- a/src/cnf.hpp +++ b/src/cnf.hpp @@ -119,7 +119,7 @@ class CNF { // compile --bool search query into operator tree, normalize to CNF, and populate CNF AND-list of ALT-term lists void compile(const char *pattern) { - OpTree(pattern, terms); + (void)OpTree(pattern, terms); } // return the CNF AND-list of ALT-term lists diff --git a/src/query.cpp b/src/query.cpp index e9c88f78..c239d55e 100644 --- a/src/query.cpp +++ b/src/query.cpp @@ -176,7 +176,7 @@ void Query::display(int col, int len) bool list = false; bool braced = false; bool literal = false; - int prev = ' '; + int prev = -1; if (!Screen::mono) { if (globbing_) @@ -292,15 +292,18 @@ void Query::display(int col, int len) Screen::put(color_qr); ptr = next + 1; } - else if ((ch == '*' || ch == '?' || ch == ',') && !list) + else if (strchr("!*,?^", ch) != NULL && !list) { - Screen::put(ptr, next - ptr); - Screen::normal(); - Screen::put(color_qm); - Screen::put(ch); - Screen::normal(); - Screen::put(color_qr); - ptr = next + 1; + if ((ch != '!' && ch != '^') || prev == ',' || prev == -1) + { + Screen::put(ptr, next - ptr); + Screen::normal(); + Screen::put(color_qm); + Screen::put(ch); + Screen::normal(); + Screen::put(color_qr); + ptr = next + 1; + } } else if (ch == '\\' && !list) { @@ -411,14 +414,12 @@ void Query::display(int col, int len) Screen::put(color_qr); ptr = next + 1; } - else if (!literal && flag_fixed_strings) + else if (!literal) { - if (prev == ' ' || prev == '|' || prev == '(') + if (prev == ' ' || prev == '|' || prev == '(' || prev == -1) { if (ch == '-' || - ch == '|' || - (ch == '(' && strchr(next, ')') != NULL) || - ch == ')' || + (flag_fixed_strings && (ch == '(' || ch == ')' || ch == '|')) || strncmp(next, "AND ", 4) == 0 || strncmp(next, "OR ", 3) == 0 || strncmp(next, "NOT ", 4) == 0) @@ -428,17 +429,17 @@ void Query::display(int col, int len) Screen::put(color_qm); ptr = next; if (isalpha(ch)) - next += 2 + (next[2] != ' '); - ch = ' '; - } - else - { - Screen::put(ptr, next - ptr); + next += 1 + (next[2] != ' '); + Screen::put(ptr, next - ptr + 1); Screen::normal(); - ptr = next; + Screen::put(color_qr); + ptr = next + 1; + ch = ' '; } } - else if (ch == '|' || (ch == ')' && (next + 1 >= end || next[1] == ' ' || next[1] == '|' || next[1] == ')'))) + else if (flag_fixed_strings && + (ch == '|' || + (ch == ')' && (next + 1 >= end || next[1] == ' ' || next[1] == '|' || next[1] == ')')))) { Screen::put(ptr, next - ptr); Screen::normal(); @@ -2152,13 +2153,12 @@ void Query::back() if (compare_dir && flag_tree) { - if (ref == 0) - return; - - --ref; - - while (ref > 0 && view_[ref].size() > 1) - --ref; + if (ref > 0) + { + do + --ref; + while (ref > 0 && view_[ref].size() > 1); + } } else { @@ -2811,6 +2811,12 @@ void Query::select() history_.emplace(); history_.top().save(line_, col_, row_, flags_, mark_); + if (flag_directories_action != Action::RECURSE) + { + flags_[18].flag = flag_dereference; + flags_[19].flag = !flag_dereference; + } + mark_.reset(); size_t n = pathname.find(PATHSEPCHR, 1); // ignore PATHSEPCHR at front when present @@ -2870,7 +2876,7 @@ void Query::deselect() return; } - if (selected_file_.empty() && !Static::arg_files.empty()) + if (!Static::arg_files.empty()) { message("cannot chdir .. because file or directory arguments are present"); return; diff --git a/src/ugrep-indexer.cpp b/src/ugrep-indexer.cpp index b3583755..fce71db5 100644 --- a/src/ugrep-indexer.cpp +++ b/src/ugrep-indexer.cpp @@ -35,7 +35,7 @@ */ // DO NOT ALTER THIS LINE: updated by makemake.sh and we need it physically here for MSVC++ build from source -#define UGREP_VERSION "7.0.4" +#define UGREP_VERSION "7.1.0" // use a task-parallel thread to decompress the stream into a pipe to search, also handles nested archives #define WITH_DECOMPRESSION_THREAD @@ -2051,7 +2051,7 @@ void load_config(const char *config_filename) int main(int argc, const char **argv) { #if !defined(OS_WIN) && defined(HAVE_LIBZ) && defined(WITH_DECOMPRESSION_THREAD) - // ignore SIGPIPE, should never happen, but just in case + // ignore SIGPIPE signal(SIGPIPE, SIG_IGN); #endif diff --git a/src/ugrep.cpp b/src/ugrep.cpp index 6da542eb..12a6ebb1 100644 --- a/src/ugrep.cpp +++ b/src/ugrep.cpp @@ -3726,6 +3726,7 @@ struct Grep { if (*command == '*') default_command = strchr(command, ':'); + // match filter filename extension (case sensitive) if (strncmp(suffix, command, sep) == 0 && (command[sep] == ':' || command[sep] == ',' || isspace(static_cast(command[sep])))) { command = strchr(command, ':'); @@ -3752,10 +3753,10 @@ struct Grep { int fd[2]; #ifdef OS_WIN - // CreateProcess requires an inherited pipe handle specific to Windows - bool ok = pipe_inherit(fd) == 0; + // Windows CreateProcess requires an "inherited" pipe handle specific to Windows + bool ok = (pipe_inherit(fd) == 0); #else - bool ok = pipe(fd) == 0; + bool ok = (pipe(fd) == 0); #endif if (ok) @@ -5126,9 +5127,9 @@ static void save_config() fprintf(file, "### SEARCH PATTERNS ###\n\n"); - fprintf(file, "# Enable case-insensitive search, default: no-ignore-case\n%s\n\n", flag_ignore_case.is_undefined() ? "# no-ignore-case" : flag_ignore_case ? "ignore-case" : "no-ignore-case"); - fprintf(file, "# Enable smart case, default: no-smart-case\n%s\n\n", flag_smart_case.is_undefined() ? "# no-smart-case" : flag_smart_case ? "smart-case" : "no-smart-case"); - fprintf(file, "# Enable empty pattern matches, default: no-empty\n%s\n\n", flag_empty.is_undefined() ? "# no-empty" : flag_empty ? "empty" : "no-empty"); + fprintf(file, "# Enable case-insensitive search, default: no-ignore-case\n%signore-case\n\n", flag_ignore_case.is_undefined() ? "# " : flag_ignore_case ? "" : "no-"); + fprintf(file, "# Enable smart case, default: no-smart-case\n%ssmart-case\n\n", flag_smart_case.is_undefined() ? "# " : flag_smart_case ? "" : "no-"); + fprintf(file, "# Enable empty pattern matches, default: no-empty\n%sempty\n\n", flag_empty.is_undefined() ? "# " : flag_empty ? "" : "no-"); fprintf(file, "# Force option -c (--count) to return nonzero matches with --min-count=1, default: --min-count=0\n"); if (flag_min_count == 0) fprintf(file, "# min-count=1\n\n"); @@ -5137,8 +5138,8 @@ static void save_config() fprintf(file, "### SEARCH TARGETS ###\n\n"); - fprintf(file, "# Enable case-insensitive glob matching, default: no-glob-ignore-case\n%sglob-ignore-case\n\n", flag_glob_ignore_case ? "" : "# "); - fprintf(file, "# Search hidden files and directories, default: no-hidden\n%s\n\n", flag_hidden ? "hidden" : "no-hidden"); + fprintf(file, "# Case-insensitive glob matching, default: no-glob-ignore-case\n%sglob-ignore-case\n\n", flag_glob_ignore_case ? "" : "# "); + fprintf(file, "# Search hidden files and directories, default: no-hidden\n%shidden\n\n", flag_hidden ? "" : "# "); fprintf(file, "# Ignore binary files, default: no-ignore-binary\n%signore-binary\n\n", strcmp(flag_binary_files, "without-match") == 0 ? "" : "# "); if (!flag_include_fs.empty()) { diff --git a/src/zstream.hpp b/src/zstream.hpp index 0d993137..81242888 100644 --- a/src/zstream.hpp +++ b/src/zstream.hpp @@ -175,6 +175,13 @@ class zstreambuf : public std::streambuf { // constructor ZipInfo(const char *pathname, FILE *file, const unsigned char *buf = NULL, size_t len = 0) : + version(0), + flag(0), + method(Compression::STORE), + mtime(0), + crc(0), + size(0), + usize(0), pathname_(pathname), file_(file), z_strm_(NULL), diff --git a/src/zthread.hpp b/src/zthread.hpp index fc2eefa7..3d16d8ec 100644 --- a/src/zthread.hpp +++ b/src/zthread.hpp @@ -60,6 +60,7 @@ inline int pipe(int fd[2]) fd[1] = _open_osfhandle(reinterpret_cast(pipe_w), _O_WRONLY); return 0; } + errno = GetLastError(); return -1; }