Add support for attributed output
Colours, colours, colours. Configurable.
This commit is contained in:
		@@ -20,11 +20,13 @@ set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
 | 
			
		||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
 | 
			
		||||
 | 
			
		||||
# Dependencies
 | 
			
		||||
find_package (Curses REQUIRED)
 | 
			
		||||
find_package (PkgConfig REQUIRED)
 | 
			
		||||
pkg_check_modules (dependencies REQUIRED libcurl jansson)
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
#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 "utils.c"
 | 
			
		||||
 | 
			
		||||
#include <langinfo.h>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
#include <strings.h>
 | 
			
		||||
 | 
			
		||||
#include <ev.h>
 | 
			
		||||
#include <readline/readline.h>
 | 
			
		||||
@@ -33,13 +47,38 @@
 | 
			
		||||
#include <curl/curl.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 ------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
{
 | 
			
		||||
	CURL *curl;                         ///< cURL handle
 | 
			
		||||
	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 verbose;                       ///< Print requests
 | 
			
		||||
	bool trust_all;                     ///< Don't verify peer certificates
 | 
			
		||||
@@ -52,6 +91,321 @@ static struct app_context
 | 
			
		||||
}
 | 
			
		||||
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(...)                                                        \
 | 
			
		||||
	BLOCK_START                                                                \
 | 
			
		||||
		print_error (__VA_ARGS__);                                             \
 | 
			
		||||
@@ -146,7 +500,7 @@ parse_response (struct app_context *ctx, struct str *buf)
 | 
			
		||||
		if (!s)
 | 
			
		||||
			print_error ("character conversion failed for `%s'", "result");
 | 
			
		||||
		else
 | 
			
		||||
			printf ("%s\n", s);
 | 
			
		||||
			print_attributed (ctx, stdout, ATTR_INCOMING, "%s\n", s);
 | 
			
		||||
		free (s);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +604,7 @@ make_json_rpc_call (struct app_context *ctx,
 | 
			
		||||
		if (!req_term)
 | 
			
		||||
			print_error ("%s: %s", "verbose", "character conversion failed");
 | 
			
		||||
		else
 | 
			
		||||
			printf ("%s\n", req_term);
 | 
			
		||||
			print_attributed (ctx, stdout, ATTR_OUTGOING, "%s\n", 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" },
 | 
			
		||||
		{ 't', "trust-all", NULL, 0, "don't care about SSL/TLS certificates" },
 | 
			
		||||
		{ '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 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@@ -478,21 +837,29 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
 | 
			
		||||
	case 'V':
 | 
			
		||||
		printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
 | 
			
		||||
		exit (EXIT_SUCCESS);
 | 
			
		||||
	case 'a':
 | 
			
		||||
		ctx->auto_id = true;
 | 
			
		||||
		break;
 | 
			
		||||
	case 'o':
 | 
			
		||||
		*origin = optarg;
 | 
			
		||||
		break;
 | 
			
		||||
	case 'p':
 | 
			
		||||
		ctx->pretty_print = true;
 | 
			
		||||
		break;
 | 
			
		||||
	case 't':
 | 
			
		||||
		ctx->trust_all = true;
 | 
			
		||||
		break;
 | 
			
		||||
	case 'v':
 | 
			
		||||
		ctx->verbose = true;
 | 
			
		||||
 | 
			
		||||
	case 'o': *origin = optarg;         break;
 | 
			
		||||
	case 'a': ctx->auto_id      = true; break;
 | 
			
		||||
	case 'p': ctx->pretty_print = true; break;
 | 
			
		||||
	case 't': ctx->trust_all    = true; break;
 | 
			
		||||
	case 'v': ctx->verbose      = true; break;
 | 
			
		||||
 | 
			
		||||
	case 'c':
 | 
			
		||||
		if      (!strcasecmp (optarg, "never"))
 | 
			
		||||
			ctx->color_mode = COLOR_NEVER;
 | 
			
		||||
		else if (!strcasecmp (optarg, "always"))
 | 
			
		||||
			ctx->color_mode = COLOR_ALWAYS;
 | 
			
		||||
		else if (!strcasecmp (optarg, "auto"))
 | 
			
		||||
			ctx->color_mode = COLOR_AUTO;
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			print_error ("`%s' is not a valid value for `%s'", optarg, "color");
 | 
			
		||||
			exit (EXIT_FAILURE);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case 'w':
 | 
			
		||||
		call_write_default_config (optarg, g_config_table);
 | 
			
		||||
		exit (EXIT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		print_error ("wrong options");
 | 
			
		||||
@@ -516,10 +883,16 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
 | 
			
		||||
int
 | 
			
		||||
main (int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	str_map_init (&g_ctx.config);
 | 
			
		||||
	g_ctx.config.free = free;
 | 
			
		||||
 | 
			
		||||
	char *origin = NULL;
 | 
			
		||||
	char *endpoint = NULL;
 | 
			
		||||
	parse_program_arguments (&g_ctx, argc, argv, &origin, &endpoint);
 | 
			
		||||
 | 
			
		||||
	init_colors (&g_ctx);
 | 
			
		||||
	load_config (&g_ctx);
 | 
			
		||||
 | 
			
		||||
	if (strncmp (endpoint, "http://", 7)
 | 
			
		||||
	 && strncmp (endpoint, "https://", 8))
 | 
			
		||||
		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);
 | 
			
		||||
	(void) read_history (history_path);
 | 
			
		||||
 | 
			
		||||
	// XXX: we should use termcap/terminfo for the codes but who cares
 | 
			
		||||
	char *prompt = xstrdup_printf ("%c\x1b[1m%cjson-rpc> %c\x1b[0m%c",
 | 
			
		||||
		RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE,
 | 
			
		||||
		RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE);
 | 
			
		||||
	char *prompt;
 | 
			
		||||
	if (!get_attribute_printer (stdout))
 | 
			
		||||
		prompt = xstrdup_printf ("json-rpc> ");
 | 
			
		||||
	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
 | 
			
		||||
	// 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);
 | 
			
		||||
	free (origin);
 | 
			
		||||
	curl_easy_cleanup (curl);
 | 
			
		||||
	str_map_free (&g_ctx.config);
 | 
			
		||||
	free_terminal ();
 | 
			
		||||
	return EXIT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										88
									
								
								utils.c
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								utils.c
									
									
									
									
									
								
							@@ -59,8 +59,10 @@
 | 
			
		||||
// --- Logging -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
	fputs (quote, stream);
 | 
			
		||||
@@ -68,25 +70,48 @@ log_message_stdio (const char *quote, const char *fmt, va_list ap)
 | 
			
		||||
	fputs ("\n", stream);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
log_message (const char *quote, const char *fmt, ...) ATTRIBUTE_PRINTF (2, 3);
 | 
			
		||||
static void (*g_log_message_real) (void *, const char *, const char *, va_list)
 | 
			
		||||
	= log_message_stdio;
 | 
			
		||||
 | 
			
		||||
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_start (ap, fmt);
 | 
			
		||||
	log_message_stdio (quote, fmt, ap);
 | 
			
		||||
	g_log_message_real (user_data, quote, fmt, ap);
 | 
			
		||||
	va_end (ap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// `fatal' is reserved for unexpected failures that would harm further operation
 | 
			
		||||
 | 
			
		||||
// TODO: colors (probably copy over from stracepkg)
 | 
			
		||||
#define print_fatal(...)    log_message ("fatal: ",   __VA_ARGS__)
 | 
			
		||||
#define print_error(...)    log_message ("error: ",   __VA_ARGS__)
 | 
			
		||||
#define print_warning(...)  log_message ("warning: ", __VA_ARGS__)
 | 
			
		||||
#define print_status(...)   log_message ("-- ",       __VA_ARGS__)
 | 
			
		||||
#ifndef print_fatal_data
 | 
			
		||||
#define print_fatal_data    NULL
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#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(...)                                                        \
 | 
			
		||||
	BLOCK_START                                                                \
 | 
			
		||||
@@ -479,6 +504,16 @@ struct str_map
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
typedef void (*str_map_free_fn) (void *);
 | 
			
		||||
@@ -512,6 +547,29 @@ str_map_free (struct str_map *self)
 | 
			
		||||
	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
 | 
			
		||||
str_map_hash (const char *s, size_t len)
 | 
			
		||||
{
 | 
			
		||||
@@ -866,16 +924,6 @@ struct config_item
 | 
			
		||||
	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
 | 
			
		||||
read_config_file (struct str_map *config, struct error **e)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user