commit 194ad32883683c868d3b4b72d364f3cfebd66864
Author: Abdul Rahim <abdul.rahim@myyahoo.com>
Date: Mon, 30 Sep 2024 15:37:27 +0530
init
Diffstat:
A | .get_lang.h.swp | | | 0 | |
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 113 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README | | | 186 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cJSON.c | | | 3164 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cJSON.h | | | 306 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | compat.h | | | 6 | ++++++ |
A | example_create.sh | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
A | example_post-receive.sh | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | exts.json | | | 1578 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | favicon.png | | | 0 | |
A | get_lang.c | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | get_lang.h | | | 1 | + |
A | logo.png | | | 0 | |
A | reallocarray.c | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
A | reallocarray.o | | | 0 | |
A | stagit | | | 0 | |
A | stagit-index | | | 0 | |
A | stagit-index.1 | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
A | stagit-index.c | | | 263 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | stagit-index.o | | | 0 | |
A | stagit.1 | | | 125 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | stagit.c | | | 1487 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | stagit.o | | | 0 | |
A | strlcat.c | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | strlcat.o | | | 0 | |
A | strlcpy.c | | | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | strlcpy.o | | | 0 | |
A | style.css | | | 154 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
29 files changed, 7802 insertions(+), 0 deletions(-)
diff --git a/.get_lang.h.swp b/.get_lang.h.swp
Binary files differ.
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+(c) 2015-2024 Hiltjo Posthuma <hiltjo@codemadness.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,113 @@
+.POSIX:
+
+NAME = stagit
+VERSION = 1.2
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/man
+DOCPREFIX = ${PREFIX}/share/doc/${NAME}
+
+LIBGIT_INC = -I/usr/local/include -I$(PWD)/include
+LIBGIT_LIB = -L/usr/local/lib -lgit2
+
+# use system flags.
+STAGIT_CFLAGS = ${LIBGIT_INC} ${CFLAGS} -lm -Wall
+STAGIT_LDFLAGS = ${LIBGIT_LIB} ${LDFLAGS}
+STAGIT_CPPFLAGS = -D_XOPEN_SOURCE=700 -D_DEFAULT_SOURCE -D_BSD_SOURCE
+
+# Uncomment to enable workaround for older libgit2 which don't support this
+# option. This workaround will be removed in the future *pinky promise*.
+#STAGIT_CFLAGS += -DGIT_OPT_SET_OWNER_VALIDATION=-1
+
+SRC = \
+ stagit.c\
+ stagit-index.c
+COMPATSRC = \
+ reallocarray.c\
+ strlcat.c\
+ strlcpy.c
+BIN = \
+ stagit\
+ stagit-index
+MAN1 = \
+ stagit.1\
+ stagit-index.1
+DOC = \
+ LICENSE\
+ README
+HDR = compat.h cJSON.h
+
+COMPATOBJ = \
+ reallocarray.o\
+ strlcat.o\
+ strlcpy.o
+
+OBJ = ${SRC:.c=.o} ${COMPATOBJ}
+
+all: ${BIN}
+
+.o:
+ ${CC} -o $@ ${LDFLAGS}
+
+.c.o:
+ ${CC} -o $@ -c $< ${STAGIT_CFLAGS} ${STAGIT_CPPFLAGS}
+
+dist:
+ rm -rf ${NAME}-${VERSION}
+ mkdir -p ${NAME}-${VERSION}
+ cp -f ${MAN1} ${HDR} ${SRC} ${COMPATSRC} ${DOC} \
+ Makefile favicon.png logo.png style.css \
+ example_create.sh example_post-receive.sh \
+ ${NAME}-${VERSION}
+ # make tarball
+ tar -cf - ${NAME}-${VERSION} | \
+ gzip -c > ${NAME}-${VERSION}.tar.gz
+ rm -rf ${NAME}-${VERSION}
+
+${OBJ}: ${HDR}
+
+stagit: stagit.o ${COMPATOBJ}
+ ${CC} -o $@ stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
+
+stagit-index: stagit-index.o ${COMPATOBJ}
+ ${CC} -o $@ stagit-index.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
+
+clean:
+ rm -f ${BIN} ${OBJ} ${NAME}-${VERSION}.tar.gz
+
+install: all
+ # installing executable files.
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f ${BIN} ${DESTDIR}${PREFIX}/bin
+ for f in ${BIN}; do chmod 755 ${DESTDIR}${PREFIX}/bin/$$f; done
+ # installing example files.
+ mkdir -p ${DESTDIR}${DOCPREFIX}
+ cp -f style.css\
+ favicon.png\
+ logo.png\
+ example_create.sh\
+ example_post-receive.sh\
+ README\
+ ${DESTDIR}${DOCPREFIX}
+ # installing manual pages.
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ cp -f ${MAN1} ${DESTDIR}${MANPREFIX}/man1
+ for m in ${MAN1}; do chmod 644 ${DESTDIR}${MANPREFIX}/man1/$$m; done
+
+uninstall:
+ # removing executable files.
+ for f in ${BIN}; do rm -f ${DESTDIR}${PREFIX}/bin/$$f; done
+ # removing example files.
+ rm -f \
+ ${DESTDIR}${DOCPREFIX}/style.css\
+ ${DESTDIR}${DOCPREFIX}/favicon.png\
+ ${DESTDIR}${DOCPREFIX}/logo.png\
+ ${DESTDIR}${DOCPREFIX}/example_create.sh\
+ ${DESTDIR}${DOCPREFIX}/example_post-receive.sh\
+ ${DESTDIR}${DOCPREFIX}/README
+ -rmdir ${DESTDIR}${DOCPREFIX}
+ # removing manual pages.
+ for m in ${MAN1}; do rm -f ${DESTDIR}${MANPREFIX}/man1/$$m; done
+
+.PHONY: all clean dist install uninstall
diff --git a/README b/README
@@ -0,0 +1,186 @@
+stagit
+------
+
+static git page generator.
+
+It generates static HTML pages for a git repository.
+
+
+Usage
+-----
+
+Make files per repository:
+
+ $ mkdir -p htmlroot/htmlrepo1 && cd htmlroot/htmlrepo1
+ $ stagit path/to/gitrepo1
+ repeat for other repositories
+ $ ...
+
+Make index file for repositories:
+
+ $ cd htmlroot
+ $ stagit-index path/to/gitrepo1 \
+ path/to/gitrepo2 \
+ path/to/gitrepo3 > index.html
+
+
+Build and install
+-----------------
+
+$ make
+# make install
+
+
+Dependencies
+------------
+
+- C compiler (C99).
+- libc (tested with OpenBSD, FreeBSD, NetBSD, Linux: glibc and musl).
+- libgit2 (v0.22+).
+- POSIX make (optional).
+
+
+Documentation
+-------------
+
+See man pages: stagit(1) and stagit-index(1).
+
+
+Building a static binary
+------------------------
+
+It may be useful to build static binaries, for example to run in a chroot.
+
+It can be done like this at the time of writing (v0.24):
+
+cd libgit2-src
+
+# change the options in the CMake file: CMakeLists.txt
+BUILD_SHARED_LIBS to OFF (static)
+CURL to OFF (not needed)
+USE_SSH OFF (not needed)
+THREADSAFE OFF (not needed)
+USE_OPENSSL OFF (not needed, use builtin)
+
+mkdir -p build && cd build
+cmake ../
+make
+make install
+
+
+Extract owner field from git config
+-----------------------------------
+
+A way to extract the gitweb owner for example in the format:
+
+ [gitweb]
+ owner = Name here
+
+Script:
+
+ #!/bin/sh
+ awk '/^[ ]*owner[ ]=/ {
+ sub(/^[^=]*=[ ]*/, "");
+ print $0;
+ }'
+
+
+Set clone URL for a directory of repos
+--------------------------------------
+ #!/bin/sh
+ cd "$dir"
+ for i in *; do
+ test -d "$i" && echo "git://git.codemadness.org/$i" > "$i/url"
+ done
+
+
+Update files on git push
+------------------------
+
+Using a post-receive hook the static files can be automatically updated.
+Keep in mind git push -f can change the history and the commits may need
+to be recreated. This is because stagit checks if a commit file already
+exists. It also has a cache (-c) option which can conflict with the new
+history. See stagit(1).
+
+git post-receive hook (repo/.git/hooks/post-receive):
+
+ #!/bin/sh
+ # detect git push -f
+ force=0
+ while read -r old new ref; do
+ hasrevs=$(git rev-list "$old" "^$new" | sed 1q)
+ if test -n "$hasrevs"; then
+ force=1
+ break
+ fi
+ done
+
+ # remove commits and .cache on git push -f
+ #if test "$force" = "1"; then
+ # ...
+ #fi
+
+ # see example_create.sh for normal creation of the files.
+
+
+Create .tar.gz archives by tag
+------------------------------
+ #!/bin/sh
+ name="stagit"
+ mkdir -p archives
+ git tag -l | while read -r t; do
+ f="archives/${name}-$(echo "${t}" | tr '/' '_').tar.gz"
+ test -f "${f}" && continue
+ git archive \
+ --format tar.gz \
+ --prefix "${t}/" \
+ -o "${f}" \
+ -- \
+ "${t}"
+ done
+
+
+Features
+--------
+
+- Log of all commits from HEAD.
+- Log and diffstat per commit.
+- Show file tree with linkable line numbers.
+- Show references: local branches and tags.
+- Detect README and LICENSE file from HEAD and link it as a webpage.
+- Detect submodules (.gitmodules file) from HEAD and link it as a webpage.
+- Atom feed of the commit log (atom.xml).
+- Atom feed of the tags/refs (tags.xml).
+- Make index page for multiple repositories with stagit-index.
+- After generating the pages (relatively slow) serving the files is very fast,
+ simple and requires little resources (because the content is static), only
+ a HTTP file server is required.
+- Usable with text-browsers such as dillo, links, lynx and w3m.
+
+
+Cons
+----
+
+- Not suitable for large repositories (2000+ commits), because diffstats are
+ an expensive operation, the cache (-c flag) is a workaround for this in
+ some cases.
+- Not suitable for large repositories with many files, because all files are
+ written for each execution of stagit. This is because stagit shows the lines
+ of textfiles and there is no "cache" for file metadata (this would add more
+ complexity to the code).
+- Not suitable for repositories with many branches, a quite linear history is
+ assumed (from HEAD).
+
+ In these cases it is better to just use cgit or possibly change stagit to
+ run as a CGI program.
+
+- Relatively slow to run the first time (about 3 seconds for sbase,
+ 1500+ commits), incremental updates are faster.
+- Does not support some of the dynamic features cgit has, like:
+ - Snapshot tarballs per commit.
+ - File tree per commit.
+ - History log of branches diverged from HEAD.
+ - Stats (git shortlog -s).
+
+ This is by design, just use git locally.
diff --git a/cJSON.c b/cJSON.c
@@ -0,0 +1,3164 @@
+/*
+ Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+/* cJSON */
+/* JSON parser in C. */
+
+/* disable warnings about old C89 functions in MSVC */
+#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER)
+#define _CRT_SECURE_NO_DEPRECATE
+#endif
+
+#ifdef __GNUC__
+#pragma GCC visibility push(default)
+#endif
+#if defined(_MSC_VER)
+#pragma warning (push)
+/* disable warning about single line comments in system headers */
+#pragma warning (disable : 4001)
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+#include <float.h>
+
+#ifdef ENABLE_LOCALES
+#include <locale.h>
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning (pop)
+#endif
+#ifdef __GNUC__
+#pragma GCC visibility pop
+#endif
+
+#include "cJSON.h"
+
+/* define our own boolean type */
+#ifdef true
+#undef true
+#endif
+#define true ((cJSON_bool)1)
+
+#ifdef false
+#undef false
+#endif
+#define false ((cJSON_bool)0)
+
+/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */
+#ifndef isinf
+#define isinf(d) (isnan((d - d)) && !isnan(d))
+#endif
+#ifndef isnan
+#define isnan(d) (d != d)
+#endif
+
+#ifndef NAN
+#ifdef _WIN32
+#define NAN sqrt(-1.0)
+#else
+#define NAN 0.0/0.0
+#endif
+#endif
+
+typedef struct {
+ const unsigned char *json;
+ size_t position;
+} error;
+static error global_error = { NULL, 0 };
+
+CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)
+{
+ return (const char*) (global_error.json + global_error.position);
+}
+
+CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)
+{
+ if (!cJSON_IsString(item))
+ {
+ return NULL;
+ }
+
+ return item->valuestring;
+}
+
+CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)
+{
+ if (!cJSON_IsNumber(item))
+ {
+ return (double) NAN;
+ }
+
+ return item->valuedouble;
+}
+
+/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */
+#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18)
+ #error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
+#endif
+
+CJSON_PUBLIC(const char*) cJSON_Version(void)
+{
+ static char version[15];
+ sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
+
+ return version;
+}
+
+/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */
+static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2)
+{
+ if ((string1 == NULL) || (string2 == NULL))
+ {
+ return 1;
+ }
+
+ if (string1 == string2)
+ {
+ return 0;
+ }
+
+ for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++)
+ {
+ if (*string1 == '\0')
+ {
+ return 0;
+ }
+ }
+
+ return tolower(*string1) - tolower(*string2);
+}
+
+typedef struct internal_hooks
+{
+ void *(CJSON_CDECL *allocate)(size_t size);
+ void (CJSON_CDECL *deallocate)(void *pointer);
+ void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
+} internal_hooks;
+
+#if defined(_MSC_VER)
+/* work around MSVC error C2322: '...' address of dllimport '...' is not static */
+static void * CJSON_CDECL internal_malloc(size_t size)
+{
+ return malloc(size);
+}
+static void CJSON_CDECL internal_free(void *pointer)
+{
+ free(pointer);
+}
+static void * CJSON_CDECL internal_realloc(void *pointer, size_t size)
+{
+ return realloc(pointer, size);
+}
+#else
+#define internal_malloc malloc
+#define internal_free free
+#define internal_realloc realloc
+#endif
+
+/* strlen of character literals resolved at compile time */
+#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(""))
+
+static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };
+
+static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
+{
+ size_t length = 0;
+ unsigned char *copy = NULL;
+
+ if (string == NULL)
+ {
+ return NULL;
+ }
+
+ length = strlen((const char*)string) + sizeof("");
+ copy = (unsigned char*)hooks->allocate(length);
+ if (copy == NULL)
+ {
+ return NULL;
+ }
+ memcpy(copy, string, length);
+
+ return copy;
+}
+
+CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)
+{
+ if (hooks == NULL)
+ {
+ /* Reset hooks */
+ global_hooks.allocate = malloc;
+ global_hooks.deallocate = free;
+ global_hooks.reallocate = realloc;
+ return;
+ }
+
+ global_hooks.allocate = malloc;
+ if (hooks->malloc_fn != NULL)
+ {
+ global_hooks.allocate = hooks->malloc_fn;
+ }
+
+ global_hooks.deallocate = free;
+ if (hooks->free_fn != NULL)
+ {
+ global_hooks.deallocate = hooks->free_fn;
+ }
+
+ /* use realloc only if both free and malloc are used */
+ global_hooks.reallocate = NULL;
+ if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))
+ {
+ global_hooks.reallocate = realloc;
+ }
+}
+
+/* Internal constructor. */
+static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
+{
+ cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
+ if (node)
+ {
+ memset(node, '\0', sizeof(cJSON));
+ }
+
+ return node;
+}
+
+/* Delete a cJSON structure. */
+CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
+{
+ cJSON *next = NULL;
+ while (item != NULL)
+ {
+ next = item->next;
+ if (!(item->type & cJSON_IsReference) && (item->child != NULL))
+ {
+ cJSON_Delete(item->child);
+ }
+ if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
+ {
+ global_hooks.deallocate(item->valuestring);
+ item->valuestring = NULL;
+ }
+ if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
+ {
+ global_hooks.deallocate(item->string);
+ item->string = NULL;
+ }
+ global_hooks.deallocate(item);
+ item = next;
+ }
+}
+
+/* get the decimal point character of the current locale */
+static unsigned char get_decimal_point(void)
+{
+#ifdef ENABLE_LOCALES
+ struct lconv *lconv = localeconv();
+ return (unsigned char) lconv->decimal_point[0];
+#else
+ return '.';
+#endif
+}
+
+typedef struct
+{
+ const unsigned char *content;
+ size_t length;
+ size_t offset;
+ size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */
+ internal_hooks hooks;
+} parse_buffer;
+
+/* check if the given size is left to read in a given parse buffer (starting with 1) */
+#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))
+/* check if the buffer can be accessed at the given index (starting with 0) */
+#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
+#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))
+/* get a pointer to the buffer at the position */
+#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
+
+/* Parse the input text to generate a number, and populate the result into item. */
+static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)
+{
+ double number = 0;
+ unsigned char *after_end = NULL;
+ unsigned char number_c_string[64];
+ unsigned char decimal_point = get_decimal_point();
+ size_t i = 0;
+
+ if ((input_buffer == NULL) || (input_buffer->content == NULL))
+ {
+ return false;
+ }
+
+ /* copy the number into a temporary buffer and replace '.' with the decimal point
+ * of the current locale (for strtod)
+ * This also takes care of '\0' not necessarily being available for marking the end of the input */
+ for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++)
+ {
+ switch (buffer_at_offset(input_buffer)[i])
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case 'e':
+ case 'E':
+ number_c_string[i] = buffer_at_offset(input_buffer)[i];
+ break;
+
+ case '.':
+ number_c_string[i] = decimal_point;
+ break;
+
+ default:
+ goto loop_end;
+ }
+ }
+loop_end:
+ number_c_string[i] = '\0';
+
+ number = strtod((const char*)number_c_string, (char**)&after_end);
+ if (number_c_string == after_end)
+ {
+ return false; /* parse_error */
+ }
+
+ item->valuedouble = number;
+
+ /* use saturation in case of overflow */
+ if (number >= INT_MAX)
+ {
+ item->valueint = INT_MAX;
+ }
+ else if (number <= (double)INT_MIN)
+ {
+ item->valueint = INT_MIN;
+ }
+ else
+ {
+ item->valueint = (int)number;
+ }
+
+ item->type = cJSON_Number;
+
+ input_buffer->offset += (size_t)(after_end - number_c_string);
+ return true;
+}
+
+/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */
+CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)
+{
+ if (number >= INT_MAX)
+ {
+ object->valueint = INT_MAX;
+ }
+ else if (number <= (double)INT_MIN)
+ {
+ object->valueint = INT_MIN;
+ }
+ else
+ {
+ object->valueint = (int)number;
+ }
+
+ return object->valuedouble = number;
+}
+
+/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */
+CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring)
+{
+ char *copy = NULL;
+ size_t v1_len;
+ size_t v2_len;
+ /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */
+ if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference))
+ {
+ return NULL;
+ }
+ /* return NULL if the object is corrupted or valuestring is NULL */
+ if (object->valuestring == NULL || valuestring == NULL)
+ {
+ return NULL;
+ }
+
+ v1_len = strlen(valuestring);
+ v2_len = strlen(object->valuestring);
+
+ if (v1_len <= v2_len)
+ {
+ /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */
+ if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring ))
+ {
+ return NULL;
+ }
+ strcpy(object->valuestring, valuestring);
+ return object->valuestring;
+ }
+ copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks);
+ if (copy == NULL)
+ {
+ return NULL;
+ }
+ if (object->valuestring != NULL)
+ {
+ cJSON_free(object->valuestring);
+ }
+ object->valuestring = copy;
+
+ return copy;
+}
+
+typedef struct
+{
+ unsigned char *buffer;
+ size_t length;
+ size_t offset;
+ size_t depth; /* current nesting depth (for formatted printing) */
+ cJSON_bool noalloc;
+ cJSON_bool format; /* is this print a formatted print */
+ internal_hooks hooks;
+} printbuffer;
+
+/* realloc printbuffer if necessary to have at least "needed" bytes more */
+static unsigned char* ensure(printbuffer * const p, size_t needed)
+{
+ unsigned char *newbuffer = NULL;
+ size_t newsize = 0;
+
+ if ((p == NULL) || (p->buffer == NULL))
+ {
+ return NULL;
+ }
+
+ if ((p->length > 0) && (p->offset >= p->length))
+ {
+ /* make sure that offset is valid */
+ return NULL;
+ }
+
+ if (needed > INT_MAX)
+ {
+ /* sizes bigger than INT_MAX are currently not supported */
+ return NULL;
+ }
+
+ needed += p->offset + 1;
+ if (needed <= p->length)
+ {
+ return p->buffer + p->offset;
+ }
+
+ if (p->noalloc) {
+ return NULL;
+ }
+
+ /* calculate new buffer size */
+ if (needed > (INT_MAX / 2))
+ {
+ /* overflow of int, use INT_MAX if possible */
+ if (needed <= INT_MAX)
+ {
+ newsize = INT_MAX;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ newsize = needed * 2;
+ }
+
+ if (p->hooks.reallocate != NULL)
+ {
+ /* reallocate with realloc if available */
+ newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);
+ if (newbuffer == NULL)
+ {
+ p->hooks.deallocate(p->buffer);
+ p->length = 0;
+ p->buffer = NULL;
+
+ return NULL;
+ }
+ }
+ else
+ {
+ /* otherwise reallocate manually */
+ newbuffer = (unsigned char*)p->hooks.allocate(newsize);
+ if (!newbuffer)
+ {
+ p->hooks.deallocate(p->buffer);
+ p->length = 0;
+ p->buffer = NULL;
+
+ return NULL;
+ }
+
+ memcpy(newbuffer, p->buffer, p->offset + 1);
+ p->hooks.deallocate(p->buffer);
+ }
+ p->length = newsize;
+ p->buffer = newbuffer;
+
+ return newbuffer + p->offset;
+}
+
+/* calculate the new length of the string in a printbuffer and update the offset */
+static void update_offset(printbuffer * const buffer)
+{
+ const unsigned char *buffer_pointer = NULL;
+ if ((buffer == NULL) || (buffer->buffer == NULL))
+ {
+ return;
+ }
+ buffer_pointer = buffer->buffer + buffer->offset;
+
+ buffer->offset += strlen((const char*)buffer_pointer);
+}
+
+/* securely comparison of floating-point variables */
+static cJSON_bool compare_double(double a, double b)
+{
+ double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
+ return (fabs(a - b) <= maxVal * DBL_EPSILON);
+}
+
+/* Render the number nicely from the given item into a string. */
+static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)
+{
+ unsigned char *output_pointer = NULL;
+ double d = item->valuedouble;
+ int length = 0;
+ size_t i = 0;
+ unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */
+ unsigned char decimal_point = get_decimal_point();
+ double test = 0.0;
+
+ if (output_buffer == NULL)
+ {
+ return false;
+ }
+
+ /* This checks for NaN and Infinity */
+ if (isnan(d) || isinf(d))
+ {
+ length = sprintf((char*)number_buffer, "null");
+ }
+ else if(d == (double)item->valueint)
+ {
+ length = sprintf((char*)number_buffer, "%d", item->valueint);
+ }
+ else
+ {
+ /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
+ length = sprintf((char*)number_buffer, "%1.15g", d);
+
+ /* Check whether the original double can be recovered */
+ if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
+ {
+ /* If not, print with 17 decimal places of precision */
+ length = sprintf((char*)number_buffer, "%1.17g", d);
+ }
+ }
+
+ /* sprintf failed or buffer overrun occurred */
+ if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
+ {
+ return false;
+ }
+
+ /* reserve appropriate space in the output */
+ output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+
+ /* copy the printed number to the output and replace locale
+ * dependent decimal point with '.' */
+ for (i = 0; i < ((size_t)length); i++)
+ {
+ if (number_buffer[i] == decimal_point)
+ {
+ output_pointer[i] = '.';
+ continue;
+ }
+
+ output_pointer[i] = number_buffer[i];
+ }
+ output_pointer[i] = '\0';
+
+ output_buffer->offset += (size_t)length;
+
+ return true;
+}
+
+/* parse 4 digit hexadecimal number */
+static unsigned parse_hex4(const unsigned char * const input)
+{
+ unsigned int h = 0;
+ size_t i = 0;
+
+ for (i = 0; i < 4; i++)
+ {
+ /* parse digit */
+ if ((input[i] >= '0') && (input[i] <= '9'))
+ {
+ h += (unsigned int) input[i] - '0';
+ }
+ else if ((input[i] >= 'A') && (input[i] <= 'F'))
+ {
+ h += (unsigned int) 10 + input[i] - 'A';
+ }
+ else if ((input[i] >= 'a') && (input[i] <= 'f'))
+ {
+ h += (unsigned int) 10 + input[i] - 'a';
+ }
+ else /* invalid */
+ {
+ return 0;
+ }
+
+ if (i < 3)
+ {
+ /* shift left to make place for the next nibble */
+ h = h << 4;
+ }
+ }
+
+ return h;
+}
+
+/* converts a UTF-16 literal to UTF-8
+ * A literal can be one or two sequences of the form \uXXXX */
+static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer)
+{
+ long unsigned int codepoint = 0;
+ unsigned int first_code = 0;
+ const unsigned char *first_sequence = input_pointer;
+ unsigned char utf8_length = 0;
+ unsigned char utf8_position = 0;
+ unsigned char sequence_length = 0;
+ unsigned char first_byte_mark = 0;
+
+ if ((input_end - first_sequence) < 6)
+ {
+ /* input ends unexpectedly */
+ goto fail;
+ }
+
+ /* get the first utf16 sequence */
+ first_code = parse_hex4(first_sequence + 2);
+
+ /* check that the code is valid */
+ if (((first_code >= 0xDC00) && (first_code <= 0xDFFF)))
+ {
+ goto fail;
+ }
+
+ /* UTF16 surrogate pair */
+ if ((first_code >= 0xD800) && (first_code <= 0xDBFF))
+ {
+ const unsigned char *second_sequence = first_sequence + 6;
+ unsigned int second_code = 0;
+ sequence_length = 12; /* \uXXXX\uXXXX */
+
+ if ((input_end - second_sequence) < 6)
+ {
+ /* input ends unexpectedly */
+ goto fail;
+ }
+
+ if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u'))
+ {
+ /* missing second half of the surrogate pair */
+ goto fail;
+ }
+
+ /* get the second utf16 sequence */
+ second_code = parse_hex4(second_sequence + 2);
+ /* check that the code is valid */
+ if ((second_code < 0xDC00) || (second_code > 0xDFFF))
+ {
+ /* invalid second half of the surrogate pair */
+ goto fail;
+ }
+
+
+ /* calculate the unicode codepoint from the surrogate pair */
+ codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
+ }
+ else
+ {
+ sequence_length = 6; /* \uXXXX */
+ codepoint = first_code;
+ }
+
+ /* encode as UTF-8
+ * takes at maximum 4 bytes to encode:
+ * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+ if (codepoint < 0x80)
+ {
+ /* normal ascii, encoding 0xxxxxxx */
+ utf8_length = 1;
+ }
+ else if (codepoint < 0x800)
+ {
+ /* two bytes, encoding 110xxxxx 10xxxxxx */
+ utf8_length = 2;
+ first_byte_mark = 0xC0; /* 11000000 */
+ }
+ else if (codepoint < 0x10000)
+ {
+ /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */
+ utf8_length = 3;
+ first_byte_mark = 0xE0; /* 11100000 */
+ }
+ else if (codepoint <= 0x10FFFF)
+ {
+ /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */
+ utf8_length = 4;
+ first_byte_mark = 0xF0; /* 11110000 */
+ }
+ else
+ {
+ /* invalid unicode codepoint */
+ goto fail;
+ }
+
+ /* encode as utf8 */
+ for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--)
+ {
+ /* 10xxxxxx */
+ (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);
+ codepoint >>= 6;
+ }
+ /* encode first byte */
+ if (utf8_length > 1)
+ {
+ (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);
+ }
+ else
+ {
+ (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);
+ }
+
+ *output_pointer += utf8_length;
+
+ return sequence_length;
+
+fail:
+ return 0;
+}
+
+/* Parse the input text into an unescaped cinput, and populate item. */
+static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer)
+{
+ const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;
+ const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;
+ unsigned char *output_pointer = NULL;
+ unsigned char *output = NULL;
+
+ /* not a string */
+ if (buffer_at_offset(input_buffer)[0] != '\"')
+ {
+ goto fail;
+ }
+
+ {
+ /* calculate approximate size of the output (overestimate) */
+ size_t allocation_length = 0;
+ size_t skipped_bytes = 0;
+ while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"'))
+ {
+ /* is escape sequence */
+ if (input_end[0] == '\\')
+ {
+ if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)
+ {
+ /* prevent buffer overflow when last input character is a backslash */
+ goto fail;
+ }
+ skipped_bytes++;
+ input_end++;
+ }
+ input_end++;
+ }
+ if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"'))
+ {
+ goto fail; /* string ended unexpectedly */
+ }
+
+ /* This is at most how much we need for the output */
+ allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
+ output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(""));
+ if (output == NULL)
+ {
+ goto fail; /* allocation failure */
+ }
+ }
+
+ output_pointer = output;
+ /* loop through the string literal */
+ while (input_pointer < input_end)
+ {
+ if (*input_pointer != '\\')
+ {
+ *output_pointer++ = *input_pointer++;
+ }
+ /* escape sequence */
+ else
+ {
+ unsigned char sequence_length = 2;
+ if ((input_end - input_pointer) < 1)
+ {
+ goto fail;
+ }
+
+ switch (input_pointer[1])
+ {
+ case 'b':
+ *output_pointer++ = '\b';
+ break;
+ case 'f':
+ *output_pointer++ = '\f';
+ break;
+ case 'n':
+ *output_pointer++ = '\n';
+ break;
+ case 'r':
+ *output_pointer++ = '\r';
+ break;
+ case 't':
+ *output_pointer++ = '\t';
+ break;
+ case '\"':
+ case '\\':
+ case '/':
+ *output_pointer++ = input_pointer[1];
+ break;
+
+ /* UTF-16 literal */
+ case 'u':
+ sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
+ if (sequence_length == 0)
+ {
+ /* failed to convert UTF16-literal to UTF-8 */
+ goto fail;
+ }
+ break;
+
+ default:
+ goto fail;
+ }
+ input_pointer += sequence_length;
+ }
+ }
+
+ /* zero terminate the output */
+ *output_pointer = '\0';
+
+ item->type = cJSON_String;
+ item->valuestring = (char*)output;
+
+ input_buffer->offset = (size_t) (input_end - input_buffer->content);
+ input_buffer->offset++;
+
+ return true;
+
+fail:
+ if (output != NULL)
+ {
+ input_buffer->hooks.deallocate(output);
+ output = NULL;
+ }
+
+ if (input_pointer != NULL)
+ {
+ input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
+ }
+
+ return false;
+}
+
+/* Render the cstring provided to an escaped version that can be printed. */
+static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
+{
+ const unsigned char *input_pointer = NULL;
+ unsigned char *output = NULL;
+ unsigned char *output_pointer = NULL;
+ size_t output_length = 0;
+ /* numbers of additional characters needed for escaping */
+ size_t escape_characters = 0;
+
+ if (output_buffer == NULL)
+ {
+ return false;
+ }
+
+ /* empty string */
+ if (input == NULL)
+ {
+ output = ensure(output_buffer, sizeof("\"\""));
+ if (output == NULL)
+ {
+ return false;
+ }
+ strcpy((char*)output, "\"\"");
+
+ return true;
+ }
+
+ /* set "flag" to 1 if something needs to be escaped */
+ for (input_pointer = input; *input_pointer; input_pointer++)
+ {
+ switch (*input_pointer)
+ {
+ case '\"':
+ case '\\':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ /* one character escape sequence */
+ escape_characters++;
+ break;
+ default:
+ if (*input_pointer < 32)
+ {
+ /* UTF-16 escape sequence uXXXX */
+ escape_characters += 5;
+ }
+ break;
+ }
+ }
+ output_length = (size_t)(input_pointer - input) + escape_characters;
+
+ output = ensure(output_buffer, output_length + sizeof("\"\""));
+ if (output == NULL)
+ {
+ return false;
+ }
+
+ /* no characters have to be escaped */
+ if (escape_characters == 0)
+ {
+ output[0] = '\"';
+ memcpy(output + 1, input, output_length);
+ output[output_length + 1] = '\"';
+ output[output_length + 2] = '\0';
+
+ return true;
+ }
+
+ output[0] = '\"';
+ output_pointer = output + 1;
+ /* copy the string */
+ for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
+ {
+ if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
+ {
+ /* normal character, copy */
+ *output_pointer = *input_pointer;
+ }
+ else
+ {
+ /* character needs to be escaped */
+ *output_pointer++ = '\\';
+ switch (*input_pointer)
+ {
+ case '\\':
+ *output_pointer = '\\';
+ break;
+ case '\"':
+ *output_pointer = '\"';
+ break;
+ case '\b':
+ *output_pointer = 'b';
+ break;
+ case '\f':
+ *output_pointer = 'f';
+ break;
+ case '\n':
+ *output_pointer = 'n';
+ break;
+ case '\r':
+ *output_pointer = 'r';
+ break;
+ case '\t':
+ *output_pointer = 't';
+ break;
+ default:
+ /* escape and print as unicode codepoint */
+ sprintf((char*)output_pointer, "u%04x", *input_pointer);
+ output_pointer += 4;
+ break;
+ }
+ }
+ }
+ output[output_length + 1] = '\"';
+ output[output_length + 2] = '\0';
+
+ return true;
+}
+
+/* Invoke print_string_ptr (which is useful) on an item. */
+static cJSON_bool print_string(const cJSON * const item, printbuffer * const p)
+{
+ return print_string_ptr((unsigned char*)item->valuestring, p);
+}
+
+/* Predeclare these prototypes. */
+static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer);
+static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer);
+static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer);
+static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer);
+
+/* Utility to jump whitespace and cr/lf */
+static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)
+{
+ if ((buffer == NULL) || (buffer->content == NULL))
+ {
+ return NULL;
+ }
+
+ if (cannot_access_at_index(buffer, 0))
+ {
+ return buffer;
+ }
+
+ while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))
+ {
+ buffer->offset++;
+ }
+
+ if (buffer->offset == buffer->length)
+ {
+ buffer->offset--;
+ }
+
+ return buffer;
+}
+
+/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */
+static parse_buffer *skip_utf8_bom(parse_buffer * const buffer)
+{
+ if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0))
+ {
+ return NULL;
+ }
+
+ if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0))
+ {
+ buffer->offset += 3;
+ }
+
+ return buffer;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
+{
+ size_t buffer_length;
+
+ if (NULL == value)
+ {
+ return NULL;
+ }
+
+ /* Adding null character size due to require_null_terminated. */
+ buffer_length = strlen(value) + sizeof("");
+
+ return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated);
+}
+
+/* Parse an object - create a new root, and populate. */
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)
+{
+ parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
+ cJSON *item = NULL;
+
+ /* reset error position */
+ global_error.json = NULL;
+ global_error.position = 0;
+
+ if (value == NULL || 0 == buffer_length)
+ {
+ goto fail;
+ }
+
+ buffer.content = (const unsigned char*)value;
+ buffer.length = buffer_length;
+ buffer.offset = 0;
+ buffer.hooks = global_hooks;
+
+ item = cJSON_New_Item(&global_hooks);
+ if (item == NULL) /* memory fail */
+ {
+ goto fail;
+ }
+
+ if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
+ {
+ /* parse failure. ep is set. */
+ goto fail;
+ }
+
+ /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
+ if (require_null_terminated)
+ {
+ buffer_skip_whitespace(&buffer);
+ if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0')
+ {
+ goto fail;
+ }
+ }
+ if (return_parse_end)
+ {
+ *return_parse_end = (const char*)buffer_at_offset(&buffer);
+ }
+
+ return item;
+
+fail:
+ if (item != NULL)
+ {
+ cJSON_Delete(item);
+ }
+
+ if (value != NULL)
+ {
+ error local_error;
+ local_error.json = (const unsigned char*)value;
+ local_error.position = 0;
+
+ if (buffer.offset < buffer.length)
+ {
+ local_error.position = buffer.offset;
+ }
+ else if (buffer.length > 0)
+ {
+ local_error.position = buffer.length - 1;
+ }
+
+ if (return_parse_end != NULL)
+ {
+ *return_parse_end = (const char*)local_error.json + local_error.position;
+ }
+
+ global_error = local_error;
+ }
+
+ return NULL;
+}
+
+/* Default options for cJSON_Parse */
+CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)
+{
+ return cJSON_ParseWithOpts(value, 0, 0);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length)
+{
+ return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);
+}
+
+#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))
+
+static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
+{
+ static const size_t default_buffer_size = 256;
+ printbuffer buffer[1];
+ unsigned char *printed = NULL;
+
+ memset(buffer, 0, sizeof(buffer));
+
+ /* create buffer */
+ buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);
+ buffer->length = default_buffer_size;
+ buffer->format = format;
+ buffer->hooks = *hooks;
+ if (buffer->buffer == NULL)
+ {
+ goto fail;
+ }
+
+ /* print the value */
+ if (!print_value(item, buffer))
+ {
+ goto fail;
+ }
+ update_offset(buffer);
+
+ /* check if reallocate is available */
+ if (hooks->reallocate != NULL)
+ {
+ printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);
+ if (printed == NULL) {
+ goto fail;
+ }
+ buffer->buffer = NULL;
+ }
+ else /* otherwise copy the JSON over to a new buffer */
+ {
+ printed = (unsigned char*) hooks->allocate(buffer->offset + 1);
+ if (printed == NULL)
+ {
+ goto fail;
+ }
+ memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
+ printed[buffer->offset] = '\0'; /* just to be sure */
+
+ /* free the buffer */
+ hooks->deallocate(buffer->buffer);
+ buffer->buffer = NULL;
+ }
+
+ return printed;
+
+fail:
+ if (buffer->buffer != NULL)
+ {
+ hooks->deallocate(buffer->buffer);
+ buffer->buffer = NULL;
+ }
+
+ if (printed != NULL)
+ {
+ hooks->deallocate(printed);
+ printed = NULL;
+ }
+
+ return NULL;
+}
+
+/* Render a cJSON item/entity/structure to text. */
+CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
+{
+ return (char*)print(item, true, &global_hooks);
+}
+
+CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)
+{
+ return (char*)print(item, false, &global_hooks);
+}
+
+CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
+{
+ printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
+
+ if (prebuffer < 0)
+ {
+ return NULL;
+ }
+
+ p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);
+ if (!p.buffer)
+ {
+ return NULL;
+ }
+
+ p.length = (size_t)prebuffer;
+ p.offset = 0;
+ p.noalloc = false;
+ p.format = fmt;
+ p.hooks = global_hooks;
+
+ if (!print_value(item, &p))
+ {
+ global_hooks.deallocate(p.buffer);
+ p.buffer = NULL;
+ return NULL;
+ }
+
+ return (char*)p.buffer;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)
+{
+ printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
+
+ if ((length < 0) || (buffer == NULL))
+ {
+ return false;
+ }
+
+ p.buffer = (unsigned char*)buffer;
+ p.length = (size_t)length;
+ p.offset = 0;
+ p.noalloc = true;
+ p.format = format;
+ p.hooks = global_hooks;
+
+ return print_value(item, &p);
+}
+
+/* Parser core - when encountering text, process appropriately. */
+static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
+{
+ if ((input_buffer == NULL) || (input_buffer->content == NULL))
+ {
+ return false; /* no input */
+ }
+
+ /* parse the different types of values */
+ /* null */
+ if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
+ {
+ item->type = cJSON_NULL;
+ input_buffer->offset += 4;
+ return true;
+ }
+ /* false */
+ if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0))
+ {
+ item->type = cJSON_False;
+ input_buffer->offset += 5;
+ return true;
+ }
+ /* true */
+ if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0))
+ {
+ item->type = cJSON_True;
+ item->valueint = 1;
+ input_buffer->offset += 4;
+ return true;
+ }
+ /* string */
+ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
+ {
+ return parse_string(item, input_buffer);
+ }
+ /* number */
+ if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
+ {
+ return parse_number(item, input_buffer);
+ }
+ /* array */
+ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
+ {
+ return parse_array(item, input_buffer);
+ }
+ /* object */
+ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
+ {
+ return parse_object(item, input_buffer);
+ }
+
+ return false;
+}
+
+/* Render a value to text. */
+static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
+{
+ unsigned char *output = NULL;
+
+ if ((item == NULL) || (output_buffer == NULL))
+ {
+ return false;
+ }
+
+ switch ((item->type) & 0xFF)
+ {
+ case cJSON_NULL:
+ output = ensure(output_buffer, 5);
+ if (output == NULL)
+ {
+ return false;
+ }
+ strcpy((char*)output, "null");
+ return true;
+
+ case cJSON_False:
+ output = ensure(output_buffer, 6);
+ if (output == NULL)
+ {
+ return false;
+ }
+ strcpy((char*)output, "false");
+ return true;
+
+ case cJSON_True:
+ output = ensure(output_buffer, 5);
+ if (output == NULL)
+ {
+ return false;
+ }
+ strcpy((char*)output, "true");
+ return true;
+
+ case cJSON_Number:
+ return print_number(item, output_buffer);
+
+ case cJSON_Raw:
+ {
+ size_t raw_length = 0;
+ if (item->valuestring == NULL)
+ {
+ return false;
+ }
+
+ raw_length = strlen(item->valuestring) + sizeof("");
+ output = ensure(output_buffer, raw_length);
+ if (output == NULL)
+ {
+ return false;
+ }
+ memcpy(output, item->valuestring, raw_length);
+ return true;
+ }
+
+ case cJSON_String:
+ return print_string(item, output_buffer);
+
+ case cJSON_Array:
+ return print_array(item, output_buffer);
+
+ case cJSON_Object:
+ return print_object(item, output_buffer);
+
+ default:
+ return false;
+ }
+}
+
+/* Build an array from input text. */
+static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer)
+{
+ cJSON *head = NULL; /* head of the linked list */
+ cJSON *current_item = NULL;
+
+ if (input_buffer->depth >= CJSON_NESTING_LIMIT)
+ {
+ return false; /* to deeply nested */
+ }
+ input_buffer->depth++;
+
+ if (buffer_at_offset(input_buffer)[0] != '[')
+ {
+ /* not an array */
+ goto fail;
+ }
+
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']'))
+ {
+ /* empty array */
+ goto success;
+ }
+
+ /* check if we skipped to the end of the buffer */
+ if (cannot_access_at_index(input_buffer, 0))
+ {
+ input_buffer->offset--;
+ goto fail;
+ }
+
+ /* step back to character in front of the first element */
+ input_buffer->offset--;
+ /* loop through the comma separated array elements */
+ do
+ {
+ /* allocate next item */
+ cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
+ if (new_item == NULL)
+ {
+ goto fail; /* allocation failure */
+ }
+
+ /* attach next item to list */
+ if (head == NULL)
+ {
+ /* start the linked list */
+ current_item = head = new_item;
+ }
+ else
+ {
+ /* add to the end and advance */
+ current_item->next = new_item;
+ new_item->prev = current_item;
+ current_item = new_item;
+ }
+
+ /* parse next value */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_value(current_item, input_buffer))
+ {
+ goto fail; /* failed to parse value */
+ }
+ buffer_skip_whitespace(input_buffer);
+ }
+ while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
+
+ if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']')
+ {
+ goto fail; /* expected end of array */
+ }
+
+success:
+ input_buffer->depth--;
+
+ if (head != NULL) {
+ head->prev = current_item;
+ }
+
+ item->type = cJSON_Array;
+ item->child = head;
+
+ input_buffer->offset++;
+
+ return true;
+
+fail:
+ if (head != NULL)
+ {
+ cJSON_Delete(head);
+ }
+
+ return false;
+}
+
+/* Render an array to text */
+static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer)
+{
+ unsigned char *output_pointer = NULL;
+ size_t length = 0;
+ cJSON *current_element = item->child;
+
+ if (output_buffer == NULL)
+ {
+ return false;
+ }
+
+ /* Compose the output array. */
+ /* opening square bracket */
+ output_pointer = ensure(output_buffer, 1);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+
+ *output_pointer = '[';
+ output_buffer->offset++;
+ output_buffer->depth++;
+
+ while (current_element != NULL)
+ {
+ if (!print_value(current_element, output_buffer))
+ {
+ return false;
+ }
+ update_offset(output_buffer);
+ if (current_element->next)
+ {
+ length = (size_t) (output_buffer->format ? 2 : 1);
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ *output_pointer++ = ',';
+ if(output_buffer->format)
+ {
+ *output_pointer++ = ' ';
+ }
+ *output_pointer = '\0';
+ output_buffer->offset += length;
+ }
+ current_element = current_element->next;
+ }
+
+ output_pointer = ensure(output_buffer, 2);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ *output_pointer++ = ']';
+ *output_pointer = '\0';
+ output_buffer->depth--;
+
+ return true;
+}
+
+/* Build an object from the text. */
+static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)
+{
+ cJSON *head = NULL; /* linked list head */
+ cJSON *current_item = NULL;
+
+ if (input_buffer->depth >= CJSON_NESTING_LIMIT)
+ {
+ return false; /* to deeply nested */
+ }
+ input_buffer->depth++;
+
+ if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
+ {
+ goto fail; /* not an object */
+ }
+
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}'))
+ {
+ goto success; /* empty object */
+ }
+
+ /* check if we skipped to the end of the buffer */
+ if (cannot_access_at_index(input_buffer, 0))
+ {
+ input_buffer->offset--;
+ goto fail;
+ }
+
+ /* step back to character in front of the first element */
+ input_buffer->offset--;
+ /* loop through the comma separated array elements */
+ do
+ {
+ /* allocate next item */
+ cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
+ if (new_item == NULL)
+ {
+ goto fail; /* allocation failure */
+ }
+
+ /* attach next item to list */
+ if (head == NULL)
+ {
+ /* start the linked list */
+ current_item = head = new_item;
+ }
+ else
+ {
+ /* add to the end and advance */
+ current_item->next = new_item;
+ new_item->prev = current_item;
+ current_item = new_item;
+ }
+
+ if (cannot_access_at_index(input_buffer, 1))
+ {
+ goto fail; /* nothing comes after the comma */
+ }
+
+ /* parse the name of the child */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_string(current_item, input_buffer))
+ {
+ goto fail; /* failed to parse name */
+ }
+ buffer_skip_whitespace(input_buffer);
+
+ /* swap valuestring and string, because we parsed the name */
+ current_item->string = current_item->valuestring;
+ current_item->valuestring = NULL;
+
+ if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':'))
+ {
+ goto fail; /* invalid object */
+ }
+
+ /* parse the value */
+ input_buffer->offset++;
+ buffer_skip_whitespace(input_buffer);
+ if (!parse_value(current_item, input_buffer))
+ {
+ goto fail; /* failed to parse value */
+ }
+ buffer_skip_whitespace(input_buffer);
+ }
+ while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
+
+ if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}'))
+ {
+ goto fail; /* expected end of object */
+ }
+
+success:
+ input_buffer->depth--;
+
+ if (head != NULL) {
+ head->prev = current_item;
+ }
+
+ item->type = cJSON_Object;
+ item->child = head;
+
+ input_buffer->offset++;
+ return true;
+
+fail:
+ if (head != NULL)
+ {
+ cJSON_Delete(head);
+ }
+
+ return false;
+}
+
+/* Render an object to text. */
+static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer)
+{
+ unsigned char *output_pointer = NULL;
+ size_t length = 0;
+ cJSON *current_item = item->child;
+
+ if (output_buffer == NULL)
+ {
+ return false;
+ }
+
+ /* Compose the output: */
+ length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+
+ *output_pointer++ = '{';
+ output_buffer->depth++;
+ if (output_buffer->format)
+ {
+ *output_pointer++ = '\n';
+ }
+ output_buffer->offset += length;
+
+ while (current_item)
+ {
+ if (output_buffer->format)
+ {
+ size_t i;
+ output_pointer = ensure(output_buffer, output_buffer->depth);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ for (i = 0; i < output_buffer->depth; i++)
+ {
+ *output_pointer++ = '\t';
+ }
+ output_buffer->offset += output_buffer->depth;
+ }
+
+ /* print key */
+ if (!print_string_ptr((unsigned char*)current_item->string, output_buffer))
+ {
+ return false;
+ }
+ update_offset(output_buffer);
+
+ length = (size_t) (output_buffer->format ? 2 : 1);
+ output_pointer = ensure(output_buffer, length);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ *output_pointer++ = ':';
+ if (output_buffer->format)
+ {
+ *output_pointer++ = '\t';
+ }
+ output_buffer->offset += length;
+
+ /* print value */
+ if (!print_value(current_item, output_buffer))
+ {
+ return false;
+ }
+ update_offset(output_buffer);
+
+ /* print comma if not last */
+ length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0));
+ output_pointer = ensure(output_buffer, length + 1);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ if (current_item->next)
+ {
+ *output_pointer++ = ',';
+ }
+
+ if (output_buffer->format)
+ {
+ *output_pointer++ = '\n';
+ }
+ *output_pointer = '\0';
+ output_buffer->offset += length;
+
+ current_item = current_item->next;
+ }
+
+ output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
+ if (output_pointer == NULL)
+ {
+ return false;
+ }
+ if (output_buffer->format)
+ {
+ size_t i;
+ for (i = 0; i < (output_buffer->depth - 1); i++)
+ {
+ *output_pointer++ = '\t';
+ }
+ }
+ *output_pointer++ = '}';
+ *output_pointer = '\0';
+ output_buffer->depth--;
+
+ return true;
+}
+
+/* Get Array size/item / object item. */
+CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
+{
+ cJSON *child = NULL;
+ size_t size = 0;
+
+ if (array == NULL)
+ {
+ return 0;
+ }
+
+ child = array->child;
+
+ while(child != NULL)
+ {
+ size++;
+ child = child->next;
+ }
+
+ /* FIXME: Can overflow here. Cannot be fixed without breaking the API */
+
+ return (int)size;
+}
+
+static cJSON* get_array_item(const cJSON *array, size_t index)
+{
+ cJSON *current_child = NULL;
+
+ if (array == NULL)
+ {
+ return NULL;
+ }
+
+ current_child = array->child;
+ while ((current_child != NULL) && (index > 0))
+ {
+ index--;
+ current_child = current_child->next;
+ }
+
+ return current_child;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)
+{
+ if (index < 0)
+ {
+ return NULL;
+ }
+
+ return get_array_item(array, (size_t)index);
+}
+
+static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive)
+{
+ cJSON *current_element = NULL;
+
+ if ((object == NULL) || (name == NULL))
+ {
+ return NULL;
+ }
+
+ current_element = object->child;
+ if (case_sensitive)
+ {
+ while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0))
+ {
+ current_element = current_element->next;
+ }
+ }
+ else
+ {
+ while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0))
+ {
+ current_element = current_element->next;
+ }
+ }
+
+ if ((current_element == NULL) || (current_element->string == NULL)) {
+ return NULL;
+ }
+
+ return current_element;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)
+{
+ return get_object_item(object, string, false);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)
+{
+ return get_object_item(object, string, true);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)
+{
+ return cJSON_GetObjectItem(object, string) ? 1 : 0;
+}
+
+/* Utility for array list handling. */
+static void suffix_object(cJSON *prev, cJSON *item)
+{
+ prev->next = item;
+ item->prev = prev;
+}
+
+/* Utility for handling references. */
+static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks)
+{
+ cJSON *reference = NULL;
+ if (item == NULL)
+ {
+ return NULL;
+ }
+
+ reference = cJSON_New_Item(hooks);
+ if (reference == NULL)
+ {
+ return NULL;
+ }
+
+ memcpy(reference, item, sizeof(cJSON));
+ reference->string = NULL;
+ reference->type |= cJSON_IsReference;
+ reference->next = reference->prev = NULL;
+ return reference;
+}
+
+static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
+{
+ cJSON *child = NULL;
+
+ if ((item == NULL) || (array == NULL) || (array == item))
+ {
+ return false;
+ }
+
+ child = array->child;
+ /*
+ * To find the last item in array quickly, we use prev in array
+ */
+ if (child == NULL)
+ {
+ /* list is empty, start new one */
+ array->child = item;
+ item->prev = item;
+ item->next = NULL;
+ }
+ else
+ {
+ /* append to the end */
+ if (child->prev)
+ {
+ suffix_object(child->prev, item);
+ array->child->prev = item;
+ }
+ }
+
+ return true;
+}
+
+/* Add item to array/object. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)
+{
+ return add_item_to_array(array, item);
+}
+
+#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+ #pragma GCC diagnostic push
+#endif
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+/* helper function to cast away const */
+static void* cast_away_const(const void* string)
+{
+ return (void*)string;
+}
+#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
+ #pragma GCC diagnostic pop
+#endif
+
+
+static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
+{
+ char *new_key = NULL;
+ int new_type = cJSON_Invalid;
+
+ if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))
+ {
+ return false;
+ }
+
+ if (constant_key)
+ {
+ new_key = (char*)cast_away_const(string);
+ new_type = item->type | cJSON_StringIsConst;
+ }
+ else
+ {
+ new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
+ if (new_key == NULL)
+ {
+ return false;
+ }
+
+ new_type = item->type & ~cJSON_StringIsConst;
+ }
+
+ if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
+ {
+ hooks->deallocate(item->string);
+ }
+
+ item->string = new_key;
+ item->type = new_type;
+
+ return add_item_to_array(object, item);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
+{
+ return add_item_to_object(object, string, item, &global_hooks, false);
+}
+
+/* Add an item to an object with constant string as key */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item)
+{
+ return add_item_to_object(object, string, item, &global_hooks, true);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)
+{
+ if (array == NULL)
+ {
+ return false;
+ }
+
+ return add_item_to_array(array, create_reference(item, &global_hooks));
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item)
+{
+ if ((object == NULL) || (string == NULL))
+ {
+ return false;
+ }
+
+ return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false);
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name)
+{
+ cJSON *null = cJSON_CreateNull();
+ if (add_item_to_object(object, name, null, &global_hooks, false))
+ {
+ return null;
+ }
+
+ cJSON_Delete(null);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name)
+{
+ cJSON *true_item = cJSON_CreateTrue();
+ if (add_item_to_object(object, name, true_item, &global_hooks, false))
+ {
+ return true_item;
+ }
+
+ cJSON_Delete(true_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name)
+{
+ cJSON *false_item = cJSON_CreateFalse();
+ if (add_item_to_object(object, name, false_item, &global_hooks, false))
+ {
+ return false_item;
+ }
+
+ cJSON_Delete(false_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean)
+{
+ cJSON *bool_item = cJSON_CreateBool(boolean);
+ if (add_item_to_object(object, name, bool_item, &global_hooks, false))
+ {
+ return bool_item;
+ }
+
+ cJSON_Delete(bool_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number)
+{
+ cJSON *number_item = cJSON_CreateNumber(number);
+ if (add_item_to_object(object, name, number_item, &global_hooks, false))
+ {
+ return number_item;
+ }
+
+ cJSON_Delete(number_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)
+{
+ cJSON *string_item = cJSON_CreateString(string);
+ if (add_item_to_object(object, name, string_item, &global_hooks, false))
+ {
+ return string_item;
+ }
+
+ cJSON_Delete(string_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw)
+{
+ cJSON *raw_item = cJSON_CreateRaw(raw);
+ if (add_item_to_object(object, name, raw_item, &global_hooks, false))
+ {
+ return raw_item;
+ }
+
+ cJSON_Delete(raw_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name)
+{
+ cJSON *object_item = cJSON_CreateObject();
+ if (add_item_to_object(object, name, object_item, &global_hooks, false))
+ {
+ return object_item;
+ }
+
+ cJSON_Delete(object_item);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)
+{
+ cJSON *array = cJSON_CreateArray();
+ if (add_item_to_object(object, name, array, &global_hooks, false))
+ {
+ return array;
+ }
+
+ cJSON_Delete(array);
+ return NULL;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)
+{
+ if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL))
+ {
+ return NULL;
+ }
+
+ if (item != parent->child)
+ {
+ /* not the first element */
+ item->prev->next = item->next;
+ }
+ if (item->next != NULL)
+ {
+ /* not the last element */
+ item->next->prev = item->prev;
+ }
+
+ if (item == parent->child)
+ {
+ /* first element */
+ parent->child = item->next;
+ }
+ else if (item->next == NULL)
+ {
+ /* last element */
+ parent->child->prev = item->prev;
+ }
+
+ /* make sure the detached item doesn't point anywhere anymore */
+ item->prev = NULL;
+ item->next = NULL;
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)
+{
+ if (which < 0)
+ {
+ return NULL;
+ }
+
+ return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)
+{
+ cJSON_Delete(cJSON_DetachItemFromArray(array, which));
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string)
+{
+ cJSON *to_detach = cJSON_GetObjectItem(object, string);
+
+ return cJSON_DetachItemViaPointer(object, to_detach);
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string)
+{
+ cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string);
+
+ return cJSON_DetachItemViaPointer(object, to_detach);
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string)
+{
+ cJSON_Delete(cJSON_DetachItemFromObject(object, string));
+}
+
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string)
+{
+ cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));
+}
+
+/* Replace array/object items with new ones. */
+CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem)
+{
+ cJSON *after_inserted = NULL;
+
+ if (which < 0 || newitem == NULL)
+ {
+ return false;
+ }
+
+ after_inserted = get_array_item(array, (size_t)which);
+ if (after_inserted == NULL)
+ {
+ return add_item_to_array(array, newitem);
+ }
+
+ if (after_inserted != array->child && after_inserted->prev == NULL) {
+ /* return false if after_inserted is a corrupted array item */
+ return false;
+ }
+
+ newitem->next = after_inserted;
+ newitem->prev = after_inserted->prev;
+ after_inserted->prev = newitem;
+ if (after_inserted == array->child)
+ {
+ array->child = newitem;
+ }
+ else
+ {
+ newitem->prev->next = newitem;
+ }
+ return true;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)
+{
+ if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL))
+ {
+ return false;
+ }
+
+ if (replacement == item)
+ {
+ return true;
+ }
+
+ replacement->next = item->next;
+ replacement->prev = item->prev;
+
+ if (replacement->next != NULL)
+ {
+ replacement->next->prev = replacement;
+ }
+ if (parent->child == item)
+ {
+ if (parent->child->prev == parent->child)
+ {
+ replacement->prev = replacement;
+ }
+ parent->child = replacement;
+ }
+ else
+ { /*
+ * To find the last item in array quickly, we use prev in array.
+ * We can't modify the last item's next pointer where this item was the parent's child
+ */
+ if (replacement->prev != NULL)
+ {
+ replacement->prev->next = replacement;
+ }
+ if (replacement->next == NULL)
+ {
+ parent->child->prev = replacement;
+ }
+ }
+
+ item->next = NULL;
+ item->prev = NULL;
+ cJSON_Delete(item);
+
+ return true;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem)
+{
+ if (which < 0)
+ {
+ return false;
+ }
+
+ return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem);
+}
+
+static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive)
+{
+ if ((replacement == NULL) || (string == NULL))
+ {
+ return false;
+ }
+
+ /* replace the name in the replacement */
+ if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL))
+ {
+ cJSON_free(replacement->string);
+ }
+ replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+ if (replacement->string == NULL)
+ {
+ return false;
+ }
+
+ replacement->type &= ~cJSON_StringIsConst;
+
+ return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem)
+{
+ return replace_item_in_object(object, string, newitem, false);
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem)
+{
+ return replace_item_in_object(object, string, newitem, true);
+}
+
+/* Create basic types: */
+CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_NULL;
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_True;
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_False;
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = boolean ? cJSON_True : cJSON_False;
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_Number;
+ item->valuedouble = num;
+
+ /* use saturation in case of overflow */
+ if (num >= INT_MAX)
+ {
+ item->valueint = INT_MAX;
+ }
+ else if (num <= (double)INT_MIN)
+ {
+ item->valueint = INT_MIN;
+ }
+ else
+ {
+ item->valueint = (int)num;
+ }
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_String;
+ item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+ if(!item->valuestring)
+ {
+ cJSON_Delete(item);
+ return NULL;
+ }
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if (item != NULL)
+ {
+ item->type = cJSON_String | cJSON_IsReference;
+ item->valuestring = (char*)cast_away_const(string);
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if (item != NULL) {
+ item->type = cJSON_Object | cJSON_IsReference;
+ item->child = (cJSON*)cast_away_const(child);
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if (item != NULL) {
+ item->type = cJSON_Array | cJSON_IsReference;
+ item->child = (cJSON*)cast_away_const(child);
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type = cJSON_Raw;
+ item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);
+ if(!item->valuestring)
+ {
+ cJSON_Delete(item);
+ return NULL;
+ }
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if(item)
+ {
+ item->type=cJSON_Array;
+ }
+
+ return item;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
+{
+ cJSON *item = cJSON_New_Item(&global_hooks);
+ if (item)
+ {
+ item->type = cJSON_Object;
+ }
+
+ return item;
+}
+
+/* Create Arrays: */
+CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)
+{
+ size_t i = 0;
+ cJSON *n = NULL;
+ cJSON *p = NULL;
+ cJSON *a = NULL;
+
+ if ((count < 0) || (numbers == NULL))
+ {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for(i = 0; a && (i < (size_t)count); i++)
+ {
+ n = cJSON_CreateNumber(numbers[i]);
+ if (!n)
+ {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if(!i)
+ {
+ a->child = n;
+ }
+ else
+ {
+ suffix_object(p, n);
+ }
+ p = n;
+ }
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
+ return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count)
+{
+ size_t i = 0;
+ cJSON *n = NULL;
+ cJSON *p = NULL;
+ cJSON *a = NULL;
+
+ if ((count < 0) || (numbers == NULL))
+ {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for(i = 0; a && (i < (size_t)count); i++)
+ {
+ n = cJSON_CreateNumber((double)numbers[i]);
+ if(!n)
+ {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if(!i)
+ {
+ a->child = n;
+ }
+ else
+ {
+ suffix_object(p, n);
+ }
+ p = n;
+ }
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
+ return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count)
+{
+ size_t i = 0;
+ cJSON *n = NULL;
+ cJSON *p = NULL;
+ cJSON *a = NULL;
+
+ if ((count < 0) || (numbers == NULL))
+ {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for(i = 0; a && (i < (size_t)count); i++)
+ {
+ n = cJSON_CreateNumber(numbers[i]);
+ if(!n)
+ {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if(!i)
+ {
+ a->child = n;
+ }
+ else
+ {
+ suffix_object(p, n);
+ }
+ p = n;
+ }
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
+ return a;
+}
+
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count)
+{
+ size_t i = 0;
+ cJSON *n = NULL;
+ cJSON *p = NULL;
+ cJSON *a = NULL;
+
+ if ((count < 0) || (strings == NULL))
+ {
+ return NULL;
+ }
+
+ a = cJSON_CreateArray();
+
+ for (i = 0; a && (i < (size_t)count); i++)
+ {
+ n = cJSON_CreateString(strings[i]);
+ if(!n)
+ {
+ cJSON_Delete(a);
+ return NULL;
+ }
+ if(!i)
+ {
+ a->child = n;
+ }
+ else
+ {
+ suffix_object(p,n);
+ }
+ p = n;
+ }
+
+ if (a && a->child) {
+ a->child->prev = n;
+ }
+
+ return a;
+}
+
+/* Duplication */
+cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse);
+
+CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse)
+{
+ return cJSON_Duplicate_rec(item, 0, recurse );
+}
+
+cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse)
+{
+ cJSON *newitem = NULL;
+ cJSON *child = NULL;
+ cJSON *next = NULL;
+ cJSON *newchild = NULL;
+
+ /* Bail on bad ptr */
+ if (!item)
+ {
+ goto fail;
+ }
+ /* Create new item */
+ newitem = cJSON_New_Item(&global_hooks);
+ if (!newitem)
+ {
+ goto fail;
+ }
+ /* Copy over all vars */
+ newitem->type = item->type & (~cJSON_IsReference);
+ newitem->valueint = item->valueint;
+ newitem->valuedouble = item->valuedouble;
+ if (item->valuestring)
+ {
+ newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);
+ if (!newitem->valuestring)
+ {
+ goto fail;
+ }
+ }
+ if (item->string)
+ {
+ newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);
+ if (!newitem->string)
+ {
+ goto fail;
+ }
+ }
+ /* If non-recursive, then we're done! */
+ if (!recurse)
+ {
+ return newitem;
+ }
+ /* Walk the ->next chain for the child. */
+ child = item->child;
+ while (child != NULL)
+ {
+ if(depth >= CJSON_CIRCULAR_LIMIT) {
+ goto fail;
+ }
+ newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */
+ if (!newchild)
+ {
+ goto fail;
+ }
+ if (next != NULL)
+ {
+ /* If newitem->child already set, then crosswire ->prev and ->next and move on */
+ next->next = newchild;
+ newchild->prev = next;
+ next = newchild;
+ }
+ else
+ {
+ /* Set newitem->child and move to it */
+ newitem->child = newchild;
+ next = newchild;
+ }
+ child = child->next;
+ }
+ if (newitem && newitem->child)
+ {
+ newitem->child->prev = newchild;
+ }
+
+ return newitem;
+
+fail:
+ if (newitem != NULL)
+ {
+ cJSON_Delete(newitem);
+ }
+
+ return NULL;
+}
+
+static void skip_oneline_comment(char **input)
+{
+ *input += static_strlen("//");
+
+ for (; (*input)[0] != '\0'; ++(*input))
+ {
+ if ((*input)[0] == '\n') {
+ *input += static_strlen("\n");
+ return;
+ }
+ }
+}
+
+static void skip_multiline_comment(char **input)
+{
+ *input += static_strlen("/*");
+
+ for (; (*input)[0] != '\0'; ++(*input))
+ {
+ if (((*input)[0] == '*') && ((*input)[1] == '/'))
+ {
+ *input += static_strlen("*/");
+ return;
+ }
+ }
+}
+
+static void minify_string(char **input, char **output) {
+ (*output)[0] = (*input)[0];
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+
+
+ for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) {
+ (*output)[0] = (*input)[0];
+
+ if ((*input)[0] == '\"') {
+ (*output)[0] = '\"';
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+ return;
+ } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) {
+ (*output)[1] = (*input)[1];
+ *input += static_strlen("\"");
+ *output += static_strlen("\"");
+ }
+ }
+}
+
+CJSON_PUBLIC(void) cJSON_Minify(char *json)
+{
+ char *into = json;
+
+ if (json == NULL)
+ {
+ return;
+ }
+
+ while (json[0] != '\0')
+ {
+ switch (json[0])
+ {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ json++;
+ break;
+
+ case '/':
+ if (json[1] == '/')
+ {
+ skip_oneline_comment(&json);
+ }
+ else if (json[1] == '*')
+ {
+ skip_multiline_comment(&json);
+ } else {
+ json++;
+ }
+ break;
+
+ case '\"':
+ minify_string(&json, (char**)&into);
+ break;
+
+ default:
+ into[0] = json[0];
+ json++;
+ into++;
+ }
+ }
+
+ /* and null-terminate. */
+ *into = '\0';
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Invalid;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_False;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xff) == cJSON_True;
+}
+
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & (cJSON_True | cJSON_False)) != 0;
+}
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_NULL;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Number;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_String;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Array;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Object;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)
+{
+ if (item == NULL)
+ {
+ return false;
+ }
+
+ return (item->type & 0xFF) == cJSON_Raw;
+}
+
+CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive)
+{
+ if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)))
+ {
+ return false;
+ }
+
+ /* check if type is valid */
+ switch (a->type & 0xFF)
+ {
+ case cJSON_False:
+ case cJSON_True:
+ case cJSON_NULL:
+ case cJSON_Number:
+ case cJSON_String:
+ case cJSON_Raw:
+ case cJSON_Array:
+ case cJSON_Object:
+ break;
+
+ default:
+ return false;
+ }
+
+ /* identical objects are equal */
+ if (a == b)
+ {
+ return true;
+ }
+
+ switch (a->type & 0xFF)
+ {
+ /* in these cases and equal type is enough */
+ case cJSON_False:
+ case cJSON_True:
+ case cJSON_NULL:
+ return true;
+
+ case cJSON_Number:
+ if (compare_double(a->valuedouble, b->valuedouble))
+ {
+ return true;
+ }
+ return false;
+
+ case cJSON_String:
+ case cJSON_Raw:
+ if ((a->valuestring == NULL) || (b->valuestring == NULL))
+ {
+ return false;
+ }
+ if (strcmp(a->valuestring, b->valuestring) == 0)
+ {
+ return true;
+ }
+
+ return false;
+
+ case cJSON_Array:
+ {
+ cJSON *a_element = a->child;
+ cJSON *b_element = b->child;
+
+ for (; (a_element != NULL) && (b_element != NULL);)
+ {
+ if (!cJSON_Compare(a_element, b_element, case_sensitive))
+ {
+ return false;
+ }
+
+ a_element = a_element->next;
+ b_element = b_element->next;
+ }
+
+ /* one of the arrays is longer than the other */
+ if (a_element != b_element) {
+ return false;
+ }
+
+ return true;
+ }
+
+ case cJSON_Object:
+ {
+ cJSON *a_element = NULL;
+ cJSON *b_element = NULL;
+ cJSON_ArrayForEach(a_element, a)
+ {
+ /* TODO This has O(n^2) runtime, which is horrible! */
+ b_element = get_object_item(b, a_element->string, case_sensitive);
+ if (b_element == NULL)
+ {
+ return false;
+ }
+
+ if (!cJSON_Compare(a_element, b_element, case_sensitive))
+ {
+ return false;
+ }
+ }
+
+ /* doing this twice, once on a and b to prevent true comparison if a subset of b
+ * TODO: Do this the proper way, this is just a fix for now */
+ cJSON_ArrayForEach(b_element, b)
+ {
+ a_element = get_object_item(a, b_element->string, case_sensitive);
+ if (a_element == NULL)
+ {
+ return false;
+ }
+
+ if (!cJSON_Compare(b_element, a_element, case_sensitive))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+CJSON_PUBLIC(void *) cJSON_malloc(size_t size)
+{
+ return global_hooks.allocate(size);
+}
+
+CJSON_PUBLIC(void) cJSON_free(void *object)
+{
+ global_hooks.deallocate(object);
+ object = NULL;
+}
diff --git a/cJSON.h b/cJSON.h
@@ -0,0 +1,306 @@
+/*
+ Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef cJSON__h
+#define cJSON__h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
+#define __WINDOWS__
+#endif
+
+#ifdef __WINDOWS__
+
+/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
+
+CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
+CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
+CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
+
+For *nix builds that support visibility attribute, you can define similar behavior by
+
+setting default visibility to hidden by adding
+-fvisibility=hidden (for gcc)
+or
+-xldscope=hidden (for sun cc)
+to CFLAGS
+
+then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
+
+*/
+
+#define CJSON_CDECL __cdecl
+#define CJSON_STDCALL __stdcall
+
+/* export symbols by default, this is necessary for copy pasting the C and header file */
+#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
+#define CJSON_EXPORT_SYMBOLS
+#endif
+
+#if defined(CJSON_HIDE_SYMBOLS)
+#define CJSON_PUBLIC(type) type CJSON_STDCALL
+#elif defined(CJSON_EXPORT_SYMBOLS)
+#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
+#elif defined(CJSON_IMPORT_SYMBOLS)
+#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
+#endif
+#else /* !__WINDOWS__ */
+#define CJSON_CDECL
+#define CJSON_STDCALL
+
+#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
+#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
+#else
+#define CJSON_PUBLIC(type) type
+#endif
+#endif
+
+/* project version */
+#define CJSON_VERSION_MAJOR 1
+#define CJSON_VERSION_MINOR 7
+#define CJSON_VERSION_PATCH 18
+
+#include <stddef.h>
+
+/* cJSON Types: */
+#define cJSON_Invalid (0)
+#define cJSON_False (1 << 0)
+#define cJSON_True (1 << 1)
+#define cJSON_NULL (1 << 2)
+#define cJSON_Number (1 << 3)
+#define cJSON_String (1 << 4)
+#define cJSON_Array (1 << 5)
+#define cJSON_Object (1 << 6)
+#define cJSON_Raw (1 << 7) /* raw json */
+
+#define cJSON_IsReference 256
+#define cJSON_StringIsConst 512
+
+/* The cJSON structure: */
+typedef struct cJSON
+{
+ /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
+ struct cJSON *next;
+ struct cJSON *prev;
+ /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
+ struct cJSON *child;
+
+ /* The type of the item, as above. */
+ int type;
+
+ /* The item's string, if type==cJSON_String and type == cJSON_Raw */
+ char *valuestring;
+ /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
+ int valueint;
+ /* The item's number, if type==cJSON_Number */
+ double valuedouble;
+
+ /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
+ char *string;
+} cJSON;
+
+typedef struct cJSON_Hooks
+{
+ /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
+ void *(CJSON_CDECL *malloc_fn)(size_t sz);
+ void (CJSON_CDECL *free_fn)(void *ptr);
+} cJSON_Hooks;
+
+typedef int cJSON_bool;
+
+/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
+ * This is to prevent stack overflows. */
+#ifndef CJSON_NESTING_LIMIT
+#define CJSON_NESTING_LIMIT 1000
+#endif
+
+/* Limits the length of circular references can be before cJSON rejects to parse them.
+ * This is to prevent stack overflows. */
+#ifndef CJSON_CIRCULAR_LIMIT
+#define CJSON_CIRCULAR_LIMIT 10000
+#endif
+
+/* returns the version of cJSON as a string */
+CJSON_PUBLIC(const char*) cJSON_Version(void);
+
+/* Supply malloc, realloc and free functions to cJSON */
+CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
+
+/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
+/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
+CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
+/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
+/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
+CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
+
+/* Render a cJSON entity to text for transfer/storage. */
+CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
+/* Render a cJSON entity to text for transfer/storage without any formatting. */
+CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
+/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
+CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
+/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
+/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
+CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
+/* Delete a cJSON entity and all subentities. */
+CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
+
+/* Returns the number of items in an array (or object). */
+CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
+/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
+CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
+/* Get item "string" from object. Case insensitive. */
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
+CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
+CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
+/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
+CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
+
+/* Check item type and return its value */
+CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
+CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
+
+/* These functions check the type of an item */
+CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
+CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
+
+/* These calls create a cJSON item of the appropriate type. */
+CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
+CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
+CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
+/* raw json */
+CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
+CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
+CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
+
+/* Create a string where valuestring references a string so
+ * it will not be freed by cJSON_Delete */
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
+/* Create an object/array that only references it's elements so
+ * they will not be freed by cJSON_Delete */
+CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
+CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
+
+/* These utilities create an Array of count items.
+ * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
+CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
+CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
+
+/* Append item to the specified array/object. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
+/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
+ * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
+ * writing to `item->string` */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
+/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
+CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
+
+/* Remove/Detach items from Arrays/Objects. */
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
+CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
+CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
+
+/* Update array items. */
+CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
+CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
+
+/* Duplicate a cJSON item */
+CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
+/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
+ * need to be released. With recurse!=0, it will duplicate any children connected to the item.
+ * The item->next and ->prev pointers are always zero on return from Duplicate. */
+/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
+ * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
+CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
+
+/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
+ * The input pointer json cannot point to a read-only address area, such as a string constant,
+ * but should point to a readable and writable address area. */
+CJSON_PUBLIC(void) cJSON_Minify(char *json);
+
+/* Helper functions for creating and adding items to an object at the same time.
+ * They return the added item or NULL on failure. */
+CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
+CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
+CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
+CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
+CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
+CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
+
+/* When assigning an integer value, it needs to be propagated to valuedouble too. */
+#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
+/* helper for the cJSON_SetNumberValue macro */
+CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
+#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
+/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
+CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
+
+/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
+#define cJSON_SetBoolValue(object, boolValue) ( \
+ (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
+ (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
+ cJSON_Invalid\
+)
+
+/* Macro for iterating over an array or object */
+#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
+
+/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
+CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
+CJSON_PUBLIC(void) cJSON_free(void *object);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/compat.h b/compat.h
@@ -0,0 +1,6 @@
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+#undef reallocarray
+void *reallocarray(void *, size_t, size_t);
diff --git a/example_create.sh b/example_create.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# - Makes index for repositories in a single directory.
+# - Makes static pages for each repository directory.
+#
+# NOTE, things to do manually (once) before running this script:
+# - copy style.css, logo.png and favicon.png manually, a style.css example
+# is included.
+#
+# - write clone URL, for example "git://git.codemadness.org/dir" to the "url"
+# file for each repo.
+# - write owner of repo to the "owner" file.
+# - write description in "description" file.
+#
+# Usage:
+# - mkdir -p htmldir && cd htmldir
+# - sh example_create.sh
+
+# path must be absolute.
+reposdir="/var/www/domains/git.codemadness.nl/home/src"
+curdir="$(pwd)"
+
+# make index.
+stagit-index "${reposdir}/"*/ > "${curdir}/index.html"
+
+# make files per repo.
+for dir in "${reposdir}/"*/; do
+ # strip .git suffix.
+ r=$(basename "${dir}")
+ d=$(basename "${dir}" ".git")
+ printf "%s... " "${d}"
+
+ mkdir -p "${curdir}/${d}"
+ cd "${curdir}/${d}" || continue
+ stagit -c ".cache" -u "https://git.codemadness.nl/$d/" "${reposdir}/${r}"
+
+ # symlinks
+ ln -sf log.html index.html
+ ln -sf ../style.css style.css
+ ln -sf ../logo.png logo.png
+ ln -sf ../favicon.png favicon.png
+
+ echo "done"
+done
diff --git a/example_post-receive.sh b/example_post-receive.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+# generic git post-receive hook.
+# change the config options below and call this script in your post-receive
+# hook or symlink it.
+#
+# usage: $0 [name]
+#
+# if name is not set the basename of the current directory is used,
+# this is the directory of the repo when called from the post-receive script.
+
+# NOTE: needs to be set for correct locale (expects UTF-8) otherwise the
+# default is LC_CTYPE="POSIX".
+export LC_CTYPE="en_US.UTF-8"
+
+name="$1"
+if test "${name}" = ""; then
+ name=$(basename "$(pwd)")
+fi
+
+# config
+# paths must be absolute.
+reposdir="/home/src/src"
+dir="${reposdir}/${name}"
+htmldir="/home/www/domains/git.codemadness.org/htdocs"
+stagitdir="/"
+destdir="${htmldir}${stagitdir}"
+cachefile=".htmlcache"
+# /config
+
+if ! test -d "${dir}"; then
+ echo "${dir} does not exist" >&2
+ exit 1
+fi
+cd "${dir}" || exit 1
+
+# detect git push -f
+force=0
+while read -r old new ref; do
+ test "${old}" = "0000000000000000000000000000000000000000" && continue
+ test "${new}" = "0000000000000000000000000000000000000000" && continue
+
+ hasrevs=$(git rev-list "${old}" "^${new}" | sed 1q)
+ if test -n "${hasrevs}"; then
+ force=1
+ break
+ fi
+done
+
+# strip .git suffix.
+r=$(basename "${name}")
+d=$(basename "${name}" ".git")
+printf "[%s] stagit HTML pages... " "${d}"
+
+mkdir -p "${destdir}/${d}"
+cd "${destdir}/${d}" || exit 1
+
+# remove commits and ${cachefile} on git push -f, this recreated later on.
+if test "${force}" = "1"; then
+ rm -f "${cachefile}"
+ rm -rf "commit"
+fi
+
+# make index.
+stagit-index "${reposdir}/"*/ > "${destdir}/index.html"
+
+# make pages.
+stagit -c "${cachefile}" -u "https://git.codemadness.nl/$d/" "${reposdir}/${r}"
+
+ln -sf log.html index.html
+ln -sf ../style.css style.css
+ln -sf ../logo.png logo.png
+
+echo "done"
diff --git a/exts.json b/exts.json
@@ -0,0 +1,1578 @@
+{
+ "abap": [
+ "abap"
+ ],
+ "ags script": [
+ "asc",
+ "ash"
+ ],
+ "ampl": [
+ "ampl"
+ ],
+ "antlr": [
+ "g4"
+ ],
+ "apl": [
+ "apl",
+ "dyalog"
+ ],
+ "asp": [
+ "asp",
+ "asax",
+ "ascx",
+ "ashx",
+ "asmx",
+ "aspx",
+ "axd"
+ ],
+ "ats": [
+ "dats",
+ "hats",
+ "sats"
+ ],
+ "actionscript": [
+ "as"
+ ],
+ "ada": [
+ "adb",
+ "ada",
+ "ads"
+ ],
+ "agda": [
+ "agda"
+ ],
+ "alloy": [
+ "als"
+ ],
+ "apacheconf": [
+ "apacheconf"
+ ],
+ "apex": [
+ "cls"
+ ],
+ "applescript": [
+ "applescript",
+ "scpt"
+ ],
+ "arc": [
+ "arc"
+ ],
+ "arduino": [
+ "ino"
+ ],
+ "asciidoc": [
+ "asciidoc",
+ "adoc",
+ "asc"
+ ],
+ "aspectj": [
+ "aj"
+ ],
+ "assembly": [
+ "asm",
+ "a51",
+ "nasm"
+ ],
+ "augeas": [
+ "aug"
+ ],
+ "autohotkey": [
+ "ahk",
+ "ahkl"
+ ],
+ "autoit": [
+ "au3"
+ ],
+ "awk": [
+ "awk",
+ "auk",
+ "gawk",
+ "mawk",
+ "nawk"
+ ],
+ "batchfile": [
+ "bat",
+ "cmd"
+ ],
+ "befunge": [
+ "befunge"
+ ],
+ "bison": [
+ "y"
+ ],
+ "bitbake": [
+ "bb"
+ ],
+ "blitzbasic": [
+ "bb",
+ "decls"
+ ],
+ "blitzmax": [
+ "bmx"
+ ],
+ "bluespec": [
+ "bsv"
+ ],
+ "boo": [
+ "boo"
+ ],
+ "brainfuck": [
+ "b",
+ "bf"
+ ],
+ "brightscript": [
+ "brs"
+ ],
+ "bro": [
+ "bro"
+ ],
+ "c": [
+ "c",
+ "cats",
+ "h",
+ "idc",
+ "w"
+ ],
+ "c#": [
+ "cs",
+ "cshtml",
+ "csx"
+ ],
+ "c++": [
+ "cpp",
+ "c++",
+ "cc",
+ "cp",
+ "cxx",
+ "h",
+ "h++",
+ "hh",
+ "hpp",
+ "hxx",
+ "inl",
+ "ipp",
+ "tcc",
+ "tpp"
+ ],
+ "c-objdump": [
+ "c-objdump"
+ ],
+ "c2hs haskell": [
+ "chs"
+ ],
+ "clips": [
+ "clp"
+ ],
+ "cmake": [
+ "cmake",
+ "cmake.in"
+ ],
+ "cobol": [
+ "cob",
+ "cbl",
+ "ccp",
+ "cobol",
+ "cpy"
+ ],
+ "css": [
+ "css"
+ ],
+ "cap'n proto": [
+ "capnp"
+ ],
+ "cartocss": [
+ "mss"
+ ],
+ "ceylon": [
+ "ceylon"
+ ],
+ "chapel": [
+ "chpl"
+ ],
+ "chuck": [
+ "ck"
+ ],
+ "cirru": [
+ "cirru"
+ ],
+ "clarion": [
+ "clw"
+ ],
+ "clean": [
+ "icl",
+ "dcl"
+ ],
+ "clojure": [
+ "clj",
+ "boot",
+ "cl2",
+ "cljc",
+ "cljs",
+ "cljs.hl",
+ "cljscm",
+ "cljx",
+ "hic"
+ ],
+ "coffeescript": [
+ "coffee",
+ "_coffee",
+ "cjsx",
+ "cson",
+ "iced"
+ ],
+ "coldfusion": [
+ "cfm",
+ "cfml"
+ ],
+ "coldfusion cfc": [
+ "cfc"
+ ],
+ "common lisp": [
+ "lisp",
+ "asd",
+ "cl",
+ "lsp",
+ "ny",
+ "podsl"
+ ],
+ "component pascal": [
+ "cp",
+ "cps"
+ ],
+ "cool": [
+ "cl"
+ ],
+ "coq": [
+ "coq",
+ "v"
+ ],
+ "cpp-objdump": [
+ "cppobjdump",
+ "c++-objdump",
+ "c++objdump",
+ "cpp-objdump",
+ "cxx-objdump"
+ ],
+ "creole": [
+ "creole"
+ ],
+ "crystal": [
+ "cr"
+ ],
+ "cucumber": [
+ "feature"
+ ],
+ "cuda": [
+ "cu",
+ "cuh"
+ ],
+ "cycript": [
+ "cy"
+ ],
+ "cython": [
+ "pyx",
+ "pxd",
+ "pxi"
+ ],
+ "d": [
+ "d",
+ "di"
+ ],
+ "d-objdump": [
+ "d-objdump"
+ ],
+ "digital command language": [
+ "com"
+ ],
+ "dm": [
+ "dm"
+ ],
+ "dtrace": [
+ "d"
+ ],
+ "darcs patch": [
+ "darcspatch",
+ "dpatch"
+ ],
+ "dart": [
+ "dart"
+ ],
+ "diff": [
+ "diff",
+ "patch"
+ ],
+ "dockerfile": [
+ "dockerfile"
+ ],
+ "dogescript": [
+ "djs"
+ ],
+ "dylan": [
+ "dylan",
+ "dyl",
+ "intr",
+ "lid"
+ ],
+ "e": [
+ "E"
+ ],
+ "ecl": [
+ "ecl",
+ "eclxml"
+ ],
+ "eagle": [
+ "sch",
+ "brd"
+ ],
+ "ecere projects": [
+ "epj"
+ ],
+ "eiffel": [
+ "e"
+ ],
+ "elixir": [
+ "ex",
+ "exs"
+ ],
+ "elm": [
+ "elm"
+ ],
+ "emacs lisp": [
+ "el",
+ "emacs",
+ "emacs.desktop"
+ ],
+ "emberscript": [
+ "em",
+ "emberscript"
+ ],
+ "erlang": [
+ "erl",
+ "es",
+ "escript",
+ "hrl"
+ ],
+ "f#": [
+ "fs",
+ "fsi",
+ "fsx"
+ ],
+ "flux": [
+ "fx",
+ "flux"
+ ],
+ "fortran": [
+ "f90",
+ "f",
+ "f03",
+ "f08",
+ "f77",
+ "f95",
+ "for",
+ "fpp"
+ ],
+ "factor": [
+ "factor"
+ ],
+ "fancy": [
+ "fy",
+ "fancypack"
+ ],
+ "fantom": [
+ "fan"
+ ],
+ "filterscript": [
+ "fs"
+ ],
+ "formatted": [
+ "for"
+ ],
+ "forth": [
+ "fth",
+ "4th",
+ "f",
+ "for",
+ "forth",
+ "fr",
+ "frt",
+ "fs"
+ ],
+ "frege": [
+ "fr"
+ ],
+ "g-code": [
+ "g",
+ "gco",
+ "gcode"
+ ],
+ "gams": [
+ "gms"
+ ],
+ "gap": [
+ "g",
+ "gap",
+ "gd",
+ "gi",
+ "tst"
+ ],
+ "gas": [
+ "s"
+ ],
+ "gdscript": [
+ "gd"
+ ],
+ "glsl": [
+ "glsl",
+ "fp",
+ "frag",
+ "frg",
+ "fs",
+ "fshader",
+ "geo",
+ "geom",
+ "glslv",
+ "gshader",
+ "shader",
+ "vert",
+ "vrx",
+ "vshader"
+ ],
+ "game maker language": [
+ "gml"
+ ],
+ "genshi": [
+ "kid"
+ ],
+ "gentoo ebuild": [
+ "ebuild"
+ ],
+ "gentoo eclass": [
+ "eclass"
+ ],
+ "gettext catalog": [
+ "po",
+ "pot"
+ ],
+ "glyph": [
+ "glf"
+ ],
+ "gnuplot": [
+ "gp",
+ "gnu",
+ "gnuplot",
+ "plot",
+ "plt"
+ ],
+ "go": [
+ "go"
+ ],
+ "golo": [
+ "golo"
+ ],
+ "gosu": [
+ "gs",
+ "gst",
+ "gsx",
+ "vark"
+ ],
+ "grace": [
+ "grace"
+ ],
+ "gradle": [
+ "gradle"
+ ],
+ "grammatical framework": [
+ "gf"
+ ],
+ "graph modeling language": [
+ "gml"
+ ],
+ "graphviz (dot)": [
+ "dot",
+ "gv"
+ ],
+ "groff": [
+ "man",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7"
+ ],
+ "groovy": [
+ "groovy",
+ "grt",
+ "gtpl",
+ "gvy"
+ ],
+ "groovy server pages": [
+ "gsp"
+ ],
+ "html": [
+ "html",
+ "htm",
+ "html.hl",
+ "st",
+ "xht",
+ "xhtml"
+ ],
+ "html+django": [
+ "mustache",
+ "jinja"
+ ],
+ "html+erb": [
+ "erb",
+ "erb.deface"
+ ],
+ "html+php": [
+ "phtml"
+ ],
+ "http": [
+ "http"
+ ],
+ "hack": [
+ "hh",
+ "php"
+ ],
+ "haml": [
+ "haml",
+ "haml.deface"
+ ],
+ "handlebars": [
+ "handlebars",
+ "hbs"
+ ],
+ "harbour": [
+ "hb"
+ ],
+ "haskell": [
+ "hs",
+ "hsc"
+ ],
+ "haxe": [
+ "hx",
+ "hxsl"
+ ],
+ "hy": [
+ "hy"
+ ],
+ "idl": [
+ "pro",
+ "dlm"
+ ],
+ "igor pro": [
+ "ipf"
+ ],
+ "ini": [
+ "ini",
+ "cfg",
+ "prefs",
+ "pro",
+ "properties"
+ ],
+ "irc log": [
+ "irclog",
+ "weechatlog"
+ ],
+ "idris": [
+ "idr",
+ "lidr"
+ ],
+ "inform 7": [
+ "ni",
+ "i7x"
+ ],
+ "inno setup": [
+ "iss"
+ ],
+ "io": [
+ "io"
+ ],
+ "ioke": [
+ "ik"
+ ],
+ "isabelle": [
+ "thy"
+ ],
+ "j": [
+ "ijs"
+ ],
+ "jflex": [
+ "flex",
+ "jflex"
+ ],
+ "json": [
+ "json",
+ "lock"
+ ],
+ "json5": [
+ "json5"
+ ],
+ "jsonld": [
+ "jsonld"
+ ],
+ "jsoniq": [
+ "jq"
+ ],
+ "jade": [
+ "jade"
+ ],
+ "jasmin": [
+ "j"
+ ],
+ "java": [
+ "java"
+ ],
+ "java server pages": [
+ "jsp"
+ ],
+ "javascript": [
+ "js",
+ "_js",
+ "bones",
+ "es6",
+ "frag",
+ "gs",
+ "jake",
+ "jsb",
+ "jsfl",
+ "jsm",
+ "jss",
+ "jsx",
+ "njs",
+ "pac",
+ "sjs",
+ "ssjs",
+ "sublime-build",
+ "sublime-commands",
+ "sublime-completions",
+ "sublime-keymap",
+ "sublime-macro",
+ "sublime-menu",
+ "sublime-mousemap",
+ "sublime-project",
+ "sublime-settings",
+ "sublime-theme",
+ "sublime-workspace",
+ "sublime_metrics",
+ "sublime_session",
+ "xsjs",
+ "xsjslib"
+ ],
+ "julia": [
+ "jl"
+ ],
+ "krl": [
+ "krl"
+ ],
+ "kicad": [
+ "sch"
+ ],
+ "kit": [
+ "kit"
+ ],
+ "kotlin": [
+ "kt",
+ "ktm",
+ "kts"
+ ],
+ "lfe": [
+ "lfe"
+ ],
+ "llvm": [
+ "ll"
+ ],
+ "lolcode": [
+ "lol"
+ ],
+ "lsl": [
+ "lsl"
+ ],
+ "labview": [
+ "lvproj"
+ ],
+ "lasso": [
+ "lasso",
+ "las",
+ "lasso8",
+ "lasso9",
+ "ldml"
+ ],
+ "latte": [
+ "latte"
+ ],
+ "lean": [
+ "lean",
+ "hlean"
+ ],
+ "less": [
+ "less"
+ ],
+ "lilypond": [
+ "ly",
+ "ily"
+ ],
+ "limbo": [
+ "b",
+ "m"
+ ],
+ "linker script": [
+ "ld",
+ "lds"
+ ],
+ "liquid": [
+ "liquid"
+ ],
+ "literate agda": [
+ "lagda"
+ ],
+ "literate coffeescript": [
+ "litcoffee"
+ ],
+ "literate haskell": [
+ "lhs"
+ ],
+ "livescript": [
+ "ls",
+ "_ls"
+ ],
+ "logos": [
+ "xm",
+ "x",
+ "xi"
+ ],
+ "logtalk": [
+ "lgt",
+ "logtalk"
+ ],
+ "lookml": [
+ "lookml"
+ ],
+ "loomscript": [
+ "ls"
+ ],
+ "lua": [
+ "lua",
+ "fcgi",
+ "nse",
+ "pd_lua",
+ "rbxs",
+ "wlua"
+ ],
+ "m": [
+ "mumps",
+ "m"
+ ],
+ "mtml": [
+ "mtml"
+ ],
+ "muf": [
+ "muf",
+ "m"
+ ],
+ "makefile": [
+ "mak",
+ "d",
+ "mk"
+ ],
+ "mako": [
+ "mako",
+ "mao"
+ ],
+ "markdown": [
+ "md",
+ "markdown",
+ "mkd",
+ "mkdn",
+ "mkdown",
+ "ron"
+ ],
+ "mask": [
+ "mask"
+ ],
+ "mathematica": [
+ "mathematica",
+ "cdf",
+ "m",
+ "ma",
+ "nb",
+ "nbp",
+ "wl",
+ "wlt"
+ ],
+ "matlab": [
+ "matlab",
+ "m"
+ ],
+ "max": [
+ "maxpat",
+ "maxhelp",
+ "maxproj",
+ "mxt",
+ "pat"
+ ],
+ "mediawiki": [
+ "mediawiki"
+ ],
+ "mercury": [
+ "m",
+ "moo"
+ ],
+ "minid": [
+ "minid"
+ ],
+ "mirah": [
+ "druby",
+ "duby",
+ "mir",
+ "mirah"
+ ],
+ "modelica": [
+ "mo"
+ ],
+ "module management system": [
+ "mms",
+ "mmk"
+ ],
+ "monkey": [
+ "monkey"
+ ],
+ "moocode": [
+ "moo"
+ ],
+ "moonscript": [
+ "moon"
+ ],
+ "myghty": [
+ "myt"
+ ],
+ "nl": [
+ "nl"
+ ],
+ "nsis": [
+ "nsi",
+ "nsh"
+ ],
+ "nemerle": [
+ "n"
+ ],
+ "netlinx": [
+ "axs",
+ "axi"
+ ],
+ "netlinx+erb": [
+ "axs.erb",
+ "axi.erb"
+ ],
+ "netlogo": [
+ "nlogo"
+ ],
+ "newlisp": [
+ "nl",
+ "lisp",
+ "lsp"
+ ],
+ "nginx": [
+ "nginxconf"
+ ],
+ "nimrod": [
+ "nim",
+ "nimrod"
+ ],
+ "ninja": [
+ "ninja"
+ ],
+ "nit": [
+ "nit"
+ ],
+ "nix": [
+ "nix"
+ ],
+ "nu": [
+ "nu"
+ ],
+ "numpy": [
+ "numpy",
+ "numpyw",
+ "numsc"
+ ],
+ "ocaml": [
+ "ml",
+ "eliom",
+ "eliomi",
+ "ml4",
+ "mli",
+ "mll",
+ "mly"
+ ],
+ "objdump": [
+ "objdump"
+ ],
+ "objective-c": [
+ "m",
+ "h"
+ ],
+ "objective-c++": [
+ "mm"
+ ],
+ "objective-j": [
+ "j",
+ "sj"
+ ],
+ "omgrofl": [
+ "omgrofl"
+ ],
+ "opa": [
+ "opa"
+ ],
+ "opal": [
+ "opal"
+ ],
+ "opencl": [
+ "cl",
+ "opencl"
+ ],
+ "openedge abl": [
+ "p",
+ "cls"
+ ],
+ "openscad": [
+ "scad"
+ ],
+ "org": [
+ "org"
+ ],
+ "ox": [
+ "ox",
+ "oxh",
+ "oxo"
+ ],
+ "oxygene": [
+ "oxygene"
+ ],
+ "oz": [
+ "oz"
+ ],
+ "pawn": [
+ "pwn"
+ ],
+ "php": [
+ "php",
+ "aw",
+ "ctp",
+ "fcgi",
+ "php3",
+ "php4",
+ "php5",
+ "phpt"
+ ],
+ "plsql": [
+ "pls",
+ "pkb",
+ "pks",
+ "plb",
+ "plsql",
+ "sql"
+ ],
+ "plpgsql": [
+ "sql"
+ ],
+ "pan": [
+ "pan"
+ ],
+ "papyrus": [
+ "psc"
+ ],
+ "parrot": [
+ "parrot"
+ ],
+ "parrot assembly": [
+ "pasm"
+ ],
+ "parrot internal representation": [
+ "pir"
+ ],
+ "pascal": [
+ "pas",
+ "dfm",
+ "dpr",
+ "lpr",
+ "pp"
+ ],
+ "perl": [
+ "pl",
+ "cgi",
+ "fcgi",
+ "perl",
+ "ph",
+ "plx",
+ "pm",
+ "pod",
+ "psgi",
+ "t"
+ ],
+ "perl6": [
+ "6pl",
+ "6pm",
+ "nqp",
+ "p6",
+ "p6l",
+ "p6m",
+ "pl",
+ "pl6",
+ "pm",
+ "pm6",
+ "t"
+ ],
+ "piglatin": [
+ "pig"
+ ],
+ "pike": [
+ "pike",
+ "pmod"
+ ],
+ "pod": [
+ "pod"
+ ],
+ "pogoscript": [
+ "pogo"
+ ],
+ "postscript": [
+ "ps",
+ "eps"
+ ],
+ "powershell": [
+ "ps1",
+ "psd1",
+ "psm1"
+ ],
+ "processing": [
+ "pde"
+ ],
+ "prolog": [
+ "pl",
+ "ecl",
+ "pro",
+ "prolog"
+ ],
+ "propeller spin": [
+ "spin"
+ ],
+ "protocol buffer": [
+ "proto"
+ ],
+ "public key": [
+ "asc",
+ "pub"
+ ],
+ "puppet": [
+ "pp"
+ ],
+ "pure data": [
+ "pd"
+ ],
+ "purebasic": [
+ "pb",
+ "pbi"
+ ],
+ "purescript": [
+ "purs"
+ ],
+ "python": [
+ "py",
+ "cgi",
+ "fcgi",
+ "gyp",
+ "lmi",
+ "pyde",
+ "pyp",
+ "pyt",
+ "pyw",
+ "tac",
+ "wsgi",
+ "xpy"
+ ],
+ "python traceback": [
+ "pytb"
+ ],
+ "qml": [
+ "qml"
+ ],
+ "qmake": [
+ "pro",
+ "pri"
+ ],
+ "r": [
+ "r",
+ "rd",
+ "rsx"
+ ],
+ "raml": [
+ "raml"
+ ],
+ "rdoc": [
+ "rdoc"
+ ],
+ "realbasic": [
+ "rbbas",
+ "rbfrm",
+ "rbmnu",
+ "rbres",
+ "rbtbar",
+ "rbuistate"
+ ],
+ "rhtml": [
+ "rhtml"
+ ],
+ "rmarkdown": [
+ "rmd"
+ ],
+ "racket": [
+ "rkt",
+ "rktd",
+ "rktl",
+ "scrbl"
+ ],
+ "ragel in ruby host": [
+ "rl"
+ ],
+ "raw token data": [
+ "raw"
+ ],
+ "rebol": [
+ "reb",
+ "r",
+ "r2",
+ "r3",
+ "rebol"
+ ],
+ "red": [
+ "red",
+ "reds"
+ ],
+ "redcode": [
+ "cw"
+ ],
+ "robotframework": [
+ "robot"
+ ],
+ "rouge": [
+ "rg"
+ ],
+ "ruby": [
+ "rb",
+ "builder",
+ "fcgi",
+ "gemspec",
+ "god",
+ "irbrc",
+ "jbuilder",
+ "mspec",
+ "pluginspec",
+ "podspec",
+ "rabl",
+ "rake",
+ "rbuild",
+ "rbw",
+ "rbx",
+ "ru",
+ "ruby",
+ "thor",
+ "watchr"
+ ],
+ "rust": [
+ "rs"
+ ],
+ "sas": [
+ "sas"
+ ],
+ "scss": [
+ "scss"
+ ],
+ "sparql": [
+ "sparql",
+ "rq"
+ ],
+ "sqf": [
+ "sqf",
+ "hqf"
+ ],
+ "sql": [
+ "sql",
+ "cql",
+ "ddl",
+ "prc",
+ "tab",
+ "udf",
+ "viw"
+ ],
+ "sqlpl": [
+ "sql",
+ "db2"
+ ],
+ "ston": [
+ "ston"
+ ],
+ "svg": [
+ "svg"
+ ],
+ "sage": [
+ "sage",
+ "sagews"
+ ],
+ "saltstack": [
+ "sls"
+ ],
+ "sass": [
+ "sass"
+ ],
+ "scala": [
+ "scala",
+ "sbt",
+ "sc"
+ ],
+ "scaml": [
+ "scaml"
+ ],
+ "scheme": [
+ "scm",
+ "sld",
+ "sls",
+ "sps",
+ "ss"
+ ],
+ "scilab": [
+ "sci",
+ "sce",
+ "tst"
+ ],
+ "self": [
+ "self"
+ ],
+ "shell": [
+ "sh",
+ "bash",
+ "bats",
+ "cgi",
+ "command",
+ "fcgi",
+ "ksh",
+ "tmux",
+ "tool",
+ "zsh"
+ ],
+ "shellsession": [
+ "sh-session"
+ ],
+ "shen": [
+ "shen"
+ ],
+ "slash": [
+ "sl"
+ ],
+ "slim": [
+ "slim"
+ ],
+ "smalltalk": [
+ "st",
+ "cs"
+ ],
+ "smarty": [
+ "tpl"
+ ],
+ "sourcepawn": [
+ "sp",
+ "sma"
+ ],
+ "squirrel": [
+ "nut"
+ ],
+ "standard ml": [
+ "ML",
+ "fun",
+ "sig",
+ "sml"
+ ],
+ "stata": [
+ "do",
+ "ado",
+ "doh",
+ "ihlp",
+ "mata",
+ "matah",
+ "sthlp"
+ ],
+ "stylus": [
+ "styl"
+ ],
+ "supercollider": [
+ "scd",
+ "sc"
+ ],
+ "swift": [
+ "swift"
+ ],
+ "systemverilog": [
+ "sv",
+ "svh",
+ "vh"
+ ],
+ "toml": [
+ "toml"
+ ],
+ "txl": [
+ "txl"
+ ],
+ "tcl": [
+ "tcl",
+ "adp",
+ "tm"
+ ],
+ "tcsh": [
+ "tcsh",
+ "csh"
+ ],
+ "tex": [
+ "tex",
+ "aux",
+ "bbx",
+ "bib",
+ "cbx",
+ "cls",
+ "dtx",
+ "ins",
+ "lbx",
+ "ltx",
+ "mkii",
+ "mkiv",
+ "mkvi",
+ "sty",
+ "toc"
+ ],
+ "tea": [
+ "tea"
+ ],
+ "text": [
+ "txt",
+ "fr"
+ ],
+ "textile": [
+ "textile"
+ ],
+ "thrift": [
+ "thrift"
+ ],
+ "turing": [
+ "t",
+ "tu"
+ ],
+ "turtle": [
+ "ttl"
+ ],
+ "twig": [
+ "twig"
+ ],
+ "typescript": [
+ "ts"
+ ],
+ "unified parallel c": [
+ "upc"
+ ],
+ "unrealscript": [
+ "uc"
+ ],
+ "vcl": [
+ "vcl"
+ ],
+ "vhdl": [
+ "vhdl",
+ "vhd",
+ "vhf",
+ "vhi",
+ "vho",
+ "vhs",
+ "vht",
+ "vhw"
+ ],
+ "vala": [
+ "vala",
+ "vapi"
+ ],
+ "verilog": [
+ "v",
+ "veo"
+ ],
+ "viml": [
+ "vim"
+ ],
+ "visual basic": [
+ "vb",
+ "bas",
+ "cls",
+ "frm",
+ "frx",
+ "vba",
+ "vbhtml",
+ "vbs"
+ ],
+ "volt": [
+ "volt"
+ ],
+ "web ontology language": [
+ "owl"
+ ],
+ "webidl": [
+ "webidl"
+ ],
+ "xc": [
+ "xc"
+ ],
+ "xml": [
+ "xml",
+ "ant",
+ "axml",
+ "ccxml",
+ "clixml",
+ "cproject",
+ "csproj",
+ "ct",
+ "dita",
+ "ditamap",
+ "ditaval",
+ "dll.config",
+ "filters",
+ "fsproj",
+ "fxml",
+ "glade",
+ "grxml",
+ "ivy",
+ "jelly",
+ "kml",
+ "launch",
+ "mm",
+ "mxml",
+ "nproj",
+ "nuspec",
+ "odd",
+ "osm",
+ "plist",
+ "pluginspec",
+ "ps1xml",
+ "psc1",
+ "pt",
+ "rdf",
+ "rss",
+ "scxml",
+ "srdf",
+ "storyboard",
+ "stTheme",
+ "sublime-snippet",
+ "targets",
+ "tmCommand",
+ "tml",
+ "tmLanguage",
+ "tmPreferences",
+ "tmSnippet",
+ "tmTheme",
+ "ts",
+ "ui",
+ "urdf",
+ "vbproj",
+ "vcxproj",
+ "vxml",
+ "wsdl",
+ "wsf",
+ "wxi",
+ "wxl",
+ "wxs",
+ "x3d",
+ "xacro",
+ "xaml",
+ "xib",
+ "xlf",
+ "xliff",
+ "xmi",
+ "xml.dist",
+ "xsd",
+ "xul",
+ "zcml"
+ ],
+ "xproc": [
+ "xpl",
+ "xproc"
+ ],
+ "xquery": [
+ "xquery",
+ "xq",
+ "xql",
+ "xqm",
+ "xqy"
+ ],
+ "xs": [
+ "xs"
+ ],
+ "xslt": [
+ "xslt",
+ "xsl"
+ ],
+ "xojo": [
+ "xojo_code",
+ "xojo_menu",
+ "xojo_report",
+ "xojo_script",
+ "xojo_toolbar",
+ "xojo_window"
+ ],
+ "xtend": [
+ "xtend"
+ ],
+ "yaml": [
+ "yml",
+ "reek",
+ "rviz",
+ "yaml"
+ ],
+ "zephir": [
+ "zep"
+ ],
+ "zimpl": [
+ "zimpl",
+ "zmpl",
+ "zpl"
+ ],
+ "desktop": [
+ "desktop",
+ "desktop.in"
+ ],
+ "ec": [
+ "ec",
+ "eh"
+ ],
+ "edn": [
+ "edn"
+ ],
+ "fish": [
+ "fish"
+ ],
+ "mupad": [
+ "mu"
+ ],
+ "nesc": [
+ "nc"
+ ],
+ "ooc": [
+ "ooc"
+ ],
+ "restructuredtext": [
+ "rst",
+ "rest"
+ ],
+ "wisp": [
+ "wisp"
+ ],
+ "xbase": [
+ "prg"
+ ]
+}
diff --git a/favicon.png b/favicon.png
Binary files differ.
diff --git a/get_lang.c b/get_lang.c
@@ -0,0 +1,87 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cJSON.h"
+
+// Function to find language based on extension
+const char* getLanguageByExtension(const char *extension, cJSON *langMap) {
+ cJSON *languageItem;
+ cJSON_ArrayForEach(languageItem, langMap) {
+ const char *language = languageItem->string;
+ cJSON *extensions = cJSON_GetObjectItemCaseSensitive(langMap, language);
+
+ cJSON *extItem;
+ cJSON_ArrayForEach(extItem, extensions) {
+ if (strcmp(extension, extItem->valuestring) == 0) {
+ return language;
+ }
+ }
+ }
+ return NULL;
+}
+
+// Function to load and parse the JSON file
+cJSON* loadLangMapFromFile(const char *filename) {
+ FILE *file = fopen(filename, "r");
+ if (!file) {
+ printf("Unable to open file: %s\n", filename);
+ return NULL;
+ }
+
+ // Get the file size
+ fseek(file, 0, SEEK_END);
+ long length = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ // Read the file into a string
+ char *data = (char*)malloc(length + 1);
+ if (!data) {
+ printf("Unable to allocate memory\n");
+ fclose(file);
+ return NULL;
+ }
+ fread(data, 1, length, file);
+ data[length] = '\0'; // Null-terminate the string
+
+ fclose(file);
+
+ // Parse the JSON string
+ cJSON *langMap = cJSON_Parse(data);
+ free(data); // Free the memory allocated for the file content
+
+ if (!langMap) {
+ printf("Error parsing JSON file\n");
+ return NULL;
+ }
+
+ return langMap;
+}
+
+int main() {
+ const char *filename = "exts.json";
+
+ // Load the JSON data
+ cJSON *langMap = loadLangMapFromFile(filename);
+ if (!langMap) {
+ return 1; // Exit if there was an error loading the JSON
+ }
+
+ // Find the language for a given file extension
+ const char *extensions[] = {"py", "rs", "c", "h", "js", "java", "cpp"};
+ int len = 7;
+
+ for (int i = 0; i < len; i++) {
+ const char *ext = extensions[i];
+ const char *language = getLanguageByExtension(ext, langMap);
+ if (language) {
+ printf("The language for extension '%s' is: %s\n", ext, language);
+ } else {
+ printf("No language found for extension '%s'\n", ext);
+ }
+ }
+
+ // Clean up and free the JSON structure
+ cJSON_Delete(langMap);
+
+ return 0;
+}
diff --git a/get_lang.h b/get_lang.h
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/logo.png b/logo.png
Binary files differ.
diff --git a/reallocarray.c b/reallocarray.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
diff --git a/reallocarray.o b/reallocarray.o
Binary files differ.
diff --git a/stagit b/stagit
Binary files differ.
diff --git a/stagit-index b/stagit-index
Binary files differ.
diff --git a/stagit-index.1 b/stagit-index.1
@@ -0,0 +1,47 @@
+.Dd August 2, 2021
+.Dt STAGIT-INDEX 1
+.Os
+.Sh NAME
+.Nm stagit-index
+.Nd static git index page generator
+.Sh SYNOPSIS
+.Nm
+.Op Ar repodir...
+.Sh DESCRIPTION
+.Nm
+will create an index HTML page for the repositories specified and writes
+the HTML data to stdout.
+The repos in the index are in the same order as the arguments
+.Ar repodir
+specified.
+.Pp
+The basename of the directory is used as the repository name.
+The suffix ".git" is removed from the basename, this suffix is commonly used
+for "bare" repos.
+.Pp
+The content of the follow files specifies the meta data for each repository:
+.Bl -tag -width Ds
+.It .git/description or description (bare repos).
+description
+.It .git/owner or owner (bare repo).
+owner of repository
+.El
+.Pp
+For changing the style of the page you can use the following files:
+.Bl -tag -width Ds
+.It favicon.png
+favicon image.
+.It logo.png
+32x32 logo.
+.It style.css
+CSS stylesheet.
+.El
+.Sh EXAMPLES
+.Bd -literal
+cd htmlroot
+stagit-index path/to/gitrepo1 path/to/gitrepo2 > index.html
+.Ed
+.Sh SEE ALSO
+.Xr stagit 1
+.Sh AUTHORS
+.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
diff --git a/stagit-index.c b/stagit-index.c
@@ -0,0 +1,263 @@
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <git2.h>
+
+static git_repository *repo;
+
+static const char *relpath = "";
+
+static char description[255] = "Repositories";
+static char *name = "";
+static char owner[255];
+
+/* Handle read or write errors for a FILE * stream */
+void
+checkfileerror(FILE *fp, const char *name, int mode)
+{
+ if (mode == 'r' && ferror(fp))
+ errx(1, "read error: %s", name);
+ else if (mode == 'w' && (fflush(fp) || ferror(fp)))
+ errx(1, "write error: %s", name);
+}
+
+void
+joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
+{
+ int r;
+
+ r = snprintf(buf, bufsiz, "%s%s%s",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+ if (r < 0 || (size_t)r >= bufsiz)
+ errx(1, "path truncated: '%s%s%s'",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+}
+
+/* Percent-encode, see RFC3986 section 2.1. */
+void
+percentencode(FILE *fp, const char *s, size_t len)
+{
+ static char tab[] = "0123456789ABCDEF";
+ unsigned char uc;
+ size_t i;
+
+ for (i = 0; *s && i < len; s++, i++) {
+ uc = *s;
+ /* NOTE: do not encode '/' for paths or ",-." */
+ if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') ||
+ uc == '[' || uc == ']') {
+ putc('%', fp);
+ putc(tab[(uc >> 4) & 0x0f], fp);
+ putc(tab[uc & 0x0f], fp);
+ } else {
+ putc(uc, fp);
+ }
+ }
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0. */
+void
+xmlencode(FILE *fp, const char *s, size_t len)
+{
+ size_t i;
+
+ for (i = 0; *s && i < len; s++, i++) {
+ switch(*s) {
+ case '<': fputs("<", fp); break;
+ case '>': fputs(">", fp); break;
+ case '\'': fputs("'" , fp); break;
+ case '&': fputs("&", fp); break;
+ case '"': fputs(""", fp); break;
+ default: putc(*s, fp);
+ }
+ }
+}
+
+void
+printtimeshort(FILE *fp, const git_time *intime)
+{
+ struct tm *intm;
+ time_t t;
+ char out[32];
+
+ t = (time_t)intime->time;
+ if (!(intm = gmtime(&t)))
+ return;
+ strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
+ fputs(out, fp);
+}
+
+void
+writeheader(FILE *fp)
+{
+ fputs("<!DOCTYPE html>\n"
+ "<html>\n<head>\n"
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
+ "<title>", fp);
+ xmlencode(fp, description, strlen(description));
+ fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
+ fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
+ fputs("</head>\n<body>\n", fp);
+ fprintf(fp, "<table>\n<tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>\n"
+ "<td><span class=\"desc\">", relpath);
+ xmlencode(fp, description, strlen(description));
+ fputs("</span></td></tr><tr><td></td><td>\n"
+ "</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n"
+ "<table id=\"index\"><thead>\n"
+ "<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>"
+ "<td><b>Last commit</b></td></tr>"
+ "</thead><tbody>\n", fp);
+}
+
+void
+writefooter(FILE *fp)
+{
+ fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp);
+}
+
+int
+writelog(FILE *fp)
+{
+ git_commit *commit = NULL;
+ const git_signature *author;
+ git_revwalk *w = NULL;
+ git_oid id;
+ char *stripped_name = NULL, *p;
+ int ret = 0;
+
+ git_revwalk_new(&w, repo);
+ git_revwalk_push_head(w);
+
+ if (git_revwalk_next(&id, w) ||
+ git_commit_lookup(&commit, repo, &id)) {
+ ret = -1;
+ goto err;
+ }
+
+ author = git_commit_author(commit);
+
+ /* strip .git suffix */
+ if (!(stripped_name = strdup(name)))
+ err(1, "strdup");
+ if ((p = strrchr(stripped_name, '.')))
+ if (!strcmp(p, ".git"))
+ *p = '\0';
+
+ fputs("<tr><td><a href=\"", fp);
+ percentencode(fp, stripped_name, strlen(stripped_name));
+ /*
+ * changing landing page on repo from log.html to files.html
+ */
+ //fputs("/log.html\">", fp);
+ fputs("/files.html\">", fp);
+
+ xmlencode(fp, stripped_name, strlen(stripped_name));
+ fputs("</a></td><td>", fp);
+ xmlencode(fp, description, strlen(description));
+ fputs("</td><td>", fp);
+ xmlencode(fp, owner, strlen(owner));
+ fputs("</td><td>", fp);
+ if (author)
+ printtimeshort(fp, &(author->when));
+ fputs("</td></tr>", fp);
+
+ git_commit_free(commit);
+err:
+ git_revwalk_free(w);
+ free(stripped_name);
+
+ return ret;
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ char path[PATH_MAX], repodirabs[PATH_MAX + 1];
+ const char *repodir;
+ int i, ret = 0;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s [repodir...]\n", argv[0]);
+ return 1;
+ }
+
+ /* do not search outside the git repository:
+ GIT_CONFIG_LEVEL_APP is the highest level currently */
+ git_libgit2_init();
+ for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
+ git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
+ /* do not require the git repository to be owned by the current user */
+ git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
+
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ writeheader(stdout);
+
+ for (i = 1; i < argc; i++) {
+ repodir = argv[i];
+ if (!realpath(repodir, repodirabs))
+ err(1, "realpath");
+
+ if (git_repository_open_ext(&repo, repodir,
+ GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
+ fprintf(stderr, "%s: cannot open repository\n", argv[0]);
+ ret = 1;
+ continue;
+ }
+
+ /* use directory name as name */
+ if ((name = strrchr(repodirabs, '/')))
+ name++;
+ else
+ name = "";
+
+ /* read description or .git/description */
+ joinpath(path, sizeof(path), repodir, "description");
+ if (!(fp = fopen(path, "r"))) {
+ joinpath(path, sizeof(path), repodir, ".git/description");
+ fp = fopen(path, "r");
+ }
+ description[0] = '\0';
+ if (fp) {
+ if (!fgets(description, sizeof(description), fp))
+ description[0] = '\0';
+ checkfileerror(fp, "description", 'r');
+ fclose(fp);
+ }
+
+ /* read owner or .git/owner */
+ joinpath(path, sizeof(path), repodir, "owner");
+ if (!(fp = fopen(path, "r"))) {
+ joinpath(path, sizeof(path), repodir, ".git/owner");
+ fp = fopen(path, "r");
+ }
+ owner[0] = '\0';
+ if (fp) {
+ if (!fgets(owner, sizeof(owner), fp))
+ owner[0] = '\0';
+ checkfileerror(fp, "owner", 'r');
+ fclose(fp);
+ owner[strcspn(owner, "\n")] = '\0';
+ }
+ writelog(stdout);
+ }
+ writefooter(stdout);
+
+ /* cleanup */
+ git_repository_free(repo);
+ git_libgit2_shutdown();
+
+ checkfileerror(stdout, "<stdout>", 'w');
+
+ return ret;
+}
diff --git a/stagit-index.o b/stagit-index.o
Binary files differ.
diff --git a/stagit.1 b/stagit.1
@@ -0,0 +1,125 @@
+.Dd August 2, 2021
+.Dt STAGIT 1
+.Os
+.Sh NAME
+.Nm stagit
+.Nd static git page generator
+.Sh SYNOPSIS
+.Nm
+.Op Fl c Ar cachefile
+.Op Fl l Ar commits
+.Op Fl u Ar baseurl
+.Ar repodir
+.Sh DESCRIPTION
+.Nm
+writes HTML pages for the repository
+.Ar repodir
+to the current directory.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar cachefile
+Cache the entries of the log page up to the point of
+the last commit.
+The
+.Ar cachefile
+will store the last commit id and the entries in the HTML table.
+It is up to the user to make sure the state of the
+.Ar cachefile
+is in sync with the history of the repository.
+.It Fl l Ar commits
+Write a maximum number of
+.Ar commits
+to the log.html file only.
+However the commit files are written as usual.
+.It Fl u Ar baseurl
+Base URL to make links in the Atom feeds absolute.
+For example: "https://git.codemadness.org/stagit/".
+.El
+.Pp
+The options
+.Fl c
+and
+.Fl l
+cannot be used at the same time.
+.Pp
+The following files will be written:
+.Bl -tag -width Ds
+.It atom.xml
+Atom XML feed of the last 100 commits.
+.It tags.xml
+Atom XML feed of the tags.
+.It files.html
+List of files in the latest tree, linking to the file.
+.It log.html
+List of commits in reverse chronological applied commit order, each commit
+links to a page with a diffstat and diff of the commit.
+.It refs.html
+Lists references of the repository such as branches and tags.
+.El
+.Pp
+For each entry in HEAD a file will be written in the format:
+file/filepath.html.
+This file will contain the textual data of the file prefixed by line numbers.
+The file will have the string "Binary file" if the data is considered to be
+non-textual.
+.Pp
+For each commit a file will be written in the format:
+commit/commitid.html.
+This file will contain the diffstat and diff of the commit.
+It will write the string "Binary files differ" if the data is considered to
+be non-textual.
+Too large diffs will be suppressed and a string
+"Diff is too large, output suppressed" will be written.
+.Pp
+When a commit HTML file exists it won't be overwritten again, note that if
+you've changed
+.Nm
+or changed one of the metadata files of the repository it is recommended to
+recreate all the output files because it will contain old data.
+To do this remove the output directory and
+.Ar cachefile ,
+then recreate the files.
+.Pp
+The basename of the directory is used as the repository name.
+The suffix ".git" is removed from the basename, this suffix is commonly used
+for "bare" repos.
+.Pp
+The content of the follow files specifies the metadata for each repository:
+.Bl -tag -width Ds
+.It .git/description or description (bare repo).
+description
+.It .git/owner or owner (bare repo).
+owner of repository
+.It .git/url or url (bare repo).
+primary clone URL of the repository, for example:
+git://git.codemadness.org/stagit
+.El
+.Pp
+When a README or LICENSE file exists in HEAD or a .gitmodules submodules file
+exists in HEAD a direct link in the menu is made.
+.Pp
+For changing the style of the page you can use the following files:
+.Bl -tag -width Ds
+.It favicon.png
+favicon image.
+.It logo.png
+32x32 logo.
+.It style.css
+CSS stylesheet.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Bd -literal
+mkdir -p htmlroot/htmlrepo1 && cd htmlroot/htmlrepo1
+stagit path/to/gitrepo1
+# repeat for other repositories.
+.Ed
+.Pp
+To update the HTML files when the repository is changed a git post-receive hook
+can be used, see the file example_post-receive.sh for an example.
+.Sh SEE ALSO
+.Xr stagit-index 1
+.Sh AUTHORS
+.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
diff --git a/stagit.c b/stagit.c
@@ -0,0 +1,1487 @@
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <git2.h>
+
+#include "compat.h"
+#include "get_lang.h" // for the get_lang function
+
+#define LEN(s) (sizeof(s)/sizeof(*s))
+
+struct deltainfo {
+ git_patch *patch;
+
+ size_t addcount;
+ size_t delcount;
+};
+
+struct commitinfo {
+ const git_oid *id;
+
+ char oid[GIT_OID_HEXSZ + 1];
+ char parentoid[GIT_OID_HEXSZ + 1];
+
+ const git_signature *author;
+ const git_signature *committer;
+ const char *summary;
+ const char *msg;
+
+ git_diff *diff;
+ git_commit *commit;
+ git_commit *parent;
+ git_tree *commit_tree;
+ git_tree *parent_tree;
+
+ size_t addcount;
+ size_t delcount;
+ size_t filecount;
+
+ struct deltainfo **deltas;
+ size_t ndeltas;
+};
+
+/* reference and associated data for sorting */
+struct referenceinfo {
+ struct git_reference *ref;
+ struct commitinfo *ci;
+};
+
+static git_repository *repo;
+
+static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */
+static const char *relpath = "";
+static const char *repodir;
+
+static char *name = "";
+static char *strippedname = "";
+static char description[255];
+static char cloneurl[1024];
+static char *submodules;
+static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" };
+static char *license;
+static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" };
+static char *readme;
+static long long nlogcommits = -1; /* -1 indicates not used */
+
+/* cache */
+static git_oid lastoid;
+static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
+static FILE *rcachefp, *wcachefp;
+static const char *cachefile;
+
+/* Handle read or write errors for a FILE * stream */
+void
+checkfileerror(FILE *fp, const char *name, int mode)
+{
+ if (mode == 'r' && ferror(fp))
+ errx(1, "read error: %s", name);
+ else if (mode == 'w' && (fflush(fp) || ferror(fp)))
+ errx(1, "write error: %s", name);
+}
+
+void
+joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
+{
+ int r;
+
+ r = snprintf(buf, bufsiz, "%s%s%s",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+ if (r < 0 || (size_t)r >= bufsiz)
+ errx(1, "path truncated: '%s%s%s'",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+}
+
+void
+deltainfo_free(struct deltainfo *di)
+{
+ if (!di)
+ return;
+ git_patch_free(di->patch);
+ memset(di, 0, sizeof(*di));
+ free(di);
+}
+
+int
+commitinfo_getstats(struct commitinfo *ci)
+{
+ struct deltainfo *di;
+ git_diff_options opts;
+ git_diff_find_options fopts;
+ const git_diff_delta *delta;
+ const git_diff_hunk *hunk;
+ const git_diff_line *line;
+ git_patch *patch = NULL;
+ size_t ndeltas, nhunks, nhunklines;
+ size_t i, j, k;
+
+ if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
+ goto err;
+ if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
+ if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
+ ci->parent = NULL;
+ ci->parent_tree = NULL;
+ }
+ }
+
+ git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
+ opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
+ GIT_DIFF_IGNORE_SUBMODULES |
+ GIT_DIFF_INCLUDE_TYPECHANGE;
+ if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
+ goto err;
+
+ if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
+ goto err;
+ /* find renames and copies, exact matches (no heuristic) for renames. */
+ fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ if (git_diff_find_similar(ci->diff, &fopts))
+ goto err;
+
+ ndeltas = git_diff_num_deltas(ci->diff);
+ if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
+ err(1, "calloc");
+
+ for (i = 0; i < ndeltas; i++) {
+ if (git_patch_from_diff(&patch, ci->diff, i))
+ goto err;
+
+ if (!(di = calloc(1, sizeof(struct deltainfo))))
+ err(1, "calloc");
+ di->patch = patch;
+ ci->deltas[i] = di;
+
+ delta = git_patch_get_delta(patch);
+
+ /* skip stats for binary data */
+ if (delta->flags & GIT_DIFF_FLAG_BINARY)
+ continue;
+
+ nhunks = git_patch_num_hunks(patch);
+ for (j = 0; j < nhunks; j++) {
+ if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
+ break;
+ for (k = 0; ; k++) {
+ if (git_patch_get_line_in_hunk(&line, patch, j, k))
+ break;
+ if (line->old_lineno == -1) {
+ di->addcount++;
+ ci->addcount++;
+ } else if (line->new_lineno == -1) {
+ di->delcount++;
+ ci->delcount++;
+ }
+ }
+ }
+ }
+ ci->ndeltas = i;
+ ci->filecount = i;
+
+ return 0;
+
+err:
+ git_diff_free(ci->diff);
+ ci->diff = NULL;
+ git_tree_free(ci->commit_tree);
+ ci->commit_tree = NULL;
+ git_tree_free(ci->parent_tree);
+ ci->parent_tree = NULL;
+ git_commit_free(ci->parent);
+ ci->parent = NULL;
+
+ if (ci->deltas)
+ for (i = 0; i < ci->ndeltas; i++)
+ deltainfo_free(ci->deltas[i]);
+ free(ci->deltas);
+ ci->deltas = NULL;
+ ci->ndeltas = 0;
+ ci->addcount = 0;
+ ci->delcount = 0;
+ ci->filecount = 0;
+
+ return -1;
+}
+
+void
+commitinfo_free(struct commitinfo *ci)
+{
+ size_t i;
+
+ if (!ci)
+ return;
+ if (ci->deltas)
+ for (i = 0; i < ci->ndeltas; i++)
+ deltainfo_free(ci->deltas[i]);
+
+ free(ci->deltas);
+ git_diff_free(ci->diff);
+ git_tree_free(ci->commit_tree);
+ git_tree_free(ci->parent_tree);
+ git_commit_free(ci->commit);
+ git_commit_free(ci->parent);
+ memset(ci, 0, sizeof(*ci));
+ free(ci);
+}
+
+struct commitinfo *
+commitinfo_getbyoid(const git_oid *id)
+{
+ struct commitinfo *ci;
+
+ if (!(ci = calloc(1, sizeof(struct commitinfo))))
+ err(1, "calloc");
+
+ if (git_commit_lookup(&(ci->commit), repo, id))
+ goto err;
+ ci->id = id;
+
+ git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
+ git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
+
+ ci->author = git_commit_author(ci->commit);
+ ci->committer = git_commit_committer(ci->commit);
+ ci->summary = git_commit_summary(ci->commit);
+ ci->msg = git_commit_message(ci->commit);
+
+ return ci;
+
+err:
+ commitinfo_free(ci);
+
+ return NULL;
+}
+
+int
+refs_cmp(const void *v1, const void *v2)
+{
+ const struct referenceinfo *r1 = v1, *r2 = v2;
+ time_t t1, t2;
+ int r;
+
+ if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref)))
+ return r;
+
+ t1 = r1->ci->author ? r1->ci->author->when.time : 0;
+ t2 = r2->ci->author ? r2->ci->author->when.time : 0;
+ if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1)))
+ return r;
+
+ return strcmp(git_reference_shorthand(r1->ref),
+ git_reference_shorthand(r2->ref));
+}
+
+int
+getrefs(struct referenceinfo **pris, size_t *prefcount)
+{
+ struct referenceinfo *ris = NULL;
+ struct commitinfo *ci = NULL;
+ git_reference_iterator *it = NULL;
+ const git_oid *id = NULL;
+ git_object *obj = NULL;
+ git_reference *dref = NULL, *r, *ref = NULL;
+ size_t i, refcount;
+
+ *pris = NULL;
+ *prefcount = 0;
+
+ if (git_reference_iterator_new(&it, repo))
+ return -1;
+
+ for (refcount = 0; !git_reference_next(&ref, it); ) {
+ if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) {
+ git_reference_free(ref);
+ ref = NULL;
+ continue;
+ }
+
+ switch (git_reference_type(ref)) {
+ case GIT_REF_SYMBOLIC:
+ if (git_reference_resolve(&dref, ref))
+ goto err;
+ r = dref;
+ break;
+ case GIT_REF_OID:
+ r = ref;
+ break;
+ default:
+ continue;
+ }
+ if (!git_reference_target(r) ||
+ git_reference_peel(&obj, r, GIT_OBJ_ANY))
+ goto err;
+ if (!(id = git_object_id(obj)))
+ goto err;
+ if (!(ci = commitinfo_getbyoid(id)))
+ break;
+
+ if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
+ err(1, "realloc");
+ ris[refcount].ci = ci;
+ ris[refcount].ref = r;
+ refcount++;
+
+ git_object_free(obj);
+ obj = NULL;
+ git_reference_free(dref);
+ dref = NULL;
+ }
+ git_reference_iterator_free(it);
+
+ /* sort by type, date then shorthand name */
+ qsort(ris, refcount, sizeof(*ris), refs_cmp);
+
+ *pris = ris;
+ *prefcount = refcount;
+
+ return 0;
+
+err:
+ git_object_free(obj);
+ git_reference_free(dref);
+ commitinfo_free(ci);
+ for (i = 0; i < refcount; i++) {
+ commitinfo_free(ris[i].ci);
+ git_reference_free(ris[i].ref);
+ }
+ free(ris);
+
+ return -1;
+}
+
+FILE *
+efopen(const char *filename, const char *flags)
+{
+ FILE *fp;
+
+ if (!(fp = fopen(filename, flags)))
+ err(1, "fopen: '%s'", filename);
+
+ return fp;
+}
+
+/* Percent-encode, see RFC3986 section 2.1. */
+void
+percentencode(FILE *fp, const char *s, size_t len)
+{
+ static char tab[] = "0123456789ABCDEF";
+ unsigned char uc;
+ size_t i;
+
+ for (i = 0; *s && i < len; s++, i++) {
+ uc = *s;
+ /* NOTE: do not encode '/' for paths or ",-." */
+ if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') ||
+ uc == '[' || uc == ']') {
+ putc('%', fp);
+ putc(tab[(uc >> 4) & 0x0f], fp);
+ putc(tab[uc & 0x0f], fp);
+ } else {
+ putc(uc, fp);
+ }
+ }
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0. */
+void
+xmlencode(FILE *fp, const char *s, size_t len)
+{
+ size_t i;
+
+ for (i = 0; *s && i < len; s++, i++) {
+ switch(*s) {
+ case '<': fputs("<", fp); break;
+ case '>': fputs(">", fp); break;
+ case '\'': fputs("'", fp); break;
+ case '&': fputs("&", fp); break;
+ case '"': fputs(""", fp); break;
+ default: putc(*s, fp);
+ }
+ }
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0, ignore printing '\r', '\n' */
+void
+xmlencodeline(FILE *fp, const char *s, size_t len)
+{
+ size_t i;
+
+ for (i = 0; *s && i < len; s++, i++) {
+ switch(*s) {
+ case '<': fputs("<", fp); break;
+ case '>': fputs(">", fp); break;
+ case '\'': fputs("'", fp); break;
+ case '&': fputs("&", fp); break;
+ case '"': fputs(""", fp); break;
+ case '\r': break; /* ignore CR */
+ case '\n': break; /* ignore LF */
+ default: putc(*s, fp);
+ }
+ }
+}
+
+int
+mkdirp(const char *path)
+{
+ char tmp[PATH_MAX], *p;
+
+ if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
+ errx(1, "path truncated: '%s'", path);
+ for (p = tmp + (tmp[0] == '/'); *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = '\0';
+ if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
+ return -1;
+ *p = '/';
+ }
+ if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
+ return -1;
+ return 0;
+}
+
+void
+printtimez(FILE *fp, const git_time *intime)
+{
+ struct tm *intm;
+ time_t t;
+ char out[32];
+
+ t = (time_t)intime->time;
+ if (!(intm = gmtime(&t)))
+ return;
+ strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
+ fputs(out, fp);
+}
+
+void
+printtime(FILE *fp, const git_time *intime)
+{
+ struct tm *intm;
+ time_t t;
+ char out[32];
+
+ t = (time_t)intime->time + (intime->offset * 60);
+ if (!(intm = gmtime(&t)))
+ return;
+ strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
+ if (intime->offset < 0)
+ fprintf(fp, "%s -%02d%02d", out,
+ -(intime->offset) / 60, -(intime->offset) % 60);
+ else
+ fprintf(fp, "%s +%02d%02d", out,
+ intime->offset / 60, intime->offset % 60);
+}
+
+void
+printtimeshort(FILE *fp, const git_time *intime)
+{
+ struct tm *intm;
+ time_t t;
+ char out[32];
+
+ t = (time_t)intime->time;
+ if (!(intm = gmtime(&t)))
+ return;
+ strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
+ fputs(out, fp);
+}
+
+void
+writeheader(FILE *fp, const char *title)
+{
+ fputs("<!DOCTYPE html>\n"
+ "<html lang=\"en\">\n<head>\n"
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
+ "<title>", fp);
+ xmlencode(fp, title, strlen(title));
+ if (title[0] && strippedname[0])
+ fputs(" - ", fp);
+ xmlencode(fp, strippedname, strlen(strippedname));
+ if (description[0])
+ fputs(" - ", fp);
+ xmlencode(fp, description, strlen(description));
+ fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
+ fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp);
+ xmlencode(fp, name, strlen(name));
+ fprintf(fp, " Atom Feed\" href=\"%satom.xml\" />\n", relpath);
+ fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp);
+ xmlencode(fp, name, strlen(name));
+ fprintf(fp, " Atom Feed (tags)\" href=\"%stags.xml\" />\n", relpath);
+ fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
+
+ /*
+ * add font and script for syntax highlighting
+ */
+
+ fputs("<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n", fp);
+
+ fputs("<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n", fp);
+
+ fputs("<link href=\"https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap\" rel=\"stylesheet\">\n",fp);
+
+ /*
+ * syntax highlight
+ */
+ fputs("<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/base16/tender.css\">\n", fp);
+
+ fputs("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\"></script>\n", fp);
+
+ fputs("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js\"></script>\n", fp);
+
+ fputs("<script>\n"
+ "hljs.highlightAll();\n"
+ "</script>\n", fp);
+
+ // for fixing the nextline issues
+ fputs("<style>pre code.hljs {display: inline;padding: 0;} code.hljs {padding: 0;} .hljs {background: initial;} .hljs-comment{color: rgb(96, 96, 96);}</style>", fp);
+ // Doesnt make sense to change these properties, insted, change
+ // your style.css to match the theme
+ // .hljs::selection, .hljs ::selection {background-color: initial;}
+
+ fputs("</head>\n<body>\n<table><tr><td>", fp);
+ fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
+ relpath, relpath);
+ fputs("</td><td><h1>", fp);
+ xmlencode(fp, strippedname, strlen(strippedname));
+ fputs("</h1><span class=\"desc\">", fp);
+ xmlencode(fp, description, strlen(description));
+ fputs("</span></td></tr>", fp);
+ if (cloneurl[0]) {
+ fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp);
+ xmlencode(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */
+ fputs("\">", fp);
+ xmlencode(fp, cloneurl, strlen(cloneurl));
+ fputs("</a></td></tr>", fp);
+ }
+ fputs("<tr><td></td><td>\n", fp);
+ /*
+ * more space before files, log etc.
+ */
+ fputs("<br>\n", fp);
+ fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
+ fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath);
+ fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
+ if (submodules)
+ fprintf(fp, " | <a href=\"%sfile/%s.html\">Submodules</a>",
+ relpath, submodules);
+ if (readme)
+ fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>",
+ relpath, readme);
+ if (license)
+ fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>",
+ relpath, license);
+ fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp);
+}
+
+void
+writefooter(FILE *fp)
+{
+ fputs("</div>\n</body>\n</html>\n", fp);
+}
+
+/*
+ * added parameter filename, so the <code class=?> can be determined
+ */
+size_t
+writeblobhtml(FILE *fp, const git_blob *blob, char* filename)
+{
+ size_t n = 0, i, len, prev;
+ const char *nfmt_old = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a>"; //removed <code> here
+ const char *s = git_blob_rawcontent(blob);
+
+ len = git_blob_rawsize(blob);
+ fputs("<pre id=\"blob\">\n", fp);
+ //fputs("<code>\n", fp);
+
+ char *language = (char *) malloc( strlen(filename) + 20 );
+ if (!language)
+ return 30;
+
+ // TODO the language should contain the actual language
+
+ sprintf(language, "<code class=\"language-%s\"> ", filename);
+ char *nfmt = (char *) malloc( strlen(nfmt_old) + strlen(language) + 1);
+ strcpy(nfmt, nfmt_old);
+ strcat(nfmt, language);
+
+ if (len > 0) {
+ for (i = 0, prev = 0; i < len; i++) {
+ if (s[i] != '\n')
+ continue;
+ n++;
+ fprintf(fp, nfmt, n, n, n);
+ xmlencodeline(fp, &s[prev], i - prev + 1);
+ /*
+ * odd but maybe highlight.js inserts its own
+ * newline, so we are not putting ours
+ * that did'nt fix it
+ */
+ fputs("</code>\n", fp);
+ prev = i + 1;
+ }
+ /* trailing data */
+ if ((len - prev) > 0) {
+ n++;
+ fprintf(fp, nfmt, n, n, n);
+ xmlencodeline(fp, &s[prev], len - prev);
+ }
+ }
+
+ //fputs("</code>\n", fp);
+ fputs("</pre>\n", fp);
+
+ return n;
+}
+
+void
+printcommit(FILE *fp, struct commitinfo *ci)
+{
+ fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
+ relpath, ci->oid, ci->oid);
+
+ if (ci->parentoid[0])
+ fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
+ relpath, ci->parentoid, ci->parentoid);
+
+ if (ci->author) {
+ fputs("<b>Author:</b> ", fp);
+ xmlencode(fp, ci->author->name, strlen(ci->author->name));
+ fputs(" <<a href=\"mailto:", fp);
+ xmlencode(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */
+ fputs("\">", fp);
+ xmlencode(fp, ci->author->email, strlen(ci->author->email));
+ fputs("</a>>\n<b>Date:</b> ", fp);
+ printtime(fp, &(ci->author->when));
+ putc('\n', fp);
+ }
+ if (ci->msg) {
+ putc('\n', fp);
+ xmlencode(fp, ci->msg, strlen(ci->msg));
+ putc('\n', fp);
+ }
+}
+
+void
+printshowfile(FILE *fp, struct commitinfo *ci)
+{
+ const git_diff_delta *delta;
+ const git_diff_hunk *hunk;
+ const git_diff_line *line;
+ git_patch *patch;
+ size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
+ char linestr[80];
+ int c;
+
+ printcommit(fp, ci);
+
+ if (!ci->deltas)
+ return;
+
+ if (ci->filecount > 1000 ||
+ ci->ndeltas > 1000 ||
+ ci->addcount > 100000 ||
+ ci->delcount > 100000) {
+ fputs("Diff is too large, output suppressed.\n", fp);
+ return;
+ }
+
+ /* diff stat */
+ fputs("<b>Diffstat:</b>\n<table>", fp);
+ for (i = 0; i < ci->ndeltas; i++) {
+ delta = git_patch_get_delta(ci->deltas[i]->patch);
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED: c = 'A'; break;
+ case GIT_DELTA_COPIED: c = 'C'; break;
+ case GIT_DELTA_DELETED: c = 'D'; break;
+ case GIT_DELTA_MODIFIED: c = 'M'; break;
+ case GIT_DELTA_RENAMED: c = 'R'; break;
+ case GIT_DELTA_TYPECHANGE: c = 'T'; break;
+ default: c = ' '; break;
+ }
+ if (c == ' ')
+ fprintf(fp, "<tr><td>%c", c);
+ else
+ fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
+
+ fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
+ xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+ if (strcmp(delta->old_file.path, delta->new_file.path)) {
+ fputs(" -> ", fp);
+ xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+ }
+
+ add = ci->deltas[i]->addcount;
+ del = ci->deltas[i]->delcount;
+ changed = add + del;
+ total = sizeof(linestr) - 2;
+ if (changed > total) {
+ if (add)
+ add = ((float)total / changed * add) + 1;
+ if (del)
+ del = ((float)total / changed * del) + 1;
+ }
+ memset(&linestr, '+', add);
+ memset(&linestr[add], '-', del);
+
+ fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
+ ci->deltas[i]->addcount + ci->deltas[i]->delcount);
+ fwrite(&linestr, 1, add, fp);
+ fputs("</span><span class=\"d\">", fp);
+ fwrite(&linestr[add], 1, del, fp);
+ fputs("</span></td></tr>\n", fp);
+ }
+ fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
+ ci->filecount, ci->filecount == 1 ? "" : "s",
+ ci->addcount, ci->addcount == 1 ? "" : "s",
+ ci->delcount, ci->delcount == 1 ? "" : "s");
+
+ fputs("<hr/>", fp);
+
+ for (i = 0; i < ci->ndeltas; i++) {
+ patch = ci->deltas[i]->patch;
+ delta = git_patch_get_delta(patch);
+ fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
+ percentencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+ fputs(".html\">", fp);
+ xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+ fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
+ percentencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+ fprintf(fp, ".html\">");
+ xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+ fprintf(fp, "</a></b>\n");
+
+ /* check binary data */
+ if (delta->flags & GIT_DIFF_FLAG_BINARY) {
+ fputs("Binary files differ.\n", fp);
+ continue;
+ }
+
+ nhunks = git_patch_num_hunks(patch);
+ for (j = 0; j < nhunks; j++) {
+ if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
+ break;
+
+ fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
+ xmlencode(fp, hunk->header, hunk->header_len);
+ fputs("</a>", fp);
+
+ for (k = 0; ; k++) {
+ if (git_patch_get_line_in_hunk(&line, patch, j, k))
+ break;
+ if (line->old_lineno == -1)
+ fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
+ i, j, k, i, j, k);
+ else if (line->new_lineno == -1)
+ fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
+ i, j, k, i, j, k);
+ else
+ putc(' ', fp);
+ xmlencodeline(fp, line->content, line->content_len);
+ putc('\n', fp);
+ if (line->old_lineno == -1 || line->new_lineno == -1)
+ fputs("</a>", fp);
+ }
+ }
+ }
+}
+
+void
+writelogline(FILE *fp, struct commitinfo *ci)
+{
+ fputs("<tr><td>", fp);
+ if (ci->author)
+ printtimeshort(fp, &(ci->author->when));
+ fputs("</td><td>", fp);
+ if (ci->summary) {
+ fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
+ xmlencode(fp, ci->summary, strlen(ci->summary));
+ fputs("</a>", fp);
+ }
+ fputs("</td><td>", fp);
+ if (ci->author)
+ xmlencode(fp, ci->author->name, strlen(ci->author->name));
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
+ fprintf(fp, "%zu", ci->filecount);
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
+ fprintf(fp, "+%zu", ci->addcount);
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
+ fprintf(fp, "-%zu", ci->delcount);
+ fputs("</td></tr>\n", fp);
+}
+
+int
+writelog(FILE *fp, const git_oid *oid)
+{
+ struct commitinfo *ci;
+ git_revwalk *w = NULL;
+ git_oid id;
+ char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
+ FILE *fpfile;
+ size_t remcommits = 0;
+ int r;
+
+ git_revwalk_new(&w, repo);
+ git_revwalk_push(w, oid);
+
+ while (!git_revwalk_next(&id, w)) {
+ relpath = "";
+
+ if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
+ break;
+
+ git_oid_tostr(oidstr, sizeof(oidstr), &id);
+ r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
+ if (r < 0 || (size_t)r >= sizeof(path))
+ errx(1, "path truncated: 'commit/%s.html'", oidstr);
+ r = access(path, F_OK);
+
+ /* optimization: if there are no log lines to write and
+ the commit file already exists: skip the diffstat */
+ if (!nlogcommits) {
+ remcommits++;
+ if (!r)
+ continue;
+ }
+
+ if (!(ci = commitinfo_getbyoid(&id)))
+ break;
+ /* diffstat: for stagit HTML required for the log.html line */
+ if (commitinfo_getstats(ci) == -1)
+ goto err;
+
+ if (nlogcommits != 0) {
+ writelogline(fp, ci);
+ if (nlogcommits > 0)
+ nlogcommits--;
+ }
+
+ if (cachefile)
+ writelogline(wcachefp, ci);
+
+ /* check if file exists if so skip it */
+ if (r) {
+ relpath = "../";
+ fpfile = efopen(path, "w");
+ writeheader(fpfile, ci->summary);
+ fputs("<pre>", fpfile);
+ printshowfile(fpfile, ci);
+ fputs("</pre>\n", fpfile);
+ writefooter(fpfile);
+ checkfileerror(fpfile, path, 'w');
+ fclose(fpfile);
+ }
+err:
+ commitinfo_free(ci);
+ }
+ git_revwalk_free(w);
+
+ if (nlogcommits == 0 && remcommits != 0) {
+ fprintf(fp, "<tr><td></td><td colspan=\"5\">"
+ "%zu more commits remaining, fetch the repository"
+ "</td></tr>\n", remcommits);
+ }
+
+ relpath = "";
+
+ return 0;
+}
+
+void
+printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
+{
+ fputs("<entry>\n", fp);
+
+ fprintf(fp, "<id>%s</id>\n", ci->oid);
+ if (ci->author) {
+ fputs("<published>", fp);
+ printtimez(fp, &(ci->author->when));
+ fputs("</published>\n", fp);
+ }
+ if (ci->committer) {
+ fputs("<updated>", fp);
+ printtimez(fp, &(ci->committer->when));
+ fputs("</updated>\n", fp);
+ }
+ if (ci->summary) {
+ fputs("<title>", fp);
+ if (tag && tag[0]) {
+ fputs("[", fp);
+ xmlencode(fp, tag, strlen(tag));
+ fputs("] ", fp);
+ }
+ xmlencode(fp, ci->summary, strlen(ci->summary));
+ fputs("</title>\n", fp);
+ }
+ fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"%scommit/%s.html\" />\n",
+ baseurl, ci->oid);
+
+ if (ci->author) {
+ fputs("<author>\n<name>", fp);
+ xmlencode(fp, ci->author->name, strlen(ci->author->name));
+ fputs("</name>\n<email>", fp);
+ xmlencode(fp, ci->author->email, strlen(ci->author->email));
+ fputs("</email>\n</author>\n", fp);
+ }
+
+ fputs("<content>", fp);
+ fprintf(fp, "commit %s\n", ci->oid);
+ if (ci->parentoid[0])
+ fprintf(fp, "parent %s\n", ci->parentoid);
+ if (ci->author) {
+ fputs("Author: ", fp);
+ xmlencode(fp, ci->author->name, strlen(ci->author->name));
+ fputs(" <", fp);
+ xmlencode(fp, ci->author->email, strlen(ci->author->email));
+ fputs(">\nDate: ", fp);
+ printtime(fp, &(ci->author->when));
+ putc('\n', fp);
+ }
+ if (ci->msg) {
+ putc('\n', fp);
+ xmlencode(fp, ci->msg, strlen(ci->msg));
+ }
+ fputs("\n</content>\n</entry>\n", fp);
+}
+
+int
+writeatom(FILE *fp, int all)
+{
+ struct referenceinfo *ris = NULL;
+ size_t refcount = 0;
+ struct commitinfo *ci;
+ git_revwalk *w = NULL;
+ git_oid id;
+ size_t i, m = 100; /* last 'm' commits */
+
+ fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
+ xmlencode(fp, strippedname, strlen(strippedname));
+ fputs(", branch HEAD</title>\n<subtitle>", fp);
+ xmlencode(fp, description, strlen(description));
+ fputs("</subtitle>\n", fp);
+
+ /* all commits or only tags? */
+ if (all) {
+ git_revwalk_new(&w, repo);
+ git_revwalk_push_head(w);
+ for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
+ if (!(ci = commitinfo_getbyoid(&id)))
+ break;
+ printcommitatom(fp, ci, "");
+ commitinfo_free(ci);
+ }
+ git_revwalk_free(w);
+ } else if (getrefs(&ris, &refcount) != -1) {
+ /* references: tags */
+ for (i = 0; i < refcount; i++) {
+ if (git_reference_is_tag(ris[i].ref))
+ printcommitatom(fp, ris[i].ci,
+ git_reference_shorthand(ris[i].ref));
+
+ commitinfo_free(ris[i].ci);
+ git_reference_free(ris[i].ref);
+ }
+ free(ris);
+ }
+
+ fputs("</feed>\n", fp);
+
+ return 0;
+}
+
+size_t
+writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
+{
+ char tmp[PATH_MAX] = "", *d;
+ const char *p;
+ size_t lc = 0;
+ FILE *fp;
+
+ if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
+ errx(1, "path truncated: '%s'", fpath);
+ if (!(d = dirname(tmp)))
+ err(1, "dirname");
+ if (mkdirp(d))
+ return -1;
+
+ for (p = fpath, tmp[0] = '\0'; *p; p++) {
+ if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
+ errx(1, "path truncated: '../%s'", tmp);
+ }
+ relpath = tmp;
+
+ fp = efopen(fpath, "w");
+ writeheader(fp, filename);
+ fputs("<p> ", fp);
+ xmlencode(fp, filename, strlen(filename));
+ fprintf(fp, " (%zuB)", filesize);
+ fputs("</p><hr/>", fp);
+
+ if (git_blob_is_binary((git_blob *)obj))
+ fputs("<p>Binary file.</p>\n", fp);
+ else
+ // passing parameter to be used by the code
+ lc = writeblobhtml(fp, (git_blob *)obj, filename);
+
+ writefooter(fp);
+ checkfileerror(fp, fpath, 'w');
+ fclose(fp);
+
+ relpath = "";
+
+ return lc;
+}
+
+const char *
+filemode(git_filemode_t m)
+{
+ static char mode[11];
+
+ memset(mode, '-', sizeof(mode) - 1);
+ mode[10] = '\0';
+
+ if (S_ISREG(m))
+ mode[0] = '-';
+ else if (S_ISBLK(m))
+ mode[0] = 'b';
+ else if (S_ISCHR(m))
+ mode[0] = 'c';
+ else if (S_ISDIR(m))
+ mode[0] = 'd';
+ else if (S_ISFIFO(m))
+ mode[0] = 'p';
+ else if (S_ISLNK(m))
+ mode[0] = 'l';
+ else if (S_ISSOCK(m))
+ mode[0] = 's';
+ else
+ mode[0] = '?';
+
+ if (m & S_IRUSR) mode[1] = 'r';
+ if (m & S_IWUSR) mode[2] = 'w';
+ if (m & S_IXUSR) mode[3] = 'x';
+ if (m & S_IRGRP) mode[4] = 'r';
+ if (m & S_IWGRP) mode[5] = 'w';
+ if (m & S_IXGRP) mode[6] = 'x';
+ if (m & S_IROTH) mode[7] = 'r';
+ if (m & S_IWOTH) mode[8] = 'w';
+ if (m & S_IXOTH) mode[9] = 'x';
+
+ if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
+ if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
+ if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
+
+ return mode;
+}
+
+int
+writefilestree(FILE *fp, git_tree *tree, const char *path)
+{
+ const git_tree_entry *entry = NULL;
+ git_object *obj = NULL;
+ const char *entryname;
+ char filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
+ size_t count, i, lc, filesize;
+ int r, ret;
+
+ count = git_tree_entrycount(tree);
+ for (i = 0; i < count; i++) {
+ if (!(entry = git_tree_entry_byindex(tree, i)) ||
+ !(entryname = git_tree_entry_name(entry)))
+ return -1;
+ joinpath(entrypath, sizeof(entrypath), path, entryname);
+
+ r = snprintf(filepath, sizeof(filepath), "file/%s.html",
+ entrypath);
+ if (r < 0 || (size_t)r >= sizeof(filepath))
+ errx(1, "path truncated: 'file/%s.html'", entrypath);
+
+ if (!git_tree_entry_to_object(&obj, repo, entry)) {
+ switch (git_object_type(obj)) {
+ case GIT_OBJ_BLOB:
+ break;
+ case GIT_OBJ_TREE:
+ /* NOTE: recurses */
+ ret = writefilestree(fp, (git_tree *)obj,
+ entrypath);
+ git_object_free(obj);
+ if (ret)
+ return ret;
+ continue;
+ default:
+ git_object_free(obj);
+ continue;
+ }
+
+ filesize = git_blob_rawsize((git_blob *)obj);
+ lc = writeblob(obj, filepath, entryname, filesize);
+
+ fputs("<tr><td>", fp);
+ fputs(filemode(git_tree_entry_filemode(entry)), fp);
+ fprintf(fp, "</td><td><a href=\"%s", relpath);
+ percentencode(fp, filepath, strlen(filepath));
+ fputs("\">", fp);
+ xmlencode(fp, entrypath, strlen(entrypath));
+ fputs("</a></td><td class=\"num\" align=\"right\">", fp);
+ if (lc > 0)
+ fprintf(fp, "%zuL", lc);
+ else
+ fprintf(fp, "%zuB", filesize);
+ fputs("</td></tr>\n", fp);
+ git_object_free(obj);
+ } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
+ /* commit object in tree is a submodule */
+ fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
+ relpath);
+ xmlencode(fp, entrypath, strlen(entrypath));
+ fputs("</a> @ ", fp);
+ git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
+ xmlencode(fp, oid, strlen(oid));
+ fputs("</td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
+ }
+ }
+
+ return 0;
+}
+
+int
+writefiles(FILE *fp, const git_oid *id)
+{
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
+ int ret = -1;
+
+ fputs("<table id=\"files\"><thead>\n<tr>"
+ "<td><b>Mode</b></td><td><b>Name</b></td>"
+ "<td class=\"num\" align=\"right\"><b>Size</b></td>"
+ "</tr>\n</thead><tbody>\n", fp);
+
+ if (!git_commit_lookup(&commit, repo, id) &&
+ !git_commit_tree(&tree, commit))
+ ret = writefilestree(fp, tree, "");
+
+ fputs("</tbody></table>", fp);
+
+ git_commit_free(commit);
+ git_tree_free(tree);
+
+ return ret;
+}
+
+int
+writerefs(FILE *fp)
+{
+ struct referenceinfo *ris = NULL;
+ struct commitinfo *ci;
+ size_t count, i, j, refcount;
+ const char *titles[] = { "Branches", "Tags" };
+ const char *ids[] = { "branches", "tags" };
+ const char *s;
+
+ if (getrefs(&ris, &refcount) == -1)
+ return -1;
+
+ for (i = 0, j = 0, count = 0; i < refcount; i++) {
+ if (j == 0 && git_reference_is_tag(ris[i].ref)) {
+ if (count)
+ fputs("</tbody></table><br/>\n", fp);
+ count = 0;
+ j = 1;
+ }
+
+ /* print header if it has an entry (first). */
+ if (++count == 1) {
+ fprintf(fp, "<h2>%s</h2><table id=\"%s\">"
+ "<thead>\n<tr><td><b>Name</b></td>"
+ "<td><b>Last commit date</b></td>"
+ "<td><b>Author</b></td>\n</tr>\n"
+ "</thead><tbody>\n",
+ titles[j], ids[j]);
+ }
+
+ ci = ris[i].ci;
+ s = git_reference_shorthand(ris[i].ref);
+
+ fputs("<tr><td>", fp);
+ xmlencode(fp, s, strlen(s));
+ fputs("</td><td>", fp);
+ if (ci->author)
+ printtimeshort(fp, &(ci->author->when));
+ fputs("</td><td>", fp);
+ if (ci->author)
+ xmlencode(fp, ci->author->name, strlen(ci->author->name));
+ fputs("</td></tr>\n", fp);
+ }
+ /* table footer */
+ if (count)
+ fputs("</tbody></table><br/>\n", fp);
+
+ for (i = 0; i < refcount; i++) {
+ commitinfo_free(ris[i].ci);
+ git_reference_free(ris[i].ref);
+ }
+ free(ris);
+
+ return 0;
+}
+
+void
+usage(char *argv0)
+{
+ fprintf(stderr, "usage: %s [-c cachefile | -l commits] "
+ "[-u baseurl] repodir\n", argv0);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ git_object *obj = NULL;
+ const git_oid *head = NULL;
+ mode_t mask;
+ FILE *fp, *fpread;
+ char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
+ char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
+ size_t n;
+ int i, fd;
+
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] != '-') {
+ if (repodir)
+ usage(argv[0]);
+ repodir = argv[i];
+ } else if (argv[i][1] == 'c') {
+ if (nlogcommits > 0 || i + 1 >= argc)
+ usage(argv[0]);
+ cachefile = argv[++i];
+ } else if (argv[i][1] == 'l') {
+ if (cachefile || i + 1 >= argc)
+ usage(argv[0]);
+ errno = 0;
+ nlogcommits = strtoll(argv[++i], &p, 10);
+ if (argv[i][0] == '\0' || *p != '\0' ||
+ nlogcommits <= 0 || errno)
+ usage(argv[0]);
+ } else if (argv[i][1] == 'u') {
+ if (i + 1 >= argc)
+ usage(argv[0]);
+ baseurl = argv[++i];
+ }
+ }
+ if (!repodir)
+ usage(argv[0]);
+
+ if (!realpath(repodir, repodirabs))
+ err(1, "realpath");
+
+ /* do not search outside the git repository:
+ GIT_CONFIG_LEVEL_APP is the highest level currently */
+ git_libgit2_init();
+ for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
+ git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
+ /* do not require the git repository to be owned by the current user */
+ git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
+
+#ifdef __OpenBSD__
+ if (unveil(repodir, "r") == -1)
+ err(1, "unveil: %s", repodir);
+ if (unveil(".", "rwc") == -1)
+ err(1, "unveil: .");
+ if (cachefile && unveil(cachefile, "rwc") == -1)
+ err(1, "unveil: %s", cachefile);
+
+ if (cachefile) {
+ if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
+ err(1, "pledge");
+ } else {
+ if (pledge("stdio rpath wpath cpath", NULL) == -1)
+ err(1, "pledge");
+ }
+#endif
+
+ if (git_repository_open_ext(&repo, repodir,
+ GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
+ fprintf(stderr, "%s: cannot open repository\n", argv[0]);
+ return 1;
+ }
+
+ /* find HEAD */
+ if (!git_revparse_single(&obj, repo, "HEAD"))
+ head = git_object_id(obj);
+ git_object_free(obj);
+
+ /* use directory name as name */
+ if ((name = strrchr(repodirabs, '/')))
+ name++;
+ else
+ name = "";
+
+ /* strip .git suffix */
+ if (!(strippedname = strdup(name)))
+ err(1, "strdup");
+ if ((p = strrchr(strippedname, '.')))
+ if (!strcmp(p, ".git"))
+ *p = '\0';
+
+ /* read description or .git/description */
+ joinpath(path, sizeof(path), repodir, "description");
+ if (!(fpread = fopen(path, "r"))) {
+ joinpath(path, sizeof(path), repodir, ".git/description");
+ fpread = fopen(path, "r");
+ }
+ if (fpread) {
+ if (!fgets(description, sizeof(description), fpread))
+ description[0] = '\0';
+ checkfileerror(fpread, path, 'r');
+ fclose(fpread);
+ }
+
+ /* read url or .git/url */
+ joinpath(path, sizeof(path), repodir, "url");
+ if (!(fpread = fopen(path, "r"))) {
+ joinpath(path, sizeof(path), repodir, ".git/url");
+ fpread = fopen(path, "r");
+ }
+ if (fpread) {
+ if (!fgets(cloneurl, sizeof(cloneurl), fpread))
+ cloneurl[0] = '\0';
+ checkfileerror(fpread, path, 'r');
+ fclose(fpread);
+ cloneurl[strcspn(cloneurl, "\n")] = '\0';
+ }
+
+ /* check LICENSE */
+ for (i = 0; i < LEN(licensefiles) && !license; i++) {
+ if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
+ git_object_type(obj) == GIT_OBJ_BLOB)
+ license = licensefiles[i] + strlen("HEAD:");
+ git_object_free(obj);
+ }
+
+ /* check README */
+ for (i = 0; i < LEN(readmefiles) && !readme; i++) {
+ if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
+ git_object_type(obj) == GIT_OBJ_BLOB)
+ readme = readmefiles[i] + strlen("HEAD:");
+ git_object_free(obj);
+ }
+
+ if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
+ git_object_type(obj) == GIT_OBJ_BLOB)
+ submodules = ".gitmodules";
+ git_object_free(obj);
+
+ /* log for HEAD */
+ fp = efopen("log.html", "w");
+ relpath = "";
+ mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
+ writeheader(fp, "Log");
+ fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
+ "<td><b>Commit message</b></td>"
+ "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>"
+ "<td class=\"num\" align=\"right\"><b>+</b></td>"
+ "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
+
+ if (cachefile && head) {
+ /* read from cache file (does not need to exist) */
+ if ((rcachefp = fopen(cachefile, "r"))) {
+ if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
+ errx(1, "%s: no object id", cachefile);
+ if (git_oid_fromstr(&lastoid, lastoidstr))
+ errx(1, "%s: invalid object id", cachefile);
+ }
+
+ /* write log to (temporary) cache */
+ if ((fd = mkstemp(tmppath)) == -1)
+ err(1, "mkstemp");
+ if (!(wcachefp = fdopen(fd, "w")))
+ err(1, "fdopen: '%s'", tmppath);
+ /* write last commit id (HEAD) */
+ git_oid_tostr(buf, sizeof(buf), head);
+ fprintf(wcachefp, "%s\n", buf);
+
+ writelog(fp, head);
+
+ if (rcachefp) {
+ /* append previous log to log.html and the new cache */
+ while (!feof(rcachefp)) {
+ n = fread(buf, 1, sizeof(buf), rcachefp);
+ if (ferror(rcachefp))
+ break;
+ if (fwrite(buf, 1, n, fp) != n ||
+ fwrite(buf, 1, n, wcachefp) != n)
+ break;
+ }
+ checkfileerror(rcachefp, cachefile, 'r');
+ fclose(rcachefp);
+ }
+ checkfileerror(wcachefp, tmppath, 'w');
+ fclose(wcachefp);
+ } else {
+ if (head)
+ writelog(fp, head);
+ }
+
+ fputs("</tbody></table>", fp);
+ writefooter(fp);
+ checkfileerror(fp, "log.html", 'w');
+ fclose(fp);
+
+ /* files for HEAD */
+ fp = efopen("files.html", "w");
+ writeheader(fp, "Files");
+ if (head)
+ writefiles(fp, head);
+ writefooter(fp);
+ checkfileerror(fp, "files.html", 'w');
+ fclose(fp);
+
+ /* summary page with branches and tags */
+ fp = efopen("refs.html", "w");
+ writeheader(fp, "Refs");
+ writerefs(fp);
+ writefooter(fp);
+ checkfileerror(fp, "refs.html", 'w');
+ fclose(fp);
+
+ /* Atom feed */
+ fp = efopen("atom.xml", "w");
+ writeatom(fp, 1);
+ checkfileerror(fp, "atom.xml", 'w');
+ fclose(fp);
+
+ /* Atom feed for tags / releases */
+ fp = efopen("tags.xml", "w");
+ writeatom(fp, 0);
+ checkfileerror(fp, "tags.xml", 'w');
+ fclose(fp);
+
+ /* rename new cache file on success */
+ if (cachefile && head) {
+ if (rename(tmppath, cachefile))
+ err(1, "rename: '%s' to '%s'", tmppath, cachefile);
+ umask((mask = umask(0)));
+ if (chmod(cachefile,
+ (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
+ err(1, "chmod: '%s'", cachefile);
+ }
+
+ /* cleanup */
+ git_repository_free(repo);
+ git_libgit2_shutdown();
+
+ return 0;
+}
diff --git a/stagit.o b/stagit.o
Binary files differ.
diff --git a/strlcat.c b/strlcat.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left). At most dsize-1 characters
+ * will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
diff --git a/strlcat.o b/strlcat.o
Binary files differ.
diff --git a/strlcpy.c b/strlcpy.c
@@ -0,0 +1,52 @@
+/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ ;
+ }
+
+ return(src - osrc - 1); /* count does not include NUL */
+}
diff --git a/strlcpy.o b/strlcpy.o
Binary files differ.
diff --git a/style.css b/style.css
@@ -0,0 +1,154 @@
+body {
+ color: #000;
+ background-color: #fff;
+ font-family: monospace;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-size: 1em;
+ margin: 0;
+}
+
+img, h1, h2 {
+ vertical-align: middle;
+}
+
+img {
+ border: 0;
+}
+
+a:target {
+ background-color: #ccc;
+}
+
+a.d,
+a.h,
+a.i,
+a.line {
+ text-decoration: none;
+}
+
+#blob a {
+ color: #555;
+}
+
+#blob a:hover {
+ color: blue;
+ text-decoration: none;
+}
+
+table thead td {
+ font-weight: bold;
+}
+
+table td {
+ padding: 0 0.4em;
+}
+
+#content table td {
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+#branches tr:hover td,
+#tags tr:hover td,
+#index tr:hover td,
+#log tr:hover td,
+#files tr:hover td {
+ background-color: #eee;
+}
+
+#index tr td:nth-child(2),
+#tags tr td:nth-child(3),
+#branches tr td:nth-child(3),
+#log tr td:nth-child(2) {
+ white-space: normal;
+}
+
+td.num {
+ text-align: right;
+}
+
+.desc {
+ color: #555;
+}
+
+hr {
+ border: 0;
+ border-top: 1px solid #555;
+ height: 1px;
+}
+
+pre {
+ font-family: monospace;
+}
+
+pre a.h {
+ color: #00a;
+}
+
+.A,
+span.i,
+pre a.i {
+ color: #070;
+}
+
+.D,
+span.d,
+pre a.d {
+ color: #e00;
+}
+
+pre a.h:hover,
+pre a.i:hover,
+pre a.d:hover {
+ text-decoration: none;
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #000;
+ color: #bdbdbd;
+ }
+ hr {
+ border-color: #222;
+ }
+ a {
+ color: #56c8ff;
+ }
+ a:target {
+ background-color: #222;
+ }
+ .desc {
+ color: #aaa;
+ }
+ #blob a {
+ color: #555;
+ }
+ #blob a:target {
+ color: #eee;
+ }
+ #blob a:hover {
+ color: #56c8ff;
+ }
+ pre a.h {
+ color: #00cdcd;
+ }
+ .A,
+ span.i,
+ pre a.i {
+ color: #00cd00;
+ }
+ .D,
+ span.d,
+ pre a.d {
+ color: #cd0000;
+ }
+ #branches tr:hover td,
+ #tags tr:hover td,
+ #index tr:hover td,
+ #log tr:hover td,
+ #files tr:hover td {
+ background-color: #111;
+ }
+}