├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── analytics.c ├── analytics.h ├── circle.yml ├── deps └── list │ ├── list.c │ ├── list.h │ ├── list_iterator.c │ ├── list_node.c │ └── package.json └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | *.o 3 | test.dSYM/ 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | from thlorenz/valgrind 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC := analytics.c 2 | DEPS := $(wildcard deps/*/*.c) 3 | OBJS := $(SRC:.c=.o) $(DEPS:.c=.o) 4 | 5 | CFLAGS := -std=c99 -Wall -Wextra -Ideps 6 | 7 | test: test.o $(OBJS) 8 | 9 | clean: 10 | rm -f test test.o $(OBJS) 11 | 12 | valgrind: test 13 | valgrind --leak-check=full ./$< 14 | 15 | .PHONY: clean valgrind 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # analytics-c (WIP) 2 | 3 | A C client for Segment — The hassle-free way to integrate analytics into any application. 4 | 5 | This library does not work. 6 | 7 | ## Example 8 | 9 | ```c 10 | #include 11 | #include 12 | #include "analytics.h" 13 | 14 | int 15 | main(void){ 16 | analytics_t *analytics = analytics_init("91uhdbiyvft712"); 17 | assert(analytics); 18 | 19 | int rc = analytics_track(analytics, "Did Something", "abc123", NULL); 20 | assert(rc == ANALYTICS_SUCCESS); 21 | 22 | analytics_free(analytics); 23 | return 0; 24 | } 25 | ``` 26 | 27 | ## Developing 28 | 29 | ``` 30 | $ goto analytics-c 31 | $ make test 32 | $ ./test 33 | ``` 34 | 35 | ### Running Valgrind 36 | 37 | Because Valgrind is buggy on OSX, we'll run it in a [Docker container](https://github.com/thlorenz/docker-valgrind): 38 | 39 | ``` 40 | $ docker build -t="analytics-c" . 41 | $ docker run -i analytics-c clean valgrind 42 | ``` 43 | 44 | ## License 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /analytics.c: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // analytics.c 4 | // 5 | // Copyright (c) 2017 Segment 6 | // 7 | 8 | #include 9 | #include 10 | #include "list/list.h" 11 | #include "analytics.h" 12 | 13 | static void __analytics_event_free(void *_); 14 | 15 | /** 16 | * Initialize with `write_key`. 17 | */ 18 | 19 | analytics_t * 20 | analytics_init(const char *write_key) { 21 | assert(write_key); 22 | 23 | analytics_t *self; 24 | if (!(self = malloc(sizeof(analytics_t)))) { 25 | return NULL; 26 | } 27 | 28 | self->queue = list_new(); 29 | if (!self->queue) { 30 | analytics_free(self); 31 | return NULL; 32 | } 33 | self->queue->free = __analytics_event_free; 34 | 35 | self->write_key = write_key; 36 | self->host = "https://api.segment.com"; 37 | 38 | return self; 39 | } 40 | 41 | /** 42 | * Free up analytics after use. 43 | */ 44 | 45 | void 46 | analytics_free(analytics_t *self) { 47 | if (self->queue) { 48 | list_destroy(self->queue); 49 | } 50 | free(self); 51 | self = NULL; 52 | } 53 | 54 | /** 55 | * Create a new analytics event. 56 | */ 57 | 58 | analytics_event_t * 59 | analytics_event_new (analytics_method_t type) { 60 | analytics_event_t *event; 61 | 62 | if (!(event = malloc(sizeof(analytics_event_t)))) { 63 | return NULL; 64 | } 65 | 66 | event->method = type; 67 | 68 | event->event = NULL; 69 | event->name = NULL; 70 | 71 | event->group_id = NULL; 72 | event->user_id = NULL; 73 | event->anonymous_id = NULL; 74 | event->previous_id = NULL; 75 | 76 | event->traits = NULL; 77 | event->properties = NULL; 78 | 79 | return event; 80 | } 81 | 82 | void 83 | analytics_event_free(analytics_event_t *event) { 84 | free(event); 85 | } 86 | 87 | // it's gross, i know 88 | static void 89 | __analytics_event_free(void *_) { 90 | analytics_event_t *event = (analytics_event_t *) _; 91 | analytics_event_free(event); 92 | } 93 | 94 | static int 95 | analytics_enqueue (analytics_t *self, analytics_event_t *event) { 96 | list_node_t *event_node = list_node_new(event); 97 | if (list_rpush(self->queue, event_node) == NULL) { 98 | return ANALYTICS_QUEUE_ERROR; 99 | } 100 | return ANALYTICS_SUCCESS; 101 | } 102 | 103 | /** 104 | * Track. 105 | */ 106 | 107 | int 108 | analytics_track(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties) { 109 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_TRACK); 110 | 111 | if (!event) { 112 | return ANALYTICS_MEMORY_ERROR; 113 | } 114 | 115 | event->event = event_name; 116 | event->user_id = user_id; 117 | event->properties = properties; 118 | 119 | return analytics_enqueue(self, event); 120 | } 121 | 122 | /** 123 | * Identify. 124 | */ 125 | 126 | int 127 | analytics_identify(analytics_t *self, const char *user_id, analytics_hashmap_t *traits) { 128 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_IDENTIFY); 129 | 130 | if (!event) { 131 | return ANALYTICS_MEMORY_ERROR; 132 | } 133 | 134 | event->user_id = user_id; 135 | event->traits = traits; 136 | 137 | return analytics_enqueue(self, event); 138 | } 139 | 140 | /** 141 | * Page. 142 | */ 143 | 144 | int 145 | analytics_page(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties) { 146 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_PAGE); 147 | 148 | if (!event) { 149 | return ANALYTICS_MEMORY_ERROR; 150 | } 151 | 152 | event->user_id = user_id; 153 | event->name = event_name; 154 | event->properties = properties; 155 | 156 | return analytics_enqueue(self, event); 157 | } 158 | 159 | /** 160 | * Screen. 161 | */ 162 | 163 | int 164 | analytics_screen(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties) { 165 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_SCREEN); 166 | 167 | if (!event) { 168 | return ANALYTICS_MEMORY_ERROR; 169 | } 170 | 171 | event->user_id = user_id; 172 | event->name = event_name; 173 | event->properties = properties; 174 | 175 | return analytics_enqueue(self, event); 176 | } 177 | 178 | /** 179 | * Alias. 180 | */ 181 | 182 | int 183 | analytics_alias(analytics_t *self, const char *previous_id, const char *user_id) { 184 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_ALIAS); 185 | 186 | if (!event) { 187 | return ANALYTICS_MEMORY_ERROR; 188 | } 189 | 190 | event->previous_id = previous_id; 191 | event->user_id = user_id; 192 | 193 | return analytics_enqueue(self, event); 194 | } 195 | 196 | /** 197 | * Group. 198 | */ 199 | 200 | int 201 | analytics_group(analytics_t *self, const char *group_id, analytics_hashmap_t *traits) { 202 | analytics_event_t *event = analytics_event_new(ANALYTICS_METHOD_GROUP); 203 | 204 | if (!event) { 205 | return ANALYTICS_MEMORY_ERROR; 206 | } 207 | 208 | event->group_id = group_id; 209 | event->traits = traits; 210 | 211 | return analytics_enqueue(self, event); 212 | } 213 | -------------------------------------------------------------------------------- /analytics.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // analytics.h 4 | // 5 | // Copyright (c) 2017 Segment 6 | // 7 | 8 | #ifndef ANALYTICS_H 9 | #define ANALYTICS_H 1 10 | 11 | #include "list/list.h" 12 | 13 | /** 14 | * Return codes. 15 | */ 16 | 17 | typedef enum { 18 | ANALYTICS_SUCCESS = 0, 19 | ANALYTICS_MEMORY_ERROR = -1, 20 | ANALYTICS_QUEUE_ERROR = -2 21 | } analytics_rc; 22 | 23 | /** 24 | * Analytics API method types. 25 | */ 26 | 27 | typedef enum { 28 | ANALYTICS_METHOD_IDENTIFY, 29 | ANALYTICS_METHOD_TRACK, 30 | ANALYTICS_METHOD_PAGE, 31 | ANALYTICS_METHOD_SCREEN, 32 | ANALYTICS_METHOD_GROUP, 33 | ANALYTICS_METHOD_ALIAS 34 | } analytics_method_t; 35 | 36 | /** 37 | * Analytics hashmap. 38 | */ 39 | 40 | typedef struct { 41 | // this is for stuff like: 42 | // - properties 43 | // - traits 44 | // - context 45 | int foo; 46 | } analytics_hashmap_t; 47 | 48 | /** 49 | * Event structure. 50 | */ 51 | 52 | typedef struct { 53 | analytics_method_t method; 54 | 55 | const char *event; 56 | const char *name; 57 | 58 | const char *group_id; 59 | const char *user_id; 60 | const char *anonymous_id; 61 | const char *previous_id; 62 | 63 | analytics_hashmap_t *traits; 64 | analytics_hashmap_t *properties; 65 | } analytics_event_t; 66 | 67 | /** 68 | * Create a new event. 69 | */ 70 | 71 | analytics_event_t * 72 | analytics_event_new(analytics_method_t type); 73 | 74 | /** 75 | * Free memory associated with an event. 76 | */ 77 | 78 | void 79 | analytics_event_free(analytics_event_t *event); 80 | 81 | /** 82 | * Analytics. 83 | */ 84 | 85 | typedef struct { 86 | const char *write_key; 87 | const char *host; 88 | list_t *queue; 89 | } analytics_t; 90 | 91 | // prototypes 92 | 93 | analytics_t * 94 | analytics_init(const char *write_key); 95 | 96 | void 97 | analytics_free(analytics_t *self); 98 | 99 | int 100 | analytics_track(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties); 101 | 102 | int 103 | analytics_identify(analytics_t *self, const char *user_id, analytics_hashmap_t *traits); 104 | 105 | int 106 | analytics_page(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties); 107 | 108 | int 109 | analytics_screen(analytics_t *self, const char *event_name, const char *user_id, analytics_hashmap_t *properties); 110 | 111 | int 112 | analytics_alias(analytics_t *self, const char *previous_id, const char *user_id); 113 | 114 | int 115 | analytics_group(analytics_t *self, const char *group_id, analytics_hashmap_t *traits); 116 | 117 | #endif // ANALYTICS_H 118 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | pre: 3 | - sudo apt-get install valgrind 4 | 5 | test: 6 | override: 7 | - make valgrind 8 | -------------------------------------------------------------------------------- /deps/list/list.c: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // list.c 4 | // 5 | // Copyright (c) 2010 TJ Holowaychuk 6 | // 7 | 8 | #include "list.h" 9 | 10 | /* 11 | * Allocate a new list_t. NULL on failure. 12 | */ 13 | 14 | list_t * 15 | list_new() { 16 | list_t *self; 17 | if (!(self = LIST_MALLOC(sizeof(list_t)))) 18 | return NULL; 19 | self->head = NULL; 20 | self->tail = NULL; 21 | self->free = NULL; 22 | self->match = NULL; 23 | self->len = 0; 24 | return self; 25 | } 26 | 27 | /* 28 | * Free the list. 29 | */ 30 | 31 | void 32 | list_destroy(list_t *self) { 33 | unsigned int len = self->len; 34 | list_node_t *next; 35 | list_node_t *curr = self->head; 36 | 37 | while (len--) { 38 | next = curr->next; 39 | if (self->free) self->free(curr->val); 40 | LIST_FREE(curr); 41 | curr = next; 42 | } 43 | 44 | LIST_FREE(self); 45 | } 46 | 47 | /* 48 | * Append the given node to the list 49 | * and return the node, NULL on failure. 50 | */ 51 | 52 | list_node_t * 53 | list_rpush(list_t *self, list_node_t *node) { 54 | if (!node) return NULL; 55 | 56 | if (self->len) { 57 | node->prev = self->tail; 58 | node->next = NULL; 59 | self->tail->next = node; 60 | self->tail = node; 61 | } else { 62 | self->head = self->tail = node; 63 | node->prev = node->next = NULL; 64 | } 65 | 66 | ++self->len; 67 | return node; 68 | } 69 | 70 | /* 71 | * Return / detach the last node in the list, or NULL. 72 | */ 73 | 74 | list_node_t * 75 | list_rpop(list_t *self) { 76 | if (!self->len) return NULL; 77 | 78 | list_node_t *node = self->tail; 79 | 80 | if (--self->len) { 81 | (self->tail = node->prev)->next = NULL; 82 | } else { 83 | self->tail = self->head = NULL; 84 | } 85 | 86 | node->next = node->prev = NULL; 87 | return node; 88 | } 89 | 90 | /* 91 | * Return / detach the first node in the list, or NULL. 92 | */ 93 | 94 | list_node_t * 95 | list_lpop(list_t *self) { 96 | if (!self->len) return NULL; 97 | 98 | list_node_t *node = self->head; 99 | 100 | if (--self->len) { 101 | (self->head = node->next)->prev = NULL; 102 | } else { 103 | self->head = self->tail = NULL; 104 | } 105 | 106 | node->next = node->prev = NULL; 107 | return node; 108 | } 109 | 110 | /* 111 | * Prepend the given node to the list 112 | * and return the node, NULL on failure. 113 | */ 114 | 115 | list_node_t * 116 | list_lpush(list_t *self, list_node_t *node) { 117 | if (!node) return NULL; 118 | 119 | if (self->len) { 120 | node->next = self->head; 121 | node->prev = NULL; 122 | self->head->prev = node; 123 | self->head = node; 124 | } else { 125 | self->head = self->tail = node; 126 | node->prev = node->next = NULL; 127 | } 128 | 129 | ++self->len; 130 | return node; 131 | } 132 | 133 | /* 134 | * Return the node associated to val or NULL. 135 | */ 136 | 137 | list_node_t * 138 | list_find(list_t *self, void *val) { 139 | list_iterator_t *it = list_iterator_new(self, LIST_HEAD); 140 | list_node_t *node; 141 | 142 | while ((node = list_iterator_next(it))) { 143 | if (self->match) { 144 | if (self->match(val, node->val)) { 145 | list_iterator_destroy(it); 146 | return node; 147 | } 148 | } else { 149 | if (val == node->val) { 150 | list_iterator_destroy(it); 151 | return node; 152 | } 153 | } 154 | } 155 | 156 | list_iterator_destroy(it); 157 | return NULL; 158 | } 159 | 160 | /* 161 | * Return the node at the given index or NULL. 162 | */ 163 | 164 | list_node_t * 165 | list_at(list_t *self, int index) { 166 | list_direction_t direction = LIST_HEAD; 167 | 168 | if (index < 0) { 169 | direction = LIST_TAIL; 170 | index = ~index; 171 | } 172 | 173 | if ((unsigned)index < self->len) { 174 | list_iterator_t *it = list_iterator_new(self, direction); 175 | list_node_t *node = list_iterator_next(it); 176 | while (index--) node = list_iterator_next(it); 177 | list_iterator_destroy(it); 178 | return node; 179 | } 180 | 181 | return NULL; 182 | } 183 | 184 | /* 185 | * Remove the given node from the list, freeing it and it's value. 186 | */ 187 | 188 | void 189 | list_remove(list_t *self, list_node_t *node) { 190 | node->prev 191 | ? (node->prev->next = node->next) 192 | : (self->head = node->next); 193 | 194 | node->next 195 | ? (node->next->prev = node->prev) 196 | : (self->tail = node->prev); 197 | 198 | if (self->free) self->free(node->val); 199 | 200 | LIST_FREE(node); 201 | --self->len; 202 | } 203 | -------------------------------------------------------------------------------- /deps/list/list.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // list.h 4 | // 5 | // Copyright (c) 2010 TJ Holowaychuk 6 | // 7 | 8 | #ifndef LIST_H 9 | #define LIST_H 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | #include 16 | 17 | // Library version 18 | 19 | #define LIST_VERSION "0.0.5" 20 | 21 | // Memory management macros 22 | 23 | #ifndef LIST_MALLOC 24 | #define LIST_MALLOC malloc 25 | #endif 26 | 27 | #ifndef LIST_FREE 28 | #define LIST_FREE free 29 | #endif 30 | 31 | /* 32 | * list_t iterator direction. 33 | */ 34 | 35 | typedef enum { 36 | LIST_HEAD 37 | , LIST_TAIL 38 | } list_direction_t; 39 | 40 | /* 41 | * list_t node struct. 42 | */ 43 | 44 | typedef struct list_node { 45 | struct list_node *prev; 46 | struct list_node *next; 47 | void *val; 48 | } list_node_t; 49 | 50 | /* 51 | * list_t struct. 52 | */ 53 | 54 | typedef struct { 55 | list_node_t *head; 56 | list_node_t *tail; 57 | unsigned int len; 58 | void (*free)(void *val); 59 | int (*match)(void *a, void *b); 60 | } list_t; 61 | 62 | /* 63 | * list_t iterator struct. 64 | */ 65 | 66 | typedef struct { 67 | list_node_t *next; 68 | list_direction_t direction; 69 | } list_iterator_t; 70 | 71 | // Node prototypes. 72 | 73 | list_node_t * 74 | list_node_new(void *val); 75 | 76 | // list_t prototypes. 77 | 78 | list_t * 79 | list_new(); 80 | 81 | list_node_t * 82 | list_rpush(list_t *self, list_node_t *node); 83 | 84 | list_node_t * 85 | list_lpush(list_t *self, list_node_t *node); 86 | 87 | list_node_t * 88 | list_find(list_t *self, void *val); 89 | 90 | list_node_t * 91 | list_at(list_t *self, int index); 92 | 93 | list_node_t * 94 | list_rpop(list_t *self); 95 | 96 | list_node_t * 97 | list_lpop(list_t *self); 98 | 99 | void 100 | list_remove(list_t *self, list_node_t *node); 101 | 102 | void 103 | list_destroy(list_t *self); 104 | 105 | // list_t iterator prototypes. 106 | 107 | list_iterator_t * 108 | list_iterator_new(list_t *list, list_direction_t direction); 109 | 110 | list_iterator_t * 111 | list_iterator_new_from_node(list_node_t *node, list_direction_t direction); 112 | 113 | list_node_t * 114 | list_iterator_next(list_iterator_t *self); 115 | 116 | void 117 | list_iterator_destroy(list_iterator_t *self); 118 | 119 | #ifdef __cplusplus 120 | } 121 | #endif 122 | 123 | #endif /* LIST_H */ 124 | -------------------------------------------------------------------------------- /deps/list/list_iterator.c: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // iterator.c 4 | // 5 | // Copyright (c) 2010 TJ Holowaychuk 6 | // 7 | 8 | #include "list.h" 9 | 10 | /* 11 | * Allocate a new list_iterator_t. NULL on failure. 12 | * Accepts a direction, which may be LIST_HEAD or LIST_TAIL. 13 | */ 14 | 15 | list_iterator_t * 16 | list_iterator_new(list_t *list, list_direction_t direction) { 17 | list_node_t *node = direction == LIST_HEAD 18 | ? list->head 19 | : list->tail; 20 | return list_iterator_new_from_node(node, direction); 21 | } 22 | 23 | /* 24 | * Allocate a new list_iterator_t with the given start 25 | * node. NULL on failure. 26 | */ 27 | 28 | list_iterator_t * 29 | list_iterator_new_from_node(list_node_t *node, list_direction_t direction) { 30 | list_iterator_t *self; 31 | if (!(self = LIST_MALLOC(sizeof(list_iterator_t)))) 32 | return NULL; 33 | self->next = node; 34 | self->direction = direction; 35 | return self; 36 | } 37 | 38 | /* 39 | * Return the next list_node_t or NULL when no more 40 | * nodes remain in the list. 41 | */ 42 | 43 | list_node_t * 44 | list_iterator_next(list_iterator_t *self) { 45 | list_node_t *curr = self->next; 46 | if (curr) { 47 | self->next = self->direction == LIST_HEAD 48 | ? curr->next 49 | : curr->prev; 50 | } 51 | return curr; 52 | } 53 | 54 | /* 55 | * Free the list iterator. 56 | */ 57 | 58 | void 59 | list_iterator_destroy(list_iterator_t *self) { 60 | LIST_FREE(self); 61 | self = NULL; 62 | } 63 | -------------------------------------------------------------------------------- /deps/list/list_node.c: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // node.c 4 | // 5 | // Copyright (c) 2010 TJ Holowaychuk 6 | // 7 | 8 | #include "list.h" 9 | 10 | /* 11 | * Allocates a new list_node_t. NULL on failure. 12 | */ 13 | 14 | list_node_t * 15 | list_node_new(void *val) { 16 | list_node_t *self; 17 | if (!(self = LIST_MALLOC(sizeof(list_node_t)))) 18 | return NULL; 19 | self->prev = NULL; 20 | self->next = NULL; 21 | self->val = val; 22 | return self; 23 | } -------------------------------------------------------------------------------- /deps/list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list", 3 | "version": "0.0.8", 4 | "repo": "clibs/list", 5 | "description": "Simple linked list", 6 | "keywords": ["list", "structure"], 7 | "license": "MIT", 8 | "src": [ 9 | "src/list_iterator.c", 10 | "src/list.c", 11 | "src/list_node.c", 12 | "src/list.h" 13 | ], 14 | "development": { 15 | "bench": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "analytics.h" 5 | 6 | int 7 | main(void){ 8 | analytics_t *analytics = analytics_init("91uhdbiyvft712"); 9 | assert(analytics); 10 | 11 | int rc = analytics_track(analytics, "Did Something", "abc123", NULL); 12 | assert(rc == ANALYTICS_SUCCESS); 13 | 14 | analytics_free(analytics); 15 | return 0; 16 | } 17 | --------------------------------------------------------------------------------