Sfoglia il codice sorgente

[update] add optparse(getopt-like) lib. | 添加optparse(类getopt)库.

liu2guang 7 anni fa
parent
commit
aaaaf3cf2e
7 ha cambiato i file con 702 aggiunte e 0 eliminazioni
  1. 28 0
      SConscript
  2. 24 0
      UNLICENSE
  3. 20 0
      example/SConscript
  4. 54 0
      example/optparse_demo.c
  5. 314 0
      optparse.c
  6. 77 0
      optparse.h
  7. 185 0
      readme.md

+ 28 - 0
SConscript

@@ -0,0 +1,28 @@
+import os
+from building import * 
+
+# get current dir path
+cwd = GetCurrentDir()
+
+# init src and inc vars
+src = []
+inc = []
+
+# add getparse common include
+inc = inc + [cwd]
+
+# add getparse basic code
+src = src + Glob('./*.c')
+
+# add group to IDE project
+group = DefineGroup('optparse', src, depend = ['PKG_USING_OPTPARSE'], CPPPATH = inc)
+
+# traversal subscript
+list = os.listdir(cwd)
+if GetDepend('PKG_USING_OPTPARSE'):
+    for d in list:
+        path = os.path.join(cwd, d)
+        if os.path.isfile(os.path.join(path, 'SConscript')):
+            group = group + SConscript(os.path.join(d, 'SConscript'))
+
+Return('group')

+ 24 - 0
UNLICENSE

@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>

+ 20 - 0
example/SConscript

@@ -0,0 +1,20 @@
+import os
+from building import * 
+
+# get current dir path
+cwd = GetCurrentDir()
+
+# init src and inc vars
+src = []
+inc = []
+
+# add optparse common include
+inc = inc + [cwd]
+
+# add optparse basic code
+src = src + Glob('./*.c')
+
+# add group to IDE project
+objs = DefineGroup('optparse', src, depend = ['OPTPARSE_USING_DEMO'], CPPPATH = inc)
+
+Return('objs')

+ 54 - 0
example/optparse_demo.c

@@ -0,0 +1,54 @@
+#include "optparse.h"
+#include "finsh.h"
+
+int optparse_short_test(int argc, char **argv)
+{
+    int ch; 
+    struct optparse options;
+    
+    optparse_init(&options, argv); 
+    while((ch = optparse(&options, "ab:c::")) != -1)
+    {
+        ch = ch; 
+        
+        rt_kprintf("\n"); 
+        rt_kprintf("optopt = %c\n", options.optopt);
+        rt_kprintf("optarg = %s\n", options.optarg);
+        rt_kprintf("optind = %d\n", options.optind);
+    }
+    rt_kprintf("\n");
+    
+    return RT_EOK; 
+}
+MSH_CMD_EXPORT_ALIAS(optparse_short_test, ost, test optparse_short cmd.); 
+
+static struct optparse_long long_opts[] = 
+{
+    {"aaa", 'a', OPTPARSE_NONE    }, 
+    {"bbb", 'b', OPTPARSE_REQUIRED}, 
+    {"ccc", 'c', OPTPARSE_OPTIONAL}, 
+    { NULL,  0,  OPTPARSE_NONE    }
+}; 
+
+int optparse_long_test(int argc, char **argv)
+{
+    int ch; 
+    int option_index; 
+    struct optparse options;
+    
+    optparse_init(&options, argv); 
+    while((ch = optparse_long(&options, long_opts, &option_index)) != -1)
+    {
+        ch = ch; 
+        
+        rt_kprintf("\n"); 
+        rt_kprintf("optopt = %c\n", options.optopt);
+        rt_kprintf("optarg = %s\n", options.optarg);
+        rt_kprintf("optind = %d\n", options.optind);
+        rt_kprintf("option_index = %d\n", option_index); 
+    }
+    rt_kprintf("\n");
+    
+    return RT_EOK; 
+}
+MSH_CMD_EXPORT_ALIAS(optparse_long_test, olt, test optparse_long cmd.); 

+ 314 - 0
optparse.c

@@ -0,0 +1,314 @@
+/*
+ * @File:   optparse.c
+ * @Author: liu2guang
+ * @Date:   2018-06-20 14:52:10
+ *
+ * @LICENSE: MIT
+ * https://github.com/liu2guang/optparse/blob/master/LICENSE
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-06-20     liu2guang    Adapter RT-Thread.
+ */
+
+#include "optparse.h"
+
+#define OPTPARSE_MSG_INVALID "invalid option"
+#define OPTPARSE_MSG_MISSING "option requires an argument"
+#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
+
+int optparse_error(struct optparse *options, const char *msg, const char *data)
+{
+    unsigned p = 0;
+    const char *sep = " -- '";
+    while (*msg)
+        options->errmsg[p++] = *msg++;
+    while (*sep)
+        options->errmsg[p++] = *sep++;
+    while (p < sizeof(options->errmsg) - 2 && *data)
+        options->errmsg[p++] = *data++;
+    options->errmsg[p++] = '\'';
+    options->errmsg[p++] = '\0';
+    return '?';
+}
+
+static int optparse_is_dashdash(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
+}
+
+static int optparse_is_shortopt(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
+}
+
+static int optparse_is_longopt(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
+}
+
+static void optparse_permute(struct optparse *options, int index)
+{
+    char *nonoption = options->argv[index];
+    int i;
+    for (i = index; i < options->optind - 1; i++)
+        options->argv[i] = options->argv[i + 1];
+    options->argv[options->optind - 1] = nonoption;
+}
+
+static int optparse_argtype(const char *optstring, char c)
+{
+    int count = OPTPARSE_NONE;
+    if (c == ':')
+        return -1;
+    for (; *optstring && c != *optstring; optstring++);
+    if (!*optstring)
+        return -1;
+    if (optstring[1] == ':')
+        count += optstring[2] == ':' ? 2 : 1;
+    return count;
+}
+
+static int optparse_longopts_end(const struct optparse_long *longopts, int i)
+{
+    return !longopts[i].longname && !longopts[i].shortname;
+}
+
+static void optparse_from_long(const struct optparse_long *longopts, char *optstring)
+{
+    char *p = optstring;
+    int i;
+    for (i = 0; !optparse_longopts_end(longopts, i); i++)
+    {
+        if (longopts[i].shortname)
+        {
+            int a;
+            *p++ = longopts[i].shortname;
+            for (a = 0; a < (int)longopts[i].argtype; a++)
+                *p++ = ':';
+        }
+    }
+    *p = '\0';
+}
+
+/* Unlike strcmp(), handles options containing "=". */
+static int optparse_longopts_match(const char *longname, const char *option)
+{
+    const char *a = option, *n = longname;
+    if (longname == 0)
+        return 0;
+    for (; *a && *n && *a != '='; a++, n++)
+        if (*a != *n)
+            return 0;
+    return *n == '\0' && (*a == '\0' || *a == '=');
+}
+
+/* Return the part after "=", or NULL. */
+static char *optparse_longopts_arg(char *option)
+{
+    for (; *option && *option != '='; option++);
+    if (*option == '=')
+        return option + 1;
+    else
+        return 0;
+}
+
+static int optparse_long_fallback(struct optparse *options,
+                                  const struct optparse_long *longopts,
+                                  int *longindex)
+{
+    int result;
+    char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
+    optparse_from_long(longopts, optstring);
+    result = optparse(options, optstring);
+    if (longindex != 0)
+    {
+        *longindex = -1;
+        if (result != -1)
+        {
+            int i;
+            for (i = 0; !optparse_longopts_end(longopts, i); i++)
+                if (longopts[i].shortname == options->optopt)
+                    *longindex = i;
+        }
+    }
+    return result;
+}
+
+void optparse_init(struct optparse *options, char **argv)
+{
+    options->argv = argv;
+    options->permute = 1;
+    options->optind = 1;
+    options->subopt = 0;
+    options->optarg = 0;
+    options->errmsg[0] = '\0';
+}
+
+int optparse(struct optparse *options, const char *optstring)
+{
+    int type;
+    char *next;
+    char *option = options->argv[options->optind];
+    options->errmsg[0] = '\0';
+    options->optopt = 0;
+    options->optarg = 0;
+    if (option == 0)
+    {
+        return -1;
+    }
+    else if (optparse_is_dashdash(option))
+    {
+        options->optind++; /* consume "--" */
+        return -1;
+    }
+    else if (!optparse_is_shortopt(option))
+    {
+        if (options->permute)
+        {
+            int index = options->optind++;
+            int r = optparse(options, optstring);
+            optparse_permute(options, index);
+            options->optind--;
+            return r;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+    option += options->subopt + 1;
+    options->optopt = option[0];
+    type = optparse_argtype(optstring, option[0]);
+    next = options->argv[options->optind + 1];
+    switch (type)
+    {
+    case -1:
+    {
+        char str[2] = {0, 0};
+        str[0] = option[0];
+        options->optind++;
+        return optparse_error(options, OPTPARSE_MSG_INVALID, str);
+    }
+    case OPTPARSE_NONE:
+        if (option[1])
+        {
+            options->subopt++;
+        }
+        else
+        {
+            options->subopt = 0;
+            options->optind++;
+        }
+        return option[0];
+    case OPTPARSE_REQUIRED:
+        options->subopt = 0;
+        options->optind++;
+        if (option[1])
+        {
+            options->optarg = option + 1;
+        }
+        else if (next != 0)
+        {
+            options->optarg = next;
+            options->optind++;
+        }
+        else
+        {
+            char str[2] = {0, 0};
+            str[0] = option[0];
+            options->optarg = 0;
+            return optparse_error(options, OPTPARSE_MSG_MISSING, str);
+        }
+        return option[0];
+    case OPTPARSE_OPTIONAL:
+        options->subopt = 0;
+        options->optind++;
+        if (option[1])
+            options->optarg = option + 1;
+        else
+            options->optarg = 0;
+        return option[0];
+    }
+    return 0;
+}
+
+int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex)
+{
+    int i;
+    char *option = options->argv[options->optind];
+    if (option == 0)
+    {
+        return -1;
+    }
+    else if (optparse_is_dashdash(option))
+    {
+        options->optind++; /* consume "--" */
+        return -1;
+    }
+    else if (optparse_is_shortopt(option))
+    {
+        return optparse_long_fallback(options, longopts, longindex);
+    }
+    else if (!optparse_is_longopt(option))
+    {
+        if (options->permute)
+        {
+            int index = options->optind++;
+            int r = optparse_long(options, longopts, longindex);
+            optparse_permute(options, index);
+            options->optind--;
+            return r;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+
+    /* Parse as long option. */
+    options->errmsg[0] = '\0';
+    options->optopt = 0;
+    options->optarg = 0;
+    option += 2; /* skip "--" */
+    options->optind++;
+    for (i = 0; !optparse_longopts_end(longopts, i); i++)
+    {
+        const char *name = longopts[i].longname;
+        if (optparse_longopts_match(name, option))
+        {
+            char *arg;
+            if (longindex)
+                *longindex = i;
+            options->optopt = longopts[i].shortname;
+            arg = optparse_longopts_arg(option);
+            if (longopts[i].argtype == OPTPARSE_NONE && arg != 0)
+            {
+                return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
+            }
+            if (arg != 0)
+            {
+                options->optarg = arg;
+            }
+            else if (longopts[i].argtype == OPTPARSE_REQUIRED)
+            {
+                options->optarg = options->argv[options->optind];
+                if (options->optarg == 0)
+                    return optparse_error(options, OPTPARSE_MSG_MISSING, name);
+                else
+                    options->optind++;
+            }
+            return options->optopt;
+        }
+    }
+    return optparse_error(options, OPTPARSE_MSG_INVALID, option);
+}
+
+char *optparse_arg(struct optparse *options)
+{
+    char *option = options->argv[options->optind];
+    options->subopt = 0;
+    if (option != 0)
+        options->optind++;
+    return option;
+}

+ 77 - 0
optparse.h

@@ -0,0 +1,77 @@
+/*
+ * @File:   optparse.h
+ * @Author: liu2guang
+ * @Date:   2018-06-20 14:52:10
+ *
+ * @LICENSE: MIT
+ * https://github.com/liu2guang/optparse/blob/master/LICENSE
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-06-20     liu2guang    Adapter RT-Thread.
+ */
+
+#ifndef __OPTPARSE_H_
+#define __OPTPARSE_H_
+
+struct optparse
+{
+    char **argv;
+    int permute;
+    int optind;
+    int optopt;
+    char *optarg;
+    char errmsg[64];
+    int subopt;
+};
+
+enum optparse_argtype
+{
+    OPTPARSE_NONE,
+    OPTPARSE_REQUIRED,
+    OPTPARSE_OPTIONAL
+};
+
+struct optparse_long
+{
+    const char *longname;
+    int shortname;
+    enum optparse_argtype argtype;
+};
+
+/**
+ * Initializes the parser state.
+ */
+void optparse_init(struct optparse *options, char **argv);
+
+/**
+ * Read the next option in the argv array.
+ * @param optstring a getopt()-formatted option string.
+ * @return the next option character, -1 for done, or '?' for error
+ *
+ * Just like getopt(), a character followed by no colons means no
+ * argument. One colon means the option has a required argument. Two
+ * colons means the option takes an optional argument.
+ */
+int optparse(struct optparse *options, const char *optstring);
+
+/**
+ * Handles GNU-style long options in addition to getopt() options.
+ * This works a lot like GNU's getopt_long(). The last option in
+ * longopts must be all zeros, marking the end of the array. The
+ * longindex argument may be NULL.
+ */
+int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex);
+
+/**
+ * Used for stepping over non-option arguments.
+ * @return the next non-option argument, or NULL for no more arguments
+ *
+ * Argument parsing can continue with optparse() after using this
+ * function. That would be used to parse the options for the
+ * subcommand returned by optparse_arg(). This function allows you to
+ * ignore the value of optind.
+ */
+char *optparse_arg(struct optparse *options);
+
+#endif /* __OPTPARSE_H_ */

+ 185 - 0
readme.md

@@ -0,0 +1,185 @@
+# optparse #
+
+optparse是一个开源、可移植的、可重入的和可嵌入的类getopt命令行参数解析器。它支持POSIX getopt选项字符串、GNU风格的长参数解析、参数置换和子命令处理.  
+
+## 1. 为什么不在RTT下直接使用getopt函数?
+
+POSIX getopt解析器有三个致命的缺陷。这些缺陷都是可以通过optparse来解决的: 
+
+1. getopt的解析状态存储在全局变量中,其中一些变量是静态不可访问的。这意味着在RTT(RT-Thread)下只能有一个msh命令可以使用getopt, 其他msh再次使用会出现bug, 而optparse将每次解析的状态存储在一个属于msh的私有结构体中, 每一个optparse命令解析器不会相互影响. 
+
+2. 在RTT中BSP一般支持Keil、IAR和GCC, 对于keil环境下是无法支持原生的POSIX getopt解析器, 这样就造成了在RTT下实现命令解析是无法移植到Keil中使用的, 代码的兼容性降低!
+
+3. 在getopt中,错误消息被打印到stderr。使用opterr可以禁用此功能,但消息本身仍然不可访问。optparse通过将错误消息写入它的errmsg字段来解决这个问题,该字段可以被打印到任何地方。optparse的缺点是,这个错误消息总是用英语而不是当前语言环境。
+
+## 2. 命令顺序
+
+默认情况下,argv在被解析时会将非选项的参数移动到数组的末尾。可以通过在初始化后将`permute`字段设置为0来禁用它。
+
+~~~c
+struct optparse options;
+optparse_init(&options, argv);
+options.permute = 0;
+~~~
+
+## 3. 对比
+
+optparse的接口应该是任何熟悉getopt的人都熟悉的。选项字符串具有相同的格式,解析器结构体成员变量与getopt全局变量(optarg、optind、optopt)具有相同的名称。
+
+long选项解析器`optparse_long()`API与GNU的`getopt_long()`非常相似,可以作为替代api。
+
+optparse没有分配内存。此外,optparse没有依赖项,包括libc本身,因此可以在标准C库无法使用的情况下使用。
+
+查看`optparse.h`文件可以了解完整的API。
+
+## 4. 示例使用
+
+这是一个通用的getopt命令解析代码: 
+
+~~~c
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+int main(int argc, char **argv)
+{
+    bool amend = false;
+    bool brief = false;
+    const char *color = "white";
+    int delay = 0;
+
+    int option;
+    while ((option = getopt(argc, argv, "abc:d::")) != -1) {
+        switch (option) {
+        case 'a':
+            amend = true;
+            break;
+        case 'b':
+            brief = true;
+            break;
+        case 'c':
+            color = optarg;
+            break;
+        case 'd':
+            delay = optarg ? atoi(optarg) : 1;
+            break;
+        case '?':
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    /* Print remaining arguments. */
+    for (; optind < argc; optind++)
+        printf("%s\n", argv[optind]);
+    return 0;
+}
+~~~
+
+如果使用optparse, 同样的功能将使用如下的代码的实现: 
+
+~~~c
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "optparse.h"
+
+int main(int argc, char **argv)
+{
+    bool amend = false;
+    bool brief = false;
+    const char *color = "white";
+    int delay = 0;
+
+    char *arg;
+    int option;
+    struct optparse options;
+
+    optparse_init(&options, argv);
+    while ((option = optparse(&options, "abc:d::")) != -1) {
+        switch (option) {
+        case 'a':
+            amend = true;
+            break;
+        case 'b':
+            brief = true;
+            break;
+        case 'c':
+            color = options.optarg;
+            break;
+        case 'd':
+            delay = options.optarg ? atoi(options.optarg) : 1;
+            break;
+        case '?':
+            fprintf(stderr, "%s: %s\n", argv[0], options.errmsg);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    /* Print remaining arguments. */
+    while ((arg = optparse_arg(&options)))
+        printf("%s\n", arg);
+    return 0;
+}
+~~~
+
+这是optparse对长选项参数的支持: 
+
+~~~c
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "optparse.h"
+
+int main(int argc, char **argv)
+{
+    struct optparse_long longopts[] = {
+        {"amend", 'a', OPTPARSE_NONE},
+        {"brief", 'b', OPTPARSE_NONE},
+        {"color", 'c', OPTPARSE_REQUIRED},
+        {"delay", 'd', OPTPARSE_OPTIONAL},
+        {0}
+    };
+
+    bool amend = false;
+    bool brief = false;
+    const char *color = "white";
+    int delay = 0;
+
+    char *arg;
+    int option;
+    struct optparse options;
+
+    optparse_init(&options, argv);
+    while ((option = optparse_long(&options, longopts, NULL)) != -1) {
+        switch (option) {
+        case 'a':
+            amend = true;
+            break;
+        case 'b':
+            brief = true;
+            break;
+        case 'c':
+            color = options.optarg;
+            break;
+        case 'd':
+            delay = options.optarg ? atoi(options.optarg) : 1;
+            break;
+        case '?':
+            fprintf(stderr, "%s: %s\n", argv[0], options.errmsg);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    /* Print remaining arguments. */
+    while ((arg = optparse_arg(&options)))
+        printf("%s\n", arg);
+
+    return 0;
+}
+~~~
+
+## 1. 感谢
+
+1. 该库移植于https://github.com/skeeto/optparse. 
+2. 感谢skeeto. 本移植是修改了部分原作者的代码针对RTT在线包实现的版本, 该仓库保留原作者的许可声明! 具体原作者许可请查看https://github.com/skeeto/optparse/blob/master/UNLICENSE.