# settingsgen.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    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 }