generate.main.c
/****************************************************************************************************/
/*                                                                                                  */
/* generate.main.c:                                                                                 */
/*                                                                                                  */
/****************************************************************************************************/

/****************************************************************************************************/
/*                                                                                                  */
/*     Copyright (C) 2003, 2004 Joerg Kunze                                                         */
/*                                                                                                  */
/*     This file is part of siliconBrain.                                                           */
/*                                                                                                  */
/*     siliconBrain is free software; you can redistribute it and/or modify                         */
/*     it under the terms of the GNU General Public License as published by                         */
/*     the Free Software Foundation; either version 2 of the License, or                            */
/*     (at your option) any later version.                                                          */
/*                                                                                                  */
/*     siliconBrain is distributed in the hope that it will be useful,                              */
/*     but WITHOUT ANY WARRANTY; without even the implied warranty of                               */
/*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                */
/*     GNU General Public License for more details.                                                 */
/*                                                                                                  */
/*     You should have received a copy of the GNU General Public License                            */
/*     along with this program; if not, write to the Free Software                                  */
/*     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                    */
/*                                                                                                  */
/****************************************************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>

#include <obstack.h>

#define obstack_chunk_alloc malloc
#define obstack_chunk_free  free

#include "glib.h"

#include "siliconBrainLib"
#include "siliconBrainSpecification"

static const char *siliconBrainRelease       = "$siliconBrainRelease: 0.2.3 $";
static const char *siliconBrainRcsIdentifier = "$Id: generate.main.c,v 1.27 2004/12/14 23:31:26 joerg Exp $";
static const char *siliconBrainSaveStamp     = "$siliconBrainSaveStamp: 2004/12/14 22:29:54, Joerg Kunze$";

/****************************************************************************************************/
/*                                                                                                  */
/* List:                                                                                            */
/*                                                                                                  */
/****************************************************************************************************/
typedef struct ListElem {
   struct ListElem *next;
   char            data;
} ListElem;

typedef struct List {
   ListElem  anchor;
   ListElem *last;

   size_t dataSize;

   struct obstack obstack;
} List;

void listOpen( List *list, size_t dataSize ) {
   list->anchor.next  = 0;
   list->last = &list->anchor;
   list->dataSize = dataSize + sizeof( ListElem * );
   obstack_init( &list->obstack );
}

void *listAppend( List *list ) {
   list->last = list->last->next = obstack_alloc( &list->obstack, list->dataSize );
   list->last->next = 0;
   return &(list->last->data);
}

void listClose( List * list ) {
   obstack_free( &list->obstack, 0 );
}

#define listFor( list, iterator, dataPointer ) \
   for( (iterator) = (list)->anchor.next; (iterator)&& ((dataPointer) = (void *)(&(iterator)->data)); (iterator) = (iterator)->next )

void listTest() {

   struct {
      int a;
      int b;
   } *foo;

   List list;

   listOpen( &list, sizeof *foo );

   foo = listAppend( &list );
   foo->b = 42;
   foo = listAppend( &list );
   foo->b = 23;
   foo = listAppend( &list );
   foo->b = 666;

   ListElem *iterator;

   listFor( &list, iterator, foo ) printf( "foo is %d\n", foo->b );

   listClose( &list );

}

/****************************************************************************************************/
/*                                                                                                  */
/* command:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
typedef enum {
   isCommand = 1,
   isRecord
} StructureType;

typedef enum {
   isOption = 1,
   isField
} ElementType;

typedef struct {
   char       *name;
   char       *shortDescription;
   char       *longDescription;
   char        oneCharacterName;
   char       *typeName;
   OptionType  type;
   ElementType elementType;
} Option;

typedef struct {
   Specification specification;
   List          options;
   StructureType structureType;
   bool          withoutStandardOptions;
   bool          isFileCommand;
} LocalCommand;

typedef struct {
   char *comment;
   char *versionFormat;

   void (*formatStartMain)( const LocalCommand *);
   void (*formatTitle    )( const LocalCommand *);
   void (*formatOption   )( const Option       *, int optionIndex );
   void (*formatEndMain  )( const LocalCommand *);
} Formater;

#define forEachOption( body )            \
   {                                     \
      int       optionIndex = 0;         \
      Option   *option;                  \
      ListElem *iterator;                \
      listFor( &command->options, iterator, option ) { \
         body;                           \
         ++optionIndex;                  \
      }                                  \
   }

/****************************************************************************************************/
/*                                                                                                  */
/* LocalXmlUserData:                                                                                */
/*                                                                                                  */
/****************************************************************************************************/
typedef enum {
   localContextNo,
   localContextInCommand,
   localContextInCommandInShortDescription,
   localContextInCommandInLongDescription,
   localContextInOption,
   localContextInOptionInShortDescription,
   localContextInOptionInLongDescription,
   localContextInFile,
   localContextInFileCommandOpen,
   localContextInFileCommandClose,
   localContextInFileFileOpen,
   localContextInFileFileClose,
} LocalXmlContextState;

typedef struct LocalXmlUserData {
   LocalCommand         *command;
   Option               *option;
   LocalXmlContextState  state;
   bool                  first;
} LocalXmlUserData;

/****************************************************************************************************/
/*                                                                                                  */
/* prototype:                                                                                       */
/*                                                                                                  */
/****************************************************************************************************/
static void generateAll( const Formater *formater, const LocalCommand *command );

/****************************************************************************************************/
/*                                                                                                  */
/* parser:                                                                                          */
/*                                                                                                  */
/****************************************************************************************************/
/****************************************************************************************************/
/*                                                                                                  */
/* xmlStart:                                                                                        */
/*                                                                                                  */
/****************************************************************************************************/
static void xmlStartElement(
   GMarkupParseContext *context,
   const gchar         *name,
   const gchar        **attribute,
   const gchar        **value,
   gpointer             userData,
   GError             **error
) {
   LocalXmlUserData *user    = userData;
   struct obstack   *obstack = &user->command->options.obstack;

   ElementType elementType;

   switch( user->state ) {
   case localContextNo:
      if(
         ( !strcmp( name, "command" ) && (user->command->structureType = isCommand) )  ||
         ( !strcmp( name, "record"  ) && (user->command->structureType = isRecord ) )
      ) {
         user->state = localContextInCommand;
         Specification *specification = &user->command->specification;

         if( user->first ) {
            while( *attribute ) {
               char *allocatedValue = obstack_copy0( obstack, *value, strlen( *value ) );

               if( !strcmp( *attribute, "name" ) ) {
                  specification->name = allocatedValue;
               }
               else if( !strcmp( *attribute, "release" ) ) {
                  specification->release = allocatedValue;
               }
               else if( !strcmp( *attribute, "rcsIdentifier" ) ) {
                  specification->rcsIdentifier = allocatedValue;
               }
               else if( !strcmp( *attribute, "saveStamp" ) ) {
                  specification->saveStamp = allocatedValue;
               }
               else if( !strcmp( *attribute, "title" ) ) {
                  specification->title = allocatedValue;
               }
               *attribute++;
               *value++;
            }
         }
      }
      break;

   case localContextInCommand:
      if( !strcmp( name, "shortDescription" ) ) {
         user->state = localContextInCommandInShortDescription;
      }
      else if( !strcmp( name, "longDescription" ) ) {
         user->state = localContextInCommandInLongDescription;
      }
      else if( !strcmp( name, "withoutStandardOptions" ) ) {
         user->command->withoutStandardOptions = true;
      }
      else if( !strcmp( name, "file" ) ) {
         user->command->isFileCommand = true;
         user->state = localContextInFile;
      }
      else if(
              (!strcmp( name, "option" ) && (elementType = isOption) ) ||
              (!strcmp( name, "field"  ) && (elementType = isField ) )
      ) {
         user->state = localContextInOption;

         user->option = listAppend( &user->command->options );
         user->option->elementType = elementType;

         while( *attribute ) {
            char *allocatedValue = obstack_copy0( obstack, *value, strlen( *value ) );

            if( !strcmp( *attribute, "name" ) ) {
               user->option->name = allocatedValue;
            }
            else if( !strcmp( *attribute, "oneCharacterName" ) ) {
               user->option->oneCharacterName = allocatedValue[ 0 ];
            }
            else if( !strcmp( *attribute, "type" ) ) {
               user->option->type = !strcmp( allocatedValue, "value" ) ? optionValue : optionFlag;
            }
            else if( !strcmp( *attribute, "typeName" ) ) {
               user->option->typeName = allocatedValue;
            }
            *attribute++;
            *value++;
         }
      }
      break;

   case localContextInCommandInShortDescription:
      break;

   case localContextInCommandInLongDescription:
      break;

   case localContextInOption:
      if( !strcmp( name, "shortDescription" ) ) {
         user->state = localContextInOptionInShortDescription;
      }
      else if( !strcmp( name, "longDescription" ) ) {
         user->state = localContextInOptionInLongDescription;
      }
      break;

   case localContextInOptionInShortDescription:
      break;
   case localContextInOptionInLongDescription:
      break;

   case localContextInFile:
      if( strequ( name, "openCommand" ) ) {
         user->state = localContextInFileCommandOpen;
      }
      else if( strequ( name, "closeCommand" ) ) {
         user->state = localContextInFileCommandClose;
      }
      else if( strequ( name, "openFile" ) ) {
         user->state = localContextInFileFileOpen;
      }
      else if( strequ( name, "closeFile" ) ) {
         user->state = localContextInFileFileClose;
      }
      break;

   case localContextInFileCommandOpen:
      break;
   case localContextInFileCommandClose:
      break;
   case localContextInFileFileOpen:
      break;
   case localContextInFileFileClose:
      break;
   }
}

/****************************************************************************************************/
/*                                                                                                  */
/* xmlEnd:                                                                                          */
/*                                                                                                  */
/****************************************************************************************************/
static void xmlEndElement(
   GMarkupParseContext *context,
   const gchar         *element_name,
   gpointer             user_data,
   GError             **error
) {
   LocalXmlUserData *user = user_data;

   /*----------------------------------------------------------*/
   /* tags with no inner context: don't go up in context stack */
   /*----------------------------------------------------------*/
   if( strequ( element_name, "withoutStandardOptions" ) ) return;

   switch( user->state ) {
   case localContextNo:
      user->state = localContextNo;
      break;
   case localContextInCommand:
      user->state = localContextNo;
      break;
   case localContextInCommandInShortDescription:
      user->state = localContextInCommand;
      break;
   case localContextInCommandInLongDescription:
      user->state = localContextInCommand;
      break;
   case localContextInOption:
      user->state = localContextInCommand;
      break;
   case localContextInOptionInShortDescription:
      user->state = localContextInOption;
      break;
   case localContextInOptionInLongDescription:
      user->state = localContextInOption;
      break;
   case localContextInFile:
      user->state = localContextInCommand;
      break;
   case localContextInFileCommandOpen:
      user->state = localContextInFile;
      break;
   case localContextInFileCommandClose:
      user->state = localContextInFile;
      break;
   case localContextInFileFileOpen:
      user->state = localContextInFile;
      break;
   case localContextInFileFileClose:
      user->state = localContextInFile;
      break;
   }
}

/****************************************************************************************************/
/*                                                                                                  */
/* xmlText:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
static void xmlText(
   GMarkupParseContext *context,
   const gchar         *text,
   gsize                textLength,
   gpointer             user_data,
   GError             **error
) {
   LocalXmlUserData *user = user_data;

   /*---------------------------------*/
   /* ignore text in ceratin contexts */
   /*---------------------------------*/
   switch( user->state ) {
   case localContextNo:
   case localContextInCommand:
      return;
   default: break;
   }

   Specification  *specification = &user->command->specification;
   struct obstack *obstack       = &user->command->options.obstack;
   const char     *textEnd       = text + textLength - 1;
   Option         *option        = user->option;

   /*--------------------------------------------*/
   /* trim text: skip left and right white space */
   /*--------------------------------------------*/
   while( textLength ) {
      switch( *text ) {
      case ' ': case '\n': case '\t':
         --textLength;
         ++text;
         break;
      default:
         goto endOfPrefix;
      }
   }
 endOfPrefix:

   while( textLength ) {
      switch( *textEnd ) {
      case ' ': case '\n': case '\t': case 0:
         --textLength;
         --textEnd;
         break;
      default:
         goto endOfSuffix;
      }
   }
 endOfSuffix:

   /*-----------------------------*/
   /* context dependend copy text */
   /*-----------------------------*/
   switch( user->state ) {
   case localContextInCommandInShortDescription:
      if( !user->first ) break;
      specification->shortDescription = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInCommandInLongDescription:
      if( !user->first ) break;
      specification->longDescription = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInFileCommandOpen:
      if( !user->first ) break;
      specification->commandOpen = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInFileCommandClose:
      if( !user->first ) break;
      specification->commandClose = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInFileFileOpen:
      if( !user->first ) break;
      specification->fileOpen = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInFileFileClose:
      if( !user->first ) break;
      specification->fileClose = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInOption:
      break;
   case localContextInOptionInShortDescription:
      option->shortDescription = obstack_copy0( obstack, text, textLength );
      break;
   case localContextInOptionInLongDescription:
      option->longDescription = obstack_copy0( obstack, text, textLength );
      break;
   default: break;
   }
}

static GMarkupParser parser = { &xmlStartElement, &xmlEndElement, &xmlText, 0, 0 };

/****************************************************************************************************/
/*                                                                                                  */
/* formatH:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainH( const LocalCommand *command ) {
   puts( "/* generated H-file. -*-c-*- */\n" );
}

static void formatTitleH( const LocalCommand *command ) {
   puts(
      "\n#include <stdbool.h>\n"
      "\n#include \"siliconBrainLib\"\n"
      "\n#include \"siliconBrainOption\"\n"
   );
   puts( "typedef struct {" );
}

static void formatOptionH( const Option *option, int optionIndex ) {
   printf(
      "   %s %s; /* %s */\n",
      option->type == optionFlag ? "bool  " : "char *",
      option->name,
      option->shortDescription
   );
}

static void formatEndMainH( const LocalCommand *command ) {

   char *typeName, *variableName;

   if( command->structureType == isCommand ) {
      typeName     = "Options";
      variableName = "options";
   } else {
      typeName     = "Fields";
      variableName = "fields";
   }

   printf( "} %s%s;\n\n", command->specification.name, typeName );

   printf( "CommandReturnCode %s( const %s%s *%s", command->specification.name, command->specification.name, typeName, variableName );

   if( command->isFileCommand )
      printu( ", void *commandData, String source"  );

   printu( " );\n" );

   if( command->specification.commandOpen  ) printf( "void              *%s( const %s%s *options );\n"                , command->specification.commandOpen , command->specification.name, typeName  );
   if( command->specification.commandClose ) printf( "CommandReturnCode  %s( const %s%s *options, void * );\n"        , command->specification.commandClose, command->specification.name, typeName  );
   if( command->specification.fileOpen     ) printf( "CommandReturnCode  %s( const %s%s *options, void *, String );\n", command->specification.fileOpen    , command->specification.name, typeName  );
   if( command->specification.fileClose    ) printf( "CommandReturnCode  %s( const %s%s *options, void *, String );\n", command->specification.fileClose   , command->specification.name, typeName  );

   puts( "/* END */" );
}

static const Formater formatH = {
   .comment         = "//",
   .formatStartMain = &formatStartMainH,
   .formatTitle     = &formatTitleH    ,
   .formatOption    = &formatOptionH   ,
   .formatEndMain   = &formatEndMainH
};

/****************************************************************************************************/
/*                                                                                                  */
/* formatC:                                                                                         */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainC( const LocalCommand *command ) {
   puts(
      "#include <stdio.h>\n"
      "#include <getopt.h>\n"
      "#include <stdlib.h>\n\n"

      "#include \"siliconBrainOption\"\n"
   );

   if( command->structureType == isCommand ) {
      printf( "#include \"%s\"\n\n", command->specification.name  );

      printf( "extern const char *%sSiliconBrainRelease;\n"      , command->specification.name );
      printf( "extern const char *%sSiliconBrainRcsIdentifier;\n", command->specification.name );
      printf( "extern const char *%sSiliconBrainSaveStamp;\n\n"  , command->specification.name );

   } else {
      puts(   "#include \"record\"" );
      printf( "#include \"%s.record\"\n\n", command->specification.name  );
   }

   puts( "int main( int argc, char *argv[] ) {" );
}

static void formatTitleC( const LocalCommand *command ) {

   if( command->structureType == isOption ) {
      puts(
         "   Obstack obstack;\n"
         "   obstack_init( &obstack );\n"
      );

      printf( "   %sOptions options = {0};\n", command->specification.name );
   } else {
      printf( "   %sFields fields = {0};\n", command->specification.name );
      fflush( stdout );
      system ( "record.specification | generate --optionMeat" );
      return;
   }

   /*---------------------------------------*/
   /* build options short descriptor string */
   /*---------------------------------------*/
   fputs( "   static const char optionsShortDescriptor[] = \"", stdout );
   forEachOption(
      if( option->oneCharacterName ) {
         putchar( option->oneCharacterName );
         if( option->type != optionFlag ) putchar( ':' );
      }
   );
   puts( "\";" );

   /*--------------------------------------*/
   /* build options long descriptor string */
   /*--------------------------------------*/
   puts( "   static const struct option optionsLongDescriptor[] = {" );

   int numberOfOptions = 0;

   forEachOption(
      printf(
         "      {\n"
         "         .name    = \"%s\",\n"
         "         .has_arg = optional_argument\n"
         "      },\n",
         option->name
      );
      ++numberOfOptions;
   );

   forEachOption(
      if( option->type == optionFlag ) {
         printf(
            "      {\n"
            "         .name    = \"no-%s\",\n"
            "         .has_arg = no_argument\n"
            "      },\n",
            option->name
         );
      }
   );

   puts(
      "      { 0 }\n"
      "   };\n"
   );

   /*----------------------------------------*/
   /* build options siliconBrains descriptor */
   /*----------------------------------------*/
   puts( "   OptionProcessed optionProcessed[] = {" );

   forEachOption(
      printf(
         "      {\n"
         "         .name              = optionsLongDescriptor[ %d ].name,\n"
         "         .alreadyConfigured = false,\n"
         "         .value             = &options.%s,\n"
         "         .optionType        = %s\n"
         "      },\n",
         optionIndex,
         option->name,
         option->type == optionFlag ? "optionFlag" : "optionValue"
      );
   );
   puts(
      "      { 0 }\n"
      "   };\n"
   );

   puts(
      "   int optionIndex = 0;\n"
      "   int optionCharacter;\n\n"

      "   while( 1 ) {\n"
      "      optionCharacter = getopt_long( argc, argv, optionsShortDescriptor, optionsLongDescriptor, &optionIndex );\n\n"

      "      if( optionCharacter == -1 ) break;\n\n"

      "      switch( optionCharacter ) {\n"
      "         case 0: break;\n"
   );

   forEachOption(
      if( option->oneCharacterName )
         printf(
            "         case '%c': optionIndex = %d; break;\n",
            option->oneCharacterName,
            optionIndex
         );
   );
   puts(
      "         case '?': exit( 42 );\n"
      "         default: exit( 43 );\n"
      "      };\n"
   );

   puts( "      if( !optarg ) optarg = \"\";\n" );

   puts( "      bool flagNegation;\n" );

   printf( "      int numberOfOptions = %d;\n", numberOfOptions );

   puts(
      "      if( optionIndex >= numberOfOptions ) {\n"
      "         switch( optionIndex - numberOfOptions ) {\n"
   );

   int flagOptionIndex = 0;

   forEachOption(
      if( option->type == optionFlag ) {
         printf(
            "         case %d: optionIndex = %d; break;\n",
            flagOptionIndex, optionIndex
         );
         ++flagOptionIndex;
      }
   );

   puts(
      "         }\n"
      "         flagNegation = true;\n"
      "      } else {\n"
      "         flagNegation = false;\n"
      "      }\n"
   );

   puts( "      optionProcessed[ optionIndex ].alreadyConfigured = true;" );

   puts( "      switch( optionIndex ) {\n" );

   forEachOption(
      printf( "      case %d:\n", optionIndex );
      if( option->type == optionFlag ) {
         printf( "         options.%s = stringToBool( optarg );\n", option->name );
         printf(
            "         if( flagNegation )\n"
            "            options.%s = !options.%s;\n"
            ,option->name, option->name
         );
      } else {
         printf( "         options.%s = optarg;\n", option->name );
      }

      puts( "         break;\n" );
   );

   puts(
      "      default: exit( 44 );\n"
      "      }\n"
      "   }\n"
   );

   puts(
      "\n"
      "   if( !options.complete ) {\n"
   );

   const char * const package = getenv( "siliconBrainPackageName" );

   forEachOption(
      printf( "      if( !optionProcessed[ %d ].alreadyConfigured && (optarg = getenv( \"%s_%s\" )) ) {\n", optionIndex, package, option->name );
      if( option->type == optionFlag ) {
         printf( "         options.%s = stringToBool( optarg );\n", option->name );
      } else {
         printf( "         options.%s = optarg;\n", option->name );
      }

      printf( "         optionProcessed[ %d ].alreadyConfigured = true;\n", optionIndex );
      puts( "      }\n" );
   );

   puts(
      "   }\n"
   );

   printf( "if( !options.complete ) optionReadOptions( optionProcessed, &obstack, \"%s\" );\n\n", package );
}

static void formatOptionC( const Option *option, int optionIndex ) {
}

static void formatEndMainC( const LocalCommand *command ) {

   /*----------------------------------------*/
   /* generate handling of the `help' option */
   /*----------------------------------------*/
   puts( "   if( options.help ) {" );

   printf( "      puts( \"%s\" );\n", command->specification.title );

   int optionNameLength = 0;
   int optionNameLengthCandidate = 0;

   forEachOption(
      optionNameLengthCandidate = strlen( option->name );
      optionNameLength >= optionNameLengthCandidate || (optionNameLength = optionNameLengthCandidate);
   );

   if( command->structureType == isCommand ) {
      puts( "      puts(\n         \"Invoke it like:\\n\"" );

      forEachOption(
         printf( "         \"   --%-*.*s %c%c (%s) %s\\n\"\n",
            optionNameLength,
            optionNameLength,
            option->name,
            option->oneCharacterName ? '-' : ' ',
            option->oneCharacterName ? option->oneCharacterName : ' ',
            option->type == optionFlag ? "flag " : "value",
            option->shortDescription
         );
      );
   } else {
      puts( "      puts(\n         \"The fields:\\n\"" );

      forEachOption(
         printf( "         \"   %-*.*s (%s) %s\\n\"\n",
            optionNameLength,
            optionNameLength,
            option->name,
            option->typeName,
            option->shortDescription
         );
      );

      {
         FILE *recordHelp = popen( "record --help", "r" );
         int recordLength = 200;
         char *recordLine = malloc( recordLength );
         char *lineIterator;

         while( -1 != getline( &recordLine, &recordLength, recordHelp ) ) {
            printu( "         \"" );
            for( lineIterator = recordLine; *lineIterator; ++lineIterator ) {
               switch( *lineIterator ) {
                  case '\n': printu( "\\n"  ); break;
                  case '\t': printu( "\\t"  ); break;
                  case '\\': printu( "\\\\" ); break;
                  case '"' : printu( "\\\"" ); break;
                  default  : putchar( *lineIterator );
               }
            }
            printu( "\"\n" );
         }
         free( recordLine );
      }
   }

   puts(
      "      );\n"
      "      exit( 0 );\n"
      "   }"
   );

   /*-------------------------------------------*/
   /* generate handling of the `version' option */
   /*-------------------------------------------*/
   puts(
      "   if( options.version ) {\n"
      "      puts( versionString );"
   );

   if( command->structureType == isCommand ) {
      printf(
         "      puts( \"// Implementation:\" );\n"
         "      printf(\n"
         "         \"//    %%s\\n\"\n"
         "         \"//    %%s\\n\"\n"
         "         \"//    %%s\\n\",\n"
         "         %sSiliconBrainRelease,\n"
         "         %sSiliconBrainRcsIdentifier,\n"
         "         %sSiliconBrainSaveStamp\n"
         "      );",
         command->specification.name,
         command->specification.name,
         command->specification.name
      );
   }

   puts(
      "      exit( 0 );\n"
      "   }"
   );

   /*-------------------------------------------*/
   /* call the command implementinging function */
   /*-------------------------------------------*/
   puts( "   CommandReturnCode returnCode = commandOk;\n" );

   if( command->isFileCommand ) {
      printu( "   {\n"
              "      FileReaderContext context;\n" );

      printf( "      context.openCommand  = (void *)%s;\n", command->specification.commandOpen  ?: "0" );
      printf( "      context.closeCommand = (void *)%s;\n", command->specification.commandClose ?: "0" );
      printf( "      context.openFile     = (void *)%s;\n", command->specification.fileOpen     ?: "0" );
      printf( "      context.closeFile    = (void *)%s;\n", command->specification.fileClose    ?: "0" );

      printf( "      context.userCommand = (UserCommand)&%s;\n", command->specification.name );
      printu( "      returnCode = fileReader( &context, &options, argc-optind, argv + optind );\n"
              "   }\n" );
   }
   else if( command->structureType == isOption )
      printf( "   returnCode = %s( &options );\n", command->specification.name  );
   else
      printf( "   returnCode = %s( &fields );\n" , command->specification.name  );

   puts( "   return returnCode;\n}" );
}

static const Formater formatC = {
   .comment         = "//",
   .formatStartMain = &formatStartMainC,
   .formatTitle     = &formatTitleC    ,
   .formatOption    = &formatOptionC   ,
   .formatEndMain   = &formatEndMainC  ,
   .versionFormat   =
      "static const char versionString[] =\n"
      "   \"%s %s\\n\"\n"
      "   \"\\n\"\n"
      "   \"%s generated at: %s\\n\"\n"
      "   \"%s in directory: %s\\n\"\n"
      "   \"%s on host     : %s\\n\"\n"
      "   \"%s by user     : %s, %s\\n\"\n"
      "   \"\\n\"\n"
      "   \"%s generated for specification:\\n\"\n"
      "   \"%s    %s\\n\"\n"
      "   \"%s    %s\\n\"\n"
      "   \"%s    %s\\n\"\n"
      "   \"\\n\"\n"
      "   \"%s generated with generator:\\n\"\n"
      "   \"%s    %s\\n\"\n"
      "   \"%s    %s\\n\"\n"
      "   \"%s    %s\\n\";\n"
};

/****************************************************************************************************/
/*                                                                                                  */
/* formatConfigurationReader:                                                                       */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainConfigurationReader( const LocalCommand *command ) {
   puts(
      "#include <stdio.h>\n"
      "#include <getopt.h>\n"
      "#include <stdlib.h>\n"
      "#include <obstack.h>\n"
      "\n"
      "#define obstack_chunk_alloc malloc\n"
      "#define obstack_chunk_free  free\n"
      "\n"
      "#define siliconBrainPrintShortNames\n"
      "#include \"siliconBrainLib\"\n"
      "#include \"siliconBrainOption\""
   );

   generateAll( &formatH, command );

   printf( "extern const char *%sSiliconBrainRelease;\n"      , command->specification.name );
   printf( "extern const char *%sSiliconBrainRcsIdentifier;\n", command->specification.name );
   printf( "extern const char *%sSiliconBrainSaveStamp;\n\n"  , command->specification.name );

   puts(
      "int main( int argc, char *argv[] ) {\n"
      "\n"
      "   SiliconBrainPrinter siliconBrainPrinter;\n"
  );
}

static void formatTitleConfigurationReader( const LocalCommand *command ) {
   formatTitleC( command );

   int completeOptionIndex = -1;

   forEachOption(
      if( strequ( option->name, "complete" ) ) {
         completeOptionIndex = optionIndex;
         goto completeOptionIndexFound;
      }
   );
 completeOptionIndexFound:

   printf(
      "   options.complete = true;\n"
      "   optionProcessed[ %d ].alreadyConfigured = true;\n\n", completeOptionIndex
   );

   puts(
      "   while( *argv && !strequ( *argv, \"--\" ) ) {\n"
      "      --argc;\n"
      "      ++argv;\n"
      "   }\n"
      "\n"
      "   siliconBrainPrinterInit( &siliconBrainPrinter, argc, (const char **)argv );\n"
   );

   printf( "   comment( \"%s\\n%s\" );\n", command->specification.title, command->specification.shortDescription );

   printf( "   tag( \"%s\" );\n", getenv( "siliconBrainPackageName" ) );
}

static void formatOptionConfigurationReader( const Option *option, int optionIndex ) {

   printf(    "      comment( \"%s (%s) : %s\" );\n",
      option->name,
      option->type == optionFlag ? "flag " : "value",
      option->shortDescription
   );

   printf(    "      if( optionProcessed[ %d ].alreadyConfigured ) {\n", optionIndex );

   if( option->type == optionFlag ) {
      printf( "         keyBool( \"%s\",\n", option->name );
   } else {
      printf( "         key( \"%s\",\n", option->name );
   }

   printf( "            options.%s\n", option->name  );
   puts(
              "         );\n"
              "      }\n"
   );
}

static void formatEndMainConfigurationReader( const LocalCommand *command ) {
   puts(
      "   end();\n"
      "\n"
      "   return 0;\n}"
   );
}

static const Formater formatConfigurationReader = {
   .comment         = "//",
   .formatStartMain = &formatStartMainConfigurationReader,

   .formatTitle     = &formatTitleConfigurationReader    ,
   .formatOption    = &formatOptionConfigurationReader   ,
   .formatEndMain   = &formatEndMainConfigurationReader  ,
};

/****************************************************************************************************/
/*                                                                                                  */
/* formatCommand:                                                                                   */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainCommand( const LocalCommand *command ) {
   puts( "/* generated Command-file. -*-c-*- */\n" );
}

static void formatTitleCommand( const LocalCommand *command ) {
   puts( "\n\n#include <stdbool.h>" );
   puts( "#include <stdio.h>\n" );
   printf( "#include \"%s.record\"\n\n", command->specification.name );
   printf( "int %s( const %sFields *fields ) {\n", command->specification.name, command->specification.name );
   puts(   "   puts( \"High Du!\" );" );
   puts(   "   return 0;" );
   puts(   "}" );
}

static void formatOptionCommand( const Option *option, int optionIndex ) {
}

static void formatEndMainCommand( const LocalCommand *command ) {
}

static const Formater formatCommand = {
   .comment         = "//",
   .formatStartMain = &formatStartMainCommand,
   .formatTitle     = &formatTitleCommand    ,
   .formatOption    = &formatOptionCommand   ,
   .formatEndMain   = &formatEndMainCommand
};

/****************************************************************************************************/
/*                                                                                                  */
/* formatPlain:                                                                                     */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainPlain( const LocalCommand *command ) {
   puts( "This is the specification for a command" );
}

static void formatTitlePlain( const LocalCommand *command ) {
   printf( "Title: \"%s\"\n", command->specification.title            );
   printf( "short: \"%s\"\n", command->specification.shortDescription );
   printf( "long : \"%s\"\n", command->specification.longDescription  );
}

static void formatOptionPlain( const Option *option, int optionIndex ) {
   printf( "\nOption:\n   Name: %s\n", option->name );
   printf(
      "      --%s %c%c (%s)\n"
      "%s\n%s\n\n",
      option->name,
      option->oneCharacterName ? '-' : ' ',
      option->oneCharacterName ? option->oneCharacterName : ' ',
      option->type == optionFlag ? "flag " : "value",
      option->shortDescription,
      option->longDescription ? : ""
   );
}

static void formatEndMainPlain( const LocalCommand *command ) {
   puts( "End" );
}

static const Formater formatPlain = {
   .comment         = "",
   .formatStartMain = &formatStartMainPlain,
   .formatTitle     = &formatTitlePlain    ,
   .formatOption    = &formatOptionPlain   ,
   .formatEndMain   = &formatEndMainPlain
};

/****************************************************************************************************/
/*                                                                                                  */
/* formatTexinfo:                                                                                   */
/*                                                                                                  */
/****************************************************************************************************/
static void formatStartMainTexinfo( const LocalCommand *command ) {

   char* recordSuffix = (command->structureType == isRecord ? ".record" : "");

   printf( "@node %s%s\n"   , command->specification.name, recordSuffix );
   printf( "@section %s%s\n", command->specification.name, recordSuffix );
   printf( "%s\n"           , command->specification.title              );
}

static void formatTitleTexinfo( const LocalCommand *command ) {
   puts( "@subsection Description" );
   printf( "%s\n\n", command->specification.shortDescription );
   printf( "%s\n\n", command->specification.longDescription  );
   printf(
     "@subsection %s\n"
     "@table @samp\n\n",
     command->structureType == isRecord ? "Fields" : "Options"
   );
}

static void formatOptionTexinfo( const Option *option, int optionIndex ) {

   if( option->elementType == isOption ) {
      printf(
         "@item --%s %c%c (%s)\n"
         "%s\n%s\n\n",
         option->name,
         option->oneCharacterName ? '-' : ' ',
         option->oneCharacterName ? option->oneCharacterName : ' ',
         option->type == optionFlag ? "flag " : "value",
         option->shortDescription,
         option->longDescription ? : ""
      );
   } else {
      printf(
         "@item %s (%s)\n"
         "%s\n%s\n\n",
         option->name,
         option->typeName,
         option->shortDescription,
         option->longDescription ? : ""
      );
   }
}

static void formatEndMainTexinfo( const LocalCommand *command ) {
   puts( "@end table" );
}

static const Formater formatTexinfo = {
   .comment         = "@c",
   .formatStartMain = &formatStartMainTexinfo,
   .formatTitle     = &formatTitleTexinfo    ,
   .formatOption    = &formatOptionTexinfo   ,
   .formatEndMain   = &formatEndMainTexinfo
};

/****************************************************************************************************/
/*                                                                                                  */
/* generateAll: produce all output.                                                                 */
/*                                                                                                  */
/****************************************************************************************************/
static void generateAll( const Formater *formater, const LocalCommand *command ) {

   Specification generatorSpecification = {
      .release       = siliconBrainRelease,
      .rcsIdentifier = siliconBrainRcsIdentifier,
      .saveStamp     = siliconBrainSaveStamp
   };

   (*formater->formatStartMain)( command );

   formatHeader( formater->comment, &command->specification, &generatorSpecification, formater->versionFormat );

   (*formater->formatTitle    )( command );

   forEachOption(
      (*formater->formatOption)( option, optionIndex );
   )

   (*formater->formatEndMain  )( command );
}

/****************************************************************************************************/
/*                                                                                                  */
/* addStandardOptions:                                                                              */
/*                                                                                                  */
/****************************************************************************************************/
static void addStandardOptions( LocalCommand *command ) {
   static const Option standardOptions[] = {
      {
         .name = "help",
         .shortDescription = "Print a short help message, listing the options.",
         .longDescription  =
            "Just a short listing of all available options. In case of records "
            "(AKA record commands) before the options a list of its fields "
            "is printed. For deeper and more information use man or better "
            "info.",
         .oneCharacterName = 'h',
         .elementType = isOption,
         .type = optionFlag
      },
      {
         .name = "verbose",
         .shortDescription = "Let this command talk to you a lot.",
         .longDescription  =
            "When this option is set, the command produces a lot of output. "
            "This output is written to stderr. This makes it possible to use the "
            "verbose mode even, if the actual data is transported via stdout. Without "
            "this options the comannd is more or less quiet, except when errors ocurre.",
         .oneCharacterName = 'v',
         .elementType = isOption,
         .type = optionFlag
      },
      {
         .name = "version",
         .shortDescription = "Display version information.",
         .longDescription  =
            "The version information available for command is printed. This is the "
            "release number, the RCS identifier and the date of last save the source. This three "
            "values are given for the command specification and for the command implementation.",
         .elementType = isOption,
         .type = optionFlag
      },
      {
         .name = "output",
         .shortDescription = "File to which output is written.",
         .longDescription  =
            "Says to which file the output should be written. "
            "In future it will be extended by a complex data direction "
            "system.",
         .oneCharacterName = 'o',
         .elementType = isOption,
         .type = optionValue
      },
      {
         .name = "complete",
         .shortDescription = "Indicate completeness of specified options. No further lookup in configuration chain.",
         .longDescription  =
            "The configuration chain is a number of locations, in which the options processing is looking for specifications of command "
            "and package options (all package options are command options as well). These locations are looked up in a predefined sequence. "
            "This is the configuration chain. An option is taken from the first location where it is found. The first location to look is the "
            "command line options followed by the environment variables then a configuration placed in the current directory, home directory, /etc. "
            "For performance reasons or other, the --complete option stops further lookup in the configuation chain. For example using --complete "
            "in the command line will avoid reading the environment. The configurationReader will always set this option, because it has read the "
            "complete chain already.",
         .elementType = isOption,
         .type = optionFlag
      },
   };

   const Option *begin = &standardOptions[ 0                                            ];
   const Option *end   = &standardOptions[ sizeof( standardOptions ) / sizeof( Option ) ];

   const Option *source;
   Option *target;

   for( source = begin; source != end; ++source ) {
      target = listAppend( &command->options );
      memcpy( target, source, sizeof( *target ) );
   }
}

/****************************************************************************************************/
/*                                                                                                  */
/* main:                                                                                            */
/*                                                                                                  */
/****************************************************************************************************/
extern int main( int argc, char *argv[] ) {

   bool optionMeat = (argv[ 1 ] && !strcmp( argv[ 1 ], "--optionMeat" ));

   char    xmlStreamChunk[ 1234 ];
   size_t  xmlStreamChunkLength;
   GError *error;

   LocalCommand command = {
      .specification = {
         .name              = 0,
         .title             = 0,
         .shortDescription  = 0,
         .longDescription   = 0,
         .release           = 0,
         .rcsIdentifier     = 0,
         .saveStamp         = 0,
         .commandOpen       = 0,
         .commandClose      = 0,
         .fileOpen          = 0,
         .fileClose         = 0,
      },
      .withoutStandardOptions = false,
      .isFileCommand          = false
   };

   LocalXmlUserData xmlUserData = {
      .command = &command,
      .option  = 0,
      .state   = localContextNo,
      .first   = true
   };

   GMarkupParseContext *context = g_markup_parse_context_new( &parser, 0, &xmlUserData, 0 );

   listOpen( &command.options, sizeof( Option ) );

   while( xmlStreamChunkLength = fread( xmlStreamChunk, 1, sizeof( xmlStreamChunk ) - 1, stdin ) )
      g_markup_parse_context_parse( context, xmlStreamChunk, xmlStreamChunkLength, &error);

   xmlUserData.first = false;

   if(
      command.structureType == isCommand &&
      !command.withoutStandardOptions    &&
      !strequ( argv[ 1 ], "--configurationReader" )
   ) {
      FILE *packageSpecificationFile = 0;

      char *specificationName;
      asprintf( &specificationName, "temporary/%s.specification", getenv( "siliconBrainPackageName" ) );

      packageSpecificationFile = popen( specificationName, "r" );

      if( packageSpecificationFile ) {
         while( xmlStreamChunkLength = fread( xmlStreamChunk, 1, sizeof( xmlStreamChunk ) - 1, packageSpecificationFile ) )
            g_markup_parse_context_parse( context, xmlStreamChunk, xmlStreamChunkLength, &error);

         pclose( packageSpecificationFile );
      }
      free( specificationName );

   }

   g_markup_parse_context_end_parse( context, &error );
   g_markup_parse_context_free     ( context         );

   if( command.structureType == isCommand )
      addStandardOptions( &command );

   /*---------------------------------------------------------------------------*/
   /* Hack, t reuse the option handling of the record.command for other records */
   /*---------------------------------------------------------------------------*/
   if( optionMeat ) {
      formatTitleC( &command );
   }
   else {
      /*----------------------------------*/
      /* standard generator of everything */
      /*----------------------------------*/
      generateAll(
         argv[ 1 ] ?
            !strcmp( argv[ 1 ], "--c"                   ) ? &formatC                   :
            !strcmp( argv[ 1 ], "--h"                   ) ? &formatH                   :
            !strcmp( argv[ 1 ], "--texinfo"             ) ? &formatTexinfo             :
            !strcmp( argv[ 1 ], "--command"             ) ? &formatCommand             :
            !strcmp( argv[ 1 ], "--configurationReader" ) ? &formatConfigurationReader :
            &formatPlain
         : &formatPlain,
         &command
      );
   }

   listClose( &command.options );

   return 0;
}

/*
$Log: generate.main.c,v $
Revision 1.27  2004/12/14 23:31:26  joerg
published for new release 0.2.3

Revision 1.26  2004/12/14 23:17:05  joerg
published for new release 0.2.2

Revision 1.25  2004/12/14 22:42:22  joerg
allFiles: all sources have a Log CVS keyword at the end now.

*/