Mon, 19 May 2025 16:05:58 +0200
add custom fragment indentation
src/html.cpp | file | annotate | diff | comparison | revisions | |
src/html.h | file | annotate | diff | comparison | revisions | |
src/main.cpp | file | annotate | diff | comparison | revisions | |
src/repositories.cpp | file | annotate | diff | comparison | revisions | |
src/repositories.h | file | annotate | diff | comparison | revisions | |
src/settings.h | file | annotate | diff | comparison | revisions |
--- a/src/html.cpp Mon May 19 15:34:30 2025 +0200 +++ b/src/html.cpp Mon May 19 16:05:58 2025 +0200 @@ -25,6 +25,7 @@ #include "html.h" #include <cstdio> +#include <cassert> using namespace std::chrono; @@ -32,9 +33,11 @@ static constexpr const char* weekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; static unsigned indentation; - static const char *tabs = " "; + static const char *tabs = " "; static void indent(int change = 0) { indentation += change; + assert(indentation >= 0); + assert(indentation <= max_indentation); fwrite(tabs, 4, indentation, stdout); } @@ -67,10 +70,11 @@ } } -void html::open(bool fragment) { +void html::open(bool fragment, unsigned char fragment_indent) { if (fragment) { + indent(fragment_indent); puts("<div class=\"heatmap-content\">"); - indentation = 1; + indentation++; } else { puts(R"(<!DOCTYPE html> <html> @@ -128,6 +132,7 @@ void html::close(bool fragment) { if (fragment) { + indent(-1); puts("</div>"); } else { puts(" </div>\n </body>\n</html>");
--- a/src/html.h Mon May 19 15:34:30 2025 +0200 +++ b/src/html.h Mon May 19 16:05:58 2025 +0200 @@ -31,9 +31,11 @@ namespace html { + static constexpr unsigned max_indentation = 16; + static constexpr unsigned max_external_indentation = max_indentation - 4; static constexpr unsigned columns = 53; - void open(bool fragment); + void open(bool fragment, unsigned char fragment_indent = 0); void close(bool fragment); void chart_begin(const std::string& repo, const std::string& author);
--- a/src/main.cpp Mon May 19 15:34:30 2025 +0200 +++ b/src/main.cpp Mon May 19 16:05:58 2025 +0200 @@ -48,7 +48,7 @@ " (repeat option to report multiple authors)\n" " -A, --authormap <file> Apply an author mapping file\n" " -d, --depth <num> The search depth (default: 1, max: 255)\n" - " -f, --fragment Output as fragment\n" + " -f, --fragment [indent] Output as fragment\n" " -h, --help Print this help message\n" " -p, --pull Try to pull the repositories\n" " -s, --separate Output a separate heat map for each repository\n" @@ -86,7 +86,8 @@ "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n" "its own heat map. By using the \033[1m--fragment\033[22m option, the tool only outputs a\n" "single HTML div container without any header or footer that can be embedded in\n" - "your custom web page.\n" + "your custom web page. You can optionally specify an indentation for that\n" + "container (default is 0 and maximum is 12).\n" , stderr); } @@ -100,10 +101,11 @@ errno = 0; unsigned long d = strtoul(str, &endptr, 10); if (*endptr != '\0' || errno == ERANGE) return true; - if (d < max) { + if (d <= max) { *result = d; return false; } else { + errno = ERANGE; return true; } } @@ -114,7 +116,7 @@ print_help(); return 1; } else if (chk_arg(argv[i], "-d", "--depth")) { - if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 256)) { + if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 255)) { fputs("missing or invalid depth\n", stderr); return -1; } @@ -134,6 +136,12 @@ settings.update_repos = true; } else if (chk_arg(argv[i], "-f", "--fragment")) { settings.fragment = true; + if (i + 1 < argc && !parse_unsigned(argv[i+1], &settings.fragment_indent, html::max_external_indentation)) { + i++; + } else if (errno == ERANGE) { + fputs("invalid fragment indentation\n", stderr); + return -1; + } } else if (chk_arg(argv[i], "-s", "--separate")) { settings.separate = true; } else if (chk_arg(argv[i], "-A", "--authormap")) { @@ -201,9 +209,19 @@ // scan for repos fm::repositories repos; for (auto &&path: settings.paths) { + if (!fm::repositories::exists(path)) { + fprintf(stderr, "Path '%s' does not exist!\n", path.c_str()); + return EXIT_FAILURE; + } repos.scan(path, settings.depth); } + // check if we found something + if (repos.count() == 0) { + fprintf(stderr, "No repositories found!\n"); + return EXIT_FAILURE; + } + // update repos, if not disabled if (settings.update_repos) { for (auto &&repo : repos.list()) { @@ -262,7 +280,7 @@ } } - html::open(settings.fragment); + html::open(settings.fragment, settings.fragment_indent); for (const auto &[repo, authors] : heatmap.data()) { bool h1_rendered = false; for (const auto &[author, entries] : authors) {
--- a/src/repositories.cpp Mon May 19 15:34:30 2025 +0200 +++ b/src/repositories.cpp Mon May 19 16:05:58 2025 +0200 @@ -36,6 +36,10 @@ type(type) { } +bool repositories::exists(const std::string &path) { + return fs::is_directory(path); +} + void repositories::scan(std::string path, unsigned depth) { // check the base path {
--- a/src/repositories.h Mon May 19 15:34:30 2025 +0200 +++ b/src/repositories.h Mon May 19 16:05:58 2025 +0200 @@ -25,6 +25,7 @@ #ifndef REPOSITORIES_H #define REPOSITORIES_H +#include <filesystem> #include <string> #include <vector> @@ -45,10 +46,14 @@ class repositories final { std::vector<repository> m_repositories; public: + [[nodiscard]] static bool exists(const std::string &path); void scan(std::string path, unsigned depth); [[nodiscard]] const std::vector<repository>& list() const { return m_repositories; } + [[nodiscard]] size_t count() const { + return m_repositories.size(); + } }; } // fm
--- a/src/settings.h Mon May 19 15:34:30 2025 +0200 +++ b/src/settings.h Mon May 19 16:05:58 2025 +0200 @@ -45,6 +45,7 @@ bool update_repos = false; bool separate = false; bool fragment = false; + unsigned char fragment_indent = 0; unsigned short year = settings_current_year; int parse_authormap(const std::string& path);