aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2023-09-13 00:01:28 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2023-09-13 00:01:28 +0200
commita82b6c772089aa46e30c6c89ef48f514294df3cb (patch)
treee25d9b6fd1fefe8b6ac293a5c0b53293872a8f54
parentAdd basic documentation for lens. (diff)
parentEven more documentation. (diff)
downloadcalp-a82b6c772089aa46e30c6c89ef48f514294df3cb.tar.gz
calp-a82b6c772089aa46e30c6c89ef48f514294df3cb.tar.xz
Merge branch 'next' into datarewrite-structures
-rw-r--r--.dir-locals.el1
-rw-r--r--.envrc17
-rw-r--r--.gitignore2
-rw-r--r--Makefile19
-rw-r--r--calp.c176
-rw-r--r--doc/ref/calp-prime.texi8
-rw-r--r--doc/ref/calp.texi14
-rw-r--r--doc/ref/entry-points.texi62
-rw-r--r--doc/ref/general.texi31
-rw-r--r--doc/ref/general/base64.texi (renamed from doc/ref/guile/base64.texi)0
-rw-r--r--doc/ref/general/bst.texi37
-rw-r--r--doc/ref/general/crypto.texi17
-rw-r--r--doc/ref/general/data-formats.texi (renamed from doc/ref/guile/data-formats.texi)0
-rw-r--r--doc/ref/general/data-stores.texi (renamed from doc/ref/guile/data-stores.texi)0
-rw-r--r--doc/ref/general/datetime.texi (renamed from doc/ref/guile/datetime.texi)0
-rw-r--r--doc/ref/general/glob.texi37
-rw-r--r--doc/ref/general/graph.texi65
-rw-r--r--doc/ref/general/graphviz.texi10
-rw-r--r--doc/ref/general/io.texi31
-rw-r--r--doc/ref/general/lens.texi (renamed from doc/ref/guile/lens.texi)0
-rw-r--r--doc/ref/general/options.texi32
-rw-r--r--doc/ref/general/srfi-41.texi (renamed from doc/ref/guile/srfi-41.texi)22
-rw-r--r--doc/ref/general/sxml.texi (renamed from doc/ref/guile/sxml.texi)0
-rw-r--r--doc/ref/general/util-config.texi (renamed from doc/ref/guile/util-config.texi)0
-rw-r--r--doc/ref/general/util-exceptions.texi40
-rw-r--r--doc/ref/general/util-object.texi (renamed from doc/ref/guile/util-object.texi)0
-rw-r--r--doc/ref/general/util-path.texi (renamed from doc/ref/guile/util-path.texi)9
-rw-r--r--doc/ref/general/util-type.texi (renamed from doc/ref/guile/util-type.texi)0
-rw-r--r--doc/ref/general/util.texi (renamed from doc/ref/guile/util.texi)84
-rw-r--r--doc/ref/general/uuid.texi17
-rw-r--r--doc/ref/general/webdav.texi (renamed from doc/ref/guile/webdav.texi)0
-rw-r--r--doc/ref/general/xdg-basedir.texi62
-rw-r--r--doc/ref/general/zic.texi (renamed from doc/ref/guile/zic.texi)0
-rw-r--r--doc/ref/guile.texi192
-rw-r--r--doc/ref/introspection.texi84
-rw-r--r--doc/ref/javascript.texi48
-rw-r--r--doc/ref/javascript/clock.texi78
-rw-r--r--doc/ref/javascript/components/changelog.texi10
-rw-r--r--doc/ref/javascript/components/date_time_input.texi34
-rw-r--r--doc/ref/javascript/components/edit_rrule.texi10
-rw-r--r--doc/ref/javascript/components/input_list.texi16
-rw-r--r--doc/ref/javascript/components/popup_element.texi40
-rw-r--r--doc/ref/javascript/components/tab_group_element.texi46
-rw-r--r--doc/ref/javascript/components/vevent.texi23
-rw-r--r--doc/ref/javascript/components/vevent_block.texi10
-rw-r--r--doc/ref/javascript/components/vevent_description.texi16
-rw-r--r--doc/ref/javascript/components/vevent_dl.texi11
-rw-r--r--doc/ref/javascript/components/vevent_edit.texi9
-rw-r--r--doc/ref/javascript/eventCreator.texi15
-rw-r--r--doc/ref/javascript/formatters.texi23
-rw-r--r--doc/ref/javascript/globals.texi41
-rw-r--r--doc/ref/javascript/jcal.texi7
-rw-r--r--doc/ref/javascript/lib.texi148
-rw-r--r--doc/ref/javascript/server_connect.texi22
-rw-r--r--doc/ref/javascript/types.texi95
-rw-r--r--doc/ref/javascript/user-additions.texi18
-rw-r--r--doc/ref/javascript/vevent.texi113
-rw-r--r--doc/ref/text.texi9
-rw-r--r--doc/ref/text/flow.texi23
-rw-r--r--doc/ref/text/markup.texi71
-rw-r--r--doc/ref/text/numbers.texi42
-rw-r--r--doc/ref/text/utilities.texi67
-rw-r--r--doc/ref/vcomponent.texi (renamed from doc/ref/guile/vcomponent.texi)17
-rw-r--r--doc/ref/vulgar.texi27
-rw-r--r--doc/ref/vulgar/color.texi18
-rw-r--r--doc/ref/vulgar/termios.texi217
-rw-r--r--doc/ref/web.texi6
-rw-r--r--doc/ref/web/query.texi9
-rw-r--r--doc/ref/web/routes.texi (renamed from doc/ref/guile/web.texi)23
-rw-r--r--doc/ref/web/uri-query.texi7
-rw-r--r--env17
-rwxr-xr-xmain8
-rw-r--r--module/calp/load-config.scm9
-rw-r--r--module/glob.scm15
-rw-r--r--module/hnh/module-introspection/all-modules.scm25
-rw-r--r--module/hnh/util.scm53
-rw-r--r--module/hnh/util/env.scm6
-rw-r--r--module/hnh/util/exceptions.scm3
-rw-r--r--module/hnh/util/path.scm11
-rw-r--r--module/scripts/find-undocumented.scm177
-rw-r--r--module/srfi/srfi-41/util.scm16
-rw-r--r--module/text/markup.scm1
-rw-r--r--module/text/numbers.scm8
-rw-r--r--module/vcomponent/control.scm37
-rw-r--r--module/vcomponent/util/control.scm2
-rw-r--r--module/vcomponent/util/search.scm3
-rw-r--r--module/vulgar/color.scm1
-rw-r--r--module/xdg/basedir.scm43
-rw-r--r--production-main3
-rwxr-xr-xstart2
-rw-r--r--static/.dir-locals.el1
-rw-r--r--static/.gitignore2
-rw-r--r--static/Makefile30
-rw-r--r--static/README.md46
-rw-r--r--static/components/date-jump.ts40
-rwxr-xr-xstatic/make-watch2
-rw-r--r--static/package-lock.json2180
-rw-r--r--static/package.json7
-rwxr-xr-xstatic/prettify-tsconfig.js15
-rw-r--r--static/scss/_global.scss (renamed from static/_global.scss)0
-rw-r--r--static/scss/_slider_input.scss (renamed from static/_slider_input.scss)0
-rw-r--r--static/scss/_small-calendar.scss (renamed from static/_small-calendar.scss)0
-rw-r--r--static/scss/directory-listing.scss (renamed from static/directory-listing.scss)0
-rw-r--r--static/scss/smallcal.scss (renamed from static/smallcal.scss)0
-rw-r--r--static/scss/style.scss (renamed from static/style.scss)0
-rw-r--r--static/ts/clock.ts (renamed from static/clock.ts)86
-rw-r--r--static/ts/components.ts (renamed from static/components.ts)22
-rw-r--r--static/ts/components/changelog.ts (renamed from static/components/changelog.ts)30
-rw-r--r--static/ts/components/date-jump.ts51
-rw-r--r--static/ts/components/date-time-input.ts (renamed from static/components/date-time-input.ts)70
-rw-r--r--static/ts/components/edit-rrule.ts (renamed from static/components/edit-rrule.ts)12
-rw-r--r--static/ts/components/input-list.ts (renamed from static/components/input-list.ts)84
-rw-r--r--static/ts/components/popup-element.ts (renamed from static/components/popup-element.ts)46
-rw-r--r--static/ts/components/slider.ts (renamed from static/components/slider.ts)70
-rw-r--r--static/ts/components/tab-group-element.ts (renamed from static/components/tab-group-element.ts)58
-rw-r--r--static/ts/components/vevent-block.ts (renamed from static/components/vevent-block.ts)23
-rw-r--r--static/ts/components/vevent-description.ts (renamed from static/components/vevent-description.ts)16
-rw-r--r--static/ts/components/vevent-dl.ts (renamed from static/components/vevent-dl.ts)12
-rw-r--r--static/ts/components/vevent-edit.ts (renamed from static/components/vevent-edit.ts)11
-rw-r--r--static/ts/components/vevent.ts (renamed from static/components/vevent.ts)44
-rw-r--r--static/ts/event-creator.ts (renamed from static/event-creator.ts)77
-rw-r--r--static/ts/formatters.ts (renamed from static/formatters.ts)20
-rw-r--r--static/ts/globals.ts (renamed from static/globals.ts)35
-rw-r--r--static/ts/jcal.ts (renamed from static/jcal.ts)34
-rw-r--r--static/ts/lib.ts (renamed from static/lib.ts)188
-rw-r--r--static/ts/script.ts (renamed from static/script.ts)0
-rw-r--r--static/ts/server_connect.ts (renamed from static/server_connect.ts)22
-rw-r--r--static/ts/types.ts (renamed from static/types.ts)80
-rw-r--r--static/ts/vevent.ts (renamed from static/vevent.ts)173
-rw-r--r--static/tsconfig.json75
-rwxr-xr-xtests/run-tests.scm9
-rw-r--r--tests/test/let-env.scm48
-rw-r--r--tests/test/srfi-41-util.scm20
-rw-r--r--tests/test/util.scm379
-rw-r--r--tests/test/uuid.scm13
-rw-r--r--tests/test/xdg-basedir.scm58
-rwxr-xr-xtests/validate-html/run-validator.scm8
137 files changed, 5626 insertions, 1449 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 00000000..b6a3a4ce
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1 @@
+((nil (geiser-repl-add-project-paths "." "module")))
diff --git a/.envrc b/.envrc
new file mode 100644
index 00000000..0fccc035
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,17 @@
+guile_version=$(guile -c '(display (version))')
+
+if [ "${GUILE_LOAD_PATH:-no}" = "no" ]; then
+ export GUILE_LOAD_PATH="$PWD/module"
+else
+ export GUILE_LOAD_PATH="$PWD/module:$GUILE_LOAD_PATH"
+fi
+
+if [ "${GUILE_LOAD_COMPILED_PATH:-no}" = "no" ]; then
+ export GUILE_LOAD_COMPILED_PATH="$PWD/obj-${guile_version}"
+else
+ export GUILE_LOAD_COMPILED_PATH="$PWD/obj-${guile_version}:$GUILE_LOAD_COMPILED_PATH"
+fi
+unset guile_version
+
+export PATH="$PWD/static/node_modules/.bin:$PATH"
+export PATH="$PWD/scripts/:$PATH"
diff --git a/.gitignore b/.gitignore
index b92e0bcf..b7cea985 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
coverage
obj-*
localization
+calp
+calp-release
diff --git a/Makefile b/Makefile
index 7c51dac5..7ab69459 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,11 @@ GUILE_C_FLAGS = -Lmodule \
-Wmacro-use-before-definition -Warity-mismatch \
-Wduplicate-case-datum -Wbad-case-datum
+CLIBS = guile-3.0
+CFLAGS = -Wall -pedantic -std=c11 $(shell pkg-config --cflags $(CLIBS))
+LDLIBS = $(shell pkg-config --libs $(CLIBS))
+LDFLAGS = -lrt
+
# All po-files inside po/, except new.po, and hidden files
PO_FILES = $(shell find po -type f -name \*.po -and -not -name new.po -and -not -name .\*)
LOCALIZATIONS = $(PO_FILES:po/%.po=localization/%/LC_MESSAGES/calp.mo)
@@ -36,9 +41,15 @@ LIMIT_FILES=$(LIMIT:%=--only %)
# Skip these files when testing
SKIP=--skip $(PWD)/tests/test/web-server.scm
-all: go_files static $(LOCALIZATIONS)
+all: calp go_files static $(LOCALIZATIONS)
$(MAKE) -C doc/ref
+calp: calp.c
+ $(CC) -ggdb $(CFLAGS) -DBUILD_ENV $(LDFLAGS) -o $@ $< $(LDLIBS)
+
+calp-release: calp.c
+ $(CC) -O2 $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
+
XGETTEXT_FLAGS = --from-code=UTF-8 --add-comments --indent -kG_
static:
@@ -65,8 +76,10 @@ localization/%/LC_MESSAGES/calp.mo: po/%.po
clean:
-$(MAKE) -C static clean
-rm -r obj-*
+ -rm calp
+ -rm calp-release
-install: all
+install: all calp-release
install -d $(DESTDIR)$(GUILE_SITE_DIR) $(DESTDIR)$(GUILE_CCACHE_DIR)
rsync -a module/ $(DESTDIR)$(GUILE_SITE_DIR)
rsync -a obj-$(GUILE_VERSION)/ $(DESTDIR)$(GUILE_CCACHE_DIR)
@@ -75,7 +88,7 @@ install: all
$(MAKE) -C doc/ref install
install -m 644 -D -t $(DESTDIR)/usr/share/doc/calp README.md
install -m 755 -D -t $(DESTDIR)/usr/lib/calp/ scripts/tzget
- install -m755 -D production-main $(DESTDIR)/usr/bin/calp
+ install -m755 -D calp-release $(DESTDIR)/usr/bin/calp
lcov.info: $(GO_FILES)
env DEBUG=0 tests/run-tests.scm --coverage=$@ $(if $(VERBOSE),--verbose) $(SKIP) $(LIMIT_FILES)
diff --git a/calp.c b/calp.c
new file mode 100644
index 00000000..98d883f5
--- /dev/null
+++ b/calp.c
@@ -0,0 +1,176 @@
+#define _XOPEN_SOURCE 500
+#define _POSIX_C_SOURCE 200112L
+
+/**
+ * Primary entry point for calp.
+ *
+ * This is a C file, rather than a shellscript for 2 reasons:
+ * 1. It makes the binary show up with the proper name in process
+ * listings
+ * 2. Guile's default command line handling leaves a lot to be
+ * desired.
+ *
+ * The following pre-processor variables are checked during compilation:
+ *
+ * BUILD_ENV
+ * If defined then the environment is fetched from where the file
+ * resides. This is for development when all dependencies are in the
+ * repo alongside this file.
+ * MAIN_MODULE
+ * Guile module containing the programs entry point. Note that it
+ * should be given without parenthesis.
+ *
+ * Defaults to "calp main"
+ * MAIN_PROC
+ * Procedure within the MAIN_MODULE which is the programs entry
+ * point.
+ *
+ * Default to "main"
+ *
+ * During runtime, the environment variable "__PRINT_ENVIRONMENT" is
+ * checked, and if its set then a environment variables suitable for
+ * guile is printed instead of running anything. This is mainly used
+ * by the test runners.
+ *
+ * Parts of this file is noted as being "borrowed from Guile". Guile
+ * is covered under LGPL3, this file is covered under AGPL, which
+ * means it is ok.
+ */
+
+#ifndef MAIN_MODULE
+#define MAIN_MODULE "calp main"
+#endif
+
+#ifndef MAIN_PROC
+#define MAIN_PROC "main"
+#endif
+
+#include <libgen.h>
+
+#include <libguile.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+#include <errno.h>
+#include <limits.h>
+
+/** Definitions to ensure each instance is correctly spelled below */
+#define GUILE_LOAD_PATH "GUILE_LOAD_PATH"
+#define GUILE_LOAD_COMPILED_PATH "GUILE_LOAD_COMPILED_PATH"
+#define GUILE_AUTO_COMPILE "GUILE_AUTO_COMPILE"
+
+#define PRINT_ENVIRONMENT "__PRINT_ENVIRONMENT"
+
+/** Borrowed from Guile 3.0.9 libguile/guile.c */
+static int
+get_integer_from_environment (const char *var, int def)
+{
+ char *end = 0;
+ char *val = getenv (var);
+ long res = def;
+ if (!val)
+ return def;
+ res = strtol (val, &end, 10);
+ if (end == val)
+ {
+ fprintf (stderr, "guile: warning: invalid %s: %s\n", var, val);
+ return def;
+ }
+ return res;
+}
+
+static void inner_main (void *closure, int argc, char **argv) {
+ SCM main = scm_c_public_ref(MAIN_MODULE, MAIN_PROC);
+ SCM scm_args = scm_c_make_vector (argc, SCM_UNDEFINED);
+ for (size_t i = 0; i < (size_t) argc; i++) {
+ scm_c_vector_set_x (scm_args, i, scm_from_locale_string(argv[i]));
+ }
+ scm_call_1(main, scm_vector_to_list(scm_args));
+}
+
+/** Procedure marked unused since GCC dosen't understand function
+ * pointers when checking usage */
+__attribute__((__unused__))
+static void *get_guile_version (void *data) {
+ (void) data;
+ return scm_to_locale_string(scm_version());
+}
+
+
+static void export (const char *key, const char *val) {
+ printf("export %s=%s;\n", key, val);
+}
+
+
+int main(int argc, char *argv[]) {
+
+ /* Locale initialization code borrowed from Guile 3.0.9 libguile/guile.c */
+ if (get_integer_from_environment("GUILE_INSTALL_LOCALE", 1) && setlocale(LC_ALL, "") == NULL) {
+ fprintf(stderr, "calp: warning: failed to install locale\n");
+ }
+
+#ifdef BUILD_ENV
+
+ char *bin_path = realpath(argv[0], NULL);
+ if (bin_path == NULL) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return 1;
+ }
+ char *here = dirname(bin_path);
+
+ {
+ const char *load_path = getenv(GUILE_LOAD_PATH);
+ size_t len = PATH_MAX + strlen("module") + 1;
+ if (load_path) len += strlen(load_path);
+
+ char *buf = malloc(len);
+
+ if (load_path) {
+ sprintf(buf, "%s:%s/module", load_path, here);
+ } else {
+ sprintf(buf, "%s/module", here);
+ }
+
+ setenv(GUILE_LOAD_PATH, buf, 1);
+
+ free(buf);
+ }
+
+ {
+ char *version = scm_with_guile(&get_guile_version, NULL);
+
+ const char *load_path = getenv(GUILE_LOAD_COMPILED_PATH);
+ size_t len = PATH_MAX + strlen("module") + strlen(version) + 1;
+ if (load_path) len += strlen(load_path);
+
+ char *buf = malloc(len);
+
+ if (load_path) {
+ sprintf(buf, "%s:%s/obj-%s", load_path, here, version);
+ } else {
+ sprintf(buf, "%s/obj-%s", here, version);
+ }
+
+ setenv(GUILE_LOAD_COMPILED_PATH, buf, 1);
+
+ free(buf);
+ free(version);
+ }
+
+ setenv(GUILE_AUTO_COMPILE, "0", 1);
+
+ free(bin_path);
+#endif
+
+ if (getenv(PRINT_ENVIRONMENT)) {
+ export(GUILE_LOAD_PATH, getenv(GUILE_LOAD_PATH));
+ export(GUILE_LOAD_COMPILED_PATH, getenv(GUILE_LOAD_COMPILED_PATH));
+ export(GUILE_AUTO_COMPILE, "0");
+ export("GUILE", "${GUILE:-guile}");
+ export("CALP_TEST_ENVIRONMENT", "1");
+ return 0;
+ }
+
+ scm_boot_guile (argc, argv, inner_main, 0);
+ return 0; /* never reacher */
+}
diff --git a/doc/ref/calp-prime.texi b/doc/ref/calp-prime.texi
new file mode 100644
index 00000000..a13cbd0b
--- /dev/null
+++ b/doc/ref/calp-prime.texi
@@ -0,0 +1,8 @@
+@node Calp as a Program
+@chapter Calp as a Program
+
+@include calp/translation.texi
+@include calp/error.texi
+@include calp/other.texi
+
+
diff --git a/doc/ref/calp.texi b/doc/ref/calp.texi
index 92c30242..5815d922 100644
--- a/doc/ref/calp.texi
+++ b/doc/ref/calp.texi
@@ -1,4 +1,5 @@
\input texinfo
+@setfilename calp.info
@settitle Calp
@copying
@@ -63,8 +64,14 @@ text @footnote{Improvements welcome}
@c * Index::
@c @end menu
-@include guile.texi
-@include javascript.texi
+@include calp-prime.texi
+@include entry-points.texi
+@include general.texi
+@include vcomponent.texi
+@include web.texi
+@include text.texi
+@include vulgar.texi
+@include introspection.texi
@node Index
@unnumbered Index
@@ -75,7 +82,4 @@ text @footnote{Improvements welcome}
@printindex tp
@printindex vr
-@unnumbered Web Components
-@printindex wc
-
@bye
diff --git a/doc/ref/entry-points.texi b/doc/ref/entry-points.texi
new file mode 100644
index 00000000..4c4ec90f
--- /dev/null
+++ b/doc/ref/entry-points.texi
@@ -0,0 +1,62 @@
+@node Entry Points
+@chapter Entry Points
+
+Calp has many different entry points. Each is implemented in a module
+residing under @code{calp entry-points}. Each such module @emph{must}
+export a @code{main} procedure, which should take a list of command
+line arguments (as a single parameter).
+
+When starting Calp from the command line, the @code{main} procedure
+located in @code{(calp main)} is called. That procedure parse it's own
+command line flags, up until the first non-flag argument. That word
+will be used to chose the actual entry point, which is then
+responsible for parsing the remaining command line flags.
+
+Information about global command line arguments can be accessed by
+running @command{calp --help}, and subcommands arguments through
+@command{calp @var{<subcommand>} --help}.
+
+@deftp {Entry Point} benchmark module
+Run @code{(@ (calp benchmark @var{module}) run-benchmark)} with
+profiling enabled, and prints the collected data.
+@end deftp
+
+@deftp {Entry Point} convert
+Convert calendar files between filetypes.
+@end deftp
+
+@deftp {Entry Point} html
+Generates static HTML files.
+@end deftp
+
+@deftp {Entry Point} ical
+Generates static iCal files.
+@end deftp
+
+@deftp {Entry Point} import
+Import entry into database.
+@end deftp
+
+@deftp {Entry Point} server
+Start the web server.
+@end deftp
+
+@deftp {Entry Point} terminal
+Start the terminal interface.
+@end deftp
+
+@deftp {Entry Point} text
+Format text, completely ignoring calendars.
+@end deftp
+
+@deftp {Entry Point} tidsrapport
+Generate an FDF (PDF Form submission file) for some very specific time
+reporting sheets.
+
+TODO this should be removed from this project, and moved to a
+``private'' repo of hugo.
+@end deftp
+
+@deftp {Entry Point} update-zoneinfo
+Downloads zoneinfo files and places them in the correct location.
+@end deftp
diff --git a/doc/ref/general.texi b/doc/ref/general.texi
new file mode 100644
index 00000000..b97dece0
--- /dev/null
+++ b/doc/ref/general.texi
@@ -0,0 +1,31 @@
+@node Generally Useful Utilities
+@chapter Generally Useful Utilities
+
+These are all general utilitios with no direct relation to Calp. Many
+of these should be generally useful in any project.
+
+@include general/datetime.texi
+@include general/zic.texi
+@include general/srfi-41.texi
+@include general/util.texi
+@include general/uuid.texi
+@include general/io.texi
+@include general/bst.texi
+@include general/util-path.texi
+@include general/util-config.texi
+@include general/util-exceptions.texi
+@include general/base64.texi
+@include general/xdg-basedir.texi
+@include general/glob.texi
+@include general/graphviz.texi
+@include general/crypto.texi
+@include general/graph.texi
+@include general/options.texi
+
+
+@include general/util-type.texi
+@include general/util-object.texi
+@include general/lens.texi
+@include general/data-formats.texi
+@include general/data-stores.texi
+@include general/webdav.texi
diff --git a/doc/ref/guile/base64.texi b/doc/ref/general/base64.texi
index ab6bba81..ab6bba81 100644
--- a/doc/ref/guile/base64.texi
+++ b/doc/ref/general/base64.texi
diff --git a/doc/ref/general/bst.texi b/doc/ref/general/bst.texi
new file mode 100644
index 00000000..d2bba0ff
--- /dev/null
+++ b/doc/ref/general/bst.texi
@@ -0,0 +1,37 @@
+@node Binary Search Tree
+@section Binary Search Tree
+
+A simple ``read only'' binary search tree.
+
+@defun make-tree pred? lst
+Constructs a new tree. @var{pred?} should be a procedure taking the
+first element of @var{lst}, along with each element, and should return
+a boolean value indicating if the specific element should go in the
+left or right subtree. (left subtree is ``truthy'' values).
+
+This operation is done recursively.
+@end defun
+
+@defun tree-node tree
+Return the value of a tree node.
+@end defun
+
+@defun left-subtree tree
+Return all ``truthy'' children of tree node.
+@end defun
+
+@defun right-subtree tree
+Return all ``falsy children of tree node.
+@end defun
+
+@defun length-of-longest-branch tree
+Get the depth of a tree.
+@end defun
+
+@defun tree-map proc tree
+Apply proc onto the value of every node in tree, keeping the structure
+of the tree.
+
+@b{Note:} this can cause the tree to no longer be a binary search
+tree, but simply a ``formless'' binary tree.
+@end defun
diff --git a/doc/ref/general/crypto.texi b/doc/ref/general/crypto.texi
new file mode 100644
index 00000000..b9e362d3
--- /dev/null
+++ b/doc/ref/general/crypto.texi
@@ -0,0 +1,17 @@
+@node Cryptographic and Hash Procedures
+@section Cryptographic and Hash Procedures
+
+This module links libcrypto, exposing some hash procedures.
+
+@defun sha256 message
+Calculate the sha256-sum of @var{message}. The message can either be a
+bytevector or a string (in which case it will de encoded as UTF-8).
+
+Returns the checksum as a bytevector.
+@end defun
+
+@defun checksum->string message-digest [port]
+Generates a hex digest string from a checksum (generated by
+@code{sha256}). The checksum is written to @var{port} if given, or
+returned as a string otherwise.
+@end defun
diff --git a/doc/ref/guile/data-formats.texi b/doc/ref/general/data-formats.texi
index 037d3ae7..037d3ae7 100644
--- a/doc/ref/guile/data-formats.texi
+++ b/doc/ref/general/data-formats.texi
diff --git a/doc/ref/guile/data-stores.texi b/doc/ref/general/data-stores.texi
index ec3962da..ec3962da 100644
--- a/doc/ref/guile/data-stores.texi
+++ b/doc/ref/general/data-stores.texi
diff --git a/doc/ref/guile/datetime.texi b/doc/ref/general/datetime.texi
index 037ac8d5..037ac8d5 100644
--- a/doc/ref/guile/datetime.texi
+++ b/doc/ref/general/datetime.texi
diff --git a/doc/ref/general/glob.texi b/doc/ref/general/glob.texi
new file mode 100644
index 00000000..400eb1f7
--- /dev/null
+++ b/doc/ref/general/glob.texi
@@ -0,0 +1,37 @@
+@node Glob
+@section Glob
+
+@defun glob str
+Globs (glob(7)) on @var{str}, returing a list of files, which will be
+on the same form of the glob. E.g. @code{(glob "../*")} will return
+strings all starting with ``../''
+
+If no matches are found, a misc error is thrown.
+@end defun
+
+
+
+@defvar GLOB_NOMAGIC
+@defvarx GLOB_NOCHECK
+@defvarx unix
+@defvarx GLOB_NOSPACE
+@defvarx GLOB_TILDE_CHECK
+@defvarx GLOB_ALTDIRFUNC
+@defvarx GLOB_NOSORT
+@defvarx GLOB_NOMATCH
+@defvarx GLOB_TILDE
+@defvarx GLOB_ERR
+@defvarx GLOB_MAGCHAR
+@defvarx GLOB_BRACE
+@defvarx GLOB_APPEND
+@defvarx GLOB_NOSYS
+@defvarx GLOB_DOOFFS
+@defvarx GLOB_NOESCAPE
+@defvarx GLOB_MARK
+@defvarx GLOB_PERIOD
+@defvarx linux
+@defvarx GLOB_ABORTED
+@defvarx _POSIX_VDISABLE
+@defvarx GLOB_ONLYDIR
+``Symbols'' imported from the C header ``glob.h''. See that documentation.
+@end defvar
diff --git a/doc/ref/general/graph.texi b/doc/ref/general/graph.texi
new file mode 100644
index 00000000..1677b635
--- /dev/null
+++ b/doc/ref/general/graph.texi
@@ -0,0 +1,65 @@
+@node A Graph data structure
+@section A Graph data structure
+
+This is a simple immutable directed graph.
+
+Most operations are O(n), since Scheme lacks a total order betwen
+arbitrary objects.
+
+@defun make-graph [node-key-proc=identity] [node-equal?=eq?]
+Constructs a new graph.
+
+@var{node-key-proc} should be a procedure mapping the actual nodes to
+a value which can be compared. These values are sometimes called
+``node keys''.
+@cindex node keys
+
+@var{node-equal?} should return if two nodes are equal or not.
+@end defun
+
+@defun rebuild-graph [old-graph] [nodes='()] [edges='()]
+@c TODO document me
+@end defun
+
+@defun graph-empty? graph
+Does the graph contaitn
+@end defun
+
+@defun add-node graph node edge-neighbors
+Adds the value @var{node} to @var{graph}. @var{edge-neighbors} should
+be a list of node keys.
+@end defun
+
+@defun get-node graph key
+Retrieve a node from the graph, given its node key.
+
+Returns @code{#f} if no such node exists.
+@end defun
+
+@defun remove-node graph node
+Remvoe @var{node} from @var{graph}, if it exists.
+@end defun
+
+@defun find-dangling-node graph
+Find a node in the graph which no other node ``depends'' on (has an
+edge pointing at it).
+
+NOTE this is O(n^2) (maybe, sort of?)
+Getting it faster would require building an index, which
+is hard since there isn't a total order on symbols.
+@end defun
+
+
+@defun pop-dandling-node graph
+Find a node as per @code{find-dangling-node}, and return two values:
+the node, and a new graph without that node.
+@end defun
+
+@defun resolve-dependency-graph graph
+If each edge is assumed to indicate a nodes dependencies, then this
+procedure will find return a flat list where each dependency is before
+its dependants.
+
+If any link is missing, or a cycle exists, then the result is
+undefined.
+@end defun
diff --git a/doc/ref/general/graphviz.texi b/doc/ref/general/graphviz.texi
new file mode 100644
index 00000000..72817ea8
--- /dev/null
+++ b/doc/ref/general/graphviz.texi
@@ -0,0 +1,10 @@
+@node Graphviz
+@section Graphviz
+
+The graphviz library comes bundled with Guile bindings, but without a
+corresponding .scm file exporting the symbols. The module @code{(guile)} does
+exactly that.
+
+This ``header'' is borrowed from
+@url{https://github.com/roelj/graphviz-guile/blob/master/graphviz.scm},
+under GPL 3.0.
diff --git a/doc/ref/general/io.texi b/doc/ref/general/io.texi
new file mode 100644
index 00000000..3f67700b
--- /dev/null
+++ b/doc/ref/general/io.texi
@@ -0,0 +1,31 @@
+@node IO operations
+@section IO operations
+
+Provided by module @code{(hnh util io)}.
+
+@defun open-input-port path
+@defunx open-output-port path
+Like @code{open-*-file}, but ``-'' gives @code{standard-@{input,output@}}.
+@end defun
+
+@defun read-lines port
+Return a list of all lines read from port.
+@end defun
+
+@defun with-atomic-output-to-file filename thunk
+Same functionality as the regular @var{with-output-to-file}, but
+with the difference that either everything is written, or nothing
+is written, and if anything is written it's all written atomicaly at
+once (the original file will never contain an intermidiate state).
+Does NOT handle race conditions between threads.
+
+propagates the return value of @var{thunk} upon successfully writing
+the file, and @code{#f} otherwise.
+@end defun
+
+@defun call-with-tmpfile proc [#:tmpl ``/tmp/file-XXXXXXX'']
+@end defun
+
+@defun read-file path
+Open file at path, and return its content as a string.
+@end defun
diff --git a/doc/ref/guile/lens.texi b/doc/ref/general/lens.texi
index eeddd6ca..eeddd6ca 100644
--- a/doc/ref/guile/lens.texi
+++ b/doc/ref/general/lens.texi
diff --git a/doc/ref/general/options.texi b/doc/ref/general/options.texi
new file mode 100644
index 00000000..675e91f3
--- /dev/null
+++ b/doc/ref/general/options.texi
@@ -0,0 +1,32 @@
+@node Getopt-Long Extensions
+@section Getopt-Long extensions
+
+The module @code{(hnh util options)} extend Guile's
+@code{(ice-9 getopt-long)}. The extra keys which can be specified on
+an option are
+
+@table @samp
+@item (description string)
+A propper description of the value. Should be written for human
+consumption.
+
+@item (value value-spec)
+Value is from the core library, but here ...
+@c TODO document me
+@end table
+
+@defun getopt-opt options
+Remove extensions from @var{options}, turning it into a structure
+which can be passed to @code{getopt-long}.
+@end defun
+
+@defun format-arg-help options
+Pretty print an option summary.
+
+Each description will be parsed as XML and then fed through our markup
+system @xref{Markup}.
+@end defun
+
+@defun print-arg-help options [port=(current-error-port)]
+Formats and prints option spec.
+@end defun
diff --git a/doc/ref/guile/srfi-41.texi b/doc/ref/general/srfi-41.texi
index 8c65b6eb..d8020ecc 100644
--- a/doc/ref/guile/srfi-41.texi
+++ b/doc/ref/general/srfi-41.texi
@@ -39,7 +39,7 @@ was found.
@end defun
-@defun stream-remave pred stream
+@defun stream-remove pred stream
Stream-filter, but with predicate negated.
@end defun
@@ -73,6 +73,26 @@ times.
stream cons, but eval arguments beforehand.
@end defun
+@defun stream-split-by pred strm
+Chunks the content of @var{strm} into lists, breaking on @var{pred}.
+If the end of the stream is reached, the remaining objects
+are put into a final chunk.
+
+Can for example be used to split a stream of characters into a stream
+of words.
+
+@lisp
+(stream-split-by (lambda (c) (char=? c #\space))
+ (-> "This is a short test"
+ string->list list->stream))
+⇒ #<stream (#\T #\h #\i #\s #\space)
+ (#\i #\s #\space)
+ (#\a #\space)
+ (#\s #\h #\o #\r #\t #\space)
+ (#\t #\e #\s #\t)>
+@end lisp
+@end defun
+
@defun stream-timeslice-limit stream timeslice
Wrap a stream in time limits. Each element has at most @var{timeslice}
seconds to produce a value, otherwise the stream ends. Useful for finding the
diff --git a/doc/ref/guile/sxml.texi b/doc/ref/general/sxml.texi
index dd635b4c..dd635b4c 100644
--- a/doc/ref/guile/sxml.texi
+++ b/doc/ref/general/sxml.texi
diff --git a/doc/ref/guile/util-config.texi b/doc/ref/general/util-config.texi
index 2e197bcc..2e197bcc 100644
--- a/doc/ref/guile/util-config.texi
+++ b/doc/ref/general/util-config.texi
diff --git a/doc/ref/general/util-exceptions.texi b/doc/ref/general/util-exceptions.texi
new file mode 100644
index 00000000..34ba33f9
--- /dev/null
+++ b/doc/ref/general/util-exceptions.texi
@@ -0,0 +1,40 @@
+@node Exception & Warning Utilities
+@section Exception & Warning Utilities
+@anchor{warning}
+
+Warnings are like exceptions, but only fatal when wanted.
+
+@code{(hnh util exceptions)}
+
+@defun warning fmt args ...
+Emit a warning.
+
+If the parameter @var{warnings-are-errors} is true, then an exception
+of type @code{'warning} will be raised, with @var{fmt} and @var{args}
+as arguments.
+
+If that parameter is false, then the procedure in
+@var{warning-handler} will instead be called.
+@end defun
+
+@deftp {parameter} warning-handler
+Parameter containing proceudre which will be called for non-throwing
+warnings. This procedure is assumed by the program to log the warning
+in some way, and continue program flow. But anything goes.
+
+The procedure is given a format specifier (as per Scheme's basic
+@code{format}), along with the correct number of arguments.
+@end deftp
+
+@deftp {parameter} warnings-are-errors
+Boolean parameter, which if set causes warnings to be thrown as exceptions.
+@end deftp
+
+@defun fatal fmt args ...
+Display the message in fmt, populated with args, then raises the UNIX
+signal SIGINT, which kills the program.
+@end defun
+
+@defun filter-stack pred? stack
+@c TODO document me
+@end defun
diff --git a/doc/ref/guile/util-object.texi b/doc/ref/general/util-object.texi
index ceac2f2a..ceac2f2a 100644
--- a/doc/ref/guile/util-object.texi
+++ b/doc/ref/general/util-object.texi
diff --git a/doc/ref/guile/util-path.texi b/doc/ref/general/util-path.texi
index 9cf41b40..ba78a828 100644
--- a/doc/ref/guile/util-path.texi
+++ b/doc/ref/general/util-path.texi
@@ -1,14 +1,19 @@
@node Path Utilities
@section Path Utilities
-Provided by the module @code{(hnh util path)}.
+An extended path library for Guile. This library builds upon the path
+utilities provided by @xref{File System,,,guile}, but adds
+manipulation procedures. These procedures should work no matter what
+the systems directory delimiter is, even though this documentation
+uses ``/'' for its examples.
+Provided by the module @code{(hnh util path)}.
@defun path-absolute? string
Alias of @code{absolute-file-name?} from Guile.
@end defun
-@defun path-append strings ...
+@defun path-append path paths ...
Joins all strings into a path, squeezing duplicated delimiters, but
ensuring that all delimiters that are needed are there.
diff --git a/doc/ref/guile/util-type.texi b/doc/ref/general/util-type.texi
index 104b00b3..104b00b3 100644
--- a/doc/ref/guile/util-type.texi
+++ b/doc/ref/general/util-type.texi
diff --git a/doc/ref/guile/util.texi b/doc/ref/general/util.texi
index 1d35e0bf..1d6a4e7a 100644
--- a/doc/ref/guile/util.texi
+++ b/doc/ref/general/util.texi
@@ -1,5 +1,8 @@
-@node General Utilities
-@section General Utilities
+@node Miscellaneous utilities
+@section Miscellaneous utilities
+
+A kitchen sink utility library. Note should be taken that some core
+procedures and forms are replaced, but all in compatible ways.
Provided by the module @code{(hnh util)}.
@@ -34,8 +37,8 @@ our extra specialized @var{when}}, but binds the return of
@defmacx for (key ...) in collection body ...
Syntactic sugar over @code{map}.
@example
-for x in collection
- body ...
+(for x in collection
+ body ...)
@end example
expands into
@example
@@ -44,6 +47,17 @@ expands into
If keys are a list, an match-lambda is used instead.
@xref{Pattern Matching,,,guile}
+
+@defun break args ...
+Abandon the entire loop. Returing what was given to @code{break}.
+@end defun
+
+@defun continue [arg]
+Abandon the current iteration of the loop. If an argument is given,
+it's used as the result in the resulting list, otherwise @code{#f} is
+used.
+@end defun
+
@end defmac
@@ -322,6 +336,9 @@ Converts @var{any} to a string, as per @var{display}.
@defmac let-env bindings body ...
Similar to @var{let}, but sets environment variables for the code in
body. Restores the old values once we leave.
+
+A variable can also be removed from the environment, by setting its
+value to @code{#f}.
@end defmac
@defmac with-locale1 category locale thunk
@@ -338,51 +355,20 @@ innermost.
then @code{with-throw-handler} is used instead of @code{catch}.
@end defmac
-@subsection UUID generation
+@defun uniq lst
+@defunx univ lst
+@defunx unique lst
+@defunx uniqx comp lst
+Squash repeated equivalent elements in a list into single instances,
+similar to the POSIX command uniq(1). The three variants uses
+@code{eq?}, @code{eqv?}, and @code{equal?} respectively.
-Provided by module @code{(hnh util uuid)}.
-
-@defun uuid-v4
-Generates a UUID-v4 string.
-@end defun
-
-@defun uuid
-Generates an implementation defined (but guaranteed valid) UUID.
+@code{uniqx} also takes @var{comp}, which is sholud be a binary
+procedure returning if the elements are equal.
@end defun
-@subsection IO
-
-Provided by module @code{(hnh util io)}.
-
-@defun open-input-port path
-@defunx open-output-port path
-Like @code{open-*-file}, but ``-'' gives @code{standard-@{input,output@}}.
-@end defun
-
-@defun read-lines port
-Return a list of all lines read from port.
-@end defun
-
-@defun with-atomic-output-to-file filename thunk
-Same functionality as the regular @var{with-output-to-file}, but
-with the difference that either everything is written, or nothing
-is written, and if anything is written it's all written atomicaly at
-once (the original file will never contain an intermidiate state).
-Does NOT handle race conditions between threads.
-
-propagates the return value of @var{thunk} upon successfully writing
-the file, and @code{#f} otherwise.
-@end defun
-
-@defun call-with-tmpfile proc [#:tmpl ``/tmp/file-XXXXXXX'']
-@end defun
-
-@defun ->port port-or-strings
-If @var{port-or-string} is a port, return it directly. If it's a
-string, instead return an input string containing the strings content.
-@end defun
-
-@c Is this even a procedure?
-@defun read-file path
-Open file at path, and return its content as a string.
-@end defun
+@defmac begin1 forms ...
+Port of Common Lisp's @code{begin1} form. Like @code{begin} runs each
+form in its body in order, but returns the first result instead of the
+last.
+@end defmac
diff --git a/doc/ref/general/uuid.texi b/doc/ref/general/uuid.texi
new file mode 100644
index 00000000..78674763
--- /dev/null
+++ b/doc/ref/general/uuid.texi
@@ -0,0 +1,17 @@
+@node UUIDs
+@section UUID generation
+
+Provided by module @code{(hnh util uuid)}.
+
+@defun uuid-v4
+Generates a UUID-v4 string.
+@end defun
+
+@defun uuid
+Generates an implementation defined (but guaranteed valid) UUID.
+@end defun
+
+@deftp {parameter} seed
+Guile parameter containing the seed used when generating UUID's in
+this module. Only set this when you want non-random randomness.
+@end deftp
diff --git a/doc/ref/guile/webdav.texi b/doc/ref/general/webdav.texi
index a495c945..a495c945 100644
--- a/doc/ref/guile/webdav.texi
+++ b/doc/ref/general/webdav.texi
diff --git a/doc/ref/general/xdg-basedir.texi b/doc/ref/general/xdg-basedir.texi
new file mode 100644
index 00000000..2d3b2972
--- /dev/null
+++ b/doc/ref/general/xdg-basedir.texi
@@ -0,0 +1,62 @@
+@node XDG Base Directory
+@section XDG Base Directory
+
+Implementation of the XDG Base Directory Specification
+@url{https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html}, v0.8.
+
+It's recommended to include this module with a symbol prefix:
+
+@example
+(use-modules ((xdg basedir) :prefix xdg-)
+
+(xdg-data-dirs)
+⇒ ("/usr/local/share" "/usr/share")
+@end example
+
+The users home directory is fetched from the environment variable @env{HOME}.
+
+
+@defun data-home
+@findex XDG_DATA_HOME
+@env{XDG_DATA_HOME}, usually @file{~/.local/share}.
+@end defun
+
+@defun config-home
+@findex XDG_CONFIG_HOME
+@env{XDG_CONFIG_HOME}, usually @file{~/.config}.
+@end defun
+
+@defun state-home
+@findex XDG_STATE_HOME
+@env{XDG_STATE_HOME}, usually @file{~/.local/state}.
+@end defun
+
+@defun data-dirs
+@findex XDG_DATA_DIRS
+@env{XDG_DATA_DIRS}, split into a list.
+
+Defaults to @code{("/usr/local/share" "/usr/share")}.
+@end defun
+
+@defun config-dirs
+@findex XDG_CONFIG_DIRS
+@env{XDG_CONFIG_DIRS} as a list, usually @code{("/etc/xdg")}.
+
+Defaults to @file{/etc/xdg}.
+@end defun
+
+@defun cache-home
+@findex XDG_CACHE_HOME
+@env{XDG_CACHE_HOME}, usually @file{~/.cache}.
+@end defun
+
+@defun runtime-dir
+@findex XDG_RUNTIME_DIR
+If @env{XDG_RUNTIME_DIR} is set, than that is used. Otherwise a
+warning message is printed to stderr and @file{/tmp} is returned.
+
+The standard also stipulates a few things about permissons for this
+directory. These are currently not checked.
+
+Systemd usually sets this value to @file{/run/user/$(id -u)}.
+@end defun
diff --git a/doc/ref/guile/zic.texi b/doc/ref/general/zic.texi
index 5af36fd3..5af36fd3 100644
--- a/doc/ref/guile/zic.texi
+++ b/doc/ref/general/zic.texi
diff --git a/doc/ref/guile.texi b/doc/ref/guile.texi
deleted file mode 100644
index cb3d8475..00000000
--- a/doc/ref/guile.texi
+++ /dev/null
@@ -1,192 +0,0 @@
-@node Guile
-@chapter Guile
-
-@include guile/data-formats.texi
-@include guile/data-stores.texi
-@include guile/datetime.texi
-@include guile/zic.texi
-@include guile/srfi-41.texi
-@include guile/util.texi
-@include guile/util-path.texi
-@include guile/util-config.texi
-@include guile/util-type.texi
-@include guile/util-object.texi
-@include guile/lens.texi
-@include guile/base64.texi
-@include guile/web.texi
-@include guile/vcomponent.texi
-@include guile/sxml.texi
-@include guile/webdav.texi
-
-@node Errors and Conditions
-@section Errors and Conditions
-
-@subsection ``Special'' Errors
-
-@deftp{Error type} return
-Thrown in some sub-mains to quickly return from the sub-function.
-Should possibly be replaced by an explicit return-continuation.
-@end deftp
-
-@deftp{Error type} warning fmt args
-Thrown when @code{warnings-are-errors} is true.
-@end deftp
-
-@deftp{Error type} max-page page-number
-@end deftp
-
-@subsection ``Regular'' Errors
-All below mentioned error types behave as expected, e.g., they are
-produced through @code{scm-error}.
-
-@deftp{Error Type} configuration-error
-Thrown by (calp util config), in some scenarios.
-@TODO{Better documentation}
-@end deftp
-
-@deftp{Error Type} c-parse-error
-Errors thrown by our make-shift C parser.
-@end deftp
-
-@deftp{Error Type} decoding-error
-thrown by base64 in some cases
-@end deftp
-
-@deftp{Error Type} parse-error
-Thrown by some things related to parsing, but not all.
-@TODO{normalize parsing errors further}
-@end deftp
-
-@deftp{Error Type} graph-error
-The first element of data is guaranteed to be the graph which caused
-the error.
-@end deftp
-
-@deftp{Error Type} missing-helper
-A helper program we wanted was missing, could be resolved by somehow
-downloading it into one of the searched locations.
-
-@example
-data : (program-name : string)
- , (searched-locations : (list string))
-@end example
-@end deftp
-
-
-
-@node Other
-@section Other
-
-@defun get-parser type
-@example
-get-parser ∷ type-name → hash-table x string → any
-type = 'BINARY | 'BOOLEAN | 'CAL-ADDRES | 'DATE | 'DATE-TIME
- | 'DURATION | 'FLOAT | 'INTEGER | 'PERIOD | 'RECUR
- | 'TEXT | 'TIME | 'URI | 'UTC-OFFSET
-@end example
-
-@ref{ical-get-writer}
-@end defun
-
-@subsection formats ical
-@subsubsection output
-
-@defun component->ical-string component
-@end defun
-
-@defun print-components-with-fake-parent events
-@end defun
-
-@defun print-all-events
-@end defun
-
-@defun print-events-in-interval start end
-@end defun
-
-@subsubsection parse
-
-@defun parse-calendar port
-@end defun
-
-@subsubsection types
-
-@defun escape-chars str
-Escape @code{,}, @code{;} and @code{\} with a
-backslash, and encode newlines as @code{\n}.
-@end defun
-
-@defun get-writer type
-@anchor{ical-get-writer}
-@example
-get-writer ∷ type-name → hash-table x value → string
-type = 'BINARY | 'BOOLEAN | 'CAL-ADDRES | 'DATE | 'DATE-TIME
- | 'DURATION | 'FLOAT | 'INTEGER | 'PERIOD | 'RECUR
- | 'TEXT | 'TIME | 'URI | 'UTC-OFFSET
-@end example
-@end defun
-
-@subsection formats vdir
-@subsubsection parse
-
-@defun parse-vdir path
-@end defun
-
-@subsubsection save-delete
-
-@defun save-event event
-@end defun
-
-@defun remove-event event
-@end defun
-
-@subsection formats xcal
-@subsubsection output
-
-@defun vcomponent->sxcal component
-@end defun
-
-@defun ns-wrap
-@lisp
-(define (ns-wrap sxml)
- `(icalendar (@@ (xmlns "urn:ietf:params:xml:ns:icalendar-2.0"))
- ,sxml))
-@end lisp
-Where @var{sxml} is expected to be the output of @var{vcomponent->sxcal}.
-@end defun
-
-@subsubsection parse
-@defun sxcal->vcomponent sxcal
-Parses a vcomponent in sxcal format. Requires that the vcomponent is
-the root of the document (fragment), so wrapping icalendar-tags or
-similar @emph{must} be removed.
-
-@example
-(vcalendar
- (properties ...)
- (components ...))
-@end example
-@end defun
-
-@subsubsection types
-@defun get-writer type
-@ref{ical-get-writer}
-@end defun
-
-
-@c --------------------------------------------------
-
-@c TODO
-This chapter will probably in the future be replaced by a proper
-system overview in the future.
-
-@c module (vcomponent control)
-
-@defmac with-replaced-properties (component (key value) ...) body ...
-Through the extent of @var{body} each @var{key}'s value in
-@var{component} is replaced by its repspective @var{value}.
-
-Note that @var{body} is guarded through a dynamic-wind, meaning that
-even non-local exits will restore @var{component} to its initial
-state.
-@end defmac
-
diff --git a/doc/ref/introspection.texi b/doc/ref/introspection.texi
new file mode 100644
index 00000000..9c8387d2
--- /dev/null
+++ b/doc/ref/introspection.texi
@@ -0,0 +1,84 @@
+@node Module Introspection
+@chapter Guile Module Introspection
+
+These are various procedures for getting information about modules,
+usually in a static (only looking at ``dead'' source code) way. They
+are currently strewn about a couple files, all under
+@file{hnh/module-introspection}.
+
+TODO rework the file structure.
+
+TODO the all-*-under-directory procedures really need to be
+straightened out.
+
+@defun module-uses* forms
+Find all dependencies of a module by statically analyzing it's source code.
+
+It gives worse resluts than Guile's built in @code{module-uses}, but
+has the benefit that a module which doesn't compile can still be checked.
+
+@var{froms} should be a list gotten by repeateadly calling @code{read}
+on a Scheme source file.
+@end defun
+
+@defun unique-symbols tree
+Return a list of all symbols occuring in @var{tree}. Each symbol is
+noted exactly once, anything which isn't a symbol is discarded.
+@end defun
+
+@defun module-declaration? form
+Checks if @var{form} starts with @code{'define-module}.
+@end defun
+
+@defun find-module-declaration forms
+@anchor{find-module-declaration}
+In a list of forms, find the first one which satisfies @code{module-declarations?}.
+@end defun
+
+@defun get-forms port
+Repeatadly call @code{read} on @var{port}, and return a list of the
+read forms in order of occurence in the file.
+@end defun
+
+@defun all-files-and-modules-under-directory dir
+Like @code{all-modules-under-directory}, but returns 2-lists of
+filename + module name.
+@end defun
+
+@defun all-files-under-directory directory extension
+Return a flat list of all ``regular'' files under @var{directory},
+whose end in @var{extension}.
+
+@example
+(all-files-under-directory "module" ".scm)
+⇒ '("module/hnh/util.scm")
+@end example
+@end defun
+
+@defun all-modules-under-directory directory
+Finds all ``.scm'' files under @var{directory}, and then read them to
+find a module declaration @pref{find-module-declaration}. Returns both
+the list of all the files, as well as all modules found, as a list of
+module lists, each one suitable to be sent to
+@code{reslove-interface}.
+
+Scheme files without a module declaration are not included in the
+result.
+@end defun
+
+@defun fs-find dir
+Find all files under @var{dir}. This includes all file types,
+including directories.
+
+@example
+(fs-find "module")
+;; Would return something on the following form:
+⇒ `(("module/hnh/util.scm" ,(stat "module/hnh/util.scm") regular)
+ ("module/hnh" ,(stat "module/hnh") directory)
+ ("module" ,(stat "module") directory)
+ ...)
+@end example
+@end defun
+
+@defun module-file-mapping dir
+@end defun
diff --git a/doc/ref/javascript.texi b/doc/ref/javascript.texi
deleted file mode 100644
index bbe1cb25..00000000
--- a/doc/ref/javascript.texi
+++ /dev/null
@@ -1,48 +0,0 @@
-@node Javascript
-@chapter Javascript
-
-@c web components
-@defindex wc
-
-@c done
-@node General Stuff
-@section General stuff
-The frontend code has its entry-point in @code{script.ts}
-
-All elements are initialized in elements.ts
-
-@include javascript/clock.texi
-@include javascript/lib.texi
-@include javascript/eventCreator.texi
-@include javascript/types.texi
-@include javascript/vevent.texi
-@include javascript/globals.texi
-@include javascript/server_connect.texi
-@include javascript/formatters.texi
-@include javascript/user-additions.texi
-
-@node General Components
-@section General Components
-@include javascript/components/date_time_input.texi
-@include javascript/components/input_list.texi
-
-@node VEvent Components
-@section VEvent Components
-@include javascript/components/vevent.texi
-@include javascript/components/changelog.texi
-@include javascript/components/edit_rrule.texi
-@include javascript/components/popup_element.texi
-@include javascript/components/tab_group_element.texi
-@include javascript/components/vevent_block.texi
-@include javascript/components/vevent_description.texi
-@include javascript/components/vevent_dl.texi
-@include javascript/components/vevent_edit.texi
-
-@section About our buildsystem
-Currently (almost) everything is written in Typescript, and bundled
-through browserify. Ideally we would, for debug builds, export the
-single transplied Javascript files, but Chromium Chromium lacks
-support for modules on XHTML documents
-@url{https://bugs.chromium.org/p/chromium/issues/detail?id=717643}.
-However, seeing as the issue still gets frequent updates as of 2021 I
-believe that this might one day get resolved.
diff --git a/doc/ref/javascript/clock.texi b/doc/ref/javascript/clock.texi
deleted file mode 100644
index 10ab7d4e..00000000
--- a/doc/ref/javascript/clock.texi
+++ /dev/null
@@ -1,78 +0,0 @@
-@node clock
-@subsection clock.js
-
-@deftp {abstract class} Clock
-Interface for ``things'' which wants to get updated on a human timescale.
-
-@defmethod Clock update now
-@c abstract method
-Called every now and then, with @var{now} being the current time.
-@end defmethod
-@end deftp
-
-@deftp {class} Timebar @extends{Clock}
-The (blue) vertical line which show the current time in the current day.
-
-@c @defmethod Timebar constructor ∅
-@c @end defmethod
-@c
-@c @defmethod Timebar update now
-@c @end defmethod
-@end deftp
-
-@deftp {class} SmallcalCellHighlight @extends{Clock}
-Highlights the current date in the small calendar to the side.
-Currently directly sets a border
-@TODO{but should preferably set a class instead}.
-
-@defmethod SmallcalCellHighlight constructor small_cal
-@var{small_cal} is the DOM-node of the calendar.
-(it should support querySelector).
-@end defmethod
-
-@c @defmethod SmallcalCellHighlight update now
-@c @end defmethod
-@end deftp
-
-@deftp {class} ButtonUpdater @extends{Clock}
-Updates the ``Today'' link in the side panel to point directly to the
-correct web-address. The link works without JavaScript, but then
-requires a redirect from the server.
-
-All actual updating logic is already abstracted away. It would be
-desirable if something more was done with this.
-
-@defmethod ButtonUpdater el proc
-Takes the element @var{el} to be updated, and the procedure @var{proc}
-which will be called with the element, and the current time.
-@end defmethod
-@end deftp
-
-
-As of commit
-@githash{c9719ce7937f0f0f2aa371ced1d585f67af22457,static/script.js,231}
-all objects required manual setup. See static/script.js:
-
-@verbatim
- 231 let start_time = document.querySelector("meta[name='start-time']").content;
- 232 let end_time = document.querySelector("meta[name='end-time']").content;
- 233
- 234 const button_updater = new ButtonUpdater(
- 235 document.getElementById("today-button"),
- 236 (e, d) => e.href = d.format('~Y-~m-~d') + ".html"
- 237 );
- 238
- 239 const sch = new SmallcalCellHighlight(
- 240 document.querySelector('.small-calendar'))
- 241
- 242 const timebar = new Timebar(start_time, end_time);
- 243
- 244 timebar.update(new Date);
- 245 window.setInterval(() => {
- 246 let d = new Date;
- 247 timebar.update(d);
- 248 button_updater.update(d);
- 249 sch.update(d);
- 250 }, 1000 * 60);
- 251
-@end verbatim
diff --git a/doc/ref/javascript/components/changelog.texi b/doc/ref/javascript/components/changelog.texi
deleted file mode 100644
index d14fb84e..00000000
--- a/doc/ref/javascript/components/changelog.texi
+++ /dev/null
@@ -1,10 +0,0 @@
-@subsection Changelog
-
-@deftp {Web Component for VEvent} VEventChangelog
-@wcindex <vevent-changelog>
-@wcindex vevent-changelog
-@anchor{VEventChangelog}
-@code{<vevent-changelog>}
-
-Display of a VEvents changelog. @ref{ChangeLogEntry}
-@end deftp
diff --git a/doc/ref/javascript/components/date_time_input.texi b/doc/ref/javascript/components/date_time_input.texi
deleted file mode 100644
index f26627d2..00000000
--- a/doc/ref/javascript/components/date_time_input.texi
+++ /dev/null
@@ -1,34 +0,0 @@
-@subsection date-time-input
-
-@deftp {Web Component} DateTimeInput
-@wcindex <date-time-input>
-@wcindex date-time-input
-@code {<date-time-input>}
-
-An element for input for date-times. Similar to
-@example
-<input type="date"/>
-<input type="time"/>
-@end example
-But as a single unit.
-
-@deftypeivar DateTimeInput boolean dateonly
-Setting this to true disabled the time part of the input, and makes
-any output only have date components (alternativly, the time component
-set to zero).
-@end deftypeivar
-
-@defcv {Attribute} DateTimeInput dateonly
-Same data as the field dateonly, but as an attribute. Present means
-true, absent means false.
-@end defcv
-
-@deftypeivar DateTimeInput Date value
-Returns current value as a Date object.
-@end deftypeivar
-
-@deftypeivar DateTimeInput string stringValue
-Returns current value as an ISO-8601 formatted string.
-@end deftypeivar
-
-@end deftp
diff --git a/doc/ref/javascript/components/edit_rrule.texi b/doc/ref/javascript/components/edit_rrule.texi
deleted file mode 100644
index 21437863..00000000
--- a/doc/ref/javascript/components/edit_rrule.texi
+++ /dev/null
@@ -1,10 +0,0 @@
-@subsection Edit RRule
-
-@deftp {Web Component for VEvent} EditRRule
-@wcindex <vevent-edit-rrule>
-@wcindex vevent-edit-rrule
-@code{<vevent-edit-rrule>}
-
-An edit form for a recurrence rule. Searches its template for elements
-with @code{[name="<<field name>>"]}, and binds to those.
-@end deftp
diff --git a/doc/ref/javascript/components/input_list.texi b/doc/ref/javascript/components/input_list.texi
deleted file mode 100644
index bdc00ecb..00000000
--- a/doc/ref/javascript/components/input_list.texi
+++ /dev/null
@@ -1,16 +0,0 @@
-@subsection input_list.js
-
-@deftp {Web Component} InputList
-@wcindex <input-list>
-@wcindex input-list
-@code{<input-list>}
-
-A list of identical input fields, which forms a group. For example
-useful to handle keywords.
-
-@deftypeivar DateTimeInput {any[]} value
-The value from each element, except the last which should always be empty.
-Has an unspecified type, since children:s value field might give non-strings.
-@end deftypeivar
-
-@end deftp
diff --git a/doc/ref/javascript/components/popup_element.texi b/doc/ref/javascript/components/popup_element.texi
deleted file mode 100644
index 2b76b347..00000000
--- a/doc/ref/javascript/components/popup_element.texi
+++ /dev/null
@@ -1,40 +0,0 @@
-@subsection Popup
-
-@deftp {Web Component for VEvent} PopupElement
-@wcindex <popup-element>
-@wcindex popup-element
-@code{<popup-element>}
-
-A (small) floating window containing information, which can be dragged
-arround. Consists of a navigation bar with a few buttons for
-controlling the window, which also works as a drag handle, along with
-an area for contents, which can be resized by the user.
-
-Currently tightly coupled to VEvent's, since their color
-profile is derived from their owning events calendar, and they have
-action buttons for the event in their navigation bar.
-
-@deftypecv {Static Member} PopupElement {PopupElement?} activePopup
-The popup which was most recently interacted with by the user. Used to
-move it on top of all others, as well as sending relevant key events there.
-@end deftypecv
-
-@defcv {Attribute} PopupElement visible
-Present is the popup is currently visible, absent otherwise.
-@end defcv
-
-@deftypeivar PopupElement boolean visible
-See the attribute of the same name.
-@end deftypeivar
-
-@defmethod PopupElement maximize
-Resize the popup window to fill the current viewport (mostly). Is
-probably bonud to the maximize button in the navigation bar.
-@end defmethod
-@end deftp
-
-@deftypefun PopupElement setup_popup_element VEvent
-Create a new popup element for the given VEvent, and ready it for
-editing the event. Used when creating event (through the frontend).
-The return value can safely be ignored.
-@end deftypefun
diff --git a/doc/ref/javascript/components/tab_group_element.texi b/doc/ref/javascript/components/tab_group_element.texi
deleted file mode 100644
index 7e0b190a..00000000
--- a/doc/ref/javascript/components/tab_group_element.texi
+++ /dev/null
@@ -1,46 +0,0 @@
-@subsection Tab Group Element
-
-@deftp {Web Component for VEvent} TabGroupElement
-@wcindex <tab-group>
-@wcindex tab-group
-@code{<tab-group>}
-
-A group of tabs, where only one can be visible at a time.
-
-@c TODO which form does the HTML document have? For CSS purposes
-
-Each tab consists of two parts, a label which is used for selecting
-it, and a tab-element, which contains the actual content. These two
-should refer to each other as follows:
-
-@example
-+---------------+ +-----------------+
-| TabLabel | | Tab |
-+---------------+ +-----------------+
-| id |<----| aria-labelledby |
-| aria-controls |---->| id |
-+---------------+ +-----------------+
-@end example
-
-Further information about tabs in HTML can be found here:
-@url{https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role}
-
-@defvr {CSS Variable} {--tabcount}
-Each tab element has the style property @code{--tabcount} set to how
-many tabs it has. This is mostly useful to make sure the tab context
-is large enough to fit all tab labels without overflowing.
-@end defvr
-
-@deftypemethod TabGroupElement void addTab {HTMLElement} {label: string?} {title: string?}
-Adds a new tab to the group. The first parameter will make up the body
-of the tab. The label is whath should be shown in the tab selector,
-but defaults to the first letter of the text content of the body node.
-Title is the hoover text of the label.
-@end deftypemethod
-
-@deftypemethod TabGroupElement void removeTab {HTMLElement}
-HTMLElement must be one of the tab bodies in this group. This method
-removes it, along with its TabLabel.
-@end deftypemethod
-
-@end deftp
diff --git a/doc/ref/javascript/components/vevent.texi b/doc/ref/javascript/components/vevent.texi
deleted file mode 100644
index be53a46e..00000000
--- a/doc/ref/javascript/components/vevent.texi
+++ /dev/null
@@ -1,23 +0,0 @@
-@subsection vevent
-
-@deftp {Abstract Web Component} ComponentVEvent {uid: string?}
-
-@c TODO what is done in the default constructor,
-@c and the default connectedCallback
-
-This registeres itself, but doesn't redraw
-We do however redraw in connectedCallback
-
-@deftypeivar ComponentVEvent uid uid
-@end deftypeivar
-
-@deftypeivar ComponentVEvent {HTMLTemplateElement?} template
-@end deftypeivar
-
-@deftypemethod ComponentVEvent void redraw (data: VEvent)
-While abstract for this, @emph{must} be overridden for everyone else
-@end deftypemethod
-@end deftp
-
-Note that many of these assume that their initial children are
-configured specifically, that is however not completely documented.
diff --git a/doc/ref/javascript/components/vevent_block.texi b/doc/ref/javascript/components/vevent_block.texi
deleted file mode 100644
index 1a0ef160..00000000
--- a/doc/ref/javascript/components/vevent_block.texi
+++ /dev/null
@@ -1,10 +0,0 @@
-@subsection VEvent Block
-
-@deftp {Web Component for VEvent} ComponentBlock
-@wcindex <vevent-block>
-@wcindex vevent-block
-@code{<vevent-block>}
-A block in our graphical view.
-
-Unique in that it works quite differently between the week and month view.
-@end deftp
diff --git a/doc/ref/javascript/components/vevent_description.texi b/doc/ref/javascript/components/vevent_description.texi
deleted file mode 100644
index 54dda7e3..00000000
--- a/doc/ref/javascript/components/vevent_description.texi
+++ /dev/null
@@ -1,16 +0,0 @@
-@subsection VEvent Description
-
-@deftp {Web Component for VEvent} ComponentDescription
-@wcindex <vevent-description>
-@wcindex vevent-description
-@code{<vevent-description>}
-
-A text representation of a VEvent. Used as the summary tab of our
-popup windows, and in the sidebar.
-
-When redrawn, it looks for an HTML-tag inside its template having the
-attribute @code{data-property} matching the properties name. If one is
-found, it looks in the @code{formatters} table
-(@ref{formatters-proc}), for a field matching the property value, and
-defaults to the key @code{default}.
-@end deftp
diff --git a/doc/ref/javascript/components/vevent_dl.texi b/doc/ref/javascript/components/vevent_dl.texi
deleted file mode 100644
index 26bc8fd4..00000000
--- a/doc/ref/javascript/components/vevent_dl.texi
+++ /dev/null
@@ -1,11 +0,0 @@
-@subsection VEvent Description List
-
-@deftp {Web Component for VEvent} VEventDL
-@wcindex <vevent-dl>
-@wcindex vevent-dl
-@code{<vevent-dl>}
-A description list of a vevent, used for debugging.
-
-No guarantees are given about the contents of the data fields, more
-than that they are related to the value in question.
-@end deftp
diff --git a/doc/ref/javascript/components/vevent_edit.texi b/doc/ref/javascript/components/vevent_edit.texi
deleted file mode 100644
index 67e9f6b3..00000000
--- a/doc/ref/javascript/components/vevent_edit.texi
+++ /dev/null
@@ -1,9 +0,0 @@
-@subsection VEvent Edit
-
-@deftp {Web Component for VEvent} ComponentEdit
-@wcindex <vevent-edit>
-@wcindex vevent-edit
-@code{<vevent-edit>}
-Edit form for a vevent, designed for useful human interaction (and
-thereby not being all-encompassing).
-@end deftp
diff --git a/doc/ref/javascript/eventCreator.texi b/doc/ref/javascript/eventCreator.texi
deleted file mode 100644
index 164d1335..00000000
--- a/doc/ref/javascript/eventCreator.texi
+++ /dev/null
@@ -1,15 +0,0 @@
-@deftp {class} EventCreator
-
-@defmethod EventCreator create_empty_event
-@end defmethod
-
-@defmethod EventCreator create_event_down intended_target
-@end defmethod
-
-@defmethod EventCreator create_event_move pos_in [round=1] [wide_element=false]
-@end defmethod
-
-@defmethod EventCreator create_event_finisher callback
-@end defmethod
-
-@end deftp
diff --git a/doc/ref/javascript/formatters.texi b/doc/ref/javascript/formatters.texi
deleted file mode 100644
index a3086aa9..00000000
--- a/doc/ref/javascript/formatters.texi
+++ /dev/null
@@ -1,23 +0,0 @@
-@node formatters
-@subsection formatters
-
-Formatting procedures used by some components.
-@c TODO can we have a backref of every node containing @ref{formatters-proc}?
-
-@deftypefun void format(targetElement:HTMLElement, data:VEvent, key:string)
-Checks if a specific formatter exists for the given key, and executes
-it.
-Defaults to 'default', and also runs that if the regular formatter throws.
-@end deftypefun
-
-@deftypevar {Map<string, (e:HTMLElement, d:VEvent, s:any) => void>} formatters
-@anchor{formatters-proc}
-
-Each procedure takes three arguments. The HTML-element which contents
-should be replaced, the VEvent containing all data, and the target
-value, as returned by @ref{VEvent.getProperty}.
-@end deftypevar
-
-@deftypevr {Window Value} {Map<string, (e:HTMLElement, d:VEvent, s:string) => void>} formatters
-Same object as @xref{formatters-proc}. Provided for @xref{user-additions.js}.
-@end deftypevr
diff --git a/doc/ref/javascript/globals.texi b/doc/ref/javascript/globals.texi
deleted file mode 100644
index 5ef7a43b..00000000
--- a/doc/ref/javascript/globals.texi
+++ /dev/null
@@ -1,41 +0,0 @@
-@node globals
-@subsection globals.ts
-
-Different variables and values which for different reasons needs to be
-global. Window Value's are those that are bound to the @code{window}
-context in JavaScript, so is really always available, no opt out.
-
-@deftypevar {Map<uid, VEvent>} vcal_objects
-All VEvent objects on current page, indexed by their unique identifiers.
-
-A global object store.
-@end deftypevar
-
-@deftypevar {Map<uid, string>} event_calendar_mapping
-Mapping from VEvent unique identifier, to name of its calendar. Should
-probably not be global, so refrain from using it.
-@end deftypevar
-
-@deftypevr {Window Value} {Map<uid, VEvent>} vcal_objects
-The exact same object store as the regular variable of the same
-name. Mostly here for human debugability.
-@end deftypevr
-
-@deftypevr {Window Value} {@code{'month'} | @code{'string'}} VIEW
-How the calendar is currently formatted. Should be set by the backend
-through a simple @code{script}-tag.
-@end deftypevr
-
-@deftypevr {Window Value} {boolean} EDIT_MODE
-However editing of events is enabled or not.
-Should be set by the backend through a simple @code{script}-tag.
-@end deftypevr
-
-@deftypevr {Window Value} {string} default_calendar
-Name of the calendar to assume when creating new events.
-Should be set by the backend through a simple @code{script}-tag.
-@end deftypevr
-
-@c TODO addNewEvent
-@c @deftypevr {Window Value} {string} default_calendar
-@c @end deftypevr
diff --git a/doc/ref/javascript/jcal.texi b/doc/ref/javascript/jcal.texi
deleted file mode 100644
index 997b4d59..00000000
--- a/doc/ref/javascript/jcal.texi
+++ /dev/null
@@ -1,7 +0,0 @@
-@node jcal
-@subsection jcal.js
-
-@deftypefun Document jcal_to_xcal {JCal ...}
-A document with the xcal namespace, and @code{icalendar} as its root
-element. Each child is a valid xcal representation of our JCal object.
-@end deftypefun
diff --git a/doc/ref/javascript/lib.texi b/doc/ref/javascript/lib.texi
deleted file mode 100644
index a3fb0697..00000000
--- a/doc/ref/javascript/lib.texi
+++ /dev/null
@@ -1,148 +0,0 @@
-
-@node lib
-@subsection lib.js
-
-General procedures which in theory could be used anywhere.
-
-
-@node Default prototype extensions
-@subsubsection Default prototype extensions
-
-HTMLElement extensions
-
-@defmethod HTMLElement addEventListener name proc
-Replace the default @code{addEventListener} with a version that stores
-all listeners in the dictionary @var{listeners}.
-@end defmethod
-
-@defivar HTMLElement listeners
-Dictionary of all registered listeners to this element.
-Keys are taken from @code{addEventListener}.
-@end defivar
-
-@defmethod DOMTokenList find regexp
-Finds the first element of the DOMTokenList whichs value matches
-the supplied regexp. Returns a pair of the index and the value.
-@end defmethod
-
-@defmethod Object format args ...
-Returns a string representation of the given object.
-Allows extending for custom types,
-@ref{date-format}
-@end defmethod
-
-@node General
-@subsubsection General
-
-@defun zip args ...
-Takes a list of lists, and returns a single list of tuples.
-@example
-» zip([1,2,3,4,5], "Hello")
-← [[1,'H'],[2,'e'],[3,'l'],[4,'l'],[5,'o']]
-@end example
-@end defun
-
-@defun makeElement name [attr=@{@}]
-Creates a new DOM element of type @var{name}, with all keys in
-@var{attr} transfered to it. For example, the equivalent of
-@example
-<input type='number'/>
-@end example
-would be
-@verbatim
-values.push(makeElement('input', {
- type: 'number',
-}));
-@end verbatim
-.
-@end defun
-
-@defun round_time time fraction
-TODO
-@end defun
-
-@defun date_to_percent date
-Retuns how far along the date specified by @var{date} is, between 0
-and 100, where 00:00 maps to 0, and 23:59 to ~100.
-@end defun
-
-@defun gensym [pxrefix='gensym']
-Generates a new string which is (hopefully) globally unique.
-Compare with @code{gensym} from Lisp.
-@end defun
-
-@defun asList thing
-Ensures that @var{thing} is a list. Returning it outright if it
-already is one, otherwise wrapping it in a list.
-@end defun
-
-@node Date
-@subsubsection Date
-
-Some extensions to the builtin class ``Date'' is made.
-
-@defivar Date utc
-Boolean indicating if the given timestamp is in UTC or local time.
-true means UTC.
-@end defivar
-
-@defivar Date dateonly
-Boolean indicating if the time component of the Date object should be disregarded.
-@end defivar
-
-@defun parseDate str
-Takes a string @var{str}, which should be in ISO-8601 date-format, and
-returns a javascript Date object.
-Handles date-times, with and without seconds, trailing `Z' for
-time-zones, and dates without times.
-If no time is given the @code{dateonly} attribute is set to yes.
-@end defun
-
-@defun copyDate date
-Creates a new instance of the given Date @var{date}, also transfers my
-custom fields.
-@end defun
-
-@defun to_local date
-@anchor{to_local}
-Returns a Date object (which may be new) which is guaranteed in local
-time.
-This means that the @var{utc} field is @code{false}, and that
-@code{to_local(current_time())} should show what your wall-clock shows.
-@end defun
-
-@defmethod Date format str args ...
-@anchor{date-format}
-Formats a Date object according to the format specification @var{str}.
-Keeping with Guile each format specifier starts with a ~.
-
-@table @samp
-@item ~~
-literal ~
-@c Almost all fields are left padded. How do I signify this
-@c with a single footnote?
-@item ~Y
-year, left-padding with zeroes.
-@item ~m
-month number, left padded with zeroes.
-@item ~d
-day of month.
-@item ~H
-hour
-@item ~M
-minute
-@item ~S
-second
-@item ~Z
-'Z' if Date is UTC, otherwise nothing
-@item ~L
-Converts the date to local time
-(@pxref{to_local}) (doesn't modify source object). Outputs nothing
-@end table
-@end defmethod
-
-@defun format_date date str
-Equivalent to @code{(@var{date}).format(@var{str})}.
-@c TODO link
-@end defun
-
diff --git a/doc/ref/javascript/server_connect.texi b/doc/ref/javascript/server_connect.texi
deleted file mode 100644
index c67f47ff..00000000
--- a/doc/ref/javascript/server_connect.texi
+++ /dev/null
@@ -1,22 +0,0 @@
-@node server_connect
-@subsection server_connect.js
-
-Procedures for interfacing with the backend server.
-
-@deftypefn {Async Function} void create_event {event: VEvent}
-Packs up the given event and sends it to the server to either be
-created, or simply be updated in the persistant database.
-
-Also does some minor updates registered components, to show that the
-event is actually created.
-@end deftypefn
-
-@deftypefn {Async Function} void remove_event {uid: uid}
-Requests that the server permanently remove the event with the given
-unique id from its persistant storage.
-
-If the server responds with a success also delete it from our local
-store (@code{vcal_objects}).
-
-@c TODO link to our backend flow here
-@end deftypefn
diff --git a/doc/ref/javascript/types.texi b/doc/ref/javascript/types.texi
deleted file mode 100644
index 6f518f53..00000000
--- a/doc/ref/javascript/types.texi
+++ /dev/null
@@ -1,95 +0,0 @@
-@node types
-@subsection types.js
-
-Collection of type information for calendar data.
-
-@defvar all_types
-Name of all valid icalendar types.
-
- text, uri, binary, float, integer, date-time, date, duration,
- period, utc-offset, cal-address, recur, boolean,
-@end defvar
-
-@deftp {Data Type} ical_type
-The union of all elements in @var{all_types}.
-@end deftp
-
-@defvar property_names
-All known names properties (top level keys) can have.
-Such as ``calscale'', ``dtstart'', ...
-@end defvar
-
-@deftypevar {Map<string, string[]>} valid_fields
-Which property fields each component can hold.
-
-@verbatim
-{ 'VCALENDAR': ['PRODID', 'VERSION', 'CALSCALE', 'METHOD'],
- ...
-}
-@end verbatim
-@end deftypevar
-
-@deftypevar {Map<string, Array<ical_type | ical_type[]>>} valid_input_types
-Which types are valid to store under each property.
-If multiple values are an option for that property, then
-the list of possibilities will contain a sub-list (see example).
-
-@verbatim
-{ 'DTSTART': ['date', 'date-time'],
- 'CATEGORIES': [['text']],
- ...
-}
-@end verbatim
-@end deftypevar
-
-@deftp {Data Type} tagname
-Alias of (@code{'vevent'} | @code{'string'}).
-@end deftp
-
-@deftp {Data Type} uid
-Alias of @code{'string'}.
-@end deftp
-
-@c TODO link to the RFC
-@c - RFC 7265 (jCal)
-
-@deftp {Data Type} JCalProperty
-Alias for a record consisting of
-@itemize @bullet
-@item the name of the type, as a string
-@item All parameters of the object, as a @code{Record<string, any>}
-@footnote{Which is simply a regular javascript object, mapping strings to anything}.
-@item An @code{ical_type} value, noting the type of the final field(s)
-@item And one or more values of the type specified by the third field.
-@end itemize
-@end deftp
-
-@deftp {Data Type} JCal
-A record consisting of a @code{tagname}, a list of
-@code{JCalProperties}, and a list of other @code{JCal} objects.
-@end deftp
-
-@defvar xcal
-The xml namespace name for xcalendar, which is
-``urn:ietf:params:xml:ns:icalendar-2.0''.
-@end defvar
-
-
-@deftp {Interface} ChangeLogEntry
-@anchor{ChangeLogEntry}
-
-@ref{VEventChangelog}
-
-@deftypecv {Interface Field} ChangeLogEntry {(@code{'calendar'} | @code{'property'})} type
-@end deftypecv
-
-@deftypecv {Interface Field} ChangeLogEntry {string} name
-@end deftypecv
-
-@deftypecv {Interface Field} ChangeLogEntry {string?} from
-@end deftypecv
-
-@deftypecv {Interface Field} ChangeLogEntry {string?} to
-@end deftypecv
-
-@end deftp
diff --git a/doc/ref/javascript/user-additions.texi b/doc/ref/javascript/user-additions.texi
deleted file mode 100644
index 706b1dd4..00000000
--- a/doc/ref/javascript/user-additions.texi
+++ /dev/null
@@ -1,18 +0,0 @@
-@node user-additions.js
-@section user-additions.js
-
-Some things in the JavaScript code is built to be user-extendable.
-The HTML-page attempts to load @code{/static/user/user-additions.js}.
-
-
-Currently; this only entails @ref{formatters}, where you could, for
-example, parse all HTTP-links in a description.
-
-@example
-window.formatters.set('description', (el, d) => @{
- el.innerHTML = d.replaceAll(/https?:\/\/\S+/g, '<a href="$&">$&</a>');
-@})
-@end example
-
-Remember that the documents are X-HTML, so be @emph{extremely} careful
-with innerHTML.
diff --git a/doc/ref/javascript/vevent.texi b/doc/ref/javascript/vevent.texi
deleted file mode 100644
index 97d15f2a..00000000
--- a/doc/ref/javascript/vevent.texi
+++ /dev/null
@@ -1,113 +0,0 @@
-@node vevent
-@subsection vevent.js
-
-@deftp {Interface} Redrawable
-@deftypeop {Interface Field} Redrawable void redraw VEvent
-@end deftypeop
-@end deftp
-
-@deffn {Type Predicate} isRedrawable element
-Checks if the given element is an instance of Redrawable.
-@end deffn
-
-
-@deftp {class} VEventValue {type: ical_type} {value: any} {parameters: Map<string, any>}
-
-@deftypemethod VEventValue {[Record<string, any>, ical_type, any]} @
- to_jcal {}
-The return value is @emph{almost} a @code{JCalProperty}, just without
-the field name.
-@end deftypemethod
-
-@end deftp
-
-@deftp VEvent {properties: Map<string, VEventValue | VEventValue[]>} @
- {components: VEvent[]}
-
-Component for a single instance of a calendar event. Almost all data
-access should go through @code{getProperty} and @code{setProperty},
-with the exception of the current calendar (which is accessed directly
-through @code{calendar}). Almost all changes through these interfaces
-are logged, and can be viewed through @var{changelog}.
-
-@deftypemethod VEvent {any?} getProperty {key: string}
-@anchor{VEvent.getProperty}
-Returns the value of the given property if set, or undefined otherwise.
-
-For the keys
-@itemize
-@item @code{'CATEGORIES'},
-@item @code{'RESOURCES'},
-@item @code{'FREEBUSY'},
-@item @code{'EXDATE'}, and
-@item @code{'RDATE'}
-@end itemize
-instead returns a list list of values.
-@end deftypemethod
-
-
-@deftypemethod VEvent void setProperty {key: string} {value: any} {type: ical_type?}
-Sets the given property to the given value. If type is given it's
-stored alongside the value, possibly updating what is already
-there. Do however note that no validation between the given type and
-the type of the value is done.
-
-@var{value} may also be a list, but should only be so for the keys
-mentioned in @var{getProperty}.
-
-After the value is set, @var{redraw} is called on all registered
-objects, notifying them of the change.
-@end deftypemethod
-
-@deftypemethod VEvent void setProperties {[string, any, ical_type?][]}
-Equivalent to running @var{setProperty} for each element in the input
-list, but only calls @var{redraw} once at the end.
-@end deftypemethod
-
-@deftypemethod VEvent {IteratableIterator<string>} boundProperties
-Returns an iterator of all our properties.
-@end deftypemethod
-
-@deftypeivar VEvent {ChangeLogEntry[]} {#changelog}
-Every write through getProperty gets logged here, and can be
-consumed. Hopefully this will one day turn into an undo system.
-@ref{ChangeLogEntry}.
-@end deftypeivar
-
-@deftypeivar VEvent {IterableIterator<[number, ChangeLogEntry]>} changelog
-Public (read only) interface to changelog.
-@end deftypeivar
-
-@deftypeivar VEvent {string?} calendar
-The name of the calendar which this event belongs to.
-@end deftypeivar
-
-@deftypemethod VEvent void register {htmlNode: Redrawable}
-Register something redrawable, which will be notified whenever this
-VEvents data is updated.
-@end deftypemethod
-
-@deftypemethod VEvent void unregister {htmlNode: Redrawable}
-Stop recieving redraw events on the given component.
-@end deftypemethod
-
-@deftypemethod VEvent JCal to_jcal
-Converts the object to JCal data.
-@end deftypemethod
-
-@end deftp
-
-
-@deftp {class} RecurrenceRule
-@deftypemethod RecurrenceRule {Record<string, any>} to_jcal
-Converts ourselves to JCal data.
-@end deftypemethod
-@end deftp
-
-@deftypefun RecurrencRule xml_to_recurrence_rule {Element}
-Parse a XCAL recurrence rule into a RecurrenceRule object.
-@end deftypefun
-
-@deftypefun VEvent xml_to_vcal {Element}
-Parse a complete XCAL object into a JS VEvent object.
-@end deftypefun
diff --git a/doc/ref/text.texi b/doc/ref/text.texi
new file mode 100644
index 00000000..ec7074ae
--- /dev/null
+++ b/doc/ref/text.texi
@@ -0,0 +1,9 @@
+@node Text Procedures
+@chapter Text Procedures
+
+Various utilities for formatting text in various ways.
+
+@include text/utilities.texi
+@include text/flow.texi
+@include text/markup.texi
+@include text/numbers.texi
diff --git a/doc/ref/text/flow.texi b/doc/ref/text/flow.texi
new file mode 100644
index 00000000..0613d78f
--- /dev/null
+++ b/doc/ref/text/flow.texi
@@ -0,0 +1,23 @@
+@node Reflowing Text
+@section Reflowing Text
+
+@code{(text flow)}
+
+@defun flow-text str [width=70]
+Reflow and justify @var{str} to @var{width} columns.
+
+Each newline is as a newline, so each paragraph needs to be on its own
+line. The output will look something like the following:
+
+@example
+muck calendar's Hobart's Kendall's lighthouse wooziest knot's swanky
+Ghats's witless heftiness's hailstorm's ladybugs abridgment's
+whispered Willemstad's ludo stewardess photostatic potshot ninety
+perfected Potsdam asinine swings Valiums Adrenalin's contralto
+emigration's besmears finessing resorption's literariness's
+verbalize maximums Bjork diverticulitis cascaras implacably
+overeager deepen funeral's Edwardian Calvinistic seawards microlight
+palatial Shaun fungus's unmounted armrests Culbertson's lineage
+@end example
+
+@end defun
diff --git a/doc/ref/text/markup.texi b/doc/ref/text/markup.texi
new file mode 100644
index 00000000..bec33557
--- /dev/null
+++ b/doc/ref/text/markup.texi
@@ -0,0 +1,71 @@
+@node Markup
+@section Markup
+
+@defun sxml->ansi-text tree
+Takes an HTML-like document in SXML format, and produces a formatted
+string with embedded ANSI-escapes suitable to print to a terminal.
+
+Supported tags are:
+
+@table @samp
+@item group
+@item block
+Groups elements, concatenating their format. Mainly here for helper
+procedres and the like. Also used for the root node.
+
+@item header
+Centers and bolds its content. Attributes will be sent along to the
+inner @code{<center/>} tag.
+
+@item center
+Center its contents on the line. The output is undefined if the body
+serializes to a multiline string.
+
+@item p
+A text paragraph. Justifies the content inside.
+
+Ends with a double newline, unless the parameter @code{inline} is set.
+
+@item b
+Make content bold.
+
+@item i
+@item em
+Make content italics.
+
+@item code
+Format content as code.
+
+(currently does nothing since we only support output to terminal)
+
+@item blockquote
+Justifies content, and sets it slightly indented.
+
+@item ws
+Forces horizontal whitespace. Use the parameter @code{minwidth} to
+specify how many spaces should be inserted.
+
+@item br
+Forces a linebreak.
+
+@item hr
+Generates a horrizontal line.
+
+@item dl
+Declares a description list.
+
+@item dt
+A key in a description list, only valid inside @code{dl}.
+
+@item dd
+A value of a description list, only valid inside @code{dl}.
+
+@item scheme
+Set content as Scheme code. The content will be passed through Guile's
+pretty-print.
+@end table
+
+
+Almost all of the block environments accept the attribute @var{width},
+which specifies the desired output width.
+@end defun
diff --git a/doc/ref/text/numbers.texi b/doc/ref/text/numbers.texi
new file mode 100644
index 00000000..fce5c9fa
--- /dev/null
+++ b/doc/ref/text/numbers.texi
@@ -0,0 +1,42 @@
+@node Spelled out Numbers
+@section Spelled out Numbers
+
+Numbers writtens as word. The usual interface is through
+@code{(text numbers)} which uses the current locale for translations.
+However, @code{(text numbers @var{<lang-code>})} can also be imported
+directly with the exact same interface. Language codes should be two
+letter ISO language codes (e.g. ``se'', ``en'', ...)
+
+When resolving the current language, first the environment variable
+@env{LC_MESSAGES} is checked, followed by @env{LC_ALL}, and finaly
+falls back to ``en''.
+
+English is also chosen if no implementation for the chosen language
+exists.
+
+Note that English uses the term cardinal and ordinal @emph{numeral},
+rather than @emph{number}.
+
+
+@defun number->string-ordinal n [language=(resolve-language)]
+Convert a string into an ordinal number. These are the ``ranking''
+numbers, e.g. ``first'', ``second'', ...
+@end defun
+
+@defun number->string-cardinal n [language=(resolve-language)]
+Convert a string into a cardinal number. These are the ``ordinary''
+counting numbers, e.g. ``one'', ``two'', ...
+@end defun
+
+@defun resolve-language
+Return the current language.
+@end defun
+
+@defun each-string count args ...
+Return a (locale dependant) string indicating which elements of a set
+are targeted, such as ``each'', ``every other'', ...
+
+@var{args} is reserved for locale specific extensions, such as in
+Swedish where both ``var tredje'' and ``vart tredje'' (meaning ``every
+third'') exists, and is chosen depending on the following noun.
+@end defun
diff --git a/doc/ref/text/utilities.texi b/doc/ref/text/utilities.texi
new file mode 100644
index 00000000..175b8f60
--- /dev/null
+++ b/doc/ref/text/utilities.texi
@@ -0,0 +1,67 @@
+@node Text Utilities
+@section Text Utilities
+
+@code{(text util)}
+
+Various general utilities for basic text manipulation.
+
+Some of these procedures claim to use ``true'' text width. These
+calculate text width in unicode code-points, but with (some) ANSI
+escape sequences omitted. In a perfect world, these would show exactly
+how many characters wide the outputed string is when printed to a
+(sufficiently advanced) terminal.
+
+@defun words str
+Split string on spaces. No space merging is done.
+@end defun
+
+@defun unwords list
+Join list with spaces.
+@end defun
+
+@defun lines str
+Split string on newlines.
+@end defun
+
+@defun unlines list
+Join string with newlines.
+@end defun
+
+@defun true-string-length word
+@anchor{true-string-length}
+Alternative string-length whith counts ANSI escapes as 0-length.
+@end defun
+
+@defun true-string-pad str len [chr=#\space]
+Works like the regular @code{string-pad}, but uses the ``true''
+length. @ref{true-string-length}
+@end defun
+
+@defun trim-to-width str len
+Forces @var{str} to be exactly @var{len} characters long.
+
+This is done by either right padding the string, or by drop
+characters from the right until the string is one shorter than length,
+then an ellipsis character is added.
+
+@example
+(trim-to-width "Hello, World!" 6)
+⇒ "Hello…"
+
+(trim-to-width "Hello" 10)
+⇒ "Hello "
+@end example
+@end defun
+
+@defun add-enumeration-punctuation list [final-delim=``&'']
+Intersperse punctuation into @var{list}, preparing it to be formatted
+as a inline list in the body of the document. The sequence ``, ''
+inserted between each element, except the last which will use
+@var{final-delim}. An oxford comma is not used.
+
+@example
+(string-concatenate (add-enumeration-punctuation
+ '("Pasta" "Hamburgers" "Hotdog and Mash")))
+⇒ "Pasta, Hamburgers & Hotdog and Mash"
+@end example
+@end defun
diff --git a/doc/ref/guile/vcomponent.texi b/doc/ref/vcomponent.texi
index 2560bdde..d0e032b3 100644
--- a/doc/ref/guile/vcomponent.texi
+++ b/doc/ref/vcomponent.texi
@@ -1,5 +1,12 @@
-@node VComponent
-@section (vcomponent)
+@node VComponents
+@chapter VComponents
+
+Procedures for manipulating VComponents.
+VComponents are the general container type as specified by RFC5545
+(iCalendar). The term VComponent isn't from the standard, but rather
+the generialization of VCALENDAR, VEVENT, ...
+
+Some of these values are still Calp specific.
@defvr {Configuration Variable} calendar-files
List of filepaths
@@ -89,10 +96,10 @@ have parent in its parent slot
@end lisp
@end deffn
-@deffn (extract field) vcomponent
-@deffnx (extract* field) vcomponent
+@defun {(extract field)} vcomponent
+@defunx {(extract* field)} vcomponent
Curried version of @var{prop}.
-@end deffn
+@end defun
@defun delete-property! component key
@end defun
diff --git a/doc/ref/vulgar.texi b/doc/ref/vulgar.texi
new file mode 100644
index 00000000..70101aab
--- /dev/null
+++ b/doc/ref/vulgar.texi
@@ -0,0 +1,27 @@
+@node Vulgar Terminal Interface
+@chapter Vulgar Terminal Interface
+
+The Vulgar@footnote{Since it's not Curses}
+Terminal Interface aims to be a simple way to semi advanced terminal
+interfaces.
+
+@defun cls
+Clear the screen, and move the cursor to the ``home''.
+@end defun
+
+@defun set-cursor-pos x y
+Move the cursor to the specified position on the screen.
+@end defun
+
+@defun with-vulgar [bits] thunk
+Runs @var{thunk} with
+@code{iattr.lflag &= @var{bits}} and
+@code{oattr.lflag &= @var{bite}}, along with
+@command{tput civis} being run on entrance, and
+@command{tput cnorm} being run on exit.
+
+The thunk is properly prepared on non-local entrances and exits.
+@end defun
+
+@include vulgar/color.texi
+@include vulgar/termios.texi
diff --git a/doc/ref/vulgar/color.texi b/doc/ref/vulgar/color.texi
new file mode 100644
index 00000000..611f4c54
--- /dev/null
+++ b/doc/ref/vulgar/color.texi
@@ -0,0 +1,18 @@
+@node Color
+@section Color
+
+@defvar STR-RESET
+ANSI escape sequence for removing all current formatting, as a string.
+@end defvar
+
+@defmac color-if predicate color body ...
+@code{(begin body ...)} is run, coloring it's return value using the
+ANSI escape sequence @var{color}, if @var{predicate} is true, and
+uncolored otherwise.
+@end defmac
+
+@defun color-escape n
+Generates an RGB color escape code. @var{n} can either be @code{#f},
+in which case the empty string is returned, or a a 24 bit color code
+encoded hexadecimaly as @samp{#RRGGBB}.
+@end defun
diff --git a/doc/ref/vulgar/termios.texi b/doc/ref/vulgar/termios.texi
new file mode 100644
index 00000000..58a83e61
--- /dev/null
+++ b/doc/ref/vulgar/termios.texi
@@ -0,0 +1,217 @@
+@node Termios
+@section Termios
+
+Interface to termios procedures. See termios(3) and @code{termios.h}
+for general information.
+
+@code{(vulgar termios)}
+
+Refer to termios(3) for deeper information about all these fields.
+
+@deftp {Record Type} <termios>
+See ``The termios structure'' in termios(3).
+@defun make-termios
+Create a new (empty) termios structure.
+@end defun
+
+@defun cc termios
+@defunx cflag termios
+@defunx iflag termios
+@defunx ispeed termios
+@defunx lflag termios
+@defunx line termios
+@defunx oflag termios
+@defunx ospeed termios
+Accessors to termios each field.
+@end defun
+@end deftp
+
+@defun copy-termios termios
+Create a copy of the given termios structure.
+@end defun
+
+@defun tcsetattr! termios [port] [when=TCSANOW]
+Updates @var{port} with flags from @var{termios}.
+@end defun
+
+@defun tcgetattr! termios [port=(current-input-port)]
+Gets termios information about @var{port}, and stores it in @var{termios}.
+@end defun
+
+@defun cfmakeraw! termios
+Calls the termios function @code{cfmakeraw} on @var{termios}, updating
+the structure.
+@end defun
+
+
+@defvar TOSTOP
+@defvarx NLDLY
+@defvarx CREAD
+@defvarx VSTOP
+@defvarx B1500000
+@defvarx B4000000
+@defvarx B150
+@defvarx VEOL
+@defvarx VQUIT
+@defvarx CSTART
+@defvarx CBAUD
+@defvarx CR0
+@defvarx OLCUC
+@defvarx CSTATUS
+@defvarx VSTART
+@defvarx IXANY
+@defvarx ONOCR
+@defvarx VERASE
+@defvarx TTYDEF_IFLAG
+@defvarx B1000000
+@defvarx NL0
+@defvarx FLUSHO
+@defvarx TABDLY
+@defvarx CDSUSP
+@defvarx CEOL
+@defvarx CIBAUD
+@defvarx TAB3
+@defvarx CR2
+@defvarx NL1
+@defvarx CS8
+@defvarx CERASE
+@defvarx OPOST
+@defvarx TTYDEF_SPEED
+@defvarx TAB1
+@defvarx EXTA
+@defvarx B1200
+@defvarx TAB0
+@defvarx B75
+@defvarx EXTB
+@defvarx FF1
+@defvarx CR1
+@defvarx CS5
+@defvarx INPCK
+@defvarx B576000
+@defvarx B3000000
+@defvarx OCRNL
+@defvarx TCOON
+@defvarx CBAUDEX
+@defvarx CCEQ
+@defvarx IXOFF
+@defvarx CREPRINT
+@defvarx FF0
+@defvarx ECHONL
+@defvarx IXON
+@defvarx ISTRIP
+@defvarx CSTOP
+@defvarx PENDIN
+@defvarx BRKINT
+@defvarx IEXTEN
+@defvarx TCIFLUSH
+@defvarx VSUSP
+@defvarx B38400
+@defvarx TCION
+@defvarx B921600
+@defvarx ECHOPRT
+@defvarx CQUIT
+@defvarx IMAXBEL
+@defvarx CRTSCTS
+@defvarx ECHOCTL
+@defvarx CEOT
+@defvarx VMIN
+@defvarx ICANON
+@defvarx ONLRET
+@defvarx VINTR
+@defvarx CSTOPB
+@defvarx B3500000
+@defvarx B230400
+@defvarx CS7
+@defvarx TCOFLUSH
+@defvarx TIOCSER_TEMT
+@defvarx B200
+@defvarx CSUSP
+@defvarx BS1
+@defvarx XTABS
+@defvarx CLNEXT
+@defvarx VT0
+@defvarx NCCS
+@defvarx BSDLY
+@defvarx B9600
+@defvarx ECHOKE
+@defvarx VEOF
+@defvarx TTYDEF_OFLAG
+@defvarx VTDLY
+@defvarx VT1
+@defvarx CTRL
+@defvarx NOFLSH
+@defvarx VREPRINT
+@defvarx ICRNL
+@defvarx CINTR
+@defvarx ADDRB
+@defvarx B2500000
+@defvarx EXTPROC
+@defvarx B110
+@defvarx XCASE
+@defvarx ECHOE
+@defvarx IUTF8
+@defvarx CS6
+@defvarx CFLUSH
+@defvarx B500000
+@defvarx CKILL
+@defvarx CDISCARD
+@defvarx VDISCARD
+@defvarx B2400
+@defvarx TTYDEF_CFLAG
+@defvarx VWERASE
+@defvarx INLCR
+@defvarx ONLCR
+@defvarx OFDEL
+@defvarx B1800
+@defvarx ISIG
+@defvarx IGNPAR
+@defvarx TAB2
+@defvarx CTIME
+@defvarx B1152000
+@defvarx ECHO
+@defvarx CR3
+@defvarx CMSPAR
+@defvarx PARENB
+@defvarx B2000000
+@defvarx VKILL
+@defvarx B4800
+@defvarx CLOCAL
+@defvarx IGNBRK
+@defvarx BS0
+@defvarx TCSAFLUSH
+@defvarx B19200
+@defvarx TCSANOW
+@defvarx VTIME
+@defvarx B0
+@defvarx TCOOFF
+@defvarx CEOF
+@defvarx B460800
+@defvarx PARMRK
+@defvarx VEOL2
+@defvarx FFDLY
+@defvarx TCSADRAIN
+@defvarx IGNCR
+@defvarx CRDLY
+@defvarx VLNEXT
+@defvarx PARODD
+@defvarx CRPRNT
+@defvarx B600
+@defvarx VSWTC
+@defvarx IUCLC
+@defvarx HUPCL
+@defvarx B50
+@defvarx TCIOFF
+@defvarx TTYDEF_LFLAG
+@defvarx CBRK
+@defvarx ECHOK
+@defvarx B115200
+@defvarx CSIZE
+@defvarx B300
+@defvarx OFILL
+@defvarx CWERASE
+@defvarx B134
+@defvarx B57600
+@defvarx TCIOFLUSH
+@defvarx CMIN
+Imported from the ``termios.h'' header file.
+@end defvar
diff --git a/doc/ref/web.texi b/doc/ref/web.texi
new file mode 100644
index 00000000..8574e166
--- /dev/null
+++ b/doc/ref/web.texi
@@ -0,0 +1,6 @@
+@node Webservers and -Clients
+@chapter Webservers and -Clients
+
+@include web/query.texi
+@include web/uri-query.texi
+@include web/routes.texi
diff --git a/doc/ref/web/query.texi b/doc/ref/web/query.texi
new file mode 100644
index 00000000..df3ba953
--- /dev/null
+++ b/doc/ref/web/query.texi
@@ -0,0 +1,9 @@
+@node Web Query
+@section (web query)
+
+@defun parse-query query-string [encoding=''UTF-8'']
+Given a string like ``?key=value&other=something'', returns
+@code{(key: "value" other: "something")}. Performs uri-decoding of
+both key and value. A key without a value decodes to that key, with
+itself as its value
+@end defun
diff --git a/doc/ref/guile/web.texi b/doc/ref/web/routes.texi
index 69ab726f..a2249c7a 100644
--- a/doc/ref/guile/web.texi
+++ b/doc/ref/web/routes.texi
@@ -1,24 +1,5 @@
-@node Web Stuff
-@section Web Stuff
-
-@subsection (web query)
-
-@defun parse-query query-string [encoding=''UTF-8'']
-Given a string like ``?key=value&other=something'', returns
-@code{(key: "value" other: "something")}. Performs uri-decoding of
-both key and value. A key without a value decodes to that key, with
-itself as its value
-@end defun
-
-
-@subsection (web uri-query)
-
-@defun encode-query-parameters parameters
-Given the association list @var{parameter}, encode it into a query
-string on the form ``key=value&...''.
-@end defun
-
-@subsection (web http make-routes)
+@node HTTP Routes
+@section (web http make-routes)
@defun parse-endpoint-string str
Only really public for tests.
diff --git a/doc/ref/web/uri-query.texi b/doc/ref/web/uri-query.texi
new file mode 100644
index 00000000..d3df3a70
--- /dev/null
+++ b/doc/ref/web/uri-query.texi
@@ -0,0 +1,7 @@
+@node URI Query
+@section (web uri-query)
+
+@defun encode-query-parameters parameters
+Given the association list @var{parameter}, encode it into a query
+string on the form ``key=value&...''.
+@end defun
diff --git a/env b/env
deleted file mode 100644
index 31ff2281..00000000
--- a/env
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- mode: sh -*-
-
-_here=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
-
-export GUILE=${GUILE:-guile}
-guile_version=$($GUILE -c '(display (version))')
-
-export GUILE_LOAD_COMPILED_PATH=${_here}/obj-${guile_version}:${GUILE_LOAD_COMPILED_PATH}
-export GUILE_LOAD_PATH=${_here}/module:${GUILE_LOAD_PATH}
-export GUILE_AUTO_COMPILE=0
-
-# TODO why is this set?
-export LIBEXEC=${_here}/scripts/
-
-#export GUILE_AUTO_COMPILE=0
-
-# exec "$@"
diff --git a/main b/main
deleted file mode 100755
index 5820b1cd..00000000
--- a/main
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-here=$(dirname $(realpath $0))
-. $here/env
-
-make GUILE="$GUILE" go_files
-
-exec $GUILE -c '((@ (calp main) main) (command-line))' "$@"
diff --git a/module/calp/load-config.scm b/module/calp/load-config.scm
index 5844c1b6..0ce1f131 100644
--- a/module/calp/load-config.scm
+++ b/module/calp/load-config.scm
@@ -43,8 +43,11 @@
;; altconfig could be placed in the list below. But I want to raise an error
;; if an explicitly given config is missing.
[(find file-exists?
- (list
- (path-append (xdg-config-home) "calp" "config.scm")
- (path-append (xdg-sysconfdir) "calp" "config.scm")))
+ (let ((end '("calp" "config.scm")))
+ `(,(apply path-append (xdg-config-home) end)
+ ,@(map (lambda (sysconfdir)
+ (apply path-append sysconfdir end))
+ (xdg-config-dirs))
+ ,(apply path-append "/etc" end))))
=> identity])
)
diff --git a/module/glob.scm b/module/glob.scm
index 64f97690..20cb4b1c 100644
--- a/module/glob.scm
+++ b/module/glob.scm
@@ -16,11 +16,14 @@
(define << ash)
(include# "/usr/include/glob.h" define-public)
-(define-values (glob% globfree%)
- (let ((this (dynamic-link)))
- (values
- (pointer->procedure int (dynamic-func "glob" this) `(* ,int * *))
- (pointer->procedure void (dynamic-func "globfree" this) '(*)))))
+(define lib (dynamic-link))
+
+(define glob%
+ (pointer->procedure int (dynamic-func "glob" lib)
+ `(* ,int * *)))
+(define globfree
+ (pointer->procedure void (dynamic-func "globfree" lib)
+ '(*)))
(define glob-flags (logior GLOB_MARK GLOB_BRACE GLOB_TILDE_CHECK))
@@ -41,5 +44,5 @@
(ret (map (compose pointer->string make-pointer)
(bytevector->uint-list strvec (native-endianness) (sizeof '*)))))
- (globfree% (bytevector->pointer bv))
+ (globfree (bytevector->pointer bv))
ret))))
diff --git a/module/hnh/module-introspection/all-modules.scm b/module/hnh/module-introspection/all-modules.scm
index 1bf39e1e..4b224d2f 100644
--- a/module/hnh/module-introspection/all-modules.scm
+++ b/module/hnh/module-introspection/all-modules.scm
@@ -7,8 +7,9 @@
:use-module (hnh module-introspection)
:use-module ((hnh module-introspection static-util) :select (get-forms))
:export (all-files-and-modules-under-directory
+ all-files-under-directory
all-modules-under-directory
- fs-find-base fs-find
+ fs-find
module-file-mapping
))
@@ -20,22 +21,24 @@
;; (define (fs-find proc dir)
;; (filter proc (fs-find-base dir)))
-(define (all-files-and-modules-under-directory dir)
- (define re (make-regexp "\\.scm$"))
+(define* (all-files-under-directory dir extension)
+ (define extension-rx ((@ (texinfo string-utils) escape-special-chars)
+ extension "[](){}+*?.^$" #\\))
+ (define re (make-regexp (string-append extension-rx "$")))
- (define files
- (map car
- (filter (match-lambda ((filename _ 'regular)
- (and (regexp-exec re filename)
- (not (file-hidden? filename))))
- (_ #f))
- (fs-find dir))))
+ (map car
+ (filter (match-lambda ((filename _ 'regular)
+ (and (regexp-exec re filename)
+ (not (file-hidden? filename))))
+ (_ #f))
+ (fs-find dir))))
+(define (all-files-and-modules-under-directory dir)
(map (lambda (file)
(list file
(call-with-input-file file
(compose find-module-declaration get-forms))))
- files))
+ (all-files-under-directory dir ".scm")))
(define (all-modules-under-directory dir)
"Returns two values, all scm files in dir, and all top
diff --git a/module/hnh/util.scm b/module/hnh/util.scm
index c88a029e..9f71c1ec 100644
--- a/module/hnh/util.scm
+++ b/module/hnh/util.scm
@@ -4,7 +4,8 @@
:use-module (srfi srfi-88) ; postfix keywords
:use-module ((sxml fold) :select (fold-values))
:use-module ((srfi srfi-9 gnu) :select (set-fields))
- :re-export (fold-values)
+ :use-module ((ice-9 copy-tree) :select (copy-tree))
+ :use-module ((ice-9 control) :select (call/ec))
:export (aif
awhen
for
@@ -59,7 +60,6 @@
uniqx
uniq
univ
- uniqv
unique
vector-last
@@ -118,17 +118,50 @@
-(define-syntax for
- (syntax-rules (in)
+(define-syntax (for stx)
+ (syntax-case stx (in)
((for (<var> <vars> ...) in <collection> b1 body ...)
- (map ((@ (ice-9 match) match-lambda) [(<var> <vars> ...) b1 body ...])
- <collection>))
+ (with-syntax ((break (datum->syntax stx 'break))
+ (continue (datum->syntax stx 'continue)))
+ #'(call/ec
+ (lambda (break)
+ (map ((@ (ice-9 match) match-lambda)
+ [(<var> <vars> ...)
+ (call/ec
+ (lambda (raw-continue)
+ (let ((continue
+ (case-lambda
+ (() #f)
+ (args (apply raw-continue args)))))
+ b1 body ...)))])
+ <collection>)))))
+
((for (<var> <vars> ... . <tail>) in <collection> b1 body ...)
- (map ((@ (ice-9 match) match-lambda) [(<var> <vars> ... . <tail>) b1 body ...])
- <collection>))
+ #'(call/ec
+ (lambda (break)
+ (map ((@ (ice-9 match) match-lambda)
+ [(<var> <vars> ... . <tail>)
+ (call/ec
+ (lambda (raw-continue)
+ (let ((continue
+ (case-lambda
+ (() (raw-continue #f))
+ (args (apply raw-continue args)))))
+ b1 body ...)))])
+ <collection>))))
((for <var> in <collection> b1 body ...)
- (map (lambda (<var>) b1 body ...)
- <collection>))))
+ (with-syntax ((break (datum->syntax stx 'break))
+ (continue (datum->syntax stx 'continue)))
+ #'(call/ec
+ (lambda (break)
+ (map (lambda (<var>)
+ (call/ec (lambda (raw-continue)
+ (let ((continue
+ (case-lambda
+ (() (raw-continue #f))
+ (args (apply raw-continue args)))))
+ b1 body ...))))
+ <collection>)))))))
diff --git a/module/hnh/util/env.scm b/module/hnh/util/env.scm
index 32ea1cc1..f5992245 100644
--- a/module/hnh/util/env.scm
+++ b/module/hnh/util/env.scm
@@ -16,7 +16,11 @@
(list n new-value (getenv n)))
(list (symbol->string (quote name)) ...)
(list value ...)))
- (for-each (lambda (pair) (setenv (car pair) (cadr pair)))
+ (for-each (lambda (pair)
+ (if (cadr pair)
+ (setenv (car pair)
+ (cadr pair))
+ (unsetenv (car pair))))
env-pairs))
(lambda () body ...)
(lambda ()
diff --git a/module/hnh/util/exceptions.scm b/module/hnh/util/exceptions.scm
index 344eb27a..1c3de8c7 100644
--- a/module/hnh/util/exceptions.scm
+++ b/module/hnh/util/exceptions.scm
@@ -33,8 +33,7 @@
(define (fatal fmt . args)
(display (format #f "FATAL: ~?~%" fmt (or args '()))
(current-error-port))
- (raise 2)
- )
+ (raise SIGINT))
(define (filter-stack pred? stk)
diff --git a/module/hnh/util/path.scm b/module/hnh/util/path.scm
index b0991073..b92de8cd 100644
--- a/module/hnh/util/path.scm
+++ b/module/hnh/util/path.scm
@@ -17,7 +17,8 @@
(define path-absolute? absolute-file-name?)
;; TODO remove intermidiate period components
-(define (path-append . strings)
+;; e.x. /a/../b => /b
+(define (path-append path . paths)
(fold (lambda (s done)
(string-append
done
@@ -33,11 +34,9 @@
;; the path absolute. This isn't exactly correct if we have
;; drive letters, but on those system the user should make
;; sure that the first component of the path is non-empty.
- (let ((s (car strings)))
- (if (string-null? s)
- // s))
- (cdr strings)
- ))
+ (if (string-null? path)
+ // path)
+ paths))
(define (path-join lst) (apply path-append lst))
diff --git a/module/scripts/find-undocumented.scm b/module/scripts/find-undocumented.scm
new file mode 100644
index 00000000..5aebcb25
--- /dev/null
+++ b/module/scripts/find-undocumented.scm
@@ -0,0 +1,177 @@
+(define-module (scripts find-undocumented)
+ :use-module (srfi srfi-1)
+ :use-module (hnh module-introspection all-modules)
+ :use-module (hnh util)
+ :use-module (hnh util path)
+ :use-module (ice-9 format)
+ :use-module (ice-9 regex)
+ :use-module (ice-9 rdelim)
+ :use-module (rnrs records syntactic)
+ :use-module (glob)
+ :export (main)
+ )
+
+(define %summary "Find all uncodumented exported declaration in a project.")
+
+(define %synopsis "find-undocumented <srcdir> <docdir>")
+
+;; (define %help "")
+
+;;; All texinfo forms we want to capture.
+;;; For each of these, the following grammar holds:
+;;; - The first element should be a string of the texinfo tag to match
+;;; - The following arguments are
+;;; - Any number of `_`, meaning an argument we don't care about
+;;; - a single instance of the symbol `name`, which indicates where the name of
+;;; the definition is stored.
+;;; - An optional final argument `...`, which indicates that more may argumnets
+;;; may follow.
+(define texinfo-definition-forms
+ '(("deffn" _ name ...)
+ ("deftp" _ name ...)
+ ("defun" name ...)
+ ("defmac" name ...)
+ ("defspec" name ...)
+ ("deftypefn" _ _ name ...)
+ ("deftypefun" _ name ...)
+ ("defvr" _ name)
+ ("defvar" name)
+ ("defopt" name)
+ ("deftypevr" _ _ name)
+ ("deftypevar" _ name)
+ ("deftp" _ name ...)
+ ("defcv" _ _ name)
+ ("deftypecv" _ _ name)
+ ("defivar" _ name)
+ ("deftypeivar" _ _ name)
+ ("defop" _ _ name ...)
+ ("deftypeop" _ _ _ name ...)
+ ("defmethod" _ name ...)
+ ("deftypemethod" _ _ name ...)))
+
+(define (command cmd) (format #f "@ *(~a)x?" cmd))
+(define parameter "(\\{(@\\}|[^}])+\\}|[^ \t]+)")
+(define rest ".*")
+(define regexpes
+ (for form in texinfo-definition-forms
+ (list
+ form
+ (string-concatenate
+ (intersperse
+ "[ \t]*"
+ (for (idx symbol) in (enumerate form)
+ (cond ((string? symbol) (command symbol))
+ ((eq? '_ symbol) parameter)
+ ((eq? '... symbol) rest)
+ ((symbol? symbol) parameter)
+ (else (scm-error 'misc-error "" "" '() #f)))))))))
+
+(define rxs
+ (for (name rx) in regexpes
+ (list name
+ (make-regexp
+ (format #f "^ *~a" rx)
+ regexp/newline))))
+
+
+(define-record-type doc-definition
+ (fields symbol type file line))
+
+(define (cmp a b)
+ (eq?
+ (doc-definition-symbol a)
+ (doc-definition-symbol b)))
+
+(define (print-header msg)
+ (define middle (format #f "= ~a =" msg))
+ (define side (make-string (string-length middle) #\=))
+ (format #t "~a~%~a~%~a~%" side middle side))
+
+(define (print-doc-definition def)
+ (display (symbol->string (doc-definition-symbol def)))
+ (cond ((doc-definition-file def)
+ => (lambda (it)
+ (display "\t(")
+ (display it)
+ (cond ((doc-definition-line def)
+ => (lambda (it)
+ (display " ")
+ (display it))))
+ (display ")"))))
+ (newline))
+
+(define (main . args)
+ (define source-directory "module")
+ (define doc-dir "doc/ref")
+ (define skip-files
+ (append
+ '(
+ ;; Ignored since we arent't the implementor.
+ ;; It could however be nice to document it
+ "module/graphviz.scm"
+ )
+ ;; Each entry-point should only export a main procedure,
+ ;; and is documented elsewhere
+ (glob "module/calp/entry-points/*.scm")
+ ;; These are scripts for `guild`.
+ ;; Each file exports a few pre-defined symbols,
+ ;; and are documented in other ways.
+ (glob "module/scripts/*.scm")
+ ))
+
+ (define documented-symbols
+ (concatenate
+ (for file in (all-files-under-directory doc-dir ".texi")
+ (let ((content (call-with-input-file file read-string)))
+ (concatenate
+ (for (form rx) in rxs
+ (for m in (list-matches rx content)
+ (make-doc-definition
+ (-> m
+ (match:substring
+ ;; Weird offsets to account for how matching groups work
+ (* 2 (1+ (list-index (lambda (x) (eqv? x 'name))
+ (cdr form)))))
+ (string-trim-both (string->char-set "{}"))
+ string->symbol)
+ (string->symbol (match:substring m 1))
+ file
+ (1+ (string-count (match:prefix m) (char-set #\newline)))
+ ))))))))
+
+ (define defined-symbols
+ (concatenate
+ (for path in (all-modules-under-directory source-directory)
+ (when (member path skip-files)
+ (continue '()))
+
+ (define components*
+ (drop (path-split path)
+ (length (path-split source-directory))))
+
+ (define name
+ (map string->symbol
+ (append (drop-right components* 1)
+ (list (basename (last components*) ".scm")))))
+ (catch 'misc-error
+ (lambda ()
+ (cond ((resolve-interface name)
+ => (lambda (module) (map (lambda (symb) (make-doc-definition symb #f path #f))
+ (module-map (lambda (k v) k) module))))
+ (else
+ (format (current-error-port) "~s is not a module~%" name)
+ '())))
+ (lambda (err proc fmt args data)
+ (format (current-error-port) "Failed loading ~s: (~a) ~?~%" name proc fmt args)
+ '())))))
+
+
+ (print-header "Documented functions without (or with private) definitions:")
+ (for-each print-doc-definition (lset-difference cmp documented-symbols defined-symbols))
+ (newline)
+
+ (print-header "Defined symbols without documentation:")
+ (for-each print-doc-definition (lset-difference cmp defined-symbols documented-symbols))
+
+ (newline)
+ )
diff --git a/module/srfi/srfi-41/util.scm b/module/srfi/srfi-41/util.scm
index 16bf1da6..1571cc4c 100644
--- a/module/srfi/srfi-41/util.scm
+++ b/module/srfi/srfi-41/util.scm
@@ -5,6 +5,7 @@
:use-module ((ice-9 sandbox) :select (call-with-time-limit))
:use-module ((hnh util) :select (find-extreme))
:export (stream-car+cdr
+ eager-stream-cons
interleave-streams
stream-insert
filter-sorted-stream
@@ -17,7 +18,8 @@
stream-partition
stream-split
stream-paginate
- stream-timeslice-limit))
+ stream-timeslice-limit
+ stream-split-by))
(define (stream-car+cdr stream)
(values (stream-car stream)
@@ -145,3 +147,15 @@
(stream-timeslice-limit (stream-cdr strm) timeslice)))
(lambda _ stream-null)))
+
+(define-stream (stream-split-by pred strm)
+ (let loop ((accumulated '())
+ (strm strm))
+ (stream-match strm
+ (() (if (null? accumulated)
+ stream-null
+ (stream (reverse accumulated))))
+ ((x . xs) (pred x)
+ (stream-cons (reverse (cons x accumulated)) (loop '() xs)))
+ ((x . xs)
+ (loop (cons x accumulated) xs)))))
diff --git a/module/text/markup.scm b/module/text/markup.scm
index a7a905df..62f93b0c 100644
--- a/module/text/markup.scm
+++ b/module/text/markup.scm
@@ -65,6 +65,7 @@
(map (lambda (line) (sxml->ansi-text `(group (ws (@ (minwidth 4))) ,line (br))))
(flow-text
(string-concatenate (map sxml->ansi-text body))
+ ;; TODO shouldn't this use (- width 4)?
width: 66)))]
[(ws) (make-string (aif (assoc-ref args 'minwidth)
(car it) 1)
diff --git a/module/text/numbers.scm b/module/text/numbers.scm
index c45016bc..7909573b 100644
--- a/module/text/numbers.scm
+++ b/module/text/numbers.scm
@@ -2,10 +2,16 @@
:use-module (srfi srfi-88)
:export (number->string-cardinal
number->string-ordinal
+ resolve-language
each-string))
(define (get mod-symb proc-symb)
- (module-ref (resolve-interface `(text numbers ,mod-symb))
+ (module-ref (catch 'misc-error
+ (lambda () (resolve-interface `(text numbers ,mod-symb)))
+ (lambda (err proc fmt args data)
+ ;; Possibly check if the err message starts with
+ ;; "no code for module"
+ (resolve-interface '(text numbers en))))
proc-symb))
;; "sv_SE.UTF-8"
diff --git a/module/vcomponent/control.scm b/module/vcomponent/control.scm
deleted file mode 100644
index 19a6fa18..00000000
--- a/module/vcomponent/control.scm
+++ /dev/null
@@ -1,37 +0,0 @@
-(define-module (vcomponent util control)
- :use-module (hnh util)
- :use-module (vcomponent)
- :export (with-replaced-properties))
-
-
-(eval-when (expand load) ; No idea why I must have load here.
- (define href (make-procedure-with-setter hash-ref hash-set!))
-
- (define (set-temp-values! table component kvs)
- (for-each (lambda (kv)
- (let ((key (car kv))
- (val (cadr kv)))
- (when (prop component key)
- (set! (href table key) (prop component key))
- (set! (prop component key) val))))
- kvs))
-
- (define (restore-values! table component keys)
- (for-each (lambda (key)
- (and=> (href table key)
- (lambda (val)
- (set! (prop component key) val))))
- keys)))
-
-;; TODO what is this even used for?
-(define-syntax with-replaced-properties
- (syntax-rules ()
- [(G_ (component (key val) ...)
- body ...)
-
- (let ((htable (make-hash-table 10)))
- (dynamic-wind
- (lambda () (set-temp-values! htable component (quote ((key val) ...)))) ; In guard
- (lambda () body ...)
- (lambda () (restore-values! htable component (quote (key ...))))))])) ; Out guard
-
diff --git a/module/vcomponent/util/control.scm b/module/vcomponent/util/control.scm
index 0869543d..19a6fa18 100644
--- a/module/vcomponent/util/control.scm
+++ b/module/vcomponent/util/control.scm
@@ -26,7 +26,7 @@
;; TODO what is this even used for?
(define-syntax with-replaced-properties
(syntax-rules ()
- [(_ (component (key val) ...)
+ [(G_ (component (key val) ...)
body ...)
(let ((htable (make-hash-table 10)))
diff --git a/module/vcomponent/util/search.scm b/module/vcomponent/util/search.scm
index e2057e9e..3c2d7663 100644
--- a/module/vcomponent/util/search.scm
+++ b/module/vcomponent/util/search.scm
@@ -175,6 +175,9 @@
(lambda (err proc fmt args data)
;; NOTE This is mostly a hack to see that we
;; actually check for the correct error.
+ ;;
+ ;; stream-ref quite unhelpfully throws this error as
+ ;; $3 = (wrong-type-arg stream-ref "beyond end of stream" () (#<stream>))
(unless (string=? fmt "beyond end of stream")
(scm-error err proc fmt args data))
diff --git a/module/vulgar/color.scm b/module/vulgar/color.scm
index 5f9fbe40..91bd977f 100644
--- a/module/vulgar/color.scm
+++ b/module/vulgar/color.scm
@@ -1,7 +1,6 @@
(define-module (vulgar color)
:export (color-if color-escape))
-(define-public STR-YELLOW "\x1b[0;33m")
(define-public STR-RESET "\x1b[m")
(define-syntax-rule (color-if pred color body ...)
diff --git a/module/xdg/basedir.scm b/module/xdg/basedir.scm
index 92a5c7d9..f4e7b89b 100644
--- a/module/xdg/basedir.scm
+++ b/module/xdg/basedir.scm
@@ -3,59 +3,66 @@
;;; Code:
(define-module (xdg basedir)
- :export (sysconfdir runtime-dir
- data-home config-home cache-home
+ :export (runtime-dir
+ data-home config-home state-home cache-home
data-dirs config-dirs))
+;;; Check if an environment variable is set to a non-empty value.
+(define (set? var)
+ (cond ((getenv var)
+ => (lambda (s)
+ (if (string-null? s)
+ #f s)))
+ (else #f)))
+
;;; XDG_DATA_HOME
;;; $HOME/.local/share
(define (data-home)
- (or (getenv "XDG_DATA_HOME")
+ (or (set? "XDG_DATA_HOME")
(string-append (getenv "HOME") "/.local/share")))
;;; XDG_CONFIG_HOME
;;; $HOME/.config
(define (config-home)
- (or (getenv "XDG_CONFIG_HOME")
+ (or (set? "XDG_CONFIG_HOME")
(string-append (getenv "HOME") "/.config")))
+;;; XDG_STATE_HOME
+;;; $HOME/.local/state
+(define (state-home)
+ (or (set? "XDG_STATE_HOME")
+ (string-append (getenv "HOME") "/.local/state")))
+
;;; XDG_DATA_DIRS
;;; colon (:) sepparated, in addition to XDG_DATA_HOME
;;; /usr/local/share/:/usr/share/
(define (data-dirs)
- (let ((str (getenv "XDG_DATA_DIRS")))
+ (let ((str (set? "XDG_DATA_DIRS")))
(if str
- (parse-path str)
+ (string-split str #\:)
'("/usr/local/share" "/usr/share"))))
-;;; sysconfdir
-;;; /etc
-;;; Techincly not part of the standard, but it's mentioned
-(define (sysconfdir)
- (or (getenv "sysconfdir")
- "/etc"))
-
-
;;; XDG_CONFIG_DIRS
;;; colon (:) separated, in adddition to XDG_CONFIG_HOME
;;; /etc/xdg
(define (config-dirs)
- (let ((str (getenv "XDG_CONFIG_DIRS")))
+ (let ((str (set? "XDG_CONFIG_DIRS")))
(if str
(string-split str #\:)
- (list (string-append sysconfdir "/xdg")))))
+ '("/etc/xdg"))))
;;; XDG_CACHE_HOME
;;; $HOME/.cache
(define (cache-home)
- (or (getenv "XDG_CACHE_HOME")
+ (or (set? "XDG_CACHE_HOME")
(string-append (getenv "HOME") "/.cache")))
;;; XDG_RUNTIME_DIR
;;; Default to /tmp or /tmp/$(uid), and raise a warning
(define (runtime-dir)
- (or (getenv "XDG_RUNTIME_DIR")
+ (or (set? "XDG_RUNTIME_DIR")
(begin
(display "WARNING: XDG_RUNTIME_DIR unset, defaulting to /tmp\n"
(current-error-port))
"/tmp")))
+
diff --git a/production-main b/production-main
deleted file mode 100644
index 69f1bc06..00000000
--- a/production-main
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-GUILE=${GUILE:-guile}
-exec "$GUILE" -c '((@ (calp main) main) (command-line))' "$@"
diff --git a/start b/start
index 2363d428..48d2c0e0 100755
--- a/start
+++ b/start
@@ -19,7 +19,7 @@ port=`find_port {8080..9000}`
echo "Starting on $port"
-$(dirname $(realpath $0))/main \
+$(dirname $(realpath $0))/calp \
--repl=$XDG_RUNTIME_DIR/calp \
--debug \
--edit-mode \
diff --git a/static/.dir-locals.el b/static/.dir-locals.el
new file mode 100644
index 00000000..143b6bbc
--- /dev/null
+++ b/static/.dir-locals.el
@@ -0,0 +1 @@
+((nil . ((typescript-indent-level . 4))))
diff --git a/static/.gitignore b/static/.gitignore
index 91b7c2f6..712e640c 100644
--- a/static/.gitignore
+++ b/static/.gitignore
@@ -5,3 +5,5 @@ deps.svg
*.js
!arbitary_kv.js
!input_list.js
+docs/
+!prettify-tsconfig.js
diff --git a/static/Makefile b/static/Makefile
index 2f715f7e..00401503 100644
--- a/static/Makefile
+++ b/static/Makefile
@@ -1,29 +1,30 @@
-.PHONY: all install clean watch watch-esbuild
+.PHONY: all install clean watch watch-esbuild doc
-TARGETS := style.css smallcal.css script.js directory-listing.css
+_TARGETS := style.css smallcal.css script.js directory-listing.css
+TARGETS = $(addprefix out/,$(_TARGETS))
WATCH=
-TS_FILES = $(shell find . -type f -name \*.ts -not -path */node_modules/*)
-JS_FILES = $(TS_FILES:%.ts=%.js)
+TS_FILES = $(shell find ts -type f -name \*.ts)
+JS_FILES = $(TS_FILES:ts/%.ts=out/%.js)
ESBUILD_LOGLEVEL=warning
# Variable for adding extra flags
ESBUILD_FLAGS =
# Used flags
__ESBUILD_FLAGS = --log-level=$(ESBUILD_LOGLEVEL) \
- --sourcemap --bundle --outdir=$(CURDIR) \
+ --sourcemap --bundle --outdir=$(CURDIR)/out \
$(ESBUILD_FLAGS)
-export PATH := $(shell npm bin):$(PATH)
+export PATH := $(CURDIR)/node_modules/.bin/:$(PATH)
all: $(TARGETS)
# script explicitly named, since that is our entry point
-script.js: script.ts $(TS_FILES)
+out/script.js: ts/script.ts $(TS_FILES)
esbuild $< $(__ESBUILD_FLAGS)
watch-esbuild:
- $(MAKE) ESBUILD_FLAGS+='--watch' ESBUILD_LOGLEVEL=info -B script.js
+ $(MAKE) ESBUILD_FLAGS+='--watch' ESBUILD_LOGLEVEL=info -B out/script.js
deps.svg: $(TS_FILES)
madge --image $@ $^
@@ -38,5 +39,16 @@ install: all
clean:
-rm $(TARGETS)
-%.css: %.scss
+out/%.css: scss/%.scss
scss -E UTF-8 $(WATCH) -I. $< $@
+
+# The grep expression is to supress irrelevant warning messages.
+# - __type since the extensions to base classes propagate to many
+# events, but typedoc only documents them if in the entry point
+# - [.]# since it's sometimes sensible to not document
+# implementation details.
+# - connectedCallback, attributeChangedCallabck: These are part
+# of the standard API for Web Components, and usually have nothing
+# interesting to note.
+doc:
+ typedoc --excludeExternals |& grep -vE '(__type|[.]#|connectedCallback|attributeChangedCallback)'
diff --git a/static/README.md b/static/README.md
new file mode 100644
index 00000000..1cb18411
--- /dev/null
+++ b/static/README.md
@@ -0,0 +1,46 @@
+The frontend code has its entry-point in `script.ts`.
+
+Much of this code assumes prior knowledge of the iCalendar standard (RFC
+5545). Besides that, the term "VComponent" is used to refer to any Calendar
+Component as specified in that RFC under 3.6.
+
+## Data Flow
+
+TODO document how data gets from the server to us, and from us to the server.
+
+A large part of this is how much we convert between serialization formats.
+
+## web components
+
+All elements are initialized in components.ts
+
+#### Boolean attributes
+Some components have properties/accessors which also appears as attributes on
+the actuall component (usually with a two-way maping).
+
+For boolean attributes, the attribute is either present or absent.
+
+### General Components
+- `components/date-time-input.ts`
+- `components/input-list.ts`
+
+### VEvent Components
+- `components/vevent.ts`
+- `components/changelog.ts`
+- `components/edit-rrule.ts`
+- `components/popup-element.ts`
+- `components/tab-group-element.ts`
+- `components/vevent-block.ts`
+- `components/vevent-description.ts`
+- `components/vevent-dl.ts`
+- `components/vevent-edit.ts`
+
+## About our buildsystem
+
+Currently (almost) everything is written in Typescript, and bundled
+through browserify. Ideally we would, for debug builds, export the
+single transplied Javascript files, but Chromium Chromium lacks
+support for modules on XHTML documents
+https://bugs.chromium.org/p/chromium/issues/detail?id=717643.
+However, seeing as the issue still gets frequent updates as of 2021 I
+believe that this might one day get resolved.
diff --git a/static/components/date-jump.ts b/static/components/date-jump.ts
deleted file mode 100644
index fd3908ae..00000000
--- a/static/components/date-jump.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export { DateJump }
-
-/* Replace backend-driven [today] link with frontend, with one that
- gets correctly set in the frontend. Similarly, update the go to
- specific date button into a link which updates wheneven the date
- form updates.
-*/
-class DateJump extends HTMLElement {
-
- readonly golink: HTMLAnchorElement;
- readonly input: HTMLInputElement;
-
- constructor() {
- super();
-
- this.golink = document.createElement('a')
- this.golink.classList.add('btn');
- this.golink.textContent = "➔"
- this.input = document.createElement('input')
- this.input.type = 'date';
- }
-
- connectedCallback() {
-
- /* Form is just here so the css works out */
- let form = document.createElement('form');
- form.replaceChildren(this.input, this.golink);
- this.replaceChildren(form);
-
- this.input.onchange = () => {
- let date = this.input.valueAsDate!.format('~Y-~m-~d');
- this.golink.href = `${date}.html`
- }
-
- let now = (new Date).format("~Y-~m-~d")
- this.input.value = now;
- /* onchange isn't triggered by manually setting the value */
- this.golink.href = `${now}.html`
- }
-}
diff --git a/static/make-watch b/static/make-watch
index b328038a..a1a6def8 100755
--- a/static/make-watch
+++ b/static/make-watch
@@ -13,7 +13,7 @@ if [ -n "$TMUX" ]; then
tmux new-window "tsc --watch"
else
tmux \
- new-session "scss --watch -I. style.scss:style.css" \; \
+ new-session "scss --watch -I. scss/style.scss:out/style.css" \; \
split-window "tsc --watch --noEmit" \; \
split-window "make watch-esbuild" \; \
rename-session "calp watch" \; \
diff --git a/static/package-lock.json b/static/package-lock.json
new file mode 100644
index 00000000..444ee8f9
--- /dev/null
+++ b/static/package-lock.json
@@ -0,0 +1,2180 @@
+{
+ "name": "calp",
+ "version": "0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "calp",
+ "version": "0.1",
+ "dependencies": {
+ "@microsoft/tsdoc": "^0.14.2",
+ "typedoc": "^0.24.6",
+ "uuid": "^8.3.2"
+ },
+ "devDependencies": {
+ "@mxssfd/typedoc-theme": "^1.1.2",
+ "@types/uuid": "^8.3.1"
+ },
+ "optionalDependencies": {
+ "comment-json": "^4.2.3",
+ "madge": "^5.0.1"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
+ "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
+ "optional": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@microsoft/tsdoc": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
+ "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
+ },
+ "node_modules/@mxssfd/typedoc-theme": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@mxssfd/typedoc-theme/-/typedoc-theme-1.1.2.tgz",
+ "integrity": "sha512-Q/9Z+sff8ey92PaB7bnsGOfyNa85vTjyofO8EOH8KwEdfGnV4lGXwLFt4AUth7CCqYplqI9q4JxHNt869mlEcw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "typedoc": "^0.24.8"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "optional": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "optional": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "optional": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "optional": true
+ },
+ "node_modules/@types/uuid": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz",
+ "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==",
+ "optional": true,
+ "engines": {
+ "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz",
+ "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==",
+ "optional": true,
+ "dependencies": {
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/visitor-keys": "4.33.0",
+ "debug": "^4.3.1",
+ "globby": "^11.0.3",
+ "is-glob": "^4.0.1",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz",
+ "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==",
+ "optional": true,
+ "dependencies": {
+ "@typescript-eslint/types": "4.33.0",
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-sequence-parser": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz",
+ "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ=="
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/app-module-path": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz",
+ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==",
+ "optional": true
+ },
+ "node_modules/array-timsort": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz",
+ "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==",
+ "optional": true
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ast-module-types": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-3.0.0.tgz",
+ "integrity": "sha512-CMxMCOCS+4D+DkOQfuZf+vLrSEmY/7xtORwdxs4wtcC1wVgvk2MqFFTwQCFhvWsI4KPU9lcWXPI8DgRiz+xetQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "optional": true,
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "optional": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "optional": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "optional": true,
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz",
+ "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "optional": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "optional": true
+ },
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "optional": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/comment-json": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz",
+ "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==",
+ "optional": true,
+ "dependencies": {
+ "array-timsort": "^1.0.3",
+ "core-util-is": "^1.0.3",
+ "esprima": "^4.0.1",
+ "has-own-prop": "^2.0.0",
+ "repeat-string": "^1.6.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "optional": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "optional": true
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "optional": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "optional": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "optional": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "optional": true
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "optional": true,
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dependency-tree": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-8.1.2.tgz",
+ "integrity": "sha512-c4CL1IKxkKng0oT5xrg4uNiiMVFqTGOXqHSFx7XEFdgSsp6nw3AGGruICppzJUrfad/r7GLqt26rmWU4h4j39A==",
+ "optional": true,
+ "dependencies": {
+ "commander": "^2.20.3",
+ "debug": "^4.3.1",
+ "filing-cabinet": "^3.0.1",
+ "precinct": "^8.0.0",
+ "typescript": "^3.9.7"
+ },
+ "bin": {
+ "dependency-tree": "bin/cli.js"
+ },
+ "engines": {
+ "node": "^10.13 || ^12 || >=14"
+ }
+ },
+ "node_modules/dependency-tree/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/dependency-tree/node_modules/typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "optional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/detective-amd": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-3.1.2.tgz",
+ "integrity": "sha512-jffU26dyqJ37JHR/o44La6CxtrDf3Rt9tvd2IbImJYxWKTMdBjctp37qoZ6ZcY80RHg+kzWz4bXn39e4P7cctQ==",
+ "optional": true,
+ "dependencies": {
+ "ast-module-types": "^3.0.0",
+ "escodegen": "^2.0.0",
+ "get-amd-module-type": "^3.0.0",
+ "node-source-walk": "^4.2.0"
+ },
+ "bin": {
+ "detective-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/detective-cjs": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-3.1.3.tgz",
+ "integrity": "sha512-ljs7P0Yj9MK64B7G0eNl0ThWSYjhAaSYy+fQcpzaKalYl/UoQBOzOeLCSFEY1qEBhziZ3w7l46KG/nH+s+L7BQ==",
+ "optional": true,
+ "dependencies": {
+ "ast-module-types": "^3.0.0",
+ "node-source-walk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/detective-es6": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-2.2.2.tgz",
+ "integrity": "sha512-eZUKCUsbHm8xoeoCM0z6JFwvDfJ5Ww5HANo+jPR7AzkFpW9Mun3t/TqIF2jjeWa2TFbAiGaWESykf2OQp3oeMw==",
+ "optional": true,
+ "dependencies": {
+ "node-source-walk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/detective-less": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/detective-less/-/detective-less-1.0.2.tgz",
+ "integrity": "sha512-Rps1xDkEEBSq3kLdsdnHZL1x2S4NGDcbrjmd4q+PykK5aJwDdP5MBgrJw1Xo+kyUHuv3JEzPqxr+Dj9ryeDRTA==",
+ "optional": true,
+ "dependencies": {
+ "debug": "^4.0.0",
+ "gonzales-pe": "^4.2.3",
+ "node-source-walk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0"
+ }
+ },
+ "node_modules/detective-postcss": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-5.1.3.tgz",
+ "integrity": "sha512-Wo7PUpF6wqeT1aRgajdyIdDRjFFJVxlXPRAlT1aankH/RVOgrJuEZFZ4ABxYXdzaRPO5Lkg8rHxsxpLnxdJIYA==",
+ "optional": true,
+ "dependencies": {
+ "is-url": "^1.2.4",
+ "postcss": "^8.4.6",
+ "postcss-values-parser": "^5.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/detective-sass": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-3.0.2.tgz",
+ "integrity": "sha512-DNVYbaSlmti/eztFGSfBw4nZvwsTaVXEQ4NsT/uFckxhJrNRFUh24d76KzoCC3aarvpZP9m8sC2L1XbLej4F7g==",
+ "optional": true,
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/detective-scss": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-2.0.2.tgz",
+ "integrity": "sha512-hDWnWh/l0tht/7JQltumpVea/inmkBaanJUcXRB9kEEXVwVUMuZd6z7eusQ6GcBFrfifu3pX/XPyD7StjbAiBg==",
+ "optional": true,
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/detective-stylus": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-1.0.3.tgz",
+ "integrity": "sha512-4/bfIU5kqjwugymoxLXXLltzQNeQfxGoLm2eIaqtnkWxqbhap9puDVpJPVDx96hnptdERzS5Cy6p9N8/08A69Q==",
+ "optional": true
+ },
+ "node_modules/detective-typescript": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-7.0.2.tgz",
+ "integrity": "sha512-unqovnhxzvkCz3m1/W4QW4qGsvXCU06aU2BAm8tkza+xLnp9SOFnob2QsTxUv5PdnQKfDvWcv9YeOeFckWejwA==",
+ "optional": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "^4.33.0",
+ "ast-module-types": "^2.7.1",
+ "node-source-walk": "^4.2.0",
+ "typescript": "^3.9.10"
+ },
+ "engines": {
+ "node": "^10.13 || >=12.0.0"
+ }
+ },
+ "node_modules/detective-typescript/node_modules/ast-module-types": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-2.7.1.tgz",
+ "integrity": "sha512-Rnnx/4Dus6fn7fTqdeLEAn5vUll5w7/vts0RN608yFa6si/rDOUonlIIiwugHBFWjylHjxm9owoSZn71KwG4gw==",
+ "optional": true
+ },
+ "node_modules/detective-typescript/node_modules/typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "optional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "optional": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
+ "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
+ "optional": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
+ "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+ "optional": true,
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "optional": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "optional": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "optional": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "optional": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "optional": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/filing-cabinet": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-3.3.0.tgz",
+ "integrity": "sha512-Tnbpbme1ONaHXV5DGcw0OFpcfP3p2itRf5VXO1bguBXdIewDbK6ZFBK//DGKM0BuCzaQLQNY4f5gljzxY1VCUw==",
+ "optional": true,
+ "dependencies": {
+ "app-module-path": "^2.2.0",
+ "commander": "^2.20.3",
+ "debug": "^4.3.3",
+ "enhanced-resolve": "^5.8.3",
+ "is-relative-path": "^1.0.2",
+ "module-definition": "^3.3.1",
+ "module-lookup-amd": "^7.0.1",
+ "resolve": "^1.21.0",
+ "resolve-dependency-path": "^2.0.0",
+ "sass-lookup": "^3.0.0",
+ "stylus-lookup": "^3.0.1",
+ "tsconfig-paths": "^3.10.1",
+ "typescript": "^3.9.7"
+ },
+ "bin": {
+ "filing-cabinet": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/filing-cabinet/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/filing-cabinet/node_modules/typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "optional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "optional": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/flatten": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz",
+ "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==",
+ "deprecated": "flatten is deprecated in favor of utility frameworks such as lodash.",
+ "optional": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "optional": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "optional": true
+ },
+ "node_modules/get-amd-module-type": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-3.0.2.tgz",
+ "integrity": "sha512-PcuKwB8ouJnKuAPn6Hk3UtdfKoUV3zXRqVEvj8XGIXqjWfgd1j7QGdXy5Z9OdQfzVt1Sk29HVe/P+X74ccOuqw==",
+ "optional": true,
+ "dependencies": {
+ "ast-module-types": "^3.0.0",
+ "node-source-walk": "^4.2.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+ "optional": true
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "optional": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "optional": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "optional": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gonzales-pe": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
+ "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
+ "optional": true,
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "gonzales": "bin/gonzales.js"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+ "optional": true
+ },
+ "node_modules/graphviz": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.9.tgz",
+ "integrity": "sha512-SmoY2pOtcikmMCqCSy2NO1YsRfu9OO0wpTlOYW++giGjfX1a6gax/m1Fo8IdUd0/3H15cTOfR1SMKwohj4LKsg==",
+ "optional": true,
+ "dependencies": {
+ "temp": "~0.4.0"
+ },
+ "engines": {
+ "node": ">=0.6.8"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "optional": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-own-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz",
+ "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/indexes-of": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+ "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==",
+ "optional": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "optional": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "optional": true
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "optional": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "optional": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "optional": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-relative-path": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.2.tgz",
+ "integrity": "sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA==",
+ "optional": true
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
+ "optional": true
+ },
+ "node_modules/is-url-superb": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz",
+ "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "optional": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/jsonc-parser": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
+ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
+ },
+ "node_modules/levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+ "optional": true,
+ "dependencies": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "optional": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "optional": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
+ },
+ "node_modules/madge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/madge/-/madge-5.0.1.tgz",
+ "integrity": "sha512-krmSWL9Hkgub74bOjnjWRoFPAJvPwSG6Dbta06qhWOq6X/n/FPzO3ESZvbFYVIvG2g4UHXvCJN1b+RZLaSs9nA==",
+ "optional": true,
+ "dependencies": {
+ "chalk": "^4.1.1",
+ "commander": "^7.2.0",
+ "commondir": "^1.0.1",
+ "debug": "^4.3.1",
+ "dependency-tree": "^8.1.1",
+ "detective-amd": "^3.1.0",
+ "detective-cjs": "^3.1.1",
+ "detective-es6": "^2.2.0",
+ "detective-less": "^1.0.2",
+ "detective-postcss": "^5.0.0",
+ "detective-sass": "^3.0.1",
+ "detective-scss": "^2.0.1",
+ "detective-stylus": "^1.0.0",
+ "detective-typescript": "^7.0.0",
+ "graphviz": "0.0.9",
+ "ora": "^5.4.1",
+ "pluralize": "^8.0.0",
+ "precinct": "^8.1.0",
+ "pretty-ms": "^7.0.1",
+ "rc": "^1.2.7",
+ "typescript": "^3.9.5",
+ "walkdir": "^0.4.1"
+ },
+ "bin": {
+ "madge": "bin/cli.js"
+ },
+ "engines": {
+ "node": "^10.13 || ^12 || >=14"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://www.paypal.me/pahen"
+ }
+ },
+ "node_modules/madge/node_modules/typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "optional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/marked": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "optional": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "optional": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "optional": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+ "optional": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/module-definition": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-3.4.0.tgz",
+ "integrity": "sha512-XxJ88R1v458pifaSkPNLUTdSPNVGMP2SXVncVmApGO+gAfrLANiYe6JofymCzVceGOMwQE2xogxBSc8uB7XegA==",
+ "optional": true,
+ "dependencies": {
+ "ast-module-types": "^3.0.0",
+ "node-source-walk": "^4.0.0"
+ },
+ "bin": {
+ "module-definition": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/module-lookup-amd": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz",
+ "integrity": "sha512-w9mCNlj0S8qviuHzpakaLVc+/7q50jl9a/kmJ/n8bmXQZgDPkQHnPBb8MUOYh3WpAYkXuNc2c+khsozhIp/amQ==",
+ "optional": true,
+ "dependencies": {
+ "commander": "^2.8.1",
+ "debug": "^4.1.0",
+ "glob": "^7.1.6",
+ "requirejs": "^2.3.5",
+ "requirejs-config-file": "^4.0.0"
+ },
+ "bin": {
+ "lookup-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/module-lookup-amd/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "optional": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "optional": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-source-walk": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-4.3.0.tgz",
+ "integrity": "sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA==",
+ "optional": true,
+ "dependencies": {
+ "@babel/parser": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "optional": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "optional": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "optional": true,
+ "dependencies": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.6",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "word-wrap": "~1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "optional": true,
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
+ "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "optional": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "optional": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "optional": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "optional": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "optional": true,
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-values-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-5.0.0.tgz",
+ "integrity": "sha512-2viDDjMMrt21W2izbeiJxl3kFuD/+asgB0CBwPEgSyhCmBnDIa/y+pLaoyX+q3I3DHH0oPPL3cgjVTQvlS1Maw==",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "is-url-superb": "^4.0.0",
+ "quote-unquote": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.9"
+ }
+ },
+ "node_modules/precinct": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/precinct/-/precinct-8.3.1.tgz",
+ "integrity": "sha512-pVppfMWLp2wF68rwHqBIpPBYY8Kd12lDhk8LVQzOwqllifVR15qNFyod43YLyFpurKRZQKnE7E4pofAagDOm2Q==",
+ "optional": true,
+ "dependencies": {
+ "commander": "^2.20.3",
+ "debug": "^4.3.3",
+ "detective-amd": "^3.1.0",
+ "detective-cjs": "^3.1.1",
+ "detective-es6": "^2.2.1",
+ "detective-less": "^1.0.2",
+ "detective-postcss": "^4.0.0",
+ "detective-sass": "^3.0.1",
+ "detective-scss": "^2.0.1",
+ "detective-stylus": "^1.0.0",
+ "detective-typescript": "^7.0.0",
+ "module-definition": "^3.3.1",
+ "node-source-walk": "^4.2.0"
+ },
+ "bin": {
+ "precinct": "bin/cli.js"
+ },
+ "engines": {
+ "node": "^10.13 || ^12 || >=14"
+ }
+ },
+ "node_modules/precinct/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/precinct/node_modules/detective-postcss": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-4.0.0.tgz",
+ "integrity": "sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A==",
+ "optional": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "is-url": "^1.2.4",
+ "postcss": "^8.1.7",
+ "postcss-values-parser": "^2.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/precinct/node_modules/postcss-values-parser": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz",
+ "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==",
+ "optional": true,
+ "dependencies": {
+ "flatten": "^1.0.2",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=6.14.4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-ms": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
+ "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
+ "optional": true,
+ "dependencies": {
+ "parse-ms": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true
+ },
+ "node_modules/quote-unquote": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz",
+ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==",
+ "optional": true
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "optional": true,
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/requirejs": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
+ "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
+ "optional": true,
+ "bin": {
+ "r_js": "bin/r.js",
+ "r.js": "bin/r.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/requirejs-config-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz",
+ "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==",
+ "optional": true,
+ "dependencies": {
+ "esprima": "^4.0.0",
+ "stringify-object": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "optional": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-dependency-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz",
+ "integrity": "sha512-DIgu+0Dv+6v2XwRaNWnumKu7GPufBBOr5I1gRPJHkvghrfCGOooJODFvgFimX/KRxk9j0whD2MnKHzM1jYvk9w==",
+ "optional": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "optional": true,
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "optional": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true,
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true
+ },
+ "node_modules/sass-lookup": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-3.0.0.tgz",
+ "integrity": "sha512-TTsus8CfFRn1N44bvdEai1no6PqdmDiQUiqW5DlpmtT+tYnIt1tXtDIph5KA1efC+LmioJXSnCtUVpcK9gaKIg==",
+ "optional": true,
+ "dependencies": {
+ "commander": "^2.16.0"
+ },
+ "bin": {
+ "sass-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/sass-lookup/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "optional": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shiki": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.2.tgz",
+ "integrity": "sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==",
+ "dependencies": {
+ "ansi-sequence-parser": "^1.1.0",
+ "jsonc-parser": "^3.2.0",
+ "vscode-oniguruma": "^1.7.0",
+ "vscode-textmate": "^8.0.0"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "optional": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "optional": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "optional": true,
+ "dependencies": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "optional": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "optional": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stylus-lookup": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-3.0.2.tgz",
+ "integrity": "sha512-oEQGHSjg/AMaWlKe7gqsnYzan8DLcGIHe0dUaFkucZZ14z4zjENRlQMCHT4FNsiWnJf17YN9OvrCfCoi7VvOyg==",
+ "optional": true,
+ "dependencies": {
+ "commander": "^2.8.1",
+ "debug": "^4.1.0"
+ },
+ "bin": {
+ "stylus-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/stylus-lookup/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "optional": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/temp": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/temp/-/temp-0.4.0.tgz",
+ "integrity": "sha512-IsFisGgDKk7qzK9erMIkQe/XwiSUdac7z3wYOsjcLkhPBy3k1SlvLoIh2dAHIlEpgA971CgguMrx9z8fFg7tSA==",
+ "engines": [
+ "node >=0.4.0"
+ ],
+ "optional": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "optional": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+ "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "optional": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "optional": true
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+ "optional": true,
+ "dependencies": {
+ "prelude-ls": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typedoc": {
+ "version": "0.24.8",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz",
+ "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==",
+ "dependencies": {
+ "lunr": "^2.3.9",
+ "marked": "^4.3.0",
+ "minimatch": "^9.0.0",
+ "shiki": "^0.14.1"
+ },
+ "bin": {
+ "typedoc": "bin/typedoc"
+ },
+ "engines": {
+ "node": ">= 14.14"
+ },
+ "peerDependencies": {
+ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x"
+ }
+ },
+ "node_modules/typedoc/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/typedoc/node_modules/minimatch": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
+ "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/uniq": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+ "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==",
+ "optional": true
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "optional": true
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vscode-oniguruma": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
+ "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA=="
+ },
+ "node_modules/vscode-textmate": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
+ "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg=="
+ },
+ "node_modules/walkdir": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz",
+ "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "optional": true,
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "optional": true
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "optional": true
+ }
+ }
+}
diff --git a/static/package.json b/static/package.json
index 81db3a61..f671def1 100644
--- a/static/package.json
+++ b/static/package.json
@@ -1,11 +1,18 @@
{
+ "name": "calp",
+ "version": "0.1",
+ "private": true,
"devDependencies": {
+ "@mxssfd/typedoc-theme": "^1.1.2",
"@types/uuid": "^8.3.1"
},
"optionalDependencies": {
+ "comment-json": "^4.2.3",
"madge": "^5.0.1"
},
"dependencies": {
+ "@microsoft/tsdoc": "^0.14.2",
+ "typedoc": "^0.24.6",
"uuid": "^8.3.2"
}
}
diff --git a/static/prettify-tsconfig.js b/static/prettify-tsconfig.js
new file mode 100755
index 00000000..a2bef6f0
--- /dev/null
+++ b/static/prettify-tsconfig.js
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+
+/*
+ * Script to normalize tsconfig.json.
+ */
+
+const { parse, stringify, assign } = require('comment-json')
+const fs = require('fs')
+
+const filename = 'tsconfig.json'
+
+const obj = parse(fs.readFileSync(filename).toString())
+const output = stringify(obj, null, 4)
+
+fs.writeFileSync(filename, output)
diff --git a/static/_global.scss b/static/scss/_global.scss
index 58e05155..58e05155 100644
--- a/static/_global.scss
+++ b/static/scss/_global.scss
diff --git a/static/_slider_input.scss b/static/scss/_slider_input.scss
index adae56ae..adae56ae 100644
--- a/static/_slider_input.scss
+++ b/static/scss/_slider_input.scss
diff --git a/static/_small-calendar.scss b/static/scss/_small-calendar.scss
index c4814285..c4814285 100644
--- a/static/_small-calendar.scss
+++ b/static/scss/_small-calendar.scss
diff --git a/static/directory-listing.scss b/static/scss/directory-listing.scss
index 745b5bc3..745b5bc3 100644
--- a/static/directory-listing.scss
+++ b/static/scss/directory-listing.scss
diff --git a/static/smallcal.scss b/static/scss/smallcal.scss
index c9a356c4..c9a356c4 100644
--- a/static/smallcal.scss
+++ b/static/scss/smallcal.scss
diff --git a/static/style.scss b/static/scss/style.scss
index d5920f79..d5920f79 100644
--- a/static/style.scss
+++ b/static/scss/style.scss
diff --git a/static/clock.ts b/static/ts/clock.ts
index bbd15de0..11b2b2c5 100644
--- a/static/clock.ts
+++ b/static/ts/clock.ts
@@ -1,3 +1,20 @@
+/**
+ * Components for working with things which depend on the current time.
+ *
+ * Also introduces two web components:
+ *
+ * ```html
+ * <today-button />
+ * <current-time />
+ * ```
+ *
+ * TODO shouldn't these be defined with the rest of the components?
+ *
+ * TODO why isn't Timebar and SmallCellHighlight also Web Components?
+ *
+ * @module
+ */
+
export {
SmallcalCellHighlight, Timebar,
initialize_clock_components
@@ -5,15 +22,23 @@ export {
import { makeElement, date_to_percent } from './lib'
-abstract class Clock {
+/**
+ * Interface for `things` which wants to get updated on a human timescale.
+ */
+export abstract class Clock {
+ /** Called every now and then
+ * @param now Called with the current time
+ */
abstract update(now: Date): void;
}
-
+/** The (blue) vertical line which show the current time in the current day. */
class Timebar extends Clock {
// start_time: Date
// end_time: Date
+
+ /** The bar to update */
bar_object: HTMLElement | null
constructor(/*start_time: Date, end_time: Date*/) {
@@ -23,7 +48,6 @@ class Timebar extends Clock {
this.bar_object = null
}
-
update(now: Date) {
// if (! (this.start_time <= now.getTime() && now.getTime() < this.end_time))
// return;
@@ -46,11 +70,26 @@ class Timebar extends Clock {
}
}
+/**
+ * Highlights the current date in the small calendar to the side.
+ * Currently directly sets a border
+ *
+ * @TODO{but should preferably set a class instead}.
+*/
class SmallcalCellHighlight extends Clock {
+ /** The calendar which a cell should be highlighted in */
small_cal: HTMLElement
+ /**
+ The currently highlighted cell, or `null` if no cell should be
+ should be highlighted (such as if a non-current month is selected
+ */
current_cell: HTMLElement | null
+ /**
+ * @param small_cal the DOM-node of the calendar widget. It must support
+ * querySelector.
+ */
constructor(small_cal: HTMLElement) {
super();
this.small_cal = small_cal;
@@ -75,8 +114,16 @@ class SmallcalCellHighlight extends Clock {
/* -------------------------------------------------- */
+/**
+ Base class for custom HTML elements which wants to be updated for a human
+ timescale.
+
+ When creating, the attribute `interval` can be given, which specifies (in
+ seconds) how often the component should be updated.
+*/
class ClockElement extends HTMLElement {
+ /** Javascript timer id. Used if the timer needs to be canceled */
timer_id: number
constructor() {
@@ -95,14 +142,25 @@ class ClockElement extends HTMLElement {
this.update(new Date)
}
- static get observedAttributes() {
- return ['timer_id']
- }
-
+ /**
+ Method which is called each "tick" (see interval)
+ @param date
+ The current timestamp when the function is called.
+ */
update(_: Date) { /* noop */ }
}
+/**
+ A "button" which always points to the link "~Y-~m-~d.html".
+
+ This class is bound to the web component <today-button />
+
+ In the backend code, a `/today` endpoint exists. That however requires that
+ we ask the server for the correct URL, and follow a 300 (series) redirect.
+
+ Since the URL:s are stable, it's possible to jump directly to the given page.
+ */
class TodayButton extends ClockElement {
a: HTMLAnchorElement;
@@ -124,12 +182,26 @@ class TodayButton extends ClockElement {
}
+/**
+ A component which displays the current time
+
+ This class is bound to the web component <current-time />
+
+ It currently is hard-coded to display time on the format ~H:~M:~S.
+*/
class CurrentTime extends ClockElement {
update(now: Date) {
this.textContent = now.format('~H:~M:~S')
}
}
+/**
+ Create Web Components mentioned on this page.
+
+ MUST be called early on in the execution.
+
+ TODO this should be merged with other web component declarations.
+*/
function initialize_clock_components() {
customElements.define('today-button', TodayButton)
customElements.define('current-time', CurrentTime)
diff --git a/static/components.ts b/static/ts/components.ts
index e5fabba6..c78b5753 100644
--- a/static/components.ts
+++ b/static/ts/components.ts
@@ -1,3 +1,12 @@
+/**
+ Actuall creation of all web components.
+
+ More text
+
+ @category Web Components
+ @module components
+ */
+
import { ComponentDescription } from './components/vevent-description'
import { ComponentEdit } from './components/vevent-edit'
import { VEventDL } from './components/vevent-dl'
@@ -13,9 +22,20 @@ import { DateJump } from './components/date-jump'
export { initialize_components }
-function initialize_components() {
+/**
+ Create web components from all our components.
+
+ The reason each components module doesn't simply initialize its own component
+ is due to some components needing to be initialized AFTER some global
+ variables (see inline comments).
+ @TODO
+ Fix the initialization order dependency
+ @TODO
+ or otherwise have a static field on each component specifying it's desired name.
+ */
+function initialize_components() {
/* These MUST be created AFTER vcal_objcets and event_calendar_mapping are
inistialized, since their constructors assume that that piece of global
state is available */
diff --git a/static/components/changelog.ts b/static/ts/components/changelog.ts
index d08f7cb3..8f8adc1c 100644
--- a/static/components/changelog.ts
+++ b/static/ts/components/changelog.ts
@@ -1,21 +1,43 @@
+/**
+ `<changelog />`
+
+ Display of a VEvents changelog. @ref{ChangeLogEntry}
+
+ TODO rename this file!
+
+
+ @privateRemarks @anchor{VEventChangelog}
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
import { makeElement } from '../lib'
import { ComponentVEvent } from './vevent'
import { VEvent } from '../vevent'
export { VEventChangelog }
+/**
+ Component displaying veevents changelog.
+
+ This component is dumb, and (almost) doesn't keep any internal state. Instead
+ other parts of the program should call it with a `VEvent`, which contains the
+ actual changelog.
+*/
class VEventChangelog extends ComponentVEvent {
- readonly ul: HTMLElement
+ /** The list holding the changelog */
+ readonly #ul: HTMLElement
constructor(uid?: string) {
super(uid);
- this.ul = makeElement('ul');
+ this.#ul = makeElement('ul');
}
connectedCallback() {
- this.replaceChildren(this.ul);
+ this.replaceChildren(this.#ul);
}
redraw(data: VEvent) {
@@ -44,6 +66,6 @@ class VEventChangelog extends ComponentVEvent {
children.push(makeElement('li', { textContent: msg }));
}
- this.ul.replaceChildren(...children)
+ this.#ul.replaceChildren(...children)
}
}
diff --git a/static/ts/components/date-jump.ts b/static/ts/components/date-jump.ts
new file mode 100644
index 00000000..f1cfe7e6
--- /dev/null
+++ b/static/ts/components/date-jump.ts
@@ -0,0 +1,51 @@
+/**
+ `<date-jump />`
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
+
+export { DateJump }
+
+/** Replace backend-driven [today] link with frontend, with one that
+ gets correctly set in the frontend. Similarly, update the go to
+ specific date button into a link which updates wheneven the date
+ form updates.
+
+ TODO is this comment correct? We somehow contain an input element also.
+*/
+class DateJump extends HTMLElement {
+
+ readonly #golink: HTMLAnchorElement;
+ readonly #input: HTMLInputElement;
+
+ constructor() {
+ super();
+
+ this.#golink = document.createElement('a')
+ this.#golink.classList.add('btn');
+ this.#golink.textContent = "➔"
+ this.#input = document.createElement('input')
+ this.#input.type = 'date';
+ }
+
+ /** Sets the link to NOW upon mounting */
+ connectedCallback() {
+
+ /* Form is just here so the css works out */
+ let form = document.createElement('form');
+ form.replaceChildren(this.#input, this.#golink);
+ this.replaceChildren(form);
+
+ this.#input.onchange = () => {
+ let date = this.#input.valueAsDate!.format('~Y-~m-~d');
+ this.#golink.href = `${date}.html`
+ }
+
+ let now = (new Date).format("~Y-~m-~d")
+ this.#input.value = now;
+ /* onchange isn't triggered by manually setting the value */
+ this.#golink.href = `${now}.html`
+ }
+}
diff --git a/static/components/date-time-input.ts b/static/ts/components/date-time-input.ts
index 20e9a505..33201653 100644
--- a/static/components/date-time-input.ts
+++ b/static/ts/components/date-time-input.ts
@@ -1,12 +1,35 @@
+/**
+ * `<date-time-input />`
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
export { DateTimeInput }
import { makeElement, parseDate } from '../lib'
-
-/* '<date-time-input />' */
+/**
+ * The HTML component `<date-time-input />`.
+ * An element for input for date-times. Similar to
+ * @example
+ * ```html
+ * <input type="date"/>
+ * <input type="time"/>
+ * ```
+ *
+ * But as a single unit.
+ *
+ * ### Attributes
+ * - dateonly
+ *
+ */
class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
+ /** Our time input element */
readonly time: HTMLInputElement;
+ /** Our date input element */
readonly date: HTMLInputElement;
constructor() {
@@ -22,22 +45,30 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}) as HTMLInputElement
}
+ /**
+ We set our children first when mounted.
+
+ This can be in the constructor for chromium, but NOT firefox...
+
+ - Vivaldi 4.3.2439.63 stable
+ - Mozilla Firefox 94.0.1
+ */
connectedCallback() {
- /* This can be in the constructor for chromium, but NOT firefox...
- Vivaldi 4.3.2439.63 stable
- Mozilla Firefox 94.0.1
- */
- /*
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes
- https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
- */
this.replaceChildren(this.date, this.time)
}
+ /**
+ Attributes which we want notifications when they are change.
+
+ Part of the Web Component API
+
+ - `dateonly`
+ */
static get observedAttributes() {
return ['dateonly']
}
+ /** Part of the Web Component API */
attributeChangedCallback(name: string, _: string | null, to: string | null): void {
switch (name) {
case 'dateonly':
@@ -54,10 +85,16 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /**
+ Setting this to true disabled the time part of the input, and makes
+ any output only have date components (alternativly, the time component
+ set to zero).
+ */
get dateonly(): boolean {
return this.hasAttribute('dateonly');
}
+ /** See getter */
set dateonly(b: boolean) {
if (b) {
this.setAttribute('dateonly', "");
@@ -66,6 +103,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /** See getter */
set value(date: Date) {
let [d, t] = date.format("~L~Y-~m-~dT~H:~M").split('T');
this.date.value = d;
@@ -74,6 +112,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
this.dateonly = date.dateonly;
}
+ /** Returns current value as a Date object. */
get value(): Date {
let dt;
let date = this.date.value;
@@ -88,6 +127,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
return dt;
}
+ /** Returns current value as an ISO-8601 formatted string. */
get stringValue(): string {
if (this.dateonly) {
return this.value.format("~Y-~m-~d")
@@ -96,6 +136,13 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /**
+ Set the selected date.
+
+ @param new_value
+ If given a date, set the input to that date.
+ If given a string, parse it as an ISO-8601 formatted datetime.
+ */
set stringValue(new_value: Date | string) {
let date, time, dateonly = false;
if (new_value instanceof Date) {
@@ -110,6 +157,9 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
this.time.value = time;
}
+ /**
+ Adds an event listener to both the date and time input.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
if (type != 'input') throw "Only input supported";
diff --git a/static/components/edit-rrule.ts b/static/ts/components/edit-rrule.ts
index a361bdee..b78171cc 100644
--- a/static/components/edit-rrule.ts
+++ b/static/ts/components/edit-rrule.ts
@@ -1,3 +1,15 @@
+/**
+ * `<vevent-edit-rrule />`
+ *
+ * An edit form for a recurrence rule. Searches its template for elements
+ * with `[name="<<field name>>"]`, and binds to those.
+ *
+ * TODO rename this file
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { EditRRule }
import { ComponentVEvent } from './vevent'
diff --git a/static/components/input-list.ts b/static/ts/components/input-list.ts
index 0afd4999..72d27cab 100644
--- a/static/components/input-list.ts
+++ b/static/ts/components/input-list.ts
@@ -1,29 +1,75 @@
+/**
+ * `<input-list />`
+ *
+ * A list of identical input fields, which forms a group. For example
+ * useful to handle keywords.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { InputList }
/*
TODO allow each item to be a larger unit, possibly containing multiple input
fields.
*/
+/**
+ A multi-valued input, done by creating extra input fields as needed.
+
+ The first element of body MUST be an input element, which will be used as the
+ template for each instance. A tag input could for example look like
+
+ @example
+ ```html
+ <input-list name="tags">
+ <input type="text" placeholder="tag ..." />
+ </input-list>
+ ```
+
+ Whenever one of the input elements `value` becomes the empty string, that tag
+ is removed, and whenever there is no element with the empty string as a
+ `value`, a new input element will be added onto the end.
+ */
class InputList extends HTMLElement {
- el: HTMLInputElement;
+ /** The element used as our template. Will be sourced from the initial HTML code. */
+ #el: HTMLInputElement;
+ /**
+ Registered listeners, which will be added onto each created entry
+
+ Keys are event names ('input', 'change', ...) and values event handlers.
+
+ This is a list of tuples rather than a dictionary, since multiple
+ listeners of the same type can be registered.
+ */
#listeners: [string, (e: Event) => void][] = [];
constructor() {
super();
- this.el = this.children[0].cloneNode(true) as HTMLInputElement;
+ this.#el = this.children[0].cloneNode(true) as HTMLInputElement;
}
+ /** Clears all existing children upon mount */
connectedCallback() {
for (let child of this.children) {
child.remove();
}
- this.addInstance();
+ this.#addInstance();
}
- createInstance(): HTMLInputElement {
- let new_el = this.el.cloneNode(true) as HTMLInputElement
+ /**
+ Instanciates a new instance of the input element.
+
+ An event listener for 'input' will be added, which will handle the
+ addition and removing of other elements.
+
+ All event listeners attachet on the input-list component will also be
+ added.
+ */
+ #createInstance(): HTMLInputElement {
+ let new_el = this.#el.cloneNode(true) as HTMLInputElement
let that = this;
new_el.addEventListener('input', function() {
/* TODO .value is empty both if it's actually empty, but also
@@ -39,7 +85,7 @@ class InputList extends HTMLElement {
}
} else {
if (!this.nextElementSibling) {
- that.addInstance();
+ that.#addInstance();
// window.setTimeout(() => this.focus())
this.focus();
}
@@ -53,12 +99,17 @@ class InputList extends HTMLElement {
return new_el;
}
- addInstance() {
- let new_el = this.createInstance();
+ /** Add a new instance of the input element to the container */
+ #addInstance() {
+ let new_el = this.#createInstance();
this.appendChild(new_el);
}
- get value(): any[] {
+ /**
+ * The value from each element, except the last which should always be empty.
+ * Has an unspecified type, since children:s value field might give non-strings.
+ */
+ get value(): unknown[] {
let value_list = []
for (let child of this.children) {
value_list.push((child as any).value);
@@ -69,6 +120,12 @@ class InputList extends HTMLElement {
return value_list
}
+ /**
+ Overwrite the current value with a new one.
+
+ Each entry in the array will be mapped unto one instance of the template
+ input element. A final empty element will also be added.
+ */
set value(new_value: any[]) {
let all_equal = true;
@@ -97,17 +154,22 @@ class InputList extends HTMLElement {
/* clear dictionary */
values.set(value, false);
} else {
- let new_el = this.createInstance();
+ let new_el = this.#createInstance();
new_el.value = value;
output_list.push(new_el);
}
}
/* final, trailing, element */
- output_list.push(this.createInstance());
+ output_list.push(this.#createInstance());
this.replaceChildren(...output_list);
}
+ /**
+ Add an event listener to each of the inputs.
+
+ This basically works as the "regular" version.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
// if (type != 'input') throw "Only input supported";
diff --git a/static/components/popup-element.ts b/static/ts/components/popup-element.ts
index 458f543c..a1e81f0e 100644
--- a/static/components/popup-element.ts
+++ b/static/ts/components/popup-element.ts
@@ -1,3 +1,19 @@
+/**
+ * `<popup-element />`
+ *
+ * A (small) floating window containing information, which can be dragged
+ * arround. Consists of a navigation bar with a few buttons for
+ * controlling the window, which also works as a drag handle, along with
+ * an area for contents, which can be resized by the user.
+
+ * Currently tightly coupled to VEvent's, since their color
+ * profile is derived from their owning events calendar, and they have
+ * action buttons for the event in their navigation bar.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { PopupElement, setup_popup_element }
import { VEvent } from '../vevent'
@@ -7,12 +23,19 @@ import { ComponentVEvent } from './vevent'
import { remove_event } from '../server_connect'
-/* <popup-element /> */
+/**
+ ### Attributes
+ - visible
+ */
class PopupElement extends ComponentVEvent {
/* The popup which is the "selected" popup.
- /* Makes the popup last hovered over the selected popup, moving it to
+ * Makes the popup last hovered over the selected popup, moving it to
* the top, and allowing global keyboard bindings to affect it. */
+ /**
+ The popup which was most recently interacted with by the user. Used to
+ move it on top of all others, as well as sending relevant key events there.
+ */
static activePopup: PopupElement | null = null;
constructor(uid?: string) {
@@ -67,6 +90,7 @@ class PopupElement extends ComponentVEvent {
this.replaceChildren(body);
}
+ /** ['visible'] */
static get observedAttributes() {
return ['visible'];
}
@@ -76,15 +100,22 @@ class PopupElement extends ComponentVEvent {
case 'visible':
if (newValue !== null)
/* Only run resize code when showing the popup */
- this.onVisibilityChange()
+ this.#onVisibilityChange()
break;
}
}
+ /**
+ If the popup is currently visible.
+
+ Adds the `visible` attribute to the component, which must then be handled
+ through CSS.
+ */
get visible(): boolean {
return this.hasAttribute('visible');
}
+ /** Set the visibility status of the component. */
set visible(isVisible: boolean) {
if (isVisible) {
this.setAttribute('visible', 'visible');
@@ -93,7 +124,7 @@ class PopupElement extends ComponentVEvent {
}
}
- private onVisibilityChange() {
+ #onVisibilityChange() {
console.log('here');
/* TODO better way to find root */
@@ -128,6 +159,10 @@ class PopupElement extends ComponentVEvent {
el.style.removeProperty('height');
}
+ /**
+ Resize the popup window to fill the current viewport (mostly). Is
+ probably bonud to the maximize button in the navigation bar.
+ */
maximize() {
/* TODO this assumes that popups are direct decendant of their parent,
which they really ought to be */
@@ -144,7 +179,8 @@ class PopupElement extends ComponentVEvent {
}
}
-/* Create a new popup element for the given VEvent, and ready it for editing the
+/**
+ Create a new popup element for the given VEvent, and ready it for editing the
event. Used when creating event (through the frontend).
The return value can safely be ignored.
*/
diff --git a/static/components/slider.ts b/static/ts/components/slider.ts
index 48abc91b..8be66a73 100644
--- a/static/components/slider.ts
+++ b/static/ts/components/slider.ts
@@ -1,24 +1,58 @@
-export { SliderInput }
+/**
+ <slider-input />
+
+ A Web Component implementing a slider with a corresponding number input.
+
+ TODO rename this file
+
+ ### Parameters
+
+ All of these are optional, see {@linkcode dflt} for defaults.
+
+ #### min
+ Minimum allowed value.
+
+ #### max
+ Maximum allowed value.
+
+ #### step
+ How large each step of the slider/number box should be.
+
+ @module
+*/
+
+export { SliderInput, Attribute, dflt }
import { makeElement } from '../lib'
+/** Defalut values for all attributes, if not given */
const dflt = {
min: 0,
max: 100,
step: 1,
}
+/** Valid attributes for SliderInput */
type Attribute = 'min' | 'max' | 'step'
+/**
+ Component displaying an input slider, together with a corresponding numerical
+ input
+*/
class SliderInput extends HTMLElement {
/* value a string since javascript kind of expects that */
- #value = "0";
- min = 0;
- max = 100;
- step = 1;
-
+ #value = "" + dflt.min
+ /** Minimum allowed value */
+ min = dflt.min
+ /** Maximum allowed value */
+ max = dflt.max
+ /** How large each step should be */
+ step = dflt.step
+
+ /** The HTML slider component */
readonly slider: HTMLInputElement;
+ /** The HTML number input component */
readonly textIn: HTMLInputElement;
constructor(min?: number, max?: number, step?: number, value?: number) {
@@ -48,8 +82,8 @@ class SliderInput extends HTMLElement {
value: this.value,
}) as HTMLInputElement
- this.slider.addEventListener('input', e => this.propagate(e));
- this.textIn.addEventListener('input', e => this.propagate(e));
+ this.slider.addEventListener('input', e => this.#propagate(e));
+ this.textIn.addEventListener('input', e => this.#propagate(e));
/* MUST be after sub components are bound */
this.value = "" + (value || this.getAttribute('value') || defaultValue);
@@ -59,7 +93,7 @@ class SliderInput extends HTMLElement {
this.replaceChildren(this.slider, this.textIn);
}
-
+ /** ['min', 'max', 'step'] */
static get observedAttributes(): Attribute[] {
return ['min', 'max', 'step']
}
@@ -75,19 +109,35 @@ class SliderInput extends HTMLElement {
this[name] = parseFloat(to || "" + dflt[name])
}
- propagate(e: Event) {
+ /**
+ Helper for updating the value attribute
+
+ Event listeners are bound on both the input elements, which both simply
+ call this. This procedure then updates the classes value field.
+
+ TODO `oninput`?
+ */
+ #propagate(e: Event) {
this.value = (e.target as HTMLInputElement).value;
if (e instanceof InputEvent && this.oninput) {
this.oninput(e);
}
}
+ /**
+ Set a new numerical value.
+
+ A number not possible due to the current `min`, `max`, and `step`
+ properties can be set and will work, the slider will however not
+ properly show it, but rather the closest value it can display.
+ */
set value(value: string) {
this.slider.value = value;
this.textIn.value = value;
this.#value = value;
}
+ /** Get the current numerical value */
get value(): string {
return this.#value;
}
diff --git a/static/components/tab-group-element.ts b/static/ts/components/tab-group-element.ts
index e90997e9..bcd45b40 100644
--- a/static/components/tab-group-element.ts
+++ b/static/ts/components/tab-group-element.ts
@@ -1,3 +1,40 @@
+/**
+ * `<tab-group />`
+
+A group of tabs, where only one can be visible at a time.
+
+@privateRemarks TODO which form does the HTML document have? For CSS purposes
+
+Each tab consists of two parts, a label which is used for selecting
+it, and a tab-element, which contains the actual content. These two
+should refer to each other as follows:
+
+@example
+```
++---------------+ +-----------------+
+| TabLabel | | Tab |
++---------------+ +-----------------+
+| id |<----| aria-labelledby |
+| aria-controls |---->| id |
++---------------+ +-----------------+
+```
+
+Further information about tabs in HTML can be found here:
+https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role
+
+#### CSS Variables
+
+##### tabcount
+Each tab element has the style property `--tabcount` set to how
+many tabs it has. This is mostly useful to make sure the tab context
+is large enough to fit all tab labels without overflowing.
+
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
import { ComponentVEvent } from './vevent'
import { makeElement, gensym } from '../lib'
import { EditRRule } from './edit-rrule'
@@ -6,7 +43,7 @@ import { vcal_objects } from '../globals'
export { TabGroupElement }
-/* Lacks a template, since it's trivial
+/** Lacks a template, since it's trivial
The initial children of this element all becomes tabs, each child may have
the datapropertys 'label' and 'title' set, where label is what is shown in
the tab bar, and title is the hower text.
@@ -21,9 +58,12 @@ export { TabGroupElement }
*/
class TabGroupElement extends ComponentVEvent {
+ /** The container holding all the tabLabels */
readonly menu: HTMLElement;
+ /** Contents of each tab */
tabs: HTMLElement[] = [];
+ /** Label element of each tab */
tabLabels: HTMLElement[] = [];
constructor(uid?: string) {
@@ -81,6 +121,12 @@ class TabGroupElement extends ComponentVEvent {
} /* end connectedCallback */
+ /**
+ Adds a new tab to the group. The first parameter will make up the body
+ of the tab. The label is whath should be shown in the tab selector,
+ but defaults to the first letter of the text content of the body node.
+ Title is the hoover text of the label.
+ */
addTab(child: HTMLElement, label?: string, title?: string) {
/* First character of text is a good a guess as any for our label,
@@ -123,11 +169,15 @@ class TabGroupElement extends ComponentVEvent {
this.tabLabels.push(tabLabel);
this.menu.appendChild(tabLabel);
- tabLabel.addEventListener('click', () => this.tabClickedCallback(tabLabel));
+ tabLabel.addEventListener('click', () => this.#tabClickedCallback(tabLabel));
this.style.setProperty('--tabcount', '' + this.tabs.length);
}
+ /**
+ HTMLElement must be one of the tab bodies in this group. This method
+ removes it, along with its TabLabel.
+ */
removeTab(tab: HTMLElement) {
let id = tab.getAttribute('aria-labelledby')!
let label = document.getElementById(id)
@@ -152,7 +202,7 @@ class TabGroupElement extends ComponentVEvent {
}
/* TODO replace querySelectors here with our already saved references */
- tabClickedCallback(tab: Element) {
+ #tabClickedCallback(tab: Element) {
/* hide all tab panels */
for (let tabcontent of this.querySelectorAll('[role="tabpanel"]')) {
@@ -171,7 +221,7 @@ class TabGroupElement extends ComponentVEvent {
}
- /* returns our rrule tab if we have one */
+ /** Return our rrule tab if we have one */
has_rrule_tab(): Element | false {
for (let child of this.children) {
if (child.firstChild! instanceof EditRRule) {
diff --git a/static/components/vevent-block.ts b/static/ts/components/vevent-block.ts
index 9bbb8e7e..90460740 100644
--- a/static/components/vevent-block.ts
+++ b/static/ts/components/vevent-block.ts
@@ -1,3 +1,14 @@
+/**
+ * `<vevent-block />`
+ *
+ * A block in our graphical view.
+ *
+ * Unique in that it works quite differently between the week and month view.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { ComponentBlock }
import { ComponentVEvent } from './vevent'
@@ -5,10 +16,16 @@ import { VEvent } from '../vevent'
import { parseDate, to_local } from '../lib'
-/* <vevent-block />
+/**
+ A graphical block in the inline view.
- A grahpical block in the week view.
-*/
+ The back-end links what should become these to elements in the sidebar
+ containing extra info, jumping between them using fragment links.
+ That functionality is removed when we replace the non-js fallback children of
+ these elements, but we instead link it to a
+ {@linkcode components/popup-element.PopupElement}
+ containing the detailed information, along with editing controls and more.
+ */
class ComponentBlock extends ComponentVEvent {
constructor(uid?: string) {
super(uid);
diff --git a/static/components/vevent-description.ts b/static/ts/components/vevent-description.ts
index b44185e7..bf62c10d 100644
--- a/static/components/vevent-description.ts
+++ b/static/ts/components/vevent-description.ts
@@ -1,3 +1,19 @@
+/**
+ * `<vevent-description />`
+
+A text representation of a VEvent. Used as the summary tab of our
+popup windows, and in the sidebar.
+
+When redrawn, it looks for an HTML-tag inside its template having the
+attribute `data-property` matching the properties name. If one is
+found, it looks in the `formatters` table
+({@link formatters}), for a field matching the property value, and
+defaults to the key `default`.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { ComponentDescription }
import { VEvent } from '../vevent'
diff --git a/static/components/vevent-dl.ts b/static/ts/components/vevent-dl.ts
index a792c07f..a4b51dd9 100644
--- a/static/components/vevent-dl.ts
+++ b/static/ts/components/vevent-dl.ts
@@ -1,3 +1,15 @@
+/**
+ * `<vevent-dl />`
+ *
+ * A description list of a vevent, used for debugging.
+ *
+ * No guarantees are given about the contents of the data fields, more
+ * than that they are related to the value in question.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { VEventDL }
import { ComponentVEvent } from './vevent'
diff --git a/static/components/vevent-edit.ts b/static/ts/components/vevent-edit.ts
index e3b5d105..5dd39ee9 100644
--- a/static/components/vevent-edit.ts
+++ b/static/ts/components/vevent-edit.ts
@@ -1,3 +1,14 @@
+/**
+ * `<vevent-edit />`
+ *
+ * Edit form for a vevent, designed for useful human interaction (and
+ * thereby not being all-encompassing).
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
export { ComponentEdit }
import { ComponentVEvent } from './vevent'
diff --git a/static/components/vevent.ts b/static/ts/components/vevent.ts
index 7487cbb6..50ff4a30 100644
--- a/static/components/vevent.ts
+++ b/static/ts/components/vevent.ts
@@ -1,18 +1,48 @@
+/**
+ * Root component for all events which content is closely linked to a `VEvent` object
+ *
+ * Lacks an accompaning tag, and shouldn't be directly instanciated.
+ *
+ * Note that many of these assume that their initial children are
+ * configured specifically, that is however not completely documented.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
export { ComponentVEvent }
import { vcal_objects } from '../globals'
import { VEvent } from '../vevent'
-/* Root component for all events which content is closely linked to a
-@code{VEvent} object
+/**
+ Base class for all Web Components closely linked with VEvents.
+
+ TODO document how templates work.
-Lacks an accompaning tag, and shouldn't be directly instanciated.
-*/
+ TODO document lifecycle, and how objects are fetched from the "global" store.
+ */
abstract class ComponentVEvent extends HTMLElement {
+ /**
+ The template for this event.
+
+ TODO document how this is populate
+ */
template?: HTMLTemplateElement
+
+ /** The UID of the VEvent we are tracking */
uid: string
+ /**
+ * This registeres itself, but doesn't redraw
+ * We do however redraw in connectedCallback
+
+ * @privateRemarks
+ * TODO what is done in the default constructor,
+ * and the default connectedCallback
+ */
constructor(uid?: string) {
super();
this.template = document.getElementById(this.tagName.toLowerCase()) as HTMLTemplateElement | undefined
@@ -56,6 +86,11 @@ abstract class ComponentVEvent extends HTMLElement {
should take care of that some other way */
}
+ /**
+ Called when the component is mounted.
+
+ Redraws the target if the wanted object is available at that time.
+ */
connectedCallback() {
let uid = this.dataset.uid
if (uid) {
@@ -64,6 +99,7 @@ abstract class ComponentVEvent extends HTMLElement {
}
}
+ /** While abstract for this, @emph{must} be overridden for everyone else */
abstract redraw(data: VEvent): void
}
diff --git a/static/event-creator.ts b/static/ts/event-creator.ts
index 5e55e64e..a3231d24 100644
--- a/static/event-creator.ts
+++ b/static/ts/event-creator.ts
@@ -6,40 +6,69 @@ import { ComponentBlock } from './components/vevent-block'
import { round_time, parseDate } from './lib'
import { ical_type } from './types'
+/**
+ Class managing the state while creating events.
+
+ This is mainly for, when in the UI, the user starts to create events by
+ dragging on the calendar.
+
+*/
class EventCreator {
- /* Event which we are trying to create */
+ /** Event which we are trying to create */
ev?: VEvent
- /* Graphical block for event. Only here so we can find its siblings,
- and update pointer events accordingly */
+ /** Graphical block for event. Only here so we can find its siblings, and
+ update pointer events accordingly */
event?: Element
- event_start: { x: number, y: number } = { x: NaN, y: NaN }
- down_on_event: boolean = false
- timeStart: number = 0
+ /**
+ Where the mouse down for the event started.
+
+ This is here to check if the user is actually dragging, or just randomly
+ clicking on the background with a shaky hand.
+
+ There are some constants in the code for what a shaky hand means
+ (currently less than 10 pixels in X, or 5 in Y)
+ */
+ #event_start: { x: number, y: number } = { x: NaN, y: NaN }
+ #down_on_event: boolean = false
+ #time_start: number = 0
+ /**
+ Event handler for `mosedown` events.
+ */
create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any {
let that = this;
return function(e: MouseEvent) {
/* Only trigger event creation stuff on actuall events background,
NOT on its children */
- that.down_on_event = false;
+ that.#down_on_event = false;
if (e.target != intended_target) return;
- that.down_on_event = true;
+ that.#down_on_event = true;
- that.event_start.x = e.clientX;
- that.event_start.y = e.clientY;
+ that.#event_start.x = e.clientX;
+ that.#event_start.y = e.clientY;
}
}
- /*
- round_to: what start and end times should round to when dragging, in fractionsb
- of the width of the containing container.
+ /**
+ Event handler for `mousemove` events.
- TODO limit this to only continue when on the intended event_container.
+ @param pos_in
+ TODO
- (event → [0, 1)), 𝐑, bool → event → ()
+ @param round_to
+ what start and end times should round to when dragging, in fractionsb of
+ the width of the containing container.
+
+ @param wide_element
+ Does the element expect to grow horizontally (`true`) or vertically
+ (`false`).
+
+ TODO limit this to only continue when on the intended event_container.
+
+ (event → [0, 1)), 𝐑, bool → event → ()
*/
create_event_move(
pos_in: ((c: HTMLElement, e: MouseEvent) => number),
@@ -48,13 +77,13 @@ class EventCreator {
): ((e: MouseEvent) => void) {
let that = this;
return function(this: HTMLElement, e: MouseEvent) {
- if (e.buttons != 1 || !that.down_on_event) return;
+ if (e.buttons != 1 || !that.#down_on_event) return;
/* Create event when we start moving the mouse. */
if (!that.ev) {
/* Small deadzone so tiny click and drags aren't registered */
- if (Math.abs(that.event_start.x - e.clientX) < 10
- && Math.abs(that.event_start.y - e.clientY) < 5) { return; }
+ if (Math.abs(that.#event_start.x - e.clientX) < 10
+ && Math.abs(that.#event_start.y - e.clientY) < 5) { return; }
/* only allow start of dragging on background */
if (e.target !== this) return;
@@ -112,7 +141,7 @@ class EventCreator {
(e as HTMLElement).style.pointerEvents = "none";
}
- that.timeStart = round_time(pos_in(this, e), round_to);
+ that.#time_start = round_time(pos_in(this, e), round_to);
}
let time = round_time(pos_in(this, e), round_to);
@@ -136,8 +165,8 @@ class EventCreator {
/* ms */
let duration = container_end.valueOf() - container_start.valueOf();
- let start_in_duration = duration * Math.min(that.timeStart, time);
- let end_in_duration = duration * Math.max(that.timeStart, time);
+ let start_in_duration = duration * Math.min(that.#time_start, time);
+ let end_in_duration = duration * Math.max(that.#time_start, time);
/* Notice that these are converted to UTC, since the intervals are given
in utc, and I only really care about local time (which a specific local
@@ -158,6 +187,12 @@ class EventCreator {
}
}
+ /**
+ Event handler for `mouseup` events.
+
+ TODO callback?
+ TODO return value?
+ */
create_event_finisher(callback: ((ev: VEvent) => void)) {
let that = this;
return function create_event_up(_: MouseEvent) {
diff --git a/static/formatters.ts b/static/ts/formatters.ts
index e0018278..b5c55913 100644
--- a/static/formatters.ts
+++ b/static/ts/formatters.ts
@@ -1,3 +1,19 @@
+/**
+ * Formatting procedures used by some components.
+ *
+ * // TODO can we have a backref of every node containing `{@link formatters-proc}`?
+ *
+ * {@label FORMATTERS}
+ *
+ * Each procedure takes three arguments. The HTML-element which contents
+ * should be replaced, the VEvent containing all data, and the target
+ * value, as returned by {@linkcode vevent.VEvent.getProperty}.
+ *
+ * Also bound to the window object.
+ *
+ * @module
+ */
+
export {
format
}
@@ -16,6 +32,10 @@ declare global {
let formatters: Map<string, formatter>;
formatters = window.formatters = new Map();
+/**
+ * Checks if a specific formatter exists for the given key, and executes it.
+ * Defaults to 'default', and also runs that if the regular formatter throws.
+ */
async function format(targetElement: HTMLElement, data: VEvent, key: string): Promise<void> {
let d = data.getProperty(key);
if (!d) return
diff --git a/static/globals.ts b/static/ts/globals.ts
index 243e15e4..1cdf1733 100644
--- a/static/globals.ts
+++ b/static/ts/globals.ts
@@ -1,3 +1,10 @@
+/**
+ * Different variables and values which for different reasons needs to be
+ * global. Window Value's are those that are bound to the `window`
+ * context in JavaScript, so is really always available, no opt out.
+ * @module
+ */
+
export {
find_block,
vcal_objects, event_calendar_mapping
@@ -10,14 +17,38 @@ import { ComponentBlock } from './components/vevent-block'
import { v4 as uuid } from 'uuid'
import { setup_popup_element } from './components/popup-element'
+/**
+ * All VEvent objects on current page, indexed by their unique identifiers.
+ *
+ * A global object store.
+ *
+ * Also bound to the window object for easy access.
+ */
const vcal_objects: Map<uid, VEvent> = new Map;
+
+/**
+ * Mapping from VEvent unique identifier, to name of its calendar. Should
+ * probably not be global, so refrain from using it.
+ */
const event_calendar_mapping: Map<uid, string> = new Map;
declare global {
interface Window {
vcal_objects: Map<uid, VEvent>;
+ /**
+ * How the calendar is currently formatted. Should be set by the backend
+ * through a simple `script`-tag.
+ */
VIEW: 'month' | 'week';
+ /**
+ * However editing of events is enabled or not.
+ * Should be set by the backend through a simple `script`-tag.
+ */
EDIT_MODE: boolean;
+ /**
+ * Name of the calendar to assume when creating new events.
+ * Should be set by the backend through a simple `script`-tag.
+ */
default_calendar: string;
addNewEvent(): void;
@@ -45,6 +76,10 @@ window.addNewEvent = () => {
popup.maximize();
}
+/**
+ Find the calendar block in the inline view containing the VEvent identified
+ by the uid
+*/
function find_block(uid: uid): ComponentBlock | null {
let obj = vcal_objects.get(uid)
if (obj === undefined) {
diff --git a/static/jcal.ts b/static/ts/jcal.ts
index 41f33db4..feac297b 100644
--- a/static/jcal.ts
+++ b/static/ts/jcal.ts
@@ -1,8 +1,19 @@
+/**
+ Operations for working with jCal.
+
+ jCal is defined in RFC 7265, and is a JSON mapping of the iCalendar standard.
+*/
+
+
export { jcal_to_xcal }
import { xcal, ical_type, JCalProperty, JCal } from './types'
import { asList } from './lib'
+/**
+ * A document with the xcal namespace, and @code{icalendar} as its root
+ * element. Each child is a valid xcal representation of our JCal object.
+ */
function jcal_type_to_xcal(doc: Document, type: ical_type, value: any): Element {
let el = doc.createElementNS(xcal, type);
switch (type) {
@@ -157,6 +168,17 @@ function jcal_property_to_xcal_property(
}
+/**
+ Convert a jCal document into an xCal document.
+
+ @param jcals A list of jcal components. Most iCal formats supports multiple
+ "root" levels components. jCal might do it, which is why this parameter is
+ multi-valued.
+
+ @return A document note which is the root of an xCal document.
+ The root will be an icalendar tag, with each child getting its data from each
+ element of the input.
+ */
function jcal_to_xcal(...jcals: JCal[]): Document {
let doc = document.implementation.createDocument(xcal, 'icalendar');
for (let jcal of jcals) {
@@ -165,6 +187,18 @@ function jcal_to_xcal(...jcals: JCal[]): Document {
return doc;
}
+/**
+ Convert a single jCal entry into a single xCal entry.
+
+ @param doc
+ A Document element in the xcal namespace.
+
+ @param jcal
+ The object to convert
+
+ @return
+ A 1-to-1 mapping of the jCal object as xCal.
+ */
function jcal_to_xcal_inner(doc: Document, jcal: JCal) {
let [tagname, properties, components] = jcal;
diff --git a/static/lib.ts b/static/ts/lib.ts
index 2ef5b596..d503ac5d 100644
--- a/static/lib.ts
+++ b/static/ts/lib.ts
@@ -1,48 +1,112 @@
+/**
+ General procedures which in theory could be used anywhere.
+
+ Besides exporting the mentioned functions, this module also
+ extends some base classes.
+
+ @module
+ */
export {
makeElement, date_to_percent,
parseDate, gensym, to_local, to_boolean,
- asList, round_time
+ asList, round_time,
+ format_date,
}
/*
- General procedures which in theory could be used anywhere.
- */
-
-/*
* https://www.typescriptlang.org/docs/handbook/declaration-merging.html
*/
declare global {
interface Object {
+ /**
+ Introduce a format method on ALL objects
+
+ The default implementation simply stringifies the object, but this
+ allows any component to overwrite it, allowing for generic custom
+ formatting of data.
+
+ This also means that the format string is ignored for the default
+ implementation.
+
+ See `Data.prototype.format`.
+ */
format: (fmt: string) => string
}
+ /** HTMLElement extensions */
interface HTMLElement {
+ /**
+ "Private" property, storing the "true" add event listener. The
+ exposed addEventListener is later overwritten to also store a list of
+ which event listeners are added.
+ */
_addEventListener: (name: string, proc: (e: Event) => void) => void
- listeners: Map<string, ((e: Event) => void)[]>
+
+ /**
+ Contains all listeners added through `addEventListener`.
+
+ The keys are the same as to `addEventListener` ('load', 'mouseover',
+ ...)
+
+ Values are simply a list of all added listeners, probably in addition
+ order.
+ */
+ listeners: Map<string, ((e: Event) => void)[]>;
+
+ /**
+ Returns listeners.
+
+ TODO why does this exist?
+ */
getListeners: () => Map<string, ((e: Event) => void)[]>
}
interface Date {
+ /**
+ A proper date formatter for javascript.
+
+ See {@ref format_date} for details
+ */
format: (fmt: string) => string
+
+ /** Indicates if the date object is in UTC or local time. */
utc: boolean
+
+ /**
+ Indicates that the object only contains a date component.
+
+ This means that any time is ignored in most operations.
+ */
dateonly: boolean
// type: 'date' | 'date-time'
}
interface DOMTokenList {
+ /**
+ Searches a DOMTokenList for anything matching.
+
+ DOMTokenLists are returned by `.classList` and similar.
+
+ @return Returns the matching index, and the matched value,
+ or `undefined` if nothing was found.
+ */
find: (regex: string) => [number, string] | undefined
}
interface HTMLCollection {
+ /** Adds an iterator to HTMLCollections */
forEach: (proc: ((el: Element) => void)) => void
}
interface HTMLCollectionOf<T> {
+ /** Adds an iterator to HTMLCollections */
forEach: (proc: ((el: T) => void)) => void
}
}
+/** See interface above */
HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener;
+/** See interface above */
HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) {
if (!this.listeners) this.listeners = new Map
if (!this.listeners.get(name)) this.listeners.set(name, []);
@@ -50,6 +114,7 @@ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event)
this.listeners.get(name)!.push(proc);
return this._addEventListener(name, proc);
};
+/** See interface above */
HTMLElement.prototype.getListeners = function() {
return this.listeners;
}
@@ -70,6 +135,13 @@ HTMLElement.prototype.getListeners = function() {
century, due to how javascript works (...).
*/
+/**
+ * Takes a string `str`, which should be in ISO-8601 date-format, and
+ * returns a javascript Date object.
+ * Handles date-times, with and without seconds, trailing `Z' for
+ * time-zones, and dates without times.
+ * If no time is given the `dateonly` attribute is set to yes.
+ */
function parseDate(str: string): Date {
let year: number;
let month: number;
@@ -112,6 +184,11 @@ function parseDate(str: string): Date {
return date;
}
+/**
+ * Returns a Date object (which may be new) which is guaranteed in local time.
+ * This means that the `utc` field is `false`, and that
+ * `to_local(current_time())` should show what your wall-clock shows.
+ */
function to_local(date: Date): Date {
if (!date.utc) return date;
@@ -120,6 +197,27 @@ function to_local(date: Date): Date {
/* -------------------------------------------------- */
+/**
+ * Creates a new DOM element of type `name`, with all keys in
+ * `attr` transfered to it. For example, the equivalent of
+
+ * ```html
+ * <input type='number'/>
+ * ```
+
+ * would be
+
+ * ```js
+ * values.push(makeElement('input', {
+ * type: 'number',
+ * }));
+ * ```
+ *
+ * @param name HTML tagname
+ * @param attr Attributes which will be set on the created element.
+ * @param actualAttr Attributes which will be set on the created element,
+ * but through `el.setAttribute` instead of `el[key] =`...
+ */
function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement {
let element: HTMLElement = document.createElement(name);
for (let [key, value] of Object.entries(attr)) {
@@ -131,6 +229,21 @@ function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement {
return element;
}
+/**
+ Round clock time to closest interval.
+
+ @param time
+ The desired clock-time, in decimal time. So 12:30 would be given as 12.30.
+
+ @param fraction
+ The time interval to round to. To round to nearest half hour, give 0.5.
+
+ @example
+ ```js
+ > round_time(10.1, 15/60)
+ 10
+ ```
+ */
function round_time(time: number, fraction: number): number {
let scale = 1 / fraction;
return Math.round(time * scale) / scale;
@@ -142,14 +255,26 @@ function round_time(time: number, fraction: number): number {
Just doing (new Date()/(86400*1000)) would be nice, but there's no good
way to get the time in the current day.
*/
+/**
+ * Retuns how far along the date specified by `date` is, between 0
+ * and 100, where 00:00 maps to 0, and 23:59 to ~100.
+ */
function date_to_percent(date: Date): number /* in 0, 100 */ {
return (date.getHours() + (date.getMinutes() / 60)) * 100 / 24;
}
/* if only there was such a thing as a let to wrap around my lambda... */
/* js infix to not collide with stuff generated backend */
+/**
+ * Generates a new string which is (hopefully) globally unique.
+ * Compare with `gensym` from Lisp.
+ */
const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0)
+/**
+ * Ensures that `thing` is a list. Returning it outright if it
+ * already is one, otherwise wrapping it in a list.
+*/
function asList<T>(thing: Array<T> | T): Array<T> {
if (thing instanceof Array) {
return thing;
@@ -159,6 +284,16 @@ function asList<T>(thing: Array<T> | T): Array<T> {
}
+/**
+ Smartly converts a value into a boolean.
+
+ Booleans are returned as if,
+
+ Strings are parsed, mapping `'true'` onto `true`, `'false'` onto `false`,
+ empty strings onto `false`, and anything else onto `true`.
+
+ Anything else is left onto JavaScript to coerce a boolean.
+ */
function to_boolean(value: any): boolean {
switch (typeof value) {
case 'string':
@@ -182,12 +317,43 @@ function datepad(thing: number | string, width = 2): string {
return (thing + "").padStart(width, "0");
}
-function format_date(date: Date, str: string): string {
+/**
+ Format a date into a string.
+
+ @param date
+ The datetime to format
+
+ @param format
+ How the date should be converted into a string.
+
+ The format is similar to `strftime`, but with tilde (`~`) characters
+ instead of percent signs, to match how Scheme does it. Valid format
+ specifiers are:
+
+ | Fmt | Output | Width¹ |
+ |------|----------------------------------|--------|
+ | `~~` | Literal '~' | |
+ | `~Y` | Year | 4 |
+ | `~m` | Month number | 2 |
+ | `~d` | Day of month | 2 |
+ | `~H` | Hour | 2 |
+ | `~M` | Minute | 2 |
+ | `~S` | Second | 2 |
+ | `~Z` | 'Z' if date is UTC, otherwise '' | |
+ | `~L` | Converts date to local time² | |
+
+ - ¹ These fields will be left padded with zeroes to that width
+ - ² This forces the output to be in local time, possibly converting
+ timezone if needed. It then outputs nothing.
+ See {@link to_local `to_local`} for details.
+
+*/
+function format_date(date: Date, format: string): string {
let fmtmode = false;
let outstr = "";
- for (var i = 0; i < str.length; i++) {
+ for (var i = 0; i < format.length; i++) {
if (fmtmode) {
- switch (str[i]) {
+ switch (format[i]) {
/* Moves the date into local time. */
case 'L': date = to_local(date); break;
case 'Y': outstr += datepad(date.getFullYear(), 4); break;
@@ -199,10 +365,10 @@ function format_date(date: Date, str: string): string {
case 'Z': if (date.utc) outstr += 'Z'; break;
}
fmtmode = false;
- } else if (str[i] == '~') {
+ } else if (format[i] == '~') {
fmtmode = true;
} else {
- outstr += str[i];
+ outstr += format[i];
}
}
return outstr;
diff --git a/static/script.ts b/static/ts/script.ts
index 9238d834..9238d834 100644
--- a/static/script.ts
+++ b/static/ts/script.ts
diff --git a/static/server_connect.ts b/static/ts/server_connect.ts
index 29f5bab2..ad4bc9eb 100644
--- a/static/server_connect.ts
+++ b/static/ts/server_connect.ts
@@ -1,3 +1,9 @@
+/**
+ * Procedures for interfacing with the backend server.
+ *
+ * @module
+ */
+
export { create_event, remove_event }
import { jcal_to_xcal } from './jcal'
@@ -6,6 +12,15 @@ import { uid } from './types'
import { vcal_objects } from './globals'
import { PopupElement } from './components/popup-element'
+/**
+ * Requests that the server permanently remove the event with the given
+ * unique id from its persistant storage.
+ *
+ * If the server responds with a success also delete it from our local
+ * store (`vcal_objects`).
+ *
+ * // TODO link to our backend flow here
+*/
async function remove_event(uid: uid) {
let element = vcal_objects.get(uid);
if (!element) {
@@ -55,6 +70,13 @@ async function remove_event(uid: uid) {
// ];
// }
+/**
+ * Packs up the given event and sends it to the server to either be
+ * created, or simply be updated in the persistant database.
+
+ * Also does some minor updates registered components, to show that the
+ * event is actually created.
+*/
async function create_event(event: VEvent) {
// let xml = event.getElementsByTagName("icalendar")[0].outerHTML
diff --git a/static/types.ts b/static/ts/types.ts
index 64e2c709..a01f6672 100644
--- a/static/types.ts
+++ b/static/ts/types.ts
@@ -1,3 +1,8 @@
+/**
+ * Collection of type information for calendar data.
+ * @module
+ */
+
export {
ical_type,
valid_input_types,
@@ -6,6 +11,8 @@ export {
ChangeLogEntry
}
+
+/** Name of all valid icalendar types. */
let all_types = [
'text',
'uri',
@@ -23,6 +30,7 @@ let all_types = [
]
+/** The union of all elements in `all_types`. */
type ical_type
= 'text'
| 'uri'
@@ -39,6 +47,10 @@ type ical_type
| 'boolean'
| 'unknown'
+/**
+ * All known names properties (top level keys) can have.
+ * Such as "calscale", "dtstart", ...
+ */
let property_names = [
'calscale', 'method', 'prodid', 'version', 'attach', 'categories',
'class', 'comment', 'description', 'geo', 'location', 'percent-complete',
@@ -50,6 +62,15 @@ let property_names = [
];
+/**
+ * Which property fields each component can hold.
+ *
+ * ```json
+ * { 'VCALENDAR': ['PRODID', 'VERSION', 'CALSCALE', 'METHOD'],
+ * ...
+ * }
+ * ```
+ */
let valid_fields: Map<string, string[]> = new Map([
['VCALENDAR', ['PRODID', 'VERSION', 'CALSCALE', 'METHOD']],
['VEVENT', ['DTSTAMP', 'UID', 'DTSTART', 'CLASS', 'CREATED',
@@ -83,6 +104,12 @@ let valid_fields: Map<string, string[]> = new Map([
valid_fields.set('DAYLIGHT', valid_fields.get('STANDARD')!);
+/**
+ All registered property types for VComponents.
+
+ Note that only some of these are valid for each type of component (VCALENDAR,
+ VEVENT, ...), and that they all support both iana and `x-` extensions.
+ */
type known_ical_types
= 'ACTION'
| 'ATTACH'
@@ -130,6 +157,18 @@ type known_ical_types
| 'URL'
| 'VERSION'
+/**
+ * Which types are valid to store under each property.
+ * If multiple values are an option for that property, then
+ * the list of possibilities will contain a sub-list (see example).
+ *
+ * ```json
+ * { 'DTSTART': ['date', 'date-time'],
+ * 'CATEGORIES': [['text']],
+ * ...
+ * }
+ * ```
+ */
let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
new Map([
['ACTION', ['text']], // AUDIO|DISPLAY|EMAIL|*other*
@@ -184,25 +223,60 @@ let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
// type JCalLine {
// }
-type tagname = 'vevent' | string
-
+/** The UID of a VEvent, to make declarations clearer. */
type uid = string
/* TODO is this type correct?
What really are valid values for any? Does that depend on ical_type? Why is the tail a list?
What really is the type for the parameter map?
*/
+/* TODO link to RFC 7265 (jCal) */
+/**
+ * Alias for a record consisting of
+ * - the name of the type, as a string
+ * - all parameters of the object, as a `Record<String, any>`
+ * - An `ical_type` value, noting the type of the final field(s)
+ * - and one or more values of the type specified by the third field.
+ */
type JCalProperty
= [string, Record<string, any>, ical_type, any]
| [string, Record<string, any>, ical_type, ...any[]]
-type JCal = [tagname, JCalProperty[], JCal[]]
+/**
+ Base type for JCal documents.
+
+ Each VComponent in a JCal document is of this form.
+ - The first element is the components type
+ ('vevent', 'vcalendar', ...), in all lower case
+ - The second element is is all properties directly
+ on the component.
+ - The third element is a list of all children.
+*/
+type JCal = [string, JCalProperty[], JCal[]]
+
+/** The xml namespace name for xcalendar */
const xcal = "urn:ietf:params:xml:ns:icalendar-2.0";
+/**
+ An entry into a changelog.
+
+ This is primarily used by VEvent, to track what has happened during a
+ session.
+ */
interface ChangeLogEntry {
+ /**
+ Type of change
+
+ 'property' is used for changes to properties.
+
+ 'calendar' is used when the containing calendar of a VEVENT is changed
+ */
type: 'calendar' | 'property',
+ /** The name of the filed changed */
name: string,
+ /** The previous value, `null` if just created */
from: string | null,
+ /** The new value, `null` if removed */
to: string | null,
}
diff --git a/static/vevent.ts b/static/ts/vevent.ts
index f3606f70..6aaa6984 100644
--- a/static/vevent.ts
+++ b/static/ts/vevent.ts
@@ -2,28 +2,49 @@ import { ical_type, valid_input_types, JCal, JCalProperty, ChangeLogEntry } from
import { parseDate } from './lib'
export {
- VEvent, xml_to_vcal,
RecurrenceRule,
+ Redrawable,
+ VEvent,
+ VEventValue,
+ freqType,
isRedrawable,
+ list_values,
+ weekday,
+ xml_to_vcal,
}
-/* Something which can be redrawn */
+/** Something which can be redrawn */
interface Redrawable extends HTMLElement {
+ /** Method which will be called upon a redraw request. */
redraw(data: VEvent): void
}
+/** Checks if the given element is an instance of Redrawable. */
function isRedrawable(x: HTMLElement): x is Redrawable {
return 'redraw' in x
}
+/**
+ A single value from a vcomponent.
+ This is basically a type tagged tuple, with an optional map of parameters.
+*/
class VEventValue {
+ /** The value type of the contained value. */
type: ical_type
- /* value should NEVER be a list, since multi-valued properties should
- be split into multiple VEventValue objects! */
+ /**
+ The actual value.
+
+ Should NEVER be a list, since those are coded as
+ lists of `VEventValue`:s in `Vevent.properties`
+ */
value: any
+
+ /**
+ VComponent parameters attached to the value.
+ */
parameters: Map<string, any>
constructor(type: ical_type, value: any, parameters = new Map) {
@@ -32,6 +53,10 @@ class VEventValue {
this.parameters = parameters;
}
+ /**
+ * The return value is *almost* a `JCalProperty`, just without
+ * the field name.
+ */
to_jcal(): [Record<string, any>, ical_type, any] {
let value;
let v = this.value;
@@ -77,40 +102,71 @@ class VEventValue {
}
/* TODO maybe ... */
-class VEventDuration extends VEventValue {
-}
+// class VEventDuration extends VEventValue {
+// }
+/** VComponent properties which contain lists */
type list_values
= 'categories' | 'resources' | 'freebusy' | 'exdate' | 'rdate'
| 'CATEGORIES' | 'RESOURCES' | 'FREEBUSY' | 'EXDATE' | 'RDATE';
-/*
- Abstract representation of a calendar event (or similar).
-All "live" calendar data in the frontend should live in an object of this type.
+/**
+ This class is the data container for the underlying VEVENT objects in the
+ backend calendar files. They also keep track on all Web Components which
+ wants to render part of the event.
+
+ Note that despite the name this component isn't limited to VEVENT:s, but is
+ used for all VComponents in the tree. This means that even calendars and
+ alarms can be instances of this class.
+
+ Property access is done through `getProperty` and `setProperty` (properties
+ are things such as 'SUMMARY', 'DTSTART', ...)
*/
class VEvent {
- /* Calendar properties */
+ /**
+ Properties bound directly on this object.
+
+ These are things such as 'DTSTART', 'SUMMARY', ...
+ */
private properties: Map<string, VEventValue | VEventValue[]>
- /* Children (such as alarms for events) */
+ /**
+ Children to this component.
+
+ Valid children depends on the type. For example, for calendars this is
+ primarily events, while for events it's alarm components
+ */
components: VEvent[]
- /* HTMLElements which wants to be redrawn when this object changes.
- Elements can be registered with the @code{register} method.
+ /**
+ HTMLElements which wants to be redrawn when this object changes.
+ Elements can be registered with the `register` method.
*/
registered: Redrawable[]
#calendar: string | null = null;
+ /**
+ * Every write through getProperty gets logged here, and can be
+ * consumed. Hopefully this will one day turn into an undo system.
+ * TODO ref ChangeLogEntry.
+ */
#changelog: ChangeLogEntry[] = []
- /* Iterator instead of direct return to ensure the receiver doesn't
- modify the array */
+ /**
+ The changelog for this component.
+
+ An iterator is returned rather than an array, to ensure modifications are
+ impossible.
+ */
get changelog(): IterableIterator<[number, ChangeLogEntry]> {
return this.#changelog.entries();
}
+ /**
+ Add an entry to the changelog.
+ */
addlog(entry: ChangeLogEntry) {
let len = this.#changelog.length
let last = this.#changelog[len - 1]
@@ -135,6 +191,17 @@ class VEvent {
}
}
+ /**
+ Construct a new Component.
+
+ @param properties
+ Initial properties for the component
+
+ @param components
+ Initial children for the component
+
+ TODO where is the type of the component registered?
+ */
constructor(
properties: Map<string, VEventValue | VEventValue[]> = new Map(),
components: VEvent[] = []
@@ -156,6 +223,19 @@ class VEvent {
// getProperty(key: 'categories'): string[] | undefined
+ /**
+ * Returns the value of the given property if set, or undefined otherwise.
+ *
+ * For the keys
+ *
+ * - `'CATEGORIES'`,
+ * - `'RESOURCES'`,
+ * - `'FREEBUSY'`,
+ * - `'EXDATE'`, and
+ * - `'RDATE'`
+ *
+ * instead returns a list list of values.
+ */
getProperty(key: string): any | any[] | undefined {
key = key.toUpperCase()
let e = this.properties.get(key);
@@ -166,11 +246,12 @@ class VEvent {
return e.value;
}
+ /** Returns an iterator of all our properties. */
get boundProperties(): IterableIterator<string> {
return this.properties.keys()
}
- private setPropertyInternal(key: string, value: any, type?: ical_type) {
+ #setPropertyInternal(key: string, value: any, type?: ical_type) {
function resolve_type(key: string, type?: ical_type): ical_type {
if (type) {
return type;
@@ -228,24 +309,40 @@ class VEvent {
setProperty(key: list_values, value: any[], type?: ical_type): void;
setProperty(key: string, value: any, type?: ical_type): void;
+ /**
+ * Sets the given property to the given value. If type is given it's
+ * stored alongside the value, possibly updating what is already
+ * there. Do however note that no validation between the given type and
+ * the type of the value is done.
+ *
+ * `value` may also be a list, but should only be so for the keys
+ * mentioned in `getProperty`.
+ *
+ * After the value is set, `redraw` is called on all registered
+ * objects, notifying them of the change.
+ */
setProperty(key: string, value: any, type?: ical_type) {
- this.setPropertyInternal(key, value, type);
+ this.#setPropertyInternal(key, value, type);
for (let el of this.registered) {
el.redraw(this);
}
}
+ /**
+ * Equivalent to running `setProperty` for each element in the input
+ * list, but only calls `redraw` once at the end.
+ */
setProperties(pairs: [string, any, ical_type?][]) {
for (let pair of pairs) {
- this.setPropertyInternal(...pair);
+ this.#setPropertyInternal(...pair);
}
for (let el of this.registered) {
el.redraw(this);
}
}
-
+ /** The name of the calendar which this event belongs to. */
set calendar(calendar: string | null) {
this.addlog({
type: 'calendar',
@@ -259,18 +356,29 @@ class VEvent {
}
}
+ /**
+ Get the name of the containing calendar for this component.
+
+ This is only valid for VEVENT components (I think)
+ */
get calendar(): string | null {
return this.#calendar;
}
+ /**
+ * Register something redrawable, which will be notified whenever this
+ * VEvents data is updated.
+ */
register(htmlNode: Redrawable) {
this.registered.push(htmlNode);
}
+ /** Stop recieving redraw events on the given component. */
unregister(htmlNode: Redrawable) {
this.registered = this.registered.filter(node => node !== htmlNode)
}
+ /** Converts the object to JCal data. */
to_jcal(): JCal {
let out_properties: JCalProperty[] = []
console.log(this.properties);
@@ -300,6 +408,7 @@ class VEvent {
}
}
+/** Helper procedure when converting xml to vcal */
function make_vevent_value(value_tag: Element): VEventValue {
/* TODO parameters */
return new VEventValue(
@@ -313,25 +422,49 @@ function make_vevent_value(value_tag: Element): VEventValue {
+/** Different frequency internals for recurrence rules. */
type freqType = 'SECONDLY' | 'MINUTELY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'
+
+/** Alternatives for when a week start, for recurrence rules */
type weekday = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU'
+/**
+ A recurrence rule.
+
+ The basic semantics of this class is borrowed from RFC 5545, and maps 1-to-1
+ on those instances. See individual fields for mappings.
+ */
class RecurrenceRule {
+ /** The type of frequency of this rule */
freq?: freqType
+ /** Final instance of this rule. */
until?: Date
+ /** Maximum number of recurrences for this rule */
count?: number
+ /** The multiplier to `freq` */
interval?: number
+ /** Which seconds are relevant for this rule */
bysecond?: number[]
+ /** Which minutes are relevant for this rule */
byminute?: number[]
+ /** Which hours are relevant for this rule */
byhour?: number[]
+ /** Which weekday or weekday offsets are relevant for this rule */
byday?: (weekday | [number, weekday])[]
+ /** Which month days are relevant for this rule */
bymonthday?: number[]
+ /** Which year days are relevant for this rule */
byyearday?: number[]
+ /** Which week number are relevant for this rule */
byweekno?: number[]
+ /** Which months relevant for this rule (interval 1-12) */
bymonth?: number[]
+ /** TODO see the RFC */
bysetpos?: number[]
+ /** Which day the week start, according to this rule */
wkst?: weekday
+ /** Converts ourselves to JCal data. */
to_jcal(): Record<string, any> {
let obj: any = {}
if (this.freq) obj['freq'] = this.freq;
@@ -368,6 +501,7 @@ class RecurrenceRule {
}
}
+/** Parse a XCAL recurrence rule into a RecurrenceRule object. */
function xml_to_recurrence_rule(xml: Element): RecurrenceRule {
let rr = new RecurrenceRule;
@@ -507,6 +641,7 @@ function make_vevent_value_(value_tag: Element): string | boolean | Date | numbe
}
}
+/** Parse a complete XCAL object into a JS VEvent object. */
function xml_to_vcal(xml: Element): VEvent {
/* xml MUST have a VEVENT (or equivalent) as its root */
let properties = xml.getElementsByTagName('properties')[0];
diff --git a/static/tsconfig.json b/static/tsconfig.json
index 82359e01..352c8ab5 100644
--- a/static/tsconfig.json
+++ b/static/tsconfig.json
@@ -1,34 +1,43 @@
{
- "compilerOptions": {
- /* Visit https://aka.ms/tsconfig.json to read more about this file */
-
- /* Projects */
-
- /* Language and Environment */
- "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
-
- /* Modules */
- "module": "CommonJS", /* Specify what module code is generated. */
-
- /* JavaScript Support */
- "allowJs": false, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
-
- /* Emit */
- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- "newLine": "lf", /* Set the newline character for emitting files. */
- "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
-
- /* Interop Constraints */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
-
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
-
- /* Completeness */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- }
-}
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+ /* Projects */
+ /* Language and Environment */
+ "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ /* Modules */
+ "module": "CommonJS", /* Specify what module code is generated. */ /* JavaScript Support */
+ "allowJs": false, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ /* Emit */// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ "newLine": "lf", /* Set the newline character for emitting files. */
+ "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ /* Interop Constraints */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */ /* Completeness */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ "include": [
+ "**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "**/*.spec.ts"
+ ],
+ "typedocOptions": {
+ "entryPointStrategy": "expand",
+ "entryPoints": [
+ "./ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ],
+ "plugin": [
+ "@mxssfd/typedoc-theme"
+ ],
+ "theme": "my-theme",
+ "validation": {
+ "notDocumented": true
+ },
+ "out": "docs"
+ }
+} \ No newline at end of file
diff --git a/tests/run-tests.scm b/tests/run-tests.scm
index 6c6ff95a..4b6d2773 100755
--- a/tests/run-tests.scm
+++ b/tests/run-tests.scm
@@ -1,9 +1,8 @@
#!/usr/bin/env bash
# -*- mode: scheme; geiser-scheme-implementation: guile -*-
-here=$(dirname $(realpath $0))
-
-. "$(dirname "$here")/env"
+root=$(dirname "$(dirname "$(realpath "$0")")")
+eval "$(env __PRINT_ENVIRONMENT=1 ${root}/calp)"
if [ "$DEBUG" = '' ]; then
exec $GUILE -s "$0" "$@"
@@ -12,6 +11,10 @@ else
fi
!#
+(unless (getenv "CALP_TEST_ENVIRONMENT")
+ (format (current-error-port) "Not running in test environment, abandoning~%")
+ (exit 1))
+
(format #t "current-filename = ~s~%" (current-filename))
(define here (dirname (current-filename)))
diff --git a/tests/test/let-env.scm b/tests/test/let-env.scm
new file mode 100644
index 00000000..a989776a
--- /dev/null
+++ b/tests/test/let-env.scm
@@ -0,0 +1,48 @@
+(define-module (test let-env)
+ :use-module (srfi srfi-64)
+ :use-module (srfi srfi-64 test-error)
+ :use-module (srfi srfi-88)
+ :use-module ((guile) :select (setenv getenv))
+ :use-module ((hnh util env) :select (let-env)))
+
+(setenv "CALP_TEST_ENV" "1")
+
+(test-equal
+ "Ensure we have set value beforehand"
+ "1"
+ (getenv "CALP_TEST_ENV"))
+
+(let-env
+ ((CALP_TEST_ENV "2"))
+ (test-equal
+ "Test our local override"
+ "2"
+ (getenv "CALP_TEST_ENV")))
+
+(test-equal
+ "Test that we have returned"
+ "1"
+ (getenv "CALP_TEST_ENV"))
+
+(catch 'test-error
+ (lambda ()
+ (let-env
+ ((CALP_TEST_ENV "2"))
+ (test-equal
+ "Test our local override again"
+ "2"
+ (getenv "CALP_TEST_ENV"))
+ (throw 'test-error)))
+ list)
+
+(test-equal
+ "Test restoration after non-local exit"
+ "1"
+ (getenv "CALP_TEST_ENV"))
+
+
+(test-group "Unsetting environment"
+ (setenv "TEST" "A")
+ (let-env ((TEST #f))
+ (test-assert (not (getenv "TEST"))))
+ (test-equal "A" (getenv "TEST")))
diff --git a/tests/test/srfi-41-util.scm b/tests/test/srfi-41-util.scm
index ff0e3cce..9a753b03 100644
--- a/tests/test/srfi-41-util.scm
+++ b/tests/test/srfi-41-util.scm
@@ -8,6 +8,7 @@
:use-module (srfi srfi-88)
:use-module (srfi srfi-41 util)
:use-module (srfi srfi-41)
+ :use-module ((srfi srfi-1) :select (circular-list))
:use-module ((ice-9 sandbox) :select (call-with-time-limit)))
(test-equal "Finite stream"
@@ -86,3 +87,22 @@
(test-equal "time limited stream"
'(1 2 3)
(stream->list strm))))
+
+
+(test-group "stream-split-by"
+ (let ((hello-chars-stream (stream-unfold
+ car
+ (const #t)
+ cdr
+ (apply circular-list
+ (string->list "Hello ")))))
+ (test-equal "Check that test list looks as expected"
+ (string->list "Hello Hell")
+ (stream->list 10 hello-chars-stream))
+ (test-equal "Check that it splits correctly"
+ '("Hello " "Hello " "Hello ")
+ (stream->list
+ 3
+ (stream-map list->string
+ (stream-split-by (lambda (c) (char=? c #\space))
+ hello-chars-stream))))))
diff --git a/tests/test/util.scm b/tests/test/util.scm
new file mode 100644
index 00000000..bdd6e98e
--- /dev/null
+++ b/tests/test/util.scm
@@ -0,0 +1,379 @@
+;;; Commentary:
+;; Checks some prodecuders from (hnh util)
+;;; Code:
+
+(define-module (test util)
+ :use-module (srfi srfi-64)
+ :use-module (srfi srfi-64 test-error)
+ :use-module (srfi srfi-88)
+ :use-module (srfi srfi-1)
+ :use-module (hnh util)
+ :use-module (hnh util env)
+ :use-module ((hnh util path)
+ :select (path-append
+ path-split
+ file-hidden?
+ realpath
+ relative-to
+ filename-extension)))
+
+(test-group "Conditionals"
+ (test-equal "when"
+ 1 (when #t 1))
+
+ (test-equal "'() when #f"
+ '() (when #f 1))
+
+ (test-equal "unless"
+ 1 (unless #f 1))
+
+ (test-equal "'() unless #t"
+ '() (unless #t 1))
+
+ (test-equal "awhen it"
+ '(3 4 5)
+ (awhen (memv 2 '(1 2 3 4 5))
+ (cdr it)))
+
+ (test-equal "awhen not"
+ '()
+ (awhen (memv 0 '(1 2 3 4 5))
+ (cdr it))))
+
+(test-group "for"
+ (test-equal "for simple"
+ (iota 10)
+ (for x in (iota 10)
+ x))
+
+ (test-equal "for matching"
+ (iota 12)
+ (for (x c) in (zip (iota 12) (string->list "Hello, World"))
+ x))
+
+ (test-equal "for with improper list elements"
+ `(3 7)
+ (for (a . b) in '((1 . 2) (3 . 4))
+ (+ a b)))
+
+ (test-equal "for with longer improper list elements"
+ '(1 2 4)
+ (for (a b . c) in '((1 -1 . 1) (2 -2 . 2) (4 -4 . 4))
+ (* c (+ 1 a b))))
+
+ (test-equal "for break"
+ 'x
+ (for x in (iota 10)
+ (break 'x)
+ (test-assert "This should never happen" #f)))
+
+ (test-equal "for continue"
+ '(x #f 2)
+ (for x in (iota 3)
+ (case x
+ ((0)
+ (continue 'x)
+ (test-assert "Continue with value failed" #f))
+ ((1)
+ (continue)
+ (test-assert "Continue without value failed" #f))
+ (else x)))))
+
+(test-equal "procedure label"
+ 120
+ ((label factorial (lambda (n)
+ (if (zero? n)
+ 1 (* n (factorial (1- n))))))
+ 5))
+
+;; we can't test if sort*! destroys the list, since its only /allowed/ to do it,
+;; not required.
+(test-equal "sort*!"
+ '("a" "Hello" "Assparagus")
+ (sort*! '("Hello" "a" "Assparagus")
+ < string-length))
+
+(test-assert "not equal"
+ (!= 1 2))
+
+(test-equal "Take to"
+ '() (take-to '() 5))
+
+(test-equal "Enumerate"
+ '((0 #\H) (1 #\e) (2 #\l) (3 #\l) (4 #\o) (5 #\,) (6 #\space) (7 #\W) (8 #\o) (9 #\r) (10 #\l) (11 #\d) (12 #\!))
+ (enumerate (string->list "Hello, World!")))
+
+(test-equal "unval first"
+ 1
+ ((unval (lambda () (values 1 2 3)))))
+
+(test-equal "unval other"
+ 2
+ ((unval car+cdr 1)
+ (cons 1 2)))
+
+(test-group "Flatten"
+ (test-equal "flatten already flat"
+ (iota 10)
+ (flatten (iota 10)))
+
+ (test-equal "flatten really deep"
+ '(1)
+ (flatten '(((((((((((((((1)))))))))))))))))
+
+ (test-equal "flatten mixed"
+ '(1 2 3 4 5)
+ (flatten '((((((1(((((2((((3))))))4))))))))5))))
+
+;; TODO test let-lazy
+
+(test-group "map/dotted"
+ (test-equal "map/dotted without dot"
+ '(1 2 3 4)
+ (map/dotted 1+ '(0 1 2 3)))
+
+ (test-equal "map/dotted with dot"
+ '(1 2 3 . 4)
+ (map/dotted 1+ '(0 1 2 . 3)))
+
+ (test-equal "map/dotted direct value"
+ 1 (map/dotted 1+ 0)))
+
+
+(test-group "Arrows"
+ (test-equal "->" 9 (-> 1 (+ 2) (* 3)))
+ (test-equal "-> order dependant" -1 (-> 1 (- 2)))
+ (test-equal "->> order dependant" 1 (->> 1 (- 2))))
+
+;; TODO set and set->
+
+;; TODO and=>>
+
+(test-equal "Group"
+ '((0 1) (2 3) (4 5) (6 7) (8 9))
+ (group (iota 10) 2))
+
+;; TODO test failure when grouping isn't possible?
+
+(test-group "Associations"
+ (test-equal "assoc-ref-all" '(1 3) (assoc-ref-all '((a . 1) (b . 2) (a . 3)) 'a))
+ (test-equal "assq-ref-all" '(1 3) (assq-ref-all '((a . 1) (b . 2) (a . 3)) 'a))
+ (test-equal "assv-ref-all "'(1 3) (assv-ref-all '((a . 1) (b . 2) (a . 3)) 'a))
+
+ ;; TODO assq-limit ?
+
+ (test-equal "assq merge"
+ '((k 2 1) (v 2))
+ (assq-merge '((k 1) (v 2)) '((k 2))))
+
+ (test-equal "kvlist->assq"
+ '((a . 1) (b . 2))
+ (kvlist->assq '(a: 1 b: 2)))
+
+
+ (test-equal "kvlist->assq repeated key"
+ '((a . 1) (b . 2) (a . 3))
+ (kvlist->assq '(a: 1 b: 2 a: 3))))
+
+(test-equal "vector-last"
+ 1 (vector-last #(0 2 3 1)))
+
+;; TODO test catch*
+
+(test-equal
+ "Filter sorted"
+ '(3 4 5)
+ (filter-sorted (lambda (x) (<= 3 x 5)) (iota 10)))
+
+(test-equal
+ "set/r! = single"
+ #f
+ (let ((x #t)) (set/r! x = not)))
+
+(test-error
+ 'syntax-error
+ (test-read-eval-string "(set/r! x err not)"))
+
+(test-group "Find extremes"
+ (call-with-values
+ (lambda () (find-min (iota 10)))
+ (lambda (extreme rest)
+ (test-equal "Found correct minimum" 0 extreme)
+ (test-equal
+ "Removed \"something\" from the set"
+ 9
+ (length rest))))
+
+ (call-with-values
+ (lambda ()
+ (find-max
+ '("Hello" "Test" "Something long")
+ string-length))
+ (lambda (extreme rest)
+ (test-equal
+ "Found the longest string"
+ "Something long"
+ extreme)
+ (test-equal "Removed the string" 2 (length rest))
+ (test-assert
+ "Other members left 1"
+ (member "Hello" rest))
+ (test-assert
+ "Other members left 2"
+ (member "Test" rest))))
+
+ (test-error 'wrong-type-arg (find-extreme '())))
+
+(test-group "Span upto"
+ (call-with-values
+ (lambda ()
+ (span-upto
+ 2
+ char-numeric?
+ (string->list "123456")))
+ (lambda (head tail)
+ (test-equal '(#\1 #\2) head)
+ (test-equal '(#\3 #\4 #\5 #\6) tail)))
+
+ (call-with-values
+ (lambda ()
+ (span-upto
+ 2
+ char-numeric?
+ (string->list "H123456")))
+ (lambda (head tail)
+ (test-equal '() head)
+ (test-equal '(#\H #\1 #\2 #\3 #\4 #\5 #\6) tail))))
+
+(test-group "Begin1"
+ (let ((value #f))
+ (test-equal
+ "begin1 return value"
+ "Hello"
+ (begin1 "Hello" (set! value "World")))
+ (test-equal "begin1 side effects" "World" value))
+
+ (let ((x 1))
+ (test-eqv "begin1 set! after return"
+ 1 (begin1 x (set! x 10)))
+ (test-eqv "Updates value"
+ 10 x)))
+
+(test-equal 0 (iterate 1- zero? 10))
+
+(test-equal "5" (->string 5))
+
+(test-equal "5" (->string "5"))
+
+(test-group "Path operations"
+ (test-equal
+ "no slashes"
+ "home/user"
+ (path-append "home" "user"))
+
+ (test-equal
+ "no slashes, absolute"
+ "/home/user"
+ (path-append "" "home" "user"))
+
+ (test-equal
+ "slashes in one component, absolute"
+ "/home/user"
+ (path-append "" "/home/" "user"))
+
+ (test-equal
+ "slashes in one component, absolute due to first"
+ "/home/user"
+ (path-append "/home/" "user"))
+
+ (test-equal
+ "Slashes in both"
+ "home/user"
+ (path-append "home/" "/user"))
+
+ (test-equal "root" "/" (path-append ""))
+
+ (test-equal
+ '("usr" "lib" "test")
+ (path-split "usr/lib/test"))
+
+ (test-equal
+ '("usr" "lib" "test")
+ (path-split "usr/lib/test/"))
+
+ (test-equal
+ '("" "usr" "lib" "test")
+ (path-split "/usr/lib/test"))
+
+ (test-equal
+ '("" "usr" "lib" "test")
+ (path-split "//usr////lib/test"))
+
+ (test-assert (file-hidden? ".just-filename"))
+ (test-assert (file-hidden? "/path/to/.hidden"))
+ (test-assert (not (file-hidden? "/visible/.in/hidden")))
+ (test-assert (not (file-hidden? "")))
+
+ ;; TODO test realpath with .. and similar
+
+ (test-equal "Realpath for path fragment"
+ "/home/hugo"
+ (with-working-directory
+ "/home"
+ (lambda () (realpath "hugo"))))
+
+ (test-equal "Realpath for already absolute path"
+ "/home/hugo"
+ (with-working-directory
+ "/tmp"
+ (lambda () (realpath "/home/hugo"))))
+
+
+ (test-group "Relative to"
+
+ (test-group "With relative child"
+ (test-equal "/some/path" (relative-to "/some" "path")))
+
+ ;; Relative parent just adds (getcwd) to start of parent,
+ ;; but this is "hard" to test.
+ ;; (test-group "With relative parent")
+
+ (test-group "With absolute child"
+ (test-error 'misc-error (relative-to "" "/some/path"))
+ (test-equal "some/path" (relative-to "/" "/some/path"))
+ (test-group "Without trailing slashes"
+ (test-equal "path" (relative-to "/some" "/some/path"))
+ (test-equal "../path" (relative-to "/some" "/other/path")))
+ (test-group "With trailing slashes"
+ (test-equal "path" (relative-to "/some" "/some/path/"))
+ (test-equal "../path" (relative-to "/some" "/other/path/"))))
+
+ (test-equal "/a/b" (relative-to "/a/b/c" "/a/b"))
+
+ )
+
+
+ (test-equal "Extension of simple file"
+ "txt" (filename-extension "file.txt"))
+
+ (test-equal "Extension of file with directory"
+ "txt" (filename-extension "/direcotry/file.txt"))
+
+ (test-equal "Extension of file with multiple"
+ "gz" (filename-extension "filename.tar.gz"))
+
+ (test-equal "Filename extension when none is present"
+ "" (filename-extension "filename"))
+
+ (test-equal "Filename extension when none is present, but directory has"
+ "" (filename-extension "config.d/filename"))
+
+ (test-equal "Filename extension of directory"
+ "d" (filename-extension "config.d/"))
+
+
+ (test-equal "Extension of hidden file"
+ "sh" (filename-extension ".bashrc.sh"))
+
+ (test-equal "Extension of hidden file without extension"
+ "bashrc" (filename-extension ".bashrc")))
diff --git a/tests/test/uuid.scm b/tests/test/uuid.scm
index b73db5f4..1cedb59e 100644
--- a/tests/test/uuid.scm
+++ b/tests/test/uuid.scm
@@ -6,13 +6,6 @@
(test-equal "UUIDv4 fixed seed"
- (let ((version (version)))
- (cond ((string=? version "2.2.7")
- "d19c9347-9a85-4432-a876-5fb9c0d24d2b")
- ((string=? version "3.0.9")
- "d19c9347-9a85-4432-a876-5fb9c0d24d2b")
- (else
- "Randomness isn't stable between guile versions")))
- (begin
- (parameterize ((seed (seed->random-state 0)))
- (uuid-v4))))
+ "d19c9347-9a85-4432-a876-5fb9c0d24d2b"
+ (parameterize ((seed (seed->random-state 0)))
+ (uuid-v4)))
diff --git a/tests/test/xdg-basedir.scm b/tests/test/xdg-basedir.scm
new file mode 100644
index 00000000..682c1347
--- /dev/null
+++ b/tests/test/xdg-basedir.scm
@@ -0,0 +1,58 @@
+(define-module (test xdg-basedir)
+ :use-module (srfi srfi-64)
+ :use-module ((xdg basedir) :prefix xdg-)
+ :use-module (srfi srfi-88)
+ :use-module ((hnh util env) :select (let-env))
+ )
+
+
+(let-env ((HOME "/home/user")
+ (XDG_DATA_HOME #f)
+ (XDG_CONFIG_HOME #f)
+ (XDG_STATE_HOME #f)
+ (XDG_DATA_DIRS #f)
+ (XDG_CONFIG_DIRS #f)
+ (XDG_CACHE_HOME #f)
+ (XDG_RUNTIME_DIR #f))
+ (test-group "Defaults"
+ (test-equal "XDG_DATA_HOME" "/home/user/.local/share"
+ (xdg-data-home))
+ (test-equal "XDG_CONFIG_HOME" "/home/user/.config"
+ (xdg-config-home))
+ (test-equal "XDG_STATE_HOME" "/home/user/.local/state"
+ (xdg-state-home))
+ (test-equal "XDG_DATA_DIRS" (xdg-data-dirs)
+ '("/usr/local/share" "/usr/share"))
+ (test-equal "XDG_CONFIG_DIRS" '("/etc/xdg")
+ (xdg-config-dirs))
+ (test-equal "XDG_CACHE_HOME" "/home/user/.cache"
+ (xdg-cache-home))
+ (let ((warning
+ (with-error-to-string
+ (lambda ()
+ (test-equal "XDG_RUNTIME_DIR"
+ "/tmp" (xdg-runtime-dir))))))
+ (test-assert "The warning actually contains something"
+ (< 0 (string-length warning)))))
+
+ (test-group "Custom values"
+ (let-env ((XDG_DATA_HOME "/a"))
+ (test-equal "XDG_DATA_HOME" "/a" (xdg-data-home)))
+ (let-env ((XDG_CONFIG_HOME "/b"))
+ (test-equal "XDG_CONFIG_HOME" "/b" (xdg-config-home)))
+ (let-env ((XDG_STATE_HOME "/c"))
+ (test-equal "XDG_STATE_HOME" "/c" (xdg-state-home)))
+ (let-env ((XDG_DATA_DIRS "/d:/e"))
+ (test-equal "XDG_DATA_DIRS" '("/d" "/e") (xdg-data-dirs)))
+ (let-env ((XDG_CONFIG_DIRS "/f:/g"))
+ (test-equal "XDG_CONFIG_DIRS" '("/f" "/g") (xdg-config-dirs)))
+ (let-env ((XDG_CACHE_HOME "/h"))
+ (test-equal "XDG_CACHE_HOME" "/h" (xdg-cache-home)))
+ (let ((warning
+ (with-error-to-string
+ (lambda ()
+ (let-env ((XDG_RUNTIME_DIR "/i"))
+ (test-equal "XDG_RUNTIME_DIR" "/i" (xdg-runtime-dir)))))))
+ (test-assert "No error was emitted"
+ (string-null? warning)))))
+
diff --git a/tests/validate-html/run-validator.scm b/tests/validate-html/run-validator.scm
index 0c4ee0bc..b363e3ea 100755
--- a/tests/validate-html/run-validator.scm
+++ b/tests/validate-html/run-validator.scm
@@ -1,12 +1,16 @@
#!/usr/bin/bash
# -*- mode: scheme; geiser-scheme-implementation: guile -*-
-here=$(dirname $(realpath $0))
+root=$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")
-. "$(dirname "$(dirname "$here")")/env"
+eval "$(env __PRINT_ENVIRONMENT=1 ${root}/calp)"
exec $GUILE -e main -s "$0" -- "$@"
!#
+(unless (getenv "CALP_TEST_ENVIRONMENT")
+ (format (current-error-port) "Not running in test environment, abandoning~%")
+ (exit 1))
+
(use-modules (sxml simple)
((sxml xpath) :select (sxpath))
(sxml match)