From 8d7ea57a004908890760ffc77f2b4cc1fc6ae789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Tue, 18 Nov 2014 21:39:39 +0100 Subject: [PATCH] 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. --- CMakeLists.txt | 56 +++++++++++++++++++ Makefile | 19 ------- README | 11 ++-- cmake/FindLibEV.cmake | 18 +++++++ json-rpc-shell.c | 121 +++++++++++++++++++++++++++++------------- 5 files changed, 163 insertions(+), 62 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 cmake/FindLibEV.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..23b7ea4 --- /dev/null +++ b/CMakeLists.txt @@ -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 ") +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) + diff --git a/Makefile b/Makefile deleted file mode 100644 index 9374f3b..0000000 --- a/Makefile +++ /dev/null @@ -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) diff --git a/README b/README index 2cd62f8..0b8d43d 100644 --- a/README +++ b/README @@ -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 ------- diff --git a/cmake/FindLibEV.cmake b/cmake/FindLibEV.cmake new file mode 100644 index 0000000..73787a1 --- /dev/null +++ b/cmake/FindLibEV.cmake @@ -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) + diff --git a/json-rpc-shell.c b/json-rpc-shell.c index 149afa2..5fd4b4e 100644 --- a/json-rpc-shell.c +++ b/json-rpc-shell.c @@ -40,7 +40,10 @@ #include #include #include +#include +#include +#include #include #include #include @@ -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 '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 'a': g_ctx.auto_id = true; break; + case 'o': origin = optarg; 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);