From fa09d3ba30c13f1ca070afde824a017cbb8c8fb5 Mon Sep 17 00:00:00 2001 From: Ian Adam Naval Date: Tue, 25 Nov 2014 12:40:33 -0500 Subject: [PATCH] Implement C API --- c/.gitignore | 3 + c/Makefile | 62 ++++++++++++++++++ c/README | 6 ++ c/Sourcedeps | 1 + c/jsh.c | 106 +++++++++++++++++++++++++++++++ c/jsh.h | 48 ++++++++++++++ c/libjshtest.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++ c/minunit.h | 11 ++++ 8 files changed, 404 insertions(+) create mode 100644 c/.gitignore create mode 100644 c/Makefile create mode 100644 c/README create mode 100644 c/Sourcedeps create mode 100644 c/jsh.c create mode 100644 c/jsh.h create mode 100644 c/libjshtest.c create mode 100644 c/minunit.h diff --git a/c/.gitignore b/c/.gitignore new file mode 100644 index 0000000..6c52a44 --- /dev/null +++ b/c/.gitignore @@ -0,0 +1,3 @@ +*.o +*.out +build/ \ No newline at end of file diff --git a/c/Makefile b/c/Makefile new file mode 100644 index 0000000..02b96b9 --- /dev/null +++ b/c/Makefile @@ -0,0 +1,62 @@ +VERSION = 0.1 + +# Compiler flags +CC ?= gcc +AR ?= ar +LIBS = -ljansson +CFLAGS = -std=c99 -pedantic -Wall -Werror -Wextra -I$(PREFIX)/include +CFLAGS += -D_POSIX_C_SOURCE=200112L -DVERSION=\"$(VERSION)\" +LDFLAGS = -L$(PREFIX)/lib + +BUILDDIR = build + +# Install directories +PREFIX ?= /usr/local +BINPREFIX = $(PREFIX)/bin +MANPREFIX = $(PREFIX)/share/man +BASHCPL = $(PREFIX)/share/bash-completion/completions +ZSHCPL = $(PREFIX)/share/zsh/site-functions + +# Source files +SRC = jsh.c +OBJ = $(addprefix $(BUILDDIR)/,$(SRC:.c=.o)) + +# Dependencies and targets +all: CFLAGS += -Os +all: LDFLAGS += -s +all: $(BUILDDIR)/libjsh.so + +debug: CFLAGS += -O0 -g -DDEBUG +debug: $(BUILDDIR)/libjsh.so + +include Sourcedeps + +$(BUILDDIR): + mkdir -p $(BUILDDIR) + +$(OBJ): Makefile | $(BUILDDIR) + +$(BUILDDIR)/jsh.o: CFLAGS += -fPIC +$(BUILDDIR)/%.o: %.c + $(CC) $(CFLAGS) $(OPTFLAGS) -c -o $@ $< + +$(BUILDDIR)/libjsh.so: $(OBJ) + $(CC) -shared -o $@ $(OBJ) + +$(BUILDDIR)/libjshtest: $(BUILDDIR)/libjshtest.o $(BUILDDIR)/libjsh.so + $(CC) $(CFLAGS) $(OPTFLAGS) -o $@ $(BUILDDIR)/libjshtest.o -L $(BUILDDIR) -ljsh -ljansson + +test: $(BUILDDIR)/libjshtest + LD_LIBRARY_PATH=$(BUILDDIR) $(BUILDDIR)/libjshtest + +install: + mkdir -p "$(DESTDIR)$(BINPREFIX)" + cp -p mtsd "$(DESTDIR)$(BINPREFIX)" + +uninstall: + rm -f "$(DESTDIR)$(BINPREFIX)"/mtsd + +clean: + rm -rf $(BUILDDIR) $(OBJ) mtsd + +.PHONY: all debug test install uninstall clean \ No newline at end of file diff --git a/c/README b/c/README new file mode 100644 index 0000000..1936a57 --- /dev/null +++ b/c/README @@ -0,0 +1,6 @@ +# jsh API C implementation + +## Requirements + +* C99 compiler (e.g. gcc) +* jansson \ No newline at end of file diff --git a/c/Sourcedeps b/c/Sourcedeps new file mode 100644 index 0000000..9c60107 --- /dev/null +++ b/c/Sourcedeps @@ -0,0 +1 @@ +jsh.o: jsh.h \ No newline at end of file diff --git a/c/jsh.c b/c/jsh.c new file mode 100644 index 0000000..7533862 --- /dev/null +++ b/c/jsh.c @@ -0,0 +1,106 @@ +#include +#include +#include "jsh.h" + +struct stream *jsh_new_stream(char *name, FILE *out_file, size_t buf_size) +{ + if (name == NULL || out_file == NULL || buf_size == 0) { + return NULL; + } + struct stream *s = (struct stream *) malloc(sizeof(struct stream)); + s->name = name; + s->first = true; + s->out = out_file; + s->buf_size = buf_size; + s->counter = 0; + s->root = s; + s->data = json_object(); + return s; +} + +void jsh_close_stream(struct stream *s) +{ + // TODO: close data -> values + free(s); +} + +void jsh_start_stream(struct stream *s) +{ + fprintf(s->out, "["); +} + +void jsh_end_stream(struct stream *s) +{ + jsh_flush(s->root); + fprintf(s->out, "]"); +} + +void add_comma_if_not_first(struct stream *s) +{ + if (s->first) { + s->first = false; + } else { + fprintf(s->out, ", "); + } +} + +void jsh_flush(struct stream *s) +{ + if (s->root->counter == 0) { + return; + } + + struct stream *sub; + for (sub = s->first_child; sub != NULL; sub = sub->next) { + add_comma_if_not_first(s); + fprintf(s->out, "{\"%s\": [", sub->name); + jsh_flush(sub); + sub->first = true; + fprintf(s->out, "]}"); + } + s->root->counter = 0; + + if (json_object_size(s->data) != 0) { + add_comma_if_not_first(s); + json_dumpf(s->data, s->out, JSON_ENCODE_ANY); + const char *key; + json_t *value; + json_object_foreach(s->data, key, value) { + json_array_clear(json_object_get(s->data, key)); + } + json_object_clear(s->data); + } +} + +struct stream *jsh_new_substream(struct stream *s, char * name) +{ + struct stream *child_stream = jsh_new_stream(name, s->out, s->buf_size); + child_stream->root = s->root; + child_stream->next = NULL; + + if (s->first_child != NULL) { + // find last child and set its next to be the new child + struct stream *current_child = s->first_child; + while (current_child->next != NULL) { + current_child = current_child->next; + } + current_child->next = child_stream; + } else { + s->first_child = child_stream; + } + return child_stream; +} + +void jsh_output(struct stream *s, char *key, json_t *value) +{ + json_t *values = json_object_get(s->data, key); + if (values == NULL) { + values = json_array(); + } + if (s->root->counter >= s->buf_size) { + jsh_flush(s->root); + } + s->root->counter++; + json_array_append(values, value); + json_object_set(s->data, key, values); +} diff --git a/c/jsh.h b/c/jsh.h new file mode 100644 index 0000000..d62e850 --- /dev/null +++ b/c/jsh.h @@ -0,0 +1,48 @@ +#ifndef JSH_H +#define JSH_H + +#include +#include +#include + + +struct stream { + char *name; + FILE *out; + size_t buf_size; + size_t counter; + struct stream *root; + json_t *data; + struct stream *next; + struct stream *first_child; + bool first; +}; + + +/* + * Creates a new root stream. If buf_size is <= 0, uses the default value of 10. + */ +struct stream *jsh_new_stream(char *name, FILE *out_file, size_t buf_size); + + +void jsh_close_stream(struct stream *s); + +void jsh_start_stream(struct stream *s); + +void jsh_end_stream(struct stream *s); + +struct stream *jsh_new_substream(struct stream *s, char * name); + +/* + * Outputs the given object to the top level stream. + */ +void jsh_output(struct stream *s, char *key, json_t *value); + + +/* + * Flushes the output for the entire tree of substreams. + */ +void jsh_flush(struct stream *s); + + +#endif \ No newline at end of file diff --git a/c/libjshtest.c b/c/libjshtest.c new file mode 100644 index 0000000..333147b --- /dev/null +++ b/c/libjshtest.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include "jsh.h" +#include "minunit.h" + + +int tests_run = 0; + + +void usage(const char *program) +{ + fprintf(stderr, "Usage: %s", program); +} + + +// return 0 if files are the same, else -1 +int fd_cmp(FILE *expected, FILE *actual) +{ + fseek(expected, 0, SEEK_SET); + fseek(actual, 0, SEEK_SET); + char e; + while ((e = fgetc(expected)) != EOF) { + if (e != fgetc(actual)) { + return -1; + } + } + return 0; +} + + + +static char *test_new_stream(void) +{ + struct stream *s = jsh_new_stream("stdout", stdout, 10); + mu_assert( + "test_new_stream: Stream returned null pointer", + s != NULL); + mu_assert( + "test_new_stream: Did not set name", + !strncmp(s->name, "stdout", 6)); + mu_assert( + "test_new_stream: Did not set out file descriptor", + s->out == stdout); + mu_assert( + "test_new_stream: counter != 0", + s->counter == 0); + mu_assert( + "test_new_stream: root != s", + s->root == s); + mu_assert( + "test_new_stream: data == NULL", + s->data != NULL); + mu_assert( + "test_new_stream: next != NULL", + s->next == NULL); + mu_assert( + "test_new_stream: first_child != NULL", + s->first_child == NULL); + mu_assert( + "test_new_stream: first != true", + s->first == true); + jsh_close_stream(s); + return 0; +} + +static char *test_cannot_make_new_stream(void) +{ + mu_assert( + "test_cannot_make_new_stream: Should not be able to make new stream", + jsh_new_stream(NULL, stdout, 10) == NULL); + mu_assert( + "test_cannot_make_new_stream: Should not be able to make new stream", + jsh_new_stream("test", NULL, 10) == NULL); + mu_assert( + "test_cannot_make_new_stream: Should not be able to make new stream", + jsh_new_stream("test", stdout, 0) == NULL); + return 0; +} + + +static char *test_output(void) +{ + FILE *stdtest = tmpfile(); + FILE *stdexpected = tmpfile(); + fprintf(stdexpected, "[{\"test\": [1, 2, 3]}]"); + struct stream *s = jsh_new_stream("stdtest", stdtest, 10); + jsh_start_stream(s); + jsh_output(s, "test", json_integer(1)); + jsh_output(s, "test", json_integer(2)); + jsh_output(s, "test", json_integer(3)); + jsh_end_stream(s); + mu_assert( + "test_output: Did not output correctly", + fd_cmp(stdtest, stdexpected) == 0); + jsh_close_stream(s); + return 0; +} + +static char *test_buf_size(void) +{ + FILE *stdtest = tmpfile(); + FILE *stdexpected = tmpfile(); + fprintf(stdexpected, "[{\"test\": [1, 2]}, {\"test\": [3]}]"); + struct stream *s = jsh_new_stream("stdout", stdtest, 2); + jsh_start_stream(s); + jsh_output(s, "test", json_integer(1)); + jsh_output(s, "test", json_integer(2)); + jsh_output(s, "test", json_integer(3)); + jsh_end_stream(s); + + mu_assert( + "test_buf_size: Did not output correctly", + fd_cmp(stdtest, stdexpected) == 0); + jsh_close_stream(s); + return 0; +} + +static char *test_substream(void) +{ + FILE *stdtest = tmpfile(); + FILE *stdexpected = tmpfile(); + fprintf(stdexpected, "[{\"s2\": [{\"test2\": [1, 2]}]}, {\"s2\": [{\"test2\": [3]}]}]"); + struct stream *s = jsh_new_stream("stdout", stdtest, 2); + struct stream *s2 = jsh_new_substream(s, "s2"); + jsh_start_stream(s); + jsh_output(s2, "test2", json_integer(1)); + jsh_output(s2, "test2", json_integer(2)); + jsh_output(s2, "test2", json_integer(3)); + jsh_end_stream(s); + + mu_assert( + "test_substream: Output not the same", + fd_cmp(stdtest, stdexpected) == 0); + jsh_close_stream(s); + return 0; +} + +static char *all_tests() +{ + mu_run_test(test_new_stream); + mu_run_test(test_buf_size); + mu_run_test(test_output); + mu_run_test(test_cannot_make_new_stream); + mu_run_test(test_substream); + return 0; + } + + +int main(int argc, const char *argv[]) +{ + if (argc > 1) { + usage(argv[0]); + exit(1); + } + printf("Running jsh tests...\n"); + char *result = all_tests(); + if (result != 0) { + printf("%s\n", result); + } else { + printf("ALL TESTS PASSED.\n"); + } + printf("Tests run: %d\n", tests_run); + + return result != 0; + return 0; +} \ No newline at end of file diff --git a/c/minunit.h b/c/minunit.h new file mode 100644 index 0000000..b130b2d --- /dev/null +++ b/c/minunit.h @@ -0,0 +1,11 @@ +/* + * From http://www.jera.com/techinfo/jtns/jtn002.html + * + * You may use the code in this tech note for any purpose, with the + * understanding that it comes with NO WARRANTY. + */ + +#define mu_assert(message, test) do { if (!(test)) return message; } while (0) +#define mu_run_test(test) do { char *message = test(); tests_run++; \ + if (message) return message; } while (0) +extern int tests_run; \ No newline at end of file