Maintain a circular buffer of recent commands, add to crashlog.
[openttd-joker.git] / src / settingsgen / settingsgen.cpp
blobc6685aba4aa2eeed7e2c732f70838a73bf52e3a3
1 /* $Id: settingsgen.cpp 23940 2012-02-12 19:46:40Z smatz $ */
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 */
10 /** @file settingsgen.cpp Tool to create computer-readable settings. */
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"
19 #include <stdarg.h>
21 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #endif
26 #ifdef __MORPHOS__
27 #ifdef stderr
28 #undef stderr
29 #endif
30 #define stderr stdout
31 #endif /* __MORPHOS__ */
33 #include "../safeguards.h"
35 /**
36 * Report a fatal error.
37 * @param s Format string.
38 * @note Function does not return.
40 void NORETURN CDECL error(const char *s, ...)
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);
51 static const int OUTPUT_BLOCK_SIZE = 16000; ///< Block size of the buffer in #OutputBuffer.
53 /** Output buffer for a block of data. */
54 class OutputBuffer {
55 public:
56 /** Prepare buffer for use. */
57 void Clear()
59 this->size = 0;
62 /**
63 * Add text to the output buffer.
64 * @param text Text to store.
65 * @param length Length of the text in bytes.
66 * @return Number of bytes actually stored.
68 int Add(const char *text, int length)
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;
78 /**
79 * Dump buffer to the output stream.
80 * @param out_fp Stream to write the \a data to.
82 void Write(FILE *out_fp) const
84 if (fwrite(this->data, 1, this->size, out_fp) != (size_t)this->size) {
85 fprintf(stderr, "Error: Cannot write output\n");
89 /**
90 * Does the block have room for more data?
91 * @return \c true if room is available, else \c false.
93 bool HasRoom() const
95 return this->size < OUTPUT_BLOCK_SIZE;
98 int size; ///< Number of bytes stored in \a data.
99 char data[OUTPUT_BLOCK_SIZE]; ///< Stored data.
102 /** Temporarily store output. */
103 class OutputStore {
104 public:
105 OutputStore()
107 this->Clear();
110 /** Clear the temporary storage. */
111 void Clear()
113 this->output_buffer.Clear();
117 * Add text to the output storage.
118 * @param text Text to store.
119 * @param length Length of the text in bytes, \c 0 means 'length of the string'.
121 void Add(const char *text, int length = 0)
123 if (length == 0) length = strlen(text);
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;
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;
140 * Write all stored output to the output stream.
141 * @param out_fp Stream to write the \a data to.
143 void Write(FILE *out_fp) const
145 for (const OutputBuffer *out_data = this->output_buffer.Begin(); out_data != this->output_buffer.End(); out_data++) {
146 out_data->Write(out_fp);
150 private:
152 * Does the buffer have room without adding a new #OutputBuffer block?
153 * @return \c true if room is available, else \c false.
155 bool BufferHasRoom() const
157 uint num_blocks = this->output_buffer.Length();
158 return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
161 typedef SmallVector<OutputBuffer, 2> OutputBufferVector; ///< Vector type for output buffers.
162 OutputBufferVector output_buffer; ///< Vector of blocks containing the stored output.
166 /** Derived class for loading INI files without going through Fio stuff. */
167 struct SettingsIniFile : IniLoadFile {
169 * Construct a new ini loader.
170 * @param list_group_names A \c NULL terminated list with group names that should be loaded as lists instead of variables. @see IGT_LIST
171 * @param seq_group_names A \c NULL terminated list with group names that should be loaded as lists of names. @see IGT_SEQUENCE
173 SettingsIniFile(const char * const *list_group_names = NULL, const char * const *seq_group_names = NULL) :
174 IniLoadFile(list_group_names, seq_group_names)
178 virtual FILE *OpenFile(const char *filename, Subdirectory subdir, size_t *size)
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;
185 fseek(in, 0L, SEEK_END);
186 *size = ftell(in);
188 fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
189 return in;
192 virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
194 error("%s%s%s", pre, buffer, post);
198 OutputStore _stored_output; ///< Temporary storage of the output, until all processing is done.
200 static const char *PREAMBLE_GROUP_NAME = "pre-amble"; ///< Name of the group containing the pre amble.
201 static const char *POSTAMBLE_GROUP_NAME = "post-amble"; ///< Name of the group containing the post amble.
202 static const char *TEMPLATES_GROUP_NAME = "templates"; ///< Name of the group containing the templates.
203 static const char *DEFAULTS_GROUP_NAME = "defaults"; ///< Name of the group containing default values for the template variables.
206 * Load the INI file.
207 * @param filename Name of the file to load.
208 * @param subdir The subdirectory to load from.
209 * @return Loaded INI data.
211 static IniLoadFile *LoadIniFile(const char *filename)
213 static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, NULL};
215 IniLoadFile *ini = new SettingsIniFile(NULL, seq_groups);
216 ini->LoadFromDisk(filename, NO_DIRECTORY);
217 return ini;
221 * Dump a #IGT_SEQUENCE group into #_stored_output.
222 * @param ifile Loaded INI data.
223 * @param group_name Name of the group to copy.
225 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
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);
239 * Find the value of a template variable.
240 * @param name Name of the item to find.
241 * @param grp Group currently being expanded (searched first).
242 * @param defaults Fallback group to search, \c NULL skips the search.
243 * @return Text of the item if found, else \c NULL.
245 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
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;
254 * Output all non-special sections through the template / template variable expansion system.
255 * @param ifile Loaded INI data.
257 static void DumpSections(IniLoadFile *ifile)
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};
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;
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;
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;
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++;
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;
301 txt++;
302 if (*txt == '$') { // Literal $
303 _stored_output.Add(txt, 1);
304 txt++;
305 continue;
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++;
316 variable[i] = '\0';
317 txt += i;
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);
327 _stored_output.Add("\n", 1); // \n after the expanded template.
328 while (count > 0) {
329 _stored_output.Add("#endif\n");
330 count--;
336 * Copy a file to the output.
337 * @param fname Filename of file to copy.
338 * @param out_fp Output stream to write to.
340 static void CopyFile(const char *fname, FILE *out_fp)
342 if (fname == NULL) return;
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;
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;
358 } while (length == lengthof(buffer));
360 fclose(in_fp);
364 * Compare two files for identity.
365 * @param n1 First file.
366 * @param n2 Second file.
367 * @return True if both files are identical.
369 static bool CompareFiles(const char *n1, const char *n2)
371 FILE *f2 = fopen(n2, "rb");
372 if (f2 == NULL) return false;
374 FILE *f1 = fopen(n1, "rb");
375 if (f1 == NULL) error("can't open %s", n1);
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);
384 if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
385 fclose(f2);
386 fclose(f1);
387 return false;
389 } while (l1 != 0);
391 fclose(f2);
392 fclose(f1);
393 return true;
396 /** Options of settingsgen. */
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(),
408 * Process a single INI file.
409 * The file should have a [templates] group, where each item is one template.
410 * Variables in a template have the form '\$[_a-z0-9]+' (a literal '$' followed
411 * by one or more '_', lowercase letters, or lowercase numbers).
413 * After loading, the [pre-amble] group is copied verbatim if it exists.
415 * For every group with a name that matches a template name the template is written.
416 * It starts with a optional '#if' line if an 'if' item exists in the group. The item
417 * value is used as condition. Similarly, '#ifdef' and '#ifndef' lines are also written.
418 * Below the macro processor directives, the value of the template is written
419 * at a line with its variables replaced by item values of the group being written.
420 * If the group has no item for the variable, the [defaults] group is tried as fall back.
421 * Finally, '#endif' lines are written to match the macro processor lines.
423 * Last but not least, the [post-amble] group is copied verbatim.
425 * @param fname Ini file to process. @return Exit status of the processing.
427 static void ProcessIniFile(const char *fname)
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;
437 * And the main program (what else?)
438 * @param argc Number of command-line arguments including the program name itself.
439 * @param argv Vector of the command-line arguments.
441 int CDECL main(int argc, char *argv[])
443 const char *output_file = NULL;
444 const char *before_file = NULL;
445 const char *after_file = NULL;
447 GetOptData mgo(argc - 1, argv + 1, _opts);
448 for (;;) {
449 int i = mgo.GetOpt();
450 if (i == -1) break;
452 switch (i) {
453 case 'v':
454 puts("$Revision$");
455 return 0;
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;
468 case 'o':
469 output_file = mgo.opt;
470 break;
472 case 'a':
473 after_file = mgo.opt;
474 break;
476 case 'b':
477 before_file = mgo.opt;
478 break;
480 case -2:
481 fprintf(stderr, "Invalid arguments\n");
482 return 1;
486 _stored_output.Clear();
488 for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
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";
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;
503 CopyFile(before_file, fp);
504 _stored_output.Write(fp);
505 CopyFile(after_file, fp);
506 fclose(fp);
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");
519 return 0;