Selaa lähdekoodia

sokol_args.h: support key-only args (see documentation for details) fixes #876

Andre Weissflog 2 vuotta sitten
vanhempi
commit
737c263ff8
2 muutettua tiedostoa jossa 112 lisäystä ja 40 poistoa
  1. 64 40
      sokol_args.h
  2. 48 0
      tests/functional/sokol_args_test.c

+ 64 - 40
sokol_args.h

@@ -35,11 +35,22 @@
 
 
     When running as WebAssembly app, arguments are taken from the page URL:
     When running as WebAssembly app, arguments are taken from the page URL:
 
 
-    https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc
+        https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc
 
 
     The same arguments provided to a command line app:
     The same arguments provided to a command line app:
 
 
-    kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc
+        kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc
+
+    You can also use standalone keys without value:
+
+        https://floooh.github.io/tiny8bit/kc85.html?bla&blub
+
+    On the command line:
+
+        kc85 bla blub
+
+    Such value-less keys are reported as the value being an empty string, but they
+    can be tested with `sapp_exists("bla")` or `sapp_boolean("blub")`.
 
 
     ARGUMENT FORMATTING
     ARGUMENT FORMATTING
     ===================
     ===================
@@ -57,6 +68,12 @@
 
 
         key=value
         key=value
 
 
+    or
+
+        key
+
+    When a key has no value, the value will be assigned an empty string.
+
     Key/value pairs are separated by 'whitespace', valid whitespace
     Key/value pairs are separated by 'whitespace', valid whitespace
     characters are space and tab.
     characters are space and tab.
 
 
@@ -71,9 +88,6 @@
 
 
     The 'key' string must be a simple string without escape sequences or whitespace.
     The 'key' string must be a simple string without escape sequences or whitespace.
 
 
-    Currently 'single keys' without values are not allowed, but may be
-    in the future.
-
     The 'value' string can be quoted, and quoted value strings can contain
     The 'value' string can be quoted, and quoted value strings can contain
     whitespace:
     whitespace:
 
 
@@ -123,7 +137,7 @@
                 ...
                 ...
             }
             }
 
 
-            // check if a key's value is "true", "yes" or "on"
+            // check if a key's value is "true", "yes" or "on" or if this is a standalone key
             if (sargs_boolean("joystick_enabled")) {
             if (sargs_boolean("joystick_enabled")) {
                 ...
                 ...
             }
             }
@@ -183,23 +197,23 @@
         Return true between sargs_setup() and sargs_shutdown()
         Return true between sargs_setup() and sargs_shutdown()
 
 
     bool sargs_exists(const char* key)
     bool sargs_exists(const char* key)
-        Test if a key arg exists.
+        Test if an argument exists by its key name.
 
 
     const char* sargs_value(const char* key)
     const char* sargs_value(const char* key)
-        Return value associated with key. Returns an empty
-        string ("") if the key doesn't exist.
+        Return value associated with key. Returns an empty string ("") if the
+        key doesn't exist, or if the key doesn't have a value.
 
 
     const char* sargs_value_def(const char* key, const char* default)
     const char* sargs_value_def(const char* key, const char* default)
-        Return value associated with key, or the provided default
-        value if the value doesn't exist.
+        Return value associated with key, or the provided default value if the
+        key doesn't exist, or this is a value-less key.
 
 
     bool sargs_equals(const char* key, const char* val);
     bool sargs_equals(const char* key, const char* val);
         Return true if the value associated with key matches
         Return true if the value associated with key matches
         the 'val' argument.
         the 'val' argument.
 
 
     bool sargs_boolean(const char* key)
     bool sargs_boolean(const char* key)
-        Return true if the value string of 'key' is one
-        of 'true', 'yes', 'on'.
+        Return true if the value string of 'key' is one of 'true', 'yes', 'on',
+        or this is a key without value.
 
 
     int sargs_find(const char* key)
     int sargs_find(const char* key)
         Find argument by key name and return its index, or -1 if not found.
         Find argument by key name and return its index, or -1 if not found.
@@ -213,7 +227,7 @@
 
 
     const char* sargs_value_at(int index)
     const char* sargs_value_at(int index)
         Return the value of argument at index. Returns empty string
         Return the value of argument at index. Returns empty string
-        if index is outside range.
+        if the key at index has no value, or the index is out-of-range.
 
 
 
 
     MEMORY ALLOCATION OVERRIDE
     MEMORY ALLOCATION OVERRIDE
@@ -327,13 +341,13 @@ SOKOL_ARGS_API_DECL void sargs_shutdown(void);
 SOKOL_ARGS_API_DECL bool sargs_isvalid(void);
 SOKOL_ARGS_API_DECL bool sargs_isvalid(void);
 /* test if an argument exists by key name */
 /* test if an argument exists by key name */
 SOKOL_ARGS_API_DECL bool sargs_exists(const char* key);
 SOKOL_ARGS_API_DECL bool sargs_exists(const char* key);
-/* get value by key name, return empty string if key doesn't exist */
+/* get value by key name, return empty string if key doesn't exist or an existing key has no value */
 SOKOL_ARGS_API_DECL const char* sargs_value(const char* key);
 SOKOL_ARGS_API_DECL const char* sargs_value(const char* key);
-/* get value by key name, return provided default if key doesn't exist */
+/* get value by key name, return provided default if key doesn't exist or has no value */
 SOKOL_ARGS_API_DECL const char* sargs_value_def(const char* key, const char* def);
 SOKOL_ARGS_API_DECL const char* sargs_value_def(const char* key, const char* def);
 /* return true if val arg matches the value associated with key */
 /* return true if val arg matches the value associated with key */
 SOKOL_ARGS_API_DECL bool sargs_equals(const char* key, const char* val);
 SOKOL_ARGS_API_DECL bool sargs_equals(const char* key, const char* val);
-/* return true if key's value is "true", "yes" or "on" */
+/* return true if key's value is "true", "yes", "on" or an existing key has no value */
 SOKOL_ARGS_API_DECL bool sargs_boolean(const char* key);
 SOKOL_ARGS_API_DECL bool sargs_boolean(const char* key);
 /* get index of arg by key name, return -1 if not exists */
 /* get index of arg by key name, return -1 if not exists */
 SOKOL_ARGS_API_DECL int sargs_find(const char* key);
 SOKOL_ARGS_API_DECL int sargs_find(const char* key);
@@ -486,8 +500,8 @@ _SOKOL_PRIVATE bool _sargs_val_expected(void) {
     return 0 != (_sargs.parse_state & _SARGS_EXPECT_VAL);
     return 0 != (_sargs.parse_state & _SARGS_EXPECT_VAL);
 }
 }
 
 
-_SOKOL_PRIVATE void _sargs_expect_sep(void) {
-    _sargs.parse_state = _SARGS_EXPECT_SEP;
+_SOKOL_PRIVATE void _sargs_expect_sep_or_key(void) {
+    _sargs.parse_state = _SARGS_EXPECT_SEP | _SARGS_EXPECT_KEY;
 }
 }
 
 
 _SOKOL_PRIVATE bool _sargs_any_expected(void) {
 _SOKOL_PRIVATE bool _sargs_any_expected(void) {
@@ -524,14 +538,17 @@ _SOKOL_PRIVATE bool _sargs_is_whitespace(char c) {
 }
 }
 
 
 _SOKOL_PRIVATE void _sargs_start_key(void) {
 _SOKOL_PRIVATE void _sargs_start_key(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args));
     _sargs.parse_state = _SARGS_PARSING_KEY;
     _sargs.parse_state = _SARGS_PARSING_KEY;
     _sargs.args[_sargs.num_args].key = _sargs.buf_pos;
     _sargs.args[_sargs.num_args].key = _sargs.buf_pos;
 }
 }
 
 
 _SOKOL_PRIVATE void _sargs_end_key(void) {
 _SOKOL_PRIVATE void _sargs_end_key(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args));
     _sargs_putc(0);
     _sargs_putc(0);
+    // declare val as empty string in case this is a key-only arg
+    _sargs.args[_sargs.num_args].val = _sargs.buf_pos - 1;
+    _sargs.num_args++;
     _sargs.parse_state = 0;
     _sargs.parse_state = 0;
 }
 }
 
 
@@ -540,15 +557,13 @@ _SOKOL_PRIVATE bool _sargs_parsing_key(void) {
 }
 }
 
 
 _SOKOL_PRIVATE void _sargs_start_val(void) {
 _SOKOL_PRIVATE void _sargs_start_val(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args > 0) && (_sargs.num_args <= _sargs.max_args));
     _sargs.parse_state = _SARGS_PARSING_VAL;
     _sargs.parse_state = _SARGS_PARSING_VAL;
-    _sargs.args[_sargs.num_args].val = _sargs.buf_pos;
+    _sargs.args[_sargs.num_args - 1].val = _sargs.buf_pos;
 }
 }
 
 
 _SOKOL_PRIVATE void _sargs_end_val(void) {
 _SOKOL_PRIVATE void _sargs_end_val(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
     _sargs_putc(0);
     _sargs_putc(0);
-    _sargs.num_args++;
     _sargs.parse_state = 0;
     _sargs.parse_state = 0;
 }
 }
 
 
@@ -596,7 +611,12 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
         if (_sargs_any_expected()) {
         if (_sargs_any_expected()) {
             if (!_sargs_is_whitespace(c)) {
             if (!_sargs_is_whitespace(c)) {
                 /* start of key, value or separator */
                 /* start of key, value or separator */
-                if (_sargs_key_expected()) {
+                if (_sargs_is_separator(c)) {
+                    /* skip separator and expect value */
+                    _sargs_expect_val();
+                    continue;
+                }
+                else if (_sargs_key_expected()) {
                     /* start of new key */
                     /* start of new key */
                     _sargs_start_key();
                     _sargs_start_key();
                 }
                 }
@@ -608,13 +628,6 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
                     }
                     }
                     _sargs_start_val();
                     _sargs_start_val();
                 }
                 }
-                else {
-                    /* separator */
-                    if (_sargs_is_separator(c)) {
-                        _sargs_expect_val();
-                        continue;
-                    }
-                }
             }
             }
             else {
             else {
                 /* skip white space */
                 /* skip white space */
@@ -629,7 +642,7 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
                     _sargs_expect_val();
                     _sargs_expect_val();
                 }
                 }
                 else {
                 else {
-                    _sargs_expect_sep();
+                    _sargs_expect_sep_or_key();
                 }
                 }
                 continue;
                 continue;
             }
             }
@@ -657,7 +670,7 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
     }
     }
     if (_sargs_parsing_key()) {
     if (_sargs_parsing_key()) {
         _sargs_end_key();
         _sargs_end_key();
-        _sargs_expect_sep();
+        _sargs_expect_sep_or_key();
     }
     }
     else if (_sargs_parsing_val() && !_sargs_in_quotes()) {
     else if (_sargs_parsing_val() && !_sargs_in_quotes()) {
         _sargs_end_val();
         _sargs_end_val();
@@ -823,7 +836,13 @@ SOKOL_API_IMPL const char* sargs_value_def(const char* key, const char* def) {
     SOKOL_ASSERT(_sargs.valid && key && def);
     SOKOL_ASSERT(_sargs.valid && key && def);
     int arg_index = sargs_find(key);
     int arg_index = sargs_find(key);
     if (-1 != arg_index) {
     if (-1 != arg_index) {
-        return sargs_value_at(arg_index);
+        const char* res = sargs_value_at(arg_index);
+        SOKOL_ASSERT(res);
+        if (res[0] == 0) {
+            return def;
+        } else {
+            return res;
+        }
     }
     }
     else {
     else {
         return def;
         return def;
@@ -836,10 +855,15 @@ SOKOL_API_IMPL bool sargs_equals(const char* key, const char* val) {
 }
 }
 
 
 SOKOL_API_IMPL bool sargs_boolean(const char* key) {
 SOKOL_API_IMPL bool sargs_boolean(const char* key) {
-    const char* val = sargs_value(key);
-    return (0 == strcmp("true", val)) ||
-           (0 == strcmp("yes", val)) ||
-           (0 == strcmp("on", val));
+    if (sargs_exists(key)) {
+        const char* val = sargs_value(key);
+        return (0 == strcmp("true", val)) ||
+               (0 == strcmp("yes", val)) ||
+               (0 == strcmp("on", val)) ||
+               (0 == strcmp("", val));
+    } else {
+        return false;
+    }
 }
 }
 
 
 #endif /* SOKOL_ARGS_IMPL */
 #endif /* SOKOL_ARGS_IMPL */

+ 48 - 0
tests/functional/sokol_args_test.c

@@ -252,3 +252,51 @@ UTEST(sokol_args, escape_sequence) {
     TSTR(sargs_value_at(2), "val2\tval3");
     TSTR(sargs_value_at(2), "val2\tval3");
     sargs_shutdown();
     sargs_shutdown();
 }
 }
+
+static char* argv_11[] = { "exe_name", "kvp0 kvp1", "kvp2 = val2", "kvp3", "kvp4=val4" };
+UTEST(sokol_args, key_only_args) {
+    sargs_setup(&(sargs_desc){
+        .argc = NUM_ARGS(argv_11),
+        .argv = argv_11,
+    });
+    T(sargs_isvalid());
+    T(sargs_num_args() == 5);
+    T(0 == sargs_find("kvp0"));
+    T(1 == sargs_find("kvp1"));
+    T(2 == sargs_find("kvp2"));
+    T(3 == sargs_find("kvp3"));
+    T(4 == sargs_find("kvp4"))
+    T(-1 == sargs_find("kvp5"));
+    T(-1 == sargs_find("val2"));
+    T(-1 == sargs_find("val4"));
+    T(sargs_exists("kvp0"));
+    T(sargs_exists("kvp1"));
+    T(sargs_exists("kvp2"));
+    T(sargs_exists("kvp3"));
+    T(sargs_exists("kvp4"));
+    T(!sargs_exists("kvp5"));
+    TSTR(sargs_value("kvp0"), "");
+    TSTR(sargs_value("kvp1"), "");
+    TSTR(sargs_value("kvp2"), "val2");
+    TSTR(sargs_value("kvp3"), "");
+    TSTR(sargs_value("kvp4"), "val4");
+    TSTR(sargs_value("kvp5"), "");
+    TSTR(sargs_value_def("kvp0", "bla0"), "bla0");
+    TSTR(sargs_value_def("kvp1", "bla1"), "bla1");
+    TSTR(sargs_value_def("kvp2", "bla2"), "val2");
+    TSTR(sargs_value_def("kvp3", "bla3"), "bla3");
+    TSTR(sargs_value_def("kvp4", "bla4"), "val4");
+    TSTR(sargs_value_def("kvp5", "bla5"), "bla5");
+    TSTR(sargs_key_at(0), "kvp0");
+    TSTR(sargs_key_at(1), "kvp1");
+    TSTR(sargs_key_at(2), "kvp2");
+    TSTR(sargs_key_at(3), "kvp3");
+    TSTR(sargs_key_at(4), "kvp4");
+    TSTR(sargs_key_at(5), "");
+    TSTR(sargs_value_at(0), "");
+    TSTR(sargs_value_at(1), "");
+    TSTR(sargs_value_at(2), "val2");
+    TSTR(sargs_value_at(3), "");
+    TSTR(sargs_value_at(4), "val4");
+    TSTR(sargs_value_at(5), "");
+}