/*
 *  	MakeDMake v0.19 -- automatically construct a makefile for DMake
 *
 *  	Basically it's Tim McGrath's 'MakeMake' from Fish 74,
 *  	-----------------------------------------------------
 *
 *  	but it has been slightly hacked, so that it got much more
 *  	enterprising and workaholic, so to say.
 *
 *
 *  	HOW TO:
 *
 *  	You give it the names of all the C-files that are to produce your
 *  	executable (but NOT #include'd .c or .h files!), and it will
 *  	automatically scan them to find all dependencies, and produce a
 *  	ready to use (in many cases) DMakeFile calling DCC with options
 *  	you will need for normal compilation and linking. Other things
 *  	can be added if needed by editing that file. In most cases it
 *  	should be enough to give MakeDMake '#? or '*' on command-line.
 *  	You will be than asked about the name of the executable, if you
 *		just press RETURN -- you will get the default name, being EITHER
 *		the NAME of the input file without the extension (if it was the
 *		ONLY name MakeDMake got), or 'main' (without quotes).
 *
 *  	For exemple, if you call it with the command-line like this:
 *
 *  	MakeDMake file1.c file2.c file3.c ... filen.c
 *
 *  	and choose 'ex_file' as the name for the executable,
 *
 *  	you will get something like that in your DMakeFile:
 *
 *  	OPT1 =
 *  	OPT2 =
 *
 *  	ex_file 		: file1.o file2.o file3.o ... filen.o
 *  		dcc $(OPT1) -o ex_file  file1.o file2.o file3.o ... filen.o
 *
 *  	filex.o :	filex.c ... header file list ...
 *  		dcc $(OPT1) filex.c
 *
 *  	where the header file list is the transitive closure of all
 *  	files #included in filex.c (i.e. if filex.c #includes "header.h",
 *  	and "header.h" #includes "subheader.h", both "header.h" and
 *  	"subheader.h" appear in the list of header files). MakeDMake only
 *  	examines header files delimited by double quotes ("). System header
 *      files enclosed in angle brackets (< and >) are not examined.
 *      (That last bit of beautiful native-speaker English was almost
 *      literally taken from Tim's original comment. Now, back to my own
 *      personal variant...)
 *
 *      As you probably know, if you have all properly set, 'dmake'
 *      typed in the CLI with no parameters, will look for the
 *      file called 'DMakeFile' in current directory and use its information
 *      to update/recompile etc. your files. And it can be made even easier
 *      with aliases. With two csh aliases like that:
 *
 *          alias mdm "MakeDMake *.c"   ## for all C files in current dir
 *          alias go "DMake"            ## not so much really, but...
 *
 *          	## alternatively -- if you may have 'unique' makefile names
 *				## in the form <something>.make, you may use:
 *
 *			"alias go DMake -f *make"
 *
 *      I can compile most of the C programs with no effort whatsoever,
 *      as most of the DCC I need use are already set in the ENV:DCCOPTS.
 *      You can, however, recompile with your own standard set of options.
 *      Many other things here can be easilly changed, adjusted and
 *      then recompiled; also for other compilers than DICE. Try to do
 *      that by changing the #defines first!
 *
 *      The original 'MakeMake' was in Public Domain, and this stuff here
 *      also is.
 *
 *                                  Piotr Obminski, January 1993
 */

#include <stdio.h>

#ifndef	void
#define void int
#endif

#define LINEMAX			80			/* length of line in created DMakeFile */
#define CC_NAME         "dcc"		/* or e.g. "lc" for Lattice */

/* default name for executable (only if if given two or more C files!).
 * NO '.' (like in 'a.out') PLEASE! This is still no UNIX!
 */
#define MAIN_NAME       "main"

/* this program doesn't NECESSARILY need to call itself MakeDMake...!
 */
#define OUTFILE_NAME    "DMakeFile"

/* default OPT1 & OPT2 strings
 */
#define O2X_OPT         NULL            /* 'object to executable' options */
#define C2O_OPT         NULL            /* 'C to object' options */

/* zero if we want call outfile 'DMakeFile' instead of the individualized
 * (unique) name in the form '<executable>.make'
 */
#define UNIQUE_OUTNAME	0

/*	ANSI sequences, #define them as '""' (no single quotes!)
 *	if you hate 'em!
 */
#define	ANSI_BOLD	"›1m"
#define	ANSI_WHITE	"›2m"				/* more or less... */
#define ANSI_NORMAL	"›0m"

char main_name[ 40 ] = MAIN_NAME;

FILE *OutFile;


	void
main( argc, argv )
    char **argv;
    int argc;
{
    int dependent_count;
    char **dependents;
    char outfile_name[ 40 ] = OUTFILE_NAME;
	char temp[ 40 ] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
	int good_file_count = 0;
    int i;

    /* no input or '?', 'h', 'H' or something beginning with '-', so
	 * he needs help!
     */
    if ( 	argc == 1 || ( *argv[ 1 ] == '?' ) || ( *argv[ 1 ] == '-' ) ||
            ( ( *argv[ 1 ] == 'h' ) && ( *( argv[ 1 ] + 1 ) == '\0' ) )	||
			( ( *argv[ 1 ] == 'H' ) && ( *( argv[ 1 ] + 1 ) == '\0' ) ) ) {

        puts( "------------------------------------------------------------\n"
				ANSI_BOLD "MakeDMake v0.19" ANSI_NORMAL ANSI_WHITE
				"  PD Utility for creating" ANSI_NORMAL ANSI_BOLD
				" DMakeFile" ANSI_NORMAL "'s\n"
        		"------------------------------------------------------------\n"
        		ANSI_WHITE "USAGE:  " ANSI_NORMAL "Give me names of your "
				ANSI_BOLD "source files " ANSI_NORMAL
				"and then we'll\n"
				"        get interactive.  If I'm successful, the resulting\n"
				"        " ANSI_BOLD "DMakeFile" ANSI_NORMAL
				" will be in the current directory.\n"
				"        Now that you know -- "
				ANSI_BOLD "START AGAIN" ANSI_WHITE " !\n" ANSI_NORMAL
        		"------------------------------------------------------------" );

        exit ( 1 );
    }

	/* if we got only one command-line argument, and it ends in '.c'
	 * we start by cutting off this '.c', then we look...
	 */
	if ( argc == 2 ) {
		register char *p;
		strcpy( temp, argv[ 1 ] );
		if ( ( p = (char *)strstr( temp, ".c" ) ) && ( *( p + 2 ) == '\0' ) ) {
			*p = '\0';
			strcpy( main_name, temp );
		}
		else {
			puts( ANSI_BOLD "This stuff you gave me ain't no C code!. "
					ANSI_NORMAL ANSI_WHITE "Aborting!" ANSI_NORMAL );
			exit( 1 );
		}
	}

    printf( "Name for the executable? (or "
			ANSI_BOLD "RETURN" ANSI_NORMAL " for "
				"'" ANSI_WHITE "%s" ANSI_NORMAL "'): ", main_name );

    gets( temp );

    if ( isalpha( *temp ) ) {
    	strcpy( main_name, temp );

#if UNIQUE_OUTNAME
   		/* change name of the generated MakeFile to
     	* <executable>.make
     	*/
     	strcpy( outfile_name, temp );
     	strcat( outfile_name, ".make" );
#endif

	}

	printf( "------------------------------------------------------" );

	{
	 	register short i = strlen( temp );
		if ( i )
	 		putchar( '-' );

	 	while ( i-- )
	 			putchar( '-' );
	}

	putchar( '\n' );

    if ( ( OutFile = fopen( outfile_name, "w" ) ) == 0L ) {
        printf( ANSI_BOLD "CAN'T CREATE " ANSI_NORMAL ANSI_WHITE
					"%s" ANSI_NORMAL ANSI_BOLD " !\n\07" ANSI_NORMAL,
					outfile_name );
		exit( 1 );
    }

    fputs( "# DMakeFile generated by MakeDMake v0.19\n\n", OutFile );
    fprintf( OutFile, "OPT1 = %s\nOPT2 = %s\n\n", O2X_OPT, C2O_OPT );

    depend_file( argc, argv, main_name, "  ", ".o" );

    for ( i = 1; i < argc; i++ ) {
		/* we don't want MakeDMake to make a fool of himself by
		 * trying to compile various ReadMe's and Trashcan.icon!
		 * So we are skipping such stuff! However it's not always totally
		 * successful, so far...
		 */
		register char *p;
		if ( ( p = (char *)strstr( argv[ i ], ".c" ) ) &&
											( *( p + 2 ) == '\0' ) ) {
        	get_dependents( argv[ i ], &dependents, &dependent_count );
        	depend_file( dependent_count, dependents, argv[ i ], ".o", "" );
        	free_space( dependents, dependent_count );
			good_file_count++;
		}
		else {
			printf( "%s", argv[ i ] );
			if ( strlen( argv[ i ] ) < 16 )
				putchar( '\t' );
			if ( strlen( argv[ i ] ) < 8 )
				putchar( '\t' );
			puts( "\t" ANSI_NORMAL ANSI_WHITE "<" ANSI_NORMAL ANSI_BOLD
					"ignored" ANSI_NORMAL ANSI_WHITE ">" ANSI_NORMAL );
		}
    }

    fclose( OutFile );
	if ( good_file_count == 0 ) {
		puts( ANSI_WHITE "Not even one good C file!" ANSI_NORMAL ANSI_BOLD
				" Who" ANSI_NORMAL "(m) " ANSI_BOLD
				"do you think you're kidding?" ANSI_NORMAL );
		exit( 1 );
	}
	exit( 0 );
}


	int
free_space( dp, dc )
/*
 * Purpose: free up list of file names
 * Inputs:  dp - points to list of pointers to strings
 *          dc - number of pointers in the list
 */
    char **dp;
    int dc;
{
    while ( dc > 0 ) {
        free( *dp++ ); dc--;
    }
    /* free( dp );  <------ here is the offender in the original
     * 						which makes the program to hang.
     */
}


	char *
file_exten( pgm_name, xtension, bufout )
/*
 * Purpose: append new extension onto file name
 * Inputs:  pgm_name - pointer to name of file
 *          xtension - pointer to new file name extension (2 chars only)
 * Outputs: bufout - points to area for new file name
 * Returns: bufout
 */
     char *pgm_name, *xtension, *bufout;
{
    int i = 0;

    while ( *pgm_name ) {
        bufout[ i++ ] = *pgm_name;
        if ( *pgm_name++ == '.' && xtension[ 0 ] != '\0' ) {
            bufout[ i++ ] = xtension[ 1 ];
            break;
        }
    }
    bufout[ i ] = '\0';  return( bufout );
}


	void
depend_file( ct, flist, pgm_name, pgmx, filex )
/*
 * Purpose: print file name and list of dependents
 * Inputs:  ct - number of dependents in the list
 *          flist - pointer to a list of pointers to dependent names
 *          pgm_name - name of file whose dependents are being printed
 *          pgmx - extension for pgm_name file (or "" if none)
 *          filex - extension for dependent file names (or "" if none)
 */
    char **flist, *pgm_name, *pgmx, *filex;
    int ct;
{
    int i;
    char buf[ LINEMAX ], add_name[ LINEMAX ], pname[ LINEMAX ];
    char bare_name[ LINEMAX ];

    {
        register short i = 0;

        do {
            bare_name[ i ] = pgm_name[ i ];
        } while ( pgm_name[ i ] && ( pgm_name[ i++ ] != '.' ) );

        bare_name[ --i ] = '\0';
    }

	start_line( file_exten( pgm_name, pgmx, pname ), buf );

    if ( strcmp( pgm_name, main_name ) )
		strcat( strcat( buf, " " ), pgm_name );

    for ( i = 1; i < ct; i++ ) {

		file_exten( flist[ i ], filex, add_name );

        if ( columns( buf ) + strlen( add_name ) + 1 >= LINEMAX - 1 ) {
            fputs( buf, OutFile ); fputs( "\\\n", OutFile );
            continue_line( file_exten( NULL, NULL, pname ), buf );
        }
        strcat( strcat( buf, " " ), add_name );
    }
    fputs( buf, OutFile ); fputc( '\n', OutFile );

    if ( strcmp( pgm_name, main_name ) ) {
        fprintf( OutFile,"\t%s $(OPT2) -c %s.o %s.c\n\n",
										CC_NAME, bare_name, bare_name );
    }
    else {
        fprintf( OutFile, "\t%s $(OPT1) -o %s ", CC_NAME, main_name );
        output_objects( flist, ct );
        fputs( "\n\n", OutFile );
    }
}


	void
output_objects( files, count )
    char **files;
    int count;
{
    unsigned short i, col, len;
    char arr[ 40 ], *brk1, *brk2;
    register char *p;

    col = 32;       /* more or less initial column */

    for ( i = 1; i < count; i++ ) {
		/* see if file has legal name ending in '.c'
		 */
		if ( ( p = (char *)strstr( files[ i ], ".c" ) ) &&
											( *( p + 2 ) == '\0' ) ) {

        	strcpy( arr, files[ i ] );

        	len = strlen( arr );
        	col += len;

        	if ( col >= LINEMAX - 2 ) {
            	brk1 = "\\\n\t\t\t";
            	brk2 = "";
            	col = 32 + len + 1;
        	}
        	else {
            	brk1 = " ";
            	brk2 = "";
       		}

        	p = (char *)strchr( arr, '.' );
        	*++p = 'o';

        	fprintf( OutFile, "%s%s%s", brk1, arr, brk2 );
		}
    }
}


	void
start_line( with_name, buf )
/*
 * Purpose: give each line a standard indentation
 * Inputs:  with_name - name of root file on each line
 *          buf - place to put indented line
 */
    char *with_name, *buf;
{
    strcpy( buf, with_name ); strcat( buf, "\t" );

    if ( columns( buf ) < 16 )
		strcat( buf, "\t" );

    if ( columns( buf ) < 24 )
		strcat( buf, "\t" );

    strcat( buf, ":" );
}


	void
continue_line( with_name, buf )
/*
 * Purpose: let the line continue after '\' and '\n'
 * Inputs:  with_name - name of root file on each line
 *          buf - place to put indented line
 */
    char *with_name, *buf;
{
    strcpy( buf, with_name );
	strcat( buf, "\t " );

    if ( columns( buf ) < 16 )
		strcat( buf, "\t " );

    if ( columns( buf ) < 24 )
		strcat( buf, "\t " );
}


	int
columns( s )
/*
 * Purpose: count the number of columns a line spans
 * Inputs:  s - the characters in a line
 * Returns: the number of columns ( including tab expansion )
 */
    char *s;
{
    int col = 0;

    while ( *s ) {
        if ( *s++ == '\t' )
			while ( ++col & 7 )
				;
        else ++col;
    }
    return( col );
}


	void
get_dependents( fn, depv, depc )
/*
 * Purpose: return a list of files depending on a C source file
 * Inputs:  fn - name of the c source file
 * Outputs: depv - list of dependents (an array of pointers to filenames)
 *          depc - number of dependents
 */
     char *fn, ***depv;
     int *depc;
{
    char **lst;
    int i;

    lst = (char **)malloc( 1024 * sizeof( char * ) );
    move_name( &lst[ 0 ], fn );
    fputs( fn, stdout ); fputc( '\n', stdout ); i = 0;

    scan_file( lst, &i, fn ); *depv = lst; *depc = i + 1;
}


	void
move_name( p, s )
/*
 * Purpose: Allocate space for a new filename and copy it
 * Inputs:  p - location for new pointer to filename
 *          s - pointer to file name
 */
     char **p, *s;
{
    *p = (char *)malloc( strlen( s ) + 1 );
	strcpy( *p, s );
}


	void
scan_file( file_name_list, last_list_used, fn )
/*
 * Purpose: search a C source file file #includes, and search the #includes
 *          for nested #includes
 * Inputs:  fn - name of file to scan
 * Outputs: file_name_list - list of included files
 *          last_list_used - last used filename position in file_name_list
 */
    char **file_name_list, *fn;
    int *last_list_used;
{
    FILE *fp;
    char buf[ 1024 ], ifn[ LINEMAX ];
    int j,k;

    fp = fopen( fn, "r" );

    if ( ! fp ) {
		fprintf( stdout, ANSI_BOLD "\tcouldn't open file "
					ANSI_NORMAL ANSI_WHITE "%s\n" ANSI_NORMAL, fn );
		return;
	}

    while ( fgets( buf, 1024, fp ) ) {
    	if ( strncmp( buf, "#include", 8 ) == 0 ) {
        	j = 8;
        	while ( buf[ j ] == ' ' || buf[ j ] == '\t' )
				j++;
        	if ( buf[ j++ ] != '"' )
				continue;
			k = 0;
			while ( buf[ j ] ) {
				if ( buf[ j ] == '"' || buf[ j ] == '\n' )
					break;
            	else
					ifn[ k++ ] = buf[ j++ ];
        	}
        	ifn[ k ] = '\0';
        	if ( add_name( file_name_list, last_list_used, ifn ) )
            	scan_file( file_name_list, last_list_used, ifn );
        }
    }
    fclose( fp );
}


	int
add_name( file_name_list, last_list_used, fn )
/*
 * Purpose: Add a file name to the list if it's not there already
 * Inputs:  file_name_list - pointer to array of pointers to file names
 *          last_list_used - last element in array with a filename
 *          fn - name of file
 * Returns: 1 if file name added, 0 otherwise
 */
    char **file_name_list, *fn;
    int *last_list_used;
{
    int i;

    for ( i = 0; i <= *last_list_used; i++ )
        if ( ! strcmp( file_name_list[ i ], fn ) )
			return( 0 );

    *last_list_used += 1;
    move_name( &file_name_list[ *last_list_used ], fn );
    return( 1 );
}

