/* * mklist.c, a replacement for James Jessiman's makelist * Copyright (C) 1999 Lars C. Hassing * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /***************************************************************************** Please do not edit this file. In stead contact Lars C. Hassing (lch@cci.dk) to get your changes integrated in a future release. ******************************************************************************/ /* 990214 lch Release v1.0 990316 lch Release v1.1: added options + minor changes to sort algorithms 990518 lch Release v1.2: Skip "~Moved to xxx" by default 20000703 lch Release v1.3: Added -i and -o 20030625 dmh Release v1.4: Ported to gcc. 200????? ??? Release v1.5: ??? Lost track of this one... 20090928 dmh Release v1.6beta1: Added -f -q -v -8 and allow 25 chars in filenames. 20100706 dmh Release v1.6: Added -l and made long format the default. */ /* Compile with Borland Turbo C 2.0: tcc -mc -d -f -k- -N -v- -y- -wrvl -wstv -wucp -wnod -wpro -wuse -wsig -ncmdsrel -M src\mklist.c */ #include "stdio.h" #include "stdlib.h" #include "ctype.h" #include "string.h" #include "alloc.h" #include "dir.h" #include "conio.h" #include char *ProgVer = "mklist v1.6 20100706 (C) 1999-2010 Lars C. Hassing lch@ccieurope.com"; int forceit = 0; int quiet = 0; /*****************************************************************/ /* Filename length compatibility stuff */ /*****************************************************************/ #ifndef MAX_PATH #define MAX_PATH 256 #endif char shortfilepath[MAX_PATH]; char shortfilename[MAX_PATH]; #ifdef __BORLANDC__ #define LACKS_BASENAME 1 int numlines = 1000; /* Use small chunks for DOS */ int namelen = 12; /* Set to 12 for 8.3 DOS compatibility. */ #else #ifdef __TURBOC__ #define LACKS_BASENAME 1 int numlines = 1000; /* Use small chunks for DOS */ int namelen = 12; /* Set to 12 for 8.3 DOS compatibility. */ #else int numlines = 16384; /* Allocate lines in bigger chunks if not DOS. */ int namelen = 25; /* LSC specs allow 25 chars in filename. */ #endif #endif #ifdef _WIN32 #include #define LACKS_BASENAME 1 #else int GetShortPathName(char *longpath, char * shortpath, int psize) { strncpy(shortpath, longpath, psize); return(strlen(shortpath)); } #endif #if LACKS_BASENAME char *basename( const char *filepath ) { char *tmpstr, *ptr; if (filepath == NULL) { return NULL; } if ( (ptr = strrchr(filepath, '\\')) || (ptr = strrchr(filepath, '/')) ) { /* If there isn't anything after the last separator, the result is a 0-length string */ tmpstr = strdup(ptr+1); } else { /* dup the string, so caller can safely free whatever we return */ tmpstr = strdup(filepath); } return tmpstr; } #endif /*****************************************************************/ int CmpNumber(const void *p1, const void *p2) { char *s1 = *((char **) p1); char *s2 = *((char **) p2); long l1; long l2; if (isdigit(*s1) && isdigit(*s2)) { l1 = atol(s1); l2 = atol(s2); if (l1 != l2) return (l1 < l2 ? -1 : 1); /* Numbers are equal. Be sure to make 3005.dat come before 3005-1.dat */ do { l1 = (unsigned char) *s1++; if ('A' <= l1 && l1 <= 'Z') l1 -= ('A' - 'a'); else if (l1 == '.') l1 = '\0'; /* Sort dot very first */ l2 = (unsigned char) *s2++; if ('A' <= l2 && l2 <= 'Z') l2 -= ('A' - 'a'); else if (l2 == '.') l2 = '\0'; /* Sort dot very first */ } while (l1 && (l1 == l2)); return ((int) (l1 - l2)); } return (stricmp(s1, s2)); } /*****************************************************************/ int CmpDescription(const void *p1, const void *p2) { char *s1 = *((char **) p1); char *s2 = *((char **) p2); int Res; #if 0 Res = stricmp(s1 + (namelen+2), s2 + (namelen+2)); #else s1 += strcspn(s1, " \t"); /* Find the beginning of whitespace. */ s1 += strspn(s1, " \t"); /* Skip to end of whitespace (start of description). */ s2 += strcspn(s2, " \t"); /* Find the beginning of whitespace. */ s2 += strspn(s2, " \t"); /* Skip to end of whitespace (start of description). */ Res = stricmp(s1, s2); #endif return (Res ? Res : CmpNumber(p1, p2)); } /*****************************************************************/ void PressAnyKey(void) { if (forceit) return; if (quiet) return; printf(" Press any key to continue"); getch(); printf("\n"); } /*****************************************************************/ void PrintUsage(void) { printf("Options:\n"); printf(" -h You already figured this one out :-)\n"); printf(" -n Sort by Number\n"); printf(" -d Sort by Description\n"); printf(" -c Check for duplicate descriptions. \"parts.lst\" unchanged.\n"); printf(" -m Don't skip parts with \"~Moved to xxx\" description\n"); printf(" -~ Skip parts with ~ description, e.g. \"~Winch 2 x 4 x 2 Top\"\n"); printf(" -i input directory, default is \"PARTS\" in current directory\n"); printf(" -o output filename, default is \"parts.lst\" in current directory\n"); printf(" -f Force it to finish. No prompts.\n"); printf(" -q Quiet mode. No warnings, and no prompts.\n"); printf(" -8 Use 8.3 names for compatibility.\n"); printf(" -t Truncating descriptions to fit in an 80 char terminal window.\n"); printf(" -r Ragged filename column. Size it to fit short filenames.\n"); printf(" -l Truncate Long descriptions at 64 chars.\n"); printf(" -v Print verbose info. Useful for debugging.\n"); } /*****************************************************************/ int main(int argc, char **argv) { int CheckDuplicateDescriptions; int SortBy; int SkipTilde; int SkipMovedto; char *arg; int c; int i; int j; FILE *fp; struct ffblk ffb; int done; int Len; char **Lines; int maxLines; int nLines; int pathlen; char Line[200]; char Dirname[MAX_PATH]; char Filename[MAX_PATH]; char OutFilename[MAX_PATH]; char *s; char *Description; char *FormattedLine; unsigned long farcoreleftStart; unsigned long farcoreleftEnd; long FileSize; struct stat statbuf; int verbose; int ragged; int terminalsized; int longDescriptions; printf("%s\n", ProgVer); printf("Replacement for James Jessiman's makelist\n"); printf("Call with -h to see a list of options.\n\n"); strcpy(Dirname, "PARTS"); /* Default input directory path */ strcpy(OutFilename, "parts.lst"); /* Default output filename */ verbose = 0; ragged = 0; /* Steve wanted it ragged (1) by default */ terminalsized = 0; /* Steve wanted it (1) to fit in 80 chars by default */ longDescriptions = 1; /* Do not truncate descriptions at 64 chars by default */ CheckDuplicateDescriptions = 0; SortBy = 0; SkipTilde = 0; SkipMovedto = 1; while (--argc > 0) { arg = *++argv; if (arg[0] == '-') { switch (arg[1]) { case '?': case 'h': PrintUsage(); exit(1); break; case '8': namelen = 12; break; case 'c': CheckDuplicateDescriptions = 1; break; case 'n': case 'd': SortBy = arg[1]; break; case 'm': SkipMovedto = 0; break; case '~': SkipTilde = 1; break; case 'i': if (--argc > 0) strcpy(Dirname, *++argv); else { PrintUsage(); printf("*** input directory expected as next argument after -i.\n"); exit(1); } break; case 'o': if (--argc > 0) strcpy(OutFilename, *++argv); else { PrintUsage(); printf("*** output filename expected as next argument after -o.\n"); exit(1); } break; case 'q': quiet = 1; case 'f': forceit = 1; break; case 'r': ragged = ragged ^ 1; break; case 't': terminalsized = terminalsized ^ 1; break; case 'l': longDescriptions = longDescriptions ^ 1; break; case 'v': verbose = 1; break; default: PrintUsage(); printf("*** Unknown option '%s'.\n", arg); exit(1); break; } } else { PrintUsage(); exit(1); } } /* Do a stat to see if Dirname exists and is a directory. */ if (stat(Dirname, &statbuf) < 0) { printf("*** Could not stat input directory \"%s\".\n", Dirname); exit(1); } if ((statbuf.st_mode & S_IFDIR) == 0) { printf("*** Input directory \"%s\" is not a directory.\n", Dirname); exit(1); } if (CheckDuplicateDescriptions) SortBy = 'd'; if (!SortBy) { if (forceit) SortBy = 'd'; if (!quiet) printf("Sorting by [D]escription.\n"); } if (!SortBy) { printf("Sort by [N]umber or [D]escription: "); c = getch(); printf("%c\n", c); if (c == 'N' || c == 'n') SortBy = 'n'; else if (c == 'D' || c == 'd') SortBy = 'd'; else { printf("Nothing done.\n"); exit(0); } } farcoreleftStart = farcoreleft(); nLines = 0; maxLines = numlines; Lines = farmalloc(maxLines * sizeof(char *)); if (!Lines) { printf("Out of memory after %d parts\n", nLines); printf("Memory available at beginning: %ld kBytes\n", (farcoreleftStart + 1023) / 1024); exit(1); } strcpy(Filename, Dirname); strcat(Filename, "\\"); pathlen = strlen(Filename); strcat(Filename, "*.*"); for (done = findfirst(Filename, &ffb, 0); !done; done = findnext(&ffb)) { if (verbose) { printf("Processing file: \"%s\"\n", ffb.ff_name); } strcpy(Filename + pathlen, ffb.ff_name); fp = fopen(Filename, "rt"); if (!fp) { if (!quiet) printf("Cannot open \"%s\"", ffb.ff_name); PressAnyKey(); continue; } fgets(Line, sizeof(Line), fp); fclose(fp); s = Line + strlen(Line) - 1; while (s >= Line && (*s == '\n' || *s == '\r' || *s == '\t' || *s == ' ')) *s-- = '\0'; /* clear newline and trailing tabs and spaces */ s = Line; while (*s == '\t' || *s == ' ') *s++; if (*s++ != '0') { if (!quiet) printf("Line type 0 expected in \"%s\", skipping...", ffb.ff_name); PressAnyKey(); continue; } while (*s == '\t' || *s == ' ') *s++; Description = s; if (SkipTilde && Description[0] == '~') continue; if (SkipMovedto && strncmp(Description, "~Moved to", 9) == 0) continue; Len = strlen(Description); if (Len == 0) { if (!quiet) printf("Empty description in \"%s\"", ffb.ff_name); PressAnyKey(); } if ((Len > 64) && !longDescriptions) { /* Original makelist truncates to 64 characters. */ if (!quiet) { printf("Description in \"%s\" will be truncated to 64 characters:\n", ffb.ff_name); printf("Before: \"%s\"\n", Description); printf("After: \"%-64.64s\"\n", Description); } PressAnyKey(); } if (namelen == 12) FormattedLine = farmalloc(79); else FormattedLine = farmalloc(256); if (!FormattedLine) { printf("Out of memory after %d parts\n", nLines); printf("Memory available at beginning: %ld kBytes\n", (farcoreleftStart + 1023) / 1024); exit(1); } if (namelen > 12) strcpy(shortfilename, ffb.ff_name); else { GetShortPathName(Filename, shortfilepath, MAX_PATH); s = basename(shortfilepath); strcpy(shortfilename, s); if (s != NULL) free(s); if (strcmp(ffb.ff_name, shortfilename)) { if (!quiet) printf("Filename \"%s\" will be shortened to %s\n", ffb.ff_name, shortfilename); PressAnyKey(); } } Len = strlen(shortfilename); if (Len > namelen) { if (!quiet) printf("Filename \"%s\" will be truncated to %d characters.\n", shortfilename, namelen); PressAnyKey(); } shortfilename[namelen] = 0; if (namelen == 12) sprintf(FormattedLine, "%-12s %-64.64s", shortfilename, Description); else if (ragged && terminalsized) { if (Len > 14) /* Squeeze every last char out of the 80. */ sprintf(FormattedLine, "%s %-64.64s", shortfilename, Description); else sprintf(FormattedLine, "%-14s %-64.64s", shortfilename, Description); } else if (ragged && longDescriptions) { if (Len > 12) sprintf(FormattedLine, "%s %s", shortfilename, Description); else sprintf(FormattedLine, "%-12s %s", shortfilename, Description); } else if (ragged) { if (Len > 12) sprintf(FormattedLine, "%s %-64.64s", shortfilename, Description); else sprintf(FormattedLine, "%-12s %-64.64s", shortfilename, Description); } else if (longDescriptions) sprintf(FormattedLine, "%-25s %s", shortfilename, Description); else sprintf(FormattedLine, "%-25s %s", shortfilename, Description); if (terminalsized) { if (namelen == 12) FormattedLine[78] = 0; else FormattedLine[80] = 0; } if (verbose) { printf("%d:\t%s\n", nLines, FormattedLine); } if (nLines >= maxLines) { /* Let's have another 1000 pointers */ maxLines += numlines; Lines = farrealloc(Lines, maxLines * sizeof(char *)); if (!Lines) { printf("Out of memory after %d parts\n", nLines); printf("Memory available at beginning: %ld kBytes\n", (farcoreleftStart + 1023) / 1024); exit(1); } } Lines[nLines++] = FormattedLine; if (nLines % 100 == 0) printf("%d parts so far...\r", nLines); } printf("%d parts found in %s.\n", nLines, Dirname); if (nLines == 0) { printf("No parts found, nothing done.\n"); exit(0); } printf("Sorting...\n"); qsort(Lines, nLines, sizeof(Lines[0]), (SortBy == 'n') ? CmpNumber : CmpDescription); if (CheckDuplicateDescriptions) { printf("Checking for duplicate descriptions. \"%s\" unchanged.\n", OutFilename); for (i = 0; i < nLines; i += j) { for (j = 1; i + j < nLines; j++) { if (stricmp(Lines[i] + (namelen+2), Lines[i + j] + (namelen+2)) != 0) break; /* OK to break, lines are sorted */ if (j == 1) /* First duplicate */ printf("%s\n", Lines[i]); printf("%s\n", Lines[i + j]); } if (j > 1) /* Duplicates found */ PressAnyKey(); } } else { fp = fopen(OutFilename, "wt"); if (!fp) { printf("Cannot open \"%s\" for writing.\n", OutFilename); exit(1); } for (i = 0; i < nLines; i++) fprintf(fp, "%s\n", Lines[i]); FileSize = ftell(fp); fclose(fp); printf("\"%s\" successfully written, %ld kBytes\n", OutFilename, (FileSize + 1023) / 1024); } if (numlines > 1000) /* if not Borland DOS compiler then skip the mem msg. */ { return (0); } farcoreleftEnd = farcoreleft(); printf("Maximum memory usage: %ld kBytes of %ld kBytes available\n", (farcoreleftStart - farcoreleftEnd + 1023) / 1024, (farcoreleftStart + 1023) / 1024); return (0); }