mirror of https://github.com/pybricks/ldraw.git
598 lines
18 KiB
C
598 lines
18 KiB
C
/*
|
||
* 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 "libgen.h"
|
||
|
||
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 <windows.h>
|
||
#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 <dir> input directory, default is \"PARTS\" in current directory\n");
|
||
printf(" -o <file> 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);
|
||
}
|