src/main.cpp

changeset 52
e9edc3bd0301
parent 46
7e099403e5b0
equal deleted inserted replaced
51:49fdc2eb7cd4 52:e9edc3bd0301
46 "Options:\n" 46 "Options:\n"
47 " -a, --author <name> Only report this author\n" 47 " -a, --author <name> Only report this author\n"
48 " (repeat option to report multiple authors)\n" 48 " (repeat option to report multiple authors)\n"
49 " -A, --authormap <file> Apply an author mapping file\n" 49 " -A, --authormap <file> Apply an author mapping file\n"
50 " -d, --depth <num> The search depth (default: 1, max: 255)\n" 50 " -d, --depth <num> The search depth (default: 1, max: 255)\n"
51 " -f, --fragment Output as fragment\n" 51 " -f, --fragment [indent] Output as fragment\n"
52 " -h, --help Print this help message\n" 52 " -h, --help Print this help message\n"
53 " -p, --pull Try to pull the repositories\n" 53 " -p, --pull Try to pull the repositories\n"
54 " -s, --separate Output a separate heat map for each repository\n" 54 " -s, --separate Output a separate heat map for each repository\n"
55 " -V, --version Output the version of this program and exit\n" 55 " -V, --version Output the version of this program and exit\n"
56 " -y, --year <year> The year for which to create the heat map\n" 56 " -y, --year <year> The year for which to create the heat map\n"
84 "Finally, this tool prints an HTML page to stdout. A separate heap map is\n" 84 "Finally, this tool prints an HTML page to stdout. A separate heap map is\n"
85 "generated for each author showing commits across all repositories, unless the\n" 85 "generated for each author showing commits across all repositories, unless the\n"
86 "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n" 86 "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n"
87 "its own heat map. By using the \033[1m--fragment\033[22m option, the tool only outputs a\n" 87 "its own heat map. By using the \033[1m--fragment\033[22m option, the tool only outputs a\n"
88 "single HTML div container without any header or footer that can be embedded in\n" 88 "single HTML div container without any header or footer that can be embedded in\n"
89 "your custom web page.\n" 89 "your custom web page. You can optionally specify an indentation for that\n"
90 "container (default is 0 and maximum is 12).\n"
90 , stderr); 91 , stderr);
91 } 92 }
92 93
93 static bool chk_arg(const char *arg, const char *opt1, const char *opt2) { 94 static bool chk_arg(const char *arg, const char *opt1, const char *opt2) {
94 return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0); 95 return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0);
98 static bool parse_unsigned(const char *str, T *result, unsigned long max) { 99 static bool parse_unsigned(const char *str, T *result, unsigned long max) {
99 char *endptr; 100 char *endptr;
100 errno = 0; 101 errno = 0;
101 unsigned long d = strtoul(str, &endptr, 10); 102 unsigned long d = strtoul(str, &endptr, 10);
102 if (*endptr != '\0' || errno == ERANGE) return true; 103 if (*endptr != '\0' || errno == ERANGE) return true;
103 if (d < max) { 104 if (d <= max) {
104 *result = d; 105 *result = d;
105 return false; 106 return false;
106 } else { 107 } else {
108 errno = ERANGE;
107 return true; 109 return true;
108 } 110 }
109 } 111 }
110 112
111 static int parse_args(fm::settings &settings, int argc, char *argv[]) { 113 static int parse_args(fm::settings &settings, int argc, char *argv[]) {
112 for (int i = 1; i < argc; i++) { 114 for (int i = 1; i < argc; i++) {
113 if (chk_arg(argv[i], "-h", "--help")) { 115 if (chk_arg(argv[i], "-h", "--help")) {
114 print_help(); 116 print_help();
115 return 1; 117 return 1;
116 } else if (chk_arg(argv[i], "-d", "--depth")) { 118 } else if (chk_arg(argv[i], "-d", "--depth")) {
117 if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 256)) { 119 if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 255)) {
118 fputs("missing or invalid depth\n", stderr); 120 fputs("missing or invalid depth\n", stderr);
119 return -1; 121 return -1;
120 } 122 }
121 } else if (chk_arg(argv[i], "-y", "--year")) { 123 } else if (chk_arg(argv[i], "-y", "--year")) {
122 if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.year, 9999)) { 124 if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.year, 9999)) {
132 } 134 }
133 } else if (chk_arg(argv[i], "-p", "--pull")) { 135 } else if (chk_arg(argv[i], "-p", "--pull")) {
134 settings.update_repos = true; 136 settings.update_repos = true;
135 } else if (chk_arg(argv[i], "-f", "--fragment")) { 137 } else if (chk_arg(argv[i], "-f", "--fragment")) {
136 settings.fragment = true; 138 settings.fragment = true;
139 if (i + 1 < argc && !parse_unsigned(argv[i+1], &settings.fragment_indent, html::max_external_indentation)) {
140 i++;
141 } else if (errno == ERANGE) {
142 fputs("invalid fragment indentation\n", stderr);
143 return -1;
144 }
137 } else if (chk_arg(argv[i], "-s", "--separate")) { 145 } else if (chk_arg(argv[i], "-s", "--separate")) {
138 settings.separate = true; 146 settings.separate = true;
139 } else if (chk_arg(argv[i], "-A", "--authormap")) { 147 } else if (chk_arg(argv[i], "-A", "--authormap")) {
140 if (i + 1 < argc) { 148 if (i + 1 < argc) {
141 if (settings.parse_authormap(argv[++i])) { 149 if (settings.parse_authormap(argv[++i])) {
199 } 207 }
200 208
201 // scan for repos 209 // scan for repos
202 fm::repositories repos; 210 fm::repositories repos;
203 for (auto &&path: settings.paths) { 211 for (auto &&path: settings.paths) {
212 if (!fm::repositories::exists(path)) {
213 fprintf(stderr, "Path '%s' does not exist!\n", path.c_str());
214 return EXIT_FAILURE;
215 }
204 repos.scan(path, settings.depth); 216 repos.scan(path, settings.depth);
217 }
218
219 // check if we found something
220 if (repos.count() == 0) {
221 fprintf(stderr, "No repositories found!\n");
222 return EXIT_FAILURE;
205 } 223 }
206 224
207 // update repos, if not disabled 225 // update repos, if not disabled
208 if (settings.update_repos) { 226 if (settings.update_repos) {
209 for (auto &&repo : repos.list()) { 227 for (auto &&repo : repos.list()) {
260 } 278 }
261 heatmap.add(settings, proc.output()); 279 heatmap.add(settings, proc.output());
262 } 280 }
263 } 281 }
264 282
265 html::open(settings.fragment); 283 html::open(settings.fragment, settings.fragment_indent);
266 for (const auto &[repo, authors] : heatmap.data()) { 284 for (const auto &[repo, authors] : heatmap.data()) {
267 bool h1_rendered = false; 285 bool h1_rendered = false;
268 for (const auto &[author, entries] : authors) { 286 for (const auto &[author, entries] : authors) {
269 if (settings.exclude_author(author)) continue; 287 if (settings.exclude_author(author)) continue;
270 if (!h1_rendered) { 288 if (!h1_rendered) {

mercurial