OpenTTD
settingsgen.cpp
Go to the documentation of this file.
1 /* $Id$ */
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(__CYGWIN__)
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #endif
25 
26 #include "../safeguards.h"
27 
33 void NORETURN CDECL error(const char *s, ...)
34 {
35  char buf[1024];
36  va_list va;
37  va_start(va, s);
38  vseprintf(buf, lastof(buf), s, va);
39  va_end(va);
40  fprintf(stderr, "FATAL: %s\n", buf);
41  exit(1);
42 }
43 
44 static const size_t OUTPUT_BLOCK_SIZE = 16000;
45 
47 class OutputBuffer {
48 public:
50  void Clear()
51  {
52  this->size = 0;
53  }
54 
61  size_t Add(const char *text, size_t length)
62  {
63  size_t store_size = min(length, OUTPUT_BLOCK_SIZE - this->size);
64  assert(store_size <= OUTPUT_BLOCK_SIZE);
65  MemCpyT(this->data + this->size, text, store_size);
66  this->size += store_size;
67  return store_size;
68  }
69 
74  void Write(FILE *out_fp) const
75  {
76  if (fwrite(this->data, 1, this->size, out_fp) != this->size) {
77  fprintf(stderr, "Error: Cannot write output\n");
78  }
79  }
80 
85  bool HasRoom() const
86  {
87  return this->size < OUTPUT_BLOCK_SIZE;
88  }
89 
90  size_t size;
92 };
93 
95 class OutputStore {
96 public:
97  OutputStore()
98  {
99  this->Clear();
100  }
101 
103  void Clear()
104  {
105  this->output_buffer.clear();
106  }
107 
113  void Add(const char *text, size_t length = 0)
114  {
115  if (length == 0) length = strlen(text);
116 
117  if (length > 0 && this->BufferHasRoom()) {
118  size_t stored_size = this->output_buffer[this->output_buffer.size() - 1].Add(text, length);
119  length -= stored_size;
120  text += stored_size;
121  }
122  while (length > 0) {
123  /*C++17: OutputBuffer &block =*/ this->output_buffer.emplace_back();
124  OutputBuffer &block = this->output_buffer.back();
125  block.Clear(); // Initialize the new block.
126  size_t stored_size = block.Add(text, length);
127  length -= stored_size;
128  text += stored_size;
129  }
130  }
131 
136  void Write(FILE *out_fp) const
137  {
138  for (const OutputBuffer &out_data : output_buffer) {
139  out_data.Write(out_fp);
140  }
141  }
142 
143 private:
148  bool BufferHasRoom() const
149  {
150  size_t num_blocks = this->output_buffer.size();
151  return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
152  }
153 
154  typedef std::vector<OutputBuffer> OutputBufferVector;
155  OutputBufferVector output_buffer;
156 };
157 
158 
166  SettingsIniFile(const char * const *list_group_names = nullptr, const char * const *seq_group_names = nullptr) :
167  IniLoadFile(list_group_names, seq_group_names)
168  {
169  }
170 
171  virtual FILE *OpenFile(const char *filename, Subdirectory subdir, size_t *size)
172  {
173  /* Open the text file in binary mode to prevent end-of-line translations
174  * done by ftell() and friends, as defined by K&R. */
175  FILE *in = fopen(filename, "rb");
176  if (in == nullptr) return nullptr;
177 
178  fseek(in, 0L, SEEK_END);
179  *size = ftell(in);
180 
181  fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
182  return in;
183  }
184 
185  virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
186  {
187  error("%s%s%s", pre, buffer, post);
188  }
189 };
190 
192 
193 static const char *PREAMBLE_GROUP_NAME = "pre-amble";
194 static const char *POSTAMBLE_GROUP_NAME = "post-amble";
195 static const char *TEMPLATES_GROUP_NAME = "templates";
196 static const char *DEFAULTS_GROUP_NAME = "defaults";
197 
203 static IniLoadFile *LoadIniFile(const char *filename)
204 {
205  static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, nullptr};
206 
207  IniLoadFile *ini = new SettingsIniFile(nullptr, seq_groups);
208  ini->LoadFromDisk(filename, NO_DIRECTORY);
209  return ini;
210 }
211 
217 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
218 {
219  IniGroup *grp = ifile->GetGroup(group_name, 0, false);
220  if (grp != nullptr && grp->type == IGT_SEQUENCE) {
221  for (IniItem *item = grp->item; item != nullptr; item = item->next) {
222  if (item->name) {
223  _stored_output.Add(item->name);
224  _stored_output.Add("\n", 1);
225  }
226  }
227  }
228 }
229 
237 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
238 {
239  IniItem *item = grp->GetItem(name, false);
240  if (item == nullptr && defaults != nullptr) item = defaults->GetItem(name, false);
241  if (item == nullptr || item->value == nullptr) return nullptr;
242  return item->value;
243 }
244 
249 static void DumpSections(IniLoadFile *ifile)
250 {
251  static const int MAX_VAR_LENGTH = 64;
252  static const char * const special_group_names[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, nullptr};
253 
254  IniGroup *default_grp = ifile->GetGroup(DEFAULTS_GROUP_NAME, 0, false);
255  IniGroup *templates_grp = ifile->GetGroup(TEMPLATES_GROUP_NAME, 0, false);
256  if (templates_grp == nullptr) return;
257 
258  /* Output every group, using its name as template name. */
259  for (IniGroup *grp = ifile->group; grp != nullptr; grp = grp->next) {
260  const char * const *sgn;
261  for (sgn = special_group_names; *sgn != nullptr; sgn++) if (strcmp(grp->name, *sgn) == 0) break;
262  if (*sgn != nullptr) continue;
263 
264  IniItem *template_item = templates_grp->GetItem(grp->name, false); // Find template value.
265  if (template_item == nullptr || template_item->value == nullptr) {
266  fprintf(stderr, "settingsgen: Warning: Cannot find template %s\n", grp->name);
267  continue;
268  }
269 
270  /* Prefix with #if/#ifdef/#ifndef */
271  static const char * const pp_lines[] = {"if", "ifdef", "ifndef", nullptr};
272  int count = 0;
273  for (const char * const *name = pp_lines; *name != nullptr; name++) {
274  const char *condition = FindItemValue(*name, grp, default_grp);
275  if (condition != nullptr) {
276  _stored_output.Add("#", 1);
277  _stored_output.Add(*name);
278  _stored_output.Add(" ", 1);
279  _stored_output.Add(condition);
280  _stored_output.Add("\n", 1);
281  count++;
282  }
283  }
284 
285  /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
286  const char *txt = template_item->value;
287  while (*txt != '\0') {
288  if (*txt != '$') {
289  _stored_output.Add(txt, 1);
290  txt++;
291  continue;
292  }
293  txt++;
294  if (*txt == '$') { // Literal $
295  _stored_output.Add(txt, 1);
296  txt++;
297  continue;
298  }
299 
300  /* Read variable. */
301  char variable[MAX_VAR_LENGTH];
302  int i = 0;
303  while (i < MAX_VAR_LENGTH - 1) {
304  if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
305  variable[i] = txt[i];
306  i++;
307  }
308  variable[i] = '\0';
309  txt += i;
310 
311  if (i > 0) {
312  /* Find the text to output. */
313  const char *valitem = FindItemValue(variable, grp, default_grp);
314  if (valitem != nullptr) _stored_output.Add(valitem);
315  } else {
316  _stored_output.Add("$", 1);
317  }
318  }
319  _stored_output.Add("\n", 1); // \n after the expanded template.
320  while (count > 0) {
321  _stored_output.Add("#endif\n");
322  count--;
323  }
324  }
325 }
326 
332 static void CopyFile(const char *fname, FILE *out_fp)
333 {
334  if (fname == nullptr) return;
335 
336  FILE *in_fp = fopen(fname, "r");
337  if (in_fp == nullptr) {
338  fprintf(stderr, "settingsgen: Warning: Cannot open file %s for copying\n", fname);
339  return;
340  }
341 
342  char buffer[4096];
343  size_t length;
344  do {
345  length = fread(buffer, 1, lengthof(buffer), in_fp);
346  if (fwrite(buffer, 1, length, out_fp) != length) {
347  fprintf(stderr, "Error: Cannot copy file\n");
348  break;
349  }
350  } while (length == lengthof(buffer));
351 
352  fclose(in_fp);
353 }
354 
361 static bool CompareFiles(const char *n1, const char *n2)
362 {
363  FILE *f2 = fopen(n2, "rb");
364  if (f2 == nullptr) return false;
365 
366  FILE *f1 = fopen(n1, "rb");
367  if (f1 == nullptr) {
368  fclose(f2);
369  error("can't open %s", n1);
370  }
371 
372  size_t l1, l2;
373  do {
374  char b1[4096];
375  char b2[4096];
376  l1 = fread(b1, 1, sizeof(b1), f1);
377  l2 = fread(b2, 1, sizeof(b2), f2);
378 
379  if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
380  fclose(f2);
381  fclose(f1);
382  return false;
383  }
384  } while (l1 != 0);
385 
386  fclose(f2);
387  fclose(f1);
388  return true;
389 }
390 
392 static const OptionData _opts[] = {
393  GETOPT_NOVAL( 'v', "--version"),
394  GETOPT_NOVAL( 'h', "--help"),
395  GETOPT_GENERAL('h', '?', nullptr, ODF_NO_VALUE),
396  GETOPT_VALUE( 'o', "--output"),
397  GETOPT_VALUE( 'b', "--before"),
398  GETOPT_VALUE( 'a', "--after"),
399  GETOPT_END(),
400 };
401 
422 static void ProcessIniFile(const char *fname)
423 {
424  IniLoadFile *ini_data = LoadIniFile(fname);
425  DumpGroup(ini_data, PREAMBLE_GROUP_NAME);
426  DumpSections(ini_data);
427  DumpGroup(ini_data, POSTAMBLE_GROUP_NAME);
428  delete ini_data;
429 }
430 
436 int CDECL main(int argc, char *argv[])
437 {
438  const char *output_file = nullptr;
439  const char *before_file = nullptr;
440  const char *after_file = nullptr;
441 
442  GetOptData mgo(argc - 1, argv + 1, _opts);
443  for (;;) {
444  int i = mgo.GetOpt();
445  if (i == -1) break;
446 
447  switch (i) {
448  case 'v':
449  puts("$Revision$");
450  return 0;
451 
452  case 'h':
453  puts("settingsgen - $Revision$\n"
454  "Usage: settingsgen [options] ini-file...\n"
455  "with options:\n"
456  " -v, --version Print version information and exit\n"
457  " -h, -?, --help Print this help message and exit\n"
458  " -b FILE, --before FILE Copy FILE before all settings\n"
459  " -a FILE, --after FILE Copy FILE after all settings\n"
460  " -o FILE, --output FILE Write output to FILE\n");
461  return 0;
462 
463  case 'o':
464  output_file = mgo.opt;
465  break;
466 
467  case 'a':
468  after_file = mgo.opt;
469  break;
470 
471  case 'b':
472  before_file = mgo.opt;
473  break;
474 
475  case -2:
476  fprintf(stderr, "Invalid arguments\n");
477  return 1;
478  }
479  }
480 
481  _stored_output.Clear();
482 
483  for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
484 
485  /* Write output. */
486  if (output_file == nullptr) {
487  CopyFile(before_file, stdout);
488  _stored_output.Write(stdout);
489  CopyFile(after_file, stdout);
490  } else {
491  static const char * const tmp_output = "tmp2.xxx";
492 
493  FILE *fp = fopen(tmp_output, "w");
494  if (fp == nullptr) {
495  fprintf(stderr, "settingsgen: Warning: Cannot open file %s\n", tmp_output);
496  return 1;
497  }
498  CopyFile(before_file, fp);
499  _stored_output.Write(fp);
500  CopyFile(after_file, fp);
501  fclose(fp);
502 
503  if (CompareFiles(tmp_output, output_file)) {
504  /* Files are equal. tmp2.xxx is not needed. */
505  unlink(tmp_output);
506  } else {
507  /* Rename tmp2.xxx to output file. */
508 #if defined(_WIN32)
509  unlink(output_file);
510 #endif
511  if (rename(tmp_output, output_file) == -1) error("rename() failed");
512  }
513  }
514  return 0;
515 }
A group within an ini file.
Definition: ini_type.h:38
static void ProcessIniFile(const char *fname)
Process a single INI file.
static void CopyFile(const char *fname, FILE *out_fp)
Copy a file to the output.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:110
A plain option (no value attached to it).
Definition: getoptdata.h:17
int CDECL main(int argc, char *argv[])
And the main program (what else?)
char ** argv
Remaining command line arguments.
Definition: getoptdata.h:35
void Write(FILE *out_fp) const
Dump buffer to the output stream.
Definition: settingsgen.cpp:74
IniItem * item
the first item in the group
Definition: ini_type.h:41
#define GETOPT_VALUE(shortname, longname)
Short option with value.
Definition: getoptdata.h:78
int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap)
Safer implementation of vsnprintf; same as vsnprintf except:
Definition: string.cpp:62
size_t Add(const char *text, size_t length)
Add text to the output buffer.
Definition: settingsgen.cpp:61
void Write(FILE *out_fp) const
Write all stored output to the output stream.
Data of an option.
Definition: getoptdata.h:24
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:50
IniGroup * GetGroup(const char *name, size_t len=0, bool create_new=true)
Get the group with the given name.
Definition: ini_load.cpp:156
IniItem * next
The next item in this group.
Definition: ini_type.h:26
static const size_t OUTPUT_BLOCK_SIZE
Block size of the buffer in OutputBuffer.
Definition: settingsgen.cpp:44
bool HasRoom() const
Does the block have room for more data?
Definition: settingsgen.cpp:85
static void DumpGroup(IniLoadFile *ifile, const char *const group_name)
Dump a IGT_SEQUENCE group into _stored_output.
static const char * FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
Find the value of a template variable.
A single "line" in an ini file.
Definition: ini_type.h:25
std::vector< OutputBuffer > OutputBufferVector
Vector type for output buffers.
void Clear()
Prepare buffer for use.
Definition: settingsgen.cpp:50
IniGroup * group
the first group in the ini
Definition: ini_type.h:55
static bool CompareFiles(const char *n1, const char *n2)
Compare two files for identity.
IniGroupType type
type of group
Definition: ini_type.h:40
#define GETOPT_GENERAL(id, shortname, longname, flags)
General macro for creating an option.
Definition: getoptdata.h:64
void LoadFromDisk(const char *filename, Subdirectory subdir)
Load the Ini file&#39;s data from the disk.
Definition: ini_load.cpp:212
A path without any base directory.
Definition: fileio_type.h:127
bool BufferHasRoom() const
Does the buffer have room without adding a new OutputBuffer block?
void Clear()
Clear the temporary storage.
Temporarily store output.
Definition: settingsgen.cpp:95
static const OptionData _opts[]
Options of settingsgen.
#define GETOPT_END()
Option terminator.
Definition: getoptdata.h:109
char * value
The value of this item.
Definition: ini_type.h:28
void Add(const char *text, size_t length=0)
Add text to the output storage.
char * opt
Option value, if available (else nullptr).
Definition: getoptdata.h:33
static const char * TEMPLATES_GROUP_NAME
Name of the group containing the templates.
#define GETOPT_NOVAL(shortname, longname)
Short option without value.
Definition: getoptdata.h:71
char data[OUTPUT_BLOCK_SIZE]
Stored data.
Definition: settingsgen.cpp:91
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
static T min(const T a, const T b)
Returns the minimum of two values.
Definition: math_func.hpp:42
Output buffer for a block of data.
Definition: settingsgen.cpp:47
OutputStore _stored_output
Temporary storage of the output, until all processing is done.
static void DumpSections(IniLoadFile *ifile)
Output all non-special sections through the template / template variable expansion system...
static void MemCpyT(T *destination, const T *source, size_t num=1)
Type-safe version of memcpy().
Definition: mem_func.hpp:25
OutputBufferVector output_buffer
Vector of blocks containing the stored output.
static IniLoadFile * LoadIniFile(const char *filename)
Load the INI file.
A list of uninterpreted lines, terminated by the next group block.
Definition: ini_type.h:21
Data storage for parsing command line options.
Definition: getoptdata.h:32
size_t size
Number of bytes stored in data.
Definition: settingsgen.cpp:90
virtual void ReportFileError(const char *const pre, const char *const buffer, const char *const post)
Report an error about the file contents.
IniItem * GetItem(const char *name, bool create)
Get the item with the given name, and if it doesn&#39;t exist and create is true it creates a new item...
Definition: ini_load.cpp:105
static const char * POSTAMBLE_GROUP_NAME
Name of the group containing the post amble.
virtual FILE * OpenFile(const char *filename, Subdirectory subdir, size_t *size)
Open the INI file.
void NORETURN CDECL error(const char *s,...)
Report a fatal error.
Definition: settingsgen.cpp:33
int GetOpt()
Find the next option.
Definition: getoptdata.cpp:24
static const char * DEFAULTS_GROUP_NAME
Name of the group containing default values for the template variables.
IniGroup * next
the next group within this file
Definition: ini_type.h:39
int numleft
Number of arguments left in argv.
Definition: getoptdata.h:34
static const char * PREAMBLE_GROUP_NAME
Name of the group containing the pre amble.
Ini file that only supports loading.
Definition: ini_type.h:54
SettingsIniFile(const char *const *list_group_names=nullptr, const char *const *seq_group_names=nullptr)
Construct a new ini loader.
Derived class for loading INI files without going through Fio stuff.