aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2014-11-18 21:39:39 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2014-11-18 22:06:25 +0100
commit8d7ea57a004908890760ffc77f2b4cc1fc6ae789 (patch)
tree189fd1ef8befde54786e240810e71e66b0a8550e
parenta24fa3e3053eccae3981c7a1bf3995d0e67b1036 (diff)
downloadjson-rpc-shell-8d7ea57a004908890760ffc77f2b4cc1fc6ae789.tar.gz
json-rpc-shell-8d7ea57a004908890760ffc77f2b4cc1fc6ae789.tar.xz
json-rpc-shell-8d7ea57a004908890760ffc77f2b4cc1fc6ae789.zip
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.
-rw-r--r--CMakeLists.txt56
-rw-r--r--Makefile19
-rw-r--r--README11
-rw-r--r--cmake/FindLibEV.cmake18
-rw-r--r--json-rpc-shell.c121
5 files changed, 163 insertions, 62 deletions
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 <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)
+
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 <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 \
@@ -570,6 +562,47 @@ fail:
}
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)
{
fprintf (stderr,
@@ -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);