From 408416020fdbccf6d7e4d7bfd607ea46df74d63e Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Thu, 15 Jun 2023 00:24:40 +0200 Subject: [PATCH] Added --progress option for diagram progress indicators --- src/cli/cli_handler.cc | 18 ++- src/cli/cli_handler.h | 1 + src/common/generators/generators.cc | 93 ++++++++++++--- src/common/generators/generators.h | 28 +++-- src/common/generators/progress_indicator.cc | 121 ++++++++++++++++++++ src/common/generators/progress_indicator.h | 55 +++++++++ src/main.cc | 2 +- 7 files changed, 289 insertions(+), 29 deletions(-) create mode 100644 src/common/generators/progress_indicator.cc create mode 100644 src/common/generators/progress_indicator.h diff --git a/src/cli/cli_handler.cc b/src/cli/cli_handler.cc index b3d1daac..aa72e612 100644 --- a/src/cli/cli_handler.cc +++ b/src/cli/cli_handler.cc @@ -26,6 +26,7 @@ #include #include +#include namespace clanguml::cli { cli_handler::cli_handler( @@ -38,7 +39,17 @@ cli_handler::cli_handler( void cli_handler::setup_logging() { spdlog::drop("clanguml-logger"); - spdlog::register_logger(logger_); + + if (!progress) { + spdlog::register_logger(logger_); + } + else { + // Setup null logger for clean progress indicators + std::vector sinks; + logger_ = std::make_shared( + "clanguml-logger", begin(sinks), end(sinks)); + spdlog::register_logger(logger_); + } logger_->set_pattern("[%^%l%^] [tid %t] %v"); @@ -79,6 +90,8 @@ cli_flow_t cli_handler::parse(int argc, const char **argv) app.add_flag("-V,--version", show_version, "Print version and exit"); app.add_flag("-v,--verbose", verbose, "Verbose logging (use multiple times to increase - e.g. -vvv)"); + app.add_flag( + "-p,--progress", progress, "Show progress bars for generated diagrams"); app.add_flag("-q,--quiet", quiet, "Minimal logging"); app.add_flag("-l,--list-diagrams", list_diagrams, "Print list of diagrams defined in the config file"); @@ -135,6 +148,9 @@ cli_flow_t cli_handler::parse(int argc, const char **argv) else verbose++; + if (progress) + verbose = 0; + return cli_flow_t::kContinue; } diff --git a/src/cli/cli_handler.h b/src/cli/cli_handler.h index 6e4f097b..86239fe3 100644 --- a/src/cli/cli_handler.h +++ b/src/cli/cli_handler.h @@ -118,6 +118,7 @@ public: unsigned int thread_count{}; bool show_version{false}; int verbose{}; + bool progress{false}; bool list_diagrams{false}; bool quiet{false}; bool initialize{false}; diff --git a/src/common/generators/generators.cc b/src/common/generators/generators.cc index 45419224..6271e4cc 100644 --- a/src/common/generators/generators.cc +++ b/src/common/generators/generators.cc @@ -18,6 +18,8 @@ #include "generators.h" +#include "progress_indicator.h" + namespace clanguml::common::generators { void find_translation_units_for_diagrams( const std::vector &diagram_names, @@ -82,7 +84,7 @@ void generate_diagram_impl(const std::string &od, const std::string &name, const common::compilation_database &db, const std::vector &translation_units, const std::vector &generators, - bool verbose) + bool verbose, std::function &&progress) { using diagram_config = DiagramConfig; using diagram_model = typename diagram_model_t::type; @@ -90,7 +92,8 @@ void generate_diagram_impl(const std::string &od, const std::string &name, auto model = clanguml::common::generators::generate(db, diagram->name, - dynamic_cast(*diagram), translation_units, verbose); + dynamic_cast(*diagram), translation_units, verbose, + std::move(progress)); for (const auto generator_type : generators) { if (generator_type == generator_type_t::plantuml) { @@ -110,7 +113,7 @@ void generate_diagram(const std::string &od, const std::string &name, const common::compilation_database &db, const std::vector &translation_units, const std::vector &generators, - bool verbose) + bool verbose, std::function &&progress) { using clanguml::common::generator_type_t; using clanguml::common::model::diagram_t; @@ -121,27 +124,27 @@ void generate_diagram(const std::string &od, const std::string &name, using clanguml::config::sequence_diagram; if (diagram->type() == diagram_t::kClass) { - detail::generate_diagram_impl( - od, name, diagram, db, translation_units, generators, verbose); + detail::generate_diagram_impl(od, name, diagram, db, + translation_units, generators, verbose, std::move(progress)); } else if (diagram->type() == diagram_t::kSequence) { - detail::generate_diagram_impl( - od, name, diagram, db, translation_units, generators, verbose); + detail::generate_diagram_impl(od, name, diagram, db, + translation_units, generators, verbose, std::move(progress)); } else if (diagram->type() == diagram_t::kPackage) { - detail::generate_diagram_impl( - od, name, diagram, db, translation_units, generators, verbose); + detail::generate_diagram_impl(od, name, diagram, db, + translation_units, generators, verbose, std::move(progress)); } else if (diagram->type() == diagram_t::kInclude) { - detail::generate_diagram_impl( - od, name, diagram, db, translation_units, generators, verbose); + detail::generate_diagram_impl(od, name, diagram, db, + translation_units, generators, verbose, std::move(progress)); } } void generate_diagrams(const std::vector &diagram_names, config::config &config, const std::string &od, const common::compilation_database_ptr &db, const int verbose, - const unsigned int thread_count, + const unsigned int thread_count, bool progress, const std::vector &generators, const std::map> &translation_units_map) @@ -149,6 +152,14 @@ void generate_diagrams(const std::vector &diagram_names, util::thread_pool_executor generator_executor{thread_count}; std::vector> futs; + std::unique_ptr indicator; + + if (progress) { + std::cout << termcolor::white + << "Processing translation units and generating diagrams:\n"; + indicator = std::make_unique(); + } + for (const auto &[name, diagram] : config.diagrams) { // If there are any specific diagram names provided on the command // line, and this diagram is not in that list - skip it @@ -158,22 +169,44 @@ void generate_diagrams(const std::vector &diagram_names, const auto &valid_translation_units = translation_units_map.at(name); if (valid_translation_units.empty()) { - LOG_ERROR("Diagram {} generation failed: no translation units " - "found. Please make sure that your 'glob' patterns match " - "at least 1 file in 'compile_commands.json'.", - name); + if (indicator) { + indicator->add_progress_bar( + name, 0, diagram_type_to_color(diagram->type())); + indicator->fail(name); + } + else { + LOG_ERROR( + "Diagram {} generation failed: no translation units " + "found. Please make sure that your 'glob' patterns match " + "at least 1 file in 'compile_commands.json'.", + name); + } continue; } futs.emplace_back(generator_executor.add( - [&od, &generators, &name = name, &diagram = diagram, + [&od, &generators, &name = name, &diagram = diagram, &indicator, db = std::ref(*db), translation_units = valid_translation_units, - verbose]() { + verbose]() mutable { try { + if (indicator) + indicator->add_progress_bar(name, + translation_units.size(), + diagram_type_to_color(diagram->type())); + generate_diagram(od, name, diagram, db, translation_units, - generators, verbose != 0); + generators, verbose != 0, [&indicator, &name]() { + if (indicator) + indicator->increment(name); + }); + + if (indicator) + indicator->complete(name); } catch (std::runtime_error &e) { + if (indicator) + indicator->fail(name); + LOG_ERROR(e.what()); } })); @@ -182,6 +215,28 @@ void generate_diagrams(const std::vector &diagram_names, for (auto &fut : futs) { fut.get(); } + + if (progress) { + indicator->stop(); + std::cout << termcolor::white << "Done\n"; + std::cout << termcolor::reset; + } +} + +indicators::Color diagram_type_to_color(model::diagram_t diagram_type) +{ + switch (diagram_type) { + case model::diagram_t::kClass: + return indicators::Color::yellow; + case model::diagram_t::kSequence: + return indicators::Color::blue; + case model::diagram_t::kPackage: + return indicators::Color::cyan; + case model::diagram_t::kInclude: + return indicators::Color::magenta; + default: + return indicators::Color::unspecified; + } } } // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index 7b6acdec..f6ad1db3 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -26,6 +26,7 @@ #include "config/config.h" #include "include_diagram/generators/json/include_diagram_generator.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" +#include "indicators/indicators.hpp" #include "package_diagram/generators/json/package_diagram_generator.h" #include "package_diagram/generators/plantuml/package_diagram_generator.h" #include "sequence_diagram/generators/json/sequence_diagram_generator.h" @@ -164,10 +165,11 @@ template class diagram_fronted_action : public clang::ASTFrontendAction { public: - explicit diagram_fronted_action( - DiagramModel &diagram, const DiagramConfig &config) + explicit diagram_fronted_action(DiagramModel &diagram, + const DiagramConfig &config, std::function progress) : diagram_{diagram} , config_{config} + , progress_{std::move(progress)} { } @@ -191,6 +193,9 @@ protected: { LOG_DBG("Visiting source file: {}", getCurrentFile().str()); + if (progress_) + progress_(); + if constexpr (std::is_same_v) { auto find_includes_callback = @@ -208,6 +213,7 @@ protected: private: DiagramModel &diagram_; const DiagramConfig &config_; + std::function progress_; }; template progress) : diagram_{diagram} , config_{config} + , progress_{progress} { } std::unique_ptr create() override { return std::make_unique>(diagram_, config_); + DiagramConfig, DiagramVisitor>>(diagram_, config_, progress_); } private: DiagramModel &diagram_; const DiagramConfig &config_; + std::function progress_; }; template std::unique_ptr generate(const common::compilation_database &db, const std::string &name, DiagramConfig &config, - const std::vector &translation_units, bool /*verbose*/ = false) + const std::vector &translation_units, bool /*verbose*/ = false, + std::function progress = {}) { LOG_INFO("Generating diagram {}", name); @@ -252,7 +261,8 @@ std::unique_ptr generate(const common::compilation_database &db, clang::tooling::ClangTool clang_tool(db, translation_units); auto action_factory = std::make_unique>(*diagram, config); + DiagramConfig, DiagramVisitor>>( + *diagram, config, std::move(progress)); auto res = clang_tool.run(action_factory.get()); @@ -275,9 +285,11 @@ void generate_diagram(const std::string &od, const std::string &name, void generate_diagrams(const std::vector &diagram_names, clanguml::config::config &config, const std::string &od, const common::compilation_database_ptr &db, int verbose, - unsigned int thread_count, + unsigned int thread_count, bool progress, const std::vector &generators, const std::map> &translation_units_map); +indicators::Color diagram_type_to_color(model::diagram_t diagram_type); + } // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/progress_indicator.cc b/src/common/generators/progress_indicator.cc new file mode 100644 index 00000000..d6dd4379 --- /dev/null +++ b/src/common/generators/progress_indicator.cc @@ -0,0 +1,121 @@ +/** + * src/common/generators/progress_indicator.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "progress_indicator.h" + +#include + +namespace clanguml::common::generators { + +progress_indicator::progress_indicator() +{ + progress_bars_.set_option(indicators::option::HideBarWhenComplete{false}); +} + +void progress_indicator::add_progress_bar( + const std::string &name, size_t max, indicators::Color color) +{ + auto postfix_text = max > 0 ? fmt::format("{}/{}", 0, max) : std::string{}; + + auto bar = std::make_shared( + indicators::option::BarWidth{34}, + indicators::option::ForegroundColor{color}, + indicators::option::ShowElapsedTime{true}, + indicators::option::Fill{"█"}, indicators::option::Lead{"█"}, + indicators::option::Remainder{"-"}, + indicators::option::PrefixText{ + fmt::format("{:<25}", util::abbreviate(name, 25))}, + indicators::option::PostfixText{postfix_text}); + + progress_bars_mutex_.lock(); + + progress_bars_.push_back(*bar); + bars_.push_back(bar); + auto bar_index = bars_.size() - 1; + progress_bar_index_.emplace( + name, progress_state{.index = bar_index, .progress = 0, .max = max}); + + progress_bars_mutex_.unlock(); +} + +void progress_indicator::increment(const std::string &name) +{ + progress_bars_mutex_.lock(); + + if (progress_bar_index_.count(name) == 0) { + progress_bars_mutex_.unlock(); + return; + } + + auto &p = progress_bar_index_.at(name); + auto &bar = progress_bars_[p.index]; + progress_bars_mutex_.unlock(); + + p.progress++; + bar.set_progress((p.progress * 95) / p.max); + bar.set_option(indicators::option::PostfixText{ + fmt::format("{}/{}", p.progress, p.max)}); +} + +void progress_indicator::stop() +{ + progress_bars_mutex_.lock(); + + for (auto &[name, p] : progress_bar_index_) { + progress_bars_[p.index].mark_as_completed(); + } + + progress_bars_mutex_.unlock(); +} + +void progress_indicator::complete(const std::string &name) +{ + progress_bars_mutex_.lock(); + + if (progress_bar_index_.count(name) == 0) { + progress_bars_mutex_.unlock(); + return; + } + + auto &p = progress_bar_index_.at(name); + auto &bar = progress_bars_[p.index]; + progress_bars_mutex_.unlock(); + + bar.set_progress(100); + + bar.set_option(indicators::option::PostfixText{ + fmt::format("{}/{} ✔", p.progress, p.max)}); + bar.set_option( + indicators::option::ForegroundColor{indicators::Color::green}); + bar.mark_as_completed(); +} + +void progress_indicator::fail(const std::string &name) +{ + progress_bars_mutex_.lock(); + auto &p = progress_bar_index_.at(name); + auto &bar = progress_bars_[p.index]; + progress_bars_mutex_.unlock(); + + bar.set_option(indicators::option::ForegroundColor{indicators::Color::red}); + bar.set_option(indicators::option::PostfixText{ + fmt::format("{}/{} ✗", p.progress, p.max)}); + bar.mark_as_completed(); +} + +} // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/progress_indicator.h b/src/common/generators/progress_indicator.h new file mode 100644 index 00000000..1294eeee --- /dev/null +++ b/src/common/generators/progress_indicator.h @@ -0,0 +1,55 @@ +/** + * src/common/generators/progress_indicator.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include +#include + +namespace clanguml::common::generators { + +class progress_indicator { +public: + struct progress_state { + size_t index; + size_t progress; + size_t max; + }; + + progress_indicator(); + + void add_progress_bar( + const std::string &name, size_t max, indicators::Color color); + + void increment(const std::string &name); + + void stop(); + + void complete(const std::string &name); + + void fail(const std::string &name); + +private: + indicators::DynamicProgress progress_bars_; + std::vector> bars_; + std::map progress_bar_index_; + std::mutex progress_bars_mutex_; +}; +} // namespace clanguml::common::generators diff --git a/src/main.cc b/src/main.cc index 45b9cf21..df2f38a0 100644 --- a/src/main.cc +++ b/src/main.cc @@ -79,7 +79,7 @@ int main(int argc, const char *argv[]) common::generators::generate_diagrams(cli.diagram_names, cli.config, cli.effective_output_directory, db, cli.verbose, cli.thread_count, - cli.generators, translation_units_map); + cli.progress, cli.generators, translation_units_map); } catch (common::compilation_database_error &e) { LOG_ERROR("Failed to load compilation database from {} due to: {}",