tutorial.rst 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. .. _tutorial:
  2. ********
  3. Tutorial
  4. ********
  5. .. highlight:: c
  6. In this tutorial, we create a program that fetches the latest commits
  7. of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
  8. the result can be parsed using Jansson.
  9. To stick to the the scope of this tutorial, we will only cover the the
  10. parts of the program related to handling JSON data. For the best user
  11. experience, the full source code is available:
  12. :download:`github_commits.c`. To compile it (on Unix-like systems with
  13. gcc), use the following command::
  14. gcc -o github_commits github_commits.c -ljansson -lcurl
  15. libcurl_ is used to communicate over the web, so it is required to
  16. compile the program.
  17. The command line syntax is::
  18. github_commits USER REPOSITORY
  19. ``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
  20. name. Please note that the GitHub API is rate limited, so if you run
  21. the program too many times within a short period of time, the sever
  22. starts to respond with an error.
  23. .. _GitHub: https://github.com/
  24. .. _GitHub API: http://developer.github.com/
  25. .. _libcurl: http://curl.haxx.se/
  26. .. _tutorial-github-commits-api:
  27. The GitHub Repo Commits API
  28. ===========================
  29. The `GitHub Repo Commits API`_ is used by sending HTTP requests to
  30. URLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
  31. where ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
  32. of the repository whose commits are to be listed, respectively.
  33. GitHub responds with a JSON array of the following form:
  34. .. code-block:: none
  35. [
  36. {
  37. "sha": "<the commit ID>",
  38. "commit": {
  39. "message": "<the commit message>",
  40. <more fields, not important to this tutorial...>
  41. },
  42. <more fields...>
  43. },
  44. {
  45. "sha": "<the commit ID>",
  46. "commit": {
  47. "message": "<the commit message>",
  48. <more fields...>
  49. },
  50. <more fields...>
  51. },
  52. <more commits...>
  53. ]
  54. In our program, the HTTP request is sent using the following
  55. function::
  56. static char *request(const char *url);
  57. It takes the URL as a parameter, preforms a HTTP GET request, and
  58. returns a newly allocated string that contains the response body. If
  59. the request fails, an error message is printed to stderr and the
  60. return value is *NULL*. For full details, refer to :download:`the code
  61. <github_commits.c>`, as the actual implementation is not important
  62. here.
  63. .. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
  64. .. _tutorial-the-program:
  65. The Program
  66. ===========
  67. First the includes::
  68. #include <string.h>
  69. #include <jansson.h>
  70. Like all the programs using Jansson, we need to include
  71. :file:`jansson.h`.
  72. The following definitions are used to build the GitHub API request
  73. URL::
  74. #define URL_FORMAT "https://api.github.com/repos/%s/%s/commits"
  75. #define URL_SIZE 256
  76. The following function is used when formatting the result to find the
  77. first newline in the commit message::
  78. /* Return the offset of the first newline in text or the length of
  79. text if there's no newline */
  80. static int newline_offset(const char *text)
  81. {
  82. const char *newline = strchr(text, '\n');
  83. if(!newline)
  84. return strlen(text);
  85. else
  86. return (int)(newline - text);
  87. }
  88. The main function follows. In the beginning, we first declare a bunch
  89. of variables and check the command line parameters::
  90. int main(int argc, char *argv[])
  91. {
  92. size_t i;
  93. char *text;
  94. char url[URL_SIZE];
  95. json_t *root;
  96. json_error_t error;
  97. if(argc != 3)
  98. {
  99. fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
  100. fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
  101. return 2;
  102. }
  103. Then we build the request URL using the user and repository names
  104. given as command line parameters::
  105. snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
  106. This uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
  107. Now we're ready to actually request the JSON data over the web::
  108. text = request(url);
  109. if(!text)
  110. return 1;
  111. If an error occurs, our function ``request`` prints the error and
  112. returns *NULL*, so it's enough to just return 1 from the main
  113. function.
  114. Next we'll call :func:`json_loads()` to decode the JSON text we got
  115. as a response::
  116. root = json_loads(text, 0, &error);
  117. free(text);
  118. if(!root)
  119. {
  120. fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
  121. return 1;
  122. }
  123. We don't need the JSON text anymore, so we can free the ``text``
  124. variable right after decoding it. If :func:`json_loads()` fails, it
  125. returns *NULL* and sets error information to the :type:`json_error_t`
  126. structure given as the second parameter. In this case, our program
  127. prints the error information out and returns 1 from the main function.
  128. Now we're ready to extract the data out of the decoded JSON response.
  129. The structure of the response JSON was explained in section
  130. :ref:`tutorial-github-commits-api`.
  131. We check that the returned value really is an array::
  132. if(!json_is_array(root))
  133. {
  134. fprintf(stderr, "error: root is not an array\n");
  135. json_decref(root);
  136. return 1;
  137. }
  138. Then we proceed to loop over all the commits in the array::
  139. for(i = 0; i < json_array_size(root); i++)
  140. {
  141. json_t *data, *sha, *commit, *message;
  142. const char *message_text;
  143. data = json_array_get(root, i);
  144. if(!json_is_object(data))
  145. {
  146. fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
  147. json_decref(root);
  148. return 1;
  149. }
  150. ...
  151. The function :func:`json_array_size()` returns the size of a JSON
  152. array. First, we again declare some variables and then extract the
  153. i'th element of the ``root`` array using :func:`json_array_get()`.
  154. We also check that the resulting value is a JSON object.
  155. Next we'll extract the commit ID (a hexadecimal SHA-1 sum),
  156. intermediate commit info object, and the commit message from that
  157. object. We also do proper type checks::
  158. sha = json_object_get(data, "sha");
  159. if(!json_is_string(sha))
  160. {
  161. fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
  162. json_decref(root);
  163. return 1;
  164. }
  165. commit = json_object_get(data, "commit");
  166. if(!json_is_object(commit))
  167. {
  168. fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
  169. json_decref(root);
  170. return 1;
  171. }
  172. message = json_object_get(commit, "message");
  173. if(!json_is_string(message))
  174. {
  175. fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
  176. json_decref(root);
  177. return 1;
  178. }
  179. ...
  180. And finally, we'll print the first 8 characters of the commit ID and
  181. the first line of the commit message. A C-style string is extracted
  182. from a JSON string using :func:`json_string_value()`::
  183. message_text = json_string_value(message);
  184. printf("%.8s %.*s\n",
  185. json_string_value(id),
  186. newline_offset(message_text),
  187. message_text);
  188. }
  189. After sending the HTTP request, we decoded the JSON text using
  190. :func:`json_loads()`, remember? It returns a *new reference* to the
  191. JSON value it decodes. When we're finished with the value, we'll need
  192. to decrease the reference count using :func:`json_decref()`. This way
  193. Jansson can release the resources::
  194. json_decref(root);
  195. return 0;
  196. For a detailed explanation of reference counting in Jansson, see
  197. :ref:`apiref-reference-count` in :ref:`apiref`.
  198. The program's ready, let's test it and view the latest commits in
  199. Jansson's repository:
  200. .. code-block:: shell
  201. $ ./github_commits akheron jansson
  202. 1581f26a Merge branch '2.3'
  203. aabfd493 load: Change buffer_pos to be a size_t
  204. bd72efbd load: Avoid unexpected behaviour in macro expansion
  205. e8fd3e30 Document and tweak json_load_callback()
  206. 873eddaf Merge pull request #60 from rogerz/contrib
  207. bd2c0c73 Ignore the binary test_load_callback
  208. 17a51a4b Merge branch '2.3'
  209. 09c39adc Add json_load_callback to the list of exported symbols
  210. cbb80baf Merge pull request #57 from rogerz/contrib
  211. 040bd7b0 Add json_load_callback()
  212. 2637faa4 Make test stripping locale independent
  213. <...>
  214. Conclusion
  215. ==========
  216. In this tutorial, we implemented a program that fetches the latest
  217. commits of a GitHub repository using the GitHub Repo Commits API.
  218. Jansson was used to decode the JSON response and to extract the commit
  219. data.
  220. This tutorial only covered a small part of Jansson. For example, we
  221. did not create or manipulate JSON values at all. Proceed to
  222. :ref:`apiref` to explore all features of Jansson.