Fix cc39fa9: New orders are non-stop by default (#8689)
[openttd-github.git] / src / settingsgen / settingsgen.cpp
blobac3a2a85acaa0cb2825c6cd259c217297c7aac00
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file settingsgen.cpp Tool to create computer-readable settings. */
10 #include "../stdafx.h"
11 #include "../string_func.h"
12 #include "../strings_type.h"
13 #include "../misc/getoptdata.h"
14 #include "../ini_type.h"
15 #include "../core/smallvec_type.hpp"
17 #include <stdarg.h>
19 #if !defined(_WIN32) || defined(__CYGWIN__)
20 #include <unistd.h>
21 #include <sys/stat.h>
22 #endif
24 #include "../safeguards.h"
26 /**
27 * Report a fatal error.
28 * @param s Format string.
29 * @note Function does not return.
31 void NORETURN CDECL error(const char *s, ...)
33 char buf[1024];
34 va_list va;
35 va_start(va, s);
36 vseprintf(buf, lastof(buf), s, va);
37 va_end(va);
38 fprintf(stderr, "FATAL: %s\n", buf);
39 exit(1);
42 static const size_t OUTPUT_BLOCK_SIZE = 16000; ///< Block size of the buffer in #OutputBuffer.
44 /** Output buffer for a block of data. */
45 class OutputBuffer {
46 public:
47 /** Prepare buffer for use. */
48 void Clear()
50 this->size = 0;
53 /**
54 * Add text to the output buffer.
55 * @param text Text to store.
56 * @param length Length of the text in bytes.
57 * @return Number of bytes actually stored.
59 size_t Add(const char *text, size_t length)
61 size_t store_size = std::min(length, OUTPUT_BLOCK_SIZE - this->size);
62 assert(store_size <= OUTPUT_BLOCK_SIZE);
63 MemCpyT(this->data + this->size, text, store_size);
64 this->size += store_size;
65 return store_size;
68 /**
69 * Dump buffer to the output stream.
70 * @param out_fp Stream to write the \a data to.
72 void Write(FILE *out_fp) const
74 if (fwrite(this->data, 1, this->size, out_fp) != this->size) {
75 fprintf(stderr, "Error: Cannot write output\n");
79 /**
80 * Does the block have room for more data?
81 * @return \c true if room is available, else \c false.
83 bool HasRoom() const
85 return this->size < OUTPUT_BLOCK_SIZE;
88 size_t size; ///< Number of bytes stored in \a data.
89 char data[OUTPUT_BLOCK_SIZE]; ///< Stored data.
92 /** Temporarily store output. */
93 class OutputStore {
94 public:
95 OutputStore()
97 this->Clear();
100 /** Clear the temporary storage. */
101 void Clear()
103 this->output_buffer.clear();
107 * Add text to the output storage.
108 * @param text Text to store.
109 * @param length Length of the text in bytes, \c 0 means 'length of the string'.
111 void Add(const char *text, size_t length = 0)
113 if (length == 0) length = strlen(text);
115 if (length > 0 && this->BufferHasRoom()) {
116 size_t stored_size = this->output_buffer[this->output_buffer.size() - 1].Add(text, length);
117 length -= stored_size;
118 text += stored_size;
120 while (length > 0) {
121 OutputBuffer &block = this->output_buffer.emplace_back();
122 block.Clear(); // Initialize the new block.
123 size_t stored_size = block.Add(text, length);
124 length -= stored_size;
125 text += stored_size;
130 * Write all stored output to the output stream.
131 * @param out_fp Stream to write the \a data to.
133 void Write(FILE *out_fp) const
135 for (const OutputBuffer &out_data : output_buffer) {
136 out_data.Write(out_fp);
140 private:
142 * Does the buffer have room without adding a new #OutputBuffer block?
143 * @return \c true if room is available, else \c false.
145 bool BufferHasRoom() const
147 size_t num_blocks = this->output_buffer.size();
148 return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
151 typedef std::vector<OutputBuffer> OutputBufferVector; ///< Vector type for output buffers.
152 OutputBufferVector output_buffer; ///< Vector of blocks containing the stored output.
156 /** Derived class for loading INI files without going through Fio stuff. */
157 struct SettingsIniFile : IniLoadFile {
159 * Construct a new ini loader.
160 * @param list_group_names A \c nullptr terminated list with group names that should be loaded as lists instead of variables. @see IGT_LIST
161 * @param seq_group_names A \c nullptr terminated list with group names that should be loaded as lists of names. @see IGT_SEQUENCE
163 SettingsIniFile(const char * const *list_group_names = nullptr, const char * const *seq_group_names = nullptr) :
164 IniLoadFile(list_group_names, seq_group_names)
168 virtual FILE *OpenFile(const std::string &filename, Subdirectory subdir, size_t *size)
170 /* Open the text file in binary mode to prevent end-of-line translations
171 * done by ftell() and friends, as defined by K&R. */
172 FILE *in = fopen(filename.c_str(), "rb");
173 if (in == nullptr) return nullptr;
175 fseek(in, 0L, SEEK_END);
176 *size = ftell(in);
178 fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
179 return in;
182 virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
184 error("%s%s%s", pre, buffer, post);
188 OutputStore _stored_output; ///< Temporary storage of the output, until all processing is done.
190 static const char *PREAMBLE_GROUP_NAME = "pre-amble"; ///< Name of the group containing the pre amble.
191 static const char *POSTAMBLE_GROUP_NAME = "post-amble"; ///< Name of the group containing the post amble.
192 static const char *TEMPLATES_GROUP_NAME = "templates"; ///< Name of the group containing the templates.
193 static const char *DEFAULTS_GROUP_NAME = "defaults"; ///< Name of the group containing default values for the template variables.
196 * Load the INI file.
197 * @param filename Name of the file to load.
198 * @return Loaded INI data.
200 static IniLoadFile *LoadIniFile(const char *filename)
202 static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, nullptr};
204 IniLoadFile *ini = new SettingsIniFile(nullptr, seq_groups);
205 ini->LoadFromDisk(filename, NO_DIRECTORY);
206 return ini;
210 * Dump a #IGT_SEQUENCE group into #_stored_output.
211 * @param ifile Loaded INI data.
212 * @param group_name Name of the group to copy.
214 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
216 IniGroup *grp = ifile->GetGroup(group_name, false);
217 if (grp != nullptr && grp->type == IGT_SEQUENCE) {
218 for (IniItem *item = grp->item; item != nullptr; item = item->next) {
219 if (!item->name.empty()) {
220 _stored_output.Add(item->name.c_str());
221 _stored_output.Add("\n", 1);
228 * Find the value of a template variable.
229 * @param name Name of the item to find.
230 * @param grp Group currently being expanded (searched first).
231 * @param defaults Fallback group to search, \c nullptr skips the search.
232 * @return Text of the item if found, else \c nullptr.
234 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
236 IniItem *item = grp->GetItem(name, false);
237 if (item == nullptr && defaults != nullptr) item = defaults->GetItem(name, false);
238 if (item == nullptr || !item->value.has_value()) return nullptr;
239 return item->value->c_str();
243 * Output all non-special sections through the template / template variable expansion system.
244 * @param ifile Loaded INI data.
246 static void DumpSections(IniLoadFile *ifile)
248 static const int MAX_VAR_LENGTH = 64;
249 static const char * const special_group_names[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, nullptr};
251 IniGroup *default_grp = ifile->GetGroup(DEFAULTS_GROUP_NAME, false);
252 IniGroup *templates_grp = ifile->GetGroup(TEMPLATES_GROUP_NAME, false);
253 if (templates_grp == nullptr) return;
255 /* Output every group, using its name as template name. */
256 for (IniGroup *grp = ifile->group; grp != nullptr; grp = grp->next) {
257 const char * const *sgn;
258 for (sgn = special_group_names; *sgn != nullptr; sgn++) if (grp->name == *sgn) break;
259 if (*sgn != nullptr) continue;
261 IniItem *template_item = templates_grp->GetItem(grp->name, false); // Find template value.
262 if (template_item == nullptr || !template_item->value.has_value()) {
263 fprintf(stderr, "settingsgen: Warning: Cannot find template %s\n", grp->name.c_str());
264 continue;
267 /* Prefix with #if/#ifdef/#ifndef */
268 static const char * const pp_lines[] = {"if", "ifdef", "ifndef", nullptr};
269 int count = 0;
270 for (const char * const *name = pp_lines; *name != nullptr; name++) {
271 const char *condition = FindItemValue(*name, grp, default_grp);
272 if (condition != nullptr) {
273 _stored_output.Add("#", 1);
274 _stored_output.Add(*name);
275 _stored_output.Add(" ", 1);
276 _stored_output.Add(condition);
277 _stored_output.Add("\n", 1);
278 count++;
282 /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
283 const char *txt = template_item->value->c_str();
284 while (*txt != '\0') {
285 if (*txt != '$') {
286 _stored_output.Add(txt, 1);
287 txt++;
288 continue;
290 txt++;
291 if (*txt == '$') { // Literal $
292 _stored_output.Add(txt, 1);
293 txt++;
294 continue;
297 /* Read variable. */
298 char variable[MAX_VAR_LENGTH];
299 int i = 0;
300 while (i < MAX_VAR_LENGTH - 1) {
301 if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
302 variable[i] = txt[i];
303 i++;
305 variable[i] = '\0';
306 txt += i;
308 if (i > 0) {
309 /* Find the text to output. */
310 const char *valitem = FindItemValue(variable, grp, default_grp);
311 if (valitem != nullptr) _stored_output.Add(valitem);
312 } else {
313 _stored_output.Add("$", 1);
316 _stored_output.Add("\n", 1); // \n after the expanded template.
317 while (count > 0) {
318 _stored_output.Add("#endif\n");
319 count--;
325 * Copy a file to the output.
326 * @param fname Filename of file to copy.
327 * @param out_fp Output stream to write to.
329 static void CopyFile(const char *fname, FILE *out_fp)
331 if (fname == nullptr) return;
333 FILE *in_fp = fopen(fname, "r");
334 if (in_fp == nullptr) {
335 fprintf(stderr, "settingsgen: Warning: Cannot open file %s for copying\n", fname);
336 return;
339 char buffer[4096];
340 size_t length;
341 do {
342 length = fread(buffer, 1, lengthof(buffer), in_fp);
343 if (fwrite(buffer, 1, length, out_fp) != length) {
344 fprintf(stderr, "Error: Cannot copy file\n");
345 break;
347 } while (length == lengthof(buffer));
349 fclose(in_fp);
353 * Compare two files for identity.
354 * @param n1 First file.
355 * @param n2 Second file.
356 * @return True if both files are identical.
358 static bool CompareFiles(const char *n1, const char *n2)
360 FILE *f2 = fopen(n2, "rb");
361 if (f2 == nullptr) return false;
363 FILE *f1 = fopen(n1, "rb");
364 if (f1 == nullptr) {
365 fclose(f2);
366 error("can't open %s", n1);
369 size_t l1, l2;
370 do {
371 char b1[4096];
372 char b2[4096];
373 l1 = fread(b1, 1, sizeof(b1), f1);
374 l2 = fread(b2, 1, sizeof(b2), f2);
376 if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
377 fclose(f2);
378 fclose(f1);
379 return false;
381 } while (l1 != 0);
383 fclose(f2);
384 fclose(f1);
385 return true;
388 /** Options of settingsgen. */
389 static const OptionData _opts[] = {
390 GETOPT_NOVAL( 'v', "--version"),
391 GETOPT_NOVAL( 'h', "--help"),
392 GETOPT_GENERAL('h', '?', nullptr, ODF_NO_VALUE),
393 GETOPT_VALUE( 'o', "--output"),
394 GETOPT_VALUE( 'b', "--before"),
395 GETOPT_VALUE( 'a', "--after"),
396 GETOPT_END(),
400 * Process a single INI file.
401 * The file should have a [templates] group, where each item is one template.
402 * Variables in a template have the form '\$[_a-z0-9]+' (a literal '$' followed
403 * by one or more '_', lowercase letters, or lowercase numbers).
405 * After loading, the [pre-amble] group is copied verbatim if it exists.
407 * For every group with a name that matches a template name the template is written.
408 * It starts with a optional \c \#if line if an 'if' item exists in the group. The item
409 * value is used as condition. Similarly, \c \#ifdef and \c \#ifndef lines are also written.
410 * Below the macro processor directives, the value of the template is written
411 * at a line with its variables replaced by item values of the group being written.
412 * If the group has no item for the variable, the [defaults] group is tried as fall back.
413 * Finally, \c \#endif lines are written to match the macro processor lines.
415 * Last but not least, the [post-amble] group is copied verbatim.
417 * @param fname Ini file to process. @return Exit status of the processing.
419 static void ProcessIniFile(const char *fname)
421 IniLoadFile *ini_data = LoadIniFile(fname);
422 DumpGroup(ini_data, PREAMBLE_GROUP_NAME);
423 DumpSections(ini_data);
424 DumpGroup(ini_data, POSTAMBLE_GROUP_NAME);
425 delete ini_data;
429 * And the main program (what else?)
430 * @param argc Number of command-line arguments including the program name itself.
431 * @param argv Vector of the command-line arguments.
433 int CDECL main(int argc, char *argv[])
435 const char *output_file = nullptr;
436 const char *before_file = nullptr;
437 const char *after_file = nullptr;
439 GetOptData mgo(argc - 1, argv + 1, _opts);
440 for (;;) {
441 int i = mgo.GetOpt();
442 if (i == -1) break;
444 switch (i) {
445 case 'v':
446 puts("$Revision$");
447 return 0;
449 case 'h':
450 puts("settingsgen - $Revision$\n"
451 "Usage: settingsgen [options] ini-file...\n"
452 "with options:\n"
453 " -v, --version Print version information and exit\n"
454 " -h, -?, --help Print this help message and exit\n"
455 " -b FILE, --before FILE Copy FILE before all settings\n"
456 " -a FILE, --after FILE Copy FILE after all settings\n"
457 " -o FILE, --output FILE Write output to FILE\n");
458 return 0;
460 case 'o':
461 output_file = mgo.opt;
462 break;
464 case 'a':
465 after_file = mgo.opt;
466 break;
468 case 'b':
469 before_file = mgo.opt;
470 break;
472 case -2:
473 fprintf(stderr, "Invalid arguments\n");
474 return 1;
478 _stored_output.Clear();
480 for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
482 /* Write output. */
483 if (output_file == nullptr) {
484 CopyFile(before_file, stdout);
485 _stored_output.Write(stdout);
486 CopyFile(after_file, stdout);
487 } else {
488 static const char * const tmp_output = "tmp2.xxx";
490 FILE *fp = fopen(tmp_output, "w");
491 if (fp == nullptr) {
492 fprintf(stderr, "settingsgen: Warning: Cannot open file %s\n", tmp_output);
493 return 1;
495 CopyFile(before_file, fp);
496 _stored_output.Write(fp);
497 CopyFile(after_file, fp);
498 fclose(fp);
500 if (CompareFiles(tmp_output, output_file)) {
501 /* Files are equal. tmp2.xxx is not needed. */
502 unlink(tmp_output);
503 } else {
504 /* Rename tmp2.xxx to output file. */
505 #if defined(_WIN32)
506 unlink(output_file);
507 #endif
508 if (rename(tmp_output, output_file) == -1) error("rename() failed");
511 return 0;