settingsgen.cpp

Go to the documentation of this file.
00001 /* $Id: settingsgen.cpp 23940 2012-02-12 19:46:40Z smatz $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD 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, version 2.
00006  * OpenTTD 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.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "../stdafx.h"
00013 #include "../string_func.h"
00014 #include "../strings_type.h"
00015 #include "../misc/getoptdata.h"
00016 #include "../ini_type.h"
00017 #include "../core/smallvec_type.hpp"
00018 
00019 #include <stdarg.h>
00020 
00021 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
00022 #include <unistd.h>
00023 #include <sys/stat.h>
00024 #endif
00025 
00026 #ifdef __MORPHOS__
00027 #ifdef stderr
00028 #undef stderr
00029 #endif
00030 #define stderr stdout
00031 #endif /* __MORPHOS__ */
00032 
00038 void NORETURN CDECL error(const char *s, ...)
00039 {
00040   char buf[1024];
00041   va_list va;
00042   va_start(va, s);
00043   vsnprintf(buf, lengthof(buf), s, va);
00044   va_end(va);
00045   fprintf(stderr, "FATAL: %s\n", buf);
00046   exit(1);
00047 }
00048 
00049 static const int OUTPUT_BLOCK_SIZE = 16000; 
00050 
00052 class OutputBuffer {
00053 public:
00055   void Clear()
00056   {
00057     this->size = 0;
00058   }
00059 
00066   int Add(const char *text, int length)
00067   {
00068     int store_size = min(length, OUTPUT_BLOCK_SIZE - this->size);
00069     assert(store_size >= 0);
00070     assert(store_size <= OUTPUT_BLOCK_SIZE);
00071     MemCpyT(this->data + this->size, text, store_size);
00072     this->size += store_size;
00073     return store_size;
00074   }
00075 
00080   void Write(FILE *out_fp) const
00081   {
00082     if (fwrite(this->data, 1, this->size, out_fp) != (size_t)this->size) {
00083       fprintf(stderr, "Error: Cannot write output\n");
00084     }
00085   }
00086 
00091   bool HasRoom() const
00092   {
00093     return this->size < OUTPUT_BLOCK_SIZE;
00094   }
00095 
00096   int size;                     
00097   char data[OUTPUT_BLOCK_SIZE]; 
00098 };
00099 
00101 class OutputStore {
00102 public:
00103   OutputStore()
00104   {
00105     this->Clear();
00106   }
00107 
00109   void Clear()
00110   {
00111     this->output_buffer.Clear();
00112   }
00113 
00119   void Add(const char *text, int length = 0)
00120   {
00121     if (length == 0) length = strlen(text);
00122 
00123     if (length > 0 && this->BufferHasRoom()) {
00124       int stored_size = this->output_buffer[this->output_buffer.Length() - 1].Add(text, length);
00125       length -= stored_size;
00126       text += stored_size;
00127     }
00128     while (length > 0) {
00129       OutputBuffer *block = this->output_buffer.Append();
00130       block->Clear(); // Initialize the new block.
00131       int stored_size = block->Add(text, length);
00132       length -= stored_size;
00133       text += stored_size;
00134     }
00135   }
00136 
00141   void Write(FILE *out_fp) const
00142   {
00143     for (const OutputBuffer *out_data = this->output_buffer.Begin(); out_data != this->output_buffer.End(); out_data++) {
00144       out_data->Write(out_fp);
00145     }
00146   }
00147 
00148 private:
00153   bool BufferHasRoom() const
00154   {
00155     uint num_blocks = this->output_buffer.Length();
00156     return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
00157   }
00158 
00159   typedef SmallVector<OutputBuffer, 2> OutputBufferVector; 
00160   OutputBufferVector output_buffer; 
00161 };
00162 
00163 
00165 struct SettingsIniFile : IniLoadFile {
00171   SettingsIniFile(const char * const *list_group_names = NULL, const char * const *seq_group_names = NULL) :
00172       IniLoadFile(list_group_names, seq_group_names)
00173   {
00174   }
00175 
00176   virtual FILE *OpenFile(const char *filename, Subdirectory subdir, size_t *size)
00177   {
00178     /* Open the text file in binary mode to prevent end-of-line translations
00179      * done by ftell() and friends, as defined by K&R. */
00180     FILE *in = fopen(filename, "rb");
00181     if (in == NULL) return NULL;
00182 
00183     fseek(in, 0L, SEEK_END);
00184     *size = ftell(in);
00185 
00186     fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
00187     return in;
00188   }
00189 
00190   virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
00191   {
00192     error("%s%s%s", pre, buffer, post);
00193   }
00194 };
00195 
00196 OutputStore _stored_output; 
00197 
00198 static const char *PREAMBLE_GROUP_NAME  = "pre-amble";  
00199 static const char *POSTAMBLE_GROUP_NAME = "post-amble"; 
00200 static const char *TEMPLATES_GROUP_NAME = "templates";  
00201 static const char *DEFAULTS_GROUP_NAME  = "defaults";   
00202 
00209 static IniLoadFile *LoadIniFile(const char *filename)
00210 {
00211   static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, NULL};
00212 
00213   IniLoadFile *ini = new SettingsIniFile(NULL, seq_groups);
00214   ini->LoadFromDisk(filename, NO_DIRECTORY);
00215   return ini;
00216 }
00217 
00223 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
00224 {
00225   IniGroup *grp = ifile->GetGroup(group_name, 0, false);
00226   if (grp != NULL && grp->type == IGT_SEQUENCE) {
00227     for (IniItem *item = grp->item; item != NULL; item = item->next) {
00228       if (item->name) {
00229         _stored_output.Add(item->name);
00230         _stored_output.Add("\n", 1);
00231       }
00232     }
00233   }
00234 }
00235 
00243 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
00244 {
00245   IniItem *item = grp->GetItem(name, false);
00246   if (item == NULL && defaults != NULL) item = defaults->GetItem(name, false);
00247   if (item == NULL || item->value == NULL) return NULL;
00248   return item->value;
00249 }
00250 
00255 static void DumpSections(IniLoadFile *ifile)
00256 {
00257   static const int MAX_VAR_LENGTH = 64;
00258   static const char * const special_group_names[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, NULL};
00259 
00260   IniGroup *default_grp = ifile->GetGroup(DEFAULTS_GROUP_NAME, 0, false);
00261   IniGroup *templates_grp  = ifile->GetGroup(TEMPLATES_GROUP_NAME, 0, false);
00262   if (templates_grp == NULL) return;
00263 
00264   /* Output every group, using its name as template name. */
00265   for (IniGroup *grp = ifile->group; grp != NULL; grp = grp->next) {
00266     const char * const *sgn;
00267     for (sgn = special_group_names; *sgn != NULL; sgn++) if (strcmp(grp->name, *sgn) == 0) break;
00268     if (*sgn != NULL) continue;
00269 
00270     IniItem *template_item = templates_grp->GetItem(grp->name, false); // Find template value.
00271     if (template_item == NULL || template_item->value == NULL) {
00272       fprintf(stderr, "settingsgen: Warning: Cannot find template %s\n", grp->name);
00273       continue;
00274     }
00275 
00276     /* Prefix with #if/#ifdef/#ifndef */
00277     static const char * const pp_lines[] = {"if", "ifdef", "ifndef", NULL};
00278     int count = 0;
00279     for (const char * const *name = pp_lines; *name != NULL; name++) {
00280       const char *condition = FindItemValue(*name, grp, default_grp);
00281       if (condition != NULL) {
00282         _stored_output.Add("#", 1);
00283         _stored_output.Add(*name);
00284         _stored_output.Add(" ", 1);
00285         _stored_output.Add(condition);
00286         _stored_output.Add("\n", 1);
00287         count++;
00288       }
00289     }
00290 
00291     /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
00292     const char *txt = template_item->value;
00293     while (*txt != '\0') {
00294       if (*txt != '$') {
00295         _stored_output.Add(txt, 1);
00296         txt++;
00297         continue;
00298       }
00299       txt++;
00300       if (*txt == '$') { // Literal $
00301         _stored_output.Add(txt, 1);
00302         txt++;
00303         continue;
00304       }
00305 
00306       /* Read variable. */
00307       char variable[MAX_VAR_LENGTH];
00308       int i = 0;
00309       while (i < MAX_VAR_LENGTH - 1) {
00310         if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
00311         variable[i] = txt[i];
00312         i++;
00313       }
00314       variable[i] = '\0';
00315       txt += i;
00316 
00317       if (i > 0) {
00318         /* Find the text to output. */
00319         const char *valitem = FindItemValue(variable, grp, default_grp);
00320         if (valitem != NULL) _stored_output.Add(valitem);
00321       } else {
00322         _stored_output.Add("$", 1);
00323       }
00324     }
00325     _stored_output.Add("\n", 1); // \n after the expanded template.
00326     while (count > 0) {
00327       _stored_output.Add("#endif\n");
00328       count--;
00329     }
00330   }
00331 }
00332 
00338 static void CopyFile(const char *fname, FILE *out_fp)
00339 {
00340   if (fname == NULL) return;
00341 
00342   FILE *in_fp = fopen(fname, "r");
00343   if (in_fp == NULL) {
00344     fprintf(stderr, "settingsgen: Warning: Cannot open file %s for copying\n", fname);
00345     return;
00346   }
00347 
00348   char buffer[4096];
00349   size_t length;
00350   do {
00351     length = fread(buffer, 1, lengthof(buffer), in_fp);
00352     if (fwrite(buffer, 1, length, out_fp) != length) {
00353       fprintf(stderr, "Error: Cannot copy file\n");
00354       break;
00355     }
00356   } while (length == lengthof(buffer));
00357 
00358   fclose(in_fp);
00359 }
00360 
00367 static bool CompareFiles(const char *n1, const char *n2)
00368 {
00369   FILE *f2 = fopen(n2, "rb");
00370   if (f2 == NULL) return false;
00371 
00372   FILE *f1 = fopen(n1, "rb");
00373   if (f1 == NULL) error("can't open %s", n1);
00374 
00375   size_t l1, l2;
00376   do {
00377     char b1[4096];
00378     char b2[4096];
00379     l1 = fread(b1, 1, sizeof(b1), f1);
00380     l2 = fread(b2, 1, sizeof(b2), f2);
00381 
00382     if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
00383       fclose(f2);
00384       fclose(f1);
00385       return false;
00386     }
00387   } while (l1 != 0);
00388 
00389   fclose(f2);
00390   fclose(f1);
00391   return true;
00392 }
00393 
00395 static const OptionData _opts[] = {
00396     GETOPT_NOVAL(     'v', "--version"),
00397     GETOPT_NOVAL(     'h', "--help"),
00398   GETOPT_GENERAL('h', '?', NULL, ODF_NO_VALUE),
00399     GETOPT_VALUE(     'o', "--output"),
00400     GETOPT_VALUE(     'b', "--before"),
00401     GETOPT_VALUE(     'a', "--after"),
00402   GETOPT_END(),
00403 };
00404 
00425 static void ProcessIniFile(const char *fname)
00426 {
00427   IniLoadFile *ini_data = LoadIniFile(fname);
00428   DumpGroup(ini_data, PREAMBLE_GROUP_NAME);
00429   DumpSections(ini_data);
00430   DumpGroup(ini_data, POSTAMBLE_GROUP_NAME);
00431   delete ini_data;
00432 }
00433 
00439 int CDECL main(int argc, char *argv[])
00440 {
00441   const char *output_file = NULL;
00442   const char *before_file = NULL;
00443   const char *after_file = NULL;
00444 
00445   GetOptData mgo(argc - 1, argv + 1, _opts);
00446   for (;;) {
00447     int i = mgo.GetOpt();
00448     if (i == -1) break;
00449 
00450     switch (i) {
00451       case 'v':
00452         puts("$Revision$");
00453         return 0;
00454 
00455       case 'h':
00456         puts("settingsgen - $Revision$\n"
00457             "Usage: settingsgen [options] ini-file...\n"
00458             "with options:\n"
00459             "   -v, --version           Print version information and exit\n"
00460             "   -h, -?, --help          Print this help message and exit\n"
00461             "   -b FILE, --before FILE  Copy FILE before all settings\n"
00462             "   -a FILE, --after FILE   Copy FILE after all settings\n"
00463             "   -o FILE, --output FILE  Write output to FILE\n");
00464         return 0;
00465 
00466       case 'o':
00467         output_file = mgo.opt;
00468         break;
00469 
00470       case 'a':
00471         after_file = mgo.opt;
00472         break;
00473 
00474       case 'b':
00475         before_file = mgo.opt;
00476         break;
00477 
00478       case -2:
00479         fprintf(stderr, "Invalid arguments\n");
00480         return 1;
00481     }
00482   }
00483 
00484   _stored_output.Clear();
00485 
00486   for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
00487 
00488   /* Write output. */
00489   if (output_file == NULL) {
00490     CopyFile(before_file, stdout);
00491     _stored_output.Write(stdout);
00492     CopyFile(after_file, stdout);
00493   } else {
00494     static const char * const tmp_output = "tmp2.xxx";
00495 
00496     FILE *fp = fopen(tmp_output, "w");
00497     if (fp == NULL) {
00498       fprintf(stderr, "settingsgen: Warning: Cannot open file %s\n", tmp_output);
00499       return 1;
00500     }
00501     CopyFile(before_file, fp);
00502     _stored_output.Write(fp);
00503     CopyFile(after_file, fp);
00504     fclose(fp);
00505 
00506     if (CompareFiles(tmp_output, output_file)) {
00507       /* Files are equal. tmp2.xxx is not needed. */
00508       unlink(tmp_output);
00509     } else {
00510       /* Rename tmp2.xxx to output file. */
00511 #if defined(WIN32) || defined(WIN64)
00512       unlink(output_file);
00513 #endif
00514       if (rename(tmp_output, output_file) == -1) error("rename() failed");
00515     }
00516   }
00517   return 0;
00518 }