tutorial.rst 8.68 KB
Newer Older
1 2 3 4 5 6 7 8 9
.. _tutorial:

********
Tutorial
********

.. highlight:: c

In this tutorial, we create a program that fetches the latest commits
10 11
of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
the result can be parsed using Jansson.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

To stick to the the scope of this tutorial, we will only cover the the
parts of the program related to handling JSON data. For the best user
experience, the full source code is available:
:download:`github_commits.c`. To compile it (on Unix-like systems with
gcc), use the following command::

    gcc -o github_commits github_commits.c -ljansson -lcurl

libcurl_ is used to communicate over the web, so it is required to
compile the program.

The command line syntax is::

    github_commits USER REPOSITORY

``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
name. Please note that the GitHub API is rate limited, so if you run
the program too many times within a short period of time, the sever
starts to respond with an error.

33 34
.. _GitHub: https://github.com/
.. _GitHub API: http://developer.github.com/
35 36 37 38 39
.. _libcurl: http://curl.haxx.se/


.. _tutorial-github-commits-api:

40 41
The GitHub Repo Commits API
===========================
42

43 44 45 46
The `GitHub Repo Commits API`_ is used by sending HTTP requests to
URLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
where ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
of the repository whose commits are to be listed, respectively.
47

48
GitHub responds with a JSON array of the following form:
49 50 51

.. code-block:: none

52 53 54 55
    [
        {
            "sha": "<the commit ID>",
            "commit": {
56
                "message": "<the commit message>",
57
                <more fields, not important to this tutorial...>
58
            },
59 60 61 62 63
            <more fields...>
        },
        {
            "sha": "<the commit ID>",
            "commit": {
64
                "message": "<the commit message>",
65
                <more fields...>
66
            },
67 68 69 70
            <more fields...>
        },
        <more commits...>
    ]
71 72 73 74 75 76 77 78 79 80 81 82 83

In our program, the HTTP request is sent using the following
function::

    static char *request(const char *url);

It takes the URL as a parameter, preforms a HTTP GET request, and
returns a newly allocated string that contains the response body. If
the request fails, an error message is printed to stderr and the
return value is *NULL*. For full details, refer to :download:`the code
<github_commits.c>`, as the actual implementation is not important
here.

84
.. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
85 86 87 88 89 90 91 92 93 94 95 96 97 98

.. _tutorial-the-program:

The Program
===========

First the includes::

    #include <string.h>
    #include <jansson.h>

Like all the programs using Jansson, we need to include
:file:`jansson.h`.

99 100
The following definitions are used to build the GitHub API request
URL::
101

102
   #define URL_FORMAT   "https://api.github.com/repos/%s/%s/commits"
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
   #define URL_SIZE     256

The following function is used when formatting the result to find the
first newline in the commit message::

    /* Return the offset of the first newline in text or the length of
       text if there's no newline */
    static int newline_offset(const char *text)
    {
        const char *newline = strchr(text, '\n');
        if(!newline)
            return strlen(text);
        else
            return (int)(newline - text);
    }

The main function follows. In the beginning, we first declare a bunch
of variables and check the command line parameters::

122 123 124 125 126
    int main(int argc, char *argv[])
    {
        size_t i;
        char *text;
        char url[URL_SIZE];
127

128 129
        json_t *root;
        json_error_t error;
130

131 132 133 134 135 136
        if(argc != 3)
        {
            fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
            fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
            return 2;
        }
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

Then we build the request URL using the user and repository names
given as command line parameters::

    snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);

This uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
Now we're ready to actually request the JSON data over the web::

    text = request(url);
    if(!text)
        return 1;

If an error occurs, our function ``request`` prints the error and
returns *NULL*, so it's enough to just return 1 from the main
function.

154
Next we'll call :func:`json_loads()` to decode the JSON text we got
155 156
as a response::

157
    root = json_loads(text, 0, &error);
158 159 160 161 162 163 164 165 166
    free(text);

    if(!root)
    {
        fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
        return 1;
    }

We don't need the JSON text anymore, so we can free the ``text``
167 168
variable right after decoding it. If :func:`json_loads()` fails, it
returns *NULL* and sets error information to the :type:`json_error_t`
169 170 171 172 173 174 175
structure given as the second parameter. In this case, our program
prints the error information out and returns 1 from the main function.

Now we're ready to extract the data out of the decoded JSON response.
The structure of the response JSON was explained in section
:ref:`tutorial-github-commits-api`.

176
We check that the returned value really is an array::
177

178
    if(!json_is_array(root))
179
    {
180
        fprintf(stderr, "error: root is not an array\n");
181
        json_decref(root);
182 183 184 185 186
        return 1;
    }

Then we proceed to loop over all the commits in the array::

187
    for(i = 0; i < json_array_size(root); i++)
188
    {
189
        json_t *data, *sha, *commit, *message;
190 191
        const char *message_text;

192 193
        data = json_array_get(root, i);
        if(!json_is_object(data))
194
        {
195
            fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
196
            json_decref(root);
197 198 199 200
            return 1;
        }
    ...

201
The function :func:`json_array_size()` returns the size of a JSON
202
array. First, we again declare some variables and then extract the
203
i'th element of the ``root`` array using :func:`json_array_get()`.
204 205
We also check that the resulting value is a JSON object.

206 207 208 209
Next we'll extract the commit ID (a hexadecimal SHA-1 sum),
intermediate commit info object, and the commit message from that
object. We also do proper type checks::

210
        sha = json_object_get(data, "sha");
211 212 213
        if(!json_is_string(sha))
        {
            fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
214
            json_decref(root);
215 216
            return 1;
        }
217

218 219
        commit = json_object_get(data, "commit");
        if(!json_is_object(commit))
220
        {
221
            fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
222
            json_decref(root);
223 224 225 226 227 228 229
            return 1;
        }

        message = json_object_get(commit, "message");
        if(!json_is_string(message))
        {
            fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
230
            json_decref(root);
231 232 233 234 235 236
            return 1;
        }
    ...

And finally, we'll print the first 8 characters of the commit ID and
the first line of the commit message. A C-style string is extracted
237
from a JSON string using :func:`json_string_value()`::
238 239 240 241 242 243 244 245 246

        message_text = json_string_value(message);
        printf("%.8s %.*s\n",
               json_string_value(id),
               newline_offset(message_text),
               message_text);
    }

After sending the HTTP request, we decoded the JSON text using
247
:func:`json_loads()`, remember? It returns a *new reference* to the
248
JSON value it decodes. When we're finished with the value, we'll need
249
to decrease the reference count using :func:`json_decref()`. This way
250 251 252 253 254 255 256 257 258 259 260 261
Jansson can release the resources::

    json_decref(root);
    return 0;

For a detailed explanation of reference counting in Jansson, see
:ref:`apiref-reference-count` in :ref:`apiref`.

The program's ready, let's test it and view the latest commits in
Jansson's repository::

    $ ./github_commits akheron jansson
262 263 264 265 266 267 268 269 270 271 272 273
    1581f26a Merge branch '2.3'
    aabfd493 load: Change buffer_pos to be a size_t
    bd72efbd load: Avoid unexpected behaviour in macro expansion
    e8fd3e30 Document and tweak json_load_callback()
    873eddaf Merge pull request #60 from rogerz/contrib
    bd2c0c73 Ignore the binary test_load_callback
    17a51a4b Merge branch '2.3'
    09c39adc Add json_load_callback to the list of exported symbols
    cbb80baf Merge pull request #57 from rogerz/contrib
    040bd7b0 Add json_load_callback()
    2637faa4 Make test stripping locale independent
    <...>
274 275 276 277 278 279


Conclusion
==========

In this tutorial, we implemented a program that fetches the latest
280 281 282
commits of a GitHub repository using the GitHub Repo Commits API.
Jansson was used to decode the JSON response and to extract the commit
data.
283 284 285 286

This tutorial only covered a small part of Jansson. For example, we
did not create or manipulate JSON values at all. Proceed to
:ref:`apiref` to explore all features of Jansson.