Implement C API
This commit is contained in:
parent
b23a4f40f8
commit
fa09d3ba30
3
c/.gitignore
vendored
Normal file
3
c/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
*.out
|
||||
build/
|
62
c/Makefile
Normal file
62
c/Makefile
Normal 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
6
c/README
Normal file
@ -0,0 +1,6 @@
|
||||
# jsh API C implementation
|
||||
|
||||
## Requirements
|
||||
|
||||
* C99 compiler (e.g. gcc)
|
||||
* jansson
|
1
c/Sourcedeps
Normal file
1
c/Sourcedeps
Normal file
@ -0,0 +1 @@
|
||||
jsh.o: jsh.h
|
106
c/jsh.c
Normal file
106
c/jsh.c
Normal 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
48
c/jsh.h
Normal 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
167
c/libjshtest.c
Normal 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
11
c/minunit.h
Normal 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;
|
Reference in New Issue
Block a user