#define ROOT_DIR "..\\"
//
// BUILD 4.1A
//
// Copyright (c) 1994, 1995 Greenleaf Software, Inc.  All Rights Reserved
//
// June 8, 1995  4.0C
//
//  Modified the get first/get next stuff to support native mode OS/2
//  compilation of BUILD.CPP
//
// January 31, 1995 4.0B
//
// One last change.  This version of BUILD supports batch mode compilation.
// If your compiler can handle a CMD file with a list of a zillion files,
// we can supply it.  This often speeds up the build process, because
// the compiler is only loaded once, instead of once per file.
//
// Batch mode operation has a taint of kludgeness about it.  There
// are three hardcoded compilation passes: C, CPP, and ASM.  During the
// stage where we build config files for the compilation, BUILD pays
// special attention to any config files that have the string "*.c",
// "*.cpp", or "*.asm" in their contents.  If it sees those strings,
// it does two things.  First, it expands the line with "*.xxx" on it,
// adding the line to the config file once per file.  Second, it switches
// the appropriate compiler command from ONE_AT_A_TIME to BATCH.
//
// When it comes time to compile, any command that was switched to batch
// mode will be executed once and only once, with no file names added
// to the processing line.  A typical command might be: BCC @CLIB.CMD.
//
// The batch flag is in the item class, which means even non-command keys
// such as DeleteFiles and Description are set up to run one at a time
// or batch mode.  This indicates a lack of rigor in the class design,
// mea culpa.
//
// January 26, 1995 4.0
//
// Yet another major personality change for BUILD.EXE.  I targeted
// this release with simplifying the program.  In release 3.0, I
// modified BUILD to create EXEs and LIBraries, but it added quite
// a bit of complexity to the program.  This release still has both
// of those capabilities, but the implementation has changed quite
// a bit.
//
// BUILD now operates like two completely different programs that
// share a common infrastructore.  If you are building libraries,
// as indicated by the presence of "-lib" on the command line,
// BUILD first compiles all of your source code, then builds
// your library and/or DLL.  Compilation is done by running
// the command lines specified by the CPP, C, and ASM key values.
// Libraries are built by the LIB key values.  This means we no longer
// have separate key values for the linker and the librarion, the are
// all combined into LIB.  This release of BUILD allows you to specify
// multiple commands for a specific tag, so when building a DLL you
// might have LIB broken into three or four steps.
//
// If you are building an executable, presumably a demo program, build
// takes a completely different approach.  It doesn't do any precompilation
// on the file list.  Instead, it executes all the EXE tag values for
// each file.  DOS programs may only use 1 EXE line, but building a Windows
// application might take three or four lines.
//
// The biggest change to this program is in the area of configuration
// files.  BUILD used to automatically build config files, using names
// like CPP.CMD to hold the options from the CppOptions key value.  Now,
// BUILD requires you to specify exactly which CMD files you want to BUILD.
// When you are building libraries, it will create all CMD files that are
// defined using a key value that looks like this: *LIB.CMD.  When building
// EXE files, it will create all CMD files that are defined using a key
// value that looks like this: *EXE.CMD.
//
// Config files for libraries are built once, before any compilation takes
// place.  This means that you can't include the name of a specific file
// that you are compiling in the config file, it has to be listed on the
// command line.  Config files can have multiple lines, just by creating
// multiple lines with the same key in the INI file.
//
// The most exciting thing about config files built for libraries is the
// ability to put all your file names into one config file dynamically.
// As BUILD is scanning the input lines for the config file, it looks for
// %file*% strings inside the file.  If it finds it, it creates a line
// for every single file in the input list.  This is how we create LIB.CMD,
// which is used to feed file names to the librarian.
//
// When building CONFIG files for example programs, this file name
// expansion isn't done.  Instead, a config file is build for each
// demo program being built, and file name substitutions are performed
// just as they would be for any other arguments.
//
// The final item in the new BUILD is the argument substitution syntax.
// There are a bunch of arguments in INI files that get substituted
// when executing command lines or when building config files.  In
// BUILD 3.0 these were a bunch of single letter codes escaped with
// a '%' symbol.  In build 4.0, I have modified these to be actual
// strings enclosed in a pair of '%' symbols.  The goal is to make the
// INI file more readable.  Don't know if I succeeded or not.  For the
// record, the current crop of arguments that will be substituted in
// command lines are:
//
//  %file%          :  The complete name of the source file.  For example,
//                     when building libraries, this string will be turned
//                     into a name something like: ..\C_W16\TW_W16.C
//
//  %file.path%     :  The drive and path of the file, with a guaranteed
//                     '\' character.  In the above example, this would
//                     yield: "..\C_W16\".  If the file is in the current
//                     directory, you get ".\".
//
//  %file.name%     :  The name component of the source file, minus an
//                     extension.  If my source file is "EXAMP00.C", this
//                     argument will be converted to "EXAMP00".  We use
//                     this argument a lot for commands passed to the
//                     librarian or linker.
//
//  %model%         :  This string yields the model name, as defined in the
//                     "[]" string in the INI file.  For example, a small
//                     model build will return the "S" string.  The commlib
//                     6.0 DLL will probably return "600".  This may or
//                     may not have anything to do with the actual memory
//                     model in use by the compiler.  By convention, normal
//                     models will often use the same first initial that
//                     is used to pass the memory model to the compiler.
//
//  %model.lower%   :  Borland usually takes the model specification for
//                     the compiler with a lower case letter, generally
//                     chosen from s, m, c, l, h.  This expression will
//                     yield those letters by taken the first letter from
//                     the model name, and converting it to lower case.
//
//  %model.upper%   :  Same deal, but converts to upper case instead.
//                     Microsoft usually wants model letters in upper
//                     case.  No accounting for taste.
//
//  %model.asm%     :  This creates a string suitable for passing to
//                     MASM or TASM with the /DGF_MODEL=xxx option.
//                     Note that it figures this out based on the
//                     first character of the model name.  Values
//                     other than s, m, c, l, and h give a bad result,
//                     because we can't figure out what the memory
//                     model should be.
//
//  %nl%            :  Inserts a new line into the output stream,
//                     literally a '\n';
//
//  %%              :  Inserts the literal '%' character.
//
//  %comspec%       :  Inserts the name of the current command processor.
//                     We figure this out by looking at the COMSPEC
//                     environment variable.  If this variable isn't
//                     defined, "COMMAND.COM" is used.  On my system,
//                     the substitution yields "C:\4DOS40\4DOS.COM".
//
//  %continue%      :  This means the character immediately following
//                     the %continue% is a continuation character.  If
//                     there are more files coming after this one, we
//                     output the continuation character.  Otherwise,
//                     it gets eaten.
//
// To add new tags to the INI file substitutions, just edit the process()
// function.  It's easy.
//
// June 29, 1994  2.1
//
//  Lots of changes to BUILD.CPP.  First, I added the template
//  capability.  You can now define a library template, which is
//  shared among multiple libraries.  For example, to build DOS
//  libraries, I have a single DOS template which is shared among
//  S, SD, C, ...etc.  The include the template by using the
//  the new identifier Template=xxx.  Template definitions start
//  usually have a "." right before the name, which prevents them
//  from being displayed when sections are being dumped.
//
//  The second major change in this release of BUILD is the addition
//  of format specifiers to the keywords.  It used to be that you
//  could put %s in a command line, and BUILD would substitute the
//  filename.  It still does that, but now you can put %m for the model
//  letter in lower case, %M for upper case, %p for the file path and
//  drive, %n for the name, and %l for the library name.
//
//  The third major change is the addition of the BUILDDEMO capability.
//  Each library now has a Demo identifier, which allows BUILD
//  to create demo programs using that library.
//
//  A minor feature change is that most commands can now appear multiple
//  times in a section.  Each multiple appearance of a command is
//  tacked on to the previous ones.  When it comes time to execute the
//  commands, they are done one at a time.  This is real useful for
//  building demos that require a compile/rc/link sequence.  See the INI
//  file for details on how this works.
//
//  The Linker line now accepts multiple commands, which need
//  to be separated by the caret character: ^.  Because of this,
//  all BUILD.INI files will need to be modified to perform
//  an explicit IMPLIB.
//
// May 18, 1994   2.0
//
// This is a completely new version of the Greenleaf Build program.
// It has the same syntax as the previous version, and works mostly
// the same way.  The major changes are:
//
//   o  This version of BUILD will create DLLs and Import Libraries
//
//   o  Instead of hardcoding the information in BUILD.C, this version
//      of BUILD reads all of its information out of BUILD.INI.
//
// One of the major constraints on BUILD 1.0 was that it was required
// to have as small a memory footprint as possible.  That is why so
// much of the program was hardcoded instead of flexible.  Now, all of
// our compilers use DOS Extenders, so we don't care any more how
// much memory we take up.  This version of BUILD is the result.
//
// Much of the functionality in this version of BUILD is found into
// the bstring [Build String] class.  Some day we may be able to use a
// standard ANSI string class, but that does not exist yet.

//
// The rules:  All of the information needed by the linker is stored
//             in BUILD.INI.  The data in BUILD.INI is used to generate
//             CPPLIB.CMD and LINKLIB.CMD (and others).  LIB.CMD is generated
//             using a list of file names.
//
// "BUILD ?" will create the CMD files, and not delete them
// afterwards.  You have to delete them yourself.
//
// "BUILD -keep" will leave the objects around even if the library
// builds properly.
//
// "BUILD -lib" builds libraries
//
// "BUILD -exe" builds demos and examples
//
// "BUILD -cmd " performs a dry run, showing you the commands without
//               actually executing any of them.
//
#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <process.h>
#include <errno.h>
#include <conio.h>
#if defined( __WATCOMC__ )
#include <io.h> // Had to get unlink from here...
#endif
#include <ctype.h>
#include "build.h"
//#include "c:/tools/mc30/memcheck.h"
//
// int main( int argc, char *argv[] )
//
// ARGUMENTS
//
//  argc    : The number of arguments being passed on the command line.
//
//  argv    : The strings containing the arguments.
//
// RETURNS
//
//  0 if all files were built, and everything else ran without error.
//  1 if anything bad happened along the way.
//
// DESCRIPTION
//
//  The main routine just dispatches the functions that do all the work
//  for the build.  It checks the return codes from all the functions
//  to see if an error has occurredd.  An error at any point will 
//  stop the process.
//
//  Note that this build program really operates in two completely
//  different modes.  If it is building LIBs, it takes one path.  If
//  it is building EXEs, it takes a completely different approach.
//
//  The process goes like this:
//
//        Read in build parameters
//        Display the arguments on the screen.
//
//        If building EXE files:
//          Expand wild cards to make the list of files to process
//          Build EXE files using all the EXE lines in the INI file
//
//        If building LIB files:
//          Expand wild cards to make the list of files to process
//          Build all the config files to be used by the compiler/linker/etc.
//          Compile all eligible C, CPP, and ASM files in batch mode.
//          Compile all eligible C, CPP, and ASM files in one-at-a-time mode.
//          Build the library using all the LIB lines in the INI file.
//
//
//  REVISION HISTORY
//
//   May 18, 1994  2.0A      :  First release
//
//   January 5, 1955 3.0A    : New release with more capabilities
//
//   January 26, 1995  4.0A  : Major changes to simplify the code, and add
//                             to its capabilities.
//

int main( int argc, char *argv[] )
{
//
// These to auto variables are important.  files will contain a list of all
// the files to be processed during the build.  The algorithm that chooses
// which files go in the list is different depending on whether you are
// building EXEs or LIBs.
//
// opts holds all the option lines from the INI file, as well as some of the
// info regarding command line parameters.  The list of predefined options
// that are passed to the constructor are the *only* options that can be
// read into an INI file, with two exceptions.  The exceptions are the
// *LIB.CMD and *EXE.CMD lines.  You can add as many of those as you care
// too, as long as the names conform to one of these two schemes.
//
    file_list files;
    options opts( "Description",
                  "Directories",
                  "Template",
                  "Cpp",
                  "C",
                  "Asm",
                  "Lib",
                  "Exe",
                  "DeleteFiles",
                  "Opts",
                  "" );

// Symantec defaults to buffered output on cout and cerr.  Yuk.

    cout.rdbuf()->setbuf( (char*) 0, 0 );
    cerr.rdbuf()->setbuf( (char*) 0, 0 );
//
// These two functions are used to parse the command line, then read in the
// INI file.  Everything of importance that comes out of this process is
// stored in the opts object.
//
    if ( parse_args( argc, argv, opts ) == 0 )
        return 1;
    if ( read_ini_file( opts ) == 0 )
        return 1;
    dump_options( opts );
//
// If I am being asked to build demo programs, I first expand
// the list of files on the command line.  This is done using
// simple wild card expansion, I don't do any funny directory
// path manipulation.  This means that if you want to compile
// files from the examples directory, you have to type:
//
//  BUILD -exe L ..\ex_dos\ex*.c
//
    if ( opts.target == options::EXE ) {
        if ( build_exe_file_list( argc, argv, files ) == 0 )
            return 1;
        if ( build_exe_files( opts, files ) == 0 )
            return 1;
    }
//
// Things are completely different when building libraries.  Building
// the file list is done by looking in the targeted directories for
// each file type.  For example, if you want to build -lib s *.C, we
// might look in ..\C_DOS, ..\C_GSCI, ..\C_**, and ..\C_ALL for all
// of the *.C files.
//
// Building libraries also differs in that the config files are only created
// once, so we do it here.  In addition, we have two steps to build the
// library.  The first step involves compiling or assembling every one of
// the files in the list.  The second step involves turning those OBJs
// into a library, using all of the command lines specified in the
// various LIB= lines in the INI file.
//
    if ( opts.target == options::LIB ) {
        if ( build_lib_file_list( argc, argv, files, opts ) == 0 )
            return 1;
        build_lib_config_files( opts, files );
        if ( lib_batch_compiles( opts ) == 0 )
            return 1;
        if ( compile_lib_files( opts, files ) == 0 )
            return 1;
        if ( build_lib( opts ) == 0 )
            return 1;
    }
    if ( !opts.keep_files )
        cleanup( files, opts );
    return 0;
}

//
// int parse_args( int &argc, char **&argv, options &opts )
//
// ARGUMENTS:
//
//  argc      :  A reference to the argument count passed on the
//               command line.  One nice bonus of using C++ is that
//               using a reference argument makes it easy for the
//               parse routine to modify argc as it reads arguments,
//               then pass the modified value back to the calling
//               routine.
//
//  argv      :  A reference to the pointer to the array of command
//               line arguments.  Like argc, this value gets incremented
//               as we work our way through the command line.
//
//  opts      :  A reference to the options structure used by this program.
//               This routine has the job of initializing four members:
//               dry_run, keep_files, target, and model.
//
//
// RETURNS
//
//  1 if it is okay to keep going, 0 if a crummy command line got passed.
//
// DESCRIPTION
//
//  This routine is called to initialize four of the options used in the
//  build process.  dry_run is set if the user specifies -cmd.  keep_files
//  is set if -keep is found, and target is set to EXE if -exe is specified.
//  The library model is the next argument, it gets stored in a member of
//  opts, after being converted to upper case.
//
//  Note also that I strip any "." character from the name of a model.
//  The "." character in the first position just prevents the model
//  name from being displayed when the dump_sections() routine is called.
//
//  After parsing the one or two arguments, argc and argv will be updated
//  so the file name parsing can start right up.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A   : First release
//
//   January 5, 1995 3.0A : Added support for the -exe option.
//
//   January 26, 1995 4.0A : Added support for the -lib option.  I think to
//                           be downward compatible I need to make this
//                           guy use -lib as the default, instead of
//                           requiring it.
//

int parse_args( int &argc, char **&argv, options &opts )
{
    opts.dry_run = 0;
    opts.keep_files = 0;
    opts.ignore = 0;
    opts.target = options::LIB;
//
// Instead of blindly looking in BUILD.INI for the INI file, I look
// for an INI file that has the same name as this executable.  This
// makes it easy to have BUILDB31.EXE look for BUILDB31.INI, which
// helps keep our patch program happy.
//
    opts.ini_file = *argv;
    int i = opts.ini_file.last( '.' );
    if ( i != -1 )
        opts.ini_file.remove_after( i - 1 );
    opts.ini_file = opts.ini_file + ".INI";
    argc--;  // go past argv[ 0 ], which is the program name
    argv++;
//
// Now I strip any of the command line arguments, setting option flags
// accordingly.
//
    while ( argc >= 1 ) {
        if ( stricmp( *argv, "-cmd" ) == 0 ) {
            opts.dry_run = 1;
            argc--;
            argv++;
        } else if ( stricmp( *argv, "-keep" ) == 0 ) {
            opts.keep_files = 1;
            argc--;
            argv++;
        } else if ( stricmp( *argv, "-ignore" ) == 0 ) {
            opts.ignore = 1;
            argc--;
            argv++;
        } else if ( stricmp( *argv, "-exe" ) == 0 ) {
            opts.target = options::EXE;
            argc--;
            argv++;
        } else if ( stricmp( *argv, "-lib" ) == 0 ) {
            opts.target = options::LIB;
            argc--;
            argv++;
        } else if ( *argv[0]=='-' || *argv[0]=='/' ) {
            bstring *stored_value = opts.find_value( "Opts" );
            if ( stored_value ) {
                if ( strlen( *stored_value ) > 0 )
                    *stored_value= *stored_value + " ";
                *stored_value = *stored_value +*argv;
            }
            argc--;
            argv++;
        } else
            break;
    }
//
// Print the command line format if you didn't get anything good on the
// command line.  At a minimum, I expect to get a memory model, which
// would mean that argc would have to be 1 or more.
//
    if ( argc < 1 || opts.target == options::UNKNOWN ) {
        cerr << "\nBuild Version 4.1B\n";
        cerr << "Copyright (c) 1994, 1995 Greenleaf Software, Inc.\n";
        cerr << "Usage: Build -exe | -lib [-cmd] [-keep] model [file(s)]\n\n";
        cerr << "Options:\n";
        cerr << "-cmd    Create CMD files and quit\n";
        cerr << "-keep   Keep OBJ and CMD files after build\n";
        cerr << "-lib    Build a library or DLL\n";
        cerr << "-exe    Make an example or a demo, not a library\n";
        cerr << "-ignore Ignore EXE and OBJ files that already exist\n";
        cerr << "\n";
        cerr << "Any other command switches get added to the opts variable.\n";
        cerr << "\n";
        cerr << "Note:  If you are building a library, you shouldn't\n";
        cerr << "       include path names for your source files.  The\n";
        cerr << "       BUILD program will look in the appropriate\n";
        cerr << "       directories for the memory model you are building.\n";
        cerr << "       Building demo files requires a path to the file.\n";
        cerr << "\nIni file = " << opts.ini_file << "\n";
        cerr << "\nHit any key to continue...";
        getch();
        cerr << "\nModels:\n";
        dump_sections( opts );
        return 0;
    }
//
// The next argument should be the memory model.
//
    opts.model = *argv;
    opts.model.upper();
    argc--;
    argv++;
    return 1;
}

//
// void dump_sections( options &opts )
//
//
// ARGUMENTS:
//
//  opts       :  A reference to the options object.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  This function is called when parse_args() determines that that user
//  didn't put anything on the command line.  All it does is blow through
//  the INI file, printing the name of each section it encounters
//  along the way.  Hidden sections are denoted by a leading ".".  They
//  don't get displayed, but they are treated as a normal section
//  at all times.
//
// REVISION HISTORY
//
//   May 19, 1994     2.0A : First release
//
//   January 5, 1995  3.0A : Added hidden sections
//

void dump_sections( options &opts )
{
    bstring current_description;
    bstring section;

#ifdef __SC__
    fstream config_file( opts.ini_file, ios::in );
#else
    fstream config_file( opts.ini_file, ios::in | ios::binary );
#endif
    if ( config_file.fail() ) {
        cerr << "\nError - Could not open "
             << opts.ini_file
             << "!\n";
        return;
    }
    int first_in_section = 1;
    for ( ; ; ) {
        switch ( get_next_section( section, config_file ) ) {
            case NONE :
                cerr << endl; //For the last section
                return;
            case HIDDEN :
                break;
            case NORMAL :
                read_options( opts, config_file, ""  );
                bstring *d = opts.find_value( "Description" );
                bstring new_description = *d;
                int index = new_description.first( ',' );
                if ( index >= 0 )
                    new_description.remove_after( index - 1 );
                if ( !( new_description == current_description ) ) {
                    cerr << endl;
                    cerr.width( 29 );
// I don't know why, but with Symantec I have to do more than
// just set left, I have to clear right!!
                    cerr.setf( ios::left );
                    cerr.unsetf( ios::right );
                    cerr << new_description;
                    cerr << ": ";
                    current_description = new_description;
                    first_in_section = 1;
                }
                if ( !first_in_section )
                    cerr << ", ";
                cerr << section;
                first_in_section = 0;
                opts.erase_values();
                break;
        }
    }
}

//
// int read_ini_file( options &opts )
//
// ARGUMENTS:
//
//  opts      :  A reference to the options parameter that is used
//               throughout the program.  The options stucture has
//               a linked list of elements, each of which a tag
//               name and a value.  This routine reads those elemeents
//               out of BUILD.INI.
// RETURNS
//
//  1 if it is okay to keep going, 0 if a crummy command line got passed.
//
// DESCRIPTION
//
//  This routine opens up BUILD???.INI, then scans until it finds a section
//  with the same name as the memory model specified on the command line.
//  If it doesn't find that section, it is considered an error, and a 0
//  is immediately returned to main().
//
//  If the correction section is found, read_options() is then called to
//  read all of the options into the options array.  read_options() takes
//  care of expanding any templates that are encounterd in the options
//  file.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A   : First release
//
//   January 5, 1995 3.0A : Updated to support templates
//
//   January 26, 1995  4.0A : Removed template support from this section,
//                            since it is now handled in the read_options()
//                            routine.
//

int read_ini_file( options &opts )
{
    bstring section;

#ifdef __SC__  // Symantec doesn't define binary, don't know how they do it, don't care
    fstream config_file( opts.ini_file, ios::in );
#else
    fstream config_file( opts.ini_file, ios::in | ios::binary );
#endif
    if ( config_file.fail() ) {
        cerr << "\nError - Could not open "
             << opts.ini_file
             << "!\n";
        return 0;
    }
    if ( !read_options( opts, config_file, opts.model ) )
        return 0;
    else
        return 1;
}

//
// SECTION_TYPE get_next_section( bstring &section, fstream &file )
//
//
// ARGUMENTS:
//
//  section    :  The string where we return the name of the next section
//                after discovering it.
//
//  file       :  A reference to the open fstream object.
//
// RETURNS
//
//  Returns a SECTION_TYPE enum: NONE, HIDDEN, or NORMAL, depending
//  on what type of section we found.  Note that a section becomes
//  a hidden section if the first character of its name is ".".  If
//  that is the case, we strip the leading period here, so it looks
//  to the rest of the world as if it has a normal name.  In other words,
//  when I read in section [.DOS], I return HIDDEN, but switch the
//  name to DOS.  (The brackets get stripped too!)
//
//  Note that the important thing about this routine is that it seeks
//  to the appropriate position in the file, leaving the input pointer
//  pointing directly to the first key value to be read in.
//
// DESCRIPTION
//
//  This utility function is used by read_ini_file() and dump_sections().
//  All it does is advance through the file until it reaches the next
//  section.  A section in BUILD???.INI is defined as a line that contains
//  nothing except "[string]".
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A   : First release
//
//   January 5, 1995 3.0A : Added support for hidden sections
//

SECTION_TYPE get_next_section( bstring &section, fstream &file )
{
    char buffer[ 129 ];
    for ( ; ; ) {
        file.getline( buffer, 129 );
        if ( file.fail() || file.eof() ) {
            section = "";
            file.clear( 0 );
            return NONE;
        }
        section = buffer;
//
// I had to open this file in binary mode because of some problems
// with Microsoft C++.  Since it is in binary mode, I get an extra '\r'
// which needs to be nuked.
//
        int index = section.first( '\r' );
        if ( index >= 0 && index == ( section.length() - 1 ) )
            section.remove_after( index - 1 );
        if ( section.first( '[' ) != 0 )
            continue;
        if ( section.last( ']' ) != ( section.length() - 1 ) )
            continue;
        section.remove_before( 1 );
        section.remove_after( section.length() - 2 );
//
// Detect and mung a hidden section
//
        if ( section[ 0 ] == '.' ) {
            section.remove_before( 1 );
            return HIDDEN;
        }
        return NORMAL;
    }
}

//
// int read_options( options &opts, fstream &file, const bstring& section )
//
// ARGUMENTS:
//
//  opts      :  A reference to the options parameter that is used
//               throughout the program.  The options stucture has
//               a linked list of elements, each of which a tag
//               name and a value.  This routine reads those elements
//               out of BUILD.INI.
//
//  file      :  A reference to an open fstream connected to BUILD.INI.
//               This routine is called by read_ini_file(), and it is
//               that function's job to make sure that the input pointer
//               is sitting just at the right spot.  In this case the
//               spot should be right at the start of the first line
//               of text immediately following the desired section.
//
//  section   :  A reference to the section whose options I am supposed
//               to read.  If this is a blank string, it means I should
//               just read in the options starting at the current location
//               in the file.  This is how it would work, for example, when
//               I have just called get_next_section(), and I know that I
//               am at the right place.  If a valid section name is here,
//               I instead seek back to the start of the file and start
//               hunting for the appropriate section.  This is what happens
//               when parse_options() is trying to expand a template.
//
// RETURNS
//
//  1 if it is okay to keep going, 0 if a crummy command line got passed.
//  Note that when this function returns after succesfully reading in a list
//  of options, it will leave the input file pointer exactly where it was
//  when it got called.
//
//  This business of leaving things right were they were is very important,
//  because this function may call itself recursively.  When it reads in
//  a line of text and passes it to parse_option(), it may be a Template=
//  key line.  If this is the case parse_option() will recursively call
//  read_options() to read in the options for *that* section.  When it
//  finally returns, parse_option() will come back here leaving me in the
//  dark as to all that really went on.  Since I'm not aware that anything
//  funny happened, I better not have to deal with an input file pointer
//  that has moved around.
//
// DESCRIPTION
//
//  This function is called by read_ini_file() during the startup of
//  BUILD.  Its job is read in all of the options defined for the current
//  section.  It doesn't attempt to find the correct section, that
//  has to be done by read_ini_file() before this guy gets called.
//
//  This function is actually a little lazy also.  It reads in lines
//  from the INI file, checks to make sure they aren't comments or the
//  start of a new section, then calls parse_option() to do the
//  real work.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A   : First release
//
//   January 5, 1995 3.0A : Unchanged in this release.  Normally this would
//                          pass without comment, but so much other stuff
//                          changed that I thought I should mention it.
//
//   January 26, 1995  4.0A : Modified to save and restore the file pointer
//                            settings when called.  This lets me be
//                            called recursively when expanding templates.

int read_options( options &opts,
                  fstream &file,
                  const bstring &target_section )
{
    bstring line;
    bstring section;
    char buffer[ 257 ];

//
// Save the current location in the file, so I can go back to it on exit.
//
    long saved_spot;
    saved_spot = file.tellg();
//
// If I am looking for a specific section, I go back to the start of
// the file.  If I wasn't asked for a specific section, I just assume
// I can start reading options right where I am at this moment, in which
// case I don't have to do any hunting.
//
    if ( target_section != "" ) {
        file.rdbuf()->seekoff( 0, ios::beg, ios::in );
        for ( ; ; ) {
//
// I repeatedly call get_next_section() until it returns a match to the
// section I am looking for.  If I get to the end of the file and
// haven't found it, flag this as an error.
//
            if ( get_next_section( section, file ) == NONE ) {
                cerr << "\nError - Couldn't find the section for model "
                     << target_section
                     << " in "
                     << opts.ini_file
                     << "!\n";
                return 0;
            } else if ( section == target_section )
                break;
        }
    }
//
// Now that I am at the correct section, I can just sit in a loop,
// reading in lines, and processing them.
//
    for ( ; ; ) {
        file.getline( buffer, 257 );
        if ( file.fail() || file.eof() ) {
            file.clear();
            file.rdbuf()->seekoff( saved_spot, ios::beg, ios::in );
            return 1;
        }
        line = buffer;
//
// I had to open this file in binary mode because of some problems
// with Microsoft C++.  Since it is in binary mode, I get an extra '\r'
// at the end of the line which needs to be nuked.
//
        int index = line.first( '\r' );
        if ( index >= 0 && index == ( line.length() - 1 ) )
            line.remove_after( index - 1 );
//
// If this is the start of a new section, I have read in all the
// options I am going to get, so I can quit.
//
        if ( line[ 0 ] == '[' ) {
            file.rdbuf()->seekoff( saved_spot, ios::beg, ios::in );
            return 1;
        }
// A comment or a blank line?
        if ( line[ 0 ] == ';' || line[ 0 ] == '\0' )
            continue;
// This line looks good, I call somebody else to do the parsing.
        if ( parse_option( opts, line, file ) == 0 )
            return 0;
    }
}

//
// int parse_option( options &opts, bstring &line )
//
// ARGUMENTS:
//
//  opts      :  A reference to the options parameter that is used
//               throughout the program.  The options stucture has
//               a linked list of elements, each of which a tag
//               name and a value.  This routine sets up one of those
//               elements based on what it finds in the command line
//               passed to it by read_options().
//
//  line      :  An actual line of input that has been read in from the
//               INI file.  It better be of the form "tag=value", or this
//               routine is going to complain and return an error.
//
// RETURNS
//
//  0 if the input line looked bad, 1 if it looked okay.  Note that if
//  this guy doesn't like an input line, it will return a value of
//  0, and the whole process will shut down.
//
// DESCRIPTION
//
//  This routine is a specialist at one thing only.  It takes a line that
//  somebody else took the trouble of reading in from the INI file, and
//  divides it into a tag and value.  It then inserts that tag and value
//  into the opts structure, so that anyone who wants to can take a gander
//  at it later in the program.
//
//  This function makes use of many member functions from the bstring
//  and options classes to do its work.  If they look a little strange,
//  you can view the class descriptions in BUILD.H.
//
//  In release 2.0B, I added support for multiple lines in a single command.
//  This was done to give me more control over the Link/Implib process
//  in CommLib.  You put multiple commands together on one line, separated
//  by a single caret ('^').  In this release, some of my Demo lines
//  were getting really long, and unreadable.  So in 3.0, I added
//  a new way to enter multiple lines.  You can now include multiple
//  lines by repeating definitions of the the same value in a section.
//  As each new value is encountered,
//  it gets appended to the existing value, with a caret to separate them.
//  Now multiple lines appear in the order to are going to execute them.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A    : First release
//
//   January 5, 1995  3.0A : New release supports multiple lines per command.
//
//   January 26, 1995  4.0A : Added the ability to recursively call
//                            read_options() when I encounter a template
//                            definition.  Note that it doesn't do the
//                            recursive call if opts.model == "", which
//                            means we are just displaying the options
//                            on the screen, since the user didn't specify
//                            a memory model.
//

int parse_option( options &opts, bstring &line, fstream& file )
{
//
// This code divides the line into two parts, a key and a value.
// The '=' is the dividing point.  If the line doesn't divide
// up properly, I return an error message.
//
    int divide = line.first( '=' );
    if ( divide <= 0 ) {
        cerr << "\nError - Badly formed option line in "
             << opts.ini_file
             << ": "
             << line
             << "\n";
        return 0;
    }
    bstring key = line;
    key.remove_after( divide - 1 );
    bstring value = line;
    value.remove_before( divide + 1 );
//
// This is a new piece of code.  If it turns out that the key is "Template",
// it means I now need to read in all the values for that template.  The
// name of the template is stored in the value, so I just pass it to
// read_options().  This is a recursive call, so everyone needs to be
// careful!!!
//
    if ( key == "Template" && opts.model != "" )
        return read_options( opts, file, value );
//
// Now that I have a new value, I need to store it in the options argument.
// I have to check to see if there is a stored_value already for my key.
// I'm not allowed to add new keys to the options unless they are CMD file
// definitions.  So when I check on the stored value, I better get something
// back, even if it is an empty string.  If I get a null pointer, it means
// that the key isn't in the opts container.  If this is the case, the
// key better be "*LIB.CMD or "*EXE.CMD", or I flag an error.
//
    bstring *stored_value = opts.find_value( key );
    if ( stored_value == 0 ) {
        if ( key.first( "LIB.CMD" ) < 0 && key.first( "EXE.CMD" ) < 0 ) {
            cerr << "\nError - Unknown option found in "
                 << opts.ini_file
                 << ": "
                 << line
                 << "\n";
            return 0;
        } else {
            opts.add_option( key, "" );
            stored_value = opts.find_value( key );
        }
    }
//
// If I already have this description on file, I am going to append it
// to the existing line.  I use the "^" symbol to separate lines
//
    if ( *stored_value != "" )
        *stored_value = *stored_value + "^" + value;
    else
        *stored_value = value;
    return 1;
}

//
// int build_lib_file_list( int argc,
//                      char **argv,
//                      file_list &files,
//                      options& opts )
//
//
// ARGUMENTS:
//
//  argc      :  A count of the arguments left after the command
//               line has been partially parsed.
//
//  argv      :  A pointer to the array of strings that are the
//               arguments passed on the command line.
//
//  files     :  This is a reference to the structure that contains
//               the list of files that are going to be compiled.
//
//  opts      :  A reference to the options structure for this build.
//               By the time this function gets called, the entire
//               option list will have been filed out.  I need to know
//               about it here because it contains the directory list.
//
// RETURNS
//
//  0 if no files were added to the file list.  1 if some were.  main()
//  considers a value of 0 to be a good reason to abort.
//
// DESCRIPTION
//
//  This routine is called by main() after it has finished parsing the
//  INI file.  Its job is to go through the list of files on the command
//  line and add them all to the file list object.  It does this by
//  taking each of the file name arguments (which may or may not contain
//  wildcards), and checking each of the defined directories for the
//  presence of files that match the spec.  The actual expansion of the
//  wildcard and its addition to the file list is accomplished in
//  add_wild_cards().
//
//  Note that if the user doesn't specify *any* file names on the command
//  line, we go ahead and parse "*.CPP", "*.C", and "*.ASM".  This is so
//  the build program can be run with even less thinking than before.
//
//  REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int build_lib_file_list( int argc, char **argv, file_list &files, options& opts )
{
    char *default_files[ 10 ];
    bstring default_directories( "ALL" );
    bstring *dirs = opts.find_value( "Directories" );
    if ( dirs == 0 )
        dirs = &default_directories;
    if ( argc == 0 ) {
        bstring *s;
        s = opts.find_value( "Cpp" );
        if ( s != 0 && strlen( *s ) > 0 )
            default_files[ argc++ ] = "*.CPP";
        s = opts.find_value( "C" );
        if ( s != 0 && strlen( *s ) > 0 )
            default_files[ argc++ ] = "*.C";
        s = opts.find_value( "Asm" );
        if ( s != 0 && strlen( *s ) > 0 )
            default_files[ argc++ ] = "*.ASM";
        argv = default_files;
    }
//
// This loop is where I expand wildcards for each of the specified files,
// in each of the specified directories.
//
    for ( int i = 0 ; argc ; i++ ) {
        bstring ext = bstring( *argv ).ext();
        ext.remove_before( 1 );
        bstring search_dirs = *dirs;
        bstring dir;
        for ( dir = search_dirs.get_first_token("^ ");
              dir != "";
              dir = search_dirs.get_next_token("^ ") ) {
//
// Microsoft 8.0 chokes on this constructor, had to replace it with the
// alternate one below it.
//
//          bstring wild_guy( bstring( ROOT_DIR ) + ext + "_" + dir + "\\" + *argv );
            bstring wild_guy = bstring( ROOT_DIR ) + ext + "_" + dir + "\\" + *argv;
            add_wild_cards( files, wild_guy );
        }
        argv++;
        argc--;
    }
    if ( files.get_first_file() == 0 ) {
        cerr << "\nError - No files found to build!\n";
        return 0;
    }
    return 1;
}

//
// int build_exe_file_list( int argc,
//                           char **argv,
//                           file_list &files )
//
// ARGUMENTS:
//
//  argc      :  A count of the arguments left after the command
//               line has been partially parsed.
//
//  argv      :  A pointer to the array of strings that are the
//               arguments passed on the command line.
//
//  files     :  This is a reference to the structure that contains
//               the list of files that are going to be compiled.
//
// RETURNS
//
//  0 if no files were added to the file list.  1 if some were.  main()
//  considers a value of 0 to be a good reason to abort.
//
// DESCRIPTION
//
//  In release 3.0, we added the ability to build demo programs.  When
//  building libraries, we have a function just like this that is used
//  to build a list of files.  Unfortunately, we have to have a specialized
//  version of this function for building demos, because we specify files
//  in a slightly different way.  When building libraries, we always
//  specify files without a path name, like "STORCMP.CPP", and let BUILD
//  figure out which directories to search.  With demos it is different,
//  because we give a full path name.  So I have to perform a simpler
//  version of the wild card expansion in this routine.
//
//  REVISION HISTORY
//
//   January 5, 1995  3.0A : First release
//

int build_exe_file_list( int argc, char **argv, file_list &files )
{
    if ( argc == 0 ) {
        cerr << "\nError - No files found to build!\n";
        return 0;
    }
//
// This loop is where I expand wildcards for each of the specified files.
//
    for ( int i = 0 ; argc ; i++ ) {
        bstring wild_guy = *argv;
        add_wild_cards( files, wild_guy );
        argv++;
        argc--;
    }
//
// After it is all done, I check to see if I really got any files.
//
    if ( files.get_first_file() == 0 ) {
        cerr << "\nError - No files found to build!\n";
        return 0;
    }
    return 1;
}

//
// void add_wild_cards( file_list &files, const bstring &wild_spec )
//
// ARGUMENTS:
//
//  files     :  A reference to a file list structure.  The file list
//               is simply a linked list of file names.  My job here
//               is to expand the wild card I get passed and add the
//               names to this guy.
//
//  wild_spec :  This is the wild card specification, although it is not
//               necessarily a wild card, it might just be a name.
//
// RETURNS
//
//  None.
//
// DESCRIPTION
//
//  The user passes a list of file names on the command line, after
//  the model.  The names can be things like *.CPP and BOB.C.  The
//  routine that builds this file list takes each of those names,
//  and prepends each of the directories to it.  Then it calls this
//  guy to expand the resulting wild card specification and add it to
//  the file list structure.  Adding to the structure is done via
//  a nice simple member function.
//
//  So if the guy types BUILD -lib S *.CPP, this function might get called
//  three times, with the names "..\CPP_ALL\*.CPP", "..\CPP_DOS\*.CPP"
//  and "..\CPP_FOO\*.CPP".  I could add anywhere from zero to a zillion
//  file names to the list as a result;
//
//  Note that this function is called by both the LIB thread of execution
//  and the EXE path.  In the case of the EXE path, no path names are
//  prepended to the file names, so we get a straightforward expansion.
//
//
//  REVISION HISTORY
//
//   May 18, 1994  2.0A  : First release
//

#ifdef __OS2__
#define INCL_DOSFILEMGR
#include <os2.h>

void add_wild_cards( file_list &files, const bstring &wild_spec )
{
    APIRET rc;
    HDIR handle;
    FILEFINDBUF3 buffer = { 0 };
    ULONG count;

    handle = 1;
    count = 1;
    rc = DosFindFirst( wild_spec,
                       &handle,
                       0,
                       (PVOID) &buffer,
                       sizeof( buffer ),
                       &count,
                       FIL_STANDARD );
    bstring drive_and_path = wild_spec.drive() + wild_spec.path();
    while ( rc == 0 ) {
        bstring new_name = drive_and_path + buffer.achName;
        files.add_file( new_name );   //Handy member function of class file_list
        count = 1;
        rc = DosFindNext( handle,
                          (PVOID) &buffer,
                          sizeof( buffer ),
                          &count );
    }
}
#else
void add_wild_cards( file_list &files, const bstring &wild_spec )
{
    struct find_t ffblk;
    unsigned int result;
//
// Because of the way DOS expands names I need to break out the
// drive and path separately.  So, if I am expanding C:\BOB\*.CPP,
// I want to keep a copy here of C:\BOB\.  Then, as I expand the
// *.CPP part, I pack the resuling 8.3 name to the drive and path
// and voila, I have a complete filespec.
//
    bstring drive_and_path = wild_spec.drive() + wild_spec.path();

// The funny cast of wild_spec is to accomodate Symantec C.  They
// foolishly use char * instead of const char * for this arg.

    result = _dos_findfirst( (char *) (const char *) wild_spec, 0, &ffblk );
    while ( result == 0 ) {
        bstring new_name = drive_and_path + ffblk.name;
        files.add_file( new_name );   //Handy member function of class file_list
        result = _dos_findnext( &ffblk );
    }
}
#endif
//
// void process( bstring &result,
//               const char *command_string,
//               const bstring &file_name,
//               options &opts )
//
//
// ARGUMENTS:
//
//  result         :  A reference to an output string.  Since the dorky
//                    little string class I built for this program doesn't
//                    have good functions for adding characters one at at
//                    time, I build this result in a temporary buffer, then
//                    copy it over before returning.  Warning!  the output
//                    buffer is big, so I made it static!
//
//  command_string :  A pointer to the command string.  This is the string
//                    that was actually read out of the INI file.  If there
//                    were multiple commands in one line, this guy is just
//                    one of them that has been separated out.  This command
//                    line should be chock full of special formatting
//                    arguments such as %file%, %model%, etc.
//
//  file_name      :  The name of the source file being affected by this
//                    command.  This argument is not always meaningful.
//
//  opts            :  This structure contains all of the options that have
//                     been read out of the INI file.  I need to look at these
//                     because I need to get the option value for this particular
//                     CMD file out of this structure.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//   This routine takes a command line and processes it.  Processing means
//   that it takes all the special formatting arguments and then
//   substitutes the appropriate values in the output string.  The command
//   is built up and stored in an output string.  The following formatting
//   codes are supported: (see top of file for more detail)
//
//  %file%          :  The complete name of the source file.
//
//  %file.path%     :  The drive and path of the file, with a guaranteed
//                     '\' character.
//
//  %file.name%     :  The name component of the source file, minus an
//                     extension.
//
//  %model%         :  The model/library name, as found inside the "[]".
//
//  %model.lower%   :  The first letter of the model name, in lower case.
//
//  %model.upper%   :  The first letter of the model name, in upper case.
//
//  %model.asm%     :  A greenleaf model name for ASM files.
//
//  %nl%            :  Inserts a new line into the output stream,
//                     literally a '\n';
//
//  %%              :  Inserts the literal '%' character.
//
//  %comspec%       :  Inserts the name of the current command processor.
//
//  %continue%      :  This means the character immediately following
//                     the %continue% is a continuation character.  If
//                     there are more files coming after this one, we
//                     output the continuation character.  Otherwise,
//                     it gets eaten.
//
//  REVISION HISTORY
//
//   January 5, 1995
//

void process( bstring &result,
              const char *command_string,
              const bstring &file_name,
              options &opts,
              int more_to_come )
{
    const char *p;
    static char _temp[ 1024 ];
    char *buffer = _temp;
    bstring path = file_name.drive() + file_name.path();
    bstring name = file_name.name();

    while ( *command_string ) {
        if ( *command_string == '%' ) {
            command_string++;
            bstring tag = command_string;
            p = strchr( command_string, '%' );
            if ( !p ) {
                cerr << "Missing closing '%' in command line: "
                     << command_string
                     << endl;
                exit( -1 );
            }
            tag.remove_after( p - command_string - 1 );
            command_string = p + 1;
            if ( tag == "file" || tag == "*.c" || tag == "*.cpp" || tag == "*.asm" )
                for ( p = file_name ; *p != 0 ; )
                    *buffer++ = *p++;
            else if ( tag == "file.path" )
                for ( p = path ; *p != 0 ; )
                    *buffer++ = *p++;
            else if ( tag == "file.name" )
                for ( p = name ; *p != 0 ; )
                    *buffer++ = *p++;
            else if ( tag == "model" )
                for ( p = opts.model ; *p != 0 ; )
                    *buffer++ = *p++;
            else if ( tag == "model.upper" )
                *buffer++ = toupper( opts.model[ 0 ] );
            else if ( tag == "model.lower" )
                *buffer++ = tolower( opts.model[ 0 ] );
            else if ( tag == "nl" )
                *buffer++ = '\n';
            else if ( tag == "" )
                *buffer++ = '%';
            else if ( tag == "comspec" ) {
                p = getenv( "COMSPEC" );
                if ( !p )
                    p = "COMMAND.COM";
                for ( ; *p != 0 ; )
                    *buffer++ = *p++;
            } else if ( tag == "model.asm" ) {
                switch( tolower( opts.model[ 0 ] ) ) {
                    case 's' : p = "SMALL"; break;
                    case 'c' : p = "COMPACT"; break;
                    case 'm' : p = "MEDIUM"; break;
                    case 'l' : p = "LARGE"; break;
                    case 'h' : p = "HUGE"; break;
                    default  : p = "UNKNOWN"; break;
                }
                for ( ; *p != 0 ; )
                    *buffer++ = *p++;
            } else if ( tag == "continue" ) {
                if ( !more_to_come )
                    command_string++;
            } else if ( tag == "Opts" ) {
                bstring *def_value = opts.find_value( "Opts" );
                for ( p = *def_value ; *p != 0 ; )
                    *buffer++ = *p++;
            } else {
                cerr << "Unrecognized tag in INI file: "
                     << tag
                     << endl;
                exit( 1 );
            }
        } else
            *buffer++ = *command_string++;

    }
    *buffer = '\0';
    result = _temp;
}

//
// void build_lib_config_files( options &opts, file_list &files )
//
// ARGUMENTS:
//
//  opts      :  This structure contains all of the options that have
//               been read out of the INI file.  I need to look at these
//               options so I can figure out which CMD files need to be
//               built.
//
//  files     :  A list of all the files that are going to get compiled.
//               I need this list so that I can build LIB.CMD, as well as
//               any compiler response files that get wild cards expanded.
//
// RETURNS
//
//  None.
//
// DESCRIPTION
//
//  Sometimes compiler or link commands get too big for the command line.
//  When this happens, we have to put options into a response file, and
//  execute something like this:  CC blah blah @CCLIB.CMD file.c.
//
//  The stuff that goes into the CMD file is stored in BUILD.INI.  It
//  is always stored in a tag with a name like xxxLIB.CMD.  This routine
//  finds every option tag whose name ends with "LIB.CMD".  It then dumps
//  the value of that tag into a file called "xxxLIB.CMD.
//
//  There is one real tricky bit in here.  If one of the lines in the CMD
//  file contains a %filexxxx% keyword, I repeatedly output that line for
//  each and every file in the file list.  This is how the LIB.CMD file
//  gets built, for example.  If the CMD file instead contains "*.ASM",
//  "*.C", or "*.CPP", we expand just those guys.
//
//  REVISION HISTORY
//
//   May 18, 1994  2.0A  : First release
//
//   January 31, 1195 4.0B : Modified to expand wild cards like "*.C".
//                           Since these files are only built once at
//                           the start of a compile cycle, this wasn't a
//                           a useful feature up until the time I addes
//                           support for batch compilation.
//


void build_lib_config_files( options &opts, file_list &files )
{
    for ( item *option = opts.get_first_item();
          option != 0;
          option = option->get_next_item() ) {
        if ( option->name.first( "LIB.CMD" ) >= 0 ) {
            fstream config_file( option->name, ios::out );
            bstring command_string;
            command_string = option->value.get_first_token( "^" );
            while ( command_string != "" ) {
                bstring parsed_string;
                if ( command_string.first( "%*." ) >= 0 )
                    build_wild_card_line( opts, files, config_file, command_string );
                else if ( command_string.first(  "%file" ) >= 0 ) {
                    for ( bstring *file = files.get_first_file() ;
                          file != 0; ) {
                        bstring *temp = file;
                        file = files.get_next_file();
                        process( parsed_string, command_string, *temp, opts, file != 0 );
                        config_file << parsed_string << endl;
                    }
                } else {
                    process( parsed_string, command_string, "XXXXXXXX.XXX", opts, 0 );
                    config_file << parsed_string << endl;
                }
                command_string = option->value.get_next_token( "^" );
            }
        }
    }
}

//
// void build_wild_card_line( options &opts,
//                            file_list &files,
//                            fstream &config_file,
//                            bstring &command_string )
//
// ARGUMENTS:
//
//  opts      :  This structure contains all of the options that have
//               been read out of the INI file.  I need to look at these
//               options so I can figure out how to build this cmd file.
//
//  files     :  A list of all the files that are going to get compiled.
//               I need to search through this list to find the files that
//               match the wild card specification, and output a parsed line
//               to the config file for each one.
//
//  config_file    : The open config file.  I will be writing out to this file.
//
//  command_string : The string that contains the wild card.  I have to process
//                   this string and write it out once per matching file.
// RETURNS
//
//  None.
//
// DESCRIPTION
//
//  Sometimes I want to perform an assembly or compilation in batch mode.
//  When this is the case, instead of calling BCC once for every file,
//  I just call it once with a line like this:  BCC blah blah @CLIB.CMD.
//  This means that CLIB.CMD has to have a list of files in it.  To put
//  a list of files in a CMD file, you could just put "%file.name%" or
//  one of the other "%file%" tages.  But that would put *all* the files
//  in their, and I just want the "*.c", or "*.asm", or whatever.
//
//  So instead, I put exactly that wildcard specification in the definition
//  for my CMD file: "*.CPP", or "*.C", or "*.ASM".  When a line like this
//  is encountered, build_lib_config_files() calls this routine to expand
//  the line and write it out to the output file.
//
//  This routine then has to go through the list, and select every file that
//  matches the extension of the wild card.  Each of those files gets
//  processed into the output line, and added to the config file.
//
//  If even one file gets added to the config file, the compiler for that
//  type of file gets set to run in BATCH mode.
//
//  Note that this guy also checks to see if any objects exist for each
//  particular file, preventing us from performing compilations that
//  don't need to be done.
//
//  REVISION HISTORY
//
//   January 31, 1995  4.0B : First release.
//

void build_wild_card_line( options &opts,
                           file_list &files,
                           fstream &config_file,
                           bstring &command_string )
{
//
// First I need to isolate the wild card extension.  I remove the leading
// "*.", then the trailing index.  When that is all done, I should have
// a file extension, as in "C", "CPP", or "ASM".  I put that into bstring
// extension for clarity, and use it throughout this routine.
//
    bstring wild_card = command_string;
    int index = command_string.first( "%*." );
    wild_card.remove_before( index + 2 ); // delete % and *
    index = wild_card.first( "%" );
    if ( index < 0 ) {
        cerr << "Failed to find matching % in config file definition!" << endl;
        exit( 1 );
    } else
        wild_card.remove_after( index - 1 );
    bstring extension = wild_card;
    extension.remove_before( 1 );
//
// I need to find the command for this extension.  If I match up any files
// with the wildcard, I am going to switch the command to BATCH mode.  If I
// can't find a command to match this wildcard, something is wrong!
//
    item *option = opts.find_item( extension );
    if ( option == 0 ) {
        cerr << "No commands defined for " << wild_card << endl;
        exit( 1 );
    }
//
// Now I just sit in a loop and get files that have the same extension as
// the wildcard.  Each one I find is going to get stuffed out into the CMD
// file via the process() routine, unless an object already exists.  If
// even one file gets added to the config file, I will set the compiler to
// run in batch mode.
//
    for ( bstring *file = files.get_first_file(); file != 0; ) {
        bstring parsed_string;
        bstring *temp = file;
        file = files.get_next_file();
        if ( temp->ext() == wild_card ) {
            if ( !opts.ignore && target_file_exists( *temp, ".OBJ" ) ) {
                cerr << "Skipping " << *temp << endl;
            } else {
                process( parsed_string, command_string, *temp, opts, file != 0 );
                config_file << parsed_string << endl;
                option->run_mode = item::BATCH;
            }
        }
    }
}
//
// void build_exe_config_files( options &opts, bstring &file )
//
// ARGUMENTS:
//
//  opts      :  This structure contains all of the options that have
//               been read out of the INI file.  I need to look at these
//               options so I can figure out which CMD files need to be
//               built.
//
//  file      :  The name of the file that is going to be turned into an
//               exe.  Note that this is different from building config
//               files for libraries.  This is because I build a new config
//               file for each and every EXE I build.  With libraries, I
//               only build a single set of config files one time.     .
//
// RETURNS
//
//  None.
//
// DESCRIPTION
//
//  Sometimes compiler or link commands get too big for the command line.
//  When this happens, we have to put options into a response file, and
//  execute something like this:  CC blah blah @CCEXE.CMD file.c.
//
//  The stuff that goes into the CMD file is stored in BUILD.INI.  It
//  is always stored in a tag with a name like "xxxxEXE.CMD".  This routine
//  finds every option tag whose name ends with "EXE.CMD".  It then dumps
//  the value of that tag into a file called xxxEXE.CMD.
//
//  REVISION HISTORY
//
//   January 26, 1995 4.0A : Brand new!

void build_exe_config_files( options &opts, bstring &file )
{
    for ( item *option = opts.get_first_item();
          option != 0;
          option = option->get_next_item() ) {
        if ( option->name.first( "EXE.CMD" ) >= 0 ) {
            fstream config_file( option->name, ios::out );
            bstring command_string;
            command_string = option->value.get_first_token( "^" );
            while ( command_string != "" ) {
                bstring parsed_string;
                process( parsed_string, command_string, file, opts, 0 );
                config_file << parsed_string << endl;
                command_string = option->value.get_next_token( "^" );
            }
        }
    }
}

//
// void dump_options( options &opts )
//
//
// ARGUMENTS:
//
//  opts       :  This structure contains all of the options that have
//                been read out of the INI file.  They are what gets
//                printed out in this routine.
// RETURNS
//
//  None.
//
// DESCRIPTION
//
//  This routine is called by main() to display all of the current options
//  to the user before starting to compile.  All of the options are stored
//  in the opts array, and I just print them one at a time.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A    : First release
//
//   January 5, 1995  3.0A : Modified the code so that I don't spill
//                           over the end of the line if an option line
//                           is really long.  Instead, I just print some
//                           dots at the end of the line.
//

void dump_options( options &opts )
{
    cout.setf( ios::left );
//
// Symantec C++ does really funny things if you don't clear ios::right
//
    cout.unsetf( ios::right );
    cout.width( 15 );
    cout << "Model" << " : " << opts.model << "\n";
    for ( item *citem = opts.get_first_item() ;
          citem != 0;
          citem = citem->get_next_item() ){
        bstring token = citem->value.get_first_token( "^" );
        int i = 0;
        while ( token != "" ) {
            cout.width( 15 );
            if ( i++ == 0 )
                cout << citem->name;
            else
                cout << "";
            cout << " : ";
            if ( strlen( token ) > 59 ) {
                token.remove_after( 57 );
                token = token + "...";
            }
            cout << token << "\n";
            token = citem->value.get_next_token( "^" );
        }
    }
    cout << "\n";
}

//
// int compile_lib_files( options &opts, file_list &files )
//
//
// ARGUMENTS:
//
//  opts       :  This structure contains all of the options that have
//                been read out of the INI file.  I need them here to
//                figure out how to compile each file.
//
//  files      :  The massive list of files.
//
// RETURNS
//
//  0 if any error occurs when compiling, 1 if every single file compiled
//  without error.
//
// DESCRIPTION
//
//  This routine is called by main() to compile all the files in the file
//  list.  The term "compiler" is used kind of loosely, because we don't
//  really care whether it is a compiler, assembler or what.
//
//  The way it works is that we step through the file list, processing files
//  one at a time.  Each file has a compile command, which is in the tag
//  specified by the file extension.  So, for example, if I want to compile
//  ..\CPP_ALL\KERMIT.CPP, I look up the tag CPP, and execute the command
//  stored there.  There are a few helper functions that help me accomplish
//  this.  One of the most important ones is execute(), because it has to
//  break the command down into tokens and stuff them into an argv[] array
//  before callling spawnv().
//
//  With the addition of BATCH compiles in 4.0, I have one additional
//  requirement before building a file.  As I process each file, I have
//  to check to see if the compiler is set to BATCH mode, or ONE_AT_A_TIME
//  mode.  If it is set to BATCH mode, we aren't going to do anything here,
//  so we just skip.  It is kind of inefficient, because we have to check
//  this for every file, not just once per compiler.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A    : First release
//
//   January 5, 1995  3.0A : Added the call to process() to provide complete
//                           substitution for formatting commands.  I used
//                           to use sprintf() with the file name, so that
//                           limited the substitutions.  Now a compiler line
//                           can include anything!
//

int compile_lib_files( options &opts, file_list &files )
{
//
// I iterate through the loop, once per file name.  Note that the first
// thing I do is check to see if the object file exists.  If it does, we
// skip the compile step.  This is kind of dangerous, because we might have
// an object left over from a different memory model, but it saves a lot of
// time when building is interrupted by an error.
//
    bstring *file = files.get_first_file();
    while ( file ) {
        bstring extension = file->ext();
        extension.remove_before( 1 );
        item *compile_item = opts.find_item( extension );
        if ( compile_item == 0 ) {
            cerr << "Error compiling " << *file << ".  No compile command defined.\n";
            return 0;
        }
        if ( compile_item->run_mode == item::ONE_AT_A_TIME ) {
            if ( !opts.ignore && target_file_exists( *file, ".OBJ" ) ) {
                cerr << "Skipping " << *file << "\n";
            } else {
//
// Once I have the file, I loop up the compile string, and the exec file.
// I use sprintf() to format a command line, then call the execute routine
// to make it all happen.
//
                bstring buffer;
                process( buffer, compile_item->value, *file, opts, 0 );
                if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
                    return 0;
            }
        } // object_exists
        file = files.get_next_file();
    }
    return 1;
}

//
//  int lib_batch_compiles( options &opts )
//
// ARGUMENTS:
//
//  opts       :  This structure contains all of the options that have
//                been read out of the INI file.  I need them here to
//                figure out how to run the compilers.
//
// RETURNS
//
//  0 if any error occurs when compiling, 1 if the compiler ran okay.
//
// DESCRIPTION
//
//  In release 4.0, I added the ability to perform batch compilations.
//  This routine performs the compilations.  If any one of the three
//  compiler types has its option set to BATCH, we run the batch compiler
//  instead of the one at a time compiler.  This routine does it.
//
//
// REVISION HISTORY
//
//   January 31, 1995  4.0B : First release.
//

int lib_batch_compiles( options &opts )
{
    char *exts[]={ "C", "CPP", "ASM", "" };

    for ( int i = 0 ; exts[ i ][ 0 ] != '\0'; i++ ) {
        item *compile_item = opts.find_item( exts[ i ] );
        if ( compile_item->run_mode != item::BATCH )
            continue;
        cerr << "Building " << exts[ i ] << " files in batch mode" << endl;
        bstring buffer;
        process( buffer, compile_item->value, "XXXXXXXX.XXX", opts, 0 );
        if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
            return 0;;
    }
    return 1;
}

//
// int build_exe_files( options &opts, file_list &files )
//
//
// ARGUMENTS:
//
//  opts       :  This structure contains all of the options that have
//                been read out of the INI file.  I need them here to
//                figure out how to compile each file.
//
//  files      :  The massive list of files.
//
// RETURNS
//
//  0 if any error occurs when compiling, 1 if every single file compiled
//  without error.
//
// DESCRIPTION
//
//  This is the routine I call when the program is in demo building mode.
//  It is similar to the previous version used to compiler programs, but
//  has enough differences to justify a separate routine.  The first big
//  difference is that this guy will handle compound statements.  I can
//  use four or five different lines to build a demo.  Second, in the
//  previous function, I picked a command based on the extension of the
//  file.  In this routine, I only use the Demo command.  I think
//  that covers it.
//
// REVISION HISTORY
//
//   January 5, 1995  3.0A : First release.
//
//   January 5, 1995  3.0A : Added the call to process() to provide complete
//                           substitution for formatting commands.  I used
//                           to use sprintf() with the file name, so that
//                           limited the substitutions.  Now a compiler line
//                           can include anything!
//
//  January 26, 1995  4.0A : Added the call to build_exe_config_files()
//                           before the start of each file's execution.
//                           Previously demo files were restricted to
//                           a single hardcode DEMO.CMD file name, now
//                           they can have any number of files, as long
//                           as they all fit the pattern of *EXE.CMD
//

int build_exe_files( options &opts, file_list &files )
{
    bstring *compile_string = opts.find_value( "EXE" );
    if ( compile_string == 0 || *compile_string == "" ) {
        cerr << "Error in build of demo.  No EXE command(s) defined.\n";
        return 0;
    }

    bstring *file = files.get_first_file();
    while ( file ) {
        build_exe_config_files( opts, *file );
        bstring command_string = compile_string->get_first_token( "^" );
        if ( !opts.ignore && target_file_exists( *file, ".EXE" ) ) {
            cout << "Skipping " << *file << endl;
        } else
            while ( command_string != "" ) {
                bstring buffer;
                process( buffer, command_string, *file, opts, 0 );
                if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
                    return 0;
                command_string = compile_string->get_next_token( "^" );
            }
        file = files.get_next_file();
    }
    return 1;
}

//
// int target_file_exists( const bstring &file, const bstring &ext )
//
//
// ARGUMENTS:
//
//  file        :  The name of a source file.  This can contain a full
//                 pathname and an extension.  Normally, this parameter
//                 will be something akin to "..\\CPP_WIN\FOO.ASM".
//
// RETURNS
//
//  1 if an object file exists, 0 if not.
//
// DESCRIPTION
//
//  This routine is called by compile_lib_files().  It doesn't want to
//  compile a file if a good object for that file already exists.
//  This routine just strips the path and extension, then checks to see if
//  an OBJ file exists in the current directory.  If so, it returns
//  a 1.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int target_file_exists( const bstring &file, const bstring &ext )
{
    FILE *p = fopen ( file.name() + ext, "r" );
    if ( !p )
        return 0;
    fclose( p );
    return 1;
}

//
// int execute( bstring &command, int dry_run )
//
//
// ARGUMENTS:
//
//  command    :  This is the command string, just as you would type it at
//                the DOS prompt, e.g. "cl /I..\H /AL /c foo.cpp".
//
//  dry_run    :  If this flag is set, it means the user has passed the
//                -cmd on the command line, meaning I don't really want
//                to execute the command, instead I just want to display
//                the commands that would have been executed.
//
// RETURNS
//
//  The return code from the spawn() command.  If things went okay it
//  will be a 0.  If the command didn't run it will be 0xffff. If it
//  is greater than 0, we assume the application ran and returned some
//  sort of error code.  If the user hits the escape key I return 1.
//
// DESCRIPTION
//
//  This is the function that actually executes a command.  We need a
//  completely separate function because of the way the spawn() functoin
//  works.  Instead of passing spawnv() a nicely built command line, we
//  have to break it apart into tokens, and pass an array of pointers
//  to those tokens.  What a pain!  Not only that, the tokens have to
//  be nice little char * objects, which means I have to allocate them
//  and then free them up after the command.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A   : First release
//
//   June 29, 1994 2.1A   : Added the ability to cancel by hitting the ESC key.
//
//   January 5, 1995 3.0A : Changed the token separator characters.  I was
//                          having a little problem with TLINK from Borland.
//                          it seems to want *all* of its arguments piled
//                          up in a single character.  When I separated them
//                          base on the comma character, I was getting lots
//                          of problems.  So, comma is no longer a separator.
//
//   January 26, 1995 4.0A : I used to search the path for the file to
//                           execute, which was a lot of extra work.  I did
//                           it because I figured that if I searched the
//                           path once, then cached the result, I could save
//                           time the next time I invoked the same command.
//                           Now I'm not so sure I believe that.
//

int execute( bstring &command, int dry_run )
{
    const int COUNT = 20;
    char *args[ COUNT ];

    int i = 0;
    while ( kbhit() ) {
        int c = getch();
        if ( c == 0x1b ) {
            cerr << "User abort!\n";
            return 1;
        }
    }
    cerr << command << "\n";
    for ( bstring token = command.get_first_token( " " );
          token.length() != 0;
          token = command.get_next_token( " " ) ) {
        if ( i >= ( COUNT - 3 ) ) {
            cerr << "\nError - Too many arguments in command!\n";
            return 1;
        }
        args[ i++ ] = strdup( token );
    }
    args[ i ] = strdup( "" );
    args[ i + 1 ] = 0;
    int ret_code = 0;
    if ( !dry_run ) {
#if defined( __TURBOC__ ) || defined( __WATCOMC__ )
        ret_code =  spawnvp( P_WAIT, args[ 0 ], args );
#else
        ret_code =  spawnvp( P_WAIT, args[ 0 ], (const char * const *) args );
#endif
        if ( ret_code != 0 ) {
            cerr << "Error code "
                 << ret_code
                 << " executing: <"
                 << command
                 << ">\n";
        }
    }
    for ( i = 0 ; i < COUNT ; i++ )
        if ( args[ i ] == 0 )
            break;
        else
            free( args[ i ] );
    return ret_code;
}

//
// int build_lib( options &opts )
//
//
// ARGUMENTS:
//
//  opts       :  The options object.  I need this because it holds
//                the linker command.
//
// RETURNS
//
//  0 if things didn't go well, otherwise a 1.
//
// DESCRIPTION
//
//  After all of the files for the library have been built, we call
//  this routine.  It's job is to take all the OBJ files and turn it
//  into a useful library.  This might mean just running the librarian,
//  but it also might mean running lib, then the linker, then implib.
//  Either way, it happens here.  I just execute all of the LIB commands
//  from the INI file, one after another.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A    : First release
//
//   January 5, 1995  3.0A : Added the call to process(), so that linker
//                           command gets full format processing.
//
//   January 26, 1995 4.0A : The librarian work used to be divided up
//                           between this function, and another one that
//                           took care of linking.  Now it all happens
//                           here.  All of the commands should be grouped
//                           under the LIB command.
//

int build_lib( options &opts )
{
    bstring *linker_string = opts.find_value( "Lib" );
    if ( linker_string == 0 || *linker_string == "" )
        return 1;

    bstring command_string;
    command_string = linker_string->get_first_token( "^" );
    while ( command_string != "" ) {
        bstring buffer;
        process( buffer, command_string, "", opts, 0 );
        if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
           return 0;
        command_string = linker_string->get_next_token( "^" );
    }
    return 1;
}

//
// int cleanup( file_list &files, options &opts )
//
//
// ARGUMENTS:
//
//  files      :  A list of the files that were processed.  This function
//                needs the list in order to delete all of the object
//                files.
//
// opts        :  The options object.  I need this because it contains
//                the DeleteFiles options, which tells me the names of
//                the other files to delete.
//
// RETURNS
//
//  Always returns a 1, indicating success.
//
// DESCRIPTION
//
//  This function is called by main() after all the steps in the build
//  have completed.  Its job is to delete all the objects that have been
//  left around, then any other files the BUILD.INI parameters may have
//  specified.  Deleting the objects is simply a matter of churning through
//  the list of files, deleting them one by one.  The list of files
//  to delete specified in DeleteFiles is pretty easy too, except I 
//  expand them first for wildcards.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int cleanup( file_list &files, options &opts )
{
//
// The first half of this routine is dedicated to deleting all
// the files specified in the DeleteFiles option of BUILD.INI
//
    bstring *delete_files = opts.find_value( "DeleteFiles" );
    if ( delete_files ) {
        for ( bstring wild_file = delete_files->get_first_token( "^ " );
              wild_file != "";
              wild_file = delete_files->get_next_token( "^ " ) ) {
//
// The funny cast of wild_spec is to accomodate Symantec C.  They
// foolishly use char * instead of const char * for this arg.
//
#if __OS2__
            APIRET rc;
            HDIR handle;
            FILEFINDBUF3 buffer = { 0 };
            ULONG count;
            handle = 1;
            count = 1;
            rc = DosFindFirst( wild_file,
                               &handle,
                               0,
                               (PVOID) &buffer,
                               sizeof( buffer ),
                               &count,
                               FIL_STANDARD );
            while ( rc == 0 ) {
                cerr << "DEL " << buffer.achName;
                if ( opts.keep_files )
                    cerr << "... Kept!";
                else
                    unlink( buffer.achName );
                cerr << "\n";
                count = 1;
                rc = DosFindNext( handle,
                                  (PVOID) &buffer,
                                  sizeof( buffer ),
                                  &count );
            }
#else
            struct find_t ffblk;
            unsigned int result;
            result = _dos_findfirst( (char *) (const char *) wild_file, 0, &ffblk );
            while ( result == 0 ) {
                cerr << "DEL " << ffblk.name;
                if ( opts.keep_files )
                    cerr << "... Kept!";
                else
                    unlink( ffblk.name );
                cerr << "\n";
                result = _dos_findnext( &ffblk );
            }
#endif
        }
    }
//
// The second half of this routine is dedicated to deleting all of the
// objects left behind after the compile completed.
//
    bstring *file = files.get_first_file();
    while ( file ) {
        bstring obj_name = file->name() + ".OBJ";
        cerr << "DEL " << obj_name;
        if ( opts.keep_files )
            cerr << "... Kept!";
        else
            unlink( obj_name );
        cerr << "\n";
        file = files.get_next_file();
    }
    return 1;
}

//
// bstring::bstring( const char *init = "" )
//
//
// ARGUMENTS:
//
//  init        :  The character string to initialize the string to.
//
// RETURNS
//
//  No returns.
//
// DESCRIPTION
//
//  This is one of the constructors for my string class.  It initializes
//  the tokenizer pointer to 0 (since we don't need a tokenizer copy
//  until somebody calls get_first_token()).  It allocates space for
//  the initial string, then copies it.
//
//  Note that this guy generates an assertion error if the memory
//  allocation fails.  There should never be a time when a valid bstring
//  object has a null pointer in the string member.  Doing things this
//  way simplifies my life a little.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring::bstring( const char *init )
{
    if ( init == 0 )
        init = "";
    string = new char[ strlen( init ) + 1 ];
    assert( string );
    strcpy( string, init );
    tokenizer = 0;
}

//
// bstring::bstring( const bstring& rhs )
//
//
// ARGUMENTS:
//
//  rhs        :  The bstring object being used to construct this object.
//
// RETURNS
//
//  No returns from constructors.
//
// DESCRIPTION
//
//  This is one of the constructors for my string class.  It initializes
//  the tokenizer pointer to 0 (since we don't need a tokenizer copy
//  until somebody calls get_first_token()).  It allocates space for
//  the initial string, then copies it.
//
//  Note that this guy generates an assertion error if the memory
//  allocation fails.  There should never be a time when a valid bstring
//  object has a null pointer in the string member.  Doing things this
//  way simplifies my life a little.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring::bstring( const bstring& rhs )
{
    const char *p = rhs;
    string = new char[ strlen( p ) + 1 ];
    assert( string );
    strcpy( string, p );
    tokenizer = 0;
}

//
// bstring::~bstring()
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  None. 
//
// DESCRIPTION
//
//  The string destructor just has to free memory.  There will always
//  be memory allocated for the "string" member.  There may or may not
//  be memory allocated in the tokenizer member.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring::~bstring()
{
    delete[] string;
    if ( tokenizer )
        delete[] tokenizer;
}

//
// bstring& bstring::operator = ( const bstring& rhs )
//
//
// ARGUMENTS:
//
//  rhs        :  Another string object that is going to be copied into this.
//
// RETURNS
//
//  A reference to this.
//
// DESCRIPTION
//
//  This operator is used to copy one bstring object to another.  Note
//  once again the assertion error that occurs in the event of an
//  allocation failure.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring& bstring::operator = ( const bstring& rhs )
{
    if ( (const char *) rhs == string )
        return *this;
    delete[] string;
    string = new char[ strlen( rhs ) + 1 ];
    assert( string );
    strcpy( string, rhs );
    return *this;
}

//
// bstring& bstring::operator = ( const char *s )
//
// ARGUMENTS:
//
//  s          :  The character string to copy into this.
//
// RETURNS
//
//  A reference to this.
//
// DESCRIPTION
//
//  This copies a character string into a bstring.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring& bstring::operator = ( const char *s )
{
    if ( string == s )
        return *this;
    delete[] string;
    string = new char[ strlen( s ) + 1 ];
    assert( string );
    strcpy( string, s );
    return *this;
}

//
// bstring& bstring::upper()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  A reference to this.
//
// DESCRIPTION
//
//  Converts a string to all upper case.
//
// REVISION HISTORY
//
//   January 5, 1995  3.0A : First release.
//

bstring& bstring::upper()
{
    for ( int i = 0 ; string[ i ] ; i++ )
        string[ i ] = toupper( string[ i ] );
    return *this;
}

//
// bstring bstring::operator + ( const char *s )
//
//
// ARGUMENTS:
//
//  s          :  A pointer to a character string.
//
// RETURNS
//
//  A bstring that results from adding s to this.  "Add" means concatenate.
//
// DESCRIPTION
//
//  This operator adds a character string to a bstring, creating a
//  new bstring.  This is very useful in a program like this, where
//  we are adding strings together all the time.  Note the assertion
//  error that takes place if a memory allocation fails.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::operator + ( const char *s )
{
    assert( s );
    char *p = new char[ strlen( string ) + strlen( s ) + 1 ];
    assert( p );
    strcpy( p, string );
    strcat( p, s );
    bstring new_guy( p );
    delete[] p;
    return new_guy;
}

//
// bstring bstring::operator + ( const bstring &rhs )
//
//
// ARGUMENTS:
//
//  rhs        :  A bstring that is going to be added to this.
//
// RETURNS
//
//  A bstring that results from adding s to this. "Add" means concatenate.
//
// DESCRIPTION
//
//  This operator adds a bstring to a bstring, creating a
//  new bstring.  This is very useful in a program like this, where
//  we are adding strings together all the time.  Note the assertion
//  error that takes place if a memory allocation fails.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::operator + ( const bstring &rhs )
{
    char *p = new char[ strlen( string ) + strlen( rhs ) + 1 ];
    assert( p );
    strcpy( p, string );
    strcat( p, rhs );
    bstring new_guy( p );
    delete[] p;
    return new_guy;
}

//
// int bstring::operator != ( const char *s ) const
//
//
// ARGUMENTS:
//
//  s          :  Pointer to a character string.
//
// RETURNS
//
//  True or false.
//
// DESCRIPTION
//
//  This is a wonderful function that we use all the time.  It lets
//  us compare bstrings using the != operator.  One major deal to note
//  is that string comaprisons are case insensitive.  If you don't know
//  this things aren't going to work quite like you expect.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::operator != ( const char *s ) const
{
    return stricmp( string, s );
}

//
// int bstring::operator == ( const char *s )
//
//
// ARGUMENTS:
//
//  s          :  Pointer to a character string.
//
// RETURNS
//
//  True or false.
//
// DESCRIPTION
//
//  This is a another function that we use all the time.  It lets
//  us compare bstrings using the == operator.  One major deal to note
//  is that string comaprisons are case insensitive.  If you don't know
//  this things aren't going to work quite like you expect.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::operator == ( const char *s )
{
    return !stricmp( string, s );
}

//
// int bstring::operator == ( const bstring &rhs )
//
//
// ARGUMENTS:
//
//  rhs          :  Reference to the bstring argument being compared to this.
//
// RETURNS
//
//  True or false.
//
// DESCRIPTION
//
//  This is a another function that we use all the time.  It lets
//  us compare bstrings using the == operator.  One major deal to note
//  is that string comaprisons are case insensitive.  If you don't know
//  this things aren't going to work quite like you expect.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::operator == ( const bstring& rhs )
{
    return !stricmp( string, rhs );
}


//
// int bstring::first( char c )
//
//
// ARGUMENTS:
//
//  c        :  The character to search for.
//
// RETURNS
//
//  An index, ranging from 0 to ( strlen( this ) -1 ) if the character
//  is found, or -1 if it isn't found.
//
// DESCRIPTION
//
//  This function finds the first appearance of character c in bstring this.
//  It returns the index of the character.
//
// One really bad deal here is that this search *is* case sensitive, which
// really differs from the rest of the library.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::first( char c )
{
    char *p = strchr( string, c );
    if ( p == 0 )
        return -1;
    else
        return (int) ( p - string );
}

//
// int bstring::first( const char *s )
//
//
// ARGUMENTS:
//
//  s        :  The string to search for.
//
// RETURNS
//
//  An index, ranging from 0 to ( strlen( this ) -1 ) if the substring
//  is found, or -1 if it isn't found.
//
// DESCRIPTION
//
//  This function finds the first appearance of substring s in bstring this.
//  It returns the index of the first character of the substring.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//
//   January 26, 1995  4.0A : Modified so this guy would be case insensitive.
//                            This routine could use some work!
//

int bstring::first( const char *s )
{
    char *x = strdup( string );
    char *y = strdup( s );
    assert( x );
    assert( y );
    for ( int i = 0 ; i < strlen( x ) ; i++ )
        x[ i ] = toupper( x[ i ] );
    for ( i = 0 ; i < strlen( y ) ; i++ )
        y[ i ] = toupper( y[ i ] );
    char *p = strstr( x, y );
    free( x );
    free( y );
    if ( p == 0 )
        return -1;
    else
        return (int) ( p - x );
}

//
// int bstring::last( char c )
//
//
// ARGUMENTS:
//
//  c        :  The character to search for.
//
// RETURNS
//
//  An index, ranging from 0 to ( strlen( this ) -1 ) if the character
//  is found, or -1 if it isn't found.
//
// DESCRIPTION
//
//  This function finds the last appearance of character c in bstring this.
//  It returns the index of the character.
//
// One really bad deal here is that this search *is* case sensitive, which
// really differs from the rest of the library.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::last( char c )
{
    char *p = strrchr( string, c );
    if ( p == 0 )
        return -1;
    else
        return (int) ( p - string );

}

//
// int bstring::remove_after( int index )
//
//
// ARGUMENTS:
//
//  index      :  The point in the string which will now be the last
//                character.
//
// RETURNS
//
//  0 if the index is no good, otherwise 1.
//
// DESCRIPTION
//
//  This function lets you chop off the end of a bstring.  It deletes
//  everything after a specific index.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::remove_after( int index )
{
    if ( index >= (int) strlen( string ) )
        return 0;
    if ( index < -1 )
        return 0;
    string[ index + 1 ] = '\0';
    return 1;
}

//
// int bstring::remove_before( int index )
//
//
// ARGUMENTS:
//
//  index      :  The point in the string which will now be the first
//                character.
//
// RETURNS
//
//  0 if the index is no good, otherwise 1.
//
// DESCRIPTION
//
//  This function lets you chop off the start of a bstring.  It deletes
//  everything before a specific index.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::remove_before( int index )
{
    if ( index > (int) strlen( string ) )
        return 0;
    if ( index < 0 )
        return 0;
    char *dest = string;
    char *source = string + index;
    while ( ( *dest++ = *source++ ) != '\0' )
        ;
    return 1;
}

//
// int bstring::operator[]( int index ) const
//
//
// ARGUMENTS:
//
//  index    :  This indexing operator lets you safely get
//              a character out of a bstring.  You can pass a no
//              good index and this function will deal with it.
//
// RETURNS
//
//  The character, or a -1 if the index is no good.
//
// DESCRIPTION
//
//  This indexing operator returns a character from the bstring.  If
//  for some reason the index is no good, it returns a -1.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::operator[]( int index ) const
{
    if ( index < 0 )
        return 0;
    if ( index >= (int) strlen( string ) )
        return 0;
    return string[ index ];
}

//
// int bstring::index_from_end( int index ) const
//
//
// ARGUMENTS:
//
//  index      : The index into the string.
//
// RETURNS
//
//  Either the character from the string, or a -1 if the index is no good.
//
// DESCRIPTION
//
//  Sometimes I want to get a character that is a certain distance from the
//  end of the string.  This is just like the [] operator, except it 
//  operates in revers.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

int bstring::index_from_end( int index ) const
{
    index = strlen( string ) - index;
    if ( index < 0 )
        return 0;
    if ( index >= (int) strlen( string ) )
        return 0;
    return string[ index ];
}

//
// bstring bstring::drive() const
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  The drive component of a file name.  If the drive component
//  is not present in the file name, and empty string is returned.
//
// DESCRIPTION
//
//  This is one of four bstring functions that help to support 
//  separation of filenames into components.  If the filename has
//  a drive component, this guy will strip it out, put it in a 
//  new string, and return it.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::drive() const
{
    bstring drive_name = *this;
    if ( drive_name != "" ) {
        int i = drive_name.first( ':' );
        if ( i == -1 )
            drive_name = "";
        else
            drive_name.remove_after( i );
    }
    return drive_name;
}

//
// bstring bstring::path() const
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  The path component of a file name.  If the path component
//  is not present in the file name, we return sort of a default
//  path, "..\\";
//
// DESCRIPTION
//
//  This is one of four bstring functions that help to support
//  separation of filenames into components.  This function sort
//  of tries to second guess the best thing to return for the path
//  component of a file name.  I'm not sure this is as good as it
//  should be, but it works for BUILD.CPP.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::path() const
{
    bstring path = *this;
    int i = path.first( ':' );
    if ( i != -1 )
        path.remove_before( i + 1 );
//
// Now we might have :
//     \\comp1\\comp2\\name  which should return "\\comp1\\comp2\\"
//     name                  which should return ".\\"
//     \\name return         which should return "\\"
//     \\name\\              which should return "\\name""
//     comp1\\comp2\\name    which should return "comp1\\comp2\\"
//
    i = path.last( '\\' );
    if ( i != -1 ) {
        path.remove_after( i );
        if ( path.first( '\\' ) == 0 )
            path = bstring( ".\\" ) + path;
        return path;
    }
//
// Now we might have :
//     name                  which should return ".\\"
//
    return path = ".\\";
}

//
// bstring bstring::name() const
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  The name component of a file name.  Note that in an 8.3 system,
//  this function returns the 8, but no decimal point.  The point
//  gets returned with the extension.
//
// DESCRIPTION
//
//  This is one of four bstring functions that help to support
//  separation of filenames into components.  This function returns
//  the name portion of the file.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::name() const
{
    bstring name = *this;
    int i = name.last( '\\' );
    if ( i >= 0 )
        name.remove_before( i + 1 );
    else {
        i = name.first( ':' );
        if ( i >= 0 )
            name.remove_before( i + 1 );
    }
    i = name.first( '.' );
    if ( i >= 0 )
        name.remove_after( i - 1 );
    return name;
}

//
// bstring bstring::ext() const
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  The extension component of a file name.  Note that in an 8.3 system,
//  this function returns the 3 letter extension, along with the
//  decimal point.  A lot of times you will want to strip that
//  decimal point.  In fact, I think I just about always strip it.
//
// DESCRIPTION
//
//  This is one of four bstring functions that help to support 
//  separation of filenames into components.  This function returns
//  the extension portion of the file.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::ext() const
{
    bstring ext = *this;
    int i = ext.last( '.' );
    if ( i >= 0 )
        ext.remove_before( i );
    else
        ext = ".";
    return ext;
}

//
// bstring bstring::get_first_token( const char *s = ", " )
//
//
// ARGUMENTS:
//
//  s   : A string containing all the token delimiters.
//
// RETURNS
//
//  A string containing the first token from the string.  If there
//  is no token you get an empty string.
//
// DESCRIPTION
//
//  The get_first_token() / get_next_token() interface is really useful.
//  It lets you suck tokens out of a string, using an arbitrary set of
//  delimiters.  Usually we are just looking for tokens separated by
//  spaces, but when parsing the dos PATH, we look for tokens separated
//  by semicolons.
//
//  To make this work properly, the guy makes a little copy of the string,
//  and mangles it as we go along.  When we are done with it it gets
//  deleted.  It might be just as easy to just use a pointer into the
//  string, but this is what I ended up with.  It works.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::get_first_token( const char *s /* = " ," */ )
{
    if ( tokenizer )
        delete[] tokenizer;
    tokenizer = new char[ strlen( string ) + 1 ];
    assert( tokenizer );
    strcpy( tokenizer, string );
    return get_next_token(s );
}

//
// bstring bstring::get_next_token( const char *delimiters = " ," )
//
//
// ARGUMENTS:
//
//  s   : A string containing all the token delimiters.
//
// RETURNS
//
//  A string containing the next token from the string.  If there
//  is no token you get an empty string.
//
// DESCRIPTION
//
//  The get_first_token() / get_next_token() interface is really useful.
//  It lets you suck tokens out of a string, using an arbitrary set of
//  delimiters.  Usually we are just looking for tokens separated by
//  spaces, but when parsing the dos PATH, we look for tokens separated
//  by semicolons.
//
//  To make this work properly, the guy makes a little copy of the string,
//  and mangles it as we go along.  When we are done with it it gets
//  deleted.  It might be just as easy to just use a pointer into the
//  string, but this is what I ended up with.  It works.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring bstring::get_next_token( const char *delimiters /* = " ," */ )
{
    assert( tokenizer );
    char *temp_src = tokenizer + strspn( tokenizer, delimiters );
    for ( char *temp_dest = tokenizer ;
          *temp_src != '\0';
          *temp_dest++ = *temp_src++ )
        ;
    *temp_dest = '\0';
    if ( *tokenizer == '\0' ) {
        delete[] tokenizer;
        tokenizer = 0;
        return "";
    }
    temp_src = tokenizer + strcspn( tokenizer, delimiters );
    char temp = *temp_src;
    *temp_src = '\0';
    bstring result = tokenizer;
    *temp_src = temp;
    for ( temp_dest = tokenizer;
          *temp_src != '\0';
          *temp_dest++ = *temp_src++ )
        ;
    *temp_dest = '\0';
    return result;
}

//
// options::options( char *first, ... )
//
//
// ARGUMENTS:
//
//  first    : The name of the first option
//
//  ...      :  A list of character strings containing the names of
//              any other options we expect to find.  The list needs
//              to be terminated with a NULL pointer or an empty string.
//
// RETURNS
//
//  Constructor, no returns
//
// DESCRIPTION
//
//  The options class holds all the options we need to use in order
//  to build a library.  The bulk of the information in the options
//  structure consists of tag names and values that are read out of
//  BUILD.INI.  A line in BUILD.INI looks like this:
//
//      tag=value
//
//  This implementation of the options class inists that we define all
//  the tag names in advance, here in the constructor.  It would be
//  easy to change the class so that new names encountered in BUILD.INI
//  could be added dynamically, but I think it would then be harder to
//  track down typos in the INI file, don't you.
//
//  This constructor is in charge of setting up the tag items with blank
//  values.  When BUILD.EXE is busy processing the files, it will assigne
//  the values to each of the tags as it encounters them.
//
//  There are two additional data members in options that don't get set up
//  here (although they probably should).  One is the memory model read in
//  from the command line.  The other is the dry_run member, used to 
//  tell the program to go through the motions of building a library,
//  but not to really do it.
//
// REVISION HISTORY
//
//   May 20, 1994  2.0A  : First release
//

options::options( char *first, ... )
{
    va_list ap;
    char *arg;

    va_start( ap, first );
    first_item = 0;
    arg = first;
    while ( arg != 0 && strlen( arg ) > 0 ) {
        item *new_item = new item( arg );
        assert( new_item );
        new_item->next_item = first_item;
        first_item = new_item;
        arg = va_arg( ap, char * );
    }
}

//
// void options::add_option( const bstring &name, const bstring &value )
//
//
// ARGUMENTS:
//
//  name   :  The name of the new option to be added.
//
//  value  :  The initial value of the new options.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  The deal used to be that you didn't get to add any new options to
//  the opts structure.  The available options were defined by the ctor,
//  and if you had something new, that was tough luck.
//
//  In 4.0, I added the ability to create as man CMD files as you wanted
//  to create.  I had to have a way to keep track of the CMD files without
//  predefining them, so I bit the bullet and changed the behavior of options.
//  Now you can add new keys to the options any time you want.
//
// REVISION HISTORY
//
//   January 31, 1995  4.0B : First release.
//

void options::add_option( const bstring &name, const bstring &value )
{
    item *new_item = new item( (const char *) name );
    assert( new_item );
    new_item->next_item = first_item;
    first_item = new_item;
    new_item->value = value;
}

//
// bstring *options::find_value( char const *tag )
//
//
// ARGUMENTS:
//
//  tag       :  The name of the tag whose value we want.
//
// RETURNS
//
//  A bstring pointer, which points to the actual tag in the list opt option
//  items.  If the tag could not be found, it returns a null pointer.
//
// DESCRIPTION
//
//  This is the function I use to look up a tag value.  It returns a bstring
//  directly, so it is really easy to use.  It might be even better to try
//  to overload the [] operator with a string, but I don't know if you can
//  actually do that.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring *options::find_value( char const *tag )
{
    item *current_item;
    for ( current_item = first_item ; ; current_item = current_item->next_item ) {
        if ( current_item == 0 )
            return 0;
        if ( current_item->name != "" && current_item->name == tag )
            return &current_item->value;
    }
}

//
// item *options::find_item( char const *tag )
//
//
// ARGUMENTS:
//
//  tag       :  The name of the tag we are searching for.
//
// RETURNS
//
//  An item pointer, which points to the actual item in the option object.
//  If the tag could not be found, it returns a null pointer.
//
// DESCRIPTION
//
//  I need to be able to look up the item pointer for a given keyword so
//  I can modify the run type from ONE_AT_A_TIME to BATCH.
//
// REVISION HISTORY
//
//   January 31, 1995 4.0B : First release.
//

item *options::find_item( char const *tag )
{
    item *current_item;
    for ( current_item = first_item ; ; current_item = current_item->next_item ) {
        if ( current_item == 0 )
            return 0;
        if ( current_item->name != "" && current_item->name == tag )
            return current_item;
    }
}

//
// options::~options()
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  The destructor deletes all of the items so there aren't any memory leaks.
//
// REVISION HISTORY
//
//   January 31, 1995  4.0B : First release
//

options::~options()
{
    item *current_item;
    item *next_item;
    current_item = first_item;
    for ( ; ; ) {
        if ( current_item == 0 )
            break;
        next_item = current_item->next_item;
        delete current_item;
        current_item = next_item;
    }
}

//
// void options::erase_values()
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  This function goes through the options list and sets each tag items'
//  value to the empty string.  I do this when I am scanning the INI
//  file in dump_sections().  Normally, when you read in an item for a
//  second time, you would generate an error.
//
// REVISION HISTORY
//
//   May 20, 1994  2.0A  : First release
//

void options::erase_values()
{
    item *current_item;
    for ( current_item = first_item ; ; current_item = current_item->next_item ) {
        if ( current_item == 0 )
            return;
        current_item->value = "";
    }
}

//
// bstring *file_list::get_first_file()
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  A pointer to a bstring.  When there are no more files to return,
//  we return a null pointer.
//
// DESCRIPTION
//
//  I keep all of the files I am going to compile in a file_list object.
//  When iterating through the list, I use the get_first_file() 
//  get_next_file() functions.  This is the get_first_file().  Note that
//  I keep track of where I am in the list using the internal protected
//  member current_item.  This value gets set up here for the first
//  time so that subsequent calls to get_next_file() will know where
//  to continue the search.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

bstring *file_list::get_first_file()
{
    current_item = first_file;
    if ( current_item )
        return &current_item->file_name;
    else
        return 0;
}

//
// file_list::~file_list()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  I added the destructor so I could clean up after myself an not have
//  any memory leaks.
//
// REVISION HISTORY
//
//   January 31, 1995  4.0B : First release.
//

file_list::~file_list()
{
    file_name_item *current_file;
    file_name_item *next_file;
    current_file = first_file;
    for ( ; ; ) {
        if ( current_file == 0 )
            break;
        next_file = current_file->next_item;
        delete current_file;
        current_file = next_file;
    }
}

//
// bstring *file_list::get_next_file()
//
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  If another file is available in the list, we return a pointer to
//  it.  If we reached the end of the list, we return a null pointer.
//
// DESCRIPTION
//
//  This is the companion function to get_first_file().
//
// REVISION HISTORY
//
//   May 20, 1994  2.0A  : First release
//

bstring *file_list::get_next_file()
{
    if ( current_item )
        current_item = current_item->next_item;
    if ( current_item )
        return &current_item->file_name;
    else
        return 0;
}

//
// void file_list::add_file( const char *name )
//
//
// ARGUMENTS:
//
//  name   :  The name of the file to add to the list.
//
// RETURNS
//
//  Nothing.
//
// DESCRIPTION
//
//  During the startup of this program, we add files to the file list.
//  This is the function that does it.  It creates a new item, and
//  assigns it the appropriate name.  Note that any memory allocation
//  failure causes an assertion error and the whole deal goes to sleep.
//
//  If I had bothered to put an end_of_list pointer in the file_list
//  class, this function would be a lot more efficient, at a very small
//  price.  Oh well.
//
// REVISION HISTORY
//
//   May 19, 1994  2.0A  : First release
//

void file_list::add_file( const char *name )
{
//
// Let's add the new guy to the end of the list!
//
    file_name_item *new_guy = new file_name_item( name );
    assert( new_guy );
    file_name_item **list = &first_file;
    while ( *list != 0 )
        list = &(*list)->next_item;
    *list = new_guy;
    new_guy->next_item = 0;
}