RPM Package Manager, CVS Repository
  http://rpm5.org/cvs/
  ____________________________________________________________________________

  Server: rpm5.org                         Name:   Jeff Johnson
  Root:   /v/rpm/cvs                       Email:  j...@rpm5.org
  Module: rpm                              Date:   17-May-2015 01:32:03
  Branch: rpm-5_4                          Handle: 2015051623320300

  Added files:              (Branch: rpm-5_4)
    rpm/rpmio               tjson.c

  Log:
    - jsmn: simmple examples for twitter/github.

  Summary:
    Revision    Changes     Path
    1.1.2.1     +520 -0     rpm/rpmio/tjson.c
  ____________________________________________________________________________

  patch -p0 <<'@@ .'
  Index: rpm/rpmio/tjson.c
  ============================================================================
  $ cvs diff -u -r0 -r1.1.2.1 tjson.c
  --- /dev/null 2015-05-17 01:30:38.000000000 +0200
  +++ tjson.c   2015-05-17 01:32:03.426201299 +0200
  @@ -0,0 +1,520 @@
  +#include "system.h"
  +#include <stdarg.h>
  +#include <stdbool.h>
  +#include <curl/curl.h>
  +
  +#include <rpmutil.h>
  +
  +#include "debug.h"
  +
  +#include "jsmn.c"
  +
  +/*==============================================================*/
  +/* --- buf.h */
  +
  +typedef struct {
  +    size_t len;                      // current length of buffer (used bytes)
  +    size_t limit;            // maximum length of buffer (allocated)
  +    uint8_t *data;           // insert bytes here
  +} buf_t;
  +
  +
  +buf_t *buf_size(buf_t * buf, size_t len);
  +void buf_push(buf_t * buf, uint8_t c);
  +void buf_concat(buf_t * buf, uint8_t * data, size_t len);
  +char *buf_tostr(buf_t * buf);
  +/*==============================================================*/
  +/* --- json.h */
  +
  +char *json_fetch(char *url);
  +jsmntok_t *json_tokenise(char *js);
  +bool json_token_streq(char *js, jsmntok_t * t, char *s)
  +     RPM_GNUC_PURE;
  +char *json_token_tostr(char *js, jsmntok_t * t);
  +/*==============================================================*/
  +/* --- log.h */
  +
  +#define STR(x) #x
  +#define STRINGIFY(x) STR(x)
  +#define LINESTR STRINGIFY(__LINE__)
  +
  +#define log_assert(x) if (!(x)) log_die("%s: assertion failed: " #x \
  +                                        " (" __FILE__ ", line " LINESTR \
  +                                        ")", __func__)
  +
  +#define log_null(x) log_assert(x != NULL)
  +
  +#define log_debug(msg, ...) log_info("%s: " msg, __func__, ##__VA_ARGS__)
  +
  +#define log_syserr(msg) log_die("%s: %s: %s", __func__, msg, strerror(errno))
  +
  +void log_die(char *msg, ...)
  +     RPM_GNUC_NORETURN;
  +void log_info(char *msg, ...);
  +/*==============================================================*/
  +/* --- buf.c */
  +/* buf: a sized buffer type. */
  +
  +buf_t *buf_size(buf_t * buf, size_t len)
  +{
  +    if (buf == NULL) {
  +     buf = malloc(sizeof(buf_t));
  +     log_null(buf);
  +     buf->data = NULL;
  +     buf->len = 0;
  +    }
  +
  +    buf->data = realloc(buf->data, len);
  +    log_null(buf->data);
  +
  +    if (buf->len > len)
  +     buf->len = len;
  +    buf->limit = len;
  +
  +    return buf;
  +}
  +
  +void buf_push(buf_t * buf, uint8_t c)
  +{
  +    log_null(buf);
  +
  +    log_assert(buf->len < buf->limit);
  +
  +    buf->data[buf->len++] = c;
  +}
  +
  +void buf_concat(buf_t * dst, uint8_t * src, size_t len)
  +{
  +    log_null(dst);
  +    log_null(src);
  +
  +    log_assert(dst->len + len <= dst->limit);
  +
  +    for (size_t i = 0; i < len; i++)
  +     dst->data[dst->len++] = src[i];
  +}
  +
  +char *buf_tostr(buf_t * buf)
  +{
  +    log_null(buf);
  +
  +    char *str = malloc(buf->len + 1);
  +
  +    memcpy(str, buf->data, buf->len);
  +    str[buf->len] = '\0';
  +
  +    return str;
  +}
  +
  +/*==============================================================*/
  +/* --- json.c */
  +
  +#define BUFFER_SIZE 32768
  +#define JSON_TOKENS 256
  +
  +static size_t fetch_data(void *buffer, size_t size, size_t nmemb,
  +                      void *userp)
  +{
  +    buf_t *buf = (buf_t *) userp;
  +    size_t total = size * nmemb;
  +
  +    if (buf->limit - buf->len < total) {
  +     buf = buf_size(buf, buf->limit + total);
  +     log_null(buf);
  +    }
  +
  +    buf_concat(buf, buffer, total);
  +
  +    return total;
  +}
  +
  +char *json_fetch(char *url)
  +{
  +    CURL *curl = curl_easy_init();
  +    log_null(curl);
  +
  +    curl_easy_setopt(curl, CURLOPT_URL, url);
  +
  +    buf_t *buf = buf_size(NULL, BUFFER_SIZE);
  +    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_data);
  +    curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
  +    curl_easy_setopt(curl, CURLOPT_USERAGENT,
  +                  "jsmn-example (https://github.com/alisdair/jsmn-example, 
alisd...@mcdiarmid.org)");
  +
  +    struct curl_slist *hs =
  +     curl_slist_append(NULL, "Accept: application/json");
  +    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
  +
  +    CURLcode res = curl_easy_perform(curl);
  +    if (res != CURLE_OK)
  +     log_die("curl_easy_perform failed: %s", curl_easy_strerror(res));
  +
  +    curl_easy_cleanup(curl);
  +    curl_slist_free_all(hs);
  +
  +    char *js = buf_tostr(buf);
  +    free(buf->data);
  +    free(buf);
  +
  +fprintf(stderr, "<-- %s(%s)\n%s\n", __FUNCTION__, url, js);
  +    return js;
  +}
  +
  +jsmntok_t *json_tokenise(char *js)
  +{
  +    jsmn_parser parser;
  +    jsmn_init(&parser);
  +
  +    unsigned int n = JSON_TOKENS;
  +    jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * n);
  +    log_null(tokens);
  +
  +    int ret = jsmn_parse(&parser, js, strlen(js), tokens, n);
  +
  +    while (ret == JSMN_ERROR_NOMEM) {
  +     n = n * 2 + 1;
  +     tokens = realloc(tokens, sizeof(jsmntok_t) * n);
  +     log_null(tokens);
  +     ret = jsmn_parse(&parser, js, strlen(js), tokens, n);
  +    }
  +
  +    if (ret == JSMN_ERROR_INVAL)
  +     log_die("jsmn_parse: invalid JSON string");
  +    if (ret == JSMN_ERROR_PART)
  +     log_die("jsmn_parse: truncated JSON string");
  +
  +    return tokens;
  +}
  +
  +bool json_token_streq(char *js, jsmntok_t * t, char *s)
  +{
  +    return (strncmp(js + t->start, s, t->end - t->start) == 0
  +         && strlen(s) == (size_t) (t->end - t->start));
  +}
  +
  +char *json_token_tostr(char *js, jsmntok_t * t)
  +{
  +    js[t->end] = '\0';
  +    return js + t->start;
  +}
  +
  +/*==============================================================*/
  +/* --- log.c */
  +/* log: stderr logging information. */
  +
  +void log_die(char *msg, ...)
  +{
  +    va_list argp;
  +
  +    log_null(msg);
  +
  +    va_start(argp, msg);
  +    vfprintf(stderr, msg, argp);
  +    va_end(argp);
  +
  +    fprintf(stderr, "\n");
  +    abort();
  +}
  +
  +void log_info(char *msg, ...)
  +{
  +    va_list argp;
  +
  +    log_null(msg);
  +
  +    va_start(argp, msg);
  +    vfprintf(stderr, msg, argp);
  +    va_end(argp);
  +
  +    fprintf(stderr, "\n");
  +}
  +
  +/*==============================================================*/
  +/* --- twitter.c */
  +
  +#if defined(JSMN_TWITTER)
  +char URL[] = "https://api.twitter.com/1.1/trends/1.json";;
  +
  +int main(void)
  +{
  +    char *js = json_fetch(URL);
  +
  +    jsmntok_t *tokens = json_tokenise(js);
  +
  +    /* The Twitter trends API response is in this format:
  +     *
  +     * [
  +     *   {
  +     *      ...,
  +     *      "trends": [
  +     *          {
  +     *              ...,
  +     *              "name": "TwitterChillers",
  +     *          },
  +     *          ...,
  +     *      ],
  +     *      ...,
  +     *   }
  +     * ]
  +     *
  +     */
  +    typedef enum {
  +     START,
  +     WRAPPER, OBJECT,
  +     TRENDS, ARRAY,
  +     TREND, NAME,
  +     SKIP,
  +     STOP
  +    } parse_state;
  +
  +    // state is the current state of the parser
  +    parse_state state = START;
  +
  +    // stack is the state we return to when reaching the end of an object
  +    parse_state stack = STOP;
  +
  +    // Counters to keep track of how far through parsing we are
  +    size_t object_tokens = 0;
  +    size_t skip_tokens = 0;
  +    size_t trends = 0;
  +    size_t trend_tokens = 0;
  +
  +    for (size_t i = 0, j = 1; j > 0; i++, j--) {
  +     jsmntok_t *t = &tokens[i];
  +
  +     // Should never reach uninitialized tokens
  +     log_assert(t->start != -1 && t->end != -1);
  +
  +     if (t->type == JSMN_ARRAY || t->type == JSMN_OBJECT)
  +         j += t->size;
  +
  +     switch (state) {
  +     case START:
  +         if (t->type != JSMN_ARRAY)
  +             log_die("Invalid response: root element must be array.");
  +         if (t->size != 1)
  +             log_die("Invalid response: array must have one element.");
  +
  +         state = WRAPPER;
  +         break;
  +
  +     case WRAPPER:
  +         if (t->type != JSMN_OBJECT)
  +             log_die("Invalid response: wrapper must be an object.");
  +
  +         state = OBJECT;
  +         object_tokens = t->size;
  +         break;
  +
  +     case OBJECT:
  +         object_tokens--;
  +
  +         // Keys are odd-numbered tokens within the object
  +         if (object_tokens % 2 == 1) {
  +             if (t->type == JSMN_STRING
  +                 && json_token_streq(js, t, "trends"))
  +                 state = TRENDS;
  +         } else if (t->type == JSMN_ARRAY || t->type == JSMN_OBJECT) {
  +             state = SKIP;
  +             stack = OBJECT;
  +             skip_tokens = t->size;
  +         }
  +         // Last object value
  +         if (object_tokens == 0)
  +             state = STOP;
  +
  +         break;
  +
  +     case SKIP:
  +         skip_tokens--;
  +
  +         if (t->type == JSMN_ARRAY || t->type == JSMN_OBJECT)
  +             skip_tokens += t->size;
  +
  +         if (skip_tokens == 0)
  +             state = stack;
  +
  +         break;
  +
  +     case TRENDS:
  +         if (t->type != JSMN_ARRAY)
  +             log_die("Unknown trends value: expected array.");
  +
  +         trends = t->size;
  +         state = ARRAY;
  +         stack = ARRAY;
  +
  +         // No trends found
  +         if (trends == 0)
  +             state = STOP;
  +
  +         break;
  +
  +     case ARRAY:
  +         trends--;
  +
  +         trend_tokens = t->size;
  +         state = TREND;
  +
  +         // Empty trend object
  +         if (trend_tokens == 0)
  +             state = STOP;
  +
  +         // Last trend object
  +         if (trends == 0)
  +             stack = STOP;
  +
  +         break;
  +
  +     case TREND:
  +     case NAME:
  +         trend_tokens--;
  +
  +         // Keys are odd-numbered tokens within the object
  +         if (trend_tokens % 2 == 1) {
  +             if (t->type == JSMN_STRING
  +                 && json_token_streq(js, t, "name"))
  +                 state = NAME;
  +         }
  +         // Only care about values in the NAME state
  +         else if (state == NAME) {
  +             if (t->type != JSMN_STRING)
  +                 log_die("Invalid trend name.");
  +
  +             char *str = json_token_tostr(js, t);
  +             puts(str);
  +
  +             state = TREND;
  +         } else if (t->type == JSMN_ARRAY || t->type == JSMN_OBJECT) {
  +             state = SKIP;
  +             stack = TREND;
  +             skip_tokens = t->size;
  +         }
  +         // Last object value
  +         if (trend_tokens == 0)
  +             state = stack;
  +
  +         break;
  +
  +     case STOP:
  +         // Just consume the tokens
  +         break;
  +
  +     default:
  +         log_die("Invalid state %u", state);
  +     }
  +    }
  +
  +    return 0;
  +}
  +#endif       /* JSMN_TWITTER */
  +
  +/*==============================================================*/
  +/* --- github.c */
  +
  +#if defined(JSMN_GITHUB)
  +
  +char URL[] = "https://api.github.com/users/alisdair";;
  +char *KEYS[] = { "name", "location", "public_repos", "hireable" };
  +
  +int main(void)
  +{
  +    char *js = json_fetch(URL);
  +
  +    jsmntok_t *tokens = json_tokenise(js);
  +
  +    /* The GitHub user API response is a single object. States required to
  +     * parse this are simple: start of the object, keys, values we want to
  +     * print, values we want to skip, and then a marker state for the end. */
  +
  +    typedef enum { START, KEY, PRINT, SKIP, STOP } parse_state;
  +    parse_state state = START;
  +
  +    size_t object_tokens = 0;
  +
  +    for (size_t i = 0, j = 1; j > 0; i++, j--) {
  +     jsmntok_t *t = &tokens[i];
  +
  +     // Should never reach uninitialized tokens
  +     log_assert(t->start != -1 && t->end != -1);
  +
  +     if (t->type == JSMN_ARRAY || t->type == JSMN_OBJECT)
  +         j += 2 * t->size;
  +
  +     switch (state) {
  +     case START:
  +         if (t->type != JSMN_OBJECT)
  +             log_die
  +                 ("Invalid response: root element must be an object.");
  +
  +         state = KEY;
  +         object_tokens = 2 * t->size;
  +
  +         if (object_tokens == 0)
  +             state = STOP;
  +
  +         if (object_tokens % 2 != 0)
  +             log_die
  +                 ("Invalid response: object must have even number of 
children.");
  +
  +         break;
  +
  +     case KEY:
  +         object_tokens--;
  +
  +         if (t->type != JSMN_STRING)
  +             log_die("Invalid response: object keys must be strings.");
  +
  +         state = SKIP;
  +
  +         for (size_t i = 0; i < sizeof(KEYS) / sizeof(char *); i++) {
  +             if (json_token_streq(js, t, KEYS[i])) {
  +                 printf("%s: ", KEYS[i]);
  +                 state = PRINT;
  +                 break;
  +             }
  +         }
  +
  +         break;
  +
  +     case SKIP:
  +         if (t->type != JSMN_STRING && t->type != JSMN_PRIMITIVE)
  +             log_die
  +                 ("Invalid response: object values must be strings or 
primitives.");
  +
  +         object_tokens--;
  +         state = KEY;
  +
  +         if (object_tokens == 0)
  +             state = STOP;
  +
  +         break;
  +
  +     case PRINT:
  +         if (t->type != JSMN_STRING && t->type != JSMN_PRIMITIVE)
  +             log_die
  +                 ("Invalid response: object values must be strings or 
primitives.");
  +
  +         char *str = json_token_tostr(js, t);
  +         puts(str);
  +
  +         object_tokens--;
  +         state = KEY;
  +
  +         if (object_tokens == 0)
  +             state = STOP;
  +
  +         break;
  +
  +     case STOP:
  +         // Just consume the tokens
  +         break;
  +
  +     default:
  +         log_die("Invalid state %u", state);
  +     }
  +    }
  +
  +    return 0;
  +}
  +#endif       /* JSMN_GITHUB */
  @@ .
______________________________________________________________________
RPM Package Manager                                    http://rpm5.org
CVS Sources Repository                                rpm-cvs@rpm5.org

Reply via email to