| .. _tutorial: |
| |
| ******** |
| Tutorial |
| ******** |
| |
| .. highlight:: c |
| |
| In this tutorial, we create a program that fetches the latest commits |
| of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so |
| the result can be parsed using Jansson. |
| |
| 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. |
| |
| .. _GitHub: https://github.com/ |
| .. _GitHub API: http://developer.github.com/ |
| .. _libcurl: http://curl.haxx.se/ |
| |
| |
| .. _tutorial-github-commits-api: |
| |
| The GitHub Repo Commits API |
| =========================== |
| |
| 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. |
| |
| GitHub responds with a JSON array of the following form: |
| |
| .. code-block:: none |
| |
| [ |
| { |
| "sha": "<the commit ID>", |
| "commit": { |
| "message": "<the commit message>", |
| <more fields, not important to this tutorial...> |
| }, |
| <more fields...> |
| }, |
| { |
| "sha": "<the commit ID>", |
| "commit": { |
| "message": "<the commit message>", |
| <more fields...> |
| }, |
| <more fields...> |
| }, |
| <more commits...> |
| ] |
| |
| 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. |
| |
| .. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/ |
| |
| .. _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`. |
| |
| The following definitions are used to build the GitHub API request |
| URL:: |
| |
| #define URL_FORMAT "https://api.github.com/repos/%s/%s/commits" |
| #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:: |
| |
| int main(int argc, char *argv[]) |
| { |
| size_t i; |
| char *text; |
| char url[URL_SIZE]; |
| |
| json_t *root; |
| json_error_t error; |
| |
| 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; |
| } |
| |
| 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. |
| |
| Next we'll call :func:`json_loads()` to decode the JSON text we got |
| as a response:: |
| |
| root = json_loads(text, 0, &error); |
| 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`` |
| variable right after decoding it. If :func:`json_loads()` fails, it |
| returns *NULL* and sets error information to the :type:`json_error_t` |
| 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`. |
| |
| We check that the returned value really is an array:: |
| |
| if(!json_is_array(root)) |
| { |
| fprintf(stderr, "error: root is not an array\n"); |
| json_decref(root); |
| return 1; |
| } |
| |
| Then we proceed to loop over all the commits in the array:: |
| |
| for(i = 0; i < json_array_size(root); i++) |
| { |
| json_t *data, *sha, *commit, *message; |
| const char *message_text; |
| |
| data = json_array_get(root, i); |
| if(!json_is_object(data)) |
| { |
| fprintf(stderr, "error: commit data %d is not an object\n", i + 1); |
| json_decref(root); |
| return 1; |
| } |
| ... |
| |
| The function :func:`json_array_size()` returns the size of a JSON |
| array. First, we again declare some variables and then extract the |
| i'th element of the ``root`` array using :func:`json_array_get()`. |
| We also check that the resulting value is a JSON object. |
| |
| 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:: |
| |
| sha = json_object_get(data, "sha"); |
| if(!json_is_string(sha)) |
| { |
| fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1); |
| json_decref(root); |
| return 1; |
| } |
| |
| commit = json_object_get(data, "commit"); |
| if(!json_is_object(commit)) |
| { |
| fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1); |
| json_decref(root); |
| 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); |
| json_decref(root); |
| 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 |
| from a JSON string using :func:`json_string_value()`:: |
| |
| 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 |
| :func:`json_loads()`, remember? It returns a *new reference* to the |
| JSON value it decodes. When we're finished with the value, we'll need |
| to decrease the reference count using :func:`json_decref()`. This way |
| 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 |
| 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 |
| <...> |
| |
| |
| Conclusion |
| ========== |
| |
| In this tutorial, we implemented a program that fetches the latest |
| 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. |
| |
| 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. |