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:
Přemysl Eric Janouch 2014-11-18 21:39:39 +01:00
parent a24fa3e305
commit 8d7ea57a00
5 changed files with 163 additions and 62 deletions

56
CMakeLists.txt Normal file
View 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)

View File

@ -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
View File

@ -10,15 +10,16 @@ Fuck Java. With a sharp, pointy object. In the ass. Hard. json-c as well.
Building and Running
--------------------
Build dependencies: clang, pkg-config, GNU make, 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.
Build dependencies: CMake, pkg-config, libev, Jansson, cURL, readline
$ git clone https://github.com/pjanouch/json-rpc-shell.git
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
$ 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
-------

18
cmake/FindLibEV.cmake Normal file
View 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)

View File

@ -40,7 +40,10 @@
#include <iconv.h>
#include <langinfo.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <ev.h>
#include <getopt.h>
#include <readline/readline.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';
}
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 ---------------------------------------------------------------
static char *strdup_printf (const char *format, ...) ATTRIBUTE_PRINTF (1, 2);
@ -242,7 +233,7 @@ mkdir_with_parents (char *path)
// --- Main program ------------------------------------------------------------
struct app_context
static struct app_context
{
CURL *curl; ///< cURL handle
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_from_utf8; ///< UTF-8 to terminal encoding
};
}
g_ctx;
#define PARSE_FAIL(...) \
BLOCK_START \
@ -569,6 +561,47 @@ fail:
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
print_usage (const char *program_name)
{
@ -591,9 +624,6 @@ main (int argc, char *argv[])
{
const char *invocation_name = argv[0];
struct app_context ctx;
memset (&ctx, 0, sizeof ctx);
static struct option opts[] =
{
{ "help", no_argument, NULL, 'h' },
@ -624,11 +654,11 @@ main (int argc, char *argv[])
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit (EXIT_SUCCESS);
case 'a': ctx.auto_id = true; break;
case 'a': g_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; break;
case 'p': g_ctx.pretty_print = true; break;
case 't': g_ctx.trust_all = true; break;
case 'v': g_ctx.verbose = true; break;
default:
print_error ("wrong options");
@ -652,7 +682,7 @@ main (int argc, char *argv[])
" either `http://' or `https://'");
CURL *curl;
if (!(ctx.curl = curl = curl_easy_init ()))
if (!(g_ctx.curl = curl = curl_easy_init ()))
exit_fatal ("cURL initialization failed");
struct curl_slist *headers = NULL;
@ -666,10 +696,12 @@ main (int argc, char *argv[])
if (curl_easy_setopt (curl, CURLOPT_POST, 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_SSL_VERIFYPEER, ctx.trust_all ? 0L : 1L)
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, ctx.trust_all ? 0L : 2L)
|| curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER,
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))
exit_fatal ("cURL setup failed");
@ -683,9 +715,9 @@ main (int argc, char *argv[])
encoding = strdup_printf ("%s//TRANSLIT", encoding);
#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
|| (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)
exit_fatal ("creating the UTF-8 conversion object failed: %s",
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);
char *line;
while ((line = readline (prompt)))
{
if (*line)
add_history (line);
// 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
struct ev_loop *loop = EV_DEFAULT;
if (!loop)
exit_fatal ("libev initialization failed");
process_input (&ctx, line);
free (line);
}
ev_signal winch_watcher;
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');
ev_loop_destroy (loop);
// User has terminated the program, let's save the history and clean up
char *dir = strdup (history_path);
(void) mkdir_with_parents (dirname (dir));
free (dir);
@ -731,8 +776,8 @@ main (int argc, char *argv[])
history_path, strerror (errno));
free (history_path);
iconv_close (ctx.term_from_utf8);
iconv_close (ctx.term_to_utf8);
iconv_close (g_ctx.term_from_utf8);
iconv_close (g_ctx.term_to_utf8);
curl_slist_free_all (headers);
free (origin);
curl_easy_cleanup (curl);