Adding grep tool
This commit is contained in:
@@ -14,6 +14,7 @@ set(TOOL_SOURCES
|
||||
tools/edit_tool.cpp
|
||||
tools/file_list_tool.cpp
|
||||
tools/file_read_tool.cpp
|
||||
tools/grep_tool.cpp
|
||||
)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "edit_tool.hpp"
|
||||
#include "file_list_tool.hpp"
|
||||
#include "file_read_tool.hpp"
|
||||
#include "grep_tool.hpp"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -44,6 +45,7 @@ int main(int argc, char** argv) {
|
||||
master_registery.register_tool(make_edit_tool());
|
||||
master_registery.register_tool(make_file_list_tool());
|
||||
master_registery.register_tool(make_file_read_tool());
|
||||
master_registery.register_tool(make_grep_tool());
|
||||
#if !defined(_WIN32)
|
||||
master_registery.register_tool(make_shell_tool());
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "grep_tool.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
|
||||
// Helper to skip binary files by checking for null bytes
|
||||
static bool is_text_file(const std::string& path) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f.is_open()) return false;
|
||||
char buffer[512];
|
||||
f.read(buffer, sizeof(buffer));
|
||||
for (std::streamsize i = 0; i < f.gcount(); ++i) {
|
||||
if (buffer[i] == '\0') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Tool make_grep_tool() {
|
||||
return Tool{
|
||||
.name = "grep_search",
|
||||
.description = "Searches for a pattern in a file or recursively in a directory. "
|
||||
"Displays matches with line numbers and optional context. Follows the path as a file or directory.",
|
||||
.parameters_schema = {
|
||||
{"type", "object"},
|
||||
{"properties", {
|
||||
{"path", {
|
||||
{"type", "string"},
|
||||
{"description", "The file or directory path to search."}
|
||||
}},
|
||||
{"pattern", {
|
||||
{"type", "string"},
|
||||
{"description", "The pattern to search for (standard regex)."}
|
||||
}},
|
||||
{"context", {
|
||||
{"type", "integer"},
|
||||
{"description", "Number of lines to show before and after the match. Defaults to 0."}
|
||||
}},
|
||||
{"case_insensitive", {
|
||||
{"type", "boolean"},
|
||||
{"description", "Perform case-insensitive search. Defaults to false."}
|
||||
}}
|
||||
}},
|
||||
{"required", {"path", "pattern"}}
|
||||
},
|
||||
.execute = [](const nlohmann::json& args) -> std::string {
|
||||
if (!args.contains("path") || !args["path"].is_string() ||
|
||||
!args.contains("pattern") || !args["pattern"].is_string()) {
|
||||
return "Error: missing or invalid required parameter 'path' or 'pattern'";
|
||||
}
|
||||
|
||||
std::string path = args["path"].get<std::string>();
|
||||
std::string pattern = args["pattern"].get<std::string>();
|
||||
int context = 0;
|
||||
bool case_insensitive = false;
|
||||
|
||||
if (args.contains("context") && args["context"].is_number_integer())
|
||||
context = args["context"].get<int>();
|
||||
if (context < 0) context = 0;
|
||||
|
||||
if (args.contains("case_insensitive") && args["case_insensitive"].is_boolean())
|
||||
case_insensitive = args["case_insensitive"].get<bool>();
|
||||
|
||||
if (!std::filesystem::exists(path))
|
||||
return "Error: path does not exist: " + path;
|
||||
|
||||
auto regex_flags = case_insensitive ? std::regex::icase : std::regex::ECMAScript;
|
||||
std::regex re;
|
||||
try {
|
||||
re.assign(pattern, regex_flags);
|
||||
} catch (const std::regex_error& e) {
|
||||
return "Error: invalid regex pattern: " + std::string(e.what());
|
||||
}
|
||||
|
||||
std::string output;
|
||||
|
||||
auto process_file = [&](const std::string& file_path) -> void {
|
||||
if (!is_text_file(file_path)) return;
|
||||
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) return;
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
int last_printed_line = -1;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
std::smatch match;
|
||||
if (std::regex_search(lines[i], match, re)) {
|
||||
int start = std::max(0, static_cast<int>(i) - context);
|
||||
int end = std::min(static_cast<int>(lines.size()) - 1, static_cast<int>(i) + context);
|
||||
|
||||
for (int j = start; j <= end; ++j) {
|
||||
if (j > last_printed_line) {
|
||||
bool is_match_line = (j == static_cast<int>(i));
|
||||
output += file_path + ":" + std::to_string(j + 1) + ":";
|
||||
if (is_match_line) output += "[MATCH] ";
|
||||
output += lines[j] + "\n";
|
||||
last_printed_line = j;
|
||||
}
|
||||
}
|
||||
output += "=== " + file_path + " ===\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
process_file(path);
|
||||
} else if (std::filesystem::is_directory(path)) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) {
|
||||
if (std::filesystem::is_regular_file(entry.path())) {
|
||||
process_file(entry.path().string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "Error: path is neither a regular file nor a directory: " + path;
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
output += "Error iterating path: " + std::string(e.what()) + "\n";
|
||||
}
|
||||
|
||||
return output.empty() ? "(No matches found)" : output;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "tool.hpp"
|
||||
|
||||
Tool make_grep_tool();
|
||||
Reference in New Issue
Block a user