mirror of
https://github.com/onyx-and-iris/vmrcli.git
synced 2026-03-12 04:39:15 +00:00
improved parser that preserves quoted commands.
This commit is contained in:
parent
4e58015411
commit
a1556e38c8
4
.gitignore
vendored
4
.gitignore
vendored
@ -56,4 +56,6 @@ dkms.conf
|
|||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
test*
|
test/
|
||||||
|
|
||||||
|
test-*
|
||||||
@ -20,5 +20,6 @@ char *kind_as_string(char *s, int kind, int n);
|
|||||||
char *version_as_string(char *s, long v, int n);
|
char *version_as_string(char *s, long v, int n);
|
||||||
bool is_comment(char *s);
|
bool is_comment(char *s);
|
||||||
struct quickcommand *command_in_quickcommands(const char *command, const struct quickcommand *quickcommands, int n);
|
struct quickcommand *command_in_quickcommands(const char *command, const struct quickcommand *quickcommands, int n);
|
||||||
|
bool add_quotes_if_needed(const char *command, char *output, size_t max_len);
|
||||||
|
|
||||||
#endif /* __UTIL_H__ */
|
#endif /* __UTIL_H__ */
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* @author Vincent Burel, Onyx and Iris (code@onyxandiris.online)
|
* @author Vincent Burel, Onyx and Iris (code@onyxandiris.online)
|
||||||
* @brief Functions for initializing the iVMR interface.
|
* @brief Functions for initializing the iVMR interface.
|
||||||
* Defines a single public function that returns a pointer to the interface.
|
* Defines a single public function that returns a pointer to the interface.
|
||||||
* @version 0.11.0
|
* @version 0.13.0
|
||||||
* @date 2024-07-06
|
* @date 2024-07-06
|
||||||
*
|
*
|
||||||
* @copyright Vincent Burel(c)2015-2021 All Rights Reserved
|
* @copyright Vincent Burel(c)2015-2021 All Rights Reserved
|
||||||
|
|||||||
66
src/util.c
66
src/util.c
@ -2,7 +2,7 @@
|
|||||||
* @file util.c
|
* @file util.c
|
||||||
* @author Onyx and Iris (code@onyxandiris.online)
|
* @author Onyx and Iris (code@onyxandiris.online)
|
||||||
* @brief Utility functions.
|
* @brief Utility functions.
|
||||||
* @version 0.11.0
|
* @version 0.13.0
|
||||||
* @date 2024-07-06
|
* @date 2024-07-06
|
||||||
*
|
*
|
||||||
* @copyright Copyright (c) 2024
|
* @copyright Copyright (c) 2024
|
||||||
@ -125,3 +125,67 @@ struct quickcommand *command_in_quickcommands(const char *command_key, const str
|
|||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds quotes around the value part of a command if it contains spaces or tabs
|
||||||
|
*
|
||||||
|
* @param command The input command string (parameter=value format)
|
||||||
|
* @param output Buffer to store the result
|
||||||
|
* @param max_len Maximum length of the output buffer
|
||||||
|
* @return true if quotes were added or command was copied successfully
|
||||||
|
* @return false if the command is too long or invalid
|
||||||
|
*/
|
||||||
|
bool add_quotes_if_needed(const char *command, char *output, size_t max_len)
|
||||||
|
{
|
||||||
|
const char *equals_pos = strchr(command, '=');
|
||||||
|
|
||||||
|
// No '=' found, copy command as-is
|
||||||
|
if (equals_pos == NULL) {
|
||||||
|
if (strlen(command) >= max_len)
|
||||||
|
return false;
|
||||||
|
strcpy(output, command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *value = equals_pos + 1;
|
||||||
|
|
||||||
|
// Value doesn't contain spaces or tabs, copy command as-is
|
||||||
|
if (strchr(value, ' ') == NULL && strchr(value, '\t') == NULL) {
|
||||||
|
if (strlen(command) >= max_len)
|
||||||
|
return false;
|
||||||
|
strcpy(output, command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value needs quotes - calculate required buffer size
|
||||||
|
size_t param_len = equals_pos - command;
|
||||||
|
size_t value_len = strlen(value);
|
||||||
|
size_t quotes_len = 2;
|
||||||
|
size_t required_len = param_len + 1 + quotes_len + value_len + 1; // param + '=' + '"' + value + '"' + '\0'
|
||||||
|
|
||||||
|
if (required_len > max_len)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the output string in the format: parameter="value"
|
||||||
|
* - Copy the parameter part (up to the '=')
|
||||||
|
* - Append '=' and opening quote
|
||||||
|
* - Append the value
|
||||||
|
* - Append closing quote and null terminator
|
||||||
|
*/
|
||||||
|
char *pos = output;
|
||||||
|
|
||||||
|
strncpy(pos, command, param_len);
|
||||||
|
pos += param_len;
|
||||||
|
|
||||||
|
*pos++ = '=';
|
||||||
|
*pos++ = '"';
|
||||||
|
|
||||||
|
strcpy(pos, value);
|
||||||
|
pos += value_len;
|
||||||
|
|
||||||
|
*pos++ = '"';
|
||||||
|
*pos = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
107
src/vmrcli.c
107
src/vmrcli.c
@ -2,7 +2,7 @@
|
|||||||
* @file vmrcli.c
|
* @file vmrcli.c
|
||||||
* @author Onyx and Iris (code@onyxandiris.online)
|
* @author Onyx and Iris (code@onyxandiris.online)
|
||||||
* @brief A Voicemeeter Remote Command Line Interface
|
* @brief A Voicemeeter Remote Command Line Interface
|
||||||
* @version 0.11.0
|
* @version 0.13.0
|
||||||
* @date 2024-07-06
|
* @date 2024-07-06
|
||||||
*
|
*
|
||||||
* @copyright Copyright (c) 2024
|
* @copyright Copyright (c) 2024
|
||||||
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include "interface.h"
|
#include "interface.h"
|
||||||
@ -35,7 +37,7 @@
|
|||||||
#define RES_SZ 512 /* Size of the buffer passed to VBVMR_GetParameterStringW */
|
#define RES_SZ 512 /* Size of the buffer passed to VBVMR_GetParameterStringW */
|
||||||
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
|
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
|
||||||
#define DELIMITERS " \t;,"
|
#define DELIMITERS " \t;,"
|
||||||
#define VERSION "0.12.0"
|
#define VERSION "0.13.0"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum The kind of values a get call may return.
|
* @enum The kind of values a get call may return.
|
||||||
@ -294,11 +296,35 @@ static void interactive(PT_VMR vmr, bool with_prompt, char *delimiters)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper functions for parse_input */
|
||||||
|
static inline bool is_quote_char(char c) {
|
||||||
|
return (c == '"' || c == '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_delimiter_char(char c, const char *delimiters) {
|
||||||
|
return strchr(delimiters, c) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* skip_consecutive_delimiters(char *p, const char *delimiters) {
|
||||||
|
while (*p != '\0' && is_delimiter_char(*p, delimiters)) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool add_char_to_token(char *token, size_t *token_len, char c, size_t max_len) {
|
||||||
|
if (*token_len < max_len - 1) {
|
||||||
|
token[(*token_len)++] = c;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // Buffer would overflow
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns early if input is a comment
|
* @brief Parse each input line into separate commands and execute them.
|
||||||
* Walks through each line split by " \t;," delimiters.
|
* Commands are split based on the delimiters argument, but quoted strings are preserved as single commands.
|
||||||
* Each token is passed to parse_command()
|
* See the test cases for examples of how input lines are parsed:
|
||||||
*
|
* https://github.com/onyx-and-iris/vmrcli?tab=readme-ov-file#api-commands
|
||||||
* @param vmr Pointer to the iVMR interface
|
* @param vmr Pointer to the iVMR interface
|
||||||
* @param input Each input line, from stdin or CLI args
|
* @param input Each input line, from stdin or CLI args
|
||||||
* @param delimiters A string of delimiter characters to split each input line
|
* @param delimiters A string of delimiter characters to split each input line
|
||||||
@ -308,13 +334,54 @@ static void parse_input(PT_VMR vmr, char *input, char *delimiters)
|
|||||||
if (is_comment(input))
|
if (is_comment(input))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char *token, *p;
|
char *current = input;
|
||||||
|
char token[MAX_LINE];
|
||||||
|
size_t token_length = 0;
|
||||||
|
bool inside_quotes = false;
|
||||||
|
char quote_char = '\0';
|
||||||
|
|
||||||
token = strtok_r(input, delimiters, &p);
|
while (*current != '\0')
|
||||||
while (token != NULL)
|
|
||||||
{
|
{
|
||||||
|
if (!inside_quotes && is_quote_char(*current))
|
||||||
|
{
|
||||||
|
inside_quotes = true;
|
||||||
|
quote_char = *current;
|
||||||
|
current++;
|
||||||
|
log_trace("Entering quotes with char '%c'", quote_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (inside_quotes && *current == quote_char)
|
||||||
|
{
|
||||||
|
inside_quotes = false;
|
||||||
|
quote_char = '\0';
|
||||||
|
current++;
|
||||||
|
log_trace("Exiting quotes");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!inside_quotes && is_delimiter_char(*current, delimiters))
|
||||||
|
{
|
||||||
|
if (token_length > 0)
|
||||||
|
{
|
||||||
|
token[token_length] = '\0';
|
||||||
|
parse_command(vmr, token);
|
||||||
|
token_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = skip_consecutive_delimiters(current, delimiters);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_char_to_token(token, &token_length, *current, MAX_LINE);
|
||||||
|
log_trace("Added char '%c' to token, current token: '%s'", *current, token);
|
||||||
|
}
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token_length > 0)
|
||||||
|
{
|
||||||
|
token[token_length] = '\0';
|
||||||
parse_command(vmr, token);
|
parse_command(vmr, token);
|
||||||
token = strtok_r(NULL, delimiters, &p);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,8 +408,7 @@ static void parse_command(PT_VMR vmr, char *command)
|
|||||||
if (qc_ptr != NULL)
|
if (qc_ptr != NULL)
|
||||||
{
|
{
|
||||||
set_parameters(vmr, qc_ptr->fullcommand);
|
set_parameters(vmr, qc_ptr->fullcommand);
|
||||||
if (eflag)
|
if (eflag) {
|
||||||
{
|
|
||||||
printf("Setting %s\n", qc_ptr->fullcommand);
|
printf("Setting %s\n", qc_ptr->fullcommand);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -359,8 +425,7 @@ static void parse_command(PT_VMR vmr, char *command)
|
|||||||
if (res.val.f == 1 || res.val.f == 0)
|
if (res.val.f == 1 || res.val.f == 0)
|
||||||
{
|
{
|
||||||
set_parameter_float(vmr, command, 1 - res.val.f);
|
set_parameter_float(vmr, command, 1 - res.val.f);
|
||||||
if (eflag)
|
if (eflag) {
|
||||||
{
|
|
||||||
printf("Toggling %s\n", command);
|
printf("Toggling %s\n", command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,10 +437,18 @@ static void parse_command(PT_VMR vmr, char *command)
|
|||||||
|
|
||||||
if (strchr(command, '=') != NULL) /* set */
|
if (strchr(command, '=') != NULL) /* set */
|
||||||
{
|
{
|
||||||
set_parameters(vmr, command);
|
char quoted_command[MAX_LINE];
|
||||||
if (eflag)
|
|
||||||
|
if (add_quotes_if_needed(command, quoted_command, MAX_LINE))
|
||||||
{
|
{
|
||||||
printf("Setting %s\n", command);
|
set_parameters(vmr, quoted_command);
|
||||||
|
if (eflag) {
|
||||||
|
printf("Setting %s\n", command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log_error("Command too long after adding quotes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else /* get */
|
else /* get */
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* @file wrapper.c
|
* @file wrapper.c
|
||||||
* @author Onyx and Iris (code@onyxandiris.online)
|
* @author Onyx and Iris (code@onyxandiris.online)
|
||||||
* @brief Provides public functions that wrap the iVMR calls
|
* @brief Provides public functions that wrap the iVMR calls
|
||||||
* @version 0.11.0
|
* @version 0.13.0
|
||||||
* @date 2024-07-06
|
* @date 2024-07-06
|
||||||
*
|
*
|
||||||
* @copyright Copyright (c) 2024
|
* @copyright Copyright (c) 2024
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user