diff --git a/main.cpp b/main.cpp index dba596e..7258443 100644 --- a/main.cpp +++ b/main.cpp @@ -76,7 +76,9 @@ int main(int argc, char **argv) arg_parser.m_dirjobs, files_to_scan_queue); // Set up the output task object. - OutputTask output_task(arg_parser.m_color, arg_parser.m_nocolor, arg_parser.m_column, match_queue); + OutputTask output_task(arg_parser.m_color, arg_parser.m_prefix_file, + arg_parser.m_line_number, + arg_parser.m_column, arg_parser.m_nullsep, match_queue); // Create the FileScanner object. std::unique_ptr file_scanner(FileScanner::Create(files_to_scan_queue, match_queue, arg_parser.m_pattern, arg_parser.m_ignore_case, arg_parser.m_word_regexp, arg_parser.m_pattern_is_literal)); diff --git a/src/ArgParse.cpp b/src/ArgParse.cpp index 45653ff..bf12365 100644 --- a/src/ArgParse.cpp +++ b/src/ArgParse.cpp @@ -25,7 +25,7 @@ #include "../build_info.h" -#include +#include "libext/cpuidex.hpp" // Std C++. #include @@ -55,7 +55,7 @@ namespace lmcppop = lmcppop_int::option; #include #endif #if HAVE_LIBPCRE2 == 1 -#include +#include "FileScannerPCRE2.h" #endif #include @@ -112,19 +112,17 @@ enum OPT OPT_SECTION = 255, OPT_IGNORE_CASE = 1, OPT_SMART_CASE, - OPT_NO_SMART_CASE, OPT_HANDLE_CASE, OPT_LITERAL, OPT_WORDREGEX, OPT_COLOR, - OPT_NOCOLOR, + OPT_PREFIX_FILE, + OPT_NULLSEP, OPT_IGNORE_DIR, - OPT_NOIGNORE_DIR, OPT_IGNORE_FILE, OPT_INCLUDE, OPT_EXCLUDE, OPT_FOLLOW, - OPT_NOFOLLOW, OPT_RECURSE_SUBDIRS, OPT_ONLY_KNOWN_TYPES, OPT_TYPE, @@ -138,8 +136,8 @@ enum OPT OPT_HELP_TYPES, OPT_USAGE, OPT_VERSION, + OPT_LINE_NUMBER, OPT_COLUMN, - OPT_NOCOLUMN, OPT_TEST_LOG_ALL, OPT_TEST_NOENV_USER, OPT_TEST_USE_MMAP, @@ -524,11 +522,17 @@ static const std::array f_raw_options = std::to_array({ { OPT_WORDREGEX, 0, "w", "word-regexp", Arg::None, "PATTERN must match a complete word."}, { OPT_LITERAL, 0, "Q", "literal", Arg::None, "Treat all characters in PATTERN as literal."}, { "Search Output:" }, + { OPT_PREFIX_FILE, ENABLE, "H", "with-filename", Arg::None, "Prefix the result with the file name."}, + { OPT_PREFIX_FILE, DISABLE, "h", "no-with-filename", Arg::None, "Prefix the file name on a separate line before matches (default)."}, + { OPT_LINE_NUMBER, ENABLE, "n", "line-number", Arg::None, "Print the line number of each match (default)."}, + { OPT_LINE_NUMBER, DISABLE, "", "no-line-number", Arg::None, "Don't print the line number."}, { OPT_COLUMN, ENABLE, "", "column", Arg::None, "Print column of first match after line number."}, { OPT_COLUMN, DISABLE, "", "nocolumn", Arg::None, "Don't print column of first match (default)."}, { "File presentation:" }, { OPT_COLOR, ENABLE, "", "color,colour", Arg::None, "Render the output with ANSI color codes."}, { OPT_COLOR, DISABLE, "", "nocolor,nocolour", Arg::None, "Render the output without ANSI color codes."}, + { OPT_NULLSEP, ENABLE, "", "null", Arg::None, + "Print a zero character '\0' instead of a colon ':' after a file name."}, { "File/directory inclusion/exclusion:" }, { OPT_IGNORE_DIR, ENABLE, DISABLE, "", "[no]ignore-dir,[no]ignore-directory", "NAME", Arg::NonEmpty, "[Do not] exclude directories with NAME."}, // grep-style --include=glob and --exclude=glob @@ -538,7 +542,7 @@ static const std::array f_raw_options = std::to_array({ // ack-style --ignore-file=FILTER:FILTERARGS { OPT_IGNORE_FILE, 0, "", "ignore-file", "FILTER:FILTERARGS", Arg::NonEmpty, "Files matching FILTER:FILTERARGS (e.g. ext:txt,cpp) will be ignored." }, { OPT_RECURSE_SUBDIRS, ENABLE, "r,R", "recurse", Arg::None, "Recurse into subdirectories (default: on)." }, - { OPT_RECURSE_SUBDIRS, DISABLE, "n", "no-recurse", Arg::None, "Do not recurse into subdirectories."}, + { OPT_RECURSE_SUBDIRS, DISABLE, "", "no-recurse", Arg::None, "Do not recurse into subdirectories."}, { OPT_FOLLOW, ENABLE, DISABLE, "", "[no]follow", "", Arg::None, "[Do not] follow symlinks (default: nofollow)." }, { OPT_ONLY_KNOWN_TYPES, ENABLE, "k", "known-types", Arg::None, "Only search in files of recognized types (default: on)."}, { OPT_TYPE, ENABLE, "", "type", "[no]TYPE", Arg::NonEmpty, "Include only [exclude all] TYPE files. Types may also be specified as --[no]TYPE."}, @@ -763,13 +767,41 @@ void ArgParse::Parse(int argc, char **argv) m_word_regexp = options[OPT_WORDREGEX]; m_pattern_is_literal = options[OPT_LITERAL]; - m_column = (options[OPT_COLUMN].last()->type() == ENABLE); + + // If the output is going to a terminal, use color and group + // the matches under the filename. So for the TTY, by + // default, we print: + // filename + // lineno:column:match + // [...] + // while for non-TTY we print: + // filename:lineno:column:match + // [...] + if(!isatty(fileno(stdout))) + { + m_prefix_file = true; + m_color=false; + } + if(options[OPT_COLOR]) // If not specified on command line, defaults to both == false. { m_color = (options[OPT_COLOR].last()->type() == ENABLE); - m_nocolor = !m_color; } + if(options[OPT_PREFIX_FILE]) + { + m_prefix_file = (options[OPT_PREFIX_FILE].last()->type() == ENABLE); + } + if(options[OPT_LINE_NUMBER]) + { + m_line_number = (options[OPT_LINE_NUMBER].last()->type() == ENABLE); + } + m_column = (options[OPT_COLUMN].last()->type() == ENABLE); + if(m_column) + { + m_line_number = true; + } + if(options[OPT_RECURSE_SUBDIRS]) // m_recurse defaults to true, so only assign if option was really given. { m_recurse = (options[OPT_RECURSE_SUBDIRS].last()->type() == ENABLE); diff --git a/src/ArgParse.h b/src/ArgParse.h index 0125738..2de21fe 100644 --- a/src/ArgParse.h +++ b/src/ArgParse.h @@ -123,6 +123,11 @@ class ArgParse bool m_pattern_is_literal { false }; /// true if we should print the column of the first match after the line number. + bool m_line_number { true }; + + /// true if we should print the column of the first match + /// after the line number. Setting this to true implies will + /// also set m_line_number to true. bool m_column { false }; /// The file and directory paths given on the command line. @@ -138,9 +143,13 @@ class ArgParse int m_dirjobs { 0 }; /// Whether to use color output or not. - /// both false == not specified on command line. - bool m_color { false }; - bool m_nocolor { false }; + bool m_color { true }; + + /// Whether to prefix each match with the filename + bool m_prefix_file { false }; + + /// Whether to write a null after a filename instead of ':'. + bool m_nullsep { false }; /// Whether to recurse into subdirectories or not. bool m_recurse { true }; diff --git a/src/MatchList.cpp b/src/MatchList.cpp index b14a693..f460104 100644 --- a/src/MatchList.cpp +++ b/src/MatchList.cpp @@ -23,7 +23,7 @@ #include #include -#include +#include "future/string.hpp" void MatchList::SetFilename(std::string filename) @@ -74,86 +74,57 @@ void MatchList::Print(std::ostream &sstrm, OutputContext &output_context) const std::string composition_buffer; composition_buffer.reserve(256); - - // The only real difference between TTY vs. non-TTY printing here is that for TTY we print: - // filename - // lineno:column:match - // [...] - // while for non-TTY we print: - // filename:lineno:column:match - // [...] - if(output_context.is_output_tty()) - { - // Render to a TTY device. - - // Print file header. - if(color) composition_buffer += *color_filename; - composition_buffer += no_dotslash_fn; - if(color) composition_buffer += *color_default; - composition_buffer += '\n'; - sstrm << composition_buffer; - - // Print the individual matches. - for(const Match& it : m_match_list) - { - composition_buffer.clear(); - if(color) composition_buffer += *color_lineno; - composition_buffer += std::to_string(it.m_line_number); - if(color) composition_buffer += *color_default; - composition_buffer += ':'; - sstrm << composition_buffer; - if(output_context.is_column_print_enabled()) - { - sstrm << it.m_pre_match.length()+1 << ':'; - } - composition_buffer.clear(); - composition_buffer += it.m_pre_match; - if(color) composition_buffer += *color_match; - composition_buffer += it.m_match; - if(color) composition_buffer += *color_default; - composition_buffer += it.m_post_match; - composition_buffer += '\n'; - sstrm << composition_buffer; - } - } - else - { - // Render to a pipe or file. - - for(const Match& it : m_match_list) - { - // Print file name at the beginning of each line. - composition_buffer.clear(); - if(color) composition_buffer += *color_filename; - composition_buffer += no_dotslash_fn; - if(color) composition_buffer += *color_default; - composition_buffer += ':'; - - // Line number. - if(color) composition_buffer += *color_lineno; - composition_buffer += std::to_string(it.m_line_number); - if(color) composition_buffer += *color_default; - composition_buffer += ':'; - - sstrm << composition_buffer; - - // The column, if enabled. - if(output_context.is_column_print_enabled()) - { - sstrm << it.m_pre_match.length()+1 << ':'; - } - - // The match text. - composition_buffer.clear(); - composition_buffer += it.m_pre_match; - if(color) composition_buffer += *color_match; - composition_buffer += it.m_match; - if(color) composition_buffer += *color_default; - composition_buffer += it.m_post_match; - composition_buffer += '\n'; - sstrm << composition_buffer; - } - } + + const char file_separator(output_context.use_nullsep() + ? '\0': (output_context.prefix_file() ? ':' : '\n')); + + if(!output_context.prefix_file()) + { + // Print file header. + if(color) composition_buffer += *color_filename; + composition_buffer += no_dotslash_fn; + if(color) composition_buffer += *color_default; + composition_buffer += file_separator; + sstrm << composition_buffer; + } + + // Print the individual matches. + for(const Match& it : m_match_list) + { + // Print file prefix. + if(output_context.prefix_file()) + { + composition_buffer.clear(); + if(color) composition_buffer += *color_filename; + composition_buffer += no_dotslash_fn; + if(color) composition_buffer += *color_default; + composition_buffer += file_separator; + sstrm << composition_buffer; + } + // Print line and column numbers. + if(output_context.is_line_print_enabled()) + { + composition_buffer.clear(); + if(color) composition_buffer += *color_lineno; + composition_buffer += std::to_string(it.m_line_number); + if(color) composition_buffer += *color_default; + composition_buffer += ':'; + sstrm << composition_buffer; + if(output_context.is_column_print_enabled()) + { + sstrm << it.m_pre_match.length()+1 << ':'; + } + } + // Print match + composition_buffer.clear(); + composition_buffer += it.m_pre_match; + if(color) composition_buffer += *color_match; + composition_buffer += it.m_match; + if(color) composition_buffer += *color_default; + composition_buffer += it.m_post_match; + composition_buffer += '\n'; + sstrm << composition_buffer; + } } std::vector::size_type MatchList::GetNumberOfMatchedLines() const noexcept diff --git a/src/OutputContext.cpp b/src/OutputContext.cpp index 1fe6ca3..8b81f58 100644 --- a/src/OutputContext.cpp +++ b/src/OutputContext.cpp @@ -21,8 +21,11 @@ #include "OutputContext.h" -OutputContext::OutputContext(bool output_is_tty, bool enable_color, bool print_column) - : m_output_is_tty(output_is_tty), m_enable_color(enable_color), m_print_column(print_column) +OutputContext::OutputContext(bool enable_color, bool prefix_file, bool print_line_number, + bool print_column, bool nullsep) + : m_enable_color(enable_color), m_prefix_file(prefix_file), + m_print_line_number(print_line_number), + m_print_column(print_column), m_nullsep(nullsep) { if(m_enable_color) { diff --git a/src/OutputContext.h b/src/OutputContext.h index 92c0318..1311c86 100644 --- a/src/OutputContext.h +++ b/src/OutputContext.h @@ -30,12 +30,14 @@ class OutputContext { public: - OutputContext(bool output_is_tty, bool enable_color, bool print_column); + OutputContext(bool enable_color, bool prefix_file, bool print_line_number, bool print_column, bool nullsep); ~OutputContext(); - [[nodiscard]] inline bool is_output_tty() const noexcept { return m_output_is_tty; }; [[nodiscard]] inline bool is_color_enabled() const noexcept { return m_enable_color; }; + [[nodiscard]] inline bool prefix_file() const noexcept { return m_prefix_file; }; + [[nodiscard]] inline bool is_line_print_enabled() const noexcept { return m_print_line_number; }; [[nodiscard]] inline bool is_column_print_enabled() const noexcept { return m_print_column; }; + [[nodiscard]] inline bool use_nullsep() const noexcept { return m_nullsep; }; /// @name Active colors. /// @{ @@ -47,14 +49,21 @@ class OutputContext private: - bool m_output_is_tty; - /// Whether to output color or not. Determined by logic in OutputTask's constructor. bool m_enable_color; + /// Whether to prefix each match with the filename + bool m_prefix_file; + + /// Whether to print the line number of the match or not + bool m_print_line_number; + /// Whether to print the column number of the first match or not. bool m_print_column; + /// Whether to write a null after a filename instead of ':'. + bool m_nullsep; + /// @name Default output colors. /// @{ // ANSI SGR parameter setting sequences for setting the color and boldness of the output text. diff --git a/src/OutputTask.cpp b/src/OutputTask.cpp index e25a4cd..efcce3e 100644 --- a/src/OutputTask.cpp +++ b/src/OutputTask.cpp @@ -31,28 +31,14 @@ #include -OutputTask::OutputTask(bool flag_color, bool flag_nocolor, bool flag_column, sync_queue &input_queue) - : m_input_queue(input_queue) +OutputTask::OutputTask(bool flag_color, bool flag_prefix_file, bool flag_line_number, + bool flag_column, bool flag_nullsep, sync_queue &input_queue) + : m_input_queue(input_queue), m_enable_color(flag_color), + m_prefix_file(flag_prefix_file), m_print_line_number(flag_line_number), + m_print_column(flag_column), m_nullsep(flag_nullsep) { - // Determine if the output is going to a terminal. If so we'll use color by default, group the matches under - // the filename, etc. - m_output_is_tty = isatty(fileno(stdout)); - - // Determine whether to enable color or not. - // Color is enabled if explicitly specified with --color or - // if outputting to a TTY and --nocolor is not specified. - if(flag_color || (!flag_nocolor && m_output_is_tty)) - { - m_enable_color = true; - } - else - { - m_enable_color = false; - } - - m_print_column = flag_column; - - m_output_context.reset(new OutputContext(m_output_is_tty, m_enable_color, m_print_column)); + m_output_context.reset(new OutputContext(m_enable_color, m_prefix_file, m_print_line_number, + m_print_column, m_nullsep)); } OutputTask::~OutputTask() @@ -69,7 +55,7 @@ void OutputTask::Run() while(m_input_queue.pull_front(std::move(ml)) != queue_op_status::closed) { - if(first_matchlist_printed && m_output_is_tty) + if(first_matchlist_printed && !m_prefix_file) { // Print a blank line between the match lists (i.e. the groups of matches in one file). std::cout << '\n'; diff --git a/src/OutputTask.h b/src/OutputTask.h index f12987c..2540771 100644 --- a/src/OutputTask.h +++ b/src/OutputTask.h @@ -35,7 +35,8 @@ class OutputTask { public: - OutputTask(bool flag_color, bool flag_nocolor, bool flag_column, sync_queue &input_queue); + OutputTask(bool flag_color, bool flag_prefix_file, bool flag_line_number, + bool flag_column, bool flag_nullsep, sync_queue &input_queue); virtual ~OutputTask(); void Run(); @@ -47,15 +48,21 @@ class OutputTask /// The queue from which we'll pull our MatchLists. sync_queue &m_input_queue; - /// Whether stdout is a TTY. Determined in constructor. - bool m_output_is_tty; - /// Whether to output color or not. Determined in constructor. bool m_enable_color; + /// Whether to prefix each match with the filename + bool m_prefix_file; + + /// Whether to print the line number of the match or not. + bool m_print_line_number; + /// Whether to print the column number of the first match or not. bool m_print_column; + /// Whether to write a null after a filename instead of ':'. + bool m_nullsep; + std::unique_ptr m_output_context; /// The total number of matched lines as reported by the incoming MatchLists.