Sun, 05 Jul 2026 12:50:42 +0200
move release tag
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2018 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef VERSION #error "VERSION macro must be set by build system" #endif #include "stdinc.h" #include "settings.h" #include "scanner.h" #include "arguments.h" #include "regex_parser.h" static void print_help() { printf( "\nUsage:" "\n cline [Options] [Directories...]" "\n\nCounts the line terminator characters (\\n) within all" " files in the specified\ndirectories." "\n\nOptions:" "\n -b <level> - Binary file heuristics level (default medium)" "\n One of: ignore low medium high" "\n -c - Count non-whitespace characters instead of lines" "\n -d - Report only directory sums" "\n -D <path> - Excludes the directory at the specified <path>" "\n -D <name> - Excludes all directories with the given <name>" "\n -E <pattern> - Excludes any line matching the <pattern>" "\n -e <start> <end> - Excludes lines between <start> and <end>" "\n You may use these options multiple times" "\n -h, --help - This help text" "\n -i - Print out individual sums per file extension" "\n (cannot be used together with -V)" "\n -m - Print information about matching files only" "\n -s <suffixes> - Only count files with these suffixes (separated" "\n by commas)" "\n -S <suffixes> - Count any file except those with these suffixes" "\n (separated by commas)" "\n -r, -R - Includes subdirectories" "\n -v, --version - Print out version information" "\n -V - Turn verbose output off, print the result only" "\n\nShortcuts:" "\n --exclude-cstyle-comments : -E '\\s*//' -e '\\s*/\\*' '\\*/\\s*'" "\n --exclude-blank-lines : -E '^\\s*$'" "\n" "\nMore infos are available in the man page" "\nor at https://uap-core.de/man/cline.html" "\n"); } static int exit_with_version(settings *settings) { printf("cline - Version: " VERSION "\n"); destroy_settings(settings); return 0; } static int exit_with_help(settings *settings, int code) { printf("cline - Version: " VERSION "\n"); print_help(); destroy_settings(settings); return code; } static void normalize_excluded_dirs(settings *settings) { /* normalize all paths */ for (size_t i = 0 ; i < settings->exclude_dirs->count ; i++) { char *arg = strdup(settings->exclude_dirs->items[i]); if (strpbrk(arg, "/\\") == NULL) { /* do not normalize names */ settings->exclude_dirs->items[i] = arg; } else { size_t arglen = strlen(arg); /* fix file separators */ char fs = FILE_SEPARATOR; char badfs = FILE_SEPARATOR == '/' ? '\\' : '/'; for (size_t j = 0 ; j < arglen ; j++) { if (arg[j] == badfs) { arg[j] = fs; } } /* make path absolute */ settings->exclude_dirs->items[i] = make_path_absolute(arg); free(arg); } } /* tell the string list to free the items */ settings->exclude_dirs->free_item = free; } static const char *sepline_double = "===============================================================================\n"; static const char *sepline_single = "-------------------------------------------------------------------------------\n"; int main(int argc, char **argv) { /* Settings */ settings *settings = new_settings(); if (settings == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return 1; } /* Get arguments */ string_list *directories = new_string_list(); if (directories == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return 1; } const char *includeSuffix = NULL; const char *excludeSuffix = NULL; int checked = 0; for (int t = 1 ; t < argc ; t++) { int argflags = argument_check(argv[t], "hsSrRmvVbeEicdD"); int paropt = 0; /* checks if no more than one opt with arg is combined */ /* h */ if ((argflags & 1) > 0 || strcmp(argv[t], "--help") == 0) { return exit_with_help(settings, 0); } /* s */ if ((argflags & 2) > 0) { if (paropt++ || argument_register(&checked, 2)) { return exit_with_help(settings, 1); } t++; if (t >= argc) { return exit_with_help(settings, 1); } includeSuffix = argv[t]; } /* S */ if ((argflags & 4) > 0) { if (paropt++ || argument_register(&checked, 4)) { return exit_with_help(settings, 1); } t++; if (t >= argc) { return exit_with_help(settings, 1); } excludeSuffix = argv[t]; } /* r, R */ if ((argflags & 24) > 0) { if (argument_register(&checked, 24)) { return exit_with_help(settings, 1); } settings->recursive = true; } /* m */ if ((argflags & 32) > 0) { if (argument_register(&checked, 32)) { return exit_with_help(settings, 1); } settings->matches_only = true; } /* v */ if ((argflags & 64) > 0 || strcmp(argv[t], "--version") == 0) { return exit_with_version(settings); } /* V */ if ((argflags & 128) > 0) { if (argument_register(&checked, 128)) { return exit_with_help(settings, 1); } settings->verbose = false; } /* b */ if ((argflags & 256) > 0) { if (paropt++ || argument_register(&checked, 256)) { return exit_with_help(settings, 1); } t++; if (t >= argc) { return exit_with_help(settings, 1); } if (strcasecmp(argv[t], "ignore") == 0) { settings->bfile->level = BFILE_IGNORE; } else if (strcasecmp(argv[t], "low") == 0) { settings->bfile->level = BFILE_LOW_ACCURACY; } else if (strcasecmp(argv[t], "medium") == 0) { settings->bfile->level = BFILE_MEDIUM_ACCURACY; } else if (strcasecmp(argv[t], "high") == 0) { settings->bfile->level = BFILE_HIGH_ACCURACY; } else { return exit_with_help(settings, 1); } } /* e */ if ((argflags & 512) > 0) { if (paropt++ || t + 2 >= argc) { return exit_with_help(settings, 1); } t++; add_string(settings->regex->pattern_list, argv[t]); t++; add_string(settings->regex->pattern_list, argv[t]); } /* E */ if ((argflags & 1024) > 0) { t++; if (paropt++ || t >= argc) { return exit_with_help(settings, 1); } add_string(settings->regex->pattern_list, argv[t]); add_string(settings->regex->pattern_list, "$"); } /* i */ if ((argflags & 2048) > 0) { /* cannot be used together with -V */ if ((checked & 128) > 0) { return exit_with_help(settings, 1); } settings->individual_sums = true; } /* c */ if ((argflags & 4096) > 0) { if (argument_register(&checked, 4096)) { return exit_with_help(settings, 1); } settings->count_chars = true; settings->regex->count_chars = true; } /* d */ if ((argflags & 8192) > 0) { if (argument_register(&checked, 8192)) { return exit_with_help(settings, 1); } /* ignored together with -V */ if ((checked & 128) == 0) { settings->dirs_only = true; } } /* D */ if ((argflags & 16384) > 0) { t++; if (paropt++ || t >= argc) { return exit_with_help(settings, 1); } add_string(settings->exclude_dirs, argv[t]); } if (argflags == 0) { /* SHORTCUTS */ if (strcmp(argv[t], "--exclude-cstyle-comments") == 0) { add_string(settings->regex->pattern_list, "\\s*//"); add_string(settings->regex->pattern_list, "$"); add_string(settings->regex->pattern_list, "\\s*/\\*"); add_string(settings->regex->pattern_list, "\\*/\\s*"); } else if (strcmp(argv[t], "--exclude-blank-lines") == 0) { add_string(settings->regex->pattern_list, "^\\s*$"); add_string(settings->regex->pattern_list, "$"); } /* Unknown option */ else if (argv[t][0] == '-') { fprintf(stderr, "Unrecognized option: %s\n\n", argv[t]); return exit_with_help(settings, 1); } /* Path */ else { add_string(directories, argv[t]); } } } /* Find tokens */ parse_csl(includeSuffix, settings->include_suffixes); parse_csl(excludeSuffix, settings->exclude_suffixes); /* Compiler regular expressions, if any specified */ if (!regex_compile_all(settings->regex)) { destroy_string_list(directories); destroy_settings(settings); return 1; } /* Normalize paths for excluded directories */ normalize_excluded_dirs(settings); /* Scan directories */ scanresult *result = new_scanresult(settings); const char *result_type = settings->count_chars ? "chars" : "lines"; bool has_output = false; unsigned total = 0; if (directories->count == 0) { add_string(directories, "."); } for (unsigned t = 0 ; t < directories->count ; t++) { /* Don't waste memory when only the total sum is needed */ string_list *output = NULL; if (settings->verbose) { output = new_string_list(); output->free_item = free; } scan_dir((scanner){directories->items[t], 0}, settings, output, result); total += result->result; if (settings->verbose) { has_output |= output->count > 0; for (size_t i = 0 ; i < output->count ; i++) { printf("%s", output->items[i]); } destroy_string_list(output); if (directories->count > 1) { has_output = true; fputs(sepline_single, stdout); printf("%-63s%10u %s\n", directories->items[t], result->result, result_type); fputs(sepline_single, stdout); } } } destroy_string_list(directories); /* Print result */ if (settings->verbose) { if (result->ext) { if (result->ext->count > 0) { has_output = true; fwrite(sepline_double, 1, 80, stdout); printf("\nIndividual sums:\n"); for (unsigned t = 0 ; t < result->ext->count ; t++) { printf(" %-62s%10u %s\n", result->ext->extensions[t], result->ext->result[t], result_type); } } } if (has_output) { fwrite(sepline_double, 1, 80, stdout); printf("%73d %s\n", total, result_type); } else { /* If there was not any output so far, skip formatting */ printf("%d %s\n", total, result_type); } if (settings->confusing_lnlen && settings->regex->pattern_list->count > 0) { printf("\nSome files contain too long lines.\n" "The parser currently supports a maximum line length of %u." "\nThe result might be wrong.\n", MAX_LINELENGTH); } } else { printf("%u", total); } destroy_scanresult(result); destroy_settings(settings); return 0; }