11#include " core/Configuration.hpp"
2+ #include " core/CommandLineArguments.hpp"
23#include < iostream>
34#include < fstream>
45#include < filesystem>
4445
4546using namespace std ::string_view_literals;
4647
47- namespace core {
48- static std::u8string_view to_u8string_view (std::string_view const & sv) {
48+ namespace {
49+ std::u8string_view to_u8string_view (std::string_view const & sv) {
4950 static_assert (CHAR_BIT == 8 && sizeof (char ) == 1 && sizeof (char ) == sizeof (char8_t ));
50- return { reinterpret_cast <char8_t const *>(sv.data ()), sv.size () };
51+ return { reinterpret_cast <char8_t const *>(sv.data ()), sv.length () };
5152 }
5253
53- static std::string to_string (std::u8string const & s) {
54+ std::string to_string (std::u8string const & s) {
5455 static_assert (CHAR_BIT == 8 && sizeof (char ) == 1 && sizeof (char ) == sizeof (char8_t ));
5556 return { reinterpret_cast <char const *>(s.c_str ()), s.length () };
5657 }
5758
58- static bool is_uuid (std::string_view const & uuid) {
59+ bool is_uuid (std::string_view const & uuid) {
5960 #define is_uuid_char (c ) (((c) >= ' 0' && (c) <= ' 9' ) || ((c) >= ' A' && (c) <= ' Z' ) || ((c) >= ' a' && (c) <= ' z' ))
6061 #define is_not_uuid_char (c ) (!is_uuid_char(c))
6162 constexpr std::string_view format36{ " xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" sv };
@@ -89,7 +90,7 @@ namespace core {
8990 #undef is_not_uuid_char
9091 }
9192
92- static bool readTextFile (std::string_view const & path, std::string& str) {
93+ bool readTextFile (std::string_view const & path, std::string& str) {
9394 str.clear ();
9495 std::error_code ec;
9596 std::filesystem::path fs_path (to_u8string_view (path));
@@ -114,7 +115,7 @@ namespace core {
114115 return true ;
115116 }
116117
117- static std::string_view getTypeName (nlohmann::json const & json) {
118+ std::string_view getTypeName (nlohmann::json const & json) {
118119 if (json.is_object ()) {
119120 return " object" sv;
120121 }
@@ -262,6 +263,21 @@ namespace core {
262263 loader.logging .console .setPreserve (preserve.get <bool >());
263264 }
264265 }
266+ if (logging.contains (" standard_output" sv)) {
267+ auto const & standard_output = logging.at (" standard_output" sv);
268+ assert_type_is_object (standard_output, " /logging/standard_output" sv);
269+ if (standard_output.contains (" enable" sv)) {
270+ auto const & enable = standard_output.at (" enable" sv);
271+ assert_type_is_boolean (enable, " /logging/standard_output/enable" sv);
272+ loader.logging .standard_output .setEnable (enable.get <bool >());
273+ }
274+ if (standard_output.contains (" threshold" sv)) {
275+ auto const & threshold = standard_output.at (" threshold" sv);
276+ assert_type_is_string (threshold, " /logging/standard_output/threshold" sv);
277+ get_level (" /logging/standard_output/threshold" );
278+ loader.logging .standard_output .setThreshold (level);
279+ }
280+ }
265281 if (logging.contains (" file" sv)) {
266282 auto const & file = logging.at (" file" sv);
267283 assert_type_is_object (file, " /logging/file" sv);
@@ -630,3 +646,148 @@ namespace core {
630646 return instance;
631647 }
632648}
649+
650+ namespace {
651+ std::optional<bool > to_boolean (std::string_view const & s) {
652+ if (s == " true" sv) {
653+ return { true };
654+ }
655+ else if (s == " false" sv) {
656+ return { false };
657+ }
658+ else {
659+ return std::nullopt ;
660+ }
661+ }
662+
663+ template <typename T>
664+ std::optional<T> to_unsigned_integer (std::string_view const & s) {
665+ static_assert (std::is_unsigned_v<T>);
666+ T value{};
667+ auto const result = std::from_chars (s.data (), s.data () + s.size (), value);
668+ if (result.ec == std::errc{}) {
669+ return { value };
670+ }
671+ else {
672+ return std::nullopt ;
673+ }
674+ }
675+
676+ template <typename T>
677+ std::optional<T> to_number (const std::string_view& s) {
678+ static_assert (std::is_same_v<float , T> || std::is_same_v<double , T>);
679+ const auto begin = s.data ();
680+ const auto end = s.data () + s.size ();
681+ T value{};
682+ const auto result = std::from_chars (begin, end, value);
683+ if (result.ec != std::errc{} || result.ptr != end) {
684+ return std::nullopt ;
685+ }
686+ return value;
687+ }
688+
689+ enum class OptionType {
690+ boolean,
691+ number,
692+ string,
693+ };
694+
695+ using nlohmann::operator " " _json_pointer;
696+
697+ struct OptionMetadata {
698+ OptionType type;
699+ std::string_view prefix;
700+ nlohmann::json_pointer<std::string> path;
701+ };
702+
703+ const OptionMetadata meta[]{
704+ // debug
705+ { .type = OptionType::boolean, .prefix = " --debug.track_window_focus=" sv, .path = " /debug/track_window_focus" _json_pointer },
706+ // application
707+ { .type = OptionType::string , .prefix = " --application.uuid=" sv , .path = " /application/uuid" _json_pointer },
708+ { .type = OptionType::boolean, .prefix = " --application.single_instance=" sv, .path = " /application/single_instance" _json_pointer },
709+ // logging
710+ { .type = OptionType::boolean, .prefix = " --logging.debugger.enable=" sv , .path = " /logging/debugger/enable" _json_pointer },
711+ { .type = OptionType::string , .prefix = " --logging.debugger.threshold=" sv , .path = " /logging/debugger/threshold" _json_pointer },
712+ { .type = OptionType::boolean, .prefix = " --logging.console.enable=" sv , .path = " /logging/console/enable" _json_pointer },
713+ { .type = OptionType::string , .prefix = " --logging.console.threshold=" sv , .path = " /logging/console/threshold" _json_pointer },
714+ { .type = OptionType::boolean, .prefix = " --logging.console.preserve=" sv , .path = " /logging/console/preserve" _json_pointer },
715+ { .type = OptionType::boolean, .prefix = " --logging.standard_output.enable=" sv , .path = " /logging/standard_output/enable" _json_pointer },
716+ { .type = OptionType::string , .prefix = " --logging.standard_output.threshold=" sv, .path = " /logging/standard_output/threshold" _json_pointer },
717+ { .type = OptionType::boolean, .prefix = " --logging.file.enable=" sv , .path = " /logging/file/enable" _json_pointer },
718+ { .type = OptionType::string , .prefix = " --logging.file.threshold=" sv , .path = " /logging/file/threshold" _json_pointer },
719+ { .type = OptionType::string , .prefix = " --logging.file.path=" sv , .path = " /logging/file/path" _json_pointer },
720+ { .type = OptionType::boolean, .prefix = " --logging.rolling_file.enable=" sv , .path = " /logging/rolling_file/enable" _json_pointer },
721+ { .type = OptionType::string , .prefix = " --logging.rolling_file.threshold=" sv , .path = " /logging/rolling_file/threshold" _json_pointer },
722+ { .type = OptionType::string , .prefix = " --logging.rolling_file.path=" sv , .path = " /logging/rolling_file/path" _json_pointer },
723+ { .type = OptionType::number , .prefix = " --logging.rolling_file.max_history=" sv , .path = " /logging/rolling_file/max_history" _json_pointer },
724+ // timing
725+ { .type = OptionType::number , .prefix = " --timing.frame_rate=" sv, .path = " " _json_pointer },
726+ // window
727+ { .type = OptionType::string , .prefix = " --window.title=" sv , .path = " /window/title" _json_pointer },
728+ { .type = OptionType::boolean, .prefix = " --window.cursor_visible=" sv , .path = " /window/cursor_visible" _json_pointer },
729+ { .type = OptionType::boolean, .prefix = " --window.allow_window_corner=" sv , .path = " /window/allow_window_corner" _json_pointer },
730+ { .type = OptionType::boolean, .prefix = " --window.allow_title_bar_auto_hide=" sv, .path = " /window/allow_title_bar_auto_hide" _json_pointer },
731+ // graphics_system
732+ { .type = OptionType::string , .prefix = " --graphics_system.preferred_device_name=" sv , .path = " /graphics_system/preferred_device_name" _json_pointer },
733+ { .type = OptionType::number , .prefix = " --graphics_system.width=" sv , .path = " /graphics_system/width" _json_pointer },
734+ { .type = OptionType::number , .prefix = " --graphics_system.height=" sv , .path = " /graphics_system/height" _json_pointer },
735+ { .type = OptionType::boolean, .prefix = " --graphics_system.fullscreen=" sv , .path = " /graphics_system/fullscreen" _json_pointer },
736+ { .type = OptionType::boolean, .prefix = " --graphics_system.vsync=" sv , .path = " /graphics_system/vsync" _json_pointer },
737+ { .type = OptionType::boolean, .prefix = " --graphics_system.allow_software_device=" sv , .path = " /graphics_system/allow_software_device" _json_pointer },
738+ { .type = OptionType::boolean, .prefix = " --graphics_system.allow_exclusive_fullscreen=" sv, .path = " /graphics_system/allow_exclusive_fullscreen" _json_pointer },
739+ { .type = OptionType::boolean, .prefix = " --graphics_system.allow_modern_swap_chain=" sv , .path = " /graphics_system/allow_modern_swap_chain" _json_pointer },
740+ { .type = OptionType::boolean, .prefix = " --graphics_system.allow_direct_composition=" sv , .path = " /graphics_system/allow_direct_composition" _json_pointer },
741+ // audio_system
742+ { .type = OptionType::string , .prefix = " --audio_system.preferred_endpoint_name=" sv, .path = " /audio_system/preferred_endpoint_name" _json_pointer },
743+ { .type = OptionType::number , .prefix = " --audio_system.sound_effect_volume=" sv , .path = " /audio_system/sound_effect_volume" _json_pointer },
744+ { .type = OptionType::number , .prefix = " --audio_system.music_volume=" sv , .path = " /audio_system/music_volume" _json_pointer },
745+ };
746+ }
747+
748+ namespace core {
749+ bool ConfigurationLoader::loadFromCommandLineArguments () {
750+ const auto args{ CommandLineArguments::copy () };
751+ const auto write_arg_error = [this ](const std::string_view& raw_arg) -> void {
752+ messages.emplace_back (std::format (" invalid command line argument '{}'" , raw_arg));
753+ };
754+ int error_counter{};
755+ nlohmann::json json;
756+ for (size_t i = 0 ; i < args.size (); i += 1 ) {
757+ const std::string_view arg (args[i]);
758+ for (const auto & t : meta) {
759+ if (arg.starts_with (t.prefix )) {
760+ const std::string_view value{ arg.substr (t.prefix .length ()) };
761+ switch (t.type ) {
762+ case OptionType::boolean:
763+ if (const auto result = to_boolean (value); result) {
764+ json[t.path ] = result.value ();
765+ }
766+ else {
767+ write_arg_error (arg);
768+ error_counter += 1 ;
769+ }
770+ break ; // switch (t.type)
771+ case OptionType::number:
772+ if (const auto result = to_number<double >(value); result) {
773+ json[t.path ] = result.value ();
774+ }
775+ else {
776+ write_arg_error (arg);
777+ error_counter += 1 ;
778+ }
779+ break ; // switch (t.type)
780+ case OptionType::string:
781+ json[t.path ] = value;
782+ break ; // switch (t.type)
783+ }
784+ break ; // for (const auto& t : meta)
785+ }
786+ }
787+ }
788+ if (error_counter > 0 ) {
789+ return false ;
790+ }
791+ return ConfigurationLoaderContext::merge (*this , nullptr , json);
792+ }
793+ }
0 commit comments