Add support for attributed output
Colours, colours, colours. Configurable.
This commit is contained in:
parent
66cf41f89f
commit
855d02acab
|
@ -20,11 +20,13 @@ set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
||||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
find_package (Curses REQUIRED)
|
||||||
find_package (PkgConfig REQUIRED)
|
find_package (PkgConfig REQUIRED)
|
||||||
pkg_check_modules (dependencies REQUIRED libcurl jansson)
|
pkg_check_modules (dependencies REQUIRED libcurl jansson)
|
||||||
find_package (LibEV REQUIRED)
|
find_package (LibEV REQUIRED)
|
||||||
|
|
||||||
include_directories (${dependencies_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
|
include_directories (${CURSES_INCLUDE_DIR}
|
||||||
|
${dependencies_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
|
||||||
|
|
||||||
# Generate a configuration file
|
# Generate a configuration file
|
||||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||||
|
@ -32,7 +34,7 @@ include_directories (${PROJECT_BINARY_DIR})
|
||||||
|
|
||||||
# Build the main executable and link it
|
# Build the main executable and link it
|
||||||
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c siphash.c)
|
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c siphash.c)
|
||||||
target_link_libraries (${PROJECT_NAME}
|
target_link_libraries (${PROJECT_NAME} ${CURSES_LIBRARY}
|
||||||
${dependencies_LIBRARIES} ${LIBEV_LIBRARIES} readline)
|
${dependencies_LIBRARIES} ${LIBEV_LIBRARIES} readline)
|
||||||
|
|
||||||
# The files to be installed
|
# The files to be installed
|
||||||
|
|
423
json-rpc-shell.c
423
json-rpc-shell.c
|
@ -21,11 +21,25 @@
|
||||||
/// Some arbitrary limit for the history file
|
/// Some arbitrary limit for the history file
|
||||||
#define HISTORY_LIMIT 10000
|
#define HISTORY_LIMIT 10000
|
||||||
|
|
||||||
|
// String constants for all attributes we use for output
|
||||||
|
#define ATTR_PROMPT "attr_prompt"
|
||||||
|
#define ATTR_RESET "attr_reset"
|
||||||
|
#define ATTR_WARNING "attr_warning"
|
||||||
|
#define ATTR_ERROR "attr_error"
|
||||||
|
#define ATTR_INCOMING "attr_incoming"
|
||||||
|
#define ATTR_OUTGOING "attr_outgoing"
|
||||||
|
|
||||||
|
// User data for logger functions to enable formatted logging
|
||||||
|
#define print_fatal_data ATTR_ERROR
|
||||||
|
#define print_error_data ATTR_ERROR
|
||||||
|
#define print_warning_data ATTR_WARNING
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "utils.c"
|
#include "utils.c"
|
||||||
|
|
||||||
#include <langinfo.h>
|
#include <langinfo.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
#include <readline/readline.h>
|
#include <readline/readline.h>
|
||||||
|
@ -33,13 +47,38 @@
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <jansson.h>
|
#include <jansson.h>
|
||||||
|
|
||||||
|
#include <curses.h>
|
||||||
|
#include <term.h>
|
||||||
|
|
||||||
|
// --- Configuration (application-specific) ------------------------------------
|
||||||
|
|
||||||
|
static struct config_item g_config_table[] =
|
||||||
|
{
|
||||||
|
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
|
||||||
|
{ ATTR_RESET, NULL, "String to reset terminal attributes" },
|
||||||
|
{ ATTR_WARNING, NULL, "Terminal attributes for warnings" },
|
||||||
|
{ ATTR_ERROR, NULL, "Terminal attributes for errors" },
|
||||||
|
{ ATTR_INCOMING, NULL, "Terminal attributes for incoming traffic" },
|
||||||
|
{ ATTR_OUTGOING, NULL, "Terminal attributes for outgoing traffic" },
|
||||||
|
{ NULL, NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
// --- Main program ------------------------------------------------------------
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
|
enum color_mode
|
||||||
|
{
|
||||||
|
COLOR_AUTO, ///< Autodetect if colours are available
|
||||||
|
COLOR_ALWAYS, ///< Always use coloured output
|
||||||
|
COLOR_NEVER ///< Never use coloured output
|
||||||
|
};
|
||||||
|
|
||||||
static struct app_context
|
static struct app_context
|
||||||
{
|
{
|
||||||
CURL *curl; ///< cURL handle
|
CURL *curl; ///< cURL handle
|
||||||
char curl_error[CURL_ERROR_SIZE]; ///< cURL error info buffer
|
char curl_error[CURL_ERROR_SIZE]; ///< cURL error info buffer
|
||||||
|
|
||||||
|
struct str_map config; ///< Program configuration
|
||||||
|
enum color_mode color_mode; ///< Colour output mode
|
||||||
bool pretty_print; ///< Whether to pretty print
|
bool pretty_print; ///< Whether to pretty print
|
||||||
bool verbose; ///< Print requests
|
bool verbose; ///< Print requests
|
||||||
bool trust_all; ///< Don't verify peer certificates
|
bool trust_all; ///< Don't verify peer certificates
|
||||||
|
@ -52,6 +91,321 @@ static struct app_context
|
||||||
}
|
}
|
||||||
g_ctx;
|
g_ctx;
|
||||||
|
|
||||||
|
// --- Attributed output -------------------------------------------------------
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
bool initialized; ///< Terminal is available
|
||||||
|
bool stdout_is_tty; ///< `stdout' is a terminal
|
||||||
|
bool stderr_is_tty; ///< `stderr' is a terminal
|
||||||
|
|
||||||
|
char *color_set[8]; ///< Codes to set the foreground colour
|
||||||
|
}
|
||||||
|
g_terminal;
|
||||||
|
|
||||||
|
static bool
|
||||||
|
init_terminal (void)
|
||||||
|
{
|
||||||
|
int tty_fd = -1;
|
||||||
|
if ((g_terminal.stderr_is_tty = isatty (STDERR_FILENO)))
|
||||||
|
tty_fd = STDERR_FILENO;
|
||||||
|
if ((g_terminal.stdout_is_tty = isatty (STDOUT_FILENO)))
|
||||||
|
tty_fd = STDOUT_FILENO;
|
||||||
|
|
||||||
|
int err;
|
||||||
|
if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Make sure all terminal features used by us are supported
|
||||||
|
if (!set_a_foreground || !enter_bold_mode || !exit_attribute_mode)
|
||||||
|
{
|
||||||
|
del_curterm (cur_term);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
|
||||||
|
g_terminal.color_set[i] = xstrdup (tparm (set_a_foreground,
|
||||||
|
i, 0, 0, 0, 0, 0, 0, 0, 0));
|
||||||
|
|
||||||
|
return g_terminal.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_terminal (void)
|
||||||
|
{
|
||||||
|
if (!g_terminal.initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
|
||||||
|
free (g_terminal.color_set[i]);
|
||||||
|
del_curterm (cur_term);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
typedef int (*terminal_printer_fn) (int);
|
||||||
|
|
||||||
|
static int
|
||||||
|
putchar_stderr (int c)
|
||||||
|
{
|
||||||
|
return fputc (c, stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static terminal_printer_fn
|
||||||
|
get_attribute_printer (FILE *stream)
|
||||||
|
{
|
||||||
|
if (stream == stdout && g_terminal.stdout_is_tty)
|
||||||
|
return putchar;
|
||||||
|
if (stream == stderr && g_terminal.stderr_is_tty)
|
||||||
|
return putchar_stderr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vprint_attributed (struct app_context *ctx,
|
||||||
|
FILE *stream, const char *attribute, const char *fmt, va_list ap)
|
||||||
|
{
|
||||||
|
terminal_printer_fn printer = get_attribute_printer (stream);
|
||||||
|
if (!attribute)
|
||||||
|
printer = NULL;
|
||||||
|
|
||||||
|
const char *value;
|
||||||
|
value = str_map_find (&ctx->config, attribute);
|
||||||
|
if (printer && soft_assert (value))
|
||||||
|
tputs (value, 1, printer);
|
||||||
|
|
||||||
|
vfprintf (stream, fmt, ap);
|
||||||
|
|
||||||
|
value = str_map_find (&ctx->config, ATTR_RESET);
|
||||||
|
if (printer && soft_assert (value))
|
||||||
|
tputs (value, 1, printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_attributed (struct app_context *ctx,
|
||||||
|
FILE *stream, const char *attribute, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start (ap, fmt);
|
||||||
|
vprint_attributed (ctx, stream, attribute, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
log_message_attributed (void *user_data, const char *quote, const char *fmt,
|
||||||
|
va_list ap)
|
||||||
|
{
|
||||||
|
FILE *stream = stderr;
|
||||||
|
|
||||||
|
print_attributed (&g_ctx, stream, user_data, "%s", quote);
|
||||||
|
vprint_attributed (&g_ctx, stream, user_data, fmt, ap);
|
||||||
|
fputs ("\n", stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_colors (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
// Use escape sequences from terminfo if possible, and SGR as a fallback
|
||||||
|
if (init_terminal ())
|
||||||
|
{
|
||||||
|
const char *attrs[][2] =
|
||||||
|
{
|
||||||
|
{ ATTR_PROMPT, enter_bold_mode },
|
||||||
|
{ ATTR_RESET, exit_attribute_mode },
|
||||||
|
{ ATTR_WARNING, g_terminal.color_set[3] },
|
||||||
|
{ ATTR_ERROR, g_terminal.color_set[1] },
|
||||||
|
{ ATTR_INCOMING, "" },
|
||||||
|
{ ATTR_OUTGOING, "" },
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (attrs); i++)
|
||||||
|
str_map_set (&ctx->config, attrs[i][0], xstrdup (attrs[i][1]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *attrs[][2] =
|
||||||
|
{
|
||||||
|
{ ATTR_PROMPT, "\x1b[1m" },
|
||||||
|
{ ATTR_RESET, "\x1b[0m" },
|
||||||
|
{ ATTR_WARNING, "\x1b[33m" },
|
||||||
|
{ ATTR_ERROR, "\x1b[31m" },
|
||||||
|
{ ATTR_INCOMING, "" },
|
||||||
|
{ ATTR_OUTGOING, "" },
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < N_ELEMENTS (attrs); i++)
|
||||||
|
str_map_set (&ctx->config, attrs[i][0], xstrdup (attrs[i][1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx->color_mode)
|
||||||
|
{
|
||||||
|
case COLOR_ALWAYS:
|
||||||
|
g_terminal.stdout_is_tty = true;
|
||||||
|
g_terminal.stderr_is_tty = true;
|
||||||
|
break;
|
||||||
|
case COLOR_AUTO:
|
||||||
|
if (!g_terminal.initialized)
|
||||||
|
{
|
||||||
|
case COLOR_NEVER:
|
||||||
|
g_terminal.stdout_is_tty = false;
|
||||||
|
g_terminal.stderr_is_tty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_log_message_real = log_message_attributed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Configuration loading ---------------------------------------------------
|
||||||
|
|
||||||
|
static bool
|
||||||
|
read_hexa_escape (const char **cursor, struct str *output)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char c, code = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
c = tolower (*(*cursor));
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
code = (code << 4) | (c - '0');
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
code = (code << 4) | (c - 'a' + 10);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
(*cursor)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
str_append_c (output, code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
read_octal_escape (const char **cursor, struct str *output)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char c, code = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
c = *(*cursor);
|
||||||
|
if (c < '0' || c > '7')
|
||||||
|
break;
|
||||||
|
|
||||||
|
code = (code << 3) | (c - '0');
|
||||||
|
(*cursor)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
str_append_c (output, code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
read_string_escape_sequence (const char **cursor,
|
||||||
|
struct str *output, struct error **e)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
switch ((c = *(*cursor)++))
|
||||||
|
{
|
||||||
|
case '?': str_append_c (output, '?'); break;
|
||||||
|
case '"': str_append_c (output, '"'); break;
|
||||||
|
case '\\': str_append_c (output, '\\'); break;
|
||||||
|
case 'a': str_append_c (output, '\a'); break;
|
||||||
|
case 'b': str_append_c (output, '\b'); break;
|
||||||
|
case 'f': str_append_c (output, '\f'); break;
|
||||||
|
case 'n': str_append_c (output, '\n'); break;
|
||||||
|
case 'r': str_append_c (output, '\r'); break;
|
||||||
|
case 't': str_append_c (output, '\t'); break;
|
||||||
|
case 'v': str_append_c (output, '\v'); break;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
str_append_c (output, '\x1b');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
case 'X':
|
||||||
|
if (!read_hexa_escape (cursor, output))
|
||||||
|
{
|
||||||
|
error_set (e, "invalid hexadecimal escape");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\0':
|
||||||
|
error_set (e, "premature end of escape sequence");
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
(*cursor)--;
|
||||||
|
if (!read_octal_escape (cursor, output))
|
||||||
|
{
|
||||||
|
error_set (e, "unknown escape sequence");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
unescape_string (const char *s, struct str *output, struct error **e)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
while ((c = *s++))
|
||||||
|
{
|
||||||
|
if (c != '\\')
|
||||||
|
str_append_c (output, c);
|
||||||
|
else if (!read_string_escape_sequence (&s, output, e))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_config (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
// TODO: employ a better configuration file format, so that we don't have
|
||||||
|
// to do this convoluted post-processing anymore.
|
||||||
|
|
||||||
|
struct str_map map;
|
||||||
|
str_map_init (&map);
|
||||||
|
map.free = free;
|
||||||
|
|
||||||
|
struct error *e = NULL;
|
||||||
|
if (!read_config_file (&map, &e))
|
||||||
|
{
|
||||||
|
print_error ("error loading configuration: %s", e->message);
|
||||||
|
error_free (e);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str_map_iter iter;
|
||||||
|
str_map_iter_init (&iter, &map);
|
||||||
|
while (str_map_iter_next (&iter))
|
||||||
|
{
|
||||||
|
struct error *e = NULL;
|
||||||
|
struct str value;
|
||||||
|
str_init (&value);
|
||||||
|
if (!unescape_string (iter.link->data, &value, &e))
|
||||||
|
{
|
||||||
|
print_error ("error reading configuration: %s: %s",
|
||||||
|
iter.link->key, e->message);
|
||||||
|
error_free (e);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
str_map_set (&ctx->config, iter.link->key, str_steal (&value));
|
||||||
|
}
|
||||||
|
|
||||||
|
str_map_free (&map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
#define PARSE_FAIL(...) \
|
#define PARSE_FAIL(...) \
|
||||||
BLOCK_START \
|
BLOCK_START \
|
||||||
print_error (__VA_ARGS__); \
|
print_error (__VA_ARGS__); \
|
||||||
|
@ -146,7 +500,7 @@ parse_response (struct app_context *ctx, struct str *buf)
|
||||||
if (!s)
|
if (!s)
|
||||||
print_error ("character conversion failed for `%s'", "result");
|
print_error ("character conversion failed for `%s'", "result");
|
||||||
else
|
else
|
||||||
printf ("%s\n", s);
|
print_attributed (ctx, stdout, ATTR_INCOMING, "%s\n", s);
|
||||||
free (s);
|
free (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +604,7 @@ make_json_rpc_call (struct app_context *ctx,
|
||||||
if (!req_term)
|
if (!req_term)
|
||||||
print_error ("%s: %s", "verbose", "character conversion failed");
|
print_error ("%s: %s", "verbose", "character conversion failed");
|
||||||
else
|
else
|
||||||
printf ("%s\n", req_term);
|
print_attributed (ctx, stdout, ATTR_OUTGOING, "%s\n", req_term);
|
||||||
free (req_term);
|
free (req_term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +812,11 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
|
||||||
{ 'p', "pretty", NULL, 0, "pretty-print the responses" },
|
{ 'p', "pretty", NULL, 0, "pretty-print the responses" },
|
||||||
{ 't', "trust-all", NULL, 0, "don't care about SSL/TLS certificates" },
|
{ 't', "trust-all", NULL, 0, "don't care about SSL/TLS certificates" },
|
||||||
{ 'v', "verbose", NULL, 0, "print the request before sending" },
|
{ 'v', "verbose", NULL, 0, "print the request before sending" },
|
||||||
|
{ 'c', "color", "WHEN", OPT_LONG_ONLY,
|
||||||
|
"colorize output: never, always, or auto" },
|
||||||
|
{ 'w', "write-default-cfg", "FILENAME",
|
||||||
|
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
|
||||||
|
"write a default configuration file and exit" },
|
||||||
{ 0, NULL, NULL, 0, NULL }
|
{ 0, NULL, NULL, 0, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -478,21 +837,29 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
|
||||||
case 'V':
|
case 'V':
|
||||||
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
case 'a':
|
|
||||||
ctx->auto_id = true;
|
case 'o': *origin = optarg; break;
|
||||||
break;
|
case 'a': ctx->auto_id = true; break;
|
||||||
case 'o':
|
case 'p': ctx->pretty_print = true; break;
|
||||||
*origin = optarg;
|
case 't': ctx->trust_all = true; break;
|
||||||
break;
|
case 'v': ctx->verbose = true; break;
|
||||||
case 'p':
|
|
||||||
ctx->pretty_print = true;
|
case 'c':
|
||||||
break;
|
if (!strcasecmp (optarg, "never"))
|
||||||
case 't':
|
ctx->color_mode = COLOR_NEVER;
|
||||||
ctx->trust_all = true;
|
else if (!strcasecmp (optarg, "always"))
|
||||||
break;
|
ctx->color_mode = COLOR_ALWAYS;
|
||||||
case 'v':
|
else if (!strcasecmp (optarg, "auto"))
|
||||||
ctx->verbose = true;
|
ctx->color_mode = COLOR_AUTO;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print_error ("`%s' is not a valid value for `%s'", optarg, "color");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'w':
|
||||||
|
call_write_default_config (optarg, g_config_table);
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print_error ("wrong options");
|
print_error ("wrong options");
|
||||||
|
@ -516,10 +883,16 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
|
||||||
int
|
int
|
||||||
main (int argc, char *argv[])
|
main (int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
str_map_init (&g_ctx.config);
|
||||||
|
g_ctx.config.free = free;
|
||||||
|
|
||||||
char *origin = NULL;
|
char *origin = NULL;
|
||||||
char *endpoint = NULL;
|
char *endpoint = NULL;
|
||||||
parse_program_arguments (&g_ctx, argc, argv, &origin, &endpoint);
|
parse_program_arguments (&g_ctx, argc, argv, &origin, &endpoint);
|
||||||
|
|
||||||
|
init_colors (&g_ctx);
|
||||||
|
load_config (&g_ctx);
|
||||||
|
|
||||||
if (strncmp (endpoint, "http://", 7)
|
if (strncmp (endpoint, "http://", 7)
|
||||||
&& strncmp (endpoint, "https://", 8))
|
&& strncmp (endpoint, "https://", 8))
|
||||||
exit_fatal ("the endpoint address must begin with"
|
exit_fatal ("the endpoint address must begin with"
|
||||||
|
@ -582,10 +955,18 @@ main (int argc, char *argv[])
|
||||||
xstrdup_printf ("%s/" PROGRAM_NAME "/history", data_home);
|
xstrdup_printf ("%s/" PROGRAM_NAME "/history", data_home);
|
||||||
(void) read_history (history_path);
|
(void) read_history (history_path);
|
||||||
|
|
||||||
// XXX: we should use termcap/terminfo for the codes but who cares
|
char *prompt;
|
||||||
char *prompt = xstrdup_printf ("%c\x1b[1m%cjson-rpc> %c\x1b[0m%c",
|
if (!get_attribute_printer (stdout))
|
||||||
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE,
|
prompt = xstrdup_printf ("json-rpc> ");
|
||||||
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE);
|
else
|
||||||
|
{
|
||||||
|
// XXX: to be completely correct, we should use tputs, but we cannot
|
||||||
|
const char *prompt_attrs = str_map_find (&g_ctx.config, ATTR_PROMPT);
|
||||||
|
const char *reset_attrs = str_map_find (&g_ctx.config, ATTR_RESET);
|
||||||
|
prompt = xstrdup_printf ("%c%s%cjson-rpc> %c%s%c",
|
||||||
|
RL_PROMPT_START_IGNORE, prompt_attrs, RL_PROMPT_END_IGNORE,
|
||||||
|
RL_PROMPT_START_IGNORE, reset_attrs, RL_PROMPT_END_IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
// readline 6.3 doesn't immediately redraw the terminal upon reception
|
// readline 6.3 doesn't immediately redraw the terminal upon reception
|
||||||
// of SIGWINCH, so we must run it in an event loop to remediate that
|
// of SIGWINCH, so we must run it in an event loop to remediate that
|
||||||
|
@ -625,5 +1006,7 @@ main (int argc, char *argv[])
|
||||||
curl_slist_free_all (headers);
|
curl_slist_free_all (headers);
|
||||||
free (origin);
|
free (origin);
|
||||||
curl_easy_cleanup (curl);
|
curl_easy_cleanup (curl);
|
||||||
|
str_map_free (&g_ctx.config);
|
||||||
|
free_terminal ();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
88
utils.c
88
utils.c
|
@ -59,8 +59,10 @@
|
||||||
// --- Logging -----------------------------------------------------------------
|
// --- Logging -----------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
log_message_stdio (const char *quote, const char *fmt, va_list ap)
|
log_message_stdio (void *user_data, const char *quote, const char *fmt,
|
||||||
|
va_list ap)
|
||||||
{
|
{
|
||||||
|
(void) user_data;
|
||||||
FILE *stream = stderr;
|
FILE *stream = stderr;
|
||||||
|
|
||||||
fputs (quote, stream);
|
fputs (quote, stream);
|
||||||
|
@ -68,25 +70,48 @@ log_message_stdio (const char *quote, const char *fmt, va_list ap)
|
||||||
fputs ("\n", stream);
|
fputs ("\n", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void (*g_log_message_real) (void *, const char *, const char *, va_list)
|
||||||
log_message (const char *quote, const char *fmt, ...) ATTRIBUTE_PRINTF (2, 3);
|
= log_message_stdio;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
log_message (const char *quote, const char *fmt, ...)
|
log_message (void *user_data, const char *quote, const char *fmt, ...)
|
||||||
|
ATTRIBUTE_PRINTF (3, 4);
|
||||||
|
|
||||||
|
static void
|
||||||
|
log_message (void *user_data, const char *quote, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start (ap, fmt);
|
va_start (ap, fmt);
|
||||||
log_message_stdio (quote, fmt, ap);
|
g_log_message_real (user_data, quote, fmt, ap);
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `fatal' is reserved for unexpected failures that would harm further operation
|
// `fatal' is reserved for unexpected failures that would harm further operation
|
||||||
|
|
||||||
// TODO: colors (probably copy over from stracepkg)
|
#ifndef print_fatal_data
|
||||||
#define print_fatal(...) log_message ("fatal: ", __VA_ARGS__)
|
#define print_fatal_data NULL
|
||||||
#define print_error(...) log_message ("error: ", __VA_ARGS__)
|
#endif
|
||||||
#define print_warning(...) log_message ("warning: ", __VA_ARGS__)
|
|
||||||
#define print_status(...) log_message ("-- ", __VA_ARGS__)
|
#ifndef print_error_data
|
||||||
|
#define print_error_data NULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef print_warning_data
|
||||||
|
#define print_warning_data NULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef print_status_data
|
||||||
|
#define print_status_data NULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define print_fatal(...) \
|
||||||
|
log_message (print_fatal_data, "fatal: ", __VA_ARGS__)
|
||||||
|
#define print_error(...) \
|
||||||
|
log_message (print_error_data, "error: ", __VA_ARGS__)
|
||||||
|
#define print_warning(...) \
|
||||||
|
log_message (print_warning_data, "warning: ", __VA_ARGS__)
|
||||||
|
#define print_status(...) \
|
||||||
|
log_message (print_status_data, "-- ", __VA_ARGS__)
|
||||||
|
|
||||||
#define exit_fatal(...) \
|
#define exit_fatal(...) \
|
||||||
BLOCK_START \
|
BLOCK_START \
|
||||||
|
@ -479,6 +504,16 @@ struct str_map
|
||||||
size_t (*key_xfrm) (char *dest, const char *src, size_t n);
|
size_t (*key_xfrm) (char *dest, const char *src, size_t n);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// As long as you don't remove the current entry, you can modify the map.
|
||||||
|
// Use `link' directly to access the data.
|
||||||
|
|
||||||
|
struct str_map_iter
|
||||||
|
{
|
||||||
|
struct str_map *map; ///< The map we're iterating
|
||||||
|
size_t next_index; ///< Next table index to search
|
||||||
|
struct str_map_link *link; ///< Current link
|
||||||
|
};
|
||||||
|
|
||||||
#define STR_MAP_MIN_ALLOC 16
|
#define STR_MAP_MIN_ALLOC 16
|
||||||
|
|
||||||
typedef void (*str_map_free_fn) (void *);
|
typedef void (*str_map_free_fn) (void *);
|
||||||
|
@ -512,6 +547,29 @@ str_map_free (struct str_map *self)
|
||||||
self->map = NULL;
|
self->map = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
str_map_iter_init (struct str_map_iter *self, struct str_map *map)
|
||||||
|
{
|
||||||
|
self->map = map;
|
||||||
|
self->next_index = 0;
|
||||||
|
self->link = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
str_map_iter_next (struct str_map_iter *self)
|
||||||
|
{
|
||||||
|
struct str_map *map = self->map;
|
||||||
|
if (self->link)
|
||||||
|
self->link = self->link->next;
|
||||||
|
while (!self->link)
|
||||||
|
{
|
||||||
|
if (self->next_index >= map->alloc)
|
||||||
|
return NULL;
|
||||||
|
self->link = map->map[self->next_index++];
|
||||||
|
}
|
||||||
|
return self->link->data;
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t
|
static uint64_t
|
||||||
str_map_hash (const char *s, size_t len)
|
str_map_hash (const char *s, size_t len)
|
||||||
{
|
{
|
||||||
|
@ -866,16 +924,6 @@ struct config_item
|
||||||
const char *description;
|
const char *description;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
|
||||||
load_config_defaults (struct str_map *config, const struct config_item *table)
|
|
||||||
{
|
|
||||||
for (; table->key != NULL; table++)
|
|
||||||
if (table->default_value)
|
|
||||||
str_map_set (config, table->key, xstrdup (table->default_value));
|
|
||||||
else
|
|
||||||
str_map_set (config, table->key, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
read_config_file (struct str_map *config, struct error **e)
|
read_config_file (struct str_map *config, struct error **e)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue