8 Commits

Author SHA1 Message Date
b4663a01e8 Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2025-07-16 14:10:30 +08:00
b1eaae793e docs: 0.3.0 2025-07-16 14:06:11 +08:00
ce4d0fc423 docs: update 2025-07-16 14:02:02 +08:00
f6ee49f85e feat: 添加程序值 2025-07-16 13:59:23 +08:00
924f9662b5 Merge pull request 'main' (#1) from main into dev
Reviewed-on: #1
2025-07-16 09:57:02 +08:00
5df2e31973 docs: 添加MIT许可证文件 2025-07-16 09:56:00 +08:00
c71c9b6d15 docs: 更新文档 2025-07-15 11:14:31 +08:00
3ade1fd1aa docs: 更新特性介绍 2025-07-15 10:42:23 +08:00
11 changed files with 562 additions and 62 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 youmetme
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 OR COPYRIGHT HOLDERS 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.

181
README.md
View File

@@ -5,8 +5,13 @@ C语言易用的命令参数分析
# 特性
- 自动生成帮助信息
- 命令自动帮助信息
- 默认值支持
- 子命令支持
- 命令参数支持
- 全局参数
- 互斥参数(即将)
# 安装
@@ -78,3 +83,179 @@ int main(int argc, char *argv[]) {
example -h
```
![帮助信息](docs/images/help.png)
# API
该API将分为两类一类是用于构造解析器另一类将用于解析参数
## 构造解析器
1. 初始化解析器
```c
ArgParse *argParseInit(char *documentationArgParseValueType value_type);
```
- `documentation` 解析器的文档信息
- `valueType` 程序本身需要的值类型
2. 添加命令
```c
Command *argParseAddCommand(ArgParse *argParse,
char *name,
char *help,
char *default_val,
ArgParseCallback callback,
CommandGroup *group,
ArgParseValueType value_type);
```
- `argParse` 解析器
- `name` 命令名称
- `help` 命令帮助信息
- `defaultVal` 默认值
- `callback` 命令回调函数(可选)
- `group` 命令组(可选)
- `valueType` 命令参数类型
3. 添加命令参数
```c
CommandArgs *argParseAddArg(Command *command,
char *short_opt,
char *long_opt,
char *help,
char *default_val,
ArgParseCallback callback,
bool required,
ArgParseValueType value_type);
```
- `command` 命令
- `shortOpt` 短选项
- `longOpt` 长选项
- `help` 帮助信息
- `defaultVal` 默认值
- `callback` 回调函数(可选)
- `required` 是否必须
- `valueType` 参数类型
4. 添加子命令
```c
Command *argParseAddSubCommand(Command *Parent,
char *name,
char *help,
char *default_val,
ArgParseCallback callback,
CommandGroup *group,
ArgParseValueType value_type);
```
- `Parent` 父命令
- `name` 命令名称
- `help` 命令帮助信息
- `defaultVal` 默认值
- `callback` 回调函数(可选)
- `group` 命令组(可选)
- `valueType` 命令参数类型
5. 添加全局参数
```c
CommandArgs *argParseAddGlobalArg(ArgParse *argParse,
char *short_opt,
char *long_opt,
char *help,
char *default_val,
ArgParseCallback callback,
bool required,
ArgParseValueType value_type);
```
- `argParse` 解析器
- `shortOpt` 短选项
- `longOpt` 长选项
- `help` 帮助信息
- `defaultVal` 默认值
- `callback` 回调函数(可选)
- `required` 是否必须
- `valueType` 参数类型
## 解析参数API
1. 解析参数
```c
void argParseParse(ArgParse *argParse, int argc, char *argv[]);
```
- `argParse` 解析器
- `argc` 参数个数
- `argv` 参数列表
2. 获取当前解析到的命令名字
```c
char *argParseGetCurCommandName(ArgParse *argParse);
```
- `argParse` 解析器
> 注意,解析到的命令为最后一次出现的命令,也可能是子命令
3. 获取当前命令的值
```c
char *argParseGetCurCommandValue(ArgParse *argParse);
```
- `argParse` 解析器
> 注意,解析到的命令为最后一次出现的命令,也可能是子命令
4. 获取当前命令的某个参数的值
```c
char *argParseGetCurArg(ArgParse *argParse, char *opt);
```
- `argParse` 解析器
- `opt` 参数选项
5. 获取当前命令的某个选项的值列表(当为多值类型时使用该函数)
```c
char **argParseGetCurArgList(ArgParse *argParse, char *opt, int *len);
```
- `argParse` 解析器
- `opt` 参数选项
- `len` 参数个数
6. 获取全局选项的值
```c
char *argParseGetGlobalArg(ArgParse *argParse, char *opt);
```
- `argParse` 解析器
- `opt` 参数选项
7. 获取全局选项的值列表
```c
char **argParseGetGlobalArgList(ArgParse *argParse, char *opt, int *len);
```
- `argParse` 解析器
- `opt` 参数选项
- `len` 参数个数
8. 获取程序值
该值为非命令的值,类似`gcc main.c`,该类程序没有使用命令,因此该值就是`main.c`
```c
char *argParseGetVal(ArgParse *argParse);
char **argParseGetValList(ArgParse *argParse, int *len);
```
- `argParse` 解析器
- `len` 参数个数
### 触发检测API
1. 检测当前检测的命令的某个选项是否触发
```c
bool argParseCheckCurArgTriggered(ArgParse *argParse, char *opt);
```
- `argParse` 解析器
- `opt` 参数选项
2. 检测某个命令是否被触发
```c
bool argParseCheckCommandTriggered(ArgParse *argParse, char *command_name);
```
- `argParse` 解析器
- `command_name` 命令名称
3. 检测某个全局选项是否被触发
```c
bool argParseCheckGlobalTriggered(ArgParse *argParse, char *opt);
```
- `argParse` 解析器
- `opt` 参数选项

View File

@@ -6,7 +6,7 @@ import os
class loggingRecipe(ConanFile):
name = "cargparse"
version = "0.2.0"
version = "0.3.0"
license = "MIT"
author = "321640253@qq.com"
url = "https://gitea.youmetme.wang/youmetme/logging"

View File

@@ -4,7 +4,7 @@
#include <string.h>
ArgParse *Init() {
ArgParse *ap = argParseInit("简单的命令行工具示例");
ArgParse *ap = argParseInit("简单的命令行工具示例",NOVALUE);
// 添加第一个命令
Command *cmd = argParseAddCommand(

View File

@@ -69,19 +69,25 @@ typedef struct ArgParse {
struct CommandArgs **global_args; // 全局参数
int global_args_len; // 全局参数个数
char *documentation; // 帮助文档
ArgParseValueType value_type; // 值类型 程序默认需要的值例如 gcc main.c
/* 解析所用到的属性*/
struct Command *current_command; // 当前解析到的命令
int argc; // 参数个数
char **argv; // 参数列表
char **val;
int val_len;
int argc; // 参数个数
char **argv; // 参数列表
} ArgParse;
/** Start---------------构造API---------------- */
/**
* @brief 初始化解析器
* @param documentation 帮助文档
* @param value_type 值类型,程序默认需要的值例如 gcc main.c
* @return ArgParse* 解析器指针
*/
ArgParse *argParseInit(char *documentation);
ArgParse *argParseInit(char *documentation, ArgParseValueType value_type);
/**
* @brief 释放解析器
@@ -268,6 +274,9 @@ bool argParseCheckCommandTriggered(ArgParse *argParse, char *command_name);
*/
bool argParseCheckGlobalTriggered(ArgParse *argParse, char *opt);
char *argParseGetVal(ArgParse *argParse);
char **argParseGetValList(ArgParse *argParse, int *len);
/** End----------------解析API---------------- */
/**
@@ -294,10 +303,10 @@ char *argParseGenerateHelp(ArgParse *argParse);
char *
argParseGenerateArgErrorMsg(ArgParse *argParse, char *name, bool short_flag);
_Noreturn void argParseError(ArgParse *argParse,
Command *lastCommand,
char *prefix,
char *suffix);
_Noreturn void argParseError(ArgParse *argParse,
Command *lastCommand,
const char *prefix,
const char *suffix);
#ifdef __cplusplus
}

View File

@@ -12,7 +12,7 @@ static bool _COLOR = true; // 是否启用颜色
void argParseDisableAutoHelp() { _AutoHelp = false; }
ArgParse *argParseInit(char *documentation) {
ArgParse *argParseInit(char *documentation, ArgParseValueType value_type) {
ArgParse *argParse = malloc(sizeof(ArgParse));
if (argParse == NULL) {
return NULL;
@@ -26,6 +26,9 @@ ArgParse *argParseInit(char *documentation) {
argParse->argc = 0;
argParse->argv = NULL;
argParse->documentation = stringNewCopy(documentation);
argParse->value_type = value_type;
argParse->val = NULL;
argParse->val_len = 0;
argParseAutoHelp(argParse);
@@ -259,7 +262,16 @@ void __freeCommand(Command *command) {
free(command->sub_commands);
free(command->name);
free(command->help);
free(command->args);
free(command->default_val);
if (command->val_len > 0) {
for (size_t i = 0; i < command->val_len; i++) {
free(command->val[i]);
}
free(command->val);
}
free(command);
}
@@ -274,7 +286,15 @@ void argParseFree(ArgParse *argParse) {
for (size_t i = 0; i < argParse->global_args_len; i++) {
__freeCommandArgs(argParse->global_args[i]);
}
if (argParse->val_len > 0) {
for (size_t i = 0; i < argParse->val_len; i++) {
free(argParse->val[i]);
}
free(argParse->val);
}
free(argParse->global_args);
free(argParse->documentation);
free(argParse);
}
/** End----------------内存释放API---------------- */
@@ -369,25 +389,79 @@ int __processSubCommand(ArgParse *argParse,
return -1;
}
int __processVal(ArgParse *argParse, int index) {
CommandArgs *arg = NULL;
for (int i = index; i < argParse->argc; i++) {
ArgType argType = checkArgType(argParse->argv[i]);
switch (argType) {
case COMMAND:
argParseSetVal(argParse, argParse->argv[i]);
break;
case LONG_ARG:
// 处理全局命令长选项
arg = argParseFindGlobalArgs(argParse, argParse->argv[i], false);
if (arg == NULL) {
char *msg = argParseGenerateArgErrorMsg(
argParse, argParse->argv[i], false);
argParseError(argParse, argParse->current_command, msg, NULL);
return -1;
}
i = __processArgs(argParse, arg, i);
break;
case SHORT_ARG:
// 处理全局命令短选项
arg = argParseFindGlobalArgs(argParse, argParse->argv[i], true);
if (arg == NULL) {
char *msg = argParseGenerateArgErrorMsg(
argParse, argParse->argv[i], true);
argParseError(argParse, argParse->current_command, msg, NULL);
return -1; // 错误处理
}
i = __processArgs(
argParse, arg, i); // 解析参数值并返回以解析到的索引位置
break;
default:
argParseError(
argParse, argParse->current_command, "unknown error", NULL);
return -1;
}
}
return argParse->argc - 1;
}
// 处理命令参数
int __processCommand(ArgParse *argParse, char *name, int command_index) {
Command *command = argParseFindCommand(argParse, name);
if (command == NULL) {
CommandArgs *arg = NULL;
Command *command = NULL;
argParseError(argParse, command, "Command not found", NULL);
command = argParseFindCommand(argParse, name); // 查找命令
if (command == NULL && argParse->value_type == NOVALUE) {
char *msg = NULL;
if (name != NULL) {
msg = stringNewCopy("\033[1;31mERROR\033[0m:");
__catStr(&msg, 1, name);
__catStr(&msg, 1, " is not a valid command");
}
argParseError(argParse, command, msg, NULL);
return -1;
}
command->is_trigged = true; // 标记命令被触发
if (command == NULL && argParse->value_type != NOVALUE) {
return __processVal(argParse, command_index);
}
CommandArgs *arg = NULL;
argParse->current_command = command;
if (command != NULL) {
command->is_trigged = true; // 标记命令被触发
argParse->current_command = command;
}
for (int i = command_index + 1; i < argParse->argc; i++) {
ArgType argType = checkArgType(argParse->argv[i]);
switch (argType) {
case COMMAND:
case COMMAND: {
// 命令无值则处理子命令
if (command->value_type == NOVALUE) {
__processSubCommand(argParse, command, argParse->argv[i], i);
@@ -397,6 +471,7 @@ int __processCommand(ArgParse *argParse, char *name, int command_index) {
argParseSetCommandVal(command, argParse->argv[i]);
}
break;
}
case LONG_ARG:
// 处理命令长选项
arg = argParseFindCommandArgs(command, argParse->argv[i], false);
@@ -462,6 +537,7 @@ void argParseParse(ArgParse *argParse, int argc, char *argv[]) {
ArgType argType = checkArgType(argv[i]);
switch (argType) {
case COMMAND:
// 处理命令
i = __processCommand(argParse, argv[i], i);
break;
case LONG_ARG: // 处理全局长选项
@@ -638,44 +714,27 @@ char **argParseGetGlobalArgList(ArgParse *argParse, char *opt, int *len) {
return arg->val;
}
size_t __getStrlen(char *str) {
if (str == NULL) {
return 0;
char **argParseGetValList(ArgParse *argParse, int *len) {
if (argParse == NULL) {
return NULL;
}
return strlen(str);
if (argParse->val_len == 0) {
return NULL;
}
*len = argParse->val_len;
return argParse->val;
}
void __catStr(char **dst, int count, ...) {
va_list args;
va_start(args, count);
size_t total_len = 0;
total_len = __getStrlen(*dst);
// 计算总长度
char *temp = NULL;
for (int i = 0; i < count; i++) {
temp = va_arg(args, char *);
total_len += __getStrlen(temp);
}
va_end(args);
// 分配内存
*dst = realloc(*dst, total_len + 1); // +1 是为了存储字符串结束符 '\0'
if (*dst == NULL) {
va_end(args);
return; // 处理内存分配失败
char *argParseGetVal(ArgParse *argParse) {
if (argParse == NULL) {
return NULL;
}
// 拼接字符串
va_start(args, count);
temp = NULL;
for (int i = 0; i < count; i++) {
temp = va_arg(args, char *);
strcat(*dst, temp);
if (argParse->val_len == 0) {
return NULL;
}
va_end(args);
return argParse->val[0];
}
char *argParseGenerateHelpForCommand(Command *command) {
@@ -759,25 +818,41 @@ bool argParseCheckCommandTriggered(ArgParse *argParse, char *command_name) {
return command->is_trigged;
}
_Noreturn void argParseError(ArgParse *argParse,
Command *lastCommand,
char *prefix,
char *suffix) {
_Noreturn void argParseError(ArgParse *argParse,
Command *lastCommand,
const char *prefix,
const char *suffix) {
if (argParse == NULL) {
printf("ERROR: Parse is NULL\n");
exit(1);
}
if (lastCommand == NULL) {
char *mgs = stringNewCopy("");
if (prefix != NULL) {
__catStr(&mgs, 1, prefix);
}
char *help = argParseGenerateHelp(argParse);
printf("\033[1;31mERROR\033[0m: Last command is unknown\n");
printf("%s\n", help);
if (help != NULL) {
__catStr(&mgs, 2, "\n", help);
}
if (suffix != NULL) {
__catStr(&mgs, 2, "\n", suffix);
}
// printf("\033[1;31mERROR\033[0m: Last command is unknown\n");
printf("%s\n", mgs);
free(mgs);
free(help);
argParseFree(argParse);
exit(1);
}
char *ErrorMsg = NULL;
if (prefix != NULL) {
ErrorMsg = stringNewCopy(prefix);
ErrorMsg = stringNewCopy((char *)prefix);
}
char *command_help_msg = argParseGenerateHelpForCommand(lastCommand);
@@ -794,8 +869,7 @@ _Noreturn void argParseError(ArgParse *argParse,
printf("%s\n", ErrorMsg);
free(ErrorMsg);
free(command_help_msg);
free(prefix);
free(suffix);
argParseFree(argParse);
exit(1);
}

View File

@@ -1,5 +1,7 @@
#include "ArgParseTools.h"
#include "ArgParse.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@@ -243,3 +245,84 @@ bool argParseSetCommandVal(Command *command, char *val) {
}
return false;
}
/**
* @brief 设置解析值,该值为程序所需的无命令值例如`gcc mian.c`的`main.c`
* @param argParse 解析器
* @param val 值
* @return 成功返回true失败返回false
*/
bool argParseSetVal(ArgParse *argParse, char *val) {
if (argParse->value_type == MULTIVALUE) { // 多值
argParse->val =
realloc(argParse->val, (argParse->val_len + 1) * sizeof(char *));
if (argParse->val == NULL) {
return false;
}
argParse->val[argParse->val_len] = stringNewCopy(val);
if (argParse->val[argParse->val_len] == NULL) {
return false;
}
argParse->val_len++;
return true;
} else if (argParse->value_type == SINGLEVALUE) { // 单值
if (argParse->val != NULL) {
free(argParse->val);
}
argParse->val = malloc(sizeof(char *));
if (argParse->val == NULL) {
return false;
}
argParse->val[0] = stringNewCopy(val); // 分配内存
if (argParse->val[0] == NULL) {
return false;
}
argParse->val_len = 1;
return true;
} else if (argParse->value_type == NOVALUE) { // 无值
return true;
}
return false;
}
size_t __getStrlen(char *str) {
if (str == NULL) {
return 0;
}
return strlen(str);
}
void __catStr(char **dst, int count, ...) {
va_list args;
va_start(args, count);
size_t total_len = 0;
total_len = __getStrlen(*dst);
// 计算总长度
char *temp = NULL;
for (int i = 0; i < count; i++) {
temp = va_arg(args, char *);
total_len += __getStrlen(temp);
}
va_end(args);
// 分配内存
*dst = realloc(*dst, total_len + 1); // +1 是为了存储字符串结束符 '\0'
if (*dst == NULL) {
va_end(args);
return; // 处理内存分配失败
}
// 拼接字符串
va_start(args, count);
temp = NULL;
for (int i = 0; i < count; i++) {
temp = va_arg(args, char *);
strcat(*dst, temp);
}
va_end(args);
}

View File

@@ -3,6 +3,7 @@
#include "ArgParse.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
@@ -45,6 +46,8 @@ bool argParseSetArgVal(CommandArgs *args, char *val); // 设置命令参数值
bool argParseSetCommandVal(Command *command, char *val); // 设置命令值
bool argParseSetVal(ArgParse *argParse, char *val); // 设置值
ArgType checkArgType(char *arg); // 检查参数类型
Command *argParseFindCommand(ArgParse *argParse, char *name); // 查找命令
@@ -59,7 +62,9 @@ CommandArgs *argParseFindGlobalArgs(ArgParse *argParse,
char *name,
bool short_flag); // 查找全局参数
char *stringNewCopy(char *str); // 创建字符串副本
char *stringNewCopy(char *str); // 创建字符串副本
void __catStr(char **dst, int count, ...); // 字符串拼接
size_t __getStrlen(char *str); // 获取字符串长度
#ifdef __cplusplus
}

View File

@@ -32,3 +32,8 @@ add_executable(${PROJECT_NAME}unknow_command_arg test_unknow_command.c)
target_link_libraries(${PROJECT_NAME}unknow_command_arg CArgParse)
add_test(${PROJECT_NAME}unknow_command_arg ${PROJECT_NAME}unknow_command_arg install --unknow)
set_tests_properties(${PROJECT_NAME}unknow_command_arg PROPERTIES WILL_FAIL TRUE)
# 未知命令选项测试,预期打印该命令的帮助信息
add_executable(${PROJECT_NAME}val test_val.c)
target_link_libraries(${PROJECT_NAME}val CArgParse)
add_test(${PROJECT_NAME}val ${PROJECT_NAME}val file1.txt file2.txt file3.txt -v -q)

View File

@@ -4,7 +4,7 @@
#include <string.h>
ArgParse *initArgParse() {
ArgParse *argparse = argParseInit("测试程序");
ArgParse *argparse = argParseInit("测试程序",NOVALUE);
Command *command = NULL;
Command *sub_command = NULL;

122
tests/test_val.c Normal file
View File

@@ -0,0 +1,122 @@
#include "ArgParse.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
ArgParse *initArgParse() {
ArgParse *argparse = argParseInit("测试程序", MULTIVALUE);
Command *command = NULL;
Command *sub_command = NULL;
// add global arguments
argParseAddGlobalArg(argparse,
"-v",
"--version",
"Show version",
NULL,
NULL,
false,
NOVALUE);
argParseAddGlobalArg(
argparse, "-q", "--quiet", "Quiet mode", NULL, NULL, false, NOVALUE);
// add arguments
command = argParseAddCommand(
argparse, "install", "Install the package", NULL, NULL, NULL, NOVALUE);
argParseAddArg(command,
"-i",
"--index",
"Index URL",
"https://example.com",
NULL,
false,
SINGLEVALUE);
argParseAddArg(command,
"-f",
"--file",
"Package file",
"package.json",
NULL,
false,
MULTIVALUE);
argParseAddArg(command,
"-p",
"--package",
"Package file",
"package.json",
NULL,
false,
MULTIVALUE);
sub_command = argParseAddSubCommand(
command, "tools", "Install tools", NULL, NULL, NULL, MULTIVALUE);
argParseAddArg(sub_command,
"-t",
"--tool",
"Tool name",
"Tool name",
NULL,
true,
MULTIVALUE);
sub_command = argParseAddSubCommand(
command, "tools_sub", "Install tools", NULL, NULL, NULL, MULTIVALUE);
argParseAddArg(sub_command,
"-s",
"--source",
"test_source",
"tools subcommand test",
NULL,
true,
MULTIVALUE);
command = argParseAddCommand(argparse,
"uninstall",
"Uninstall the package",
NULL,
NULL,
NULL,
SINGLEVALUE);
argParseAddArg(command,
"-p",
"--package",
"Package name",
"Package name",
NULL,
true,
MULTIVALUE);
return argparse;
}
int main(int argc, char *argv[]) {
ArgParse *argparse = initArgParse();
argParseParse(argparse, argc, argv);
char *val = argParseGetVal(argparse);
if (val) {
printf("val: %s\n", val);
}
int len = 0;
char **vals = argParseGetValList(argparse, &len);
char *test_val[3] = {"file1.txt", "file2.txt", "file3.txt"};
if (vals) {
for (int i = 0; i < len; i++) {
printf("vals: %s\n", vals[i]);
assert(strcmp(vals[i], test_val[i]) == 0);
}
}
assert(argParseCheckGlobalTriggered(argparse, "-v"));
assert(argParseCheckGlobalTriggered(argparse, "-q"));
argParseFree(argparse);
return 0;
}