OpenTTD
settingsgen.cpp
Go to the documentation of this file.
1 /* $Id: settingsgen.cpp 26506 2014-04-24 19:51:45Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "../stdafx.h"
13 #include "../string_func.h"
14 #include "../strings_type.h"
15 #include "../misc/getoptdata.h"
16 #include "../ini_type.h"
17 #include "../core/smallvec_type.hpp"
18 
19 #include <stdarg.h>
20 
21 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #endif
25 
26 #ifdef __MORPHOS__
27 #ifdef stderr
28 #undef stderr
29 #endif
30 #define stderr stdout
31 #endif /* __MORPHOS__ */
32 
33 #include "../safeguards.h"
34 
40 void NORETURN CDECL error(const char *s, ...)
41 {
42  char buf[1024];
43  va_list va;
44  va_start(va, s);
45  vseprintf(buf, lastof(buf), s, va);
46  va_end(va);
47  fprintf(stderr, "FATAL: %s\n", buf);
48  exit(1);
49 }
50 
51 static const int OUTPUT_BLOCK_SIZE = 16000;
52 
54 class OutputBuffer {
55 public:
57  void Clear()
58  {
59  this->size = 0;
60  }
61 
68  int Add(const char *text, int length)
69  {
70  int store_size = min(length, OUTPUT_BLOCK_SIZE - this->size);
71  assert(store_size >= 0);
72  assert(store_size <= OUTPUT_BLOCK_SIZE);
73  MemCpyT(this->data + this->size, text, store_size);
74  this->size += store_size;
75  return store_size;
76  }
77 
82  void Write(FILE *out_fp) const
83  {
84  if (fwrite(this->data, 1, this->size, out_fp) != (size_t)this->size) {
85  fprintf(stderr, "Error: Cannot write output\n");
86  }
87  }
88 
93  bool HasRoom() const
94  {
95  return this->size < OUTPUT_BLOCK_SIZE;
96  }
97 
98  int size;
100 };
101 
103 class OutputStore {
104 public:
105  OutputStore()
106  {
107  this->Clear();
108  }
109 
111  void Clear()
112  {
113  this->output_buffer.Clear();
114  }
115 
121  void Add(const char *text, int length = 0)
122  {
123  if (length == 0) length = strlen(text);
124 
125  if (length > 0 && this->BufferHasRoom()) {
126  int stored_size = this->output_buffer[this->output_buffer.Length() - 1].Add(text, length);
127  length -= stored_size;
128  text += stored_size;
129  }
130  while (length > 0) {
131  OutputBuffer *block = this->output_buffer.Append();
132  block->Clear(); // Initialize the new block.
133  int stored_size = block->Add(text, length);
134  length -= stored_size;
135  text += stored_size;
136  }
137  }
138 
143  void Write(FILE *out_fp) const
144  {
145  for (const OutputBuffer *out_data = this->output_buffer.Begin(); out_data != this->output_buffer.End(); out_data++) {
146  out_data->Write(out_fp);
147  }
148  }
149 
150 private:
155  bool BufferHasRoom() const
156  {
157  uint num_blocks = this->output_buffer.Length();
158  return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
159  }
160 
163 };
164 
165 
173  SettingsIniFile(const char * const *list_group_names = NULL, const char * const *seq_group_names = NULL) :
175  {
176  }
177 
178  virtual FILE *OpenFile(const char *filename, Subdirectory subdir, size_t *size)
179  {
180  /* Open the text file in binary mode to prevent end-of-line translations
181  * done by ftell() and friends, as defined by K&R. */
182  FILE *in = fopen(filename, "rb");
183  if (in == NULL) return NULL;
184 
185  fseek(in, 0L, SEEK_END);
186  *size = ftell(in);
187 
188  fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
189  return in;
190  }
191 
192  virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
193  {
194  error("%s%s%s", pre, buffer, post);
195  }
196 };
197 
199 
200 static const char *PREAMBLE_GROUP_NAME = "pre-amble";
201 static const char *POSTAMBLE_GROUP_NAME = "post-amble";
202 static const char *TEMPLATES_GROUP_NAME = "templates";
203 static const char *DEFAULTS_GROUP_NAME = "defaults";
204 
211 static IniLoadFile *LoadIniFile(const char *filename)
212 {
213  static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, NULL};
214 
215  IniLoadFile *ini = new SettingsIniFile(NULL, seq_groups);
216  ini->LoadFromDisk(filename, NO_DIRECTORY);
217  return ini;
218 }
219 
225 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
226 {
227  IniGroup *grp = ifile->GetGroup(group_name, 0, false);
228  if (grp != NULL && grp->type == IGT_SEQUENCE) {
229  for (IniItem *item = grp->item; item != NULL; item = item->next) {
230  if (item->name) {
231  _stored_output.Add(item->name);
232  _stored_output.Add("\n", 1);
233  }
234  }
235  }
236 }
237 
245 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
246 {
247  IniItem *item = grp->GetItem(name, false);
248  if (item == NULL && defaults != NULL) item = defaults->GetItem(name, false);
249  if (item == NULL || item->value == NULL) return NULL;
250  return item->value;
251 }
252 
257 static void DumpSections(IniLoadFile *ifile)
258 {
259  static const int MAX_VAR_LENGTH = 64;
260  static const char * const special_group_names[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, NULL};
261 
262  IniGroup *default_grp = ifile->GetGroup(DEFAULTS_GROUP_NAME, 0, false);
263  IniGroup *templates_grp = ifile->GetGroup(TEMPLATES_GROUP_NAME, 0, false);
264  if (templates_grp == NULL) return;
265 
266  /* Output every group, using its name as template name. */
267  for (IniGroup *grp = ifile->group; grp != NULL; grp = grp->next) {
268  const char * const *sgn;
269  for (sgn = special_group_names; *sgn != NULL; sgn++) if (strcmp(grp->name, *sgn) == 0) break;
270  if (*sgn != NULL) continue;
271 
272  IniItem *template_item = templates_grp->GetItem(grp->name, false); // Find template value.
273  if (template_item == NULL || template_item->value == NULL) {
274  fprintf(stderr, "settingsgen: Warning: Cannot find template %s\n", grp->name);
275  continue;
276  }
277 
278  /* Prefix with #if/#ifdef/#ifndef */
279  static const char * const pp_lines[] = {"if", "ifdef", "ifndef", NULL};
280  int count = 0;
281  for (const char * const *name = pp_lines; *name != NULL; name++) {
282  const char *condition = FindItemValue(*name, grp, default_grp);
283  if (condition != NULL) {
284  _stored_output.Add("#", 1);
285  _stored_output.Add(*name);
286  _stored_output.Add(" ", 1);
287  _stored_output.Add(condition);
288  _stored_output.Add("\n", 1);
289  count++;
290  }
291  }
292 
293  /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
294  const char *txt = template_item->value;
295  while (*txt != '\0') {
296  if (*txt != '$') {
297  _stored_output.Add(txt, 1);
298  txt++;
299  continue;
300  }
301  txt++;
302  if (*txt == '$') { // Literal $
303  _stored_output.Add(txt, 1);
304  txt++;
305  continue;
306  }
307 
308  /* Read variable. */
309  char variable[MAX_VAR_LENGTH];
310  int i = 0;
311  while (i < MAX_VAR_LENGTH - 1) {
312  if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
313  variable[i] = txt[i];
314  i++;
315  }
316  variable[i] = '\0';
317  txt += i;
318 
319  if (i > 0) {
320  /* Find the text to output. */
321  const char *valitem = FindItemValue(variable, grp, default_grp);
322  if (valitem != NULL) _stored_output.Add(valitem);
323  } else {
324  _stored_output.Add("$", 1);
325  }
326  }
327  _stored_output.Add("\n", 1); // \n after the expanded template.
328  while (count > 0) {
329  _stored_output.Add("#endif\n");
330  count--;
331  }
332  }
333 }
334 
340 static void CopyFile(const char *fname, FILE *out_fp)
341 {
342  if (fname == NULL) return;
343 
344  FILE *in_fp = fopen(fname, "r");
345  if (in_fp == NULL) {
346  fprintf(stderr, "settingsgen: Warning: Cannot open file %s for copying\n", fname);
347  return;
348  }
349 
350  char buffer[4096];
351  size_t length;
352  do {
353  length = fread(buffer, 1, lengthof(buffer), in_fp);
354  if (fwrite(buffer, 1, length, out_fp) != length) {
355  fprintf(stderr, "Error: Cannot copy file\n");
356  break;
357  }
358  } while (length == lengthof(buffer));
359 
360  fclose(in_fp);
361 }
362 
369 static bool CompareFiles(const char *n1, const char *n2)
370 {
371  FILE *f2 = fopen(n2, "rb");
372  if (f2 == NULL) return false;
373 
374  FILE *f1 = fopen(n1, "rb");
375  if (f1 == NULL) error("can't open %s", n1);
376 
377  size_t l1, l2;
378  do {
379  char b1[4096];
380  char b2[4096];
381  l1 = fread(b1, 1, sizeof(b1), f1);
382  l2 = fread(b2, 1, sizeof(b2), f2);
383 
384  if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
385  fclose(f2);
386  fclose(f1);
387  return false;
388  }
389  } while (l1 != 0);
390 
391  fclose(f2);
392  fclose(f1);
393  return true;
394 }
395 
397 static const OptionData _opts[] = {
398  GETOPT_NOVAL( 'v', "--version"),
399  GETOPT_NOVAL( 'h', "--help"),
400  GETOPT_GENERAL('h', '?', NULL, ODF_NO_VALUE),
401  GETOPT_VALUE( 'o', "--output"),
402  GETOPT_VALUE( 'b', "--before"),
403  GETOPT_VALUE( 'a', "--after"),
404  GETOPT_END(),
405 };
406 
427 static void ProcessIniFile(const char *fname)
428 {
429  IniLoadFile *ini_data = LoadIniFile(fname);
430  DumpGroup(ini_data, PREAMBLE_GROUP_NAME);
431  DumpSections(ini_data);
432  DumpGroup(ini_data, POSTAMBLE_GROUP_NAME);
433  delete ini_data;
434 }
435 
441 int CDECL main(int argc, char *argv[])
442 {
443  const char *output_file = NULL;
444  const char *before_file = NULL;
445  const char *after_file = NULL;
446 
447  GetOptData mgo(argc - 1, argv + 1, _opts);
448  for (;;) {
449  int i = mgo.GetOpt();
450  if (i == -1) break;
451 
452  switch (i) {
453  case 'v':
454  puts("$Revision$");
455  return 0;
456 
457  case 'h':
458  puts("settingsgen - $Revision$\n"
459  "Usage: settingsgen [options] ini-file...\n"
460  "with options:\n"
461  " -v, --version Print version information and exit\n"
462  " -h, -?, --help Print this help message and exit\n"
463  " -b FILE, --before FILE Copy FILE before all settings\n"
464  " -a FILE, --after FILE Copy FILE after all settings\n"
465  " -o FILE, --output FILE Write output to FILE\n");
466  return 0;
467 
468  case 'o':
469  output_file = mgo.opt;
470  break;
471 
472  case 'a':
473  after_file = mgo.opt;
474  break;
475 
476  case 'b':
477  before_file = mgo.opt;
478  break;
479 
480  case -2:
481  fprintf(stderr, "Invalid arguments\n");
482  return 1;
483  }
484  }
485 
486  _stored_output.Clear();
487 
488  for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
489 
490  /* Write output. */
491  if (output_file == NULL) {
492  CopyFile(before_file, stdout);
493  _stored_output.Write(stdout);
494  CopyFile(after_file, stdout);
495  } else {
496  static const char * const tmp_output = "tmp2.xxx";
497 
498  FILE *fp = fopen(tmp_output, "w");
499  if (fp == NULL) {
500  fprintf(stderr, "settingsgen: Warning: Cannot open file %s\n", tmp_output);
501  return 1;
502  }
503  CopyFile(before_file, fp);
504  _stored_output.Write(fp);
505  CopyFile(after_file, fp);
506  fclose(fp);
507 
508  if (CompareFiles(tmp_output, output_file)) {
509  /* Files are equal. tmp2.xxx is not needed. */
510  unlink(tmp_output);
511  } else {
512  /* Rename tmp2.xxx to output file. */
513 #if defined(WIN32) || defined(WIN64)
514  unlink(output_file);
515 #endif
516  if (rename(tmp_output, output_file) == -1) error("rename() failed");
517  }
518  }
519  return 0;
520 }