support dir exclusion part 2/2 default tip

Tue, 30 Jun 2026 14:19:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 30 Jun 2026 14:19:08 +0200
changeset 95
8a20001544c1
parent 94
9637e3efb8e7

support dir exclusion part 2/2

tests with MSVC are still open

resolves issue #838 (at least for non-Windows)

src/cline.c file | annotate | diff | comparison | revisions
src/scanner.c file | annotate | diff | comparison | revisions
src/scanner.h file | annotate | diff | comparison | revisions
--- a/src/cline.c	Tue Jun 30 12:00:16 2026 +0200
+++ b/src/cline.c	Tue Jun 30 14:19:08 2026 +0200
@@ -94,6 +94,61 @@
   return code;
 }
 
+static void normalize_excluded_dirs(settings_t *settings) {
+  /* normalize all paths */
+  for (int i = 0 ; i < settings->excludeDirs->count ; i++) {
+    char *arg = strdup(settings->excludeDirs->items[i]);
+    if (strpbrk(arg, "/\\") == NULL) {
+      /* do not normalize names */
+      settings->excludeDirs->items[i] = arg;
+    } else {
+      size_t arglen = strlen(arg);
+      /* fix file separators */
+      char fs = settings->fileSeparator;
+      char badfs = settings->fileSeparator == '/' ? '\\' : '/';
+      for (size_t j = 0 ; j < arglen ; j++) {
+        if (arg[j] == badfs) {
+          arg[j] = fs;
+        }
+      }
+      /* make path absolute */
+      {
+        char *ap = make_path_absolute(arg);
+        free(arg);
+        arg = ap;
+        arglen = strlen(ap);
+      }
+      /* make path canonical */
+      char *canonical = malloc(arglen+1);
+      size_t canonicallen = 0;
+      for (size_t j = 0 ; j < arglen ; j++) {
+        canonical[canonicallen++] = arg[j];
+        if (arg[j] == fs) {
+          /* collapse consecutive separators */
+          while (arg[j+1] == fs) j++;
+        } else if (arg[j] == '.') {
+          if (arg[j+1] == fs) {
+            /* skip '.' segments */
+            canonicallen--;
+            j++;
+          } else if (arg[j+1] == '.' && arg[j+2] == fs) {
+            /* trace back '..' segment */
+            canonicallen -= 2;
+            while (canonical[canonicallen-1] != fs) canonicallen--;
+            j+=2;
+          }
+        }
+      }
+      canonical[canonicallen] = '\0';
+      settings->excludeDirs->items[i] = canonical;
+      free(arg);
+    }
+  }
+
+  /* tell the string list to free the items */
+  settings->excludeDirs->free_item = free;
+}
+
 static const char * sepline_double = "===============================================================================\n";
 static const char * sepline_single = "-------------------------------------------------------------------------------\n";
 
@@ -242,8 +297,6 @@
       if (!checkParamOpt(&paropt) || t >= argc) {
         return exit_with_help(settings, 1);
       }
-      // TODO: normalize argument before adding to dir list
-      //       this makes it more efficient to compare dir names later
       add_string(settings->excludeDirs, argv[t]);
     }
     if (argflags == 0) {
@@ -280,13 +333,16 @@
     return 1;
   }
 
+  /* Normalize paths for excluded directories */
+  normalize_excluded_dirs(settings);
+
   /* Scan directories */
   scanresult_t* result = new_scanresult_t(settings);
   const char* result_type = settings->count_chars ? "chars" : "lines";
   bool has_output = false;
   unsigned total = 0;
   if (directories->count == 0) {
-      add_string(directories, "./");
+      add_string(directories, ".");
   }
   for (unsigned t = 0 ; t < directories->count ; t++) {
     /* Don't waste memory when only the total sum is needed */
--- a/src/scanner.c	Tue Jun 30 12:00:16 2026 +0200
+++ b/src/scanner.c	Tue Jun 30 14:19:08 2026 +0200
@@ -31,6 +31,54 @@
 #include <sys/stat.h>
 #include <ctype.h>
 
+#ifdef _MSC_VER
+#include <windows.h>
+#include <shlwapi.h>
+
+void get_working_dir(const char** out_ptr, size_t *out_len) {
+  static char cwd[MAX_PATH];
+  if (GetCurrentDirectory(MAX_PATH, cwd) == 0) {
+    fprintf(stderr, "Could not get current working directory.\n");
+    exit(1);
+  }
+  size_t len = strlen(cwd);
+  if (cwd[len - 1] != '/') {
+    cwd[len++] = '/';
+    cwd[len] = '\0';
+  }
+  *out_ptr = cwd;
+  *out_len = len;
+}
+bool path_is_relative(const char *path) {
+  return PathIsRelative(path);
+}
+int pathcmp(const char *p1, const char *p2) {
+  return strcasecmp(p1, p2);
+}
+#else
+#include <unistd.h>
+void get_working_dir(const char** out_ptr, size_t *out_len) {
+  static char cwd[PATH_MAX];
+  if (getcwd(cwd, sizeof(cwd)) == NULL) {
+    fprintf(stderr, "Could not get current working directory.\n");
+    exit(1);
+  }
+  size_t len = strlen(cwd);
+  if (cwd[len - 1] != '/') {
+    cwd[len++] = '/';
+    cwd[len] = '\0';
+  }
+  *out_ptr = cwd;
+  *out_len = len;
+}
+bool path_is_relative(const char *path) {
+  return path[0] != '/';
+}
+int pathcmp(const char *p1, const char *p2) {
+  return strcmp(p1, p2);
+}
+#endif
+
 typedef struct filelist filelist_t;
 
 struct filelist {
@@ -185,10 +233,32 @@
   return list;
 }
 
-static bool is_dir_excluded(string_list_t* list, char* dir) {
-  // TODO: implement
-  //       assume normalized dir names in list (cline.c will make sure of it)
-  //       remember to do case-insensitive comparison for WIN32
+static bool is_dir_excluded(settings_t* settings, const char* dir) {
+  const string_list_t * const list = settings->excludeDirs;
+
+  for (size_t i = 0 ; i < list->count ; i++) {
+    /* determine if the list item is a path or a name */
+    if (strchr(list->items[i], settings->fileSeparator) == NULL) {
+      /* compare only the name */
+      const char *dirpart = strrchr(dir, settings->fileSeparator);
+      if (dirpart == NULL) {
+        dirpart = dir;
+      } else {
+        dirpart++;
+      }
+      if (pathcmp(list->items[i], dirpart) == 0) {
+        return true;
+      }
+    } else {
+      /* compare the full paths */
+      char* absdir = make_path_absolute(dir);
+      if (pathcmp(list->items[i], absdir) == 0) {
+        free(absdir);
+        return true;
+      }
+      free(absdir);
+    }
+  }
   return false;
 }
 
@@ -208,9 +278,16 @@
     if (!S_ISREG(filelist->st_mode)) {
       if (S_ISDIR(filelist->st_mode)) {
         if (settings->recursive) {
-          if (is_dir_excluded(settings->excludeDirs, filelist->filename)) {
+          if (is_dir_excluded(settings, filelist->filename)) {
             if (!settings->matchesOnly && settings->verbose) {
-              // TODO: print "no match"
+              /* Print hint */
+              outbuf = malloc(81);
+              snprintf(outbuf, 81, "%*s%*s%19s\n",
+                       filelist->displayname_len + scanner.spaces,
+                       filelist->displayname,
+                       60 - filelist->displayname_len - scanner.spaces,
+                       "", "no match");
+              add_string(output, outbuf);
             }
           } else {
             string_list_t *recoutput = settings->verbose ? new_string_list_t() : NULL;
@@ -343,3 +420,32 @@
     free(freethis);
   }
 }
+
+char *make_path_absolute(const char *path) {
+  static const char *cwd = NULL;
+  static size_t cwdlen = 0;
+#ifdef _WIN32
+  static char fs = '\\';
+#else
+  static char fs = '/';
+#endif /* _WIN32 */
+  if (path_is_relative(path)) {
+    if (cwdlen == 0) {
+      get_working_dir(&cwd, &cwdlen);
+    }
+    size_t pathlen = strlen(path);
+    char *result = malloc(pathlen+cwdlen+1);
+    const char *part = path;
+    size_t partlen = pathlen;
+    if (part[0] == '.' && part[1] == fs) {
+      part += 2;
+      partlen -= 2;
+    }
+    memcpy(result, cwd, cwdlen);
+    memcpy(result+cwdlen, part, partlen);
+    result[cwdlen+partlen] = '\0';
+    return result;
+  } else {
+    return strdup(path);
+  }
+}
--- a/src/scanner.h	Tue Jun 30 12:00:16 2026 +0200
+++ b/src/scanner.h	Tue Jun 30 14:19:08 2026 +0200
@@ -57,6 +57,7 @@
 scanresult_t* new_scanresult_t(settings_t* settings);
 void destroy_scanresult_t(scanresult_t*);
 
+char *make_path_absolute(const char *path);
 
 #ifdef _cplusplus
 }

mercurial