Implement C API

This commit is contained in:
Ian Adam Naval 2014-11-25 12:40:33 -05:00 committed by Fredric Silberberg
parent b23a4f40f8
commit fa09d3ba30
8 changed files with 404 additions and 0 deletions

3
c/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.o
*.out
build/

62
c/Makefile Normal file
View File

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

6
c/README Normal file
View File

@ -0,0 +1,6 @@
# jsh API C implementation
## Requirements
* C99 compiler (e.g. gcc)
* jansson

1
c/Sourcedeps Normal file
View File

@ -0,0 +1 @@
jsh.o: jsh.h

106
c/jsh.c Normal file
View File

@ -0,0 +1,106 @@
#include <stdlib.h>
#include <jansson.h>
#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);
}

48
c/jsh.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef JSH_H
#define JSH_H
#include <stdbool.h>
#include <stdio.h>
#include <jansson.h>
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

167
c/libjshtest.c Normal file
View File

@ -0,0 +1,167 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

11
c/minunit.h Normal file
View File

@ -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;