Tue, 21 Jan 2025 20:24:45 +0100
add repository scanner
0 | 1 | /* Copyright 2025 Mike Becker. All rights reserved. |
2 | * | |
3 | * Redistribution and use in source and binary forms, with or without | |
4 | * modification, are permitted provided that the following conditions are met: | |
5 | * | |
6 | * 1. Redistributions of source code must retain the above copyright | |
7 | * notice, this list of conditions and the following disclaimer. | |
8 | * | |
9 | * 2. Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * | |
13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
20 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
21 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
22 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | */ | |
24 | ||
1 | 25 | #include "settings.h" |
3 | 26 | #include "repositories.h" |
1 | 27 | |
28 | #include <cstdlib> | |
29 | #include <cstdio> | |
30 | #include <cstring> | |
31 | #include <cerrno> | |
32 | ||
33 | static void print_help() { | |
34 | fputs( | |
35 | "Usage: fallusmeter [OPTION]... [PATH]...\n\n" | |
36 | "Options:\n" | |
37 | " -h, --help Print this help message\n" | |
38 | " -d, --depth <num> The search depth (default: 1, max: 255)\n" | |
39 | " -n, --no-pull Do not pull the repositories\n" | |
40 | " -s, --separate Output a separate heat map for each repository\n" | |
41 | " -y, --year <year> The year for which to create the heat map\n" | |
42 | " --hg <path> Path to hg binary (default: /usr/bin/hg)\n" | |
43 | " --git <path> Path to git binary (default: /usr/bin/git)\n\n" | |
44 | "Scans all specified paths recursively for Mercurial and Git repositories and\n" | |
45 | "creates a commit heat map for the specified \033[1myear\033[22m or the current year.\n" | |
46 | "By default, the recursion \033[1mdepth\033[22m is one, meaning that this tool assumes that\n" | |
47 | "each \033[1mpath\033[22m is either a repository or contains repositories as subdirectories.\n" | |
48 | "You can change the \033[1mdepth\033[22m to support other directory structures.\n\n" | |
49 | "When you do not specify \033[1m--no-pull\033[22m, this tool will execute the pull command\n" | |
50 | "(and for hg the update command) before retrieving the commit log, assuming\n" | |
51 | "to be on the default branch with \033[4mno uncommitted changes\033[24m.\n\n" | |
3 | 52 | "Afterwards, this tool prints an HTML page to stdout. A separate heap map is\n" |
53 | "generated for each author showing commits across all repositories, unless the\n" | |
54 | "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n" | |
55 | "its own heat map.\n" | |
1 | 56 | , stderr); |
57 | } | |
58 | ||
3 | 59 | static bool chk_arg(const char *arg, const char *opt1, const char *opt2) { |
1 | 60 | return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0); |
61 | } | |
62 | ||
63 | template<typename T> | |
64 | static bool parse_unsigned(const char *str, T *result, unsigned long max) { | |
65 | char *endptr; | |
66 | errno = 0; | |
67 | unsigned long d = strtoul(str, &endptr, 10); | |
68 | if (*endptr != '\0' || errno == ERANGE) return true; | |
69 | if (d < max) { | |
70 | *result = d; | |
71 | return false; | |
72 | } else { | |
73 | return true; | |
74 | } | |
75 | } | |
76 | ||
77 | static int parse_args(fm::settings &settings, int argc, char *argv[]) { | |
78 | for (int i = 1; i < argc; i++) { | |
79 | if (chk_arg(argv[i], "-h", "--help")) { | |
80 | print_help(); | |
81 | return -1; | |
82 | } else if (chk_arg(argv[i], "-d", "--depth")) { | |
83 | if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 256)) { | |
84 | fputs("missing or invalid depth\n", stderr); | |
85 | return -1; | |
86 | } | |
87 | } else if (chk_arg(argv[i], "-y", "--year")) { | |
88 | if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.year, 9999)) { | |
89 | fputs("missing or invalid year\n", stderr); | |
90 | return -1; | |
91 | } | |
92 | } else if (chk_arg(argv[i], "-n", "--no-pull")) { | |
3 | 93 | settings.update_repos = false; |
1 | 94 | } else if (chk_arg(argv[i], "-s", "--separate")) { |
95 | settings.separate = true; | |
96 | } else if (chk_arg(argv[i], "--hg", nullptr)) { | |
97 | if (i + 1 < argc) { | |
98 | settings.hg.assign(argv[++i]); | |
99 | } else { | |
100 | fputs("--hg is expecting a path\n", stderr); | |
101 | return -1; | |
102 | } | |
103 | } else if (chk_arg(argv[i], "--git", nullptr)) { | |
104 | if (i + 1 < argc) { | |
105 | settings.git.assign(argv[++i]); | |
106 | } else { | |
107 | fputs("--git is expecting a path\n", stderr); | |
108 | return -1; | |
109 | } | |
110 | } else if (argv[i][0] == '-') { | |
111 | fprintf(stderr, "Unknown option: %s\n", argv[i]); | |
112 | return -1; | |
113 | } else { | |
114 | settings.paths.emplace_back(argv[i]); | |
115 | } | |
116 | } | |
117 | ||
118 | if (settings.paths.empty()) { | |
119 | settings.paths.emplace_back("./"); | |
120 | } | |
121 | ||
0 | 122 | return 0; |
123 | } | |
124 | ||
3 | 125 | static void print_html_header() { |
126 | puts("<html>\n\t<body>"); | |
127 | } | |
128 | ||
129 | static void print_html_footer() { | |
130 | puts("\t</body>\n</html>"); | |
131 | } | |
132 | ||
1 | 133 | int main(int argc, char *argv[]) { |
134 | fm::settings settings; | |
135 | ||
136 | if (parse_args(settings, argc, argv)) { | |
137 | return EXIT_FAILURE; | |
138 | } | |
139 | ||
3 | 140 | fm::repositories repos; |
141 | for (auto &&path: settings.paths) { | |
142 | repos.scan(path, settings.depth); | |
143 | } | |
144 | if (!settings.update_repos) { | |
145 | // TODO: update repositories | |
146 | } | |
147 | // TODO: calculate the heat maps | |
148 | ||
149 | print_html_header(); | |
150 | // TODO: output the heat maps here | |
151 | print_html_footer(); | |
152 | ||
1 | 153 | return EXIT_SUCCESS; |
154 | } |