41#include <system_error>
43#include <unordered_map>
65concept StringType = std::is_class_v<T> &&
requires(T type) {
70 typename T::value_type;
71 typename T::size_type;
72 { type.length() } -> std::same_as<typename T::size_type>;
73 { type[0] } -> std::same_as<typename T::value_type &>;
74 { type += T{} } -> std::same_as<T &>;
75 { type = T{} } -> std::same_as<T &>;
80 std::vector<char> buffer(32768);
81 const DWORD length = GetModuleFileNameA(
nullptr, buffer.data(),
static_cast<DWORD
>(buffer.size()));
82 if (length > 0 && length < buffer.size()) {
83 return std::filesystem::path(std::string(buffer.data(), length)).parent_path().lexically_normal().string();
85#elif defined(__linux__)
86 std::vector<char> buffer(4096);
87 const ssize_t length = readlink(
"/proc/self/exe", buffer.data(), buffer.size());
88 if (length > 0 &&
static_cast<std::size_t
>(length) < buffer.size()) {
89 return std::filesystem::path(std::string(buffer.data(),
static_cast<std::size_t
>(length))).parent_path().lexically_normal().string();
93 std::error_code error;
94 const std::filesystem::path absolute_path = std::filesystem::absolute(executable ==
nullptr ?
"." : executable, error);
95 if (!error && absolute_path.has_parent_path()) {
96 return absolute_path.parent_path().lexically_normal().string();
119template <StringType String>
146template <StringType String>
166 args = std::move(a.args);
177template <StringType String>
182 String
text()
const {
return value; }
196template <StringType String>
244 arg_data.args.reserve(
static_cast<size_t>(argc - 1));
246 if constexpr (std::is_same<typename String::value_type, char>::value) {
247 for (
int i = 1; i < argc; ++i) {
248 const char *a = (argv !=
nullptr) ? argv[i] :
nullptr;
249 arg_data.args.emplace_back(a !=
nullptr ? a :
"");
254 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
255 for (
int i = 1; i < argc; ++i) {
256 const char *a = (argv !=
nullptr) ? argv[i] :
nullptr;
259 for (
size_t z = 0; a[z] != 0; ++z) {
260 data +=
static_cast<typename String::value_type
>(a[z]);
288 a.
desc = description;
303 a.
desc = description;
319 a.
desc = description;
336 a.
desc = description;
349 if (i.second.arg_name == value) {
350 return i.second.arg_letter;
363 if (index <
static_cast<int>(
arg_data.args.size())) {
364 const String &type{
arg_data.args[index]};
365 if (type.length() > 2 && type[0] ==
'-' && type[1] ==
'-') {
367 String inline_value{};
368 const auto eq_pos = type.find(
static_cast<typename String::value_type
>(
'='));
369 const bool has_inline_value = (eq_pos != String::npos);
370 for (
size_t z = 2; z < type.length(); ++z)
371 if (!has_inline_value || z < eq_pos) {
373 }
else if (z > eq_pos) {
374 inline_value += type[z];
381 if (has_inline_value) {
382 if constexpr (std::is_same<typename String::value_type, char>::value) {
385 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
396 if (has_inline_value) {
397 if (inline_value.empty()) {
398 if constexpr (std::is_same<typename String::value_type, char>::value) {
401 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
409 if (++index <
static_cast<int>(
arg_data.args.size())) {
410 const String &s{
arg_data.args[index]};
411 if (canUseAsOptionValue(s)) {
418 if constexpr (std::is_same<typename String::value_type, char>::value) {
421 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
427 throwUnknownOption(type);
429 }
else if (type.length() == 1 && type[0] ==
'-') {
430 if constexpr (std::is_same<typename String::value_type, char>::value) {
433 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
436 }
else if (type.length() > 1 && (type[0] ==
'-')) {
437 const int c{type[cindex]};
440 if (cindex >=
static_cast<int>(type.length())) {
445 name_val +=
static_cast<typename String::value_type
>(c);
452 if (index <
static_cast<int>(
arg_data.args.size())) {
453 const String &s{
arg_data.args[index]};
454 if (!canUseAsOptionValue(s)) {
455 if constexpr (std::is_same<typename String::value_type, char>::value) {
458 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
462 if (s.length() > 0) {
470 if constexpr (std::is_same<typename String::value_type, char>::value) {
473 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
478 if constexpr (std::is_same<typename String::value_type, char>::value) {
481 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
486 throwUnknownOption(type);
505 template <
typename T>
507 using char_type =
typename std::decay<decltype(*std::declval<T>().rdbuf())>::type::char_type;
508 const bool use_color = supportsColor(cout);
509 auto write_ansi = [&](
const char *seq) {
513 if constexpr (std::is_same_v<char_type, char>) {
515 }
else if constexpr (std::is_same_v<char_type, wchar_t>) {
516 for (
const char *p = seq; *p !=
'\0'; ++p) {
517 cout << static_cast<char_type>(*p);
521 auto write_padding = [&](
size_t count) {
522 for (
size_t i = 0; i <
count; ++i) {
523 cout << static_cast<char_type>(
' ');
531 std::vector<Argument<String>> v;
532 std::vector<Argument<String>> v2;
535 v.push_back(i.second);
537 v2.push_back(i.second);
539 std::ranges::sort(v);
540 std::ranges::sort(v2);
541 std::vector<HelpRow> rows;
542 rows.reserve(v.size() + v2.size());
543 for (
const auto &a : v) {
545 token +=
static_cast<char_type
>(
'-');
546 token +=
static_cast<char_type
>(a.arg_letter);
547 rows.push_back(HelpRow{std::move(token), a.desc,
true});
549 for (
const auto &a : v2) {
551 token +=
static_cast<char_type
>(
'-');
552 token +=
static_cast<char_type
>(
'-');
554 rows.push_back(HelpRow{std::move(token), a.desc,
false});
556 size_t token_width = 0;
557 for (
const auto &row : rows) {
558 token_width = std::max(token_width,
static_cast<size_t>(row.token.length()));
560 const size_t desc_column = token_width + 2;
561 for (
const auto &row : rows) {
562 write_ansi(row.is_short ?
"\x1b[1;36m" :
"\x1b[1;35m");
564 write_ansi(
"\x1b[0m");
565 if (desc_column > row.token.length()) {
566 write_padding(desc_column - row.token.length());
570 write_ansi(
"\x1b[90m");
572 write_ansi(
"\x1b[0m");
577 const size_t count()
const {
return index; }
581 std::unordered_map<int, Argument<String>>
arg_info;
584 template <
typename Stream>
585 static bool supportsColor(
const Stream &stream) {
586 if constexpr (std::is_same_v<Stream, std::ostream>) {
587 if (&stream != &std::cout) {
591 return _isatty(_fileno(stdout));
593 return isatty(fileno(stdout));
595 }
else if constexpr (std::is_same_v<Stream, std::wostream>) {
596 if (&stream != &std::wcout) {
600 return _isatty(_fileno(stdout));
602 return isatty(fileno(stdout));
609 [[noreturn]]
void throwUnknownOption(
const String &token)
const {
610 if constexpr (std::is_same<typename String::value_type, char>::value) {
611 String value =
"Error argument: ";
613 value +=
" switch not found";
616 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
617 String value = L
"Error argument: ";
619 value += L
" switch not found";
620 throw ArgException<String>(value);
624 bool isSignedNumericToken(
const String &s)
const {
629 const auto ch_minus =
static_cast<typename String::value_type
>(
'-');
630 const auto ch_plus =
static_cast<typename String::value_type
>(
'+');
631 const auto ch_dot =
static_cast<typename String::value_type
>(
'.');
632 const auto ch_e =
static_cast<typename String::value_type
>(
'e');
633 const auto ch_E =
static_cast<typename String::value_type
>(
'E');
634 const auto ch_0 =
static_cast<typename String::value_type
>(
'0');
635 const auto ch_9 =
static_cast<typename String::value_type
>(
'9');
638 if (s[i] == ch_minus || s[i] == ch_plus) {
641 if (i >= s.length()) {
645 bool has_digit =
false;
646 bool has_dot =
false;
647 for (; i < s.length(); ++i) {
648 const auto ch = s[i];
649 if (ch >= ch_0 && ch <= ch_9) {
653 if (ch == ch_dot && !has_dot) {
657 if ((ch == ch_e || ch == ch_E) && has_digit) {
659 if (j < s.length() && (s[j] == ch_minus || s[j] == ch_plus)) {
662 if (j >= s.length()) {
665 bool exp_digit =
false;
666 for (; j < s.length(); ++j) {
667 if (s[j] >= ch_0 && s[j] <= ch_9) {
680 bool isRecognizedOptionToken(
const String &s)
const {
681 if (s.length() <= 1 || s[0] !=
static_cast<typename String::value_type
>(
'-')) {
685 if (s.length() > 2 && s[1] ==
static_cast<typename String::value_type
>(
'-')) {
687 for (
size_t z = 2; z < s.length(); ++z) {
693 for (
size_t z = 1; z < s.length(); ++z) {
701 bool canUseAsOptionValue(
const String &s)
const {
705 if (s[0] !=
static_cast<typename String::value_type
>(
'-')) {
708 if (isSignedNumericToken(s)) {
711 return !isRecognizedOptionToken(s);
714 int index = 0, cindex = 1;
774[[nodiscard]]
inline int parse_arg_int(
const std::string &text,
const std::string &option_name) {
776 const char *begin = text.data();
777 const char *end = begin + text.size();
778 const std::from_chars_result result = std::from_chars(begin, end, value);
779 if (result.ec != std::errc{} || result.ptr != end) {
785[[nodiscard]]
inline double parse_arg_double(
const std::string &text,
const std::string &option_name) {
787 const char *begin = text.data();
788 const char *end = begin + text.size();
789 const std::from_chars_result result = std::from_chars(begin, end, value);
790 if (result.ec != std::errc{} || result.ptr != end || !std::isfinite(value)) {
797 if (argv0 ==
nullptr || argv0[0] ==
'\0') {
801 std::string name = argv0;
802 const std::string::size_type separator = name.find_last_of(
"/\\");
803 if (separator != std::string::npos) {
804 name.erase(0, separator + 1);
807 constexpr std::string_view exe_extension =
".exe";
808 if (name.size() >= exe_extension.size()) {
809 const std::string::size_type extension_pos = name.size() - exe_extension.size();
810 const bool has_exe_extension = std::ranges::equal(name.begin() +
static_cast<std::ptrdiff_t
>(extension_pos),
812 exe_extension.begin(),
814 [](
char left,
char right) {
815 return std::tolower(static_cast<unsigned char>(left)) == std::tolower(static_cast<unsigned char>(right));
817 if (has_exe_extension) {
818 name.erase(extension_pos);
822 return name.empty() ?
"mxvk" : name;
877 .addOptionSingle(
'v',
"Print version")
878 .addOptionSingleValue(
'p',
"assets path")
879 .addOptionDoubleValue(
'P',
"path",
"assets path")
880 .addOptionSingleValue(
'r',
"Resolution WidthxHeight")
881 .addOptionDoubleValue(
'R',
"resolution",
"Resolution WidthxHeight")
882 .addOptionSingle(
'f',
"fullscreen")
883 .addOptionDouble(
'F',
"fullscreen",
"fullscreen")
884 .addOptionDouble(301,
"fast",
"fast")
885 .addOptionDoubleValue(256,
"filename",
"input filename")
886 .addOptionDoubleValue(324,
"model",
"model filename (.obj, .mxmod, or .mxmod.z)")
887 .addOptionSingleValue(
'o',
"output filename")
888 .addOptionDoubleValue(304,
"output",
"output filename")
889 .addOptionSingleValue(
'c',
"crf value")
890 .addOptionDoubleValue(305,
"crf",
"crf value")
891 .addOptionDoubleValue(306,
"encode-preset",
"encoder preset")
892 .addOptionDoubleValue(307,
"encode-tune",
"encoder tune")
893 .addOptionDoubleValue(308,
"encode-codec",
"encoder codec policy or name")
894 .addOptionDouble(309,
"encode-realtime",
"encoder realtime mode")
895 .addOptionDouble(314,
"mxwrite-block",
"block MXWrite when its queue is full")
896 .addOptionDouble(310,
"repeat",
"repeat playback or wrapped textures")
897 .addOptionDouble(315,
"binary",
"use binary glyphs only")
898 .addOptionDouble(319,
"enable-crt",
"enable CRT post-processing at startup")
899 .addOptionDouble(320,
"enable-vsync",
"enable FIFO present mode / v-sync")
900 .addOptionDouble(322,
"enable-screenshot",
"enable F10 screenshot capture")
901 .addOptionDouble(325,
"disable-sound",
"disable background music")
902 .addOptionDouble(330,
"benchmark",
"enable application benchmark mode")
903 .addOptionDouble(331,
"wireframe",
"render supported 3D models as wireframes")
904 .addOptionDouble(326,
"nowarpfix",
"3dmath - disable perspective-correct texture mapping")
905 .addOptionDoubleValue(327,
"framebuffer",
"3dmath - software framebuffer size WidthxHeight")
906 .addOptionDouble(328,
"disable-mipmap",
"3dmath - disable mipmap generation and selection")
907 .addOptionDoubleValue(329,
"mip-bias",
"3dmath - mipmap level-of-detail bias (-16 to 16)")
908 .addOptionDoubleValue(321,
"fps",
"capture FPS override")
909 .addOptionSingleValue(
'z',
"matrix rain font size")
910 .addOptionDoubleValue(316,
"font-size",
"matrix rain font size")
911 .addOptionSingleValue(
'j',
"matrix rain font file path")
912 .addOptionDoubleValue(317,
"font-path",
"matrix rain font file path")
913 .addOptionSingleValue(
'C',
"matrix rain RGB tint (#RRGGBB or R,G,B)")
914 .addOptionDoubleValue(318,
"color",
"matrix rain RGB tint (#RRGGBB or R,G,B)")
915 .addOptionDoubleValue(302,
"resource",
"resource file")
916 .addOptionDoubleValue(303,
"resource_path",
"resource data path")
917 .addOptionDoubleValue(257,
"texture",
"texture file (.png or .tex)")
918 .addOptionDoubleValue(323,
"textures",
"texture file (.png or .tex)")
919 .addOptionSingleValue(
'S',
"shader SPV folder path (contains index.txt)")
920 .addOptionDoubleValue(258,
"shader-path",
"shader SPV folder path (contains index.txt)")
921 .addOptionDoubleValue(313,
"fragment",
"fragment shader SPV path")
922 .addOptionSingleValue(
'i',
"acidcam filter mode index")
923 .addOptionDoubleValue(311,
"index",
"acidcam filter mode index")
924 .addOptionDoubleValue(312,
"shader-index",
"initial shader entry index")
925 .addOptionDoubleValue(300,
"camera",
"camera index");
930 int tw = 1280, th = 720;
931 bool fullscreen =
false;
933 bool resolutionSpecified =
false;
934 std::string filename;
938 std::string encodePreset;
939 std::string encodeTune;
940 std::string encodeCodec;
941 bool encodeRealtime =
false;
942 bool mxwriteBlockWhenFull =
false;
945 bool enable_crt =
false;
946 bool enable_vsync =
false;
947 bool enable_screenshot =
false;
948 bool disable_sound =
false;
949 bool benchmark =
false;
950 bool wireframe =
false;
951 bool nowarpfix =
false;
952 bool disable_mipmap =
false;
953 float mip_bias = 0.0f;
955 bool framebufferSpecified =
false;
958 std::string font_path;
961 std::string shaderPath;
962 std::string fragmentPath;
963 int camera_index = 0;
965 int shader_index = 0;
966 std::string resource;
967 std::string resource_path;
968 while ((value = parser.
proc(arg)) != -1) {
1000 encodeRealtime =
true;
1003 mxwriteBlockWhenFull =
true;
1015 enable_vsync =
true;
1018 enable_screenshot =
true;
1021 disable_sound =
true;
1033 const std::string::size_type separator = arg.
arg_value.find(
'x');
1034 if (separator == std::string::npos) {
1039 if (framebuffer.
width <= 0 || framebuffer.
height <= 0) {
1042 framebufferSpecified =
true;
1045 disable_mipmap =
true;
1049 if (parsed_mip_bias < -16.0 || parsed_mip_bias > 16.0) {
1052 mip_bias =
static_cast<float>(parsed_mip_bias);
1063 if (font_size <= 0) {
1088 parser.
help(std::cout);
1098 if (pos == std::string::npos) {
1101 std::string left, right;
1106 if (tw <= 0 || th <= 0) {
1109 resolutionSpecified =
true;
1132 std::cerr <<
"mx: No path provided; using executable directory: " << path <<
'\n';
1134 std::error_code error;
1135 const std::filesystem::path absolute_path = std::filesystem::absolute(path, error);
1137 path = absolute_path.lexically_normal().string();
std::string executable_directory(const char *executable)
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
double parse_arg_double(const std::string &text, const std::string &option_name)
ArgType
Discriminator for option kind used inside Argument.
int parse_arg_int(const std::string &text, const std::string &option_name)
std::string parse_executable_name(const char *argv0)
Exception thrown by Argz::proc() on unrecognised or malformed options.
ArgException(const String &s)
Template command-line argument parser.
void help(T &cout)
Print all registered options to any ostream-like object.
std::unordered_map< int, Argument< String > > arg_info
Argz< String > & addOptionSingleValue(const int &c, const String &description)
Register a short option that requires a value argument (e.g.
ArgumentData< String > arg_data
int lookUpCode(const String &value) const
Look up the integer code registered for a long option name.
Argz< String > & addOptionDouble(const int &code, const String &value, const String &description)
Register a flag-only long option (e.g.
const size_t count() const
Argz< String > & initArgs(int argc, char **argv)
Ingest argc/argv, converting to the target String type.
Argz< String > & addOptionDoubleValue(const int &code, const String &value, const String &description)
Register a long option that requires a value argument (e.g.
int proc(Argument< String > &a)
Advance the cursor and parse the next argument.
Argz< String > & operator=(Argz< String > &&a)
Argz< String > & addOptionSingle(const int &c, const String &description)
Register a flag-only short option (e.g.
Argz< String > & operator=(const Argz< String > &a)
Argz(const Argz< String > &a)
void reset()
Reset the internal parse cursor to the beginning of argv.
Argz(int argc, char **argv)
Construct and immediately ingest argv.
Models a string-like class with indexing, concatenation, length, and value_type.
void setDefaultEnableScreenshot(bool enabled)
void setDefaultExecutableName(const std::string &name)
Holds the raw argv strings converted to the target String type.
ArgumentData(ArgumentData< String > &&a)
ArgumentData< String > & operator=(ArgumentData< String > &&a)
ArgumentData(const ArgumentData< String > &a)
ArgumentData & operator=(const ArgumentData< String > &a)
std::vector< String > args
Converted argv entries (argv[1] … argv[argc-1]).
int argc
Original argc value.
Describes a single parsed argument entry returned by Argz::proc().
int arg_letter
Short option code (e.g. 'o') or unique integer for long-only options.
Argument & operator=(const Argument< String > &a)
Argument(const Argument &a)
ArgType arg_type
Which ArgType variant this argument represents.
String arg_name
Long option name (e.g. "output").
auto operator<=>(const Argument< String > &a) const
String arg_value
Value string supplied after the option, if any.
bool operator==(const Argument< String > &a) const
String desc
Human-readable description used in help output.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
bool framebufferSpecified
Whether --framebuffer was provided.
std::string output
Optional output filename (--output).
std::string font_path
Optional font file path (--font-path).
bool mxwriteBlockWhenFull
Make MXWrite block instead of dropping frames (--mxwrite-block).
bool fast
Whether fast mode was requested (--fast).
int camera_index
Optional camera index.
bool enable_screenshot
Enable F10 screenshot capture (--enable-screenshot).
std::string encodeTune
Optional encoder tune (--encode-tune).
bool fullscreen
Whether fullscreen mode was requested.
int index
Optional acidcam filter mode index.
bool encodeRealtime
Enable low-latency encoder settings (--encode-realtime).
bool resolutionSpecified
Whether -r/–resolution was provided.
float mip_bias
Mipmap level-of-detail bias requested by --mip-bias.
std::string model
Optional model filename (--model).
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
std::string crf
Optional CRF value (--crf).
bool nowarpfix
Disable perspective-correct texture mapping (--nowarpfix).
std::string texture
Optional texture file path (--texture).
int height
Viewport height in pixels (default: 720).
double fps
Optional FPS override (--fps); non-positive means unspecified.
std::string encodeCodec
Optional encoder codec policy/name (--encode-codec).
std::string color
Optional rain RGB tint (--color).
std::string resource_path
Resource path.
bool repeat
Enable repeat behavior such as playback looping or wrapped textures.
std::string filename
Optional input filename (--filename).
std::string encodePreset
Optional encoder preset (--encode-preset).
std::string path
Asset root; proc_args() defaults it to the executable directory.
std::string fragmentPath
Optional fragment shader SPV path (--fragment).
std::string executable_name
Executable basename derived from argv[0].
bool benchmark
Enable application benchmark mode (--benchmark).
bool wireframe
Render supported 3D models as wireframes (--wireframe).
int width
Viewport width in pixels (default: 1280).
std::string resource
Resource file.
bool enable_crt
Enable CRT post-processing at startup (--enable-crt).
int shader_index
Optional initial shader entry index.
bool disable_mipmap
Disable mipmap generation and selection (--disable-mipmap).
int font_size
Matrix rain font size in pixels (--font-size).
bool binary
Use binary glyphs only (--binary).
bool disable_sound
Disable application background music (--disable-sound).
Parsed software framebuffer dimensions.
int width
Software framebuffer width in pixels.
int height
Software framebuffer height in pixels.