src/main.cpp

changeset 81
1ff88eb9555c
parent 79
cd8ad39dda76
child 82
f3242673777c
equal deleted inserted replaced
80:fa5f493adfb5 81:1ff88eb9555c
43 43
44 static void print_help() { 44 static void print_help() {
45 fputs( 45 fputs(
46 "Usage: repoheat [OPTION]... [PATH]...\n\n" 46 "Usage: repoheat [OPTION]... [PATH]...\n\n"
47 "Options:\n" 47 "Options:\n"
48 " -a, --author <name> Only report this author\n" 48 " -a, --author <name> Only report this author\n"
49 " (repeat option to report multiple authors)\n" 49 " (repeat option to report multiple authors)\n"
50 " -A, --authormap <file> Apply an author mapping file\n" 50 " -A, --authormap <file> Apply an author mapping file\n"
51 " --csv Output the gathered data as CSV to stdout\n" 51 " --csv Output the gathered data as CSV to stdout\n"
52 " --csv-path <file> Write CSV data to file instead of stdout\n" 52 " --csv-output <file> Write CSV data to file instead of stdout\n"
53 " -d, --depth <num> The search depth (default: 1, max: 255)\n" 53 " -d, --depth <num> The search depth (default: 1, max: 255)\n"
54 " -f, --fragment [indent] Output as fragment (HTML only)\n" 54 " -f, --fragment [indent] Output as fragment (HTML only)\n"
55 " -h, --help Print this help message\n" 55 " -h, --help Print this help message\n"
56 " -p, --pull Try to pull the repositories\n" 56 " -o, --output <file> Write output to file instead of stdout\n"
57 " -s, --separate Output a separate heat map for each repository\n" 57 " -p, --pull Try to pull the repositories\n"
58 " --styles-and-script Output the default CSS and Javascript and quit\n" 58 " -s, --separate Output a separate heat map for each repository\n"
59 " -V, --version Output the version of this program and exit\n" 59 " -S, --separate-output <file> Write results for -s to file instead of stdout\n"
60 " -y, --year <year> The year for which to create the heat map\n" 60 " --styles-and-script Output the default CSS and Javascript and quit\n"
61 " --hg <path> Path to hg binary (default: /usr/bin/hg)\n" 61 " -V, --version Output the version of this program and exit\n"
62 " --git <path> Path to git binary (default: /usr/bin/git)\n\n" 62 " -y, --year <year> The year for which to create the heat map\n"
63 " --hg <path> Path to hg binary (default: /usr/bin/hg)\n"
64 " --git <path> Path to git binary (default: /usr/bin/git)\n\n"
63 "Scans all specified paths recursively for Mercurial and Git repositories and\n" 65 "Scans all specified paths recursively for Mercurial and Git repositories and\n"
64 "creates a commit heat map for the specified \033[1myear\033[22m or the current year.\n" 66 "creates a commit heat map for the specified \033[1myear\033[22m or the current year.\n"
65 "By default, the recursion \033[1mdepth\033[22m is one, meaning that this tool assumes that\n" 67 "By default, the recursion \033[1mdepth\033[22m is one, meaning that this tool assumes that\n"
66 "each \033[1mpath\033[22m is either a repository or contains repositories as subdirectories.\n" 68 "each \033[1mpath\033[22m is either a repository or contains repositories as subdirectories.\n"
67 "You can change the \033[1mdepth\033[22m to support other directory structures.\n\n" 69 "You can change the \033[1mdepth\033[22m to support other directory structures.\n\n"
86 "should \033[4monly\033[24m be used on the left-hand side. When you use the \033[1m--author\033[22m option at\n" 88 "should \033[4monly\033[24m be used on the left-hand side. When you use the \033[1m--author\033[22m option at\n"
87 "the same time, you only need to specify the new author names.\n\n" 89 "the same time, you only need to specify the new author names.\n\n"
88 "Finally, this tool prints an HTML page to stdout. A separate heap map is\n" 90 "Finally, this tool prints an HTML page to stdout. A separate heap map is\n"
89 "generated for each author showing commits across all repositories, unless the\n" 91 "generated for each author showing commits across all repositories, unless the\n"
90 "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n" 92 "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n"
91 "its own heat map. By using the \033[1m--fragment\033[22m option, the tool only outputs a\n" 93 "its own heat map.\n"
92 "single HTML div container without any header or footer that can be embedded in\n" 94 "If you use \033[1m--separate-output\033[22m you can specify a file instead, and both the\n"
93 "your custom web page. You can optionally specify an indentation for that\n" 95 "aggregated and separated heat maps are written. The aggregated output is still\n"
94 "container (default is 0 and maximum is 12).\n" 96 "written to stdout unless you specify a file with \033[1m--output\033[22m.\n"
97 "By using the \033[1m--fragment\033[22m option, the tool only outputs a single HTML div\n"
98 "container without any header or footer that can be embedded in your custom web\n"
99 "page. You can optionally specify an indentation for that container (default is\n"
100 "0 and maximum is 12).\n"
95 "When you want to combine this with the default style and scripts, you can use\n" 101 "When you want to combine this with the default style and scripts, you can use\n"
96 "the \033[1m--styles-and-script\033[22m option print the defaults to stdout and redirect them\n" 102 "the \033[1m--styles-and-script\033[22m option print the defaults to stdout and redirect them\n"
97 "into a file when you are composing your custom HTML page.\n\n" 103 "into a file when you are composing your custom HTML page.\n\n"
98 "In case you want to work with the raw data, you can advise the tool to output\n" 104 "In case you want to work with the raw data, you can advise the tool to output\n"
99 "the data in CSV format with the \033[1m--csv\033[22m option. The CSV data will be written to\n" 105 "the data in CSV format with the \033[1m--csv\033[22m option. The CSV data will be written to\n"
100 "stdout instead of the HTML unless you specify a different file with \033[1m--csv-path\033[22m.\n" 106 "stdout instead of the HTML. You can use \033[1m--csv-output\033[22m to specify a file, instead.\n"
101 , stderr); 107 , stderr);
102 } 108 }
103 109
104 static bool chk_arg(const char *arg, const char *opt1, const char *opt2) { 110 static bool chk_arg(const char *arg, const char *opt1, const char *opt2) {
105 return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0); 111 return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0);
142 fputs("missing author name\n", stderr); 148 fputs("missing author name\n", stderr);
143 return -1; 149 return -1;
144 } 150 }
145 } else if (chk_arg(argv[i], "--csv", nullptr)) { 151 } else if (chk_arg(argv[i], "--csv", nullptr)) {
146 settings.csv = true; 152 settings.csv = true;
147 } else if (chk_arg(argv[i], "--csv-path", nullptr)) { 153 } else if (chk_arg(argv[i], "--csv-output", nullptr)) {
148 settings.csv = true; 154 if (i + 1 < argc) {
149 if (i + 1 < argc) { 155 settings.csv = true;
150 settings.csv_path.assign(argv[++i]); 156 settings.csv_path.assign(argv[++i]);
151 } else { 157 } else {
152 fputs("missing csv path\n", stderr); 158 fputs("missing csv path\n", stderr);
159 return -1;
160 }
161 } else if (chk_arg(argv[i], "-o", "--output")) {
162 if (i + 1 < argc) {
163 settings.out_path.assign(argv[++i]);
164 } else {
165 fputs("missing output path\n", stderr);
153 return -1; 166 return -1;
154 } 167 }
155 } else if (chk_arg(argv[i], "-p", "--pull")) { 168 } else if (chk_arg(argv[i], "-p", "--pull")) {
156 settings.update_repos = true; 169 settings.update_repos = true;
157 } else if (chk_arg(argv[i], "-f", "--fragment")) { 170 } else if (chk_arg(argv[i], "-f", "--fragment")) {
162 fputs("invalid fragment indentation\n", stderr); 175 fputs("invalid fragment indentation\n", stderr);
163 return -1; 176 return -1;
164 } 177 }
165 } else if (chk_arg(argv[i], "-s", "--separate")) { 178 } else if (chk_arg(argv[i], "-s", "--separate")) {
166 settings.separate = true; 179 settings.separate = true;
180 } else if (chk_arg(argv[i], "-S", "--separate-output")) {
181 if (i + 1 < argc) {
182 settings.separate = true;
183 settings.separate_path.assign(argv[++i]);
184 } else {
185 fputs("missing separate output path\n", stderr);
186 return -1;
187 }
167 } else if (chk_arg(argv[i], "-A", "--authormap")) { 188 } else if (chk_arg(argv[i], "-A", "--authormap")) {
168 if (i + 1 < argc) { 189 if (i + 1 < argc) {
169 if (settings.parse_authormap(argv[++i])) { 190 if (settings.parse_authormap(argv[++i])) {
170 fputs("parsing authormap failed\n", stderr); 191 fputs("parsing authormap failed\n", stderr);
171 return -1; 192 return -1;
214 235
215 static void generate_html( 236 static void generate_html(
216 const fm::settings &settings, 237 const fm::settings &settings,
217 year report_year, year_month_day report_begin, year_month_day report_end, 238 year report_year, year_month_day report_begin, year_month_day report_end,
218 const fm::heatmap &heatmap) { 239 const fm::heatmap &heatmap) {
219 240 const auto &heatmap_data = settings.separate ? heatmap.separated() : heatmap.aggregated();
220 html::open(settings.fragment, settings.fragment_indent); 241 html::open(settings.fragment, settings.fragment_indent);
221 for (const auto &[repo, authors] : heatmap.data()) { 242 for (const auto &[repo, authors] : heatmap_data) {
222 bool h1_rendered = false; 243 bool h1_rendered = false;
223 for (const auto &[author, entries] : authors) { 244 for (const auto &[author, entries] : authors) {
224 if (settings.exclude_author(author)) continue; 245 if (settings.exclude_author(author)) continue;
225 if (!h1_rendered) { 246 if (!h1_rendered) {
226 html::heading_repo(repo); 247 html::heading_repo(repo);
315 result += '"'; 336 result += '"';
316 return result; 337 return result;
317 }; 338 };
318 339
319 fprintf(out, "repository,author,date,hash,summary,is_tag\n"); 340 fprintf(out, "repository,author,date,hash,summary,is_tag\n");
320 for (const auto &authors: heatmap.data() | std::views::values) { 341 for (const auto &authors: heatmap.aggregated() | std::views::values) {
321 for (const auto &[author, entries]: authors) { 342 for (const auto &[author, entries]: authors) {
322 for (const auto &[date, commits]: entries) { 343 for (const auto &[date, commits]: entries) {
323 for (const auto &[repo, infos] : commits.summaries) { 344 for (const auto &[repo, infos] : commits.summaries) {
324 for (const auto &info : infos) { 345 for (const auto &info : infos) {
325 fprintf(out, "%s,%s,%d-%02u-%02u,%s,%s,false\n", 346 fprintf(out, "%s,%s,%d-%02u-%02u,%s,%s,false\n",
360 return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS; 381 return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
361 } 382 }
362 383
363 // check special options 384 // check special options
364 if (settings.styles_and_script) { 385 if (settings.styles_and_script) {
386 // note: no need to set output path - not supported for styles and scripts
365 html::styles_and_script(); 387 html::styles_and_script();
366 return 0; 388 return 0;
367 } 389 }
368 390
369 // check hg and git 391 // check hg and git
423 }; 445 };
424 year_month_day report_begin{report_year, January, 1d}; 446 year_month_day report_begin{report_year, January, 1d};
425 year_month_day report_end{report_year, December, 31d}; 447 year_month_day report_end{report_year, December, 31d};
426 448
427 // read the commit logs 449 // read the commit logs
428 fm::heatmap heatmap{settings.separate}; 450 fm::heatmap heatmap;
429 for (auto &&repo : repos.list()) { 451 for (auto &&repo : repos.list()) {
430 heatmap.set_repo(repo.name); 452 heatmap.set_repo(repo.name);
431 proc.chdir(repo.path); 453 proc.chdir(repo.path);
432 if (repo.type == fm::HG) { 454 if (repo.type == fm::HG) {
433 proc.setbin(settings.hg); 455 proc.setbin(settings.hg);
452 } 474 }
453 heatmap.add(settings, proc.output()); 475 heatmap.add(settings, proc.output());
454 } 476 }
455 } 477 }
456 478
457 bool html = true; 479 const bool write_csv = settings.csv;
458 if (settings.csv) { 480 const bool write_separate = settings.separate;
481 const bool write_default = !settings.out_path.empty() || (
482 !(write_csv && settings.csv_path.empty())
483 && !(write_separate && settings.separate_path.empty())
484 );
485
486 if (write_csv) {
459 if (generate_csv(settings, heatmap)) { 487 if (generate_csv(settings, heatmap)) {
488 perror("Cannot open file for writing");
460 return EXIT_FAILURE; 489 return EXIT_FAILURE;
461 } 490 }
462 html = !settings.csv_path.empty(); 491 }
463 } 492 if (write_separate) {
464 if (html) { 493 settings.separate = true;
494 if (html::set_output(settings.separate_path)) {
495 perror("Cannot open file for writing");
496 return EXIT_FAILURE;
497 }
465 generate_html(settings, report_year, report_begin, report_end, heatmap); 498 generate_html(settings, report_year, report_begin, report_end, heatmap);
466 } 499 }
500 if (write_default) {
501 settings.separate = false;
502 if (html::set_output(settings.out_path)) {
503 perror("Cannot open file for writing");
504 return EXIT_FAILURE;
505 }
506 generate_html(settings, report_year, report_begin, report_end, heatmap);
507 }
467 508
468 return EXIT_SUCCESS; 509 return EXIT_SUCCESS;
469 } 510 }

mercurial