12 #include "../stdafx.h" 13 #include "../core/endian_func.hpp" 14 #include "../string_func.h" 15 #include "../strings_type.h" 16 #include "../misc/getoptdata.h" 17 #include "../table/control_codes.h" 24 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__) 29 #if defined WIN32 || defined __WATCOMC__ 40 #include "../table/strgen_tables.h" 42 #include "../safeguards.h" 46 # define LINE_NUM_FMT(s) "%s (%d): warning: %s (" s ")\n" 48 # define LINE_NUM_FMT(s) "%s:%d: " s ": %s\n" 51 void CDECL strgen_warning(
const char *s, ...)
62 void CDECL strgen_error(
const char *s, ...)
73 void NORETURN CDECL strgen_fatal(
const char *s, ...)
82 fprintf(stderr, LINE_NUM_FMT(
"warning"),
_file,
_cur_line,
"language is not compiled");
84 throw std::exception();
87 void NORETURN CDECL
error(
const char *s, ...)
96 fprintf(stderr, LINE_NUM_FMT(
"warning"),
_file,
_cur_line,
"language is not compiled");
115 this->fh = fopen(file,
"rb");
116 if (this->fh == NULL)
error(
"Could not open %s", file);
127 return fgets(buffer,
ClampToU16(last - buffer + 1), this->fh);
137 error(
"Language must include ##name, ##ownname and ##isocode");
144 if (!memcmp(str,
"id ", 3)) {
146 }
else if (!memcmp(str,
"name ", 5)) {
148 }
else if (!memcmp(str,
"ownname ", 8)) {
150 }
else if (!memcmp(str,
"isocode ", 8)) {
152 }
else if (!memcmp(str,
"textdir ", 8)) {
153 if (!memcmp(str + 8,
"ltr", 3)) {
155 }
else if (!memcmp(str + 8,
"rtl", 3)) {
158 error(
"Invalid textdir %s", str + 8);
160 }
else if (!memcmp(str,
"digitsep ", 9)) {
163 }
else if (!memcmp(str,
"digitsepcur ", 12)) {
166 }
else if (!memcmp(str,
"decimalsep ", 11)) {
169 }
else if (!memcmp(str,
"winlangid ", 10)) {
170 const char *buf = str + 10;
171 long langid = strtol(buf, NULL, 16);
172 if (langid > (
long)UINT16_MAX || langid < 0) {
173 error(
"Invalid winlangid %s", buf);
176 }
else if (!memcmp(str,
"grflangid ", 10)) {
177 const char *buf = str + 10;
178 long langid = strtol(buf, NULL, 16);
179 if (langid >= 0x7F || langid < 0) {
180 error(
"Invalid grflangid %s", buf);
183 }
else if (!memcmp(str,
"gender ", 7)) {
184 if (this->
master)
error(
"Genders are not allowed in the base translation.");
188 const char *s = ParseWord(&buf);
190 if (s == NULL)
break;
195 }
else if (!memcmp(str,
"case ", 5)) {
196 if (this->
master)
error(
"Cases are not allowed in the base translation.");
200 const char *s = ParseWord(&buf);
202 if (s == NULL)
break;
214 FILE *f2 = fopen(n2,
"rb");
215 if (f2 == NULL)
return false;
217 FILE *f1 = fopen(n1,
"rb");
218 if (f1 == NULL)
error(
"can't open %s", n1);
224 l1 = fread(b1, 1,
sizeof(b1), f1);
225 l2 = fread(b2, 1,
sizeof(b2), f2);
227 if (l1 != l2 || memcmp(b1, b2, l1)) {
250 this->filename =
stredup(filename);
251 this->fh = fopen(this->filename,
"wb");
253 if (this->fh == NULL) {
254 error(
"Could not open %s", this->filename);
271 unlink(this->filename);
273 free(this->filename);
288 real_filename(
stredup(filename)), prev(0)
290 fprintf(this->
fh,
"/* This file is automatically generated. Do not modify */\n\n");
291 fprintf(this->
fh,
"#ifndef TABLE_STRINGS_H\n");
292 fprintf(this->
fh,
"#define TABLE_STRINGS_H\n");
303 if (prev + 1 != stringid) fprintf(this->
fh,
"\n");
304 fprintf(this->
fh,
"static const StringID %s = 0x%X;\n", name, stringid);
311 int max_plural_forms = 0;
318 "static const uint LANGUAGE_PACK_VERSION = 0x%X;\n" 319 "static const uint LANGUAGE_MAX_PLURAL = %d;\n" 320 "static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
324 fprintf(this->
fh,
"#endif /* TABLE_STRINGS_H */\n");
328 if (
CompareFiles(this->filename, this->real_filename)) {
330 unlink(this->filename);
333 #if defined(WIN32) || defined(WIN64) 334 unlink(this->real_filename);
336 if (rename(this->filename, this->real_filename) == -1)
error(
"rename() failed");
353 this->Write((
const byte *)header,
sizeof(*header));
358 if (fputc(0, this->
fh) == EOF) {
359 error(
"Could not write to %s", this->filename);
364 void Write(
const byte *buffer,
size_t length)
366 if (fwrite(buffer,
sizeof(*buffer), length, this->
fh) != length) {
367 error(
"Could not write to %s", this->filename);
377 #if defined(WIN32) || defined(__WATCOMC__) 380 mkdir(directory, 0755);
389 static inline char *
mkpath(
char *buf,
const char *last,
const char *path,
const char *
file)
393 char *p = strchr(buf,
'\0');
394 if (p[-1] != PATHSEPCHAR && p != last) *p++ = PATHSEPCHAR;
399 #if defined(__MINGW32__) 405 static inline char *replace_pathsep(
char *s)
407 for (
char *c = s; *c !=
'\0'; c++)
if (*c ==
'/') *c =
'\\';
411 static inline char *replace_pathsep(
char *s) {
return s; }
429 int CDECL
main(
int argc,
char *argv[])
431 char pathbuf[MAX_PATH];
432 const char *src_dir =
".";
433 const char *dest_dir = NULL;
442 puts(
"$Revision: 27759 $");
446 printf(
"args\tflags\tcommand\treplacement\n");
447 for (
const CmdStruct *cs = _cmd_structs; cs <
endof(_cmd_structs); cs++) {
449 if (cs->proc == EmitGender) {
451 }
else if (cs->proc == EmitPlural) {
458 printf(
"%i\t%c\t\"%s\"\t\"%s\"\n", cs->consumes, flags, cs->cmd, strstr(cs->cmd,
"STRING") ?
"STRING" : cs->cmd);
463 printf(
"count\tdescription\tnames\n");
465 printf(
"%i\t\"%s\"\t%s\n", pf->plural_count, pf->description, pf->names);
470 printf(
"name\tflags\tdefault\tdescription\n");
472 printf(
"\"%s\"\t%s\t\"%s\"\t\"%s\"\n",
487 "strgen - $Revision: 27759 $\n" 488 " -v | --version print version information and exit\n" 489 " -t | --todo replace any untranslated strings with '<TODO>'\n" 490 " -w | --warning print a warning for any untranslated strings\n" 491 " -h | -? | --help print this help message and exit\n" 492 " -s | --source_dir search for english.txt in the specified directory\n" 493 " -d | --dest_dir put output file in the specified directory, create if needed\n" 494 " -export-commands export all commands and exit\n" 495 " -export-plurals export all plural forms and exit\n" 496 " -export-pragmas export all pragmas and exit\n" 497 " Run without parameters and strgen will search for english.txt and parse it,\n" 498 " creating strings.h. Passing an argument, strgen will translate that language\n" 499 " file using english.txt as a reference and output <language>.lng." 504 src_dir = replace_pathsep(mgo.
opt);
508 dest_dir = replace_pathsep(mgo.
opt);
512 fprintf(stderr,
"Invalid arguments\n");
517 if (dest_dir == NULL) dest_dir = src_dir;
525 mkpath(pathbuf,
lastof(pathbuf), src_dir,
"english.txt");
531 if (_errors != 0)
return 1;
535 mkpath(pathbuf,
lastof(pathbuf), dest_dir,
"strings.h");
543 mkpath(pathbuf,
lastof(pathbuf), src_dir,
"english.txt");
550 for (
int i = 0; i < mgo.
numleft; i++) {
554 const char *
file = strrchr(translation, PATHSEPCHAR);
555 FileStringReader translation_reader(data, translation,
false, file == NULL || strcmp(file + 1,
"english.txt") != 0);
557 if (_errors != 0)
return 1;
560 r = strrchr(mgo.
argv[i], PATHSEPCHAR);
561 mkpath(pathbuf,
lastof(pathbuf), dest_dir, (r != NULL) ? &r[1] : mgo.
argv[i]);
564 r = strrchr(pathbuf,
'.');
565 if (r == NULL || strcmp(r,
".txt") != 0) r = strchr(pathbuf,
'\0');
573 if ((_show_todo & 2) != 0) {
574 fprintf(stdout,
"%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
void Write(const byte *buffer, size_t length)
Write a number of bytes.
virtual void WriteLang(const StringData &data)
Actually write the language.
A plain option (no value attached to it).
int _cur_line
The current line we're parsing in the input file.
Yes, simply writing to a 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...
Class for writing a language to disk.
char ** argv
Remaining command line arguments.
bool master
Are we reading the master file?
#define GETOPT_VALUE(shortname, longname)
Short option with value.
int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap)
Safer implementation of vsnprintf; same as vsnprintf except:
virtual ~FileStringReader()
Free/close the file.
#define lastof(x)
Get the last element of an fixed size array.
const char * filename
The file name we're writing to.
static T max(const T a, const T b)
Returns the maximum of two values.
void ParseFile()
Start parsing the file.
Information about the currently known strings.
void Finalise()
Finalise writing the file.
bool translation
Are we reading a translation, implies !master. However, the base translation will have this false...
static bool CompareFiles(const char *n1, const char *n2)
Compare two files for identity.
char * ReadLine(char *buffer, const char *last)
Read a single line from the source of strings.
int main(int argc, char *argv[])
Entry point.
FileStringReader(StringData &data, const char *file, bool master, bool translation)
Create the reader.
#define GETOPT_GENERAL(id, shortname, longname, flags)
General macro for creating an option.
static const OptionData _opts[]
Options of strgen.
Structures related to strgen.
uint Version() const
Make a hash of the file to get a unique "version number".
FileWriter(const char *filename)
Open a file to write to.
char * stredup(const char *s, const char *last)
Create a duplicate of the given string.
#define GETOPT_END()
Option terminator.
A reader that simply reads using fopen.
void HandlePragma(char *str)
Handle the pragma of the file.
static const uint8 MAX_NUM_GENDERS
Maximum number of supported genders.
void WriteHeader(const LanguagePackHeader *header)
Write the header metadata.
char * opt
Option value, if available (else NULL).
const char * file
The file we are reading.
#define GETOPT_NOVAL(shortname, longname)
Short option without value.
#define lengthof(x)
Return the length of an fixed size array.
static const uint8 MAX_NUM_CASES
Maximum number of supported cases.
LanguagePackHeader _lang
Header information about a language.
void FreeTranslation()
Free all data related to the translation.
Text is written left-to-right by default.
virtual void HandlePragma(char *str)
Handle the pragma of the file.
virtual void ParseFile()
Start parsing the file.
const char * _file
The filename of the input, so we can refer to it in errors/warnings.
StringData & data
The data to fill during reading.
static const char *const _pragmas[][4]
All pragmas used.
Data storage for parsing command line options.
static bool StrEmpty(const char *s)
Check if a string buffer is empty.
Base class for all language writers.
LanguageFileWriter(const char *filename)
Open a file to write to.
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
char * strecpy(char *dst, const char *src, const char *last)
Copies characters from one buffer to another.
#define endof(x)
Get the end element of an fixed size array.
FILE * fh
The file we are reading.
FILE * fh
The file handle we're writing to.
These commands aren't counted for comparison.
int GetOpt()
Find the next option.
static void ottd_mkdir(const char *directory)
Multi-OS mkdirectory function.
int next_string_id
The next string ID to allocate.
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
void Finalise()
Finalise the writing.
Text is written right-to-left by default.
int numleft
Number of arguments left in argv.
#define NBSP
A non-breaking space.
Helper for reading strings.
virtual ~FileWriter()
Make sure the file is closed.
static uint16 ClampToU16(const uint64 a)
Reduce an unsigned 64-bit int to an unsigned 16-bit one.
static const PluralForm _plural_forms[]
All plural forms used.