Convert to CMake, fix terminal resize behaviour
Fucking terminals, always broken in one way or another. For future reference, libedit acts even worse than readline.
This commit is contained in:
parent
a24fa3e305
commit
8d7ea57a00
56
CMakeLists.txt
Normal file
56
CMakeLists.txt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
project (json-rpc-shell C)
|
||||||
|
cmake_minimum_required (VERSION 2.8.5)
|
||||||
|
|
||||||
|
# Moar warnings
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
|
||||||
|
set (CMAKE_C_FLAGS "-std=c99")
|
||||||
|
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")
|
||||||
|
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
|
||||||
|
|
||||||
|
# Version
|
||||||
|
set (project_VERSION_MAJOR "0")
|
||||||
|
set (project_VERSION_MINOR "1")
|
||||||
|
set (project_VERSION_PATCH "0")
|
||||||
|
|
||||||
|
set (project_VERSION "${project_VERSION_MAJOR}")
|
||||||
|
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
|
||||||
|
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
||||||
|
|
||||||
|
# For custom modules
|
||||||
|
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
find_package (PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules (dependencies REQUIRED libcurl jansson)
|
||||||
|
find_package (LibEV REQUIRED)
|
||||||
|
|
||||||
|
include_directories (${dependencies_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
# Build the main executable and link it
|
||||||
|
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c)
|
||||||
|
target_link_libraries (${PROJECT_NAME}
|
||||||
|
${dependencies_LIBRARIES} ${LIBEV_LIBRARIES} readline)
|
||||||
|
|
||||||
|
# The files to be installed
|
||||||
|
include (GNUInstallDirs)
|
||||||
|
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
||||||
|
# CPack
|
||||||
|
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Shell for JSON-RPC 2.0 HTTP queries")
|
||||||
|
set (CPACK_PACKAGE_VENDOR "Premysl Janouch")
|
||||||
|
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p.janouch@gmail.com>")
|
||||||
|
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
||||||
|
set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR})
|
||||||
|
set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR})
|
||||||
|
set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH})
|
||||||
|
set (CPACK_GENERATOR "TGZ;ZIP")
|
||||||
|
set (CPACK_PACKAGE_FILE_NAME
|
||||||
|
"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}")
|
||||||
|
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
||||||
|
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
|
||||||
|
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}")
|
||||||
|
|
||||||
|
include (CPack)
|
||||||
|
|
19
Makefile
19
Makefile
@ -1,19 +0,0 @@
|
|||||||
SHELL = /bin/sh
|
|
||||||
CC = clang
|
|
||||||
# -Wunused-function is pretty annoying here, as everything is static
|
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-function -ggdb
|
|
||||||
# -lpthread is only there for debugging (gdb & errno)
|
|
||||||
LDFLAGS = `pkg-config --libs libcurl jansson` -lpthread -lreadline
|
|
||||||
|
|
||||||
.PHONY: all clean
|
|
||||||
.SUFFIXES:
|
|
||||||
|
|
||||||
targets = json-rpc-shell
|
|
||||||
|
|
||||||
all: $(targets)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(targets)
|
|
||||||
|
|
||||||
json-rpc-shell: json-rpc-shell.c
|
|
||||||
$(CC) $< -o $@ $(CFLAGS) $(LDFLAGS)
|
|
11
README
11
README
@ -10,15 +10,16 @@ Fuck Java. With a sharp, pointy object. In the ass. Hard. json-c as well.
|
|||||||
|
|
||||||
Building and Running
|
Building and Running
|
||||||
--------------------
|
--------------------
|
||||||
Build dependencies: clang, pkg-config, GNU make, Jansson, cURL, readline
|
Build dependencies: CMake, pkg-config, libev, Jansson, cURL, readline
|
||||||
|
|
||||||
If you don't have Clang, you can edit the Makefile to use GCC or TCC, they work
|
|
||||||
just as good. But there's no CMake support yet, so I force it in the Makefile.
|
|
||||||
|
|
||||||
$ git clone https://github.com/pjanouch/json-rpc-shell.git
|
$ git clone https://github.com/pjanouch/json-rpc-shell.git
|
||||||
|
$ mkdir build
|
||||||
|
$ cd build
|
||||||
|
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||||
$ make
|
$ make
|
||||||
|
|
||||||
That is all, no installation is required, or supported for that matter.
|
Now you can run the following command to get some help about the exact usage:
|
||||||
|
$ ./json-rpc-shell --help
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
18
cmake/FindLibEV.cmake
Normal file
18
cmake/FindLibEV.cmake
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Public Domain
|
||||||
|
|
||||||
|
# The author of libev is a dick and doesn't want to add support for pkg-config,
|
||||||
|
# forcing us to include this pointless file in the distribution.
|
||||||
|
|
||||||
|
# Some distributions do add it, though
|
||||||
|
find_package (PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules (LIBEV QUIET libev)
|
||||||
|
|
||||||
|
if (NOT LIBEV_FOUND)
|
||||||
|
find_path (LIBEV_INCLUDE_DIRS ev.h)
|
||||||
|
find_library (LIBEV_LIBRARIES NAMES ev)
|
||||||
|
|
||||||
|
if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
||||||
|
set (LIBEV_FOUND TRUE)
|
||||||
|
endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
||||||
|
endif (NOT LIBEV_FOUND)
|
||||||
|
|
121
json-rpc-shell.c
121
json-rpc-shell.c
@ -40,7 +40,10 @@
|
|||||||
#include <iconv.h>
|
#include <iconv.h>
|
||||||
#include <langinfo.h>
|
#include <langinfo.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <readline/readline.h>
|
#include <readline/readline.h>
|
||||||
#include <readline/history.h>
|
#include <readline/history.h>
|
||||||
@ -144,18 +147,6 @@ str_append_data (struct str *self, const char *data, size_t n)
|
|||||||
self->str[self->len] = '\0';
|
self->str[self->len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
str_append_c (struct str *self, char c)
|
|
||||||
{
|
|
||||||
str_append_data (self, &c, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
str_append (struct str *self, const char *s)
|
|
||||||
{
|
|
||||||
str_append_data (self, s, strlen (s));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Utilities ---------------------------------------------------------------
|
// --- Utilities ---------------------------------------------------------------
|
||||||
|
|
||||||
static char *strdup_printf (const char *format, ...) ATTRIBUTE_PRINTF (1, 2);
|
static char *strdup_printf (const char *format, ...) ATTRIBUTE_PRINTF (1, 2);
|
||||||
@ -242,7 +233,7 @@ mkdir_with_parents (char *path)
|
|||||||
|
|
||||||
// --- Main program ------------------------------------------------------------
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
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
|
||||||
@ -256,7 +247,8 @@ struct app_context
|
|||||||
|
|
||||||
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
|
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
|
||||||
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
|
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
|
||||||
};
|
}
|
||||||
|
g_ctx;
|
||||||
|
|
||||||
#define PARSE_FAIL(...) \
|
#define PARSE_FAIL(...) \
|
||||||
BLOCK_START \
|
BLOCK_START \
|
||||||
@ -569,6 +561,47 @@ fail:
|
|||||||
free (input);
|
free (input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_winch (EV_P_ ev_signal *handle, int revents)
|
||||||
|
{
|
||||||
|
(void) loop;
|
||||||
|
(void) handle;
|
||||||
|
(void) revents;
|
||||||
|
|
||||||
|
// This fucks up big time on terminals with automatic wrapping such as
|
||||||
|
// rxvt-unicode or newer VTE when the current line overflows, however we
|
||||||
|
// can't do much about that
|
||||||
|
rl_resize_terminal ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_readline_input (char *line)
|
||||||
|
{
|
||||||
|
if (!line)
|
||||||
|
{
|
||||||
|
rl_callback_handler_remove ();
|
||||||
|
ev_break (EV_DEFAULT_ EVBREAK_ONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line)
|
||||||
|
add_history (line);
|
||||||
|
|
||||||
|
// Stupid readline forces us to use a global variable
|
||||||
|
process_input (&g_ctx, line);
|
||||||
|
free (line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_tty_readable (EV_P_ ev_io *handle, int revents)
|
||||||
|
{
|
||||||
|
(void) loop;
|
||||||
|
(void) handle;
|
||||||
|
|
||||||
|
if (revents & EV_READ)
|
||||||
|
rl_callback_read_char ();
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
print_usage (const char *program_name)
|
print_usage (const char *program_name)
|
||||||
{
|
{
|
||||||
@ -591,9 +624,6 @@ main (int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
const char *invocation_name = argv[0];
|
const char *invocation_name = argv[0];
|
||||||
|
|
||||||
struct app_context ctx;
|
|
||||||
memset (&ctx, 0, sizeof ctx);
|
|
||||||
|
|
||||||
static struct option opts[] =
|
static struct option opts[] =
|
||||||
{
|
{
|
||||||
{ "help", no_argument, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
@ -624,11 +654,11 @@ main (int argc, char *argv[])
|
|||||||
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
|
|
||||||
case 'a': ctx.auto_id = true; break;
|
case 'a': g_ctx.auto_id = true; break;
|
||||||
case 'o': origin = optarg; break;
|
case 'o': origin = optarg; break;
|
||||||
case 'p': ctx.pretty_print = true; break;
|
case 'p': g_ctx.pretty_print = true; break;
|
||||||
case 't': ctx.trust_all = true; break;
|
case 't': g_ctx.trust_all = true; break;
|
||||||
case 'v': ctx.verbose = true; break;
|
case 'v': g_ctx.verbose = true; break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print_error ("wrong options");
|
print_error ("wrong options");
|
||||||
@ -652,7 +682,7 @@ main (int argc, char *argv[])
|
|||||||
" either `http://' or `https://'");
|
" either `http://' or `https://'");
|
||||||
|
|
||||||
CURL *curl;
|
CURL *curl;
|
||||||
if (!(ctx.curl = curl = curl_easy_init ()))
|
if (!(g_ctx.curl = curl = curl_easy_init ()))
|
||||||
exit_fatal ("cURL initialization failed");
|
exit_fatal ("cURL initialization failed");
|
||||||
|
|
||||||
struct curl_slist *headers = NULL;
|
struct curl_slist *headers = NULL;
|
||||||
@ -666,10 +696,12 @@ main (int argc, char *argv[])
|
|||||||
|
|
||||||
if (curl_easy_setopt (curl, CURLOPT_POST, 1L)
|
if (curl_easy_setopt (curl, CURLOPT_POST, 1L)
|
||||||
|| curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1L)
|
|| curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1L)
|
||||||
|| curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ctx.curl_error)
|
|| curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, g_ctx.curl_error)
|
||||||
|| curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers)
|
|| curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers)
|
||||||
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, ctx.trust_all ? 0L : 1L)
|
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER,
|
||||||
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, ctx.trust_all ? 0L : 2L)
|
g_ctx.trust_all ? 0L : 1L)
|
||||||
|
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST,
|
||||||
|
g_ctx.trust_all ? 0L : 2L)
|
||||||
|| curl_easy_setopt (curl, CURLOPT_URL, endpoint))
|
|| curl_easy_setopt (curl, CURLOPT_URL, endpoint))
|
||||||
exit_fatal ("cURL setup failed");
|
exit_fatal ("cURL setup failed");
|
||||||
|
|
||||||
@ -683,9 +715,9 @@ main (int argc, char *argv[])
|
|||||||
encoding = strdup_printf ("%s//TRANSLIT", encoding);
|
encoding = strdup_printf ("%s//TRANSLIT", encoding);
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
|
|
||||||
if ((ctx.term_from_utf8 = iconv_open (encoding, "utf-8"))
|
if ((g_ctx.term_from_utf8 = iconv_open (encoding, "utf-8"))
|
||||||
== (iconv_t) -1
|
== (iconv_t) -1
|
||||||
|| (ctx.term_to_utf8 = iconv_open ("utf-8", nl_langinfo (CODESET)))
|
|| (g_ctx.term_to_utf8 = iconv_open ("utf-8", nl_langinfo (CODESET)))
|
||||||
== (iconv_t) -1)
|
== (iconv_t) -1)
|
||||||
exit_fatal ("creating the UTF-8 conversion object failed: %s",
|
exit_fatal ("creating the UTF-8 conversion object failed: %s",
|
||||||
strerror (errno));
|
strerror (errno));
|
||||||
@ -711,17 +743,30 @@ main (int argc, char *argv[])
|
|||||||
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE,
|
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE,
|
||||||
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE);
|
RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE);
|
||||||
|
|
||||||
char *line;
|
// readline 6.3 doesn't immediately redraw the terminal upon reception
|
||||||
while ((line = readline (prompt)))
|
// of SIGWINCH, so we must run it in an event loop to remediate that
|
||||||
{
|
struct ev_loop *loop = EV_DEFAULT;
|
||||||
if (*line)
|
if (!loop)
|
||||||
add_history (line);
|
exit_fatal ("libev initialization failed");
|
||||||
|
|
||||||
process_input (&ctx, line);
|
ev_signal winch_watcher;
|
||||||
free (line);
|
ev_io tty_watcher;
|
||||||
}
|
|
||||||
|
ev_signal_init (&winch_watcher, on_winch, SIGWINCH);
|
||||||
|
ev_signal_start (EV_DEFAULT_ &winch_watcher);
|
||||||
|
|
||||||
|
ev_io_init (&tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ);
|
||||||
|
ev_io_start (EV_DEFAULT_ &tty_watcher);
|
||||||
|
|
||||||
|
rl_catch_sigwinch = false;
|
||||||
|
rl_callback_handler_install (prompt, on_readline_input);
|
||||||
|
|
||||||
|
ev_run (loop, 0);
|
||||||
putchar ('\n');
|
putchar ('\n');
|
||||||
|
|
||||||
|
ev_loop_destroy (loop);
|
||||||
|
|
||||||
|
// User has terminated the program, let's save the history and clean up
|
||||||
char *dir = strdup (history_path);
|
char *dir = strdup (history_path);
|
||||||
(void) mkdir_with_parents (dirname (dir));
|
(void) mkdir_with_parents (dirname (dir));
|
||||||
free (dir);
|
free (dir);
|
||||||
@ -731,8 +776,8 @@ main (int argc, char *argv[])
|
|||||||
history_path, strerror (errno));
|
history_path, strerror (errno));
|
||||||
|
|
||||||
free (history_path);
|
free (history_path);
|
||||||
iconv_close (ctx.term_from_utf8);
|
iconv_close (g_ctx.term_from_utf8);
|
||||||
iconv_close (ctx.term_to_utf8);
|
iconv_close (g_ctx.term_to_utf8);
|
||||||
curl_slist_free_all (headers);
|
curl_slist_free_all (headers);
|
||||||
free (origin);
|
free (origin);
|
||||||
curl_easy_cleanup (curl);
|
curl_easy_cleanup (curl);
|
||||||
|
Loading…
Reference in New Issue
Block a user