Go to the documentation of this file.
10 #include "../stdafx.h"
11 #include "../core/endian_func.hpp"
12 #include "../string_func.h"
13 #include "../strings_type.h"
14 #include "../misc/getoptdata.h"
15 #include "../table/control_codes.h"
22 #if !defined(_WIN32) || defined(__CYGWIN__)
27 #if defined(_WIN32) || defined(__WATCOMC__)
31 #include "../table/strgen_tables.h"
33 #include "../safeguards.h"
37 # define LINE_NUM_FMT(s) "%s (%d): warning: %s (" s ")\n"
39 # define LINE_NUM_FMT(s) "%s:%d: " s ": %s\n"
42 void CDECL strgen_warning(
const char *s, ...)
53 void CDECL strgen_error(
const char *s, ...)
64 void NORETURN CDECL strgen_fatal(
const char *s, ...)
73 fprintf(stderr, LINE_NUM_FMT(
"warning"),
_file,
_cur_line,
"language is not compiled");
75 throw std::exception();
78 void NORETURN CDECL
error(
const char *s, ...)
87 fprintf(stderr, LINE_NUM_FMT(
"warning"),
_file,
_cur_line,
"language is not compiled");
106 this->fh = fopen(
file,
"rb");
107 if (this->fh ==
nullptr)
error(
"Could not open %s",
file);
116 char *
ReadLine(
char *buffer,
const char *last)
override
118 return fgets(buffer,
ClampToU16(last - buffer + 1), this->fh);
128 error(
"Language must include ##name, ##ownname and ##isocode");
135 if (!memcmp(str,
"id ", 3)) {
137 }
else if (!memcmp(str,
"name ", 5)) {
139 }
else if (!memcmp(str,
"ownname ", 8)) {
141 }
else if (!memcmp(str,
"isocode ", 8)) {
143 }
else if (!memcmp(str,
"textdir ", 8)) {
144 if (!memcmp(str + 8,
"ltr", 3)) {
146 }
else if (!memcmp(str + 8,
"rtl", 3)) {
149 error(
"Invalid textdir %s", str + 8);
151 }
else if (!memcmp(str,
"digitsep ", 9)) {
154 }
else if (!memcmp(str,
"digitsepcur ", 12)) {
157 }
else if (!memcmp(str,
"decimalsep ", 11)) {
160 }
else if (!memcmp(str,
"winlangid ", 10)) {
161 const char *buf = str + 10;
162 long langid = strtol(buf,
nullptr, 16);
163 if (langid > (
long)UINT16_MAX || langid < 0) {
164 error(
"Invalid winlangid %s", buf);
167 }
else if (!memcmp(str,
"grflangid ", 10)) {
168 const char *buf = str + 10;
169 long langid = strtol(buf,
nullptr, 16);
170 if (langid >= 0x7F || langid < 0) {
171 error(
"Invalid grflangid %s", buf);
174 }
else if (!memcmp(str,
"gender ", 7)) {
175 if (this->
master)
error(
"Genders are not allowed in the base translation.");
179 const char *s = ParseWord(&buf);
181 if (s ==
nullptr)
break;
186 }
else if (!memcmp(str,
"case ", 5)) {
187 if (this->
master)
error(
"Cases are not allowed in the base translation.");
191 const char *s = ParseWord(&buf);
193 if (s ==
nullptr)
break;
205 FILE *f2 = fopen(n2,
"rb");
206 if (f2 ==
nullptr)
return false;
208 FILE *f1 = fopen(n1,
"rb");
211 error(
"can't open %s", n1);
218 l1 = fread(b1, 1,
sizeof(b1), f1);
219 l2 = fread(b2, 1,
sizeof(b2), f2);
221 if (l1 != l2 || memcmp(b1, b2, l1)) {
245 this->fh = fopen(this->filename,
"wb");
247 if (this->fh ==
nullptr) {
248 error(
"Could not open %s", this->filename);
265 unlink(this->filename);
267 free(this->filename);
284 fprintf(this->
fh,
"/* This file is automatically generated. Do not modify */\n\n");
285 fprintf(this->
fh,
"#ifndef TABLE_STRINGS_H\n");
286 fprintf(this->
fh,
"#define TABLE_STRINGS_H\n");
297 if (
prev + 1 != stringid) fprintf(this->
fh,
"\n");
298 fprintf(this->
fh,
"static const StringID %s = 0x%X;\n", name, stringid);
305 int max_plural_forms = 0;
307 max_plural_forms = std::max(max_plural_forms,
_plural_forms[i].plural_count);
312 "static const uint LANGUAGE_PACK_VERSION = 0x%X;\n"
313 "static const uint LANGUAGE_MAX_PLURAL = %u;\n"
314 "static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
318 fprintf(this->
fh,
"#endif /* TABLE_STRINGS_H */\n");
328 unlink(this->real_filename);
330 if (rename(this->
filename, this->real_filename) == -1)
error(
"rename() failed");
347 this->
Write((
const byte *)header,
sizeof(*header));
352 if (fputc(0, this->
fh) == EOF) {
358 void Write(
const byte *buffer,
size_t length)
360 if (fwrite(buffer,
sizeof(*buffer), length, this->
fh) != length) {
371 #if defined(_WIN32) || defined(__WATCOMC__)
374 mkdir(directory, 0755);
383 static inline char *
mkpath(
char *buf,
const char *last,
const char *path,
const char *file)
387 char *p = strchr(buf,
'\0');
388 if (p[-1] != PATHSEPCHAR && p != last) *p++ = PATHSEPCHAR;
401 static inline char *replace_pathsep(
char *s)
403 for (
char *c = s; *c !=
'\0'; c++)
if (*c ==
'/') *c =
'\\';
407 static inline char *replace_pathsep(
char *s) {
return s; }
425 int CDECL
main(
int argc,
char *argv[])
427 char pathbuf[MAX_PATH];
428 const char *src_dir =
".";
429 const char *dest_dir =
nullptr;
433 int i = mgo.GetOpt();
442 printf(
"args\tflags\tcommand\treplacement\n");
443 for (
const CmdStruct *cs = _cmd_structs; cs <
endof(_cmd_structs); cs++) {
445 if (cs->proc == EmitGender) {
447 }
else if (cs->proc == EmitPlural) {
454 printf(
"%i\t%c\t\"%s\"\t\"%s\"\n", cs->consumes, flags, cs->cmd, strstr(cs->cmd,
"STRING") ?
"STRING" : cs->cmd);
459 printf(
"count\tdescription\tnames\n");
461 printf(
"%i\t\"%s\"\t%s\n", pf->plural_count, pf->description, pf->names);
466 printf(
"name\tflags\tdefault\tdescription\n");
468 printf(
"\"%s\"\t%s\t\"%s\"\t\"%s\"\n",
483 "strgen - $Revision$\n"
484 " -v | --version print version information and exit\n"
485 " -t | --todo replace any untranslated strings with '<TODO>'\n"
486 " -w | --warning print a warning for any untranslated strings\n"
487 " -h | -? | --help print this help message and exit\n"
488 " -s | --source_dir search for english.txt in the specified directory\n"
489 " -d | --dest_dir put output file in the specified directory, create if needed\n"
490 " -export-commands export all commands and exit\n"
491 " -export-plurals export all plural forms and exit\n"
492 " -export-pragmas export all pragmas and exit\n"
493 " Run without parameters and strgen will search for english.txt and parse it,\n"
494 " creating strings.h. Passing an argument, strgen will translate that language\n"
495 " file using english.txt as a reference and output <language>.lng."
500 src_dir = replace_pathsep(mgo.opt);
504 dest_dir = replace_pathsep(mgo.opt);
508 fprintf(stderr,
"Invalid arguments\n");
513 if (dest_dir ==
nullptr) dest_dir = src_dir;
520 if (mgo.numleft == 0) {
521 mkpath(pathbuf,
lastof(pathbuf), src_dir,
"english.txt");
526 master_reader.ParseFile();
527 if (_errors != 0)
return 1;
531 mkpath(pathbuf,
lastof(pathbuf), dest_dir,
"strings.h");
534 writer.WriteHeader(data);
535 writer.Finalise(data);
536 if (_errors != 0)
return 1;
537 }
else if (mgo.numleft >= 1) {
540 mkpath(pathbuf,
lastof(pathbuf), src_dir,
"english.txt");
545 master_reader.ParseFile();
547 for (
int i = 0; i < mgo.numleft; i++) {
548 data.FreeTranslation();
550 const char *translation = replace_pathsep(mgo.argv[i]);
551 const char *file = strrchr(translation, PATHSEPCHAR);
552 FileStringReader translation_reader(data, translation,
false, file ==
nullptr || strcmp(file + 1,
"english.txt") != 0);
553 translation_reader.ParseFile();
554 if (_errors != 0)
return 1;
557 r = strrchr(mgo.argv[i], PATHSEPCHAR);
558 mkpath(pathbuf,
lastof(pathbuf), dest_dir, (r !=
nullptr) ? &r[1] : mgo.argv[i]);
561 r = strrchr(pathbuf,
'.');
562 if (r ==
nullptr || strcmp(r,
".txt") != 0) r = strchr(pathbuf,
'\0');
566 writer.WriteLang(data);
570 if ((_show_todo & 2) != 0) {
571 fprintf(stdout,
"%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
const char * _file
The filename of the input, so we can refer to it in errors/warnings.
virtual void HandlePragma(char *str)
Handle the pragma of the file.
static const uint8 MAX_NUM_GENDERS
Maximum number of supported genders.
static bool CompareFiles(const char *n1, const char *n2)
Compare two files for identity.
@ TEXT_TAB_END
End of language files.
static const OptionData _opts[]
Options of strgen.
@ TD_LTR
Text is written left-to-right by default.
void WriteHeader(const LanguagePackHeader *header)
Write the header metadata.
Class for writing a language to disk.
#define GETOPT_VALUE(shortname, longname)
Short option with value.
LanguagePackHeader _lang
Header information about a language.
@ C_DONTCOUNT
These commands aren't counted for comparison.
int _cur_line
The current line we're parsing in the input file.
void HandlePragma(char *str) override
Handle the pragma of the file.
void Write(const byte *buffer, size_t length)
Write a number of bytes.
bool master
Are we reading the master file?
FileStringReader(StringData &data, const char *file, bool master, bool translation)
Create the reader.
size_t next_string_id
The next string ID to allocate.
#define NBSP
A non-breaking space.
const char * filename
The file name we're writing to.
uint Version() const
Make a hash of the file to get a unique "version number".
virtual ~FileStringReader()
Free/close the file.
static char * mkpath(char *buf, const char *last, const char *path, const char *file)
Create a path consisting of an already existing path, a possible path separator and the filename.
Information about the currently known strings.
#define GETOPT_NOVAL(shortname, longname)
Short option without value.
#define GETOPT_END()
Option terminator.
#define GETOPT_GENERAL(id, shortname, longname, flags)
General macro for creating an option.
static bool StrEmpty(const char *s)
Check if a string buffer is empty.
static void ottd_mkdir(const char *directory)
Multi-OS mkdirectory function.
int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap)
Safer implementation of vsnprintf; same as vsnprintf except:
A reader that simply reads using fopen.
FileWriter(const char *filename)
Open a file to write to.
Data storage for parsing command line options.
int CDECL main(int argc, char *argv[])
And the main program (what else?)
void ParseFile() override
Start parsing the file.
static const char *const _pragmas[][4]
All pragmas used.
#define endof(x)
Get the end element of an fixed size array.
char * ReadLine(char *buffer, const char *last) override
Read a single line from the source of strings.
StringData & data
The data to fill during reading.
LanguageFileWriter(const char *filename)
Open a file to write to.
FILE * fh
The file we are reading.
bool translation
Are we reading a translation, implies !master. However, the base translation will have this false.
virtual void ParseFile()
Start parsing the file.
char * stredup(const char *s, const char *last)
Create a duplicate of the given string.
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
#define lengthof(x)
Return the length of an fixed size array.
Base class for all language writers.
void Finalise()
Finalise writing the file.
static uint16 ClampToU16(const uint64 a)
Reduce an unsigned 64-bit int to an unsigned 16-bit one.
const char * file
The file we are reading.
char * strecpy(char *dst, const char *src, const char *last)
Copies characters from one buffer to another.
virtual ~FileWriter()
Make sure the file is closed.
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
void Finalise()
Finalise the writing.
#define lastof(x)
Get the last element of an fixed size array.
static const uint8 MAX_NUM_CASES
Maximum number of supported cases.
static const PluralForm _plural_forms[]
All plural forms used.
@ ODF_NO_VALUE
A plain option (no value attached to it).
@ TD_RTL
Text is written right-to-left by default.
Helper for reading strings.
FILE * fh
The file handle we're writing to.
Yes, simply writing to a file.