-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
162 lines (152 loc) · 5.78 KB
/
Copy pathmain.cpp
File metadata and controls
162 lines (152 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include <optional>
#include "matcher.h"
#include "thread_pool.h"
#include "utils.h"
namespace fs = std::filesystem;
namespace mb {
/**
* @brief Holds options for the search operation.
*/
struct SearchOptions {
std::string query; ///< The search pattern
fs::path root_path{}; ///< Directory to search
bool use_regex = false; ///< Use regex for matching
bool ignore_case = false; ///< Case-insensitive search
std::optional<std::string> file_extension = std::nullopt; ///< Optional file extension filter
};
} // namespace mb
namespace {
/**
* @brief Searches the given file for matches to the pattern.
*
* @param filePath Path to the file being searched.
* @param matcher The matcher object used to determine pattern match.
* @param output_mutex Mutex used to guard console output.
*/
void search_file(const fs::path& filePath, const mb::IMatcher& matcher, std::mutex& output_mutex) {
std::ifstream file{filePath};
if (!file) {
return;
}
std::string line{};
constexpr int buffer_size = 1024;
line.reserve(buffer_size);
size_t line_num = 0;
while (std::getline(file, line)) {
++line_num;
if (matcher.match(line)) {
const std::scoped_lock lock{output_mutex};
std::cout << filePath << ", line num: " << line_num << ": " << line << std::endl;
}
}
}
/**
* @brief Recursively walks through a directory and searches files for matching lines.
*
* This function traverses the directory tree rooted at the specified path and submits
* file search tasks to the provided thread pool. It filters out non-regular and binary files,
* and optionally limits search to files with a given extension.
*
* @param options The search configuration, including root path, file extension, and query flags.
* @param pool A thread pool used to parallelize file search operations.
* @param matcher The matcher used to determine whether a line satisfies the query.
* @param output_mtx A mutex used to synchronize access to the standard output.
*/
void walk_directory(const mb::SearchOptions& options, mb::ThreadPool& pool, const mb::IMatcher& matcher, std::mutex& output_mtx) {
const auto& root_path = options.root_path;
const auto& file_extension = options.file_extension;
for (const auto& entry : fs::recursive_directory_iterator(root_path)) {
if (!entry.is_regular_file()) {
continue;
}
const auto& path = entry.path();
if (mb::is_binary_file(path)) {
continue;
}
if (file_extension.has_value() && file_extension.value() != path.extension()) {
continue;
}
pool.submit([path, &matcher, &output_mtx] { search_file(path, matcher, output_mtx); });
}
}
/**
* @brief Creates a matcher based on the search options.
*
* This function constructs a matcher object based on whether regex mode is enabled
* and whether case should be ignored. It returns a smart pointer to the appropriate
* matcher implementation.
*
* @param options The search configuration including query string, flags for regex and case sensitivity.
* @return A unique pointer to a matcher object capable of evaluating lines against the query.
*/
std::unique_ptr<mb::IMatcher> make_matcher(const mb::SearchOptions& options) {
if (options.use_regex) {
return std::make_unique<mb::RegexMatcher>(options.query, options.ignore_case);
}
return std::make_unique<mb::SubstringMatcher>(options.query, options.ignore_case);
}
/**
* @brief Extracts search options from command-line arguments.
*
* @param argc Argument count.
* @param argv Argument vector.
* @return SearchOptions Parsed search configuration.
*/
mb::SearchOptions extract_arguments(const int argc, char* argv[]) {
mb::SearchOptions options{};
options.query = argv[1];
options.root_path = argv[2];
for (int i = 3; i < argc; ++i) {
if (const std::string arg = argv[i]; arg == "--regex") {
options.use_regex = true;
} else if (arg == "--ignore-case") {
options.ignore_case = true;
} else if (arg.starts_with("--ext=")) {
constexpr int prefix_size = 6;
options.file_extension = arg.substr(prefix_size);
}
}
return options;
}
/**
* @brief Prints usage information for the program.
*
* This function outputs instructions on how to use the command-line tool,
* including accepted arguments and flags.
*
* @param program_name The name of the executable, typically from argv[0].
*/
void help(const std::string& program_name) {
std::cerr << "Usage: " << program_name << " <query> <directory> [--regex] [--ignore-case] [--ext=.txt]"
<< std::endl;
}
} // namespace
int main(int argc, char* argv[]) {
constexpr int minimalArgCount = 3;
if (argc < minimalArgCount) {
help(argv[0]);
return 1;
}
try {
auto options = extract_arguments(argc, argv);
if (!options.use_regex && mb::contains_regex_chars(options.query)) {
std::cerr << "Warning: The pattern \"" << options.query
<< "\" looks like a regular expression, but --regex flag was not set.\n";
return 1;
}
auto matcher = make_matcher(options);
const auto num_threads = mb::get_threads_number();
mb::ThreadPool pool{num_threads};
std::mutex output_mutex{};
walk_directory(options, pool, *matcher, output_mutex);
} catch (const std::exception& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception!" << std::endl;
}
return 0;
}