From c0f2f352c89def4ed984402251739495090906f4 Mon Sep 17 00:00:00 2001 From: ryouze <98982999+ryouze@users.noreply.github.com> Date: Tue, 24 Sep 2024 03:24:04 +0200 Subject: [PATCH] Use structured bindings to unpack line, use std::filesystem::path instead of strings for iflepaths, fully remove pathmaster dependency, simplify error handling. --- src/app.cpp | 7 +++++-- src/core/args.cpp | 4 ++-- src/core/args.hpp | 16 ++++++++++++---- src/core/io.cpp | 36 ++++++++++++++++++++++++++++-------- src/core/io.hpp | 25 ++++++++++++------------- src/core/string.cpp | 19 +++++++++++++++++++ src/core/string.hpp | 12 +++++++++++- src/main.cpp | 23 +++-------------------- src/modules/analyze.cpp | 15 ++++++++------- src/modules/analyze.hpp | 9 +++++---- tests/test_all.cpp | 34 +++++++++++++++------------------- 11 files changed, 120 insertions(+), 80 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 5d8895a..08c7f08 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7,6 +7,7 @@ #include "app.hpp" #include "core/args.hpp" +#include "core/string.hpp" #include "modules/analyze.hpp" void app::run(const int argc, @@ -15,14 +16,16 @@ void app::run(const int argc, // Process command-line arguments (this might throw an ArgParseError) const core::args::Args args(argc, argv); - fmt::print("Analyzing {} files: [{}]\n\n", args.filepaths.size(), fmt::join(args.filepaths, ", ")); + fmt::print("Analyzing {} files: [{}]\n\n", + args.filepaths.size(), + fmt::join(core::string::paths_to_strings(args.filepaths), ", ")); // fmt::print("Enabled: bare={}, unused={}, unlisted={}\n\n", args.enable.bare, args.enable.unused, args.enable.unlisted); fmt::print("--------------------------------------------------------------------------------\n\n"); // Process each filepath for (const auto &path : args.filepaths) { - fmt::print("##- {} -##\n\n", path); + fmt::print("##- {} -##\n\n", path.string()); const modules::analyze::CodeParser parser(path); // Get references to the parser's extracted data / results diff --git a/src/core/args.cpp b/src/core/args.cpp index 83e8a9d..62cc96c 100644 --- a/src/core/args.cpp +++ b/src/core/args.cpp @@ -80,7 +80,7 @@ core::args::Args::Args(const int argc, } // Append only if the file extension matches any of the C++ file types if (file_extensions.find(entry.path().extension().string()) != file_extensions.cend()) { - this->filepaths.emplace_back(entry.path().string()); + this->filepaths.emplace_back(entry.path()); } } } @@ -88,7 +88,7 @@ core::args::Args::Args(const int argc, else { // Append only if the file extension matches any of the C++ file types if (file_extensions.find(resolved_filepath.extension().string()) != file_extensions.cend()) { - this->filepaths.emplace_back(resolved_filepath.string()); + this->filepaths.emplace_back(resolved_filepath); } } } diff --git a/src/core/args.hpp b/src/core/args.hpp index 4231c16..47bac17 100644 --- a/src/core/args.hpp +++ b/src/core/args.hpp @@ -6,17 +6,25 @@ #pragma once -#include // for std::runtime_error -#include // for std::string -#include // for std::vector +#include // for std::filesystem +#include // for std::runtime_error +#include // for std::string +#include // for std::vector namespace core::args { /** * @brief Exceptions raised by command-line argument parser when help or version is requested. The requested message is returned. + * + * This class extends "std::runtime_error". */ class ArgsError : public std::runtime_error { public: + /** + * @brief Construct a new ArgsError object. + * + * @param message Error message that describes the cause of the exception (e.g., "Failed to load data"). + */ explicit ArgsError(const std::string &message) : std::runtime_error(message) {} }; @@ -68,7 +76,7 @@ class Args final { /** * @brief Vector of file paths. */ - std::vector filepaths; + std::vector filepaths; /** * @brief Struct of enabled features (e.g., "Enable(false, true, true)"). diff --git a/src/core/io.cpp b/src/core/io.cpp index 25effe1..2aa38e3 100644 --- a/src/core/io.cpp +++ b/src/core/io.cpp @@ -2,18 +2,38 @@ * @file io.cpp */ -#include // for std::size_t -#include // for std::exception -#include // for std::ifstream -#include // for std::runtime_error -#include // for std::string, std::getline -#include // for std::vector +#include // for std::size_t +#include // for std::exception +#include // for std::filesystem +#include // for std::ifstream +#include // for std::runtime_error +#include // for std::string, std::getline +#include // for std::vector + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include // for setlocale, LC_ALL +#include // for WideCharToMultiByte, GetLastError, CP_UTF8, SetConsoleCP, SetConsoleOutputCP +#endif #include #include "io.hpp" -std::vector core::io::read_lines(const std::string &input_path, +void core::io::setup_utf8_console() +{ +#if defined(_WIN32) + if (!SetConsoleCP(CP_UTF8) || !SetConsoleOutputCP(CP_UTF8)) { + throw std::runtime_error(fmt::format("Failed to set UTF-8 code page: {}", GetLastError())); + } + + if (!setlocale(LC_ALL, ".UTF8")) { + throw std::runtime_error("Failed to set UTF-8 locale"); + } +#endif +} + +std::vector core::io::read_lines(const std::filesystem::path &input_path, const std::size_t initial_capacity) { try { @@ -44,6 +64,6 @@ std::vector core::io::read_lines(const std::string &input_path, return lines; } catch (const std::exception &e) { - throw IOError(fmt::format("Error loading file '{}': {}", input_path, e.what())); + throw std::runtime_error(fmt::format("Error loading file '{}': {}", input_path.string(), e.what())); } } diff --git a/src/core/io.hpp b/src/core/io.hpp index 0127358..37b0b07 100644 --- a/src/core/io.hpp +++ b/src/core/io.hpp @@ -1,26 +1,25 @@ /** * @file io.hpp * - * @brief Load lines of text from disk. + * @brief Input/output functions. */ #pragma once -#include // for std::size_t -#include // for std::runtime_error -#include // for std::string -#include // for std::vector +#include // for std::size_t +#include // for std::filesystem +#include // for std::runtime_error +#include // for std::string +#include // for std::vector namespace core::io { /** - * @brief Base class for exceptions raised during I/O operations. + * @brief Setup UTF-8 input/output on Windows. Do nothing on other platforms. + * + * @throws std::runtime_error If failed to enable UTF-8 encoding on Windows. */ -class IOError : public std::runtime_error { - public: - explicit IOError(const std::string &message) - : std::runtime_error(message) {} -}; +void setup_utf8_console(); /** * @brief Struct that represents a single line of text. @@ -58,9 +57,9 @@ struct Line { * * @return Vector of Line structs (e.g., {Line(1, "Hello world!"), Line(2, "How are you?")}). * - * @throws core::io::IOError If the file cannot be opened for reading or if any other I/O error occurs. + * @throws std::runtime_error If the file cannot be opened for reading or if any other I/O error occurs. */ -[[nodiscard]] std::vector read_lines(const std::string &input_path, +[[nodiscard]] std::vector read_lines(const std::filesystem::path &input_path, const std::size_t initial_capacity = 100); } // namespace core::io diff --git a/src/core/string.cpp b/src/core/string.cpp index 5db8654..cfe71cd 100644 --- a/src/core/string.cpp +++ b/src/core/string.cpp @@ -5,12 +5,31 @@ #include // for std::transform, std::find_if_not #include // for std::tolower, std::isspace #include // for std::size_t +#include // for std::filesystem #include // for std::ostringstream #include // for std::string #include // for std::unordered_map +#include // for std::vector #include "string.hpp" +std::vector core::string::paths_to_strings(const std::vector &paths) +{ + // Get the length of the original vector + const std::size_t paths_len = paths.size(); + + // Convert vector to vector of strings + std::vector strings; + strings.reserve(paths_len); + for (const auto &path : paths) { + strings.emplace_back(path.string()); + } + + // Return shrunk vector (RVO) + strings.shrink_to_fit(); + return strings; +} + std::string core::string::to_lower(std::string str) { std::transform(str.cbegin(), str.cend(), str.begin(), diff --git a/src/core/string.hpp b/src/core/string.hpp index b69f505..bc90d15 100644 --- a/src/core/string.hpp +++ b/src/core/string.hpp @@ -6,10 +6,20 @@ #pragma once -#include // for std::string +#include // for std::filesystem +#include // for std::string +#include // for std::vector namespace core::string { +/** + * @brief Convert a vector of filesystem paths to a vector of strings. + * + * @param paths Vector of filesystem paths (e.g., {"/path/to/file1", "/path/to/file2"}). + * @return Vector of strings (e.g., {"/path/to/file1", "/path/to/file2"}). + */ +[[nodiscard]] std::vector paths_to_strings(const std::vector &v); + /** * @brief Convert a string to lowercase. * diff --git a/src/main.cpp b/src/main.cpp index 5764013..d0dbdef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,14 +7,9 @@ #include -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#include // for setlocale, LC_ALL -#include // for WideCharToMultiByte, GetLastError, CP_UTF8, SetConsoleCP, SetConsoleOutputCP -#endif - #include "app.hpp" #include "core/args.hpp" +#include "core/io.hpp" /** * @brief Entry-point of the application. @@ -28,20 +23,8 @@ int main(int argc, char **argv) { try { - -#if defined(_WIN32) - if (!SetConsoleCP(CP_UTF8) || !SetConsoleOutputCP(CP_UTF8)) { - if (throw_on_error) { - throw PathmasterError("Failed to set UTF-8 code page on Windows: " + std::to_string(GetLastError())); - } - } - - if (!setlocale(LC_ALL, ".UTF8")) { - if (throw_on_error) { - throw PathmasterError("Failed to set UTF-8 locale on Windows"); - } - } -#endif + // Setup UTF-8 input/output on Windows + core::io::setup_utf8_console(); // Run the application app::run(argc, argv); diff --git a/src/modules/analyze.cpp b/src/modules/analyze.cpp index ea94f63..d85413a 100644 --- a/src/modules/analyze.cpp +++ b/src/modules/analyze.cpp @@ -3,6 +3,7 @@ */ #include // for std::transform, std::find +#include // for std::filesystem #include // for std::back_inserter #include // for std::regex, std::smatch, std::sregex_iterator, std::regex_search #include // for std::string @@ -39,7 +40,7 @@ namespace { } // namespace -modules::analyze::CodeParser::CodeParser(const std::string &input_path) +modules::analyze::CodeParser::CodeParser(const std::filesystem::path &input_path) { // Define the regex for an include directive (e.g., "#include ") and a function call (e.g., "std::cout") static const std::regex include_directive_regex(R"(^\s*#include\s*<\S+>)", std::regex::optimize); @@ -50,15 +51,15 @@ modules::analyze::CodeParser::CodeParser(const std::string &input_path) std::vector temp_functions; // Load the file from disk and iterate over each line - for (const core::io::Line ¤t_line : core::io::read_lines(input_path)) { + for (const auto &[line_number, line_text] : core::io::read_lines(input_path)) { // Skip if the raw line is empty (avoid stripping and lowercasing the line) - if (current_line.text.empty()) { + if (line_text.empty()) { continue; } // Strip leading and trailing whitespace from line, turn line lowercase - std::string processed_line = core::string::to_lower(core::string::strip_whitespace(current_line.text)); + std::string processed_line = core::string::to_lower(core::string::strip_whitespace(line_text)); // Skip if the processed line is a comment if (begins_with_comment(processed_line)) { @@ -88,17 +89,17 @@ modules::analyze::CodeParser::CodeParser(const std::string &input_path) // Categorize the result into containers if (line_contains_include && !function_calls.empty()) { // E.g., "#include // for std::cout, std::cerr" - temp_includes_with_functions.emplace_back(current_line.number, current_line.text, function_calls); + temp_includes_with_functions.emplace_back(line_number, line_text, function_calls); } else if (line_contains_include) { // E.g., "#include "" - this->bare_includes_.emplace_back(current_line.number, current_line.text, include_directive); + this->bare_includes_.emplace_back(line_number, line_text, include_directive); } else if (!function_calls.empty()) { // E.g., "std::string" for (const auto &function_name : function_calls) { // Do not create C++ reference links, we do this later - temp_functions.emplace_back(current_line.number, current_line.text, function_name, ""); + temp_functions.emplace_back(line_number, line_text, function_name, ""); } } // Otherwise, the line is ignored, e.g., '#include "my_header.hpp"' diff --git a/src/modules/analyze.hpp b/src/modules/analyze.hpp index cad404d..cdc994e 100644 --- a/src/modules/analyze.hpp +++ b/src/modules/analyze.hpp @@ -6,9 +6,10 @@ #pragma once -#include // for std::size_t -#include // for std::string -#include // for std::vector +#include // for std::size_t +#include // for std::filesystem +#include // for std::string +#include // for std::vector #include "core/io.hpp" @@ -132,7 +133,7 @@ class CodeParser final { * * @param input_path Path to the C++ file that shall be parsed (e.g., "~/main.cpp"). */ - explicit CodeParser(const std::string &input_path); + explicit CodeParser(const std::filesystem::path &input_path); /** * @brief Get a vector of bare include directives, i.e., without any standard functions listed after them as comments. diff --git a/tests/test_all.cpp b/tests/test_all.cpp index 6184517..a97505a 100644 --- a/tests/test_all.cpp +++ b/tests/test_all.cpp @@ -17,12 +17,10 @@ #include #include -#if defined(_WIN32) -#include -#endif - #include "app.hpp" #include "core/args.hpp" +#include "core/io.hpp" +#include "core/string.hpp" #include "modules/analyze.hpp" #include "examples.hpp" @@ -56,12 +54,11 @@ namespace test_app { * * @return EXIT_SUCCESS if the application ran successfully, EXIT_FAILURE otherwise. */ -int main(int argc, char **argv) +int main(int argc, + char **argv) { -#if defined(_WIN32) - // Enable UTF-8 output on Windows - pathmaster::setup_utf8_console_output(); -#endif + // Setup UTF-8 input/output on Windows + core::io::setup_utf8_console(); // Define the formatted help message const std::string help_message = fmt::format( @@ -78,7 +75,7 @@ int main(int argc, char **argv) fmt::print("{}\n", help_message); return EXIT_FAILURE; } - + // Otherwise, define argument to function mapping const std::unordered_map> tests = { {"test_args::none", test_args::none}, @@ -170,19 +167,18 @@ int test_args::paths() } // Store the string representation of the directory path - const std::string dir_path_str = temp_dir.get().string(); - const char *fake_argv[] = {TEST_EXECUTABLE_NAME, dir_path_str.c_str()}; + const char *fake_argv[] = {TEST_EXECUTABLE_NAME, temp_dir.get().c_str()}; const core::args::Args args(2, const_cast(fake_argv)); // Compare the filepaths found by Args if (args.filepaths.size() != 2) { - fmt::print(stderr, "Filepaths test failed: expected 2, got {}: {}\n", args.filepaths.size(), fmt::join(args.filepaths, ", ")); + fmt::print(stderr, "Filepaths test failed: expected 2, got {}: {}\n", args.filepaths.size(), fmt::join(core::string::paths_to_strings(args.filepaths), ", ")); return EXIT_FAILURE; } // Iterate, because the order is not guaranteed for (const auto &path : args.filepaths) { if (path != temp_file1.string() && path != temp_file2.string()) { - fmt::print(stderr, "Filepaths test failed: expected {}, got {}\n", temp_file1.string(), path); + fmt::print(stderr, "Filepaths test failed: expected {}, got {}\n", temp_file1.string(), path.string()); return EXIT_FAILURE; } } @@ -223,7 +219,7 @@ int test_analyze::analyze_badly_formatted() }; // Analyze the temporary file - modules::analyze::CodeParser parser(temp_file.string()); + modules::analyze::CodeParser parser(temp_file); // Compare bare includes if (!helpers::compare_and_print_bare_includes(parser.get_bare_includes(), expected_bare_includes)) { @@ -268,7 +264,7 @@ int test_analyze::analyze_no_issues() const std::vector expected_unlisted_functions = {}; // Analyze the temporary file - modules::analyze::CodeParser parser(temp_file.string()); + modules::analyze::CodeParser parser(temp_file); // Compare bare includes if (!helpers::compare_and_print_bare_includes(parser.get_bare_includes(), expected_bare_includes)) { @@ -316,7 +312,7 @@ int test_analyze::analyze_bare() const std::vector expected_unlisted_functions = {}; // Analyze the temporary file - modules::analyze::CodeParser parser(temp_file.string()); + modules::analyze::CodeParser parser(temp_file); // Compare bare includes if (!helpers::compare_and_print_bare_includes(parser.get_bare_includes(), expected_bare_includes)) { @@ -366,7 +362,7 @@ int test_analyze::analyze_unused() const std::vector expected_unlisted_functions = {}; // Analyze the temporary file - modules::analyze::CodeParser parser(temp_file.string()); + modules::analyze::CodeParser parser(temp_file); // Compare bare includes if (!helpers::compare_and_print_bare_includes(parser.get_bare_includes(), expected_bare_includes)) { @@ -414,7 +410,7 @@ int test_analyze::analyze_unlisted() }; // Analyze the temporary file - modules::analyze::CodeParser parser(temp_file.string()); + modules::analyze::CodeParser parser(temp_file); // Compare bare includes if (!helpers::compare_and_print_bare_includes(parser.get_bare_includes(), expected_bare_includes)) {