From 74716d30ef02b7d8b8b64b9b124382f3c69715b3 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Thu, 27 Jun 2024 09:56:36 -0700 Subject: [PATCH] Allow to provide 'grep' regex for print to narrow output. --- README.md | 16 +++++++++-- bant/bant.cc | 26 ++++++++++++----- bant/cli-commands.cc | 6 ++-- bant/frontend/BUILD | 2 ++ bant/frontend/parsed-project.cc | 50 ++++++++++++++++++++++----------- bant/frontend/parsed-project.h | 5 +++- bant/frontend/print-visitor.cc | 6 ++++ bant/frontend/print-visitor.h | 10 ++++++- bant/session.h | 1 + 9 files changed, 91 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index eba0592..e85605a 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,13 @@ of these and print the final form: bant print -b @googletest//:gtest ``` +The `-g` option allows to 'grep' for targets where any of the strings in the +AST of a rule matches a pattern + +``` +bant print ... -g "arena.*test" +``` + #### Workspace **`workspace`** prints all the external projects found in the workspace. @@ -229,10 +236,13 @@ Options Commands (unique prefix sufficient): == Parsing == print : Print AST matching pattern. -e : only files w/ parse errors - -b : elaBorate; light eval: expand variables, concat etc. + -b : elaBorate; light eval: expand variables, concat etc. + -g : 'grep' - only print targets where any string + matches regex. + -i If '-g' is given: case insensitive parse : Parse all BUILD files from pattern. Follow deps with -r Emit parse errors. Silent otherwise: No news are good news. - -v : some stats. + -v : some stats. == Extract facts == (Use -f to choose output format) == workspace : Print external projects found in WORKSPACE. @@ -258,7 +268,7 @@ Commands (unique prefix sufficient): == Tools == dwyu : DWYU: Depend on What You Use (emit buildozer edit script) - -k strict: emit remove even if # keep comment in line. + -k strict: emit remove even if # keep comment in line. canonicalize : Emit rename edits to canonicalize targets. ``` diff --git a/bant/bant.cc b/bant/bant.cc index 0e0cbe2..31dc79e 100644 --- a/bant/bant.cc +++ b/bant/bant.cc @@ -81,10 +81,13 @@ static int usage(const char *prog, const char *message, int exit_code) { Commands (unique prefix sufficient): %s== Parsing ==%s print : Print AST matching pattern. -e : only files w/ parse errors - -b : elaBorate; light eval: expand variables, concat etc. + -b : elaBorate; light eval: expand variables, concat etc. + -g : 'grep' - only print targets where any string + matches regex. + -i If '-g' is given: case insensitive parse : Parse all BUILD files from pattern. Follow deps with -r Emit parse errors. Silent otherwise: No news are good news. - -v : some stats. + -v : some stats. %s== Extract facts ==%s (Use -f to choose output format) == workspace : Print external projects found in WORKSPACE. @@ -110,7 +113,7 @@ Commands (unique prefix sufficient): %s== Tools ==%s dwyu : DWYU: Depend on What You Use (emit buildozer edit script) - -k strict: emit remove even if # keep comment in line. + -k strict: emit remove even if # keep comment in line. canonicalize : Emit rename edits to canonicalize targets. )", BOLD, RESET, BOLD, RESET, BOLD, RESET); @@ -132,6 +135,8 @@ int main(int argc, char *argv[]) { bant::CommandlineFlags flags; + bool regex_case_insesitive = false; + using bant::OutputFormat; static const std::map kFormatOutNames = { {"native", OutputFormat::kNative}, {"s-expr", OutputFormat::kSExpr}, @@ -139,7 +144,7 @@ int main(int argc, char *argv[]) { {"json", OutputFormat::kJSON}, {"graphviz", OutputFormat::kGraphviz}, }; int opt; - while ((opt = getopt(argc, argv, "C:qo:vhpecbf:r::Vk")) != -1) { + while ((opt = getopt(argc, argv, "C:qo:vhpecbf:r::Vkg:i")) != -1) { switch (opt) { case 'C': { std::error_code err; @@ -176,10 +181,13 @@ int main(int argc, char *argv[]) { : std::numeric_limits::max(); break; - case 'k': - flags.ignore_keep_comment = true; - break; + case 'k': flags.ignore_keep_comment = true; break; + + case 'g': flags.grep_regex = optarg; break; + case 'i': + regex_case_insesitive = true; + break; // "print" options case 'p': flags.print_ast = true; break; case 'e': flags.print_only_errors = true; break; @@ -197,6 +205,10 @@ int main(int argc, char *argv[]) { } } + if (regex_case_insesitive && !flags.grep_regex.empty()) { + flags.grep_regex.insert(0, "(?i)"); + } + bant::FilesystemPrewarmCacheInit(argc, argv); bant::Session session(primary_out, info_out, flags); diff --git a/bant/cli-commands.cc b/bant/cli-commands.cc index e966111..d3b47e5 100644 --- a/bant/cli-commands.cc +++ b/bant/cli-commands.cc @@ -180,10 +180,12 @@ CliStatus RunCommand(Session &session, Command cmd, switch (cmd) { case Command::kPrint: flags.print_ast = true; [[fallthrough]]; case Command::kParse: - // Parsing has already be done by now by building the dependency graph + // Parsing has already be done by now by building the dependency graph, + // so it would already have emitted parse errors. Here we only have to + // decide if we print anything. if (flags.print_ast || flags.print_only_errors) { bant::PrintProject(pattern, session.out(), session.info(), project, - flags.print_only_errors); + flags.print_only_errors, flags.grep_regex); } break; diff --git a/bant/frontend/BUILD b/bant/frontend/BUILD index 3580931..014e47f 100644 --- a/bant/frontend/BUILD +++ b/bant/frontend/BUILD @@ -73,6 +73,7 @@ cc_library( "//bant/util:memory", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/strings", + "@re2", ], ) @@ -120,6 +121,7 @@ cc_library( "//bant/util:memory", "//bant/util:stat", "@abseil-cpp//absl/log:check", + "@re2", ], ) diff --git a/bant/frontend/parsed-project.cc b/bant/frontend/parsed-project.cc index e807996..657648a 100644 --- a/bant/frontend/parsed-project.cc +++ b/bant/frontend/parsed-project.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include "bant/util/file-utils.h" #include "bant/util/stat.h" #include "bant/workspace.h" +#include "re2/re2.h" namespace bant { namespace { @@ -236,7 +238,19 @@ const ParsedBuildFile *ParsedProject::FindParsedOrNull( void PrintProject(const BazelPattern &pattern, std::ostream &out, std::ostream &info_out, const ParsedProject &project, - bool only_files_with_errors) { + bool only_files_with_errors, std::string_view grep_regex) { + std::unique_ptr regex; + if (!grep_regex.empty()) { + // TODO: pass options to not log error + regex = std::make_unique(grep_regex); + if (!regex->ok()) { + // This really needs the session passed in so that we can reach the + // correct error stream. + std::cerr << "Grep pattern: " << regex->error() << "\n"; + return; + } + } + for (const auto &[package, file_content] : project.ParsedFiles()) { if (only_files_with_errors && file_content->errors.empty()) { continue; @@ -245,26 +259,28 @@ void PrintProject(const BazelPattern &pattern, std::ostream &out, continue; } - if (pattern.is_recursive()) { - out << "# " << file_content->name() << ": " - << file_content->package.ToString() << "\n"; - info_out << file_content->errors; - PrintVisitor(out).WalkNonNull(file_content->ast); - out << "\n"; - } else { - query::FindTargets( - file_content->ast, {}, [&](const query::Result &result) { + query::FindTargetsAllowEmptyName( + file_content->ast, {}, [&](const query::Result &result) { + if (!pattern.is_recursive()) { + // Need more specific check if matches. auto self = BazelTarget::ParseFrom(result.name, package); if (!self.has_value() || !pattern.Match(*self)) { return; } - // TODO: instead of just marking the range of the function name, - // show the range the whole function covers until closed parenthesis. - out << "# " << project.Loc(result.node->identifier()->id()) << "\n"; - PrintVisitor(out).WalkNonNull(result.node); - out << "\n"; - }); - } + } + + // TODO: instead of just marking the range of the function name, + // show the range the whole function covers until closed parenthesis. + // TODO: if isatty(out), color filename gray + std::stringstream tmp_out; + tmp_out << "# " << project.Loc(result.node->identifier()->id()) << "\n"; + PrintVisitor printer(tmp_out, regex.get()); + printer.WalkNonNull(result.node); + tmp_out << "\n"; + if (!regex || printer.any_highlight()) { // w/o regex: always print. + out << tmp_out.str(); + } + }); } } } // namespace bant diff --git a/bant/frontend/parsed-project.h b/bant/frontend/parsed-project.h index 4efb929..d9cc809 100644 --- a/bant/frontend/parsed-project.h +++ b/bant/frontend/parsed-project.h @@ -133,8 +133,11 @@ class ParsedProject : public SourceLocator { // "out" is the destination of the acutal parse tree, "info_out" will // print error message and filenames. // If "only_files_with_errors" is set, prints only the files that had issues. +// With "grep_regex", only targets are printed that have any substring match +// expression. +// TODO: this function has too many parameters. void PrintProject(const BazelPattern &pattern, std::ostream &out, std::ostream &info_out, const ParsedProject &project, - bool only_files_with_errors); + bool only_files_with_errors, std::string_view grep_regex); } // namespace bant #endif // BANT_PROJECT_PARDER_ diff --git a/bant/frontend/print-visitor.cc b/bant/frontend/print-visitor.cc index 28b4ef4..97ad5b9 100644 --- a/bant/frontend/print-visitor.cc +++ b/bant/frontend/print-visitor.cc @@ -22,6 +22,7 @@ #include "bant/frontend/ast.h" #include "bant/frontend/scanner.h" +#include "re2/re2.h" namespace bant { void PrintVisitor::VisitFunCall(FunCall *f) { @@ -122,6 +123,11 @@ void PrintVisitor::VisitScalar(Scalar *s) { str->AsString().find_first_of('"') != std::string_view::npos; const char quote_char = has_any_double_quote ? '\'' : '"'; if (str->is_triple_quoted()) out_ << quote_char << quote_char; + if (optional_highlight_) { + // TODO: actually highlight. + any_highlight_ |= + RE2::PartialMatch(str->AsString(), *optional_highlight_); + } out_ << quote_char << str->AsString() << quote_char; if (str->is_triple_quoted()) out_ << quote_char << quote_char; } diff --git a/bant/frontend/print-visitor.h b/bant/frontend/print-visitor.h index c58be38..0cf813b 100644 --- a/bant/frontend/print-visitor.h +++ b/bant/frontend/print-visitor.h @@ -18,14 +18,18 @@ #ifndef BANT_PRINT_VISITOR_H #define BANT_PRINT_VISITOR_H +#include #include #include "bant/frontend/ast.h" +#include "re2/re2.h" namespace bant { class PrintVisitor : public BaseVoidVisitor { public: - explicit PrintVisitor(std::ostream &out) : out_(out) {} + explicit PrintVisitor(std::ostream &out, + const RE2 *optional_highlight = nullptr) + : out_(out), optional_highlight_(optional_highlight) {} // Using default impl. for Assignment. void VisitFunCall(FunCall *f) final; void VisitList(List *l) final; @@ -38,9 +42,13 @@ class PrintVisitor : public BaseVoidVisitor { void VisitScalar(Scalar *s) final; void VisitIdentifier(Identifier *i) final; + bool any_highlight() const { return any_highlight_; } + private: int indent_ = 0; std::ostream &out_; + const RE2 *const optional_highlight_; + bool any_highlight_ = false; }; } // namespace bant diff --git a/bant/session.h b/bant/session.h index ba64c20..85b8d12 100644 --- a/bant/session.h +++ b/bant/session.h @@ -53,6 +53,7 @@ struct CommandlineFlags { bool ignore_keep_comment = false; int recurse_dependency_depth = 0; OutputFormat output_format = OutputFormat::kNative; + std::string grep_regex; }; // A session contains some settings such as output/verbose requests