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

OpenTTD Source 14.0-RC3

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/mem_func.hpp"
16 #include "../error_func.h"
17
18 #if !defined(_WIN32) || defined(__CYGWIN__)
19 #include <unistd.h>
20 #include <sys/stat.h>
21 #endif
22
23 #include "../safeguards.h"
24
30 [[noreturn]] void FatalErrorI(const std::string &msg)
31 {
32 fmt::print(stderr, "settingsgen: FATAL: {}\n", msg);
33 exit(1);
34 }
35
36 static const size_t OUTPUT_BLOCK_SIZE = 16000;
37
39 class OutputBuffer {
40 public:
42 void Clear()
43 {
44 this->size = 0;
45 }
46
53 size_t Add(const char *text, size_t length)
54 {
55 size_t store_size = std::min(length, OUTPUT_BLOCK_SIZE - this->size);
56 assert(store_size <= OUTPUT_BLOCK_SIZE);
57 MemCpyT(this->data + this->size, text, store_size);
58 this->size += store_size;
59 return store_size;
60 }
61
66 void Write(FILE *out_fp) const
67 {
68 if (fwrite(this->data, 1, this->size, out_fp) != this->size) {
69 FatalError("Cannot write output");
70 }
71 }
72
77 bool HasRoom() const
78 {
79 return this->size < OUTPUT_BLOCK_SIZE;
80 }
81
82 size_t size;
83 char data[OUTPUT_BLOCK_SIZE];
84 };
85
87 class OutputStore {
88 public:
89 OutputStore()
90 {
91 this->Clear();
92 }
93
95 void Clear()
96 {
97 this->output_buffer.clear();
98 }
99
105 void Add(const char *text, size_t length = 0)
106 {
107 if (length == 0) length = strlen(text);
108
109 if (length > 0 && this->BufferHasRoom()) {
110 size_t stored_size = this->output_buffer[this->output_buffer.size() - 1].Add(text, length);
111 length -= stored_size;
112 text += stored_size;
113 }
114 while (length > 0) {
115 OutputBuffer &block = this->output_buffer.emplace_back();
116 block.Clear(); // Initialize the new block.
117 size_t stored_size = block.Add(text, length);
118 length -= stored_size;
119 text += stored_size;
120 }
121 }
122
127 void Write(FILE *out_fp) const
128 {
129 for (const OutputBuffer &out_data : output_buffer) {
130 out_data.Write(out_fp);
131 }
132 }
133
134 private:
139 bool BufferHasRoom() const
140 {
141 size_t num_blocks = this->output_buffer.size();
142 return num_blocks > 0 && this->output_buffer[num_blocks - 1].HasRoom();
143 }
144
145 typedef std::vector<OutputBuffer> OutputBufferVector;
146 OutputBufferVector output_buffer;
147 };
148
149
151 struct SettingsIniFile : IniLoadFile {
157 SettingsIniFile(const IniGroupNameList &list_group_names = {}, const IniGroupNameList &seq_group_names = {}) :
158 IniLoadFile(list_group_names, seq_group_names)
159 {
160 }
161
162 FILE *OpenFile(const std::string &filename, Subdirectory, size_t *size) override
163 {
164 /* Open the text file in binary mode to prevent end-of-line translations
165 * done by ftell() and friends, as defined by K&R. */
166 FILE *in = fopen(filename.c_str(), "rb");
167 if (in == nullptr) return nullptr;
168
169 fseek(in, 0L, SEEK_END);
170 *size = ftell(in);
171
172 fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
173 return in;
174 }
175
176 void ReportFileError(const char * const pre, const char * const buffer, const char * const post) override
177 {
178 FatalError("{}{}{}", pre, buffer, post);
179 }
180 };
181
182 OutputStore _stored_output;
183 OutputStore _post_amble_output;
184
185 static const char *PREAMBLE_GROUP_NAME = "pre-amble";
186 static const char *POSTAMBLE_GROUP_NAME = "post-amble";
187 static const char *TEMPLATES_GROUP_NAME = "templates";
188 static const char *VALIDATION_GROUP_NAME = "validation";
189 static const char *DEFAULTS_GROUP_NAME = "defaults";
190
196 static void DumpGroup(const IniLoadFile &ifile, const char * const group_name)
197 {
198 const IniGroup *grp = ifile.GetGroup(group_name);
199 if (grp != nullptr && grp->type == IGT_SEQUENCE) {
200 for (const IniItem &item : grp->items) {
201 if (!item.name.empty()) {
202 _stored_output.Add(item.name.c_str());
203 _stored_output.Add("\n", 1);
204 }
205 }
206 }
207 }
208
216 static const char *FindItemValue(const char *name, const IniGroup *grp, const IniGroup *defaults)
217 {
218 const IniItem *item = grp->GetItem(name);
219 if (item == nullptr && defaults != nullptr) item = defaults->GetItem(name);
220 if (item == nullptr || !item->value.has_value()) return nullptr;
221 return item->value->c_str();
222 }
223
231 static void DumpLine(const IniItem *item, const IniGroup *grp, const IniGroup *default_grp, OutputStore &output)
232 {
233 static const int MAX_VAR_LENGTH = 64;
234
235 /* Prefix with #if/#ifdef/#ifndef */
236 static const auto pp_lines = {"if", "ifdef", "ifndef"};
237 int count = 0;
238 for (const auto &name : pp_lines) {
239 const char *condition = FindItemValue(name, grp, default_grp);
240 if (condition != nullptr) {
241 output.Add("#", 1);
242 output.Add(name);
243 output.Add(" ", 1);
244 output.Add(condition);
245 output.Add("\n", 1);
246 count++;
247 }
248 }
249
250 /* Output text of the template, except template variables of the form '$[_a-z0-9]+' which get replaced by their value. */
251 const char *txt = item->value->c_str();
252 while (*txt != '\0') {
253 if (*txt != '$') {
254 output.Add(txt, 1);
255 txt++;
256 continue;
257 }
258 txt++;
259 if (*txt == '$') { // Literal $
260 output.Add(txt, 1);
261 txt++;
262 continue;
263 }
264
265 /* Read variable. */
266 char variable[MAX_VAR_LENGTH];
267 int i = 0;
268 while (i < MAX_VAR_LENGTH - 1) {
269 if (!(txt[i] == '_' || (txt[i] >= 'a' && txt[i] <= 'z') || (txt[i] >= '0' && txt[i] <= '9'))) break;
270 variable[i] = txt[i];
271 i++;
272 }
273 variable[i] = '\0';
274 txt += i;
275
276 if (i > 0) {
277 /* Find the text to output. */
278 const char *valitem = FindItemValue(variable, grp, default_grp);
279 if (valitem != nullptr) output.Add(valitem);
280 } else {
281 output.Add("$", 1);
282 }
283 }
284 output.Add("\n", 1); // \n after the expanded template.
285 while (count > 0) {
286 output.Add("#endif\n");
287 count--;
288 }
289 }
290
295 static void DumpSections(const IniLoadFile &ifile)
296 {
297 static const auto special_group_names = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME, DEFAULTS_GROUP_NAME, TEMPLATES_GROUP_NAME, VALIDATION_GROUP_NAME};
298
299 const IniGroup *default_grp = ifile.GetGroup(DEFAULTS_GROUP_NAME);
300 const IniGroup *templates_grp = ifile.GetGroup(TEMPLATES_GROUP_NAME);
301 const IniGroup *validation_grp = ifile.GetGroup(VALIDATION_GROUP_NAME);
302 if (templates_grp == nullptr) return;
303
304 /* Output every group, using its name as template name. */
305 for (const IniGroup &grp : ifile.groups) {
306 /* Exclude special group names. */
307 if (std::find(std::begin(special_group_names), std::end(special_group_names), grp.name) != std::end(special_group_names)) continue;
308
309 const IniItem *template_item = templates_grp->GetItem(grp.name); // Find template value.
310 if (template_item == nullptr || !template_item->value.has_value()) {
311 FatalError("Cannot find template {}", grp.name);
312 }
313 DumpLine(template_item, &grp, default_grp, _stored_output);
314
315 if (validation_grp != nullptr) {
316 const IniItem *validation_item = validation_grp->GetItem(grp.name); // Find template value.
317 if (validation_item != nullptr && validation_item->value.has_value()) {
318 DumpLine(validation_item, &grp, default_grp, _post_amble_output);
319 }
320 }
321 }
322 }
323
329 static void CopyFile(const char *fname, FILE *out_fp)
330 {
331 if (fname == nullptr) return;
332
333 FILE *in_fp = fopen(fname, "r");
334 if (in_fp == nullptr) {
335 FatalError("Cannot open file {} for copying", fname);
336 }
337
338 char buffer[4096];
339 size_t length;
340 do {
341 length = fread(buffer, 1, lengthof(buffer), in_fp);
342 if (fwrite(buffer, 1, length, out_fp) != length) {
343 FatalError("Cannot copy file");
344 }
345 } while (length == lengthof(buffer));
346
347 fclose(in_fp);
348 }
349
356 static bool CompareFiles(const char *n1, const char *n2)
357 {
358 FILE *f2 = fopen(n2, "rb");
359 if (f2 == nullptr) return false;
360
361 FILE *f1 = fopen(n1, "rb");
362 if (f1 == nullptr) {
363 fclose(f2);
364 FatalError("can't open {}", n1);
365 }
366
367 size_t l1, l2;
368 do {
369 char b1[4096];
370 char b2[4096];
371 l1 = fread(b1, 1, sizeof(b1), f1);
372 l2 = fread(b2, 1, sizeof(b2), f2);
373
374 if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
375 fclose(f2);
376 fclose(f1);
377 return false;
378 }
379 } while (l1 != 0);
380
381 fclose(f2);
382 fclose(f1);
383 return true;
384 }
385
387 static const OptionData _opts[] = {
388 GETOPT_NOVAL( 'h', "--help"),
389 GETOPT_GENERAL('h', '?', nullptr, ODF_NO_VALUE),
390 GETOPT_VALUE( 'o', "--output"),
391 GETOPT_VALUE( 'b', "--before"),
392 GETOPT_VALUE( 'a', "--after"),
393 GETOPT_END(),
394 };
395
416 static void ProcessIniFile(const char *fname)
417 {
418 static const IniLoadFile::IniGroupNameList seq_groups = {PREAMBLE_GROUP_NAME, POSTAMBLE_GROUP_NAME};
419
420 SettingsIniFile ini{{}, seq_groups};
421 ini.LoadFromDisk(fname, NO_DIRECTORY);
422
423 DumpGroup(ini, PREAMBLE_GROUP_NAME);
424 DumpSections(ini);
425 DumpGroup(ini, POSTAMBLE_GROUP_NAME);
426 }
427
433 int CDECL main(int argc, char *argv[])
434 {
435 const char *output_file = nullptr;
436 const char *before_file = nullptr;
437 const char *after_file = nullptr;
438
439 GetOptData mgo(argc - 1, argv + 1, _opts);
440 for (;;) {
441 int i = mgo.GetOpt();
442 if (i == -1) break;
443
444 switch (i) {
445 case 'h':
446 fmt::print("settingsgen\n"
447 "Usage: settingsgen [options] ini-file...\n"
448 "with options:\n"
449 " -h, -?, --help Print this help message and exit\n"
450 " -b FILE, --before FILE Copy FILE before all settings\n"
451 " -a FILE, --after FILE Copy FILE after all settings\n"
452 " -o FILE, --output FILE Write output to FILE\n");
453 return 0;
454
455 case 'o':
456 output_file = mgo.opt;
457 break;
458
459 case 'a':
460 after_file = mgo.opt;
461 break;
462
463 case 'b':
464 before_file = mgo.opt;
465 break;
466
467 case -2:
468 fmt::print(stderr, "Invalid arguments\n");
469 return 1;
470 }
471 }
472
473 _stored_output.Clear();
474 _post_amble_output.Clear();
475
476 for (int i = 0; i < mgo.numleft; i++) ProcessIniFile(mgo.argv[i]);
477
478 /* Write output. */
479 if (output_file == nullptr) {
480 CopyFile(before_file, stdout);
481 _stored_output.Write(stdout);
482 _post_amble_output.Write(stdout);
483 CopyFile(after_file, stdout);
484 } else {
485 static const char * const tmp_output = "tmp2.xxx";
486
487 FILE *fp = fopen(tmp_output, "w");
488 if (fp == nullptr) {
489 FatalError("Cannot open file {}", tmp_output);
490 }
491 CopyFile(before_file, fp);
492 _stored_output.Write(fp);
493 _post_amble_output.Write(fp);
494 CopyFile(after_file, fp);
495 fclose(fp);
496
497 if (CompareFiles(tmp_output, output_file)) {
498 /* Files are equal. tmp2.xxx is not needed. */
499 unlink(tmp_output);
500 } else {
501 /* Rename tmp2.xxx to output file. */
502 #if defined(_WIN32)
503 unlink(output_file);
504 #endif
505 if (rename(tmp_output, output_file) == -1) FatalError("rename() failed");
506 }
507 }
508 return 0;
509 }
ProcessIniFile
static void ProcessIniFile(const char *fname)
Process a single INI file.
Definition: settingsgen.cpp:416
CompareFiles
static bool CompareFiles(const char *n1, const char *n2)
Compare two files for identity.
Definition: settingsgen.cpp:356
OutputStore::Write
void Write(FILE *out_fp) const
Write all stored output to the output stream.
Definition: settingsgen.cpp:127
DumpGroup
static void DumpGroup(const IniLoadFile &ifile, const char *const group_name)
Dump a IGT_SEQUENCE group into _stored_output.
Definition: settingsgen.cpp:196
OutputBuffer::data
char data[OUTPUT_BLOCK_SIZE]
Stored data.
Definition: settingsgen.cpp:83
OutputBuffer::Clear
void Clear()
Prepare buffer for use.
Definition: settingsgen.cpp:42
DEFAULTS_GROUP_NAME
static const char * DEFAULTS_GROUP_NAME
Name of the group containing default values for the template variables.
Definition: settingsgen.cpp:189
IniItem
A single "line" in an ini file.
Definition: ini_type.h:23
IniGroup::GetItem
const IniItem * GetItem(const std::string &name) const
Get the item with the given name.
Definition: ini_load.cpp:52
IniGroup
A group within an ini file.
Definition: ini_type.h:34
GETOPT_VALUE
#define GETOPT_VALUE(shortname, longname)
Short option with value.
Definition: getoptdata.h:76
FatalErrorI
void FatalErrorI(const std::string &msg)
Report a fatal error.
Definition: settingsgen.cpp:30
_post_amble_output
OutputStore _post_amble_output
Similar to _stored_output, but for the post amble.
Definition: settingsgen.cpp:183
OutputStore::BufferHasRoom
bool BufferHasRoom() const
Does the buffer have room without adding a new OutputBuffer block?
Definition: settingsgen.cpp:139
_stored_output
OutputStore _stored_output
Temporary storage of the output, until all processing is done.
Definition: settingsgen.cpp:182
IniGroup::items
std::list< IniItem > items
all items in the group
Definition: ini_type.h:35
OutputBuffer::size
size_t size
Number of bytes stored in data.
Definition: settingsgen.cpp:82
IniLoadFile::seq_group_names
const IniGroupNameList seq_group_names
list of group names that are sequences.
Definition: ini_type.h:56
IniLoadFile::list_group_names
const IniGroupNameList list_group_names
list of group names that are lists
Definition: ini_type.h:55
IniLoadFile
Ini file that only supports loading.
Definition: ini_type.h:50
GetOptData::argv
char ** argv
Remaining command line arguments.
Definition: getoptdata.h:33
POSTAMBLE_GROUP_NAME
static const char * POSTAMBLE_GROUP_NAME
Name of the group containing the post amble.
Definition: settingsgen.cpp:186
MemCpyT
void MemCpyT(T *destination, const T *source, size_t num=1)
Type-safe version of memcpy().
Definition: mem_func.hpp:23
OutputStore::Clear
void Clear()
Clear the temporary storage.
Definition: settingsgen.cpp:95
OUTPUT_BLOCK_SIZE
static const size_t OUTPUT_BLOCK_SIZE
Block size of the buffer in OutputBuffer.
Definition: settingsgen.cpp:36
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:25
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:77
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:146
GetOptData::opt
char * opt
Option value, if available (else nullptr).
Definition: getoptdata.h:31
IniLoadFile::GetGroup
const IniGroup * GetGroup(const std::string &name) const
Get the group with the given name.
Definition: ini_load.cpp:119
IniGroup::type
IniGroupType type
type of group
Definition: ini_type.h:36
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:151
OutputBuffer::Write
void Write(FILE *out_fp) const
Dump buffer to the output stream.
Definition: settingsgen.cpp:66
OutputStore
Temporarily store output.
Definition: settingsgen.cpp:87
OutputStore::OutputBufferVector
std::vector< OutputBuffer > OutputBufferVector
Vector type for output buffers.
Definition: settingsgen.cpp:145
SettingsIniFile::SettingsIniFile
SettingsIniFile(const IniGroupNameList &list_group_names={}, const IniGroupNameList &seq_group_names={})
Construct a new ini loader.
Definition: settingsgen.cpp:157
_opts
static const OptionData _opts[]
Options of settingsgen.
Definition: settingsgen.cpp:387
PREAMBLE_GROUP_NAME
static const char * PREAMBLE_GROUP_NAME
Name of the group containing the pre amble.
Definition: settingsgen.cpp:185
OutputBuffer::Add
size_t Add(const char *text, size_t length)
Add text to the output buffer.
Definition: settingsgen.cpp:53
VALIDATION_GROUP_NAME
static const char * VALIDATION_GROUP_NAME
Name of the group containing the validation statements.
Definition: settingsgen.cpp:188
main
int CDECL main(int argc, char *argv[])
And the main program (what else?)
Definition: settingsgen.cpp:433
IniGroup::name
std::string name
name of group
Definition: ini_type.h:37
IniLoadFile::IniLoadFile
IniLoadFile(const IniGroupNameList &list_group_names={}, const IniGroupNameList &seq_group_names={})
Construct a new in-memory Ini file representation.
Definition: ini_load.cpp:108
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:126
IGT_SEQUENCE
@ IGT_SEQUENCE
A list of uninterpreted lines, terminated by the next group block.
Definition: ini_type.h:19
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:329
GetOptData::GetOpt
int GetOpt()
Find the next option.
Definition: getoptdata.cpp:22
IniLoadFile::groups
std::list< IniGroup > groups
all groups in the ini
Definition: ini_type.h:53
IniItem::name
std::string name
The name of this item.
Definition: ini_type.h:24
OutputStore::Add
void Add(const char *text, size_t length=0)
Add text to the output storage.
Definition: settingsgen.cpp:105
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:300
OutputBuffer
Output buffer for a block of data.
Definition: settingsgen.cpp:39
IniLoadFile::LoadFromDisk
void LoadFromDisk(const std::string &filename, Subdirectory subdir)
Load the Ini file's data from the disk.
Definition: ini_load.cpp:187
DumpSections
static void DumpSections(const IniLoadFile &ifile)
Output all non-special sections through the template / template variable expansion system.
Definition: settingsgen.cpp:295
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
SettingsIniFile::OpenFile
FILE * OpenFile(const std::string &filename, Subdirectory, size_t *size) override
Open the INI file.
Definition: settingsgen.cpp:162
SettingsIniFile::ReportFileError
void ReportFileError(const char *const pre, const char *const buffer, const char *const post) override
Report an error about the file contents.
Definition: settingsgen.cpp:176
TEMPLATES_GROUP_NAME
static const char * TEMPLATES_GROUP_NAME
Name of the group containing the templates.
Definition: settingsgen.cpp:187
DumpLine
static void DumpLine(const IniItem *item, const IniGroup *grp, const IniGroup *default_grp, OutputStore &output)
Parse a single entry via a template and output this.
Definition: settingsgen.cpp:231
FindItemValue
static const char * FindItemValue(const char *name, const IniGroup *grp, const IniGroup *defaults)
Find the value of a template variable.
Definition: settingsgen.cpp:216