Fri, 03 Jun 2022 20:05:15 +0200
new feature: count non-whitespace characters
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 * Copyright 2018 Mike Becker. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
27 #include "cline.h"
28 #include "scanner.h"
29 #include "settings.h"
30 #include "arguments.h"
31 #include "regex_parser.h"
33 void printHelpText() {
34 printf(
35 "\nUsage:"
36 "\n cline [Options] [Directories...]"
37 "\n\nCounts the line terminator characters (\\n) within all"
38 " files in the specified\ndirectories."
39 "\n\nOptions:"
40 "\n -b <level> - binary file heuristics level (default medium)"
41 "\n One of: ignore low medium high"
42 "\n -c - Count non-whitespace characters instead of lines"
43 "\n -E <pattern> - Excludes any line matching the <pattern>"
44 "\n -e <start> <end> - Excludes lines between <start> and <end>"
45 "\n You may use these options multiple times"
46 "\n -h, --help - this help text"
47 "\n -i - print out individual sums per file extension"
48 "\n (cannot be used together with -V)"
49 "\n -m - print information about matching files only"
50 "\n -s <suffixes> - only count files with these suffixes (separated"
51 "\n by commas)"
52 "\n -S <suffixes> - count any file except those with these suffixes"
53 "\n (separated by commas)"
54 "\n -r, -R - includes subdirectories"
55 "\n -v, --version - print out version information"
56 "\n -V - turn verbose output off, print the result only"
57 "\n\nShortcuts:"
58 "\n --exclude-cstyle-comments : -E '\\s*//' -e '\\s*/\\*' '\\*/\\s*'"
59 "\n --exclude-blank-lines : -E '^\\s*$'"
60 "\n\n"
61 "The default call without any options is:"
62 "\n cline ./\n\n"
63 "So each file in the working directory is counted. If you want to count C"
64 "\nsource code in your working directory and its subdirectories, type:"
65 "\n cline -rs .c\n"
66 "\nIf you want to exclude comment lines, you may use the -e/-E option."
67 "\nAfter a line matches the regex pattern <start>, this and any following"
68 "\nline is not counted unless a line matches the <end> pattern. A line is"
69 "\nstill counted when it does not start or end with the respective pattern."
70 "\nPlease note, that cline does not trim the lines before matching against"
71 "\nthe pattern."
72 "\n\nExample (C without comments):"
73 "\n cline -s .c,.h --exclude-cstyle-comments"
74 "\n");
75 }
77 int exit_with_version(settings_t* settings) {
78 printf("cline - Version: " VERSION "\n");
79 destroy_settings_t(settings);
80 return 0;
81 }
83 int exit_with_help(settings_t* settings, int code) {
84 printf("cline - Version: " VERSION "\n");
85 printHelpText();
86 destroy_settings_t(settings);
87 return code;
88 }
90 int main(int argc, char** argv) {
92 /* Settings */
93 settings_t *settings = new_settings_t();
94 if (settings == NULL) {
95 fprintf(stderr, "Memory allocation failed.\n");
96 return 1;
97 }
99 /* Get arguments */
100 string_list_t *directories = new_string_list_t();
101 if (directories == NULL) {
102 fprintf(stderr, "Memory allocation failed.\n");
103 return 1;
104 }
105 char* includeSuffix = NULL;
106 char* excludeSuffix = NULL;
107 int checked = 0;
109 for (int t = 1 ; t < argc ; t++) {
111 int argflags = checkArgument(argv[t], "hsSrRmvVbeEic");
112 int paropt = 0;
114 /* h */
115 if ((argflags & 1) > 0 || strcmp(argv[t], "--help") == 0) {
116 return exit_with_help(settings, 0);
117 }
118 /* s */
119 if ((argflags & 2) > 0) {
120 if (!checkParamOpt(&paropt) || registerArgument(&checked, 2)) {
121 return exit_with_help(settings, 1);
122 }
123 t++;
124 if (t >= argc) {
125 return exit_with_help(settings, 1);
126 }
127 includeSuffix = argv[t];
128 }
129 /* S */
130 if ((argflags & 4) > 0) {
131 if (!checkParamOpt(&paropt) || registerArgument(&checked, 4)) {
132 return exit_with_help(settings, 1);
133 }
134 t++;
135 if (t >= argc) {
136 return exit_with_help(settings, 1);
137 }
138 excludeSuffix = argv[t];
139 }
140 /* r, R */
141 if ((argflags & 24) > 0) {
142 if (registerArgument(&checked, 24)) {
143 return exit_with_help(settings, 1);
144 }
145 settings->recursive = true;
146 }
147 /* m */
148 if ((argflags & 32) > 0) {
149 if (registerArgument(&checked, 32)) {
150 return exit_with_help(settings, 1);
151 }
152 settings->matchesOnly = true;
153 }
154 /* v */
155 if ((argflags & 64) > 0 || strcmp(argv[t], "--version") == 0) {
156 return exit_with_version(settings);
157 }
158 /* V */
159 if ((argflags & 128) > 0) {
160 if (registerArgument(&checked, 128)) {
161 return exit_with_help(settings, 1);
162 }
163 settings->verbose = false;
164 }
165 /* b */
166 if ((argflags & 256) > 0) {
167 if (!checkParamOpt(&paropt) || registerArgument(&checked, 256)) {
168 return exit_with_help(settings, 1);
169 }
170 t++;
171 if (t >= argc) {
172 return exit_with_help(settings, 1);
173 }
174 if (strcasecmp(argv[t], "ignore") == 0) {
175 settings->bfileHeuristics->level = BFILE_IGNORE;
176 } else if (strcasecmp(argv[t], "low") == 0) {
177 settings->bfileHeuristics->level = BFILE_LOW_ACCURACY;
178 } else if (strcasecmp(argv[t], "medium") == 0) {
179 settings->bfileHeuristics->level = BFILE_MEDIUM_ACCURACY;
180 } else if (strcasecmp(argv[t], "high") == 0) {
181 settings->bfileHeuristics->level = BFILE_HIGH_ACCURACY;
182 } else {
183 return exit_with_help(settings, 1);
184 }
185 }
186 /* e */
187 if ((argflags & 512) > 0) {
188 if (!checkParamOpt(&paropt) || t + 2 >= argc) {
189 return exit_with_help(settings, 1);
190 }
191 t++; add_string(settings->regex->pattern_list, argv[t]);
192 t++; add_string(settings->regex->pattern_list, argv[t]);
193 }
194 /* E */
195 if ((argflags & 1024) > 0) {
196 t++;
197 if (!checkParamOpt(&paropt) || t >= argc) {
198 return exit_with_help(settings, 1);
199 }
200 add_string(settings->regex->pattern_list, argv[t]);
201 add_string(settings->regex->pattern_list, "$");
202 }
203 /* i */
204 if ((argflags & 2048) > 0) {
205 /* cannot be used together with -V */
206 if (registerArgument(&checked, 128)) {
207 return exit_with_help(settings, 1);
208 }
209 settings->individual_sums = true;
210 }
211 if ((argflags & 4096) > 0) {
212 if (registerArgument(&checked, 4096)) {
213 return exit_with_help(settings, 1);
214 }
215 settings->count_chars = true;
216 settings->regex->count_chars = true;
217 }
218 if (argflags == 0) {
219 /* SHORTCUTS */
220 if (strcmp(argv[t], "--exclude-cstyle-comments") == 0) {
221 add_string(settings->regex->pattern_list, "\\s*//");
222 add_string(settings->regex->pattern_list, "$");
223 add_string(settings->regex->pattern_list, "\\s*/\\*");
224 add_string(settings->regex->pattern_list, "\\*/\\s*");
225 } else if (strcmp(argv[t], "--exclude-blank-lines") == 0) {
226 add_string(settings->regex->pattern_list, "^\\s*$");
227 add_string(settings->regex->pattern_list, "$");
228 }
229 /* Path */
230 else {
231 add_string(directories, argv[t]);
232 }
233 }
234 }
236 /* Find tokens */
237 parseCSL(includeSuffix, settings->includeSuffixes);
238 parseCSL(excludeSuffix, settings->excludeSuffixes);
240 /* Scan directories */
241 if (regex_compile_all(settings->regex)) {
242 scanresult_t* result = new_scanresult_t(settings);
243 /* Don't waste memory when only the total sum is needed */
244 string_list_t *output = settings->verbose ? new_string_list_t() : NULL;
245 char *outbuf;
246 const char* result_type = settings->count_chars ? "chars" : "lines";
248 unsigned total = 0;
249 if (directories->count == 0) {
250 add_string(directories, ".");
251 }
252 for (unsigned t = 0 ; t < directories->count ; t++) {
253 scanDirectory((scanner_t){directories->items[t], 0}, settings,
254 output, result);
255 total += result->result;
256 if (directories->count > 1 ) {
257 outbuf = (char*) malloc(81);
258 memset(outbuf, '-', 79);
259 outbuf[79] = '\n';
260 outbuf[80] = 0;
261 add_string(output, outbuf);
262 outbuf = (char*) malloc(81);
263 snprintf(outbuf, 81, "%-63s%10u %s\n", directories->items[t],
264 result->result, result_type);
265 add_string(output, outbuf);
266 outbuf = (char*) malloc(81);
267 memset(outbuf, '-', 79);
268 outbuf[79] = '\n';
269 outbuf[80] = 0;
270 add_string(output, outbuf);
271 }
272 }
273 destroy_string_list_t(directories);
275 /* Print result */
276 if (settings->verbose) {
277 for (int i = 0 ; i < output->count ; i++) {
278 printf("%s", output->items[i]);
279 free(output->items[i]);
280 }
282 if (result->ext) {
283 if (result->ext->count > 0) {
284 for (unsigned t = 0 ; t < 79 ; t++) {
285 printf("=");
286 }
287 printf("\nIndividual sums:\n");
288 for (unsigned t = 0 ; t < result->ext->count ; t++) {
289 printf(" %-62s%10u %s\n",
290 result->ext->extensions[t],
291 result->ext->result[t],
292 result_type);
293 }
294 }
295 }
297 for (unsigned t = 0 ; t < 79 ; t++) {
298 printf("=");
299 }
300 printf("\n%73d %s\n", total, result_type);
302 if (settings->confusing_lnlen &&
303 settings->regex->pattern_list->count > 0) {
305 printf("\nSome files contain too long lines.\n"
306 "The parser currently supports a maximum line length of %u."
307 "\nThe result might be wrong.\n", MAX_LINELENGTH);
308 }
309 } else {
310 printf("%u", total);
311 }
312 destroy_scanresult_t(result);
313 destroy_string_list_t(output);
314 destroy_settings_t(settings);
315 }
317 fflush(stdout);
318 fflush(stderr);
319 return 0;
320 }