Thu, 23 Aug 2018 19:45:36 +0200
adds simple tiny test suite and updates license headers
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 cline [Options] [Directories...]"
38 "\n\nCounts the line terminator characters (\\n) within all"
39 " files in the specified\ndirectories."
40 "\n\nOptions:"
41 "\n -b <level> - binary file heuristics level (default medium)"
42 "\n One of: ignore low medium high"
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 -m - print information about matching files only"
48 "\n -s <suffixes> - only count files with these suffixes (separated"
49 "\n by commas)"
50 "\n -S <suffixes> - count any file except those with these suffixes"
51 "\n (separated by commas)"
52 "\n -r, -R - includes subdirectories"
53 "\n -v, --version - print out version information"
54 "\n -V - turn verbose output off, print the result only"
55 "\n\nShortcuts:"
56 "\n --exclude-cstyle-comments : -E '\\s*//' -e '\\s*/\\*' '\\*/\\s*'"
57 "\n --exclude-blank-lines : -E '^\\s*$'"
58 "\n\n"
59 "The default call without any options is:"
60 "\n cline ./\n\n"
61 "So each file in the working directory is counted. If you want to count C"
62 "\nsource code in your working directory and its subdirectories, type:"
63 "\n cline -rs .c\n"
64 "\nIf you want to exclude comment lines, you may use the -e/-E option."
65 "\nAfter a line matches the regex pattern <start> any following line is"
66 "\nnot counted unless a line matches the <end> pattern. A line is still "
67 "\ncounted when it does not start or end with the respective patterns."
68 "\nPlease note, that cline does not remove whitespace characters as this"
69 "\nmight not be reasonable in some cases."
70 "\n\nExample (C without comments):"
71 "\n cline -s .c,.h --exclude-cstyle-comments"
72 "\n");
73 }
75 int exit_with_version(settings_t* settings) {
76 printf("cline - Version: " VERSION "\n");
77 destroy_settings_t(settings);
78 return 0;
79 }
81 int exit_with_help(settings_t* settings, int code) {
82 printf("cline - Version: " VERSION "\n");
83 printHelpText();
84 destroy_settings_t(settings);
85 return code;
86 }
88 int main(int argc, char** argv) {
90 /* Settings */
91 settings_t *settings = new_settings_t();
92 if (settings == NULL) {
93 fprintf(stderr, "Memory allocation failed.\n");
94 return 1;
95 }
97 /* Get arguments */
98 string_list_t *directories = new_string_list_t();
99 if (directories == NULL) {
100 fprintf(stderr, "Memory allocation failed.\n");
101 return 1;
102 }
103 char* includeSuffix = NULL;
104 char* excludeSuffix = NULL;
105 int checked = 0;
107 for (int t = 1 ; t < argc ; t++) {
109 int argflags = checkArgument(argv[t], "hsSrRmvVbeE");
110 int paropt = 0;
112 /* s */
113 if ((argflags & 2) > 0) {
114 if (!checkParamOpt(&paropt) || registerArgument(&checked, 2)) {
115 return exit_with_help(settings, 1);
116 }
117 t++;
118 if (t >= argc) {
119 return exit_with_help(settings, 1);
120 }
121 includeSuffix = argv[t];
122 }
123 /* S */
124 if ((argflags & 4) > 0) {
125 if (!checkParamOpt(&paropt) || registerArgument(&checked, 4)) {
126 return exit_with_help(settings, 1);
127 }
128 t++;
129 if (t >= argc) {
130 return exit_with_help(settings, 1);
131 }
132 excludeSuffix = argv[t];
133 }
134 /* h */
135 if ((argflags & 1) > 0 || strcmp(argv[t], "--help") == 0) {
136 return exit_with_help(settings, 0);
137 }
138 /* r, R */
139 if ((argflags & 24) > 0) {
140 if (registerArgument(&checked, 24)) {
141 return exit_with_help(settings, 1);
142 }
143 settings->recursive = true;
144 }
145 /* m */
146 if ((argflags & 32) > 0) {
147 if (registerArgument(&checked, 32)) {
148 return exit_with_help(settings, 1);
149 }
150 settings->matchesOnly = true;
151 }
152 /* v */
153 if ((argflags & 64) > 0 || strcmp(argv[t], "--version") == 0) {
154 return exit_with_version(settings);
155 }
156 /* V */
157 if ((argflags & 128) > 0) {
158 if (registerArgument(&checked, 128)) {
159 return exit_with_help(settings, 1);
160 }
161 settings->verbose = false;
162 }
163 /* b */
164 if ((argflags & 256) > 0) {
165 if (!checkParamOpt(&paropt) || registerArgument(&checked, 256)) {
166 return exit_with_help(settings, 1);
167 }
168 t++;
169 if (t >= argc) {
170 return exit_with_help(settings, 1);
171 }
172 if (strcasecmp(argv[t], "ignore") == 0) {
173 settings->bfileHeuristics->level = BFILE_IGNORE;
174 } else if (strcasecmp(argv[t], "low") == 0) {
175 settings->bfileHeuristics->level = BFILE_LOW_ACCURACY;
176 } else if (strcasecmp(argv[t], "medium") == 0) {
177 settings->bfileHeuristics->level = BFILE_MEDIUM_ACCURACY;
178 } else if (strcasecmp(argv[t], "high") == 0) {
179 settings->bfileHeuristics->level = BFILE_HIGH_ACCURACY;
180 } else {
181 return exit_with_help(settings, 1);
182 }
183 }
184 /* e */
185 if ((argflags & 512) > 0) {
186 if (!checkParamOpt(&paropt) || t + 2 >= argc) {
187 return exit_with_help(settings, 1);
188 }
189 t++; add_string(settings->regex->pattern_list, argv[t]);
190 t++; add_string(settings->regex->pattern_list, argv[t]);
191 }
192 /* E */
193 if ((argflags & 1024) > 0) {
194 t++;
195 if (!checkParamOpt(&paropt) || t >= argc) {
196 return exit_with_help(settings, 1);
197 }
198 add_string(settings->regex->pattern_list, argv[t]);
199 add_string(settings->regex->pattern_list, "$");
200 }
201 if (argflags == 0) {
202 /* SHORTCUTS */
203 if (strcmp(argv[t], "--exclude-cstyle-comments") == 0) {
204 add_string(settings->regex->pattern_list, "\\s*//");
205 add_string(settings->regex->pattern_list, "$");
206 add_string(settings->regex->pattern_list, "\\s*/\\*");
207 add_string(settings->regex->pattern_list, "\\*/\\s*");
208 } else if (strcmp(argv[t], "--exclude-blank-lines") == 0) {
209 add_string(settings->regex->pattern_list, "^\\s*$");
210 add_string(settings->regex->pattern_list, "$");
211 }
212 /* Path */
213 else {
214 add_string(directories, argv[t]);
215 }
216 }
217 }
219 /* Find tokens */
220 parseCSL(includeSuffix, settings->includeSuffixes);
221 parseCSL(excludeSuffix, settings->excludeSuffixes);
223 /* Scan directories */
224 if (regex_compile_all(settings->regex)) {
225 /* Don't waste memory when only the total sum is needed */
226 string_list_t *output = settings->verbose ? new_string_list_t() : NULL;
227 char *outbuf;
229 int lineSum = 0, lines;
230 if (directories->count == 0) {
231 add_string(directories, ".");
232 }
233 for (int t = 0 ; t < directories->count ; t++) {
234 lines = scanDirectory((scanner_t){directories->items[t], 0}, settings,
235 output);
236 lineSum += lines;
237 if (directories->count > 1 ) {
238 outbuf = (char*) malloc(81);
239 memset(outbuf, '-', 79);
240 outbuf[79] = '\n';
241 outbuf[80] = 0;
242 add_string(output, outbuf);
243 outbuf = (char*) malloc(81);
244 snprintf(outbuf, 81, "%-63s%10d lines\n", directories->items[t], lines);
245 add_string(output, outbuf);
246 outbuf = (char*) malloc(81);
247 memset(outbuf, '-', 79);
248 outbuf[79] = '\n';
249 outbuf[80] = 0;
250 add_string(output, outbuf);
251 }
252 }
253 destroy_string_list_t(directories);
255 /* Print result */
256 if (settings->verbose) {
257 for (int i = 0 ; i < output->count ; i++) {
258 printf("%s", output->items[i]);
259 free(output->items[i]);
260 }
262 for (int t = 0 ; t < 79 ; t++) {
263 printf("=");
264 }
265 printf("\n%73d lines\n", lineSum);
267 if (settings->confusing_lnlen &&
268 settings->regex->pattern_list->count > 0) {
270 printf("\nSome files contain too long lines.\n"
271 "The regex parser currently supports a maximum line length of %d."
272 "\nThe result might be wrong.\n", REGEX_MAX_LINELENGTH);
273 }
274 } else {
275 printf("%d", lineSum);
276 }
277 destroy_string_list_t(output);
278 destroy_settings_t(settings);
279 }
281 fflush(stdout);
282 fflush(stderr);
283 return 0;
284 }