gpiomon: implement custom output formats
authorBartosz Golaszewski <bartekgola@gmail.com>
Thu, 15 Jun 2017 07:23:13 +0000 (09:23 +0200)
committerBartosz Golaszewski <bartekgola@gmail.com>
Mon, 19 Jun 2017 19:24:53 +0000 (21:24 +0200)
Add a new argument allowing the user to specify the format of the
output string printed on GPIO line events.

Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com>
src/tools/gpiomon.c
tests/tests-gpiomon.c

index 6b984b985eca360d0872ba15cdc546e97fd54c5e..b54ee2ba9bb22a0bb50b8ca560e507a77a849e24 100644 (file)
@@ -25,10 +25,11 @@ static const struct option longopts[] = {
        { "silent",             no_argument,            NULL,   's' },
        { "rising-edge",        no_argument,            NULL,   'r' },
        { "falling-edge",       no_argument,            NULL,   'f' },
+       { "format",             required_argument,      NULL,   'F' },
        { 0 },
 };
 
-static const char *const shortopts = "+hvln:srf";
+static const char *const shortopts = "+hvln:srfF:";
 
 static void print_help(void)
 {
@@ -43,6 +44,13 @@ static void print_help(void)
        printf("  -s, --silent:\t\tdon't print event info\n");
        printf("  -r, --rising-edge:\tonly process rising edge events\n");
        printf("  -f, --falling-edge:\tonly process falling edge events\n");
+       printf("  -F, --format=FMT\tspecify custom output format\n");
+       printf("\n");
+       printf("Format specifiers:\n");
+       printf("  %%o:  GPIO line offset\n");
+       printf("  %%e:  event type (0 - falling edge, 1 rising edge)\n");
+       printf("  %%s:  seconds part of the event timestamp\n");
+       printf("  %%n:  nanoseconds part of the event timestamp\n");
 }
 
 static volatile bool do_run = true;
@@ -59,33 +67,126 @@ struct callback_data {
        bool silent;
        bool watch_rising;
        bool watch_falling;
+       char *fmt;
 };
 
-static int event_callback(int type, const struct timespec *ts, void *data)
+static void replace_fmt(char **base, size_t off, const char *new)
 {
-       struct callback_data *cbdata = data;
-       const char *evname = NULL;
+       size_t newlen, baselen;
+       char *tmp;
+
+       newlen = strlen(new);
+       baselen = strlen(*base);
+
+       if (newlen > 2) {
+               /*
+                * FIXME This should be done with realloc() but valgrind
+                * is reporting problems with using uninitialized memory.
+                *
+                * Also: it could be made more efficient by allocating more
+                * memory at the beginning and then doubling the size of the
+                * buffer once the previous one is exhausted.
+                */
+               tmp = malloc(baselen + newlen + 1);
+               if (!tmp)
+                       die("out of memory");
+
+               memset(tmp, 0, baselen + newlen + 1);
+               strcpy(tmp, *base);
+               free(*base);
+               *base = tmp;
+       }
+
+       memmove(*base + off + newlen, *base + off + 2, baselen - off - 2);
+       strncpy(*base + off, new, newlen);
 
-       switch (type) {
-       case GPIOD_EVENT_CB_RISING_EDGE:
-               if (cbdata->watch_rising) {
-                       evname = " RISING EDGE";
-                       cbdata->num_events_done++;
+       if (newlen < 2)
+               *(*base + baselen - 1) = '\0';
+}
+
+static void event_print_custom(int type, const struct timespec *ts,
+                              struct callback_data *data)
+{
+       char repbuf[64], *str, *pos, fmt;
+       size_t off;
+
+       str = strdup(data->fmt);
+       if (!str)
+               die("out of memory");
+
+       for (off = 0;;) {
+               pos = strchr(str + off, '%');
+               if (!pos)
+                       break;
+
+               fmt = *(pos + 1);
+               off = pos - str;
+
+               if (fmt == '%') {
+                       off += 2;
+                       continue;
                }
-               break;
-       case GPIOD_EVENT_CB_FALLING_EDGE:
-               if (cbdata->watch_falling) {
-                       evname = "FALLING EDGE";
-                       cbdata->num_events_done++;
+
+               switch (fmt) {
+               case 'o':
+                       snprintf(repbuf, sizeof(repbuf), "%u", data->offset);
+                       replace_fmt(&str, off, repbuf);
+                       break;
+               case 'e':
+                       if (type == GPIOD_EVENT_CB_RISING_EDGE)
+                               snprintf(repbuf, sizeof(repbuf), "1");
+                       else
+                               snprintf(repbuf, sizeof(repbuf), "0");
+                       replace_fmt(&str, off, repbuf);
+                       break;
+               case 's':
+                       snprintf(repbuf, sizeof(repbuf), "%ld", ts->tv_sec);
+                       replace_fmt(&str, off, repbuf);
+                       break;
+               case 'n':
+                       snprintf(repbuf, sizeof(repbuf), "%ld", ts->tv_nsec);
+                       replace_fmt(&str, off, repbuf);
+                       break;
+               default:
+                       off += 2;
+                       continue;
                }
-               break;
-       default:
-               break;
+
+               off += strlen(repbuf);
        }
 
-       if (evname && !cbdata->silent)
-               printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
-                      evname, cbdata->offset, ts->tv_sec, ts->tv_nsec);
+       printf("%s\n", str);
+       free(str);
+}
+
+static void event_print_human_readable(int type, const struct timespec *ts,
+                                      struct callback_data *data)
+{
+       char *evname;
+
+       if (type == GPIOD_EVENT_CB_RISING_EDGE)
+               evname = " RISING EDGE";
+       else
+               evname = "FALLING EDGE";
+
+       printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
+              evname, data->offset, ts->tv_sec, ts->tv_nsec);
+}
+
+static int event_callback(int type, const struct timespec *ts, void *data)
+{
+       struct callback_data *cbdata = data;
+
+       if ((type == GPIOD_EVENT_CB_FALLING_EDGE && cbdata->watch_falling)
+           || (type == GPIOD_EVENT_CB_RISING_EDGE && cbdata->watch_rising)) {
+               if (!cbdata->silent) {
+                       if (cbdata->fmt)
+                               event_print_custom(type, ts, cbdata);
+                       else
+                               event_print_human_readable(type, ts, cbdata);
+               }
+               cbdata->num_events_done++;
+       }
 
        if (cbdata->num_events_wanted &&
            cbdata->num_events_done >= cbdata->num_events_wanted)
@@ -139,6 +240,9 @@ int main(int argc, char **argv)
                case 'f':
                        cbdata.watch_falling = true;
                        break;
+               case 'F':
+                       cbdata.fmt = optarg;
+                       break;
                case '?':
                        die("try %s --help", get_progname());
                default:
index c20d7bd370b935bf723c8cb667065a19c0bb3a0a..884c45a1042eb291dd78c23998ea2215b0d7b61e 100644 (file)
@@ -190,3 +190,88 @@ static void gpiomon_more_than_one_line_given(void)
 TEST_DEFINE(gpiomon_more_than_one_line_given,
            "tools: gpiomon - more than one line given",
            0, { 4 });
+
+static void gpiomon_custom_format_event_and_offset(void)
+{
+       test_tool_run("gpiomon", "--num-events=1", "--format=%e %o",
+                     test_chip_name(0), "3", (char *)NULL);
+       test_set_event(0, 3, TEST_EVENT_RISING, 100);
+       test_tool_wait();
+
+       TEST_ASSERT(test_tool_exited());
+       TEST_ASSERT_RET_OK(test_tool_exit_status());
+       TEST_ASSERT_NOT_NULL(test_tool_stdout());
+       TEST_ASSERT_NULL(test_tool_stderr());
+       TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 3\n");
+}
+TEST_DEFINE(gpiomon_custom_format_event_and_offset,
+           "tools: gpiomon - custom output format: event and offset",
+           0, { 8, 8 });
+
+static void gpiomon_custom_format_event_and_offset_joined(void)
+{
+       test_tool_run("gpiomon", "--num-events=1", "--format=%e%o",
+                     test_chip_name(0), "3", (char *)NULL);
+       test_set_event(0, 3, TEST_EVENT_RISING, 100);
+       test_tool_wait();
+
+       TEST_ASSERT(test_tool_exited());
+       TEST_ASSERT_RET_OK(test_tool_exit_status());
+       TEST_ASSERT_NOT_NULL(test_tool_stdout());
+       TEST_ASSERT_NULL(test_tool_stderr());
+       TEST_ASSERT_STR_EQ(test_tool_stdout(), "13\n");
+}
+TEST_DEFINE(gpiomon_custom_format_event_and_offset_joined,
+           "tools: gpiomon - custom output format: event and offset, joined strings",
+           0, { 8, 8 });
+
+static void gpiomon_custom_format_timestamp(void)
+{
+       test_tool_run("gpiomon", "--num-events=1", "--format=%e %o %s.%n",
+                     test_chip_name(0), "3", (char *)NULL);
+       test_set_event(0, 3, TEST_EVENT_RISING, 100);
+       test_tool_wait();
+
+       TEST_ASSERT(test_tool_exited());
+       TEST_ASSERT_RET_OK(test_tool_exit_status());
+       TEST_ASSERT_NOT_NULL(test_tool_stdout());
+       TEST_ASSERT_NULL(test_tool_stderr());
+       TEST_ASSERT_REGEX_MATCH(test_tool_stdout(), "1 3 [0-9]+\\.[0-9]+");
+}
+TEST_DEFINE(gpiomon_custom_format_timestamp,
+           "tools: gpiomon - custom output format: timestamp",
+           0, { 8, 8 });
+
+static void gpiomon_custom_format_double_percent(void)
+{
+       test_tool_run("gpiomon", "--num-events=1", "--format=%%e",
+                     test_chip_name(0), "3", (char *)NULL);
+       test_set_event(0, 3, TEST_EVENT_RISING, 100);
+       test_tool_wait();
+
+       TEST_ASSERT(test_tool_exited());
+       TEST_ASSERT_RET_OK(test_tool_exit_status());
+       TEST_ASSERT_NOT_NULL(test_tool_stdout());
+       TEST_ASSERT_NULL(test_tool_stderr());
+       TEST_ASSERT_STR_EQ(test_tool_stdout(), "%e\n");
+}
+TEST_DEFINE(gpiomon_custom_format_double_percent,
+           "tools: gpiomon - custom output format: double percent",
+           0, { 8, 8 });
+
+static void gpiomon_custom_format_unknown_specifier(void)
+{
+       test_tool_run("gpiomon", "--num-events=1", "--format=%w",
+                     test_chip_name(0), "3", (char *)NULL);
+       test_set_event(0, 3, TEST_EVENT_RISING, 100);
+       test_tool_wait();
+
+       TEST_ASSERT(test_tool_exited());
+       TEST_ASSERT_RET_OK(test_tool_exit_status());
+       TEST_ASSERT_NOT_NULL(test_tool_stdout());
+       TEST_ASSERT_NULL(test_tool_stderr());
+       TEST_ASSERT_STR_EQ(test_tool_stdout(), "%w\n");
+}
+TEST_DEFINE(gpiomon_custom_format_unknown_specifier,
+           "tools: gpiomon - custom output format: unknown specifier",
+           0, { 8, 8 });