4139N/A.. _tutorial:
4139N/A
4139N/A********
4139N/ATutorial
4139N/A********
4139N/A
4139N/A.. highlight:: c
4139N/A
4139N/AIn this tutorial, we create a program that fetches the latest commits
4139N/Aof a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
4139N/Athe result can be parsed using Jansson.
4139N/A
4139N/ATo stick to the the scope of this tutorial, we will only cover the the
4139N/Aparts of the program related to handling JSON data. For the best user
4139N/Aexperience, the full source code is available:
4139N/A:download:`github_commits.c`. To compile it (on Unix-like systems with
4139N/Agcc), use the following command::
4139N/A
4139N/A gcc -o github_commits github_commits.c -ljansson -lcurl
4139N/A
4139N/Alibcurl_ is used to communicate over the web, so it is required to
4139N/Acompile the program.
4139N/A
4139N/AThe command line syntax is::
4139N/A
4139N/A github_commits USER REPOSITORY
4139N/A
4139N/A``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
4139N/Aname. Please note that the GitHub API is rate limited, so if you run
4139N/Athe program too many times within a short period of time, the sever
4139N/Astarts to respond with an error.
4139N/A
4139N/A.. _GitHub: https://github.com/
4139N/A.. _GitHub API: http://developer.github.com/
4139N/A.. _libcurl: http://curl.haxx.se/
4139N/A
4139N/A
4139N/A.. _tutorial-github-commits-api:
4139N/A
4139N/AThe GitHub Repo Commits API
4139N/A===========================
4139N/A
4139N/AThe `GitHub Repo Commits API`_ is used by sending HTTP requests to
4139N/AURLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
4139N/Awhere ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
4139N/Aof the repository whose commits are to be listed, respectively.
4139N/A
4139N/AGitHub responds with a JSON array of the following form:
4139N/A
4139N/A.. code-block:: none
4139N/A
4139N/A [
4139N/A {
4139N/A "sha": "<the commit ID>",
4139N/A "commit": {
4139N/A "message": "<the commit message>",
4139N/A <more fields, not important to this tutorial...>
4139N/A },
4139N/A <more fields...>
4139N/A },
4139N/A {
4139N/A "sha": "<the commit ID>",
4139N/A "commit": {
4139N/A "message": "<the commit message>",
4139N/A <more fields...>
4139N/A },
4139N/A <more fields...>
4139N/A },
4139N/A <more commits...>
4139N/A ]
4139N/A
4139N/AIn our program, the HTTP request is sent using the following
4139N/Afunction::
4139N/A
4139N/A static char *request(const char *url);
4139N/A
4139N/AIt takes the URL as a parameter, preforms a HTTP GET request, and
4139N/Areturns a newly allocated string that contains the response body. If
4139N/Athe request fails, an error message is printed to stderr and the
4139N/Areturn value is *NULL*. For full details, refer to :download:`the code
4139N/A<github_commits.c>`, as the actual implementation is not important
4139N/Ahere.
4139N/A
4139N/A.. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
4139N/A
4139N/A.. _tutorial-the-program:
4139N/A
4139N/AThe Program
4139N/A===========
4139N/A
4139N/AFirst the includes::
4139N/A
4139N/A #include <string.h>
4139N/A #include <jansson.h>
4139N/A
4139N/ALike all the programs using Jansson, we need to include
4139N/A:file:`jansson.h`.
4139N/A
4139N/AThe following definitions are used to build the GitHub API request
4139N/AURL::
4139N/A
4139N/A #define URL_FORMAT "https://api.github.com/repos/%s/%s/commits"
4139N/A #define URL_SIZE 256
4139N/A
4139N/AThe following function is used when formatting the result to find the
4139N/Afirst newline in the commit message::
4139N/A
4139N/A /* Return the offset of the first newline in text or the length of
4139N/A text if there's no newline */
4139N/A static int newline_offset(const char *text)
4139N/A {
4139N/A const char *newline = strchr(text, '\n');
4139N/A if(!newline)
4139N/A return strlen(text);
4139N/A else
4139N/A return (int)(newline - text);
4139N/A }
4139N/A
4139N/AThe main function follows. In the beginning, we first declare a bunch
4139N/Aof variables and check the command line parameters::
4139N/A
4139N/A int main(int argc, char *argv[])
4139N/A {
4139N/A size_t i;
4139N/A char *text;
4139N/A char url[URL_SIZE];
4139N/A
4139N/A json_t *root;
4139N/A json_error_t error;
4139N/A
4139N/A if(argc != 3)
4139N/A {
4139N/A fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
4139N/A fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
4139N/A return 2;
4139N/A }
4139N/A
4139N/AThen we build the request URL using the user and repository names
4139N/Agiven as command line parameters::
4139N/A
4139N/A snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
4139N/A
4139N/AThis uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
4139N/ANow we're ready to actually request the JSON data over the web::
4139N/A
4139N/A text = request(url);
4139N/A if(!text)
4139N/A return 1;
4139N/A
4139N/AIf an error occurs, our function ``request`` prints the error and
4139N/Areturns *NULL*, so it's enough to just return 1 from the main
4139N/Afunction.
4139N/A
4139N/ANext we'll call :func:`json_loads()` to decode the JSON text we got
4139N/Aas a response::
4139N/A
4139N/A root = json_loads(text, 0, &error);
4139N/A free(text);
4139N/A
4139N/A if(!root)
4139N/A {
4139N/A fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
4139N/A return 1;
4139N/A }
4139N/A
4139N/AWe don't need the JSON text anymore, so we can free the ``text``
4139N/Avariable right after decoding it. If :func:`json_loads()` fails, it
4139N/Areturns *NULL* and sets error information to the :type:`json_error_t`
4139N/Astructure given as the second parameter. In this case, our program
4139N/Aprints the error information out and returns 1 from the main function.
4139N/A
4139N/ANow we're ready to extract the data out of the decoded JSON response.
4139N/AThe structure of the response JSON was explained in section
4139N/A:ref:`tutorial-github-commits-api`.
4139N/A
4139N/AWe check that the returned value really is an array::
4139N/A
4139N/A if(!json_is_array(root))
4139N/A {
4139N/A fprintf(stderr, "error: root is not an array\n");
4139N/A json_decref(root);
4139N/A return 1;
4139N/A }
4139N/A
4139N/AThen we proceed to loop over all the commits in the array::
4139N/A
4139N/A for(i = 0; i < json_array_size(root); i++)
4139N/A {
4139N/A json_t *data, *sha, *commit, *message;
4139N/A const char *message_text;
4139N/A
4139N/A data = json_array_get(root, i);
4139N/A if(!json_is_object(data))
4139N/A {
4139N/A fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
4139N/A json_decref(root);
4139N/A return 1;
4139N/A }
4139N/A ...
4139N/A
4139N/AThe function :func:`json_array_size()` returns the size of a JSON
4139N/Aarray. First, we again declare some variables and then extract the
4139N/Ai'th element of the ``root`` array using :func:`json_array_get()`.
4139N/AWe also check that the resulting value is a JSON object.
4139N/A
4139N/ANext we'll extract the commit ID (a hexadecimal SHA-1 sum),
4139N/Aintermediate commit info object, and the commit message from that
4139N/Aobject. We also do proper type checks::
4139N/A
4139N/A sha = json_object_get(data, "sha");
4139N/A if(!json_is_string(sha))
4139N/A {
4139N/A fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
4139N/A json_decref(root);
4139N/A return 1;
4139N/A }
4139N/A
4139N/A commit = json_object_get(data, "commit");
4139N/A if(!json_is_object(commit))
4139N/A {
4139N/A fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
4139N/A json_decref(root);
4139N/A return 1;
4139N/A }
4139N/A
4139N/A message = json_object_get(commit, "message");
4139N/A if(!json_is_string(message))
4139N/A {
4139N/A fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
4139N/A json_decref(root);
4139N/A return 1;
4139N/A }
4139N/A ...
4139N/A
4139N/AAnd finally, we'll print the first 8 characters of the commit ID and
4139N/Athe first line of the commit message. A C-style string is extracted
4139N/Afrom a JSON string using :func:`json_string_value()`::
4139N/A
4139N/A message_text = json_string_value(message);
4139N/A printf("%.8s %.*s\n",
4139N/A json_string_value(id),
4139N/A newline_offset(message_text),
4139N/A message_text);
4139N/A }
4139N/A
4139N/AAfter sending the HTTP request, we decoded the JSON text using
4139N/A:func:`json_loads()`, remember? It returns a *new reference* to the
4139N/AJSON value it decodes. When we're finished with the value, we'll need
4139N/Ato decrease the reference count using :func:`json_decref()`. This way
4139N/AJansson can release the resources::
4139N/A
4139N/A json_decref(root);
4139N/A return 0;
4139N/A
4139N/AFor a detailed explanation of reference counting in Jansson, see
4139N/A:ref:`apiref-reference-count` in :ref:`apiref`.
4139N/A
4139N/AThe program's ready, let's test it and view the latest commits in
4139N/AJansson's repository::
4139N/A
4139N/A $ ./github_commits akheron jansson
4139N/A 1581f26a Merge branch '2.3'
4139N/A aabfd493 load: Change buffer_pos to be a size_t
4139N/A bd72efbd load: Avoid unexpected behaviour in macro expansion
4139N/A e8fd3e30 Document and tweak json_load_callback()
4139N/A 873eddaf Merge pull request #60 from rogerz/contrib
4139N/A bd2c0c73 Ignore the binary test_load_callback
4139N/A 17a51a4b Merge branch '2.3'
4139N/A 09c39adc Add json_load_callback to the list of exported symbols
4139N/A cbb80baf Merge pull request #57 from rogerz/contrib
4139N/A 040bd7b0 Add json_load_callback()
4139N/A 2637faa4 Make test stripping locale independent
4139N/A <...>
4139N/A
4139N/A
4139N/AConclusion
4139N/A==========
4139N/A
4139N/AIn this tutorial, we implemented a program that fetches the latest
4139N/Acommits of a GitHub repository using the GitHub Repo Commits API.
4139N/AJansson was used to decode the JSON response and to extract the commit
4139N/Adata.
4139N/A
4139N/AThis tutorial only covered a small part of Jansson. For example, we
4139N/Adid not create or manipulate JSON values at all. Proceed to
4139N/A:ref:`apiref` to explore all features of Jansson.