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/>.
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"
19 #if !defined(_WIN32) || defined(__CYGWIN__)
24 #include "../safeguards.h"
27 * Report a fatal error.
28 * @param s Format string.
29 * @note Function does not return.
31 void NORETURN CDECL
error(const char *s
, ...)
36 vseprintf(buf
, lastof(buf
), s
, va
);
38 fprintf(stderr
, "FATAL: %s\n", buf
);
42 static const size_t OUTPUT_BLOCK_SIZE
= 16000; ///< Block size of the buffer in #OutputBuffer.
44 /** Output buffer for a block of data. */
47 /** Prepare buffer for use. */
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
;
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");
80 * Does the block have room for more data?
81 * @return \c true if room is available, else \c false.
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. */
100 /** Clear the temporary storage. */
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
;
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
;
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
);
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
);
178 fseek(in
, 0L, SEEK_SET
); // Seek back to the start of the file.
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.
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
);
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());
267 /* Prefix with #if/#ifdef/#ifndef */
268 static const char * const pp_lines
[] = {"if", "ifdef", "ifndef", nullptr};
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);
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') {
286 _stored_output
.Add(txt
, 1);
291 if (*txt
== '$') { // Literal $
292 _stored_output
.Add(txt
, 1);
298 char variable
[MAX_VAR_LENGTH
];
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
];
309 /* Find the text to output. */
310 const char *valitem
= FindItemValue(variable
, grp
, default_grp
);
311 if (valitem
!= nullptr) _stored_output
.Add(valitem
);
313 _stored_output
.Add("$", 1);
316 _stored_output
.Add("\n", 1); // \n after the expanded template.
318 _stored_output
.Add("#endif\n");
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
);
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");
347 } while (length
== lengthof(buffer
));
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");
366 error("can't open %s", n1
);
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) {
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"),
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
);
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
);
441 int i
= mgo
.GetOpt();
450 puts("settingsgen - $Revision$\n"
451 "Usage: settingsgen [options] ini-file...\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");
461 output_file
= mgo
.opt
;
465 after_file
= mgo
.opt
;
469 before_file
= mgo
.opt
;
473 fprintf(stderr
, "Invalid arguments\n");
478 _stored_output
.Clear();
480 for (int i
= 0; i
< mgo
.numleft
; i
++) ProcessIniFile(mgo
.argv
[i
]);
483 if (output_file
== nullptr) {
484 CopyFile(before_file
, stdout
);
485 _stored_output
.Write(stdout
);
486 CopyFile(after_file
, stdout
);
488 static const char * const tmp_output
= "tmp2.xxx";
490 FILE *fp
= fopen(tmp_output
, "w");
492 fprintf(stderr
, "settingsgen: Warning: Cannot open file %s\n", tmp_output
);
495 CopyFile(before_file
, fp
);
496 _stored_output
.Write(fp
);
497 CopyFile(after_file
, fp
);
500 if (CompareFiles(tmp_output
, output_file
)) {
501 /* Files are equal. tmp2.xxx is not needed. */
504 /* Rename tmp2.xxx to output file. */
508 if (rename(tmp_output
, output_file
) == -1) error("rename() failed");