1 /* $Id: settingsgen.cpp 23940 2012-02-12 19:46:40Z smatz $ */
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/>.
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"
21 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
31 #endif /* __MORPHOS__ */
33 #include "../safeguards.h"
36 * Report a fatal error.
37 * @param s Format string.
38 * @note Function does not return.
40 void NORETURN CDECL
error(const char *s
, ...)
45 vseprintf(buf
, lastof(buf
), s
, va
);
47 fprintf(stderr
, "FATAL: %s\n", buf
);
51 static const int OUTPUT_BLOCK_SIZE
= 16000; ///< Block size of the buffer in #OutputBuffer.
53 /** Output buffer for a block of data. */
56 /** Prepare buffer for use. */
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
;
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");
90 * Does the block have room for more data?
91 * @return \c true if room is available, else \c false.
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. */
110 /** Clear the temporary storage. */
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
;
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
;
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
);
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
);
188 fseek(in
, 0L, SEEK_SET
); // Seek back to the start of the file.
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.
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
);
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
) {
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
;
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
);
278 /* Prefix with #if/#ifdef/#ifndef */
279 static const char * const pp_lines
[] = {"if", "ifdef", "ifndef", NULL
};
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);
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') {
297 _stored_output
.Add(txt
, 1);
302 if (*txt
== '$') { // Literal $
303 _stored_output
.Add(txt
, 1);
309 char variable
[MAX_VAR_LENGTH
];
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
];
320 /* Find the text to output. */
321 const char *valitem
= FindItemValue(variable
, grp
, default_grp
);
322 if (valitem
!= NULL
) _stored_output
.Add(valitem
);
324 _stored_output
.Add("$", 1);
327 _stored_output
.Add("\n", 1); // \n after the expanded template.
329 _stored_output
.Add("#endif\n");
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");
346 fprintf(stderr
, "settingsgen: Warning: Cannot open file %s for copying\n", fname
);
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");
358 } while (length
== lengthof(buffer
));
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
);
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) {
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"),
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
);
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
);
449 int i
= mgo
.GetOpt();
458 puts("settingsgen - $Revision$\n"
459 "Usage: settingsgen [options] ini-file...\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");
469 output_file
= mgo
.opt
;
473 after_file
= mgo
.opt
;
477 before_file
= mgo
.opt
;
481 fprintf(stderr
, "Invalid arguments\n");
486 _stored_output
.Clear();
488 for (int i
= 0; i
< mgo
.numleft
; i
++) ProcessIniFile(mgo
.argv
[i
]);
491 if (output_file
== NULL
) {
492 CopyFile(before_file
, stdout
);
493 _stored_output
.Write(stdout
);
494 CopyFile(after_file
, stdout
);
496 static const char * const tmp_output
= "tmp2.xxx";
498 FILE *fp
= fopen(tmp_output
, "w");
500 fprintf(stderr
, "settingsgen: Warning: Cannot open file %s\n", tmp_output
);
503 CopyFile(before_file
, fp
);
504 _stored_output
.Write(fp
);
505 CopyFile(after_file
, fp
);
508 if (CompareFiles(tmp_output
, output_file
)) {
509 /* Files are equal. tmp2.xxx is not needed. */
512 /* Rename tmp2.xxx to output file. */
513 #if defined(WIN32) || defined(WIN64)
516 if (rename(tmp_output
, output_file
) == -1) error("rename() failed");