doc: Add tutorial
This commit is contained in:
parent
86dc1d629b
commit
a00988f663
@ -1,3 +1,5 @@
|
|||||||
|
.. _apiref:
|
||||||
|
|
||||||
*************
|
*************
|
||||||
API Reference
|
API Reference
|
||||||
*************
|
*************
|
||||||
@ -113,6 +115,8 @@ functions:
|
|||||||
and false for values of other types and for *NULL*.
|
and false for values of other types and for *NULL*.
|
||||||
|
|
||||||
|
|
||||||
|
.. _apiref-reference-count:
|
||||||
|
|
||||||
Reference Count
|
Reference Count
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
164
doc/github_commits.c
Normal file
164
doc/github_commits.c
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <jansson.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#define BUFFER_SIZE (256 * 1024) /* 256 KB */
|
||||||
|
|
||||||
|
#define URL_FORMAT "http://github.com/api/v2/json/commits/list/%s/%s/master"
|
||||||
|
#define URL_SIZE 256
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct write_result
|
||||||
|
{
|
||||||
|
char *data;
|
||||||
|
int pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream)
|
||||||
|
{
|
||||||
|
struct write_result *result = (struct write_result *)stream;
|
||||||
|
|
||||||
|
if(result->pos + size * nmemb >= BUFFER_SIZE - 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: too small buffer\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(result->data + result->pos, ptr, size * nmemb);
|
||||||
|
result->pos += size * nmemb;
|
||||||
|
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *request(const char *url)
|
||||||
|
{
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode status;
|
||||||
|
char *data;
|
||||||
|
long code;
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
data = malloc(BUFFER_SIZE);
|
||||||
|
if(!curl || !data)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct write_result write_result = {
|
||||||
|
.data = data,
|
||||||
|
.pos = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result);
|
||||||
|
|
||||||
|
status = curl_easy_perform(curl);
|
||||||
|
if(status != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: unable to request data from %s:\n", url);
|
||||||
|
fprintf(stderr, "%s\n", curl_easy_strerror(status));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||||
|
if(code != 200)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: server responded with code %ld\n", code);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
curl_global_cleanup();
|
||||||
|
|
||||||
|
/* zero-terminate the result */
|
||||||
|
data[write_result.pos] = '\0';
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
char *text;
|
||||||
|
char url[URL_SIZE];
|
||||||
|
|
||||||
|
json_t *root;
|
||||||
|
json_error_t error;
|
||||||
|
json_t *commits;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
|
||||||
|
|
||||||
|
text = request(url);
|
||||||
|
if(!text)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
root = json_loads(text, &error);
|
||||||
|
free(text);
|
||||||
|
|
||||||
|
if(!root)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
commits = json_object_get(root, "commits");
|
||||||
|
if(!commits || !json_is_array(commits))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commits is not an array\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < json_array_size(commits); i++)
|
||||||
|
{
|
||||||
|
json_t *commit, *id, *message;
|
||||||
|
const char *message_text;
|
||||||
|
|
||||||
|
commit = json_array_get(commits, i);
|
||||||
|
if(!json_is_object(commit))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d is not an object\n", i + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = json_object_get(commit, "id");
|
||||||
|
if(!id || !json_is_string(id))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d: id is not a string\n", i + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = json_object_get(commit, "message");
|
||||||
|
if(!message || !json_is_string(message))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message_text = json_string_value(message);
|
||||||
|
printf("%.8s %.*s\n",
|
||||||
|
json_string_value(id),
|
||||||
|
newline_offset(message_text),
|
||||||
|
message_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_decref(root);
|
||||||
|
return 0;
|
||||||
|
}
|
@ -11,6 +11,7 @@ This is the documentation for Jansson_ |release|, last updated |today|.
|
|||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
gettingstarted
|
gettingstarted
|
||||||
|
tutorial
|
||||||
apiref
|
apiref
|
||||||
|
|
||||||
|
|
||||||
|
270
doc/tutorial.rst
Normal file
270
doc/tutorial.rst
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
.. _tutorial:
|
||||||
|
|
||||||
|
********
|
||||||
|
Tutorial
|
||||||
|
********
|
||||||
|
|
||||||
|
.. highlight:: c
|
||||||
|
|
||||||
|
In this tutorial, we create a program that fetches the latest commits
|
||||||
|
of a repository in GitHub_ over the web. One of the response formats
|
||||||
|
supported by `GitHub API`_ is 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: http://github.com/
|
||||||
|
.. _GitHub API: http://develop.github.com/
|
||||||
|
.. _libcurl: http://curl.haxx.se/
|
||||||
|
|
||||||
|
|
||||||
|
.. _tutorial-github-commits-api:
|
||||||
|
|
||||||
|
The GitHub Commits API
|
||||||
|
======================
|
||||||
|
|
||||||
|
The GitHub commits API is used by sending HTTP requests to URLs
|
||||||
|
starting with ``http://github.com/api/v2/json/commits/``. Our program
|
||||||
|
only lists the latest commits, so the rest of the URL is
|
||||||
|
``list/USER/REPOSITORY/BRANCH``, where ``USER``, ``REPOSITORY`` and
|
||||||
|
``BRANCH`` are the GitHub user ID, the name of the repository, and the
|
||||||
|
name of the branch whose commits are to be listed, respectively. The
|
||||||
|
following definitions are used to build the request URL::
|
||||||
|
|
||||||
|
#define URL_FORMAT "http://github.com/api/v2/json/commits/list/%s/%s/master"
|
||||||
|
#define URL_SIZE 256
|
||||||
|
|
||||||
|
GitHub responds with a JSON object of the following form:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
{
|
||||||
|
"commits": [
|
||||||
|
{
|
||||||
|
"id": "<the commit ID>",
|
||||||
|
"message": "<the commit message>",
|
||||||
|
<more fields, not important to this tutorial>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "<the commit ID>",
|
||||||
|
"message": "<the commit message>",
|
||||||
|
<more fields, not important to this tutorial>
|
||||||
|
},
|
||||||
|
<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. For
|
||||||
|
full details, refer to :download:`the code <github_commits.c>`, as the
|
||||||
|
actual implementation is not important here.
|
||||||
|
|
||||||
|
|
||||||
|
.. _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 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::
|
||||||
|
|
||||||
|
unsigned int i;
|
||||||
|
char *text;
|
||||||
|
char url[URL_SIZE];
|
||||||
|
|
||||||
|
json_t *root;
|
||||||
|
json_error_t error;
|
||||||
|
json_t *commits;
|
||||||
|
|
||||||
|
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 :cfunc:`json_loads()` to decode the JSON text we got
|
||||||
|
as a response::
|
||||||
|
|
||||||
|
root = json_loads(text, &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 :cfunc:`json_loads()` fails, it
|
||||||
|
returns *NULL* and sets error information to the :ctype:`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.
|
||||||
|
This check is really only to be sure, because we can assume that the
|
||||||
|
GitHub API returns correct JSON to us.
|
||||||
|
|
||||||
|
Next, we'll extract the ``commits`` array from the JSON response::
|
||||||
|
|
||||||
|
commits = json_object_get(root, "commits");
|
||||||
|
if(!commits || !json_is_array(commits))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commits is not an array\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
This is the array that contains objects describing latest commits in
|
||||||
|
the repository. If the key ``commits`` doesn't exist,
|
||||||
|
:cfunc:`json_object_get()` returns *NULL*. We also check that the
|
||||||
|
returned value really is an array.
|
||||||
|
|
||||||
|
Then we proceed to loop over all the commits in the array::
|
||||||
|
|
||||||
|
for(i = 0; i < json_array_size(commits); i++)
|
||||||
|
{
|
||||||
|
json_t *commit, *id, *message;
|
||||||
|
const char *message_text;
|
||||||
|
|
||||||
|
commit = json_array_get(commits, i);
|
||||||
|
if(!json_is_object(commit))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d is not an object\n", i + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
The function :cfunc:`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 ``commits`` array using :cfunc:`json_array_get()`.
|
||||||
|
We also check that the resulting value is a JSON object. (The
|
||||||
|
structure of the response JSON was explained in
|
||||||
|
:ref:`tutorial-github-commits-api`).
|
||||||
|
|
||||||
|
Next we'll extract the commit ID and commit message, and check that
|
||||||
|
they both are JSON strings::
|
||||||
|
|
||||||
|
id = json_object_get(commit, "id");
|
||||||
|
if(!id || !json_is_string(id))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d: id is not a string\n", i + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = json_object_get(commit, "message");
|
||||||
|
if(!message || !json_is_string(message))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
|
||||||
|
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 :cfunc:`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
|
||||||
|
:cfunc:`json_loads()`, remember? It returns a *new reference* to a
|
||||||
|
JSON value it decodes. When we're finished with the value, we'll need
|
||||||
|
to decrease the reference count using :cfunc:`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
|
||||||
|
86dc1d62 Fix indentation
|
||||||
|
b67e130f json_dumpf: Document the output shortage on error
|
||||||
|
4cd77771 Enhance handling of circular references
|
||||||
|
79009e62 json_dumps: Close the strbuffer if dumping fails
|
||||||
|
76999799 doc: Fix a small typo in apiref
|
||||||
|
22af193a doc/Makefile.am: Remove *.pyc in clean
|
||||||
|
951d091f Make integer, real and string mutable
|
||||||
|
185e107d Don't use non-portable asprintf()
|
||||||
|
ca7703fb Merge branch '1.0'
|
||||||
|
12cd4e8c jansson 1.0.4
|
||||||
|
<etc...>
|
||||||
|
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
==========
|
||||||
|
|
||||||
|
In this tutorial, we implemented a program that fetches the latest
|
||||||
|
commits of a GitHub repository using the GitHub 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.
|
Loading…
Reference in New Issue
Block a user