Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
829b9bd6b4
156 changed files with 1499 additions and 22389 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,13 +1,17 @@
|
|||
# Telodendria .gitignore
|
||||
|
||||
build
|
||||
out
|
||||
data
|
||||
Makefile
|
||||
|
||||
*-leaked.txt
|
||||
.env
|
||||
*.patch
|
||||
*.orig
|
||||
*.log
|
||||
vgcore.*
|
||||
*.core
|
||||
Cytoplasm/build
|
||||
Cytoplasm/out
|
||||
Cytoplasm/*-leaked.txt
|
||||
contrib/.vagrant
|
||||
src/Schema
|
||||
src/include/Schema
|
||||
|
|
28
Cytoplasm/.indent.pro
vendored
28
Cytoplasm/.indent.pro
vendored
|
@ -1,28 +0,0 @@
|
|||
-bad
|
||||
-bap
|
||||
-bbb
|
||||
-nbc
|
||||
-bl
|
||||
-c36
|
||||
-cd36
|
||||
-ncdb
|
||||
-nce
|
||||
-ci8
|
||||
-cli1
|
||||
-d0
|
||||
-di1
|
||||
-ndj
|
||||
-ei
|
||||
-fc1
|
||||
-i4
|
||||
-ip
|
||||
-l72
|
||||
-lc72
|
||||
-lp
|
||||
-npcs
|
||||
-psl
|
||||
-sc
|
||||
-nsob
|
||||
-nut
|
||||
-nv
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
|
@ -1,78 +0,0 @@
|
|||
# Cytoplasm (libcytoplasm)
|
||||
|
||||
Cytoplasm is a general-purpose C library and runtime stub for creating high-level (particularly networked and multi-threaded) C applications. It allows applications to take advantage of the speed, flexibility, and simplicity of the C programming language, while providing helpful code to perform various complex tasks. Cytoplasm provides high-level data structures, a basic logging facility, an HTTP client and server, and more.
|
||||
|
||||
Cytoplasm aims not to only do one thing well, but to do many things good enough. The primary target of Cytoplasm is simple, yet higher level C applications that have to perform relatively complex tasks, but don't want to pull in a large number of dependencies.
|
||||
|
||||
Cytoplasm is extremely opinionated on the way programs using it are written. It strives to create a comprehensive and tightly-integrated programming environment, while also maintaining C programming correctness. It doesn't do any macro magic or make C look like anything other than C. It is written entirely in C89, and depends only on a POSIX environment. This differentiates it from other general-purpose libraries that often require modern compilers and non-standard language and environment features. Cytoplasm is intended to be extremely portable and simple, while still providing some of the functionality expected in higher-level programming languages in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps low-level TLS libraries to offer a single, unified interface to TLS so that programs do not have to care about the underlying implementation.
|
||||
|
||||
Cytoplasm is probably not suitable for embedded programming. It makes liberal use of the heap, and while data structures are designed to conserve memory where possible and practical, minimal memory usage is not really a design goal for Cytoplasm, although Cytoplasm takes care not to use any more memory than it absolutely needs. Cytoplasm also wraps a few standard libraries with additional logic and checking. While this ensures better runtime safetly, this inevitably adds a little overhead.
|
||||
|
||||
Originally a part of Telodendria (https://telodendria.io), a Matrix homeserver written in C, Cytoplasm was split off into its own project due to the desire of some Telodendria developers to use Telodendria's code in other projects. Cytoplasm is still a Telodendria project, and is maintained along side of Telodendria itself, even living in the same CVS module, but it is designed specifically to be distributed and used totally independent of Telodendria.
|
||||
|
||||
The name "Cytoplasm" was chosen for a few reasons. It plays off the precedent set up by the Matrix organization in naming projects after the parts of a neuron. It also speaks to the function of Cytoplasm. The cytoplasm of a cell is the supporting material. It is what gives the cell its shape, and it facilitates the movement of materials to the other cell parts. Likewise, Cytoplasm aims to provide a support mechanism for C applications that have to perform complex tasks.
|
||||
|
||||
Cytoplasm also starts with a C, which I think is a nice touch for C libraries. It's also fun to say and unique enough that searching for "libcytoplasm" should bring you to this project and not some other one.
|
||||
|
||||
## Building
|
||||
|
||||
If your operating system or software distribution provides a pre-built package of Cytoplasm, you should prefer to use that instead of building it from source.
|
||||
|
||||
Cytoplasm aims to have zero dependencies beyond what is mandated by POSIX. You only need the standard math and pthread libraries to build it. TLS support can optionally be enabled by setting the TLS_IMPL environment variable. The supported TLS implementations are as follows:
|
||||
|
||||
- OpenSSL
|
||||
- LibreSSL
|
||||
|
||||
If TLS support is not enabled, all APIs that use it should fall back to non-TLS behavior in a sensible manner. For example, if TLS support is not enabled, then the HTTP client API will simply return an error if a TLS connection is requested. Cytoplasm uses a custom build script instead of Make, for the sake of portability. To build everything, just run the script:
|
||||
|
||||
$ sh make.sh
|
||||
|
||||
This will produce the following out/ directory:
|
||||
|
||||
```
|
||||
out/
|
||||
lib/
|
||||
libcytoplasm.so - The Cytoplasm shared library.
|
||||
libcytoplasm.a - The Cytoplasm static library.
|
||||
cytoplasm.o - The Cytoplasm runtime stub.
|
||||
bin/
|
||||
hdoc - The documentation generator tool.
|
||||
man/ - All Cytoplasm API documentation.
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Cytoplasm provides the typical .so and .a files, which can be used to link programs with it in the usual way. However, Cytoplasm also provides a minimal runtime environment that is intended to be used with the library. As such, it also provides a runtime stub, which is intended to be linked in with programs using Cytoplasm. This stub is responsible for setting up and tearing down some Cytoplasm APIs. While it isn't required by any means, it makes Cytoplasm a lot easier to use for programmers by abstracting out all of the common logic that most programs will want to use.
|
||||
|
||||
Here is the canonical Hello World written with Cytoplasm:
|
||||
|
||||
```c
|
||||
#include <Log.h>
|
||||
|
||||
int Main(void)
|
||||
{
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
If this file is Hello.c, then you can compile it by doing something like this:
|
||||
|
||||
$ cc -o hello Hello.c cytoplasm.o -lcytoplasm
|
||||
|
||||
This command assumes that the runtime stub resides in the current working directory, and that libcytoplasm.so is in your library path. If you're using the version of Cytoplasm installed by your operating system or software distribution, consult the documentation for the location of the runtime stub. It may be located in /usr/local/libexec or some other similar location. If you've built Cytoplasm from source and wish to link to it from there, you may wish to do something like this:
|
||||
|
||||
$ export CYTOPLASM=/path/to/Cytoplasm/out/lib
|
||||
$ cc -o hello Hello.c "${CYTOPLASM}/cytoplasm.o" \
|
||||
"-L${CYTOPLASM}" -lcytoplasm
|
||||
|
||||
As you may have noticed, C programs using Cytoplasm's runtime stub don't write the main() function. Instead, they use Main(). The main() function is provided by the runtime stub. The full form of Main() expected by the stub is as follows:
|
||||
|
||||
```c
|
||||
int Main(Array *args, HashMap *env);
|
||||
```
|
||||
|
||||
The first argument is a Cytoplasm array of the command line arguments, and the second is a Cytoplasm hash map of environment variables. Most linkers will let programs omit the env argument, or both arguments if you don't need either. The return value of Main() is returned to the operating system.
|
||||
|
||||
Note that both arguments to Main may be treated like any other array or hash map. However, do not invoke ArrayFree() or HashMapFree() on the passed pointers, because memory is cleaned up after Main() returns.
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include <Array.h>
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
int
|
||||
Main(Array * args, HashMap * env)
|
||||
{
|
||||
size_t i;
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
Log(LOG_INFO, "Arguments: %lu", ArraySize(args));
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
Log(LOG_INFO, " [%ld] %s", i, ArrayGet(args, i));
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "Environment:");
|
||||
while (HashMapIterate(env, &key, (void **) &val))
|
||||
{
|
||||
Log(LOG_INFO, " %s = %s", key, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
addprefix() {
|
||||
prefix="$1"
|
||||
shift
|
||||
for thing in "$@"; do
|
||||
echo "${prefix}${thing}"
|
||||
done
|
||||
|
||||
unset prefix
|
||||
unset thing
|
||||
}
|
||||
|
||||
: "${NAME:=Cytoplasm}"
|
||||
: "${LIB_NAME:=$(echo ${NAME} | tr '[A-Z]' '[a-z]')}"
|
||||
: "${VERSION:=0.4.0}"
|
||||
|
||||
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
|
||||
|
||||
|
||||
: "${SRC:=src}"
|
||||
: "${TOOLS:=tools}"
|
||||
: "${EXAMPLES:=examples}"
|
||||
: "${BUILD:=build}"
|
||||
: "${OUT:=out}"
|
||||
: "${STUB:=RtStub}"
|
||||
: "${LICENSE:=LICENSE.txt}"
|
||||
|
||||
: "${CC:=cc}"
|
||||
: "${AR:=ar}"
|
||||
|
||||
: "${DEFINES:=-D_DEFAULT_SOURCE -DLIB_NAME=\"${NAME}\" -DLIB_VERSION=\"${VERSION}\"}"
|
||||
: "${INCLUDE:=${SRC}/include}"
|
||||
|
||||
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
|
||||
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
|
||||
: "${LDFLAGS:=-lm -pthread}"
|
||||
|
||||
if [ "${DEBUG}" = "1" ]; then
|
||||
CFLAGS="${CFLAGS} -O0 -g"
|
||||
LD_EXTRA=""
|
||||
fi
|
||||
|
||||
if [ -n "${TLS_IMPL}" ]; then
|
||||
case "${TLS_IMPL}" in
|
||||
"LIBRESSL")
|
||||
TLS_LIBS="-ltls -lcrypto -lssl"
|
||||
;;
|
||||
"OPENSSL")
|
||||
TLS_LIBS="-lcrypto -lssl"
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized TLS implementation: ${TLS_IMPL}"
|
||||
echo "Consult Tls.h for supported implementations."
|
||||
echo "Note that the TLS_ prefix is omitted in TLS_IMPL."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
DEFINES="${DEFINES} -DTLS_IMPL=TLS_${TLS_IMPL}"
|
||||
LDFLAGS="${LDFLAGS} ${TLS_LIBS}"
|
||||
fi
|
||||
|
||||
CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDE})"
|
||||
LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
|
||||
|
||||
# Check the modificiation time of a file. This is used to do
|
||||
# incremental builds; we only want to rebuild files that have
|
||||
# have changed.
|
||||
mod_time() {
|
||||
if [ -n "$1" ] && [ -f "$1" ]; then
|
||||
case "$(uname)" in
|
||||
Linux | CYGWIN_NT* | Haiku)
|
||||
stat -c %Y "$1"
|
||||
;;
|
||||
*BSD | DragonFly | Minix)
|
||||
stat -f %m "$1"
|
||||
;;
|
||||
*)
|
||||
# Platform unknown, force rebuilding the whole
|
||||
# project every time.
|
||||
echo "0"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# Substitute shell variables in a stream with their actual value
|
||||
# in this shell.
|
||||
setsubst() {
|
||||
SED="/tmp/sed-$RANDOM.txt"
|
||||
|
||||
(
|
||||
set | while IFS='=' read -r var val; do
|
||||
val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
|
||||
echo "s|\\\${$var}|$val|g"
|
||||
done
|
||||
|
||||
echo "s|\\\${[a-zA-Z_]*}||g"
|
||||
echo "s|'''|'|g"
|
||||
) >"$SED"
|
||||
|
||||
sed -f "$SED" $@
|
||||
rm "$SED"
|
||||
}
|
||||
|
||||
recipe_docs() {
|
||||
export LD_LIBRARY_PATH="${OUT}/lib"
|
||||
mkdir -p "${OUT}/man/man3"
|
||||
|
||||
for header in $(find ${INCLUDE} -name '*.h'); do
|
||||
basename=$(basename "$header")
|
||||
man=$(echo "${OUT}/man/man3/$basename" | sed -e 's/\.h$/\.3/')
|
||||
|
||||
if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
|
||||
echo "DOC $basename"
|
||||
if ! "${OUT}/bin/hdoc" -D "Os=${NAME}" -i "$header" -o "$man"; then
|
||||
rm "$man"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if which makewhatis 2>&1 > /dev/null; then
|
||||
makewhatis "${OUT}/man"
|
||||
fi
|
||||
}
|
||||
|
||||
recipe_libs() {
|
||||
echo "-lm -pthread ${TLS_LIBS}"
|
||||
}
|
||||
|
||||
recipe_build() {
|
||||
mkdir -p "${BUILD}" "${OUT}/bin" "${OUT}/lib"
|
||||
cd "${SRC}"
|
||||
|
||||
|
||||
echo "CC = ${CC}"
|
||||
echo "CFLAGS = ${CFLAGS}"
|
||||
echo "LDFLAGS = ${LDFLAGS}"
|
||||
echo
|
||||
|
||||
do_rebuild=0
|
||||
objs=""
|
||||
for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
|
||||
obj=$(echo "${BUILD}/$src" | sed -e 's/\.c$/\.o/')
|
||||
|
||||
if [ $(basename "$obj" .o) != "${STUB}" ]; then
|
||||
objs="$objs $obj"
|
||||
fi
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
|
||||
echo "CC $(basename $obj)"
|
||||
obj_dir=$(dirname "../$obj")
|
||||
mkdir -p "$obj_dir"
|
||||
if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
|
||||
exit 1
|
||||
fi
|
||||
do_rebuild=1
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
|
||||
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.a" ]; then
|
||||
echo "AR lib${LIB_NAME}.a"
|
||||
if ! $AR rcs "${OUT}/lib/lib${LIB_NAME}.a" $objs; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.so" ]; then
|
||||
echo "LD lib${LIB_NAME}.so"
|
||||
if ! $CC -shared -o "${OUT}/lib/lib${LIB_NAME}.so" $objs ${LDFLAGS}; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "${BUILD}/${STUB}.o" "${OUT}/lib/${LIB_NAME}.o"
|
||||
|
||||
for src in $(find "${TOOLS}" -name '*.c'); do
|
||||
out=$(basename "$src" .c)
|
||||
out="${OUT}/bin/$out"
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
|
||||
echo "CC $(basename $out)"
|
||||
mkdir -p "$(dirname $out)"
|
||||
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
recipe_docs
|
||||
}
|
||||
|
||||
recipe_examples() {
|
||||
for src in $(find "${EXAMPLES}" -name '*.c'); do
|
||||
out=$(basename "$src" .c)
|
||||
out="${OUT}/bin/$out"
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "$out") ]; then
|
||||
echo "CC $(basename $out)"
|
||||
mkdir -p "$(dirname $out)"
|
||||
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
recipe_clean() {
|
||||
rm -r "${BUILD}" "${OUT}"
|
||||
}
|
||||
|
||||
# Update copyright comments in sources and header files.
|
||||
recipe_license() {
|
||||
find . -name '*.[ch]' | while IFS= read -r src; do
|
||||
if [ -t 1 ]; then
|
||||
printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
|
||||
fi
|
||||
srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
|
||||
head "-n$srcHeader" "$src" |
|
||||
diff -u -p - "${LICENSE}" |
|
||||
patch "$src" | grep -v "^Hmm"
|
||||
done
|
||||
if [ -t 1 ]; then
|
||||
printf "%*c\n" "50" " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Format source code files by running indent(1) on them.
|
||||
recipe_format() {
|
||||
find . -name '*.c' | while IFS= read -r src; do
|
||||
if [ -t 1 ]; then
|
||||
printf "FMT %s%*c\r" $(basename "$src") "16" " "
|
||||
fi
|
||||
if indent "$src"; then
|
||||
rm $(basename "$src").BAK
|
||||
fi
|
||||
done
|
||||
if [ -t 1 ]; then
|
||||
printf "%*c\n" "50" " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate a release tarball, checksum and sign it, and push it to
|
||||
# the web root.
|
||||
recipe_release() {
|
||||
# Tag the release at this point in time.
|
||||
cvs tag "$CVS_TAG"
|
||||
|
||||
mkdir -p "${OUT}/release"
|
||||
cd "${OUT}/release"
|
||||
|
||||
# Generate the release tarball.
|
||||
cvs export "-r$CVS_TAG" "${NAME}"
|
||||
mv "${NAME}" "${NAME}-v${VERSION}"
|
||||
tar -czf "${NAME}-v${VERSION}.tar.gz" "${NAME}-v${VERSION}"
|
||||
rm -r "${NAME}-v${VERSION}"
|
||||
|
||||
# Checksum the release tarball.
|
||||
sha256 "${NAME}-v${VERSION}.tar.gz" > "${NAME}-v${VERSION}.tar.gz.sha256"
|
||||
|
||||
# Sign the release tarball.
|
||||
if [ ! -z "${SIGNIFY_SECRET}" ]; then
|
||||
signify -S -s "${SIGNIFY_SECRET}" \
|
||||
-m "${NAME}-v${VERSION}.tar.gz" \
|
||||
-x "${NAME}-v${VERSION}.tar.gz.sig"
|
||||
else
|
||||
echo "Warning: SIGNIFY_SECRET not net."
|
||||
echo "The built tarball will not be signed."
|
||||
fi
|
||||
}
|
||||
|
||||
recipe_patch() {
|
||||
# If the user has not set their MXID, try to deduce one from
|
||||
# their system.
|
||||
if [ -z "$MXID" ]; then
|
||||
MXID="@${USER}:$(hostname)"
|
||||
fi
|
||||
|
||||
# If the user has not set their EDITOR, use a safe default.
|
||||
# (vi should be available on any POSIX system)
|
||||
if [ -z "$EDITOR" ]; then
|
||||
EDITOR=vi
|
||||
fi
|
||||
|
||||
NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
|
||||
PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"
|
||||
|
||||
# Generate the actual patch file
|
||||
# Here we write some nice headers, and then do a cvs diff.
|
||||
(
|
||||
printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
|
||||
echo "Date: $(date)"
|
||||
echo "Subject: "
|
||||
echo
|
||||
cvs -q diff -uNp $PATCHSET | grep -v '^\? '
|
||||
) >"$PATCH_FILE"
|
||||
|
||||
"$EDITOR" "$PATCH_FILE"
|
||||
echo "$PATCH_FILE"
|
||||
}
|
||||
|
||||
recipe_diff() {
|
||||
tmp_patch="/tmp/${NAME}-$(date +%s).patch"
|
||||
cvs -q diff -uNp $PATCHSET >"$tmp_patch"
|
||||
if [ -z "$PAGER" ]; then
|
||||
PAGER="less -F"
|
||||
fi
|
||||
|
||||
$PAGER "$tmp_patch"
|
||||
rm "$tmp_patch"
|
||||
}
|
||||
|
||||
# Execute the user-specified recipes.
|
||||
for recipe in $@; do
|
||||
recipe_$recipe
|
||||
done
|
||||
|
||||
# If no recipe was provided, run a build.
|
||||
if [ -z "$1" ]; then
|
||||
recipe_build
|
||||
fi
|
|
@ -1,51 +0,0 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 1
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd Generate documentation from a C header file.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl i Ar file
|
||||
.Op Fl o Ar file
|
||||
.Op Fl D Ar key=value
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an extremely simple documentation generator that generates
|
||||
a BSD man page from a specially-formatted C header file.
|
||||
See
|
||||
.Xr hdoc 5
|
||||
for details on the format of this header file.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl i Ar file
|
||||
The input C header file to read from. If this option is omitted,
|
||||
then the standard input is read.
|
||||
.It Fl o Ar file
|
||||
The output BSD man page file to write. If this option is omitted,
|
||||
then the standard output is written.
|
||||
.It Fl D Ar key=value
|
||||
Set the register
|
||||
.Ar key
|
||||
to
|
||||
.Ar value .
|
||||
.Nm
|
||||
registers are used in various places, and can be set in either
|
||||
the header file, or on the command line. Setting registers that
|
||||
should be the same in all headers is best done from the command
|
||||
line for maintainability purposes, but header-specific values
|
||||
should be set in the header file itself.
|
||||
.Pp
|
||||
Registers are explained in more detail in
|
||||
.Xr hdoc 5 .
|
||||
.El
|
||||
.Sh RETURN VALUE
|
||||
.Pp
|
||||
.Nm
|
||||
returns a success value if the header is well-formed and the
|
||||
man page is successfully generated. It returns an error code in
|
||||
all other scenarios.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
|
@ -1,195 +0,0 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 5
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd C header file format accepted by the documentation generator.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
uses an extremely primitive parser to generate documentation from
|
||||
C header files. As such, the format accepted by
|
||||
.Nm
|
||||
is rather strict and may not be suitable for other projects beyond
|
||||
of Cytoplasm. This document describes what
|
||||
.Nm
|
||||
considers to be a valid C header file, and how that header file can
|
||||
be annotated to produce a nicely-formatted man page.
|
||||
.Pp
|
||||
At the very top level, a C header is a sequences of tokens that
|
||||
represent the following:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
An ANSI C89 comment.
|
||||
.It
|
||||
A preprocessor directive.
|
||||
.It
|
||||
A typedef declaration.
|
||||
.It
|
||||
A constant or other global variable declaration.
|
||||
.It
|
||||
A function declaration.
|
||||
.El
|
||||
.Pp
|
||||
Note that global variables and functions
|
||||
.Em must
|
||||
be marked with
|
||||
.Ar extern ,
|
||||
otherwise the parser will fail to recognize them. This is by
|
||||
design; a header should make everything extern by default,
|
||||
because it does not actually implement or declare anything.
|
||||
.Pp
|
||||
Preprocessor directives are completely ignored. Regular C
|
||||
comments are also ignored.
|
||||
.Nm
|
||||
is primarily concerned with type declarations, global
|
||||
variables, and functions. It also inspects specially-formatted
|
||||
C comments, which are used to annotate these elements of the
|
||||
header. The format of these comments is described here.
|
||||
.Pp
|
||||
There are two types of special comments recognized, the first
|
||||
of which is called the ``main'' comment block, as it documents
|
||||
the header itself, not the declarations contained in it. Main
|
||||
comment blocks also control the behavior of the parser and the
|
||||
resulting man page by setting registers. The format of the
|
||||
main comment block, which typically appears only once at the
|
||||
top of the header, although this is not a requirement, is as
|
||||
follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``triple star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
Any lines that start with a ``@'' are parser directives that
|
||||
set registers. The name of the register to set follows
|
||||
immediately after the ``@'' sigil, and continues until the first
|
||||
whitespace. The rest of the line is the value of the register.
|
||||
A list of registers recognized by
|
||||
.Nm
|
||||
is as follows. These registers control the man page output,
|
||||
and the last value set is the one that is used:
|
||||
.Bl -tag -width Ds
|
||||
.It \&Nm
|
||||
The name register. The name of the man page will be set to the
|
||||
contents of this register. It defaults to ``Unnmamed.''
|
||||
.It \&Dd
|
||||
The date register. The date of the man page will be set to the
|
||||
contents of this register. If left unset, it defaults to the
|
||||
current date.
|
||||
.It \&Os
|
||||
The operating system register. The Os value, which appears in
|
||||
the bottom left and right corners of the man page, will be set
|
||||
to the contents of this register. If left unset, it is not
|
||||
output.
|
||||
.It \&Nd
|
||||
The description register. The description of the man page will
|
||||
be set to the contents of this register. It defaults to
|
||||
``No description.''
|
||||
.It \&Xr
|
||||
The cross reference register. The SEE ALSO section of the man
|
||||
page will list the specified cross references, which are to be
|
||||
separated by a single space. The section shall be omitted,
|
||||
because it is set automatically to the same section that the
|
||||
current man page will belong to. This limitation may be removed
|
||||
in the future.
|
||||
.El
|
||||
.Pp
|
||||
These registers control the parser itself, modifying its
|
||||
behavior as soon as the appear in the file:
|
||||
.Bl -tag -width Ds
|
||||
.It ignore-typedefs
|
||||
Don't throw an error for an undocumented type declaration.
|
||||
The value doesn't matter; as soon as this register shows
|
||||
up, it's set. In most cases, it should not be used, however,
|
||||
it may be helpful in a few scenarios, such as when there are
|
||||
multiple typedefs that do the same thing, but are controlled
|
||||
by preprocessor macros.
|
||||
.It suppress-warnings
|
||||
Don't issue a warning for invalid or unrecognized top-level
|
||||
tokens. They will instead be ignored until the next
|
||||
recognized top-level token is found. The value doesn't
|
||||
matter; as soon as this register shows up, it is set. In most
|
||||
cases, it should not be used, however it may be helpful in a
|
||||
few scenarios, such as when function declarations are generated
|
||||
by preprocessor directives and thus don't follow the standard
|
||||
form.
|
||||
.El
|
||||
.It
|
||||
Any other lines in the main block are output to the DESCRIPTION
|
||||
section of the main page. This description may contain mdoc
|
||||
directives in it, as the lines are copied verbatim. If multiple
|
||||
main comment blocks appear in a single header, their description
|
||||
lines are appended in the order they appear.
|
||||
.El
|
||||
.Pp
|
||||
Declaration documentation comments are created as follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``double star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/**
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
The contents of the comment are copied verbatim into the output,
|
||||
so the comment may contain mdoc directives.
|
||||
.It
|
||||
The comment must appear before a declaration. If multiple
|
||||
documentation comments appear before a declaration, the last
|
||||
one before the declaration is used.
|
||||
.El
|
||||
.Pp
|
||||
The generated man page includes the name and description of the
|
||||
header, a synopsis section that lists all of the functions in
|
||||
the header, a description section that contains all the non-register
|
||||
lines of the main comment blocks, and then all of the documentation
|
||||
for each function, with the function prototype displayed as a
|
||||
subsection header, and the documentation displayed under it.
|
||||
.Sh EXAMPLES
|
||||
.Pp
|
||||
Consider the following simple C header:
|
||||
.Bd -literal -offset indent
|
||||
#include <stdio.h>
|
||||
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
To annotate this header in the manner
|
||||
.Nm
|
||||
expects, do something like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
* @Nm Hello
|
||||
* @Nd Say hello.
|
||||
* @Dd May 17 2023
|
||||
*
|
||||
* .Nm
|
||||
* provides functionality to write hello world messages
|
||||
* into standard C file descriptors.
|
||||
*
|
||||
* @Xr fputs fprintf
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* This function writes "hello world" to the given file
|
||||
* descriptor.
|
||||
* .Pp
|
||||
* There really isn't much more to be said about it.
|
||||
*/
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
This example shows how mdoc directives can be placed in
|
||||
documentation comments. Note that the triple-star comment
|
||||
documents the header itself, and the double-star comment
|
||||
documents the type declaration or function definition
|
||||
below it.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <Args.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Log.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
ArgParseStateInit(ArgParseState * state)
|
||||
{
|
||||
state->optPos = 1;
|
||||
state->optErr = 1;
|
||||
state->optInd = 1;
|
||||
state->optOpt = 0;
|
||||
state->optArg = NULL;
|
||||
}
|
||||
|
||||
int
|
||||
ArgParse(ArgParseState * state, Array * args, const char *optStr)
|
||||
{
|
||||
const char *arg;
|
||||
|
||||
arg = ArrayGet(args, state->optInd);
|
||||
|
||||
if (arg && StrEquals(arg, "--"))
|
||||
{
|
||||
state->optInd++;
|
||||
return -1;
|
||||
}
|
||||
else if (!arg || arg[0] != '-' || !isalnum((unsigned char) arg[1]))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *opt = strchr(optStr, arg[state->optPos]);
|
||||
|
||||
state->optOpt = arg[state->optPos];
|
||||
|
||||
if (!opt)
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Illegal option: %c", ArrayGet(args, 0), state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
else if (opt[1] == ':')
|
||||
{
|
||||
if (arg[state->optPos + 1])
|
||||
{
|
||||
state->optArg = (char *) arg + state->optPos + 1;
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else if (ArrayGet(args, state->optInd + 1))
|
||||
{
|
||||
state->optArg = (char *) ArrayGet(args, state->optInd + 1);
|
||||
state->optInd += 2;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Option requires an argument: %c", state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return *optStr == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return state->optOpt;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,423 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <Memory.h>
|
||||
|
||||
struct Array
|
||||
{
|
||||
void **entries; /* An array of void pointers, to
|
||||
* store any data */
|
||||
size_t allocated; /* Elements allocated on the heap */
|
||||
size_t size; /* Elements actually filled */
|
||||
};
|
||||
|
||||
int
|
||||
ArrayAdd(Array * array, void *value)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ArrayInsert(array, array->size, value);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayCreate(void)
|
||||
{
|
||||
Array *array = Malloc(sizeof(Array));
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
array->size = 0;
|
||||
array->allocated = ARRAY_BLOCK;
|
||||
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
Free(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayDelete(Array * array, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
void *element;
|
||||
|
||||
if (!array || array->size <= index)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = array->entries[index];
|
||||
|
||||
for (i = index; i < array->size - 1; i++)
|
||||
{
|
||||
array->entries[i] = array->entries[i + 1];
|
||||
}
|
||||
|
||||
array->size--;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void
|
||||
ArrayFree(Array * array)
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
Free(array->entries);
|
||||
Free(array);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayGet(Array * array, size_t index)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array->entries[index];
|
||||
}
|
||||
|
||||
|
||||
extern int
|
||||
ArrayInsert(Array * array, size_t index, void *value)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!array || !value || index > array->size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (array->size >= array->allocated)
|
||||
{
|
||||
void **tmp;
|
||||
size_t newSize = array->allocated + ARRAY_BLOCK;
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * newSize);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
array->allocated = newSize;
|
||||
}
|
||||
|
||||
for (i = array->size; i > index; i--)
|
||||
{
|
||||
array->entries[i] = array->entries[i - 1];
|
||||
}
|
||||
|
||||
array->size++;
|
||||
|
||||
array->entries[index] = value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern void *
|
||||
ArraySet(Array * array, size_t index, void *value)
|
||||
{
|
||||
void *oldValue;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
return ArrayDelete(array, index);
|
||||
}
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
oldValue = array->entries[index];
|
||||
array->entries[index] = value;
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
size_t
|
||||
ArraySize(Array * array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return array->size;
|
||||
}
|
||||
|
||||
int
|
||||
ArrayTrim(Array * array)
|
||||
{
|
||||
void **tmp;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * array->size);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArraySwap(Array * array, size_t i, size_t j)
|
||||
{
|
||||
void *p = array->entries[i];
|
||||
|
||||
array->entries[i] = array->entries[j];
|
||||
array->entries[j] = p;
|
||||
}
|
||||
|
||||
static size_t
|
||||
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
void *pivot = array->entries[high];
|
||||
size_t i = low - 1;
|
||||
size_t j;
|
||||
|
||||
for (j = low; j <= high - 1; j++)
|
||||
{
|
||||
if (compare(array->entries[j], pivot) < 0)
|
||||
{
|
||||
i++;
|
||||
ArraySwap(array, i, j);
|
||||
}
|
||||
}
|
||||
ArraySwap(array, i + 1, high);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
if (low < high)
|
||||
{
|
||||
size_t pi = ArrayPartition(array, low, high, compare);
|
||||
|
||||
ArrayQuickSort(array, low, pi ? pi - 1 : 0, compare);
|
||||
ArrayQuickSort(array, pi + 1, high, compare);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArraySort(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArrayQuickSort(array, 0, array->size - 1, compare);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayUnique(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ArrayDuplicate(array);
|
||||
if (!ret)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ArraySize(ret) == 1)
|
||||
{
|
||||
/* There can't be any duplicates when there's only 1 value */
|
||||
return ret;
|
||||
}
|
||||
|
||||
ArraySort(ret, compare);
|
||||
|
||||
for (i = 1; i < ArraySize(ret); i++)
|
||||
{
|
||||
void *cur = ret->entries[i];
|
||||
void *prev = ret->entries[i - 1];
|
||||
|
||||
if (compare(cur, prev) == 0)
|
||||
{
|
||||
/* Remove the duplicate, and put i back where it was. */
|
||||
ArrayDelete(ret, i--);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayTrim(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Even though the following operations could be done using only the
|
||||
* public Array API defined above, I opted for low-level struct
|
||||
* manipulation because it allows much more efficient copying; we only
|
||||
* allocate what we for sure need upfront, and don't have to
|
||||
* re-allocate during the operation. */
|
||||
|
||||
Array *
|
||||
ArrayReverse(Array * array)
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
size_t size;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!array->size)
|
||||
{
|
||||
return ArrayCreate();
|
||||
}
|
||||
|
||||
ret = Malloc(sizeof(Array));
|
||||
|
||||
size = array->size;
|
||||
|
||||
ret->size = size;
|
||||
ret->allocated = size;
|
||||
ret->entries = Malloc(sizeof(void *) * size);
|
||||
|
||||
if (!ret->entries)
|
||||
{
|
||||
Free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
ret->entries[i] = array->entries[size - i - 1];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayFromVarArgs(size_t n, va_list ap)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr->size = n;
|
||||
arr->allocated = n;
|
||||
arr->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr->entries)
|
||||
{
|
||||
Free(arr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
arr->entries[i] = va_arg(ap, void *);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayDuplicate(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr2 = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr2)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr2->size = arr->size;
|
||||
arr2->allocated = arr->size;
|
||||
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr2->entries)
|
||||
{
|
||||
Free(arr2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < arr2->size; i++)
|
||||
{
|
||||
arr2->entries[i] = arr->entries[i];
|
||||
}
|
||||
|
||||
return arr2;
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <Base64.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
static const char Base64EncodeMap[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const int Base64DecodeMap[] = {
|
||||
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
|
||||
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||
43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
size_t
|
||||
Base64EncodedSize(size_t inputSize)
|
||||
{
|
||||
size_t size = inputSize;
|
||||
|
||||
if (inputSize % 3)
|
||||
{
|
||||
size += 3 - (inputSize % 3);
|
||||
}
|
||||
|
||||
size /= 3;
|
||||
size *= 4;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t
|
||||
Base64DecodedSize(const char *base64, size_t len)
|
||||
{
|
||||
size_t ret;
|
||||
size_t i;
|
||||
|
||||
if (!base64)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = len / 4 * 3;
|
||||
|
||||
for (i = len; i > 0; i--)
|
||||
{
|
||||
if (base64[i] == '=')
|
||||
{
|
||||
ret--;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Encode(const char *input, size_t len)
|
||||
{
|
||||
char *out;
|
||||
size_t outLen;
|
||||
size_t i, j, v;
|
||||
|
||||
if (!input || !len)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64EncodedSize(len);
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 3, j += 4)
|
||||
{
|
||||
v = input[i];
|
||||
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
|
||||
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
|
||||
|
||||
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
|
||||
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < len)
|
||||
{
|
||||
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 2] = '=';
|
||||
}
|
||||
if (i + 2 < len)
|
||||
{
|
||||
out[j + 3] = Base64EncodeMap[v & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 3] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int
|
||||
Base64IsValidChar(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c == '+') ||
|
||||
(c == '/') ||
|
||||
(c == '=');
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Decode(const char *input, size_t len)
|
||||
{
|
||||
size_t i, j;
|
||||
int v;
|
||||
size_t outLen;
|
||||
char *out;
|
||||
|
||||
if (!input)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64DecodedSize(input, len);
|
||||
if (len % 4)
|
||||
{
|
||||
/* Invalid length; must have incorrect padding */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Scan for invalid characters. */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
if (!Base64IsValidChar(input[i]))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 4, j += 3)
|
||||
{
|
||||
v = Base64DecodeMap[input[i] - 43];
|
||||
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
|
||||
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
|
||||
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
|
||||
|
||||
out[j] = (v >> 16) & 0xFF;
|
||||
if (input[i + 2] != '=')
|
||||
out[j + 1] = (v >> 8) & 0xFF;
|
||||
if (input[i + 3] != '=')
|
||||
out[j + 2] = v & 0xFF;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern void
|
||||
Base64Unpad(char *base64, size_t length)
|
||||
{
|
||||
if (!base64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (base64[length - 1] == '=')
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
base64[length] = '\0';
|
||||
}
|
||||
|
||||
extern int
|
||||
Base64Pad(char **base64Ptr, size_t length)
|
||||
{
|
||||
char *tmp;
|
||||
size_t newSize;
|
||||