OpenTTD Source: src/settingsgen/settingsgen.cpp Source File

OpenTTD Source 12.2

settingsgen.cpp
Go to the documentation of this file.
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 */
7
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"
16
17 #include <stdarg.h>
18
19 #if !defined(_WIN32) || defined(__CYGWIN__)
20 #include <unistd.h>
21 #include <sys/stat.h>
22 #endif
23
24 #include "../safeguards.h"
25
31 void NORETURN CDECL error(const char *s, ...)
32 {
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);
40 }
41
42 static const size_t OUTPUT_BLOCK_SIZE = 16000;
43
45 class OutputBuffer {
46 public:
48 void Clear()
49 {
50 this->size = 0;
51 }
52
59 size_t Add(const char *text, size_t length)
60 {
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;
66 }
67
72 void Write(FILE *out_fp) const
73 {
74 if (fwrite(this->data, 1, this->size, out_fp) != this->size) {
75 fprintf(stderr, "Error: Cannot write output\n");
76 }
77 }
78
83 bool HasRoom() const
84 {
85 return this->size < OUTPUT_BLOCK_SIZE;
86 }
87
88 size_t size;
89 char data[OUTPUT_BLOCK_SIZE];
90 };
91
93 class OutputStore {
94 public:
95 OutputStore()
96 {
97 this->Clear();
98 }
99
101 void Clear()
102 {
103 this->output_buffer.clear();
104 }
105
111 void Add(const char *text, size_t length = 0)
112 {
113 if (length == 0) length = strlen(text);
114
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;
119 }
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;
126 }
127 }
128
133 void Write(FILE *out_fp) const
134 {
135 for (const OutputBuffer &out_data : output_buffer) {
136 out_data.Write(out_fp);
137 }
138 }
139
140 private:
145 bool BufferHasRoom() const
146 {
147 size_t num_blocks = this->output_buffer.size();
148 return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
149 }
150
151 typedef std::vector<OutputBuffer> OutputBufferVector;
152 OutputBufferVector output_buffer;
153 };
154
155
157 struct SettingsIniFile : IniLoadFile {
163 SettingsIniFile(const char * const *list_group_names = nullptr, const char * const *seq_group_names = nullptr) :
164 IniLoadFile(list_group_names, seq_group_names)
165 {
166 }
167
168 virtual FILE *OpenFile(const std::string &filename, Subdirectory subdir, size_t *size)
169 {
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;
174
175 fseek(in, 0L, SEEK_END);
176 *size = ftell(in);
177
178 fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
179 return in;
180 }
181
182 virtual void ReportFileError(const char * const pre, const char * const buffer, const char * const post)
183 {
184 error("%s%s%s", pre, buffer, post);
185 }
186 };
187
188 OutputStore _stored_output;
189 OutputStore _post_amble_output;
190
191 static const char *PREAMBLE_GROUP_NAME = "pre-amble";
192 static const char *POSTAMBLE_GROUP_NAME = "post-amble";
193 static const char *TEMPLATES_GROUP_NAME = "templates";
194 static const char *VALIDATION_GROUP_NAME = "validation";
195 static const char *DEFAULTS_GROUP_NAME = "defaults";
196
202 static IniLoadFile *LoadIniFile(const char *filename)
203 {
204 static const char * const seq_groups[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, nullptr};
205
206 IniLoadFile *ini = new SettingsIniFile(nullptr, seq_groups);
207 ini->LoadFromDisk(filename, NO_DIRECTORY);
208 return ini;
209 }
210
216 static void DumpGroup(IniLoadFile *ifile, const char * const group_name)
217 {
218 IniGroup *grp = ifile->GetGroup(group_name, false);
219 if (grp != nullptr && grp->type == IGT_SEQUENCE) {
220 for (IniItem *item = grp->item; item != nullptr; item = item->next) {
221 if (!item->name.empty()) {
222 _stored_output.Add(item->name.c_str());
223 _stored_output.Add("\n", 1);
224 }
225 }
226 }
227 }
228
236 static const char *FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
237 {
238 IniItem *item = grp->GetItem(name, false);
239 if (item == nullptr && defaults != nullptr) item = defaults->GetItem(name, false);
240 if (item == nullptr || !item->value.has_value()) return nullptr;
241 return item->value->c_str();
242 }
243
251 static void DumpLine(IniItem *item, IniGroup *grp, IniGroup *default_grp, OutputStore &output)
252 {
253 static const int MAX_VAR_LENGTH = 64;
254
255 /* Prefix with #if/#ifdef/#ifndef */
256 static const char * const pp_lines[] = {"if", "ifdef", "ifndef", nullptr};
257 int count = 0;
258 for (const char * const *name = pp_lines; *name != nullptr; name++) {
259 const char *condition = FindItemValue(*name, grp, default_grp);
260 if (condition != nullptr) {
261 output.Add("#", 1);
262 output.Add(*name);
263 output.Add(" ", 1);
264 output.Add(condition);
265 output.Add("\n", 1);
266 count++;
267 }
268 }
269
270 /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
271 const char *txt = item->value->c_str();
272 while (*txt != '\0') {
273 if (*txt != '$') {
274 output.Add(txt, 1);
275 txt++;
276 continue;
277 }
278 txt++;
279 if (*txt == '$') { // Literal $
280 output.Add(txt, 1);
281 txt++;
282 continue;
283 }
284
285 /* Read variable. */
286 char variable[MAX_VAR_LENGTH];
287 int i = 0;
288 while (i < MAX_VAR_LENGTH - 1) {
289 if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
290 variable[i] = txt[i];
291 i++;
292 }
293 variable[i] = '\0';
294 txt += i;
295
296 if (i > 0) {
297 /* Find the text to output. */
298 const char *valitem = FindItemValue(variable, grp, default_grp);
299 if (valitem != nullptr) output.Add(valitem);
300 } else {
301 output.Add("$", 1);
302 }
303 }
304 output.Add("\n", 1); // \n after the expanded template.
305 while (count > 0) {
306 output.Add("#endif\n");
307 count--;
308 }
309 }
310
315 static void DumpSections(IniLoadFile *ifile)
316 {
317 static const char * const special_group_names[] = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, VALIDATION_GROUP_NAME, nullptr};
318
319 IniGroup *default_grp = ifile->GetGroup(DEFAULTS_GROUP_NAME, false);
320 IniGroup *templates_grp = ifile->GetGroup(TEMPLATES_GROUP_NAME, false);
321 IniGroup *validation_grp = ifile->GetGroup(VALIDATION_GROUP_NAME, false);
322 if (templates_grp == nullptr) return;
323
324 /* Output every group, using its name as template name. */
325 for (IniGroup *grp = ifile->group; grp != nullptr; grp = grp->next) {
326 const char * const *sgn;
327 for (sgn = special_group_names; *sgn != nullptr; sgn++) if (grp->name == *sgn) break;
328 if (*sgn != nullptr) continue;
329
330 IniItem *template_item = templates_grp->GetItem(grp->name, false); // Find template value.
331 if (template_item == nullptr || !template_item->value.has_value()) {
332 fprintf(stderr, "settingsgen: Warning: Cannot find template %s\n", grp->name.c_str());
333 continue;
334 }
335 DumpLine(template_item, grp, default_grp, _stored_output);
336
337 if (validation_grp != nullptr) {
338 IniItem *validation_item = validation_grp->GetItem(grp->name, false); // Find template value.
339 if (validation_item != nullptr && validation_item->value.has_value()) {
340 DumpLine(validation_item, grp, default_grp, _post_amble_output);
341 }
342 }
343 }
344 }
345
351 static void CopyFile(const char *fname, FILE *out_fp)
352 {
353 if (fname == nullptr) return;
354
355 FILE *in_fp = fopen(fname, "r");
356 if (in_fp == nullptr) {
357 fprintf(stderr, "settingsgen: Warning: Cannot open file %s for copying\n", fname);
358 return;
359 }
360
361 char buffer[4096];
362 size_t length;
363 do {
364 length = fread(buffer, 1, lengthof(buffer), in_fp);
365 if (fwrite(buffer, 1, length, out_fp) != length) {
366 fprintf(stderr, "Error: Cannot copy file\n");
367 break;
368 }
369 } while (length == lengthof(buffer));
370
371 fclose(in_fp);
372 }
373
380 static bool CompareFiles(const char *n1, const char *n2)
381 {
382 FILE *f2 = fopen(n2, "rb");
383 if (f2 == nullptr) return false;
384
385 FILE *f1 = fopen(n1, "rb");
386 if (f1 == nullptr) {
387 fclose(f2);
388 error("can't open %s", n1);
389 }
390
391 size_t l1, l2;
392 do {
393 char b1[4096];
394 char b2[4096];
395 l1 = fread(b1, 1, sizeof(b1), f1);
396 l2 = fread(b2, 1, sizeof(b2), f2);
397
398 if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
399 fclose(f2);
400 fclose(f1);
401 return false;
402 }
403 } while (l1 != 0);
404
405 fclose(f2);
406 fclose(f1);
407 return true;
408 }
409
411 static const OptionData _opts[] = {
412 GETOPT_NOVAL( 'v', "--version"),
413 GETOPT_NOVAL( 'h', "--help"),
414 GETOPT_GENERAL('h', '?', nullptr, ODF_NO_VALUE),
415 GETOPT_VALUE( 'o', "--output"),
416 GETOPT_VALUE( 'b', "--before"),
417 GETOPT_VALUE( 'a', "--after"),
418 GETOPT_END(),
419 };
420
441 static void ProcessIniFile(const char *fname)
442 {
443 IniLoadFile *ini_data = LoadIniFile(fname);
444 DumpGroup(ini_data, PREAMBLE_GROUP_NAME);
445 DumpSections(ini_data);
446 DumpGroup(ini_data, POSTAMBLE_GROUP_NAME);
447 delete ini_data;
448 }
449
455 int CDECL main(int argc, char *argv[])
456 {
457 const char *output_file = nullptr;
458 const char *before_file = nullptr;
459 const char *after_file = nullptr;
460
461 GetOptData mgo(argc - 1, argv + 1, _opts);
462 for (;;) {
463 int i = mgo.GetOpt();
464 if (i == -1) break;
465
466 switch (i) {
467 case 'v':
468 puts("$Revision$");
469 return 0;
470
471 case 'h':
472 puts("settingsgen - $Revision$\n"
473 "Usage: settingsgen [options] ini-file...\n"
474 "with options:\n"
475 " -v, --version Print version information and exit\n"
476 " -h, -?, --help Print this help message and exit\n"
477 " -b FILE, --before FILE Copy FILE before all settings\n"
478 " -a FILE, --after FILE Copy FILE after all settings\n"
479 " -o FILE, --output FILE Write output to FILE\n");
480 return 0;
481
482 case 'o':
483 output_file = mgo.opt;
484 break;
485
486 case 'a':
487 after_file = mgo.opt;
488 break;
489
490 case 'b':
491 before_file = mgo.opt;
492 break;
493
494 case -2:
495 fprintf(stderr, "Invalid arguments\n");
496 return 1;
497 }
498 }
499
500 _stored_output.Clear();
501 _post_amble_output.Clear();
502
503 for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
504
505 /* Write output. */
506 if (output_file == nullptr) {
507 CopyFile(before_file, stdout);
508 _stored_output.Write(stdout);
509 _post_amble_output.Write(stdout);
510 CopyFile(after_file, stdout);
511 } else {
512 static const char * const tmp_output = "tmp2.xxx";
513
514 FILE *fp = fopen(tmp_output, "w");
515 if (fp == nullptr) {
516 fprintf(stderr, "settingsgen: Warning: Cannot open file %s\n", tmp_output);
517 return 1;
518 }
519 CopyFile(before_file, fp);
520 _stored_output.Write(fp);
521 _post_amble_output.Write(fp);
522 CopyFile(after_file, fp);
523 fclose(fp);
524
525 if (CompareFiles(tmp_output, output_file)) {
526 /* Files are equal. tmp2.xxx is not needed. */
527 unlink(tmp_output);
528 } else {
529 /* Rename tmp2.xxx to output file. */
530 #if defined(_WIN32)
531 unlink(output_file);
532 #endif
533 if (rename(tmp_output, output_file) == -1) error("rename() failed");
534 }
535 }
536 return 0;
537 }
ProcessIniFile
static void ProcessIniFile(const char *fname)
Process a single INI file.
Definition: settingsgen.cpp:441
CompareFiles
static bool CompareFiles(const char *n1, const char *n2)
Compare two files for identity.
Definition: settingsgen.cpp:380
OutputStore::Write
void Write(FILE *out_fp) const
Write all stored output to the output stream.
Definition: settingsgen.cpp:133
LoadIniFile
static IniLoadFile * LoadIniFile(const char *filename)
Load the INI file.
Definition: settingsgen.cpp:202
IniItem::next
IniItem * next
The next item in this group.
Definition: ini_type.h:26
OutputBuffer::data
char data[OUTPUT_BLOCK_SIZE]
Stored data.
Definition: settingsgen.cpp:89
OutputBuffer::Clear
void Clear()
Prepare buffer for use.
Definition: settingsgen.cpp:48
SettingsIniFile::OpenFile
virtual FILE * OpenFile(const std::string &filename, Subdirectory subdir, size_t *size)
Open the INI file.
Definition: settingsgen.cpp:168
DEFAULTS_GROUP_NAME
static const char * DEFAULTS_GROUP_NAME
Name of the group containing default values for the template variables.
Definition: settingsgen.cpp:195
IniItem
A single "line" in an ini file.
Definition: ini_type.h:25
IniGroup
A group within an ini file.
Definition: ini_type.h:38
GETOPT_VALUE
#define GETOPT_VALUE(shortname, longname)
Short option with value.
Definition: getoptdata.h:76
_post_amble_output
OutputStore _post_amble_output
Similar to _stored_output, but for the post amble.
Definition: settingsgen.cpp:189
OutputStore::BufferHasRoom
bool BufferHasRoom() const
Does the buffer have room without adding a new OutputBuffer block?
Definition: settingsgen.cpp:145
_stored_output
OutputStore _stored_output
Temporary storage of the output, until all processing is done.
Definition: settingsgen.cpp:188
MemCpyT
static void MemCpyT(T *destination, const T *source, size_t num=1)
Type-safe version of memcpy().
Definition: mem_func.hpp:23
OutputBuffer::size
size_t size
Number of bytes stored in data.
Definition: settingsgen.cpp:88
IniLoadFile
Ini file that only supports loading.
Definition: ini_type.h:55
GetOptData::argv
char ** argv
Remaining command line arguments.
Definition: getoptdata.h:33
DumpGroup
static void DumpGroup(IniLoadFile *ifile, const char *const group_name)
Dump a IGT_SEQUENCE group into _stored_output.
Definition: settingsgen.cpp:216
POSTAMBLE_GROUP_NAME
static const char * POSTAMBLE_GROUP_NAME
Name of the group containing the post amble.
Definition: settingsgen.cpp:192
OutputStore::Clear
void Clear()
Clear the temporary storage.
Definition: settingsgen.cpp:101
OUTPUT_BLOCK_SIZE
static const size_t OUTPUT_BLOCK_SIZE
Block size of the buffer in OutputBuffer.
Definition: settingsgen.cpp:42
GETOPT_NOVAL
#define GETOPT_NOVAL(shortname, longname)
Short option without value.
Definition: getoptdata.h:69
IniItem::value
std::optional< std::string > value
The value of this item.
Definition: ini_type.h:28
GETOPT_END
#define GETOPT_END()
Option terminator.
Definition: getoptdata.h:107
OutputBuffer::HasRoom
bool HasRoom() const
Does the block have room for more data?
Definition: settingsgen.cpp:83
GETOPT_GENERAL
#define GETOPT_GENERAL(id, shortname, longname, flags)
General macro for creating an option.
Definition: getoptdata.h:62
OutputStore::output_buffer
OutputBufferVector output_buffer
Vector of blocks containing the stored output.
Definition: settingsgen.cpp:152
GetOptData::opt
char * opt
Option value, if available (else nullptr).
Definition: getoptdata.h:31
vseprintf
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:61
IniGroup::type
IniGroupType type
type of group
Definition: ini_type.h:40
OptionData
Data of an option.
Definition: getoptdata.h:22
GetOptData
Data storage for parsing command line options.
Definition: getoptdata.h:30
SettingsIniFile
Derived class for loading INI files without going through Fio stuff.
Definition: settingsgen.cpp:157
OutputBuffer::Write
void Write(FILE *out_fp) const
Dump buffer to the output stream.
Definition: settingsgen.cpp:72
OutputStore
Temporarily store output.
Definition: settingsgen.cpp:93
OutputStore::OutputBufferVector
std::vector< OutputBuffer > OutputBufferVector
Vector type for output buffers.
Definition: settingsgen.cpp:151
IniLoadFile::list_group_names
const char *const * list_group_names
nullptr terminated list with group names that are lists
Definition: ini_type.h:59
_opts
static const OptionData _opts[]
Options of settingsgen.
Definition: settingsgen.cpp:411
IniLoadFile::group
IniGroup * group
the first group in the ini
Definition: ini_type.h:56
PREAMBLE_GROUP_NAME
static const char * PREAMBLE_GROUP_NAME
Name of the group containing the pre amble.
Definition: settingsgen.cpp:191
OutputBuffer::Add
size_t Add(const char *text, size_t length)
Add text to the output buffer.
Definition: settingsgen.cpp:59
VALIDATION_GROUP_NAME
static const char * VALIDATION_GROUP_NAME
Name of the group containing the validation statements.
Definition: settingsgen.cpp:194
main
int CDECL main(int argc, char *argv[])
And the main program (what else?)
Definition: settingsgen.cpp:455
FindItemValue
static const char * FindItemValue(const char *name, IniGroup *grp, IniGroup *defaults)
Find the value of a template variable.
Definition: settingsgen.cpp:236
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:125
error
void NORETURN CDECL error(const char *s,...)
Report a fatal error.
Definition: settingsgen.cpp:31
IGT_SEQUENCE
@ IGT_SEQUENCE
A list of uninterpreted lines, terminated by the next group block.
Definition: ini_type.h:21
Subdirectory
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:108
CopyFile
static void CopyFile(const char *fname, FILE *out_fp)
Copy a file to the output.
Definition: settingsgen.cpp:351
DumpLine
static void DumpLine(IniItem *item, IniGroup *grp, IniGroup *default_grp, OutputStore &output)
Parse a single entry via a template and output this.
Definition: settingsgen.cpp:251
GetOptData::GetOpt
int GetOpt()
Find the next option.
Definition: getoptdata.cpp:22
OutputStore::Add
void Add(const char *text, size_t length=0)
Add text to the output storage.
Definition: settingsgen.cpp:111
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:378
IniGroup::item
IniItem * item
the first item in the group
Definition: ini_type.h:41
OutputBuffer
Output buffer for a block of data.
Definition: settingsgen.cpp:45
IniLoadFile::seq_group_names
const char *const * seq_group_names
nullptr terminated list with group names that are sequences.
Definition: ini_type.h:60
DumpSections
static void DumpSections(IniLoadFile *ifile)
Output all non-special sections through the template / template variable expansion system.
Definition: settingsgen.cpp:315
IniGroup::GetItem
IniItem * GetItem(const std::string &name, bool create)
Get the item with the given name, and if it doesn't exist and create is true it creates a new item.
Definition: ini_load.cpp:91
IniLoadFile::LoadFromDisk
void LoadFromDisk(const std::string &filename, Subdirectory subdir)
Load the Ini file's data from the disk.
Definition: ini_load.cpp:215
SettingsIniFile::ReportFileError
virtual void ReportFileError(const char *const pre, const char *const buffer, const char *const post)
Report an error about the file contents.
Definition: settingsgen.cpp:182
SettingsIniFile::SettingsIniFile
SettingsIniFile(const char *const *list_group_names=nullptr, const char *const *seq_group_names=nullptr)
Construct a new ini loader.
Definition: settingsgen.cpp:163
lastof
#define lastof(x)
Get the last element of an fixed size array.
Definition: stdafx.h:394
ODF_NO_VALUE
@ ODF_NO_VALUE
A plain option (no value attached to it).
Definition: getoptdata.h:15
GetOptData::numleft
int numleft
Number of arguments left in argv.
Definition: getoptdata.h:32
TEMPLATES_GROUP_NAME
static const char * TEMPLATES_GROUP_NAME
Name of the group containing the templates.
Definition: settingsgen.cpp:193
IniLoadFile::GetGroup
IniGroup * GetGroup(const std::string &name, bool create_new=true)
Get the group with the given name.
Definition: ini_load.cpp:163
IniGroup::next
IniGroup * next
the next group within this file
Definition: ini_type.h:39