28#include <unordered_set>
43#include <simgear/embedded_resources/EmbeddedResource.hxx>
44#include <simgear/io/iostreams/sgstream.hxx>
45#include <simgear/misc/argparse.hxx>
46#include <simgear/misc/sg_path.hxx>
47#include <simgear/misc/strutils.hxx>
48#include <simgear/sg_inlines.h>
49#include <simgear/structure/exception.hxx>
50#include <simgear/xml/easyxml.hxx>
58using simgear::enumValue;
66#define LOG(stuff) do { cerr << PROGNAME << ": " << stuff << "\n"; } while(0)
70 std::ostringstream oss;
72 oss << std::fixed << std::setprecision(1);
73 if (nbBytes >= 1024*1024) {
74 oss << static_cast<float>(nbBytes) / (1024*1024) <<
" MiB";
75 }
else if (nbBytes >= 1024) {
76 oss << static_cast<float>(nbBytes) / 1024 <<
" KiB";
78 oss << nbBytes <<
" byte" << ((nbBytes == 1) ?
"" :
"s");
86 if (!simgear::strutils::ends_with(firstPart,
"/")) {
90 assert( !(simgear::strutils::starts_with(secondPart,
"/") ||
91 simgear::strutils::ends_with(secondPart,
"/")) );
92 SGPath virtualPath = SGPath::fromUtf8(firstPart + secondPart);
101 const SGPath& virtualPath_,
const SGPath& realPath_,
102 const std::string& language_,
103 simgear::AbstractEmbeddedResource::CompressionType compressionType_)
115 case simgear::AbstractEmbeddedResource::CompressionType::ZLIB:
118 case simgear::AbstractEmbeddedResource::CompressionType::NONE:
122 throw sg_exception(
"bug: unexpected compression type for an embedded "
135const std::array<string, 2> ResourceBuilderXMLVisitor::_tagTypeStr = {
139const std::array<string, 5> ResourceBuilderXMLVisitor::_parserStateStr = {
140 {
"before 'FGRCC' element",
141 "inside 'FGRCC' element",
142 "inside 'qresource' element",
143 "inside 'file' element",
144 "after 'FGRCC' element"
152const vector<ResourceDeclaration>&
155 return _resourceDeclarations;
160ResourceBuilderXMLVisitor::readBoolean(
const string& s)
162 if (s ==
"yes" || s ==
"true" || s ==
"1") {
164 }
else if (s ==
"no" || s ==
"false" || s ==
"0") {
168 "invalid value for a boolean attribute: '" + s +
"'. Authorized values "
169 "are 'yes', 'no', 'true', 'false', '1' and '0'.");
174simgear::AbstractEmbeddedResource::CompressionType
175ResourceBuilderXMLVisitor::determineCompressionType(
176 const SGPath& resFilePath,
const std::string& compression)
178 const std::unordered_set<std::string> extsWithNoCompression = {
179 "png",
"jpg",
"jpeg",
"gz",
"bz2",
"xz",
"lzma",
"zip" };
180 const std::string ext = resFilePath.lower_extension();
181 simgear::AbstractEmbeddedResource::CompressionType res;
183 if (compression ==
"none") {
184 res = simgear::AbstractEmbeddedResource::CompressionType::NONE;
185 }
else if (compression ==
"zlib") {
186 res = simgear::AbstractEmbeddedResource::CompressionType::ZLIB;
187 }
else if (compression ==
"auto") {
188 res = (extsWithNoCompression.count(ext) > 0) ?
189 simgear::AbstractEmbeddedResource::CompressionType::NONE :
190 simgear::AbstractEmbeddedResource::CompressionType::ZLIB;
193 "invalid value for the 'compression' attribute: '" + compression +
"'");
200ResourceBuilderXMLVisitor::unexpectedTagError(
201 XMLTagType tagType,
const string& found,
const string& expected)
203 std::ostringstream oss;
204 string final = expected.empty() ?
"" :
205 " (expected '" + expected +
"' instead)";
208 oss << getPath() <<
":" << getLine() <<
":" << getColumn() <<
209 ": unexpected " << _tagTypeStr[enumValue(tagType)] <<
" tag: '" << found <<
212 throw sg_exception(oss.str());
217 const XMLAttributes& atts)
219 switch (_parserState) {
220 case ParserState::START:
221 if (!std::strcmp(
name,
"FGRCC")) {
222 _parserState = ParserState::INSIDE_FGRCC_ELT;
224 unexpectedTagError(XMLTagType::START,
name,
"FGRCC");
227 case ParserState::INSIDE_FGRCC_ELT:
228 if (!std::strcmp(
name,
"qresource")) {
229 startQResourceElement(atts);
230 _parserState = ParserState::INSIDE_QRESOURCE_ELT;
232 unexpectedTagError(XMLTagType::START,
name,
"qresource");
235 case ParserState::INSIDE_QRESOURCE_ELT:
236 if (!std::strcmp(
name,
"file")) {
237 startFileElement(atts);
238 _parserState = ParserState::INSIDE_FILE_ELT;
240 unexpectedTagError(XMLTagType::START,
name,
"file");
243 case ParserState::INSIDE_FILE_ELT:
244 unexpectedTagError(XMLTagType::START,
name);
245 case ParserState::END:
246 unexpectedTagError(XMLTagType::START,
name);
248 throw std::logic_error(
249 "unexpected state reached in resource file parser: " +
250 std::to_string(enumValue(_parserState)));
257 switch (_parserState) {
258 case ParserState::START:
259 unexpectedTagError(XMLTagType::END,
name);
260 case ParserState::INSIDE_FGRCC_ELT:
261 if (!std::strcmp(
name,
"FGRCC")) {
262 _parserState = ParserState::END;
264 unexpectedTagError(XMLTagType::END,
name,
"FGRCC");
267 case ParserState::INSIDE_QRESOURCE_ELT:
268 if (!std::strcmp(
name,
"qresource")) {
269 _parserState = ParserState::INSIDE_FGRCC_ELT;
271 unexpectedTagError(XMLTagType::END,
name,
"qresource");
274 case ParserState::INSIDE_FILE_ELT:
275 if (!std::strcmp(
name,
"file")) {
277 const auto throwError = [
this]
278 (
const string& contents,
const string& message)
281 std::ostringstream oss;
282 oss << this->getPath() <<
":" << this->getLine() <<
":" <<
283 this->getColumn() <<
": invalid contents for a <file> element " <<
285 throw sg_format_exception(oss.str(), contents);
288 if (_resourceFile.empty()) {
289 throwError(_resourceFile,
"(empty)");
290 }
else if (simgear::strutils::ends_with(_resourceFile,
"/")) {
291 throwError(_resourceFile,
292 "(ending with a '/'): '" + _resourceFile +
"'");
295 const string secondPart = (_currentAlias.empty() ? _resourceFile :
305 if (
p.utf8Str() ==
"/") {
308 const SGPath realPath =
p / _resourceFile;
310 const auto compressionType = determineCompressionType(
311 realPath, _currentCompressionTypeStr);
315 _resourceDeclarations.emplace_back(
316 virtualPath, realPath, _currentLanguage, compressionType);
318 _parserState = ParserState::INSIDE_QRESOURCE_ELT;
320 unexpectedTagError(XMLTagType::END,
name,
"file");
323 case ParserState::END:
324 unexpectedTagError(XMLTagType::END,
name);
326 throw std::logic_error(
327 "unexpected state reached in resource file parser: " +
328 std::to_string(enumValue(_parserState)));
333ResourceBuilderXMLVisitor::startQResourceElement(
const XMLAttributes &atts)
335 const char *prefix = atts.getValue(
"prefix");
337 _currentPrefix = string(prefix ? prefix :
"/");
340 if (!simgear::strutils::starts_with(_currentPrefix,
"/") ||
341 (_currentPrefix !=
"/" && simgear::strutils::ends_with(_currentPrefix,
344 std::ostringstream oss;
345 oss << getPath() <<
":" << getLine() <<
": invalid 'prefix' attribute: '" <<
346 _currentPrefix <<
"' (must start with a '/' and not end with a '/', "
347 "unless equal to the one-char prefix '/')";
348 throw sg_format_exception(oss.str(), _currentPrefix);
351 const char *lang = atts.getValue(
"lang");
352 _currentLanguage = string(lang ? lang :
"");
356ResourceBuilderXMLVisitor::startFileElement(
const XMLAttributes &atts)
358 const char *alias = atts.getValue(
"alias");
360 if (alias && !std::strcmp(alias,
"")) {
361 std::ostringstream oss;
362 oss << getPath() <<
":" << getLine() <<
": invalid empty 'alias' attribute";
363 throw sg_format_exception(oss.str(),
string(alias));
367 _currentAlias = string(alias ? alias :
"");
369 const auto checkForError = [
this]
370 (
const string& valueToTest,
const string& startingOrEnding,
371 std::function<bool(
const string&,
const string &)> testFunc)
373 if (testFunc(valueToTest,
"/")) {
374 this->savePosition();
375 std::ostringstream oss;
376 oss << this->getPath() <<
":" << this->getLine() <<
377 ": invalid 'alias' attribute " << startingOrEnding <<
378 " with a '/': '" << valueToTest <<
"'";
379 throw sg_format_exception(oss.str(), valueToTest);
383 checkForError(_currentAlias,
"starting", simgear::strutils::starts_with);
384 checkForError(_currentAlias,
"ending", simgear::strutils::ends_with);
388 const char *compress = atts.getValue(
"compression");
389 _currentCompressionTypeStr = string(compress ? compress :
"auto");
392 _resourceFile.clear();
398 string chunk(s, len);
400 if (_parserState == ParserState::INSIDE_FILE_ELT) {
401 if (_resourceFile.empty() && simgear::strutils::starts_with(chunk,
"/")) {
403 std::ostringstream oss;
404 oss << getPath() <<
":" << getLine() <<
":" << getColumn() <<
405 ": invalid <file> element (contents starting with a '/'): " << chunk <<
407 throw sg_format_exception(oss.str(), chunk);
410 _resourceFile += chunk;
411 }
else if (chunk.find_first_not_of(
" \t\n") != string::npos) {
415 std::ostringstream oss;
418 oss << getPath() <<
":" << getLine() <<
":" << getColumn() <<
419 ": unexpected character data " <<
420 _parserStateStr[enumValue(_parserState)] <<
": '" << string(s, len) <<
423 throw sg_exception(oss.str());
430 LOG(
"warning: " << getPath() <<
": " << line <<
":" << column <<
": " <<
435ResourceBuilderXMLVisitor::error(
const char *message,
int line,
int column)
437 std::ostringstream oss;
438 oss << getPath() <<
": " << line <<
":" << column <<
": " << message;
440 throw sg_exception(oss.str());
448 : _inputStream(inputStream)
452[[ noreturn ]]
void CPPEncoder::handleWriteError(
int errorNumber)
455 "error while writing octal-encoded resource data: " +
456 simgear::strutils::error_string(errorNumber));
465 std::streamsize nbBytesRead;
468 const std::size_t maxColumns = 78;
469 std::size_t availableColumns = 0;
471 std::size_t payloadSize = 0;
476 _inputStream.read(buf,
sizeof(buf));
478 nbBytesRead = _inputStream.gcount();
479 auto charPtr =
reinterpret_cast<const unsigned char*
>(buf);
482 for (std::streamsize remaining = nbBytesRead; remaining > 0; remaining--) {
483 std::ostringstream oss;
484 oss <<
"\\" << std::oct << static_cast<unsigned int>(*charPtr++);
485 string theCharLiteral = oss.str();
487 if (availableColumns < theCharLiteral.size()) {
488 if (!(oStream <<
"\\\n")) {
489 handleWriteError(errno);
492 availableColumns = maxColumns;
495 if (!(oStream << theCharLiteral)) {
496 handleWriteError(errno);
498 availableColumns -= theCharLiteral.size();
501 payloadSize += nbBytesRead;
502 }
while (_inputStream);
504 if (_inputStream.bad()) {
506 "error while reading from the possibly-compressed resource stream: " +
507 simgear::strutils::error_string(savedErrno));
516 cppEncoder.
write(outputStream);
525 const vector<ResourceDeclaration>& resourceDeclarations,
526 std::ostream& outputStream,
527 const SGPath& outputCppFile,
528 const string& initFuncName,
529 const SGPath& outputHeaderFile,
530 const string& headerIdentifier,
531 std::size_t compInBufSize,
532 std::size_t compOutBufSize)
533 : _resDecl(resourceDeclarations),
534 _outputStream(outputStream),
535 _outputCppFile(outputCppFile),
536 _initFuncName(initFuncName),
537 _outputHeaderFile(outputHeaderFile),
538 _headerIdentifier(headerIdentifier),
539 _compInBufSize(compInBufSize),
540 _compOutBufSize(compOutBufSize),
541 _compressionInBuf(new char[_compInBufSize]),
542 _compressionOutBuf(new char[_compOutBufSize])
545std::size_t ResourceCodeGenerator::writeEncodedResourceContents(
548 std::unique_ptr<std::istream> iFileStream_p(
549 static_cast<std::istream *
>(
new sg_ifstream(resDecl.
realPath)));
551 if (! *iFileStream_p) {
552 throw sg_exception(
"unable to open file '" + resDecl.
realPath.utf8Str() +
553 "': " + simgear::strutils::error_string(errno));
556 std::unique_ptr<std::istream> iStream_p;
559 case simgear::AbstractEmbeddedResource::CompressionType::ZLIB:
561 static_cast<std::istream *
>(
562 new simgear::ZlibCompressorIStream(
563 std::move(iFileStream_p), resDecl.
realPath, Z_BEST_COMPRESSION,
564 simgear::ZLibCompressionFormat::ZLIB,
565 simgear::ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
566 &_compressionInBuf[0], _compInBufSize,
567 &_compressionOutBuf[0], _compOutBufSize, 0)));
569 case simgear::AbstractEmbeddedResource::CompressionType::NONE:
570 iStream_p = std::move(iFileStream_p);
573 throw sg_exception(
"bug: unexpected compression type for an embedded "
579 return CPPEncoder(*iStream_p).write(_outputStream);
583string ResourceCodeGenerator::resourceClass(
584 simgear::AbstractEmbeddedResource::CompressionType compressionType)
588 switch (compressionType) {
589 case simgear::AbstractEmbeddedResource::CompressionType::ZLIB:
590 resClass =
"ZlibEmbeddedResource";
592 case simgear::AbstractEmbeddedResource::CompressionType::NONE:
593 resClass =
"RawEmbeddedResource";
596 throw sg_exception(
"bug: unexpected compression type for an embedded "
598 + std::to_string(enumValue(compressionType)));
609string ResourceCodeGenerator::encodeResourceIndex(std::size_t index) {
611 std::size_t remainder;
618 remainder = index % 26;
619 res +=
'A' + remainder;
633 _outputStream.exceptions(std::ios_base::failbit | std::ios_base::badbit);
635 string msg = (_outputCppFile.isNull()) ?
636 "writing C++ contents to the standard output" :
637 "writing C++ file: '" + _outputCppFile.utf8Str() +
"'";
641// -*- coding: utf-8 -*-\n\
643// File automatically generated by " <<
PROGNAME <<
".\n \
648#include <simgear/io/iostreams/CharArrayStream.hxx>\n\
649#include <simgear/io/iostreams/zlibstream.hxx>\n\
650#include <simgear/embedded_resources/EmbeddedResource.hxx>\n\
651#include <simgear/embedded_resources/EmbeddedResourceManager.hxx>\n\
653using std::unique_ptr;\n\
654using simgear::AbstractEmbeddedResource;\n\
655using simgear::RawEmbeddedResource;\n\
656using simgear::ZlibEmbeddedResource;\n\
657using simgear::EmbeddedResourceManager;\n";
660 vector<std::size_t> resSizeInBytes;
662 for (vector<ResourceDeclaration>::size_type resNum = 0;
663 resNum < _resDecl.size(); resNum++) {
664 const auto& resDcl = _resDecl[resNum];
665 _outputStream <<
"\nstatic const char resource" <<
666 encodeResourceIndex(resNum) <<
"[] = \"";
667 resSizeInBytes.push_back(writeEncodedResourceContents(resDcl));
668 _outputStream <<
"\";\n";
671 _outputStream <<
"\n"
672 "void " << _initFuncName <<
"()\n"
674 ((_resDecl.empty()) ?
" " :
" const auto& resMgr = ") <<
675 "EmbeddedResourceManager::instance();\n";
677 for (vector<ResourceDeclaration>::size_type resNum = 0;
678 resNum < _resDecl.size(); resNum++) {
679 const auto& resDcl = _resDecl[resNum];
680 string resClass = resourceClass(resDcl.compressionType);
681 string encodedResNum = encodeResourceIndex(resNum);
683 _outputStream.flags(std::ios::dec);
684 _outputStream <<
"\n unique_ptr<const " << resClass <<
"> res" <<
685 encodedResNum <<
"(\n new " << resClass <<
"(";
686 std::ostringstream resConstructArgs;
688 switch (resDcl.compressionType) {
689 case simgear::AbstractEmbeddedResource::CompressionType::ZLIB:
690 resConstructArgs <<
"resource" << encodedResNum <<
", " <<
691 resSizeInBytes[resNum] <<
", " << resDcl.realPath.sizeInBytes();
693 case simgear::AbstractEmbeddedResource::CompressionType::NONE:
694 resConstructArgs <<
"resource" << encodedResNum <<
", " <<
695 resDcl.realPath.sizeInBytes();
699 "bug: unexpected compression type for an embedded resource: " +
700 std::to_string(enumValue(resDcl.compressionType)));
704 _outputStream << resConstructArgs.str() <<
705 "));\n resMgr->addResource("
706 "\"" << simgear::strutils::escape(resDcl.virtualPath.utf8Str()) <<
"\", "
707 "std::move(" <<
"res" << encodedResNum <<
")";
709 if (!resDcl.language.empty()) {
710 _outputStream <<
", \"" << simgear::strutils::escape(resDcl.language) <<
714 _outputStream <<
");\n";
717 std::ostringstream oss;
718 oss <<
"added '" << resDcl.realPath.utf8Str() <<
"' (";
720 if (resDcl.isCompressed()) {
731 _outputStream <<
"}\n";
734 std::size_t staticMemoryUsedByResources = std::accumulate(
735 resSizeInBytes.begin(), resSizeInBytes.end(), std::size_t(0));
736 LOG(
"static memory used by resources (total): " <<
739 if (!_outputHeaderFile.isNull()) {
746 assert(!_outputHeaderFile.isNull());
747 sg_ofstream outFile(_outputHeaderFile);
750 throw sg_exception(
"unable to open output header file '" +
751 _outputHeaderFile.utf8Str() +
"': " +
752 simgear::strutils::error_string(errno));
755 outFile.exceptions(std::ios_base::failbit | std::ios_base::badbit);
757 LOG(
"writing header file: '" << _outputHeaderFile.utf8Str() <<
"'");
759// -*- coding: utf-8 -*-\n\
761// Header file automatically generated by " <<
PROGNAME <<
".\n \
763#ifndef " << _headerIdentifier <<
"\n\
764#define " << _headerIdentifier <<
"\n\
766void " << _initFuncName <<
"();\n\
768#endif // of " << _headerIdentifier <<
"\n";
776 os <<
"Usage: " <<
PROGNAME <<
" [OPTION...] INFILE\n"
778Compile resources declared in INFILE, into C++ code.\n\
780INFILE should be a file in XML format declaring a set of resources. Each\n\
781resource has a contents that is initially read from a file, and a virtual\n\
782path that will be used for retrieval of the resource contents via the\n\
783EmbeddedResourceManager. The real path of a resource (that allows 'fgrcc' to\n\
784retrieve the resource data), its virtual path as well as other attributes\n\
785are all declared in INPUT.\n\
787For each resource declared in INPUT, 'fgrcc' thus reads metadata (virtual\n\
788path, language attribute...) and contents from the associated file. Then, it\n\
789generates C++ code that can be used to register the resources with SimGear's\n\
790EmbeddedResourceManager, of which FlightGear has an instance. In this\n\
791generated C++ code, the contents of each resource is represented by a static\n\
792array of const char. It is compressed by default, except for a few file\n\
793extensions (png, jpg, jpeg, gz, bz2...).\n\
795The EmbeddedResourceManager\n\
796---------------------------\n\
798The EmbeddedResourceManager is a SimGear class that provides several ways to\n\
799access the contents of a given resource. The simplest way is to retrieve the\n\
800resource contents as an std::string (this works for all kinds of resources,\n\
801be they text or binary). This method may be undesirable for large resources\n\
802though[1], because std::string contents is stored in dynamically allocated\n\
803memory, and therefore the creation of an std::string instance to hold the\n\
804resource contents always makes a copy of this contents (with automatic, on\n\
805the fly decompression for compressed resources).\n\
807 [1] Which may be undesirable per se anyway, since they have to be stored\n\
810The EmbeddedResourceManager class offers a few methods that are more\n\
811memory-friendly for large resources: by using SimGear classes such as\n\
812CharArrayIStream and ZlibDecompressorIStream, it gives zero-copy,\n\
813incremental access to resource contents, with transparent decompression in\n\
814the case of compressed resources. These two classes are derived from\n\
815std::istream, therefore this contents can be easily processed with standard\n\
816C++ techniques. For highest performance (using lower-level methods), the\n\
817EmbeddedResourceManager also provides access to resource data via an\n\
818std::streambuf interface, by means of classes such as ROCharArrayStreambuf\n\
819and ZlibDecompressorIStreambuf.\n\
821Format of the resource declaration file\n\
822---------------------------------------\n\
824The supported format for INFILE is a thin superset of the v1.0 QRC format\n\
825used by Qt (<http://doc.qt.io/qt-5/resources.html>). The differences with\n\
826this QRC format are:\n\
828 1. The <!DOCTYPE RCC> declaration at the beginning should be omitted (or\n\
829 replaced with <!DOCTYPE FGRCC>, however such a DTD currently doesn't\n\
830 exist). I suggest to add an XML declaration instead, for instance:\n\
832 <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
834 2. <RCC> and </RCC> must be replaced with <FGRCC> and </FGRCC>,\n\
837 3. The FGRCC format supports a 'compression' attribute for each 'file'\n\
838 element. At the time of this writing, the allowed values for this\n\
839 attribute are 'none', 'zlib' and 'auto'. When set to a value that is\n\
840 not 'auto', this attribute of course bypasses the algorithm for\n\
841 determining whether and how to compress a given resource (algorithm\n\
842 which relies on the file extension).\n\
844 4. Resource paths (paths to the real files, not virtual paths) are\n\
845 interpreted relatively to the directory specified with the --root\n\
846 option. If this option is not passed to 'fgrcc', then the default root\n\
847 directory is the one containing INFILE, which matches the behavior of\n\
850Here follows a sample resource declaration file. In the comments, we use\n\
851$ROOT to represent the folder specified with --root (this $ROOT notation is\n\
852only a placeholder used to explain the concepts here, it is *not* syntax\n\
853understood by 'fgrcc'!).\n\
855<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
857<FGRCC version=\"1.0\">\n\
859 <!-- The contents of '$ROOT/path/to/a/file' will be served by the\n\
860 EmbeddedResourceManager under the virtual path '/path/to/a/file'\n\
861 (anchored to '/' because we didn't define any prefix; see below).\n\
863 <file>path/to/a/file</file>\n\
864 <file compression=\"none\">another/file (won't be compressed)</file>\n\
865 <!-- This one will have the virtual path '/foobar/intro.txt'. -->\n\
866 <file alias=\"foobar/intro.txt\">yet another/file</file>\n\
869 <qresource prefix=\"/some/prefix\">\n\
870 <!-- The contents of '$ROOT/path/to/file1' will be served by the\n\
871 EmbeddedResourceManager under the virtual path\n\
872 '/some/prefix/path/to/file1'. -->\n\
873 <file>path/to/file1</file>\n\
874 <!-- The contents of '$ROOT/other/file' will be accessible through the\n\
875 virtual path '/some/prefix/my/alias'. -->\n\
876 <file alias=\"my/alias\">other/file</file>\n\
880 <!-- Default version of a resource -->\n\
881 <file>some/file</file>\n\
884 <qresource lang=\"fr\">\n\
885 <!-- French version of the same resource -->\n\
886 <file alias=\"some/file\">path/to/french/version</file>\n\
889 <qresource lang=\"fr_FR\">\n\
890 <!-- Ditto, but more specialized: French from France -->\n\
891 <file alias=\"some/file\">path/to/french/from/France/version</file>\n\
894 <qresource lang=\"de\">\n\
895 <!-- German version of the same resource -->\n\
896 <file alias=\"some/file\">path/to/german/version</file>\n\
900Options supported by 'fgrcc'\n\
901----------------------------\n\
903 --root=DIR Root directory used to interpret the real path of each\n\
904 declared resource (default: the directory containing\n\
906 -o, --output-cpp-file=CPP_OUT\n\
907 File where the main C++ output is to be written. If not\n\
908 specified, or if CPP_OUT is '-', the standard output is\n\
910 --output-header-file=HPP_OUT\n\
911 File where to write C++ header code corresponding to the\n\
912 code in CPP_OUT (this declares the function whose name can\n\
913 be chosen with --init-func-name, see below).\n\
914 --output-header-identifier=IDENT\n\
915 To avoid recursive inclusion, C and C++ header files\n\
916 are typically wrapped in a construct such as:\n\
918 #ifndef _SOME_IDENTIFIER\n\
919 #define _SOME_IDENTIFIER\n\
923 #endif // _SOME_IDENTIFIER\n\
925 This option allows one to choose the identifier used in\n\
926 HPP_OUT, when the --output-header-file option has been\n\
928 --init-func-name=FUNC\n\
929 Name of the function declared in HPP_OUT and defined in\n\
930 CPP_OUT, that registers all resources from INFILE with the\n\
931 EmbeddedResourceManager.\n\
932 --help Display this message and exit.\n";
935enum class ActionAfterCommandLineParsing {
940struct CmdLineParams {
943 SGPath outputCppFile;
944 SGPath outputHeaderFile;
945 string headerIdentifier;
949std::tuple<ActionAfterCommandLineParsing, int, CmdLineParams>
950parseCommandLine(
int argc,
const char *
const *argv)
952 using simgear::argparse::OptionArgType;
953 std::tuple<ActionAfterCommandLineParsing, int, CmdLineParams> res;
954 ActionAfterCommandLineParsing& action = std::get<0>(res);
955 int& exitStatus = std::get<1>(res);
956 CmdLineParams& params = std::get<2>(res);
959 params.rootDir = SGPath();
960 params.outputCppFile = SGPath(
"-");
961 params.outputHeaderFile = SGPath();
962 params.headerIdentifier = string();
963 params.initFuncName = string(
"initEmbeddedResources");
965 simgear::argparse::ArgumentParser parser;
966 parser.addOption(
"root", OptionArgType::MANDATORY_ARGUMENT,
"",
"--root");
967 parser.addOption(
"output cpp file", OptionArgType::MANDATORY_ARGUMENT,
968 "-o",
"--output-cpp-file");
969 parser.addOption(
"output header file", OptionArgType::MANDATORY_ARGUMENT,
970 "",
"--output-header-file");
971 parser.addOption(
"header identifier", OptionArgType::MANDATORY_ARGUMENT,
972 "",
"--output-header-identifier");
973 parser.addOption(
"init func name", OptionArgType::MANDATORY_ARGUMENT,
974 "",
"--init-func-name");
975 parser.addOption(
"help", OptionArgType::NO_ARGUMENT,
"",
"--help");
977 const auto parseArgsRes = parser.parseArgs(argc, argv);
979 for (
const auto& opt: parseArgsRes.first) {
980 if (opt.id() ==
"root") {
981 params.rootDir = SGPath::fromUtf8(opt.value());
982 }
else if (opt.id() ==
"output cpp file") {
983 params.outputCppFile = SGPath::fromUtf8(opt.value());
984 }
else if (opt.id() ==
"output header file") {
985 params.outputHeaderFile = SGPath::fromUtf8(opt.value());
986 }
else if (opt.id() ==
"header identifier") {
987 if (opt.value().empty()) {
988 LOG(
"invalid empty value for option '" << opt.passedAs() <<
"'");
989 action = ActionAfterCommandLineParsing::EXIT;
990 exitStatus = EXIT_FAILURE;
993 params.headerIdentifier = opt.value();
994 }
else if (opt.id() ==
"init func name") {
995 params.initFuncName = opt.value();
996 }
else if (opt.id() ==
"help") {
998 action = ActionAfterCommandLineParsing::EXIT;
999 exitStatus = EXIT_SUCCESS;
1003 action = ActionAfterCommandLineParsing::EXIT;
1004 exitStatus = EXIT_FAILURE;
1009 if (parseArgsRes.second.size() != 1) {
1011 action = ActionAfterCommandLineParsing::EXIT;
1012 exitStatus = EXIT_FAILURE;
1016 params.inputFile = SGPath::fromUtf8(parseArgsRes.second[0]);
1017 if (!params.inputFile.isFile()) {
1018 LOG(
"not an existing file: '" << params.inputFile.utf8Str() <<
"'");
1019 action = ActionAfterCommandLineParsing::EXIT;
1020 exitStatus = EXIT_FAILURE;
1024 if (!params.outputHeaderFile.isNull() && params.headerIdentifier.empty()) {
1025 LOG(
"option --output-header-identifier must be passed when "
1026 "--output-header-file has been given");
1027 action = ActionAfterCommandLineParsing::EXIT;
1028 exitStatus = EXIT_FAILURE;
1032 if (params.rootDir.isNull()) {
1033 params.rootDir = params.inputFile.dirPath();
1036 if (!params.rootDir.isDir()) {
1037 LOG(
"not an existing directory: '" << params.rootDir.utf8Str() <<
"'");
1038 action = ActionAfterCommandLineParsing::EXIT;
1039 exitStatus = EXIT_FAILURE;
1043 action = ActionAfterCommandLineParsing::CONTINUE;
1047int doTheWork(CmdLineParams params)
1049 std::streambuf *outputStreamBuf;
1050 bool outputToStdout = (params.outputCppFile.utf8Str() ==
"-");
1051 SGPath outputCppFile;
1054 if (outputToStdout) {
1057 outputStreamBuf = cout.rdbuf();
1059 outputCppFile = params.outputCppFile;
1060 output.open(outputCppFile);
1063 LOG(
"unable to open file '" << outputCppFile.utf8Str() <<
"': " <<
1064 simgear::strutils::error_string(errno));
1065 return EXIT_FAILURE;
1068 outputStreamBuf = output.rdbuf();
1071 std::ostream outputStream(outputStreamBuf);
1073 readXML(params.inputFile, xmlVisitor);
1075 outputStream, outputCppFile,
1076 params.initFuncName,
1077 params.outputHeaderFile,
1078 params.headerIdentifier);
1079 codeGenerator.writeCode();
1081 return EXIT_SUCCESS;
1084int main(
int argc,
char **argv)
1086 int exitStatus = EXIT_FAILURE;
1088 std::setlocale(LC_ALL,
"");
1089 std::setlocale(LC_NUMERIC,
"C");
1090 std::setlocale(LC_COLLATE,
"C");
1096 ActionAfterCommandLineParsing whatToDo;
1097 CmdLineParams params;
1098 std::tie(whatToDo, exitStatus, params) = parseCommandLine(argc, argv);
1100 if (whatToDo == ActionAfterCommandLineParsing::CONTINUE) {
1101 exitStatus = doTheWork(params);
1103 }
catch (
const sg_exception &e) {
1106 LOG(e.getFormattedMessage());
1108 }
catch (
const std::exception &e) {
CPPEncoder(std::istream &inputStream)
virtual std::size_t write(std::ostream &oStream)
ResourceBuilderXMLVisitor(const SGPath &rootDir)
void endElement(const char *name) override
const std::vector< ResourceDeclaration > & getResourceDeclarations() const
void warning(const char *message, int line, int column) override
void data(const char *s, int len) override
void startElement(const char *name, const XMLAttributes &atts) override
ResourceCodeGenerator(const std::vector< ResourceDeclaration > &resourceDeclarations, std::ostream &outputStream, const SGPath &outputCppFile, const std::string &initFuncName, const SGPath &outputHeaderFile, const std::string &headerIdentifier, std::size_t inBufSize=262144, std::size_t outBufSize=242144)
void writeHeaderFile() const
void showUsage(std::ostream &os)
std::ostream & operator<<(std::ostream &outputStream, CPPEncoder &cppEncoder)
static SGPath assembleVirtualPath(string firstPart, const string &secondPart)
static const string PROGNAME
static string prettyPrintNbOfBytes(std::size_t nbBytes)
simgear::AbstractEmbeddedResource::CompressionType compressionType
bool isCompressed() const
ResourceDeclaration(const SGPath &virtualPath, const SGPath &realPath, const std::string &language, simgear::AbstractEmbeddedResource::CompressionType compressionType)