From 77324929cf588d496110eb9e933c33b393544edd Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Thu, 10 Feb 2022 04:17:23 +0900 Subject: [PATCH] readtags: add -c option Partially close #3168. TODO: * revise code, * update the readtags.1 man page, * consider Windows, and * add test cases. Signed-off-by: Masatake YAMATO --- extra-cmds/readtags-cmd.c | 354 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 343 insertions(+), 11 deletions(-) diff --git a/extra-cmds/readtags-cmd.c b/extra-cmds/readtags-cmd.c index cca0571501..e9195e745d 100644 --- a/extra-cmds/readtags-cmd.c +++ b/extra-cmds/readtags-cmd.c @@ -12,6 +12,9 @@ #include "printtags.h" #include "routines.h" #include "routines_p.h" + +#include "vstring.h" +#include "htable.h" #include /* strerror */ #include /* exit */ #include /* stderr */ @@ -24,6 +27,8 @@ static sortType SortMethod; static int allowPrintLineNumber; static int debugMode; static int escaping; +static hashTable *canonicalizePathTable; +static vString *ctagsCWD; #ifdef READTAGS_DSL #include "dsl/qualifier.h" static QCode *Qualifier; @@ -84,6 +89,219 @@ static void printPseudoTag (const tagEntry *entry) tagsPrintPseudoTag (entry, &opts, NULL, stdout); } +struct comp { + char *str; + size_t len; + struct comp *parent; + struct comp *child; +}; + +static size_t strlen_comps (struct comp *comp) +{ + size_t n = 0; + + while (comp) + { + n += (1 + comp->len); + comp = comp->child; + } + return n; +} + +static void strcpy_comps (char *buf, struct comp *comp) +{ + while (comp) + { + buf[0] = '/'; + memcpy (buf + 1, comp->str, comp->len); + buf += (1 + comp->len); + comp = comp->child; + } + buf[0] = '\0'; +} + +static char *fsimplify_absz (struct comp *comp) +{ + struct comp *root = comp; + + while (comp) + { + // fprintf (stderr, "[stack] -%s-\n", comp->str); + root = comp; + comp = comp->parent; + } + + if (root->child && root->len == 0) + root = root->child; + + size_t len = strlen_comps (root); + char *buf = malloc (len + 1); + + strcpy_comps(buf, root); + return buf; +} + +static char *fsimplify_abs0 (char *fname, struct comp *parent) +{ + char *next = strchr (fname, '/'); + struct comp comp = { + .str = fname, + .len = next? (next - fname): strlen (fname), + .parent = parent, + .child = NULL + }; + + if (comp.len == 0 + || (comp.len == 1 && fname [0] == '.')) + { + parent->child = NULL; + if (next == NULL) + return fsimplify_absz (parent); + *next = '\0'; + return fsimplify_abs0 (next + 1, parent); + } + + if (comp.len == 2 && fname [0] == '.' && fname [1] == '.') + { + if (next == NULL) + { + if (parent->parent) + { + parent->parent->child = NULL; + return fsimplify_absz (parent->parent); + } + return strdup("/"); + } + *next = '\0'; + if (parent->parent) + { + parent->parent->child = NULL; + return fsimplify_abs0 (next + 1, parent->parent); + } + comp.parent = NULL; + comp.str[0] = '\0'; + comp.len = 0; + return fsimplify_abs0 (next + 1, &comp); + } + + parent->child = ∁ + if (next == NULL) + return fsimplify_absz (&comp); + *next = '\0'; + return fsimplify_abs0 (next + 1, &comp); +} + +char *fsimplify_abs (char *fname) +{ + char *next = strchr (fname, '/'); + if (next == NULL) + { + if (!strcmp (fname, "..") || !strcmp (fname, ".")) + { + fname [0] = '/'; + fname [1] = '\0'; + return strdup (fname); + } + { + char *r = malloc (strlen (fname) + 2); + r[0] = '/'; + strcpy (r + 1, fname); + return r; + } + } + + *next = '\0'; + struct comp comp = { + .str = fname, + .len = next - fname, + .parent = NULL, + .child = NULL + }; + if (!strcmp (comp.str, "..") || !strcmp (comp.str, ".")) + { + comp.str[0] = '\0'; + comp.len = 0; + } + return fsimplify_abs0 (next + 1, &comp); +} + + + +static char *canonicalizePathNew(const char *dir, size_t dir_len, const char *rela) +{ + bool relative = false; + vString *buf = vStringNew (); + + if (rela == NULL) + vStringCopyS (buf, dir); + else if (rela[0] == '/') + vStringCopyS (buf, rela); + else + { + vStringCopyS (buf, dir); + vStringPut (buf, '/'); + vStringCatS (buf, rela); + relative = true; + } + + char *r = fsimplify_abs (vStringValue (buf)); + if (relative) + { + if (strncmp (dir, r, dir_len) == 0) + { + if (r[dir_len] == '/') + { + if (r[dir_len + 1] == '\0') + vStringCopyS (buf, "."); + else + vStringCopyS (buf, r + dir_len + 1); + eFree (r); + return vStringDeleteUnwrap (buf); + } + else if (r[dir_len] == '\0') + { + vStringCopyS (buf, "."); + eFree (r); + return vStringDeleteUnwrap (buf); + } + else + { + vStringDelete (buf); + return r; + } + } + } + + vStringDelete (buf); + return r; +} + + +static const char *canonicalizePath (const char *cwd, size_t cwd_len, const char *input, + hashTable* canon_table) +{ + static char *input_last; + static char *return_last; + + if (input_last) + { + if (strcmp (input, input_last) == 0) + return return_last; + input_last = NULL; + } + + char *r = hashTableGetItem (canon_table, input); + if (r) + return r; + + r = canonicalizePathNew (cwd, cwd_len, input); + + input_last = eStrdup (input); + return_last = r; + hashTablePutItem (canon_table, input_last, return_last); + return return_last; +} + #ifdef READTAGS_DSL static void freeCopiedTag (tagEntry *e) { @@ -230,9 +448,19 @@ static void walkTags (tagFile *const file, tagEntry *first_entry, do { + tagEntry *shadow = first_entry; + tagEntry shadowRec; + if (ctagsCWD) + { + shadow = &shadowRec; + shadowRec = *first_entry; + shadow->file = canonicalizePath (vStringValue (ctagsCWD), vStringLength (ctagsCWD), + first_entry->file, canonicalizePathTable); + } + if (Qualifier) { - int i = q_is_acceptable (Qualifier, first_entry); + int i = q_is_acceptable (Qualifier, shadow); switch (i) { case Q_REJECT: @@ -244,11 +472,11 @@ static void walkTags (tagFile *const file, tagEntry *first_entry, if (a) { - tagEntry *e = copyTag (first_entry); + tagEntry *e = copyTag (shadow); tagEntryArrayPush (a, e); } else - (* actionfn) (first_entry); + (* actionfn) (shadow); } while ( (*nextfn) (file, first_entry) == TagSuccess); int err = tagsGetErrno (file); @@ -274,7 +502,19 @@ static void walkTags (tagFile *const file, tagEntry *first_entry, void (* actionfn) (const tagEntry *)) { do - (* actionfn) (first_entry); + { + tagEntry *shadow = first_entry; + tagEntry shadowRec; + if (ctagsCWD) + { + shadow = &shadowRec; + shadowRec = *first_entry; + shadow->file = canonicalizePath (vStringValue (ctagsCWD), vStringLength (ctagsCWD), + first_entry->file, canonicalizePathTable); + } + + (* actionfn) (shadow); + } while ( (*nextfn) (file, first_entry) == TagSuccess); int err = tagsGetErrno (file); @@ -324,6 +564,52 @@ static void removeTagFile (void) eFree ((char *)TagFileName); } + +tagResult tagsFindPseudoTag (tagFile *const file, tagEntry *const entry, const char *const name, const int options) +{ + size_t len = strlen (name); + tagResult r = tagsFirstPseudoTag (file, entry); + if (r != TagSuccess) + return r; + + do + { + if (options & TAG_PARTIALMATCH) + { + if (strncmp (entry->name, name, len) == 0) + return TagSuccess; + } + else + { + if (strcmp (entry->name, name) == 0) + return TagSuccess; + } + + r = tagsNextPseudoTag (file, entry); + } while (r == TagSuccess); + + return r; +} + +static vString *loadCtagsCWD (tagFile *const file) +{ + vString *r = NULL; + + tagEntry pentry; + if (tagsFindPseudoTag (file, &pentry, "!_TAG_PROC_CWD", TAG_FULLMATCH|TAG_OBSERVECASE) == TagSuccess) + { + char *tmp = eStrdup (pentry.file); + if (!tmp) + perror(__FUNCTION__); + /* TODO */ + char *a = fsimplify_abs (tmp); + eFree (tmp); + return vStringNewOwn (a); + } + + return r; +} + static tagFile *openTags (const char *const filePath, tagFileInfo *const info) { if (strcmp (filePath, "-") == 0) @@ -359,7 +645,7 @@ static tagFile *openTags (const char *const filePath, tagFileInfo *const info) return tagsOpen (filePath, info); } -static void findTag (const char *const name, const int options) +static void findTag (const char *const name, const int options, int canon) { tagFileInfo info; tagEntry entry; @@ -374,6 +660,17 @@ static void findTag (const char *const name, const int options) exit (1); } + if (canon && ctagsCWD == NULL) + { + ctagsCWD = loadCtagsCWD (file); + if (ctagsCWD == NULL) + { + fprintf (stderr, "%s: no !_TAG_PROC_CWD in %s\n", + ProgramName, TagFileName); + exit (1); + } + } + if (SortOverride) { if (tagsSetSortType (file, SortMethod) != TagSuccess) @@ -405,7 +702,7 @@ static void findTag (const char *const name, const int options) tagsClose (file); } -static void listTags (int pseudoTags) +static void listTags (int pseudoTags, int canon) { tagFileInfo info; tagEntry entry; @@ -422,6 +719,17 @@ static void listTags (int pseudoTags) exit (1); } + if (pseudoTags == 0 && canon && ctagsCWD == NULL) + { + ctagsCWD = loadCtagsCWD (file); + if (ctagsCWD == NULL) + { + fprintf (stderr, "%s: no !_TAG_PROC_CWD in %s\n", + ProgramName, TagFileName); + exit (1); + } + } + if (pseudoTags) { if (tagsFirstPseudoTag (file, &entry) == TagSuccess) @@ -492,6 +800,8 @@ static const char *const Usage = " -s[0|1|2] | --override-sort-detection METHOD\n" " Override sort detection of tag file.\n" " METHOD: unsorted|sorted|foldcase\n" + " -c | --canonicalize-path\n" + " Reduct '..' and '.' in input fields.\n" #ifdef READTAGS_DSL " -F EXP | --formatter EXP\n" " Format the tags listed by ACTION with EXP when printing.\n" @@ -567,6 +877,7 @@ extern int main (int argc, char **argv) int actionSupplied = 0; int i; int ignore_prefix = 0; + int canon = 0; ProgramName = argv [0]; setExecutableName (ProgramName); @@ -577,7 +888,7 @@ extern int main (int argc, char **argv) const char *const arg = argv [i]; if (ignore_prefix || arg [0] != '-') { - findTag (arg, options); + findTag (arg, options, canon); actionSupplied = 1; } else if (arg [0] == '-' && arg [1] == '\0') @@ -589,7 +900,7 @@ extern int main (int argc, char **argv) debugMode++; else if (strcmp (optname, "list-pseudo-tags") == 0) { - listTags (1); + listTags (1, 0); actionSupplied = 1; } else if (strcmp (optname, "help") == 0) @@ -632,7 +943,7 @@ extern int main (int argc, char **argv) options |= TAG_PARTIALMATCH; else if (strcmp (optname, "list") == 0) { - listTags (0); + listTags (0, canon); actionSupplied = 1; } else if (strcmp (optname, "line-number") == 0) @@ -672,6 +983,13 @@ extern int main (int argc, char **argv) exit (1); } } + else if (strcmp (optname, "canonicalize-path") == 0) + { + canon = 1; + if (!canonicalizePathTable) + canonicalizePathTable = hashTableNew (7, hashCstrhash, hashCstreq, + eFree, eFree); + } #ifdef READTAGS_DSL else if (strcmp (optname, "filter") == 0) { @@ -729,7 +1047,7 @@ extern int main (int argc, char **argv) switch (arg [j]) { case 'd': debugMode++; break; - case 'D': listTags (1); actionSupplied = 1; break; + case 'D': listTags (1, 0); actionSupplied = 1; break; case 'h': printUsage (stdout, 0); break; #ifdef READTAGS_DSL case 'H': @@ -752,7 +1070,7 @@ extern int main (int argc, char **argv) case 'e': extensionFields = 1; break; case 'i': options |= TAG_IGNORECASE; break; case 'p': options |= TAG_PARTIALMATCH; break; - case 'l': listTags (0); actionSupplied = 1; break; + case 'l': listTags (0, canon); actionSupplied = 1; break; case 'n': allowPrintLineNumber = 1; break; case 't': if (arg [j+1] != '\0') @@ -775,6 +1093,12 @@ extern int main (int argc, char **argv) else printUsage(stderr, 1); break; + case 'c': + canon = 1; + if (!canonicalizePathTable) + canonicalizePathTable = hashTableNew (4093, hashCstrhash, hashCstreq, + eFree, eFree); + break; #ifdef READTAGS_DSL case 'Q': if (i + 1 == argc) @@ -822,5 +1146,13 @@ extern int main (int argc, char **argv) if (Formatter) f_destroy (Formatter); #endif + + if (canonicalizePathTable) + { + // hashTablePrintStatistics(canonicalizePathTable); + hashTableDelete (canonicalizePathTable); + } + if (ctagsCWD) + vStringDelete (ctagsCWD); return 0; }