commit 28674714e83ae6feb2be72a185ef2527c1fa6d01 Author: Deukhoofd Date: Sun Aug 30 16:44:45 2020 +0200 Initial Commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..623f0d8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,136 @@ +# ClangFormatConfigureSource: 'clang-format-file:///home/nathan/Projects/PokemonLibraries/PkmnLib/.clang-format' +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Merge +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 1 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: c++20 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION + - Try +TabWidth: 8 +UseCRLF: false +UseTab: Never +... diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..fde9860 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,39 @@ +kind: pipeline +name: default + +type: docker +steps: + - name: build-release-linux + image: deukhoofd/linux64builder + environment: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + commands: + - cmake -DCMAKE_BUILD_TYPE=Release . -B build-release + - cmake --build build-release --target all -- -j 4 + - name: build-release-windows + image: deukhoofd/windowsbuilder + commands: + - update-alternatives --set i686-w64-mingw32-gcc /usr/bin/i686-w64-mingw32-gcc-posix + - update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix + - update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix + - update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + - cmake -DCMAKE_BUILD_TYPE=Release . -B build-release-windows -D CMAKE_C_COMPILER=/usr/bin/x86_64-w64-mingw32-gcc -D CMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ + - cmake --build build-release-windows --target all -- -j 4 + - name: gitea_release + image: plugins/gitea-release + settings: + api_key: 684b59c5281894a312cfb5f7c158a7720791eff1 + base_url: https://git.p-epsilon.com/ + files: + - build-release/LangBuilder + - build-release-windows/LangBuilder.exe + checksum: + - md5 + - sha1 + - sha256 + - sha512 + - adler32 + - crc32 + when: + event: tag \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39c8951 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/cmake-build-debug/ +/cmake-build-release/ +/build-release-windows/ +/.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ffc3e51 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.17) +project(LangBuilder) + +# Enable all warnings, and make them error when occurring. +add_compile_options(-Wall -Wextra -Werror) +# We like new stuff, so set the c++ standard to c++20. +set(CMAKE_CXX_STANDARD 20) + +file(GLOB_RECURSE SRC_FILES "src/*.cpp" "src/*.hpp") + +add_executable(LangBuilder ${SRC_FILES}) +target_link_libraries(LangBuilder -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread) \ No newline at end of file diff --git a/extern/argparse.hpp b/extern/argparse.hpp new file mode 100644 index 0000000..b058184 --- /dev/null +++ b/extern/argparse.hpp @@ -0,0 +1,566 @@ +/** + * License: Apache 2.0 with LLVM Exception or GPL v3 + * + * Author: Jesse Laning + */ + +#ifndef ARGPARSE_H +#define ARGPARSE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace argparse { + namespace detail { + static inline bool _not_space(int ch) { return !std::isspace(ch); } + static inline void _ltrim(std::string &s, bool (*f)(int) = _not_space) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), f)); + } + static inline void _rtrim(std::string &s, bool (*f)(int) = _not_space) { + s.erase(std::find_if(s.rbegin(), s.rend(), f).base(), s.end()); + } + static inline void _trim(std::string &s, bool (*f)(int) = _not_space) { + _ltrim(s, f); + _rtrim(s, f); + } + static inline std::string _ltrim_copy(std::string s, + bool (*f)(int) = _not_space) { + _ltrim(s, f); + return s; + } + static inline std::string _rtrim_copy(std::string s, + bool (*f)(int) = _not_space) { + _rtrim(s, f); + return s; + } + static inline std::string _trim_copy(std::string s, + bool (*f)(int) = _not_space) { + _trim(s, f); + return s; + } + template + static inline std::string _join(InputIt begin, InputIt end, + const std::string &separator = " ") { + std::ostringstream ss; + if (begin != end) { + ss << *begin++; + } + while (begin != end) { + ss << separator; + ss << *begin++; + } + return ss.str(); + } + static inline bool _is_number(const std::string &arg) { + std::istringstream iss(arg); + float f; + iss >> std::noskipws >> f; + return iss.eof() && !iss.fail(); + } + + static inline int _find_equal(const std::string &s) { + for (size_t i = 0; i < s.length(); ++i) { + // if find graph symbol before equal, end search + // i.e. don't accept --asd)f=0 arguments + // but allow --asd_f and --asd-f arguments + if (std::ispunct(static_cast(s[i]))) { + if (s[i] == '=') { + return static_cast(i); + } else if (s[i] == '_' || s[i] == '-') { + continue; + } + return -1; + } + } + return -1; + } + + static inline size_t _find_name_end(const std::string &s) { + size_t i; + for (i = 0; i < s.length(); ++i) { + if (std::ispunct(static_cast(s[i]))) { + break; + } + } + return i; + } + + namespace is_vector_impl { + template + struct is_vector : std::false_type {}; + template + struct is_vector> : std::true_type {}; + } // namespace is_vector_impl + +// type trait to utilize the implementation type traits as well as decay the +// type + template + struct is_vector { + static constexpr bool const value = + is_vector_impl::is_vector::type>::value; + }; + } // namespace detail + + class ArgumentParser { + private: + public: + class Argument; + + class Result { + public: + Result() {} + Result(std::string err) noexcept : _error(true), _what(err) {} + + operator bool() const { return _error; } + + friend std::ostream &operator<<(std::ostream &os, const Result &dt); + + const std::string &what() const { return _what; } + + private: + bool _error{false}; + std::string _what{}; + }; + + class Argument { + public: + enum Position : int { LAST = -1, DONT_CARE = -2 }; + enum Count : int { ANY = -1 }; + + Argument &name(const std::string &name) { + _names.push_back(name); + return *this; + } + + Argument &names(std::vector names) { + _names.insert(_names.end(), names.begin(), names.end()); + return *this; + } + + Argument &description(const std::string &description) { + _desc = description; + return *this; + } + + Argument &required(bool req) { + _required = req; + return *this; + } + + Argument &position(int position) { + if (position != Position::LAST) { + // position + 1 because technically argument zero is the name of the + // executable + _position = position + 1; + } else { + _position = position; + } + return *this; + } + + Argument &count(int count) { + _count = count; + return *this; + } + + bool found() const { return _found; } + + template + typename std::enable_if::value, T>::type get() { + T t = T(); + typename T::value_type vt; + for (auto &s : _values) { + std::istringstream in(s); + in >> vt; + t.push_back(vt); + } + return t; + } + + template + typename std::enable_if::value, T>::type get() { + std::istringstream in(get()); + T t = T(); + in >> t >> std::ws; + return t; + } + + private: + Argument(const std::string &name, const std::string &desc, + bool required = false) + : _desc(desc), _required(required) { + _names.push_back(name); + } + + Argument() {} + + friend class ArgumentParser; + int _position{Position::DONT_CARE}; + int _count{Count::ANY}; + std::vector _names{}; + std::string _desc{}; + bool _found{false}; + bool _required{false}; + int _index{-1}; + + std::vector _values{}; + }; + + ArgumentParser(const std::string &bin, const std::string &desc) + : _bin(bin), _desc(desc) {} + + Argument &add_argument() { + _arguments.push_back({}); + _arguments.back()._index = static_cast(_arguments.size()) - 1; + return _arguments.back(); + } + + Argument &add_argument(const std::string &name, const std::string &long_name, + const std::string &desc, const bool required = false) { + _arguments.push_back(Argument(name, desc, required)); + _arguments.back()._names.push_back(long_name); + _arguments.back()._index = static_cast(_arguments.size()) - 1; + return _arguments.back(); + } + + Argument &add_argument(const std::string &name, const std::string &desc, + const bool required = false) { + _arguments.push_back(Argument(name, desc, required)); + _arguments.back()._index = static_cast(_arguments.size()) - 1; + return _arguments.back(); + } + + void print_help(size_t count = 0, size_t page = 0) { + if (page * count > _arguments.size()) { + return; + } + if (page == 0) { + std::cout << "Usage: " << _bin; + if (_positional_arguments.empty()) { + std::cout << " [options...]" << std::endl; + } else { + int current = 1; + for (auto &v : _positional_arguments) { + if (v.first != Argument::Position::LAST) { + for (; current < v.first; current++) { + std::cout << " [" << current << "]"; + } + std::cout + << " [" + << detail::_ltrim_copy( + _arguments[static_cast(v.second)]._names[0], + [](int c) -> bool { return c != static_cast('-'); }) + << "]"; + } + } + auto it = _positional_arguments.find(Argument::Position::LAST); + if (it == _positional_arguments.end()) { + std::cout << " [options...]"; + } else { + std::cout + << " [options...] [" + << detail::_ltrim_copy( + _arguments[static_cast(it->second)]._names[0], + [](int c) -> bool { return c != static_cast('-'); }) + << "]"; + } + std::cout << std::endl; + } + std::cout << "Options:" << std::endl; + } + if (count == 0) { + page = 0; + count = _arguments.size(); + } + for (size_t i = page * count; + i < std::min(page * count + count, _arguments.size()); i++) { + Argument &a = _arguments[i]; + std::string name = a._names[0]; + for (size_t n = 1; n < a._names.size(); ++n) { + name.append(", " + a._names[n]); + } + std::cout << " " << std::setw(23) << std::left << name << std::setw(23) + << a._desc; + if (a._required) { + std::cout << " (Required)"; + } + std::cout << std::endl; + } + } + + Result parse(int argc, const char *argv[]) { + Result err; + if (argc > 1) { + // build name map + for (auto &a : _arguments) { + for (auto &n : a._names) { + std::string name = detail::_ltrim_copy( + n, [](int c) -> bool { return c != static_cast('-'); }); + if (_name_map.find(name) != _name_map.end()) { + return Result("Duplicate of argument name: " + n); + } + _name_map[name] = a._index; + } + if (a._position >= 0 || a._position == Argument::Position::LAST) { + _positional_arguments[a._position] = a._index; + } + } + if (err) { + return err; + } + + // parse + std::string current_arg; + size_t arg_len; + for (int argv_index = 1; argv_index < argc; ++argv_index) { + current_arg = std::string(argv[argv_index]); + arg_len = current_arg.length(); + if (arg_len == 0) { + continue; + } + if (_help_enabled && (current_arg == "-h" || current_arg == "--help")) { + _arguments[static_cast(_name_map["help"])]._found = true; + } else if (argv_index == argc - 1 && + _positional_arguments.find(Argument::Position::LAST) != + _positional_arguments.end()) { + err = _end_argument(); + Result b = err; + err = _add_value(current_arg, Argument::Position::LAST); + if (b) { + return b; + } + if (err) { + return err; + } + } else if (arg_len >= 2 && + !detail::_is_number(current_arg)) { // ignores the case if + // the arg is just a - + // look for -a (short) or --arg (long) args + if (current_arg[0] == '-') { + err = _end_argument(); + if (err) { + return err; + } + // look for --arg (long) args + if (current_arg[1] == '-') { + err = _begin_argument(current_arg.substr(2), true, argv_index); + if (err) { + return err; + } + } else { // short args + err = _begin_argument(current_arg.substr(1), false, argv_index); + if (err) { + return err; + } + } + } else { // argument value + err = _add_value(current_arg, argv_index); + if (err) { + return err; + } + } + } else { // argument value + err = _add_value(current_arg, argv_index); + if (err) { + return err; + } + } + } + } + if (_help_enabled && exists("help")) { + return Result(); + } + err = _end_argument(); + if (err) { + return err; + } + for (auto &p : _positional_arguments) { + Argument &a = _arguments[static_cast(p.second)]; + if (a._values.size() > 0 && a._values[0][0] == '-') { + std::string name = detail::_ltrim_copy(a._values[0], [](int c) -> bool { + return c != static_cast('-'); + }); + if (_name_map.find(name) != _name_map.end()) { + if (a._position == Argument::Position::LAST) { + return Result( + "Poisitional argument expected at the end, but argument " + + a._values[0] + " found instead"); + } else { + return Result("Poisitional argument expected in position " + + std::to_string(a._position) + ", but argument " + + a._values[0] + " found instead"); + } + } + } + } + for (auto &a : _arguments) { + if (a._required && !a._found) { + return Result("Required argument not found: " + a._names[0]); + } + if (a._position >= 0 && argc >= a._position && !a._found) { + return Result("Argument " + a._names[0] + " expected in position " + + std::to_string(a._position)); + } + } + return Result(); + } + + void enable_help() { + add_argument("-h", "--help", "Shows this page", false); + _help_enabled = true; + } + + bool exists(const std::string &name) const { + std::string n = detail::_ltrim_copy( + name, [](int c) -> bool { return c != static_cast('-'); }); + auto it = _name_map.find(n); + if (it != _name_map.end()) { + return _arguments[static_cast(it->second)]._found; + } + return false; + } + + template + T get(const std::string &name) { + auto t = _name_map.find(name); + if (t != _name_map.end()) { + return _arguments[static_cast(t->second)].get(); + } + return T(); + } + + private: + Result _begin_argument(const std::string &arg, bool longarg, int position) { + auto it = _positional_arguments.find(position); + if (it != _positional_arguments.end()) { + Result err = _end_argument(); + Argument &a = _arguments[static_cast(it->second)]; + a._values.push_back((longarg ? "--" : "-") + arg); + a._found = true; + return err; + } + if (_current != -1) { + return Result("Current argument left open"); + } + size_t name_end = detail::_find_name_end(arg); + std::string arg_name = arg.substr(0, name_end); + if (longarg) { + int equal_pos = detail::_find_equal(arg); + auto nmf = _name_map.find(arg_name); + if (nmf == _name_map.end()) { + return Result("Unrecognized command line option '" + arg_name + "'"); + } + _current = nmf->second; + _arguments[static_cast(nmf->second)]._found = true; + if (equal_pos == 0 || + (equal_pos < 0 && + arg_name.length() < arg.length())) { // malformed argument + return Result("Malformed argument: " + arg); + } else if (equal_pos > 0) { + std::string arg_value = arg.substr(name_end + 1); + _add_value(arg_value, position); + } + } else { + Result r; + if (arg_name.length() == 1) { + return _begin_argument(arg, true, position); + } else { + for (char &c : arg_name) { + r = _begin_argument(std::string(1, c), true, position); + if (r) { + return r; + } + r = _end_argument(); + if (r) { + return r; + } + } + } + } + return Result(); + } + + Result _add_value(const std::string &value, int location) { + if (_current >= 0) { + Result err; + Argument &a = _arguments[static_cast(_current)]; + if (a._count >= 0 && static_cast(a._values.size()) >= a._count) { + err = _end_argument(); + if (err) { + return err; + } + goto unnamed; + } + a._values.push_back(value); + if (a._count >= 0 && static_cast(a._values.size()) >= a._count) { + err = _end_argument(); + if (err) { + return err; + } + } + return Result(); + } else { + unnamed: + auto it = _positional_arguments.find(location); + if (it != _positional_arguments.end()) { + Argument &a = _arguments[static_cast(it->second)]; + a._values.push_back(value); + a._found = true; + } + // TODO + return Result(); + } + } + + Result _end_argument() { + if (_current >= 0) { + Argument &a = _arguments[static_cast(_current)]; + _current = -1; + if (static_cast(a._values.size()) < a._count) { + return Result("Too few arguments given for " + a._names[0]); + } + if (a._count >= 0) { + if (static_cast(a._values.size()) > a._count) { + return Result("Too many arguments given for " + a._names[0]); + } + } + } + return Result(); + } + + bool _help_enabled{false}; + int _current{-1}; + std::string _bin{}; + std::string _desc{}; + std::vector _arguments{}; + std::map _positional_arguments{}; + std::map _name_map{}; + }; + + std::ostream &operator<<(std::ostream &os, const ArgumentParser::Result &r) { + os << r.what(); + return os; + } + template <> + inline std::string ArgumentParser::Argument::get() { + return detail::_join(_values.begin(), _values.end()); + } + template <> + inline std::vector + ArgumentParser::Argument::get>() { + return _values; + } + +} // namespace argparse +#endif diff --git a/src/LocalizationData.cpp b/src/LocalizationData.cpp new file mode 100644 index 0000000..a39200f --- /dev/null +++ b/src/LocalizationData.cpp @@ -0,0 +1,114 @@ +#include "LocalizationData.hpp" + +void LocalizationData::LoadFromPath(const std::filesystem::path& path) { + auto cfgPath = path / "config.cfg"; + if (!std::filesystem::exists(cfgPath)) { + std::stringstream ss; + ss << "Config file 'config.cfg' does not exist in path " << path; + throw std::logic_error(ss.str()); + } + std::ifstream cfgFile; + cfgFile.open(cfgPath, std::ios::in); + if (!cfgFile.is_open()) { + throw std::logic_error("Something went wrong loading config file."); + } + std::string line; + while (std::getline(cfgFile, line)) { + std::string key; + std::string value; + std::stringstream linestream(line); + std::getline(linestream, key, '='); + std::getline(linestream, value, '='); + if (key == "language-code") + _code = value; + else if (key == "language-display") + _display = value; + else if (key == "global-path") + _globalPath = value; + else if (key == "temp-path") + _tempPath = value; + } + cfgFile.close(); + + std::cout << "Language code: " << _code << std::endl; + std::cout << "Language Display: " << _display << std::endl; + std::cout << "Globals Path: " << _globalPath << std::endl; + std::cout << "Temp Path: " << _tempPath << std::endl; + + for (const auto& p : std::filesystem::recursive_directory_iterator(path / _globalPath)) { + if (p.path().extension() != ".csv") + continue; + LocalizationFile file; + file.LoadFile(p); + _globalFiles[p.path().filename().stem().string()] = file; + } + for (const auto& p : std::filesystem::recursive_directory_iterator(path / _tempPath)) { + if (p.path().extension() != ".csv") + continue; + LocalizationFile file; + file.LoadFile(p); + _tempFiles[p.path().filename().stem().string()] = file; + } +} + +void LocalizationData::WriteToFile(const std::filesystem::path& path) { + std::ofstream outFile; + outFile.open(path, std::ios::out | std::ios::trunc); + if (!outFile.is_open()) { + std::stringstream ss; + ss << "Something went wrong opening output file " << path; + throw std::logic_error(ss.str()); + } + outFile << _code << "~" << _display << std::endl; + outFile << "global" << std::endl; + + std::unordered_map globalFilesPositions; + for (const auto& file : _globalFiles) { + outFile << file.first << "~"; + auto p1 = outFile.tellp(); + outFile << "0000~0000" << std::endl; + globalFilesPositions[file.first] = p1; + } + outFile << "temp" << std::endl; + std::unordered_map tempFilesPositions; + for (const auto& file : _tempFiles) { + outFile << file.first << "~"; + auto p1 = outFile.tellp(); + outFile << "0000~0000" << std::endl; + tempFilesPositions[file.first] = p1; + } + outFile << "ENDDATA" << std::endl; + // actual localization + for (const auto& file : _globalFiles) { + auto pos = outFile.tellp(); + for (const auto& kv : file.second.GetMap()) { + outFile << kv.first << "|" << kv.second << std::endl; + } + auto end = outFile.tellp(); + auto length = end - pos; + auto data = globalFilesPositions[file.first]; + outFile.seekp(data); + outFile << std::setfill ('0') << std::setw(4) << std::hex << pos; + outFile << "~"; + outFile << std::setfill ('0') << std::setw(4) << std::hex << length; + outFile.seekp(end); + } + for (const auto& file : _tempFiles) { + auto pos = outFile.tellp(); + for (const auto& kv : file.second.GetMap()) { + outFile << kv.first << "|" << kv.second << std::endl; + } + auto end = outFile.tellp(); + auto length = end - pos; + auto data = tempFilesPositions[file.first]; + outFile.seekp(data); + outFile << std::setfill ('0') << std::setw(4) << std::hex << pos; + outFile << "~"; + outFile << std::setfill ('0') << std::setw(4) << std::hex << length; + outFile.seekp(end); + } + + outFile.close(); + + std::cout << "Wrote language file to " << path << std::endl; +} diff --git a/src/LocalizationData.hpp b/src/LocalizationData.hpp new file mode 100644 index 0000000..961f981 --- /dev/null +++ b/src/LocalizationData.hpp @@ -0,0 +1,30 @@ +#ifndef LANGBUILDER_LOCALIZATIONDATA_HPP +#define LANGBUILDER_LOCALIZATIONDATA_HPP + +#include +#include +#include +#include +#include +#include "LocalizationFile.hpp" + +class LocalizationData { + std::string _code; + std::string _display; + std::string _globalPath; + std::string _tempPath; + + std::unordered_map _globalFiles; + std::unordered_map _tempFiles; + +public: + void LoadFromPath(const std::filesystem::path& path); + + void WriteToFile(const std::filesystem::path& path); + + const std::string& GetCode() const noexcept { return _code; } + + const std::string& GetDisplay() const noexcept { return _display; } +}; + +#endif // LANGBUILDER_LOCALIZATIONDATA_HPP diff --git a/src/LocalizationFile.cpp b/src/LocalizationFile.cpp new file mode 100644 index 0000000..f5ff53b --- /dev/null +++ b/src/LocalizationFile.cpp @@ -0,0 +1,37 @@ +#include "LocalizationFile.hpp" +#include +#include + +void LocalizationFile::LoadFile(const std::filesystem::path& path) { + if (!std::filesystem::exists(path)) { + std::stringstream ss; + ss << "File at path " << path << " does not exist."; + throw std::logic_error(ss.str()); + } + std::ifstream file; + file.open(path, std::ios::in); + if (!file.is_open()) { + std::stringstream ss; + ss << "Something went wrong opening file at path " << path; + throw std::logic_error(ss.str()); + } + std::string line; + char sep = ','; + bool firstLine = true; + while (std::getline(file, line)) { + if (firstLine) { + firstLine = false; + if (line.substr(0, 4) == "sep=") { + sep = line[4]; + continue; + } + } + std::string key; + std::string value; + std::stringstream linestream(line); + std::getline(linestream, key, sep); + std::getline(linestream, value, sep); + _map[key] = value; + } + file.close(); +} diff --git a/src/LocalizationFile.hpp b/src/LocalizationFile.hpp new file mode 100644 index 0000000..e8cd992 --- /dev/null +++ b/src/LocalizationFile.hpp @@ -0,0 +1,17 @@ +#ifndef LANGBUILDER_LOCALIZATIONFILE_HPP +#define LANGBUILDER_LOCALIZATIONFILE_HPP + +#include +#include +#include + +class LocalizationFile { + std::unordered_map _map; + +public: + void LoadFile(const std::filesystem::path& path); + + const std::unordered_map& GetMap() const { return _map; } +}; + +#endif // LANGBUILDER_LOCALIZATIONFILE_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cce57dc --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,38 @@ +#include +#include +#include "../extern/argparse.hpp" +#include "LocalizationData.hpp" + +int main(int argc, const char* argv[]) { + argparse::ArgumentParser parser("example", "Argument parser example"); + parser.add_argument("-p", "--path", "path", false).description("Base path of the data files for the language."); + parser.add_argument("-o", "--output", "output", false).description("Output file for language file."); + parser.enable_help(); + auto err = parser.parse(argc, argv); + if (err) { + std::cout << err << std::endl; + return -1; + } + if (parser.exists("help")) { + parser.print_help(); + return 0; + } + auto path = std::filesystem::current_path(); + if (parser.exists("path")) { + path = parser.get("path"); + } + std::cout << "Building localization file from path " << path << "." << std::endl; + LocalizationData data; + data.LoadFromPath(path); + + std::filesystem::path outFile; + if (parser.exists("output")) { + outFile = parser.get("output"); + } else { + outFile = path / data.GetCode(); + outFile.replace_extension("l10n"); + } + data.WriteToFile(outFile); + + return 0; +}