Mercurial > sci
changeset 33:1d0ddf9e6efd
misc: general documentation
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 04 Aug 2022 16:47:10 +0200 |
parents | 081e1c258e64 |
children | e52c762d8ba8 |
files | .hgignore Makefile config.mk doc/Doxyfile doc/doxygen-awesome.css doc/mainpage.c lib/apic.c lib/apic.h lib/log.c lib/log.h lib/strlcpy.c lib/strtonum.c lib/util.c lib/util.h libsci/apic.c libsci/apic.h libsci/log.c libsci/log.h libsci/strlcpy.c libsci/strtonum.c libsci/util.c libsci/util.h scid/crud.c scid/crud.h scid/db.h scid/http.h scid/main.c scid/page-api-jobresults.c scid/page-api-jobresults.h scid/page-api-jobs.c scid/page-api-jobs.h scid/page-api-projects.c scid/page-api-projects.h scid/page-api-todo.c scid/page-api-todo.h scid/page-api-workers.c scid/page-api-workers.h scid/page-api.h scid/page-index.c scid/page-index.h scid/page-static.c scid/page-static.h scid/pageutil.h scid/scid.c scid/scid.h scid/theme.c scid/theme.h |
diffstat | 47 files changed, 3488 insertions(+), 1005 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Thu Aug 04 14:59:33 2022 +0200 +++ b/.hgignore Thu Aug 04 16:47:10 2022 +0200 @@ -25,3 +25,6 @@ # tests. ^tests/test-db$ + +# documentation. +^doc/html
--- a/Makefile Thu Aug 04 14:59:33 2022 +0200 +++ b/Makefile Thu Aug 04 16:47:10 2022 +0200 @@ -20,13 +20,14 @@ include config.mk -LIBSCI= lib/libsci.a -LIBSCI_SRCS= extern/libsqlite/sqlite3.c \ - lib/apic.c \ - lib/log.c \ - lib/strlcpy.c \ - lib/strtonum.c \ - lib/util.c +VERSION= 0.1.0 + +LIBSCI= libsci/libsci.a +LIBSCI_SRCS= libsci/apic.c \ + libsci/log.c \ + libsci/strlcpy.c \ + libsci/strtonum.c \ + libsci/util.c LIBSCI_OBJS= ${LIBSCI_SRCS:.c=.o} LIBSCI_DEPS= ${LIBSCI_SRCS:.c=.d} @@ -54,6 +55,7 @@ SCID= scid/scid SCID_SRCS= extern/libduktape/duktape.c \ extern/libmustache4c/mustache.c \ + extern/libsqlite/sqlite3.c \ scid/crud.c \ scid/db.c \ scid/http.c \ @@ -100,14 +102,15 @@ -Iextern/libgreatest \ -Iextern/libmustache4c \ -Iextern/libduktape \ - -Ilib \ + -Ilibsci \ -I. DEFS= -DVARDIR=\"${VARDIR}\" \ - -DTMPDIR=\"${TMPDIR}\" \ + -DVERSION=\"${VERSION}\" \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_OMIT_LOAD_EXTENSION=0 \ -DSQLITE_OMIT_DEPRECATED=0 \ -DSQLITE_DEFAULT_FOREIGN_KEY=1 +SUBST= -e "s,@VERSION@,${VERSION},g" .SUFFIXES: .SUFFIXES: .c .o .sql .h @@ -147,8 +150,7 @@ ${SCICTL_OBJS}: ${LIBSCI} ${SCICTL}: ${SCICTL_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCICTL_OBJS} ${LIBSCI} \ - ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} + ${CC} ${CFLAGS} -o $@ ${SCICTL_OBJS} ${LIBSCI} ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} # }}} @@ -157,8 +159,7 @@ ${SCID_OBJS}: ${LIBSCI} ${SCID}: ${SCID_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCID_OBJS} lib/libsci.a \ - ${JANSSON_LIBS} ${KCGI_LIBS} -lm ${LDFLAGS} + ${CC} ${CFLAGS} -o $@ ${SCID_OBJS} ${LIBSCI} ${JANSSON_LIBS} ${KCGI_LIBS} -lm ${LDFLAGS} # }}} @@ -167,8 +168,7 @@ ${SCIWORKERD_OBJS}: ${LIBSCI} ${SCIWORKERD}: ${SCIWORKERD_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCIWORKERD_OBJS} lib/libsci.a \ - ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} + ${CC} ${CFLAGS} -o $@ ${SCIWORKERD_OBJS} ${LIBSCI} ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} # }}} @@ -188,9 +188,12 @@ rm -f ${SCIWORKERD}${SCIWORKERD_OBJS} ${SCIWORKERD_DEPS} rm -f ${TESTS_OBJS} ${TESTS_DEPS} +doxygen: + sed ${SUBST} < doc/Doxyfile | doxygen - + ${TESTS_OBJS}: lib/libsci.a tests: lib/libsci.a ${TESTS_OBJS} for t in ${TESTS_OBJS}; do $$t -v; done -.PHONY: all clean install tests +.PHONY: all clean doxygen install tests
--- a/config.mk Thu Aug 04 14:59:33 2022 +0200 +++ b/config.mk Thu Aug 04 16:47:10 2022 +0200 @@ -1,6 +1,5 @@ -CC= cc -CFLAGS= -g -O0 -#CFLAGS= -g -O0 -Wall -Wextra -fsanitize=address -Wno-format-truncation +CC= clang +CFLAGS= -g -O0 -Wall -fsanitize=address #CFLAGS= -Wall -Wextra -fsanitize=address,undefined -g -O0 #LDFLAGS= -fsanitize=address,undefined
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/Doxyfile Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,36 @@ +DOXYFILE_ENCODING = UTF-8 + +PROJECT_NAME = "sci" +PROJECT_NUMBER = @VERSION@ +PROJECT_BRIEF = "simple continuous integration" + +OUTPUT_DIRECTORY = doc + +TAB_SIZE = 8 +OPTIMIZE_OUTPUT_FOR_C = YES +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = NO +MAX_INITIALIZER_LINES = 0 + +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES + +SHOW_INCLUDE_FILES = NO +QUIET = YES + +GENERATE_TREEVIEW = YES +HTML_EXTRA_STYLESHEET = doc/doxygen-awesome.css + +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO + +INPUT = lib scictl scid sciworkerd doc/mainpage.c +FILE_PATTERNS = *.c *.h +RECURSIVE = YES + +GENERATE_HTML = YES +GENERATE_LATEX = NO +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html + +HAVE_DOT = YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/doxygen-awesome.css Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,2008 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +html { + /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ + --primary-color: #1779c4; + --primary-dark-color: #335c80; + --primary-light-color: #70b1e9; + + /* page base colors */ + --page-background-color: white; + --page-foreground-color: #2f4153; + --page-secondary-foreground-color: #637485; + + /* color for all separators on the website: hr, borders, ... */ + --separator-color: #dedede; + + /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ + --border-radius-large: 8px; + --border-radius-small: 4px; + --border-radius-medium: 6px; + + /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ + --spacing-small: 5px; + --spacing-medium: 10px; + --spacing-large: 16px; + + /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); + + --odd-color: rgba(0,0,0,.028); + + /* font-families. will affect all text on the website + * font-family: the normal font for text, headlines, menus + * font-family-monospace: used for preformatted text in memtitle, code, fragments + */ + --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; + --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + + /* font sizes */ + --page-font-size: 15.6px; + --navigation-font-size: 14.4px; + --code-font-size: 14px; /* affects code, fragment */ + --title-font-size: 22px; + + /* content text properties. These only affect the page content, not the navigation or any other ui elements */ + --content-line-height: 27px; + /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ + --content-maxwidth: 1000px; + + /* colors for various content boxes: @warning, @note, @deprecated @bug */ + --warning-color: #f8d1cc; + --warning-color-dark: #b61825; + --warning-color-darker: #75070f; + --note-color: #faf3d8; + --note-color-dark: #f3a600; + --note-color-darker: #5f4204; + --todo-color: #e4f3ff; + --todo-color-dark: #1879C4; + --todo-color-darker: #274a5c; + --deprecated-color: #ecf0f3; + --deprecated-color-dark: #5b6269; + --deprecated-color-darker: #43454a; + --bug-color: #e4dafd; + --bug-color-dark: #5b2bdd; + --bug-color-darker: #2a0d72; + --invariant-color: #d8f1e3; + --invariant-color-dark: #44b86f; + --invariant-color-darker: #265532; + + /* blockquote colors */ + --blockquote-background: #f8f9fa; + --blockquote-foreground: #636568; + + /* table colors */ + --tablehead-background: #f1f1f1; + --tablehead-foreground: var(--page-foreground-color); + + /* menu-display: block | none + * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. + * `GENERATE_TREEVIEW` MUST be enabled! + */ + --menu-display: block; + + --menu-focus-foreground: var(--page-background-color); + --menu-focus-background: var(--primary-color); + --menu-selected-background: rgba(0,0,0,.05); + + + --header-background: var(--page-background-color); + --header-foreground: var(--page-foreground-color); + + /* searchbar colors */ + --searchbar-background: var(--side-nav-background); + --searchbar-foreground: var(--page-foreground-color); + + /* searchbar size + * (`searchbar-width` is only applied on screens >= 768px. + * on smaller screens the searchbar will always fill the entire screen width) */ + --searchbar-height: 33px; + --searchbar-width: 210px; + --searchbar-border-radius: var(--searchbar-height); + + /* code block colors */ + --code-background: #f5f5f5; + --code-foreground: var(--page-foreground-color); + + /* fragment colors */ + --fragment-background: #F8F9FA; + --fragment-foreground: #37474F; + --fragment-keyword: #bb6bb2; + --fragment-keywordtype: #8258b3; + --fragment-keywordflow: #d67c3b; + --fragment-token: #438a59; + --fragment-comment: #969696; + --fragment-link: #5383d6; + --fragment-preprocessor: #46aaa5; + --fragment-linenumber-color: #797979; + --fragment-linenumber-background: #f4f4f5; + --fragment-linenumber-border: #e3e5e7; + --fragment-lineheight: 20px; + + /* sidebar navigation (treeview) colors */ + --side-nav-background: #fbfbfb; + --side-nav-foreground: var(--page-foreground-color); + --side-nav-arrow-opacity: 0; + --side-nav-arrow-hover-opacity: 0.9; + + --toc-background: var(--side-nav-background); + --toc-foreground: var(--side-nav-foreground); + + /* height of an item in any tree / collapsable table */ + --tree-item-height: 30px; + + --memname-font-size: var(--code-font-size); + --memtitle-font-size: 18px; + + --webkit-scrollbar-size: 7px; + --webkit-scrollbar-padding: 4px; + --webkit-scrollbar-color: var(--separator-color); +} + +@media screen and (max-width: 767px) { + html { + --page-font-size: 16px; + --navigation-font-size: 16px; + --code-font-size: 15px; /* affects code, fragment */ + --title-font-size: 22px; + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; + } +} + +/* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ +html.dark-mode { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; +} + +body { + color: var(--page-foreground-color); + background-color: var(--page-background-color); + font-size: var(--page-font-size); +} + +body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { + font-family: var(--font-family); +} + +h1, h2, h3, h4, h5 { + margin-top: .9em; + font-weight: 600; + line-height: initial; +} + +p, div, table, dl { + font-size: var(--page-font-size); +} + +a:link, a:visited, a:hover, a:focus, a:active { + color: var(--primary-color) !important; + font-weight: 500; +} + +a.anchor { + scroll-margin-top: var(--spacing-large); +} + +/* + Title and top navigation + */ + +#top { + background: var(--header-background); + border-bottom: 1px solid var(--separator-color); +} + +@media screen and (min-width: 768px) { + #top { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + } +} + +#main-nav { + flex-grow: 5; + padding: var(--spacing-small) var(--spacing-medium); +} + +#titlearea { + width: auto; + padding: var(--spacing-medium) var(--spacing-large); + background: none; + color: var(--header-foreground); + border-bottom: none; +} + +@media screen and (max-width: 767px) { + #titlearea { + padding-bottom: var(--spacing-small); + } +} + +#titlearea table tbody tr { + height: auto !important; +} + +#projectname { + font-size: var(--title-font-size); + font-weight: 600; +} + +#projectnumber { + font-family: inherit; + font-size: 60%; +} + +#projectbrief { + font-family: inherit; + font-size: 80%; +} + +#projectlogo { + vertical-align: middle; +} + +#projectlogo img { + max-height: calc(var(--title-font-size) * 2); + margin-right: var(--spacing-small); +} + +.sm-dox, .tabs, .tabs2, .tabs3 { + background: none; + padding: 0; +} + +.tabs, .tabs2, .tabs3 { + border-bottom: 1px solid var(--separator-color); + margin-bottom: -1px; +} + +@media screen and (max-width: 767px) { + .sm-dox a span.sub-arrow { + background: var(--code-background); + } + + #main-menu a.has-submenu span.sub-arrow { + color: var(--page-secondary-foreground-color); + border-radius: var(--border-radius-medium); + } + + #main-menu a.has-submenu:hover span.sub-arrow { + color: var(--page-foreground-color); + } +} + +@media screen and (min-width: 768px) { + .sm-dox li, .tablist li { + display: var(--menu-display); + } + + .sm-dox a span.sub-arrow { + border-color: var(--header-foreground) transparent transparent transparent; + } + + .sm-dox a:hover span.sub-arrow { + border-color: var(--menu-focus-foreground) transparent transparent transparent; + } + + .sm-dox ul a span.sub-arrow { + border-color: transparent transparent transparent var(--page-foreground-color); + } + + .sm-dox ul a:hover span.sub-arrow { + border-color: transparent transparent transparent var(--menu-focus-foreground); + } +} + +.sm-dox ul { + background: var(--page-background-color); + box-shadow: var(--box-shadow); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium) !important; + padding: var(--spacing-small); + animation: ease-out 150ms slideInMenu; +} + +@keyframes slideInMenu { + from { + opacity: 0; + transform: translate(0px, -2px); + } + + to { + opacity: 1; + transform: translate(0px, 0px); + } +} + +.sm-dox ul a { + color: var(--page-foreground-color) !important; + background: var(--page-background-color); + font-size: var(--navigation-font-size); +} + +.sm-dox>li>ul:after { + border-bottom-color: var(--page-background-color) !important; +} + +.sm-dox>li>ul:before { + border-bottom-color: var(--separator-color) !important; +} + +.sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { + font-size: var(--navigation-font-size) !important; + color: var(--menu-focus-foreground) !important; + text-shadow: none; + background-color: var(--menu-focus-background); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { + text-shadow: none; + background: transparent; + background-image: none !important; + color: var(--header-foreground) !important; + font-weight: normal; + font-size: var(--navigation-font-size); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a:focus { + outline: auto; +} + +.sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { + text-shadow: none; + font-weight: normal; + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; + border-radius: var(--border-radius-small) !important; + font-size: var(--navigation-font-size); +} + +.tablist li.current { + border-radius: var(--border-radius-small); + background: var(--menu-selected-background); +} + +.tablist li { + margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); +} + +.tablist a { + padding: 0 var(--spacing-large); +} + + +/* + Search box + */ + +#MSearchBox { + height: var(--searchbar-height); + background: var(--searchbar-background); + border-radius: var(--searchbar-border-radius); + border: 1px solid var(--separator-color); + overflow: hidden; + width: var(--searchbar-width); + position: relative; + box-shadow: none; + display: block; + margin-top: 0; +} + +.left #MSearchSelect { + left: 0; + user-select: none; +} + +.SelectionMark { + user-select: none; +} + +.tabs .left #MSearchSelect { + padding-left: 0; +} + +.tabs #MSearchBox { + position: absolute; + right: var(--spacing-medium); +} + +@media screen and (max-width: 767px) { + .tabs #MSearchBox { + position: relative; + right: 0; + margin-left: var(--spacing-medium); + margin-top: 0; + } +} + +#MSearchSelectWindow, #MSearchResultsWindow { + z-index: 9999; +} + +#MSearchBox.MSearchBoxActive { + border-color: var(--primary-color); + box-shadow: inset 0 0 0 1px var(--primary-color); +} + +#main-menu > li:last-child { + margin-right: 0; +} + +@media screen and (max-width: 767px) { + #main-menu > li:last-child { + height: 50px; + } +} + +#MSearchField { + font-size: var(--navigation-font-size); + height: calc(var(--searchbar-height) - 2px); + background: transparent; + width: calc(var(--searchbar-width) - 64px); +} + +.MSearchBoxActive #MSearchField { + color: var(--searchbar-foreground); +} + +#MSearchSelect { + top: calc(calc(var(--searchbar-height) / 2) - 11px); +} + +.left #MSearchSelect { + padding-left: 8px; +} + +#MSearchBox span.left, #MSearchBox span.right { + background: none; +} + +#MSearchBox span.right { + padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); + position: absolute; + right: var(--spacing-small); +} + +.tabs #MSearchBox span.right { + top: calc(calc(var(--searchbar-height) / 2) - 12px); +} + +@keyframes slideInSearchResults { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } +} + +#MSearchResultsWindow { + left: auto !important; + right: var(--spacing-medium); + border-radius: var(--border-radius-large); + border: 1px solid var(--separator-color); + transform: translate(0, 20px); + box-shadow: var(--box-shadow); + animation: ease-out 280ms slideInSearchResults; + background: var(--page-background-color); +} + +iframe#MSearchResults { + margin: 4px; +} + +iframe { + color-scheme: normal; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) iframe#MSearchResults { + filter: invert() hue-rotate(180deg); + } +} + +html.dark-mode iframe#MSearchResults { + filter: invert() hue-rotate(180deg); +} + +#MSearchSelectWindow { + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + box-shadow: var(--box-shadow); + background: var(--page-background-color); + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); +} + +#MSearchSelectWindow a.SelectItem { + font-size: var(--navigation-font-size); + line-height: var(--content-line-height); + margin: 0 var(--spacing-small); + border-radius: var(--border-radius-small); + color: var(--page-foreground-color) !important; + font-weight: normal; +} + +#MSearchSelectWindow a.SelectItem:hover { + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; +} + +@media screen and (max-width: 767px) { + #MSearchBox { + margin-top: var(--spacing-medium); + margin-bottom: var(--spacing-medium); + width: calc(100vw - 30px); + } + + #main-menu > li:last-child { + float: none !important; + } + + #MSearchField { + width: calc(100vw - 110px); + } + + @keyframes slideInSearchResultsMobile { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } + } + + #MSearchResultsWindow { + left: var(--spacing-medium) !important; + right: var(--spacing-medium); + overflow: auto; + transform: translate(0, 20px); + animation: ease-out 280ms slideInSearchResultsMobile; + } + + /* + * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 + */ + label.main-menu-btn ~ #searchBoxPos1 { + top: 3px !important; + right: 6px !important; + left: 45px; + display: flex; + } + + label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { + margin-top: 0; + margin-bottom: 0; + flex-grow: 2; + float: left; + } +} + +/* + Tree view + */ + +#side-nav { + padding: 0 !important; + background: var(--side-nav-background); +} + +@media screen and (max-width: 767px) { + #side-nav { + display: none; + } + + #doc-content { + margin-left: 0 !important; + } +} + +#nav-tree { + background: transparent; +} + +#nav-tree .label { + font-size: var(--navigation-font-size); +} + +#nav-tree .item { + height: var(--tree-item-height); + line-height: var(--tree-item-height); +} + +#nav-sync { + bottom: 12px; + right: 12px; + top: auto !important; + user-select: none; +} + +#nav-tree .selected { + text-shadow: none; + background-image: none; + background-color: transparent; + box-shadow: inset 4px 0 0 0 var(--primary-color); +} + +#nav-tree a { + color: var(--side-nav-foreground) !important; + font-weight: normal; +} + +#nav-tree a:focus { + outline-style: auto; +} + +#nav-tree .arrow { + opacity: var(--side-nav-arrow-opacity); +} + +.arrow { + color: inherit; + cursor: pointer; + font-size: 45%; + vertical-align: middle; + margin-right: 2px; + font-family: serif; + height: auto; + text-align: right; +} + +#nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { + opacity: var(--side-nav-arrow-hover-opacity); +} + +#nav-tree .selected a { + color: var(--primary-color) !important; + font-weight: bolder; + font-weight: 600; +} + +.ui-resizable-e { + background: var(--separator-color); + width: 1px; +} + +/* + Contents + */ + +div.header { + border-bottom: 1px solid var(--separator-color); + background-color: var(--page-background-color); + background-image: none; +} + +div.contents, div.header .title, div.header .summary { + max-width: var(--content-maxwidth); +} + +div.contents, div.header .title { + line-height: initial; + margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; +} + +div.header .summary { + margin: var(--spacing-medium) auto 0 auto; +} + +div.headertitle { + padding: 0; +} + +div.header .title { + font-weight: 600; + font-size: 210%; + padding: var(--spacing-medium) var(--spacing-large); + word-break: break-word; +} + +div.header .summary { + width: auto; + display: block; + float: none; + padding: 0 var(--spacing-large); +} + +td.memSeparator { + border-color: var(--separator-color); +} + +.mdescLeft, .mdescRight, .memItemLeft, .memItemRight, .memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background: var(--code-background); +} + +span.mlabel { + background: var(--primary-color); + border: none; + padding: 4px 9px; + border-radius: 12px; + margin-right: var(--spacing-medium); +} + +span.mlabel:last-of-type { + margin-right: 2px; +} + +div.contents { + padding: 0 var(--spacing-large); +} + +div.contents p, div.contents li { + line-height: var(--content-line-height); +} + +div.contents div.dyncontent { + margin: var(--spacing-medium) 0; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) div.contents div.dyncontent img, + html:not(.light-mode) div.contents center img, + html:not(.light-mode) div.contents table img, + html:not(.light-mode) div.contents div.dyncontent iframe, + html:not(.light-mode) div.contents center iframe, + html:not(.light-mode) div.contents table iframe { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode div.contents div.dyncontent img, +html.dark-mode div.contents center img, +html.dark-mode div.contents table img, +html.dark-mode div.contents div.dyncontent iframe, +html.dark-mode div.contents center iframe, +html.dark-mode div.contents table iframe { + filter: hue-rotate(180deg) invert(); +} + +h2.groupheader { + border-bottom: 0px; + color: var(--page-foreground-color); + box-shadow: + 100px 0 var(--page-background-color), + -100px 0 var(--page-background-color), + 100px 1px var(--separator-color), + -100px 1px var(--separator-color), + 500px 0 var(--page-background-color), + -500px 0 var(--page-background-color), + 500px 1px var(--separator-color), + -500px 1px var(--separator-color), + 1500px 0 var(--page-background-color), + 1500px 1px var(--separator-color), + var(--content-maxwidth) 0 var(--page-background-color), + calc(0px - var(--content-maxwidth)) 0 var(--page-background-color), + var(--content-maxwidth) 1px var(--separator-color), + calc(0px - var(--content-maxwidth)) 1px var(--separator-color), + calc(2 * var(--content-maxwidth)) 0 var(--page-background-color), + calc(0px - 2 * var(--content-maxwidth)) 0 var(--page-background-color), + calc(2 * var(--content-maxwidth)) 1px var(--separator-color), + calc(0px - 2 * var(--content-maxwidth)) 1px var(--separator-color); +} + +blockquote { + margin: 0 var(--spacing-medium) 0 var(--spacing-medium); + padding: var(--spacing-small) var(--spacing-large); + background: var(--blockquote-background); + color: var(--blockquote-foreground); + border-left: 0; + overflow: visible; + border-radius: var(--border-radius-medium); + overflow: visible; + position: relative; +} + +blockquote::before, blockquote::after { + font-weight: bold; + font-family: serif; + font-size: 360%; + opacity: .15; + position: absolute; +} + +blockquote::before { + content: "“"; + left: -10px; + top: 4px; +} + +blockquote::after { + content: "”"; + right: -8px; + bottom: -25px; +} + +blockquote p { + margin: var(--spacing-small) 0 var(--spacing-medium) 0; +} +.paramname { + font-weight: 600; + color: var(--primary-dark-color); +} + +.paramname > code { + border: 0; +} + +table.params .paramname { + font-weight: 600; + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + padding-right: var(--spacing-small); +} + +.glow { + text-shadow: 0 0 15px var(--primary-light-color) !important; +} + +.alphachar a { + color: var(--page-foreground-color); +} + +/* + Table of Contents + */ + +div.toc { + z-index: 10; + position: relative; + background-color: var(--toc-background); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + box-shadow: var(--box-shadow); + padding: 0 var(--spacing-large); + margin: 0 0 var(--spacing-medium) var(--spacing-medium); +} + +div.toc h3 { + color: var(--toc-foreground); + font-size: var(--navigation-font-size); + margin: var(--spacing-large) 0; +} + +div.toc li { + font-size: var(--navigation-font-size); + padding: 0; + background: none; +} + +div.toc li:before { + content: '↓'; + font-weight: 800; + font-family: var(--font-family); + margin-right: var(--spacing-small); + color: var(--toc-foreground); + opacity: .4; +} + +div.toc ul li.level1 { + margin: 0; +} + +div.toc ul li.level2, div.toc ul li.level3 { + margin-top: 0; +} + + +@media screen and (max-width: 767px) { + div.toc { + float: none; + width: auto; + margin: 0 0 var(--spacing-medium) 0; + } +} + +/* + Code & Fragments + */ + +code, div.fragment, pre.fragment { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + overflow: hidden; +} + +code { + display: inline; + background: var(--code-background); + color: var(--code-foreground); + padding: 2px 6px; + word-break: break-word; +} + +div.fragment, pre.fragment { + margin: var(--spacing-medium) 0; + padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); + background: var(--fragment-background); + color: var(--fragment-foreground); + overflow-x: auto; +} + +@media screen and (max-width: 767px) { + div.fragment, pre.fragment { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + } + + .contents > div.fragment, + .textblock > div.fragment, + .textblock > pre.fragment, + .contents > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + border-radius: 0; + border-left: 0; + } + + .textblock li > .fragment, + .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + } + + .memdoc li > .fragment, + .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + } + + .textblock ul, .memdoc ul { + overflow: initial; + } + + .memdoc > div.fragment, + .memdoc > pre.fragment, + dl dd > div.fragment, + dl dd pre.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, + dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, + dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + border-radius: 0; + border-left: 0; + } +} + +code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size) !important; +} + +div.line:after { + margin-right: var(--spacing-medium); +} + +div.fragment .line, pre.fragment { + white-space: pre; + word-wrap: initial; + line-height: var(--fragment-lineheight); +} + +div.fragment span.keyword { + color: var(--fragment-keyword); +} + +div.fragment span.keywordtype { + color: var(--fragment-keywordtype); +} + +div.fragment span.keywordflow { + color: var(--fragment-keywordflow); +} + +div.fragment span.stringliteral { + color: var(--fragment-token) +} + +div.fragment span.comment { + color: var(--fragment-comment); +} + +div.fragment a.code { + color: var(--fragment-link) !important; +} + +div.fragment span.preprocessor { + color: var(--fragment-preprocessor); +} + +div.fragment span.lineno { + display: inline-block; + width: 27px; + border-right: none; + background: var(--fragment-linenumber-background); + color: var(--fragment-linenumber-color); +} + +div.fragment span.lineno a { + background: none; + color: var(--fragment-link) !important; +} + +div.fragment .line:first-child .lineno { + box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); +} + +/* + dl warning, attention, note, deprecated, bug, ... + */ + +dl.bug dt a, dl.deprecated dt a, dl.todo dt a { + font-weight: bold !important; +} + +dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.todo, dl.remark { + padding: var(--spacing-medium); + margin: var(--spacing-medium) 0; + color: var(--page-background-color); + overflow: hidden; + margin-left: 0; + border-radius: var(--border-radius-small); +} + +dl.section dd { + margin-bottom: 2px; +} + +dl.warning, dl.attention { + background: var(--warning-color); + border-left: 8px solid var(--warning-color-dark); + color: var(--warning-color-darker); +} + +dl.warning dt, dl.attention dt { + color: var(--warning-color-dark); +} + +dl.note, dl.remark { + background: var(--note-color); + border-left: 8px solid var(--note-color-dark); + color: var(--note-color-darker); +} + +dl.note dt, dl.remark dt { + color: var(--note-color-dark); +} + +dl.todo { + background: var(--todo-color); + border-left: 8px solid var(--todo-color-dark); + color: var(--todo-color-darker); +} + +dl.todo dt { + color: var(--todo-color-dark); +} + +dl.bug dt a { + color: var(--todo-color-dark) !important; +} + +dl.bug { + background: var(--bug-color); + border-left: 8px solid var(--bug-color-dark); + color: var(--bug-color-darker); +} + +dl.bug dt a { + color: var(--bug-color-dark) !important; +} + +dl.deprecated { + background: var(--deprecated-color); + border-left: 8px solid var(--deprecated-color-dark); + color: var(--deprecated-color-darker); +} + +dl.deprecated dt a { + color: var(--deprecated-color-dark) !important; +} + +dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { + margin-inline-start: 0px; +} + +dl.invariant, dl.pre { + background: var(--invariant-color); + border-left: 8px solid var(--invariant-color-dark); + color: var(--invariant-color-darker); +} + +dl.invariant dt, dl.pre dt { + color: var(--invariant-color-dark); +} + +/* + memitem + */ + +div.memdoc, div.memproto, h2.memtitle { + box-shadow: none; + background-image: none; + border: none; +} + +div.memdoc { + padding: 0 var(--spacing-medium); + background: var(--page-background-color); +} + +h2.memtitle, div.memitem { + border: 1px solid var(--separator-color); + box-shadow: var(--box-shadow); +} + +h2.memtitle { + box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); +} + +div.memitem { + transition: none; +} + +div.memproto, h2.memtitle { + background: var(--fragment-background); + text-shadow: none; +} + +h2.memtitle { + font-weight: 500; + font-size: var(--memtitle-font-size); + font-family: var(--font-family-monospace); + border-bottom: none; + border-top-left-radius: var(--border-radius-medium); + border-top-right-radius: var(--border-radius-medium); + word-break: break-all; + position: relative; +} + +h2.memtitle:after { + content: ""; + display: block; + background: var(--fragment-background); + height: var(--spacing-medium); + bottom: calc(0px - var(--spacing-medium)); + left: 0; + right: -14px; + position: absolute; + border-top-right-radius: var(--border-radius-medium); +} + +h2.memtitle > span.permalink { + font-size: inherit; +} + +h2.memtitle > span.permalink > a { + text-decoration: none; + padding-left: 3px; + margin-right: -4px; + user-select: none; + display: inline-block; + margin-top: -6px; +} + +h2.memtitle > span.permalink > a:hover { + color: var(--primary-dark-color) !important; +} + +a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { + border-color: var(--primary-light-color); +} + +div.memitem { + border-top-right-radius: var(--border-radius-medium); + border-bottom-right-radius: var(--border-radius-medium); + border-bottom-left-radius: var(--border-radius-medium); + overflow: hidden; + display: block !important; +} + +div.memdoc { + border-radius: 0; +} + +div.memproto { + border-radius: 0 var(--border-radius-small) 0 0; + overflow: auto; + border-bottom: 1px solid var(--separator-color); + padding: var(--spacing-medium); + margin-bottom: -1px; +} + +div.memtitle { + border-top-right-radius: var(--border-radius-medium); + border-top-left-radius: var(--border-radius-medium); +} + +div.memproto table.memname { + font-family: var(--font-family-monospace); + color: var(--page-foreground-color); + font-size: var(--memname-font-size); +} + +table.mlabels, table.mlabels > tbody { + display: block; +} + +td.mlabels-left { + width: auto; +} + +td.mlabels-right { + margin-top: 3px; + position: sticky; + left: 0; +} + +table.mlabels > tbody > tr:first-child { + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.memname, .memitem span.mlabels { + margin: 0 +} + +/* + reflist + */ + +dl.reflist { + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-medium); + border: 1px solid var(--separator-color); + overflow: hidden; + padding: 0; +} + + +dl.reflist dt, dl.reflist dd { + box-shadow: none; + text-shadow: none; + background-image: none; + border: none; + padding: 12px; +} + + +dl.reflist dt { + font-weight: 500; + border-radius: 0; + background: var(--code-background); + border-bottom: 1px solid var(--separator-color); + color: var(--page-foreground-color) +} + + +dl.reflist dd { + background: none; +} + +/* + Table + */ + +table.markdownTable, table.fieldtable { + width: 100%; + border: none; + margin: var(--spacing-medium) 0; + box-shadow: 0 0 0 1px var(--separator-color); + border-radius: var(--border-radius-small); +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background: var(--tablehead-background); + color: var(--tablehead-foreground); + font-weight: 600; + font-size: var(--page-font-size); +} + +th.markdownTableHeadLeft:first-child, th.markdownTableHeadRight:first-child, th.markdownTableHeadCenter:first-child, th.markdownTableHeadNone:first-child { + border-top-left-radius: var(--border-radius-small); +} + +th.markdownTableHeadLeft:last-child, th.markdownTableHeadRight:last-child, th.markdownTableHeadCenter:last-child, th.markdownTableHeadNone:last-child { + border-top-right-radius: var(--border-radius-small); +} + +table.markdownTable td, table.markdownTable th, table.fieldtable dt { + border: none; + border-right: 1px solid var(--separator-color); + padding: var(--spacing-small) var(--spacing-medium); +} + +table.markdownTable td:last-child, table.markdownTable th:last-child, table.fieldtable dt:last-child { + border: none; +} + +table.markdownTable tr, table.markdownTable tr { + border-bottom: 1px solid var(--separator-color); +} + +table.markdownTable tr:last-child, table.markdownTable tr:last-child { + border-bottom: none; +} + +table.fieldtable th { + font-size: var(--page-font-size); + font-weight: 600; + background-image: none; + background-color: var(--tablehead-background); + color: var(--tablehead-foreground); + border-bottom: 1px solid var(--separator-color); +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + border-bottom: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--separator-color); +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--primary-light-color); + box-shadow: 0 0 15px var(--primary-light-color); +} + +table.memberdecls { + display: block; +} + +table.memberdecls tr[class^='memitem'] { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); +} + +table.memberdecls .memItemLeft, table.memberdecls .memItemRight { + transition: none; + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + background-color: var(--fragment-background); +} + +table.memberdecls .memItemLeft { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + padding-left: var(--spacing-medium); + padding-right: 0; +} + +table.memberdecls .memItemRight { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-right: var(--spacing-medium); + padding-left: 0; + +} + +table.memberdecls .mdescLeft, table.memberdecls .mdescRight { + background: none; + color: var(--page-foreground-color); + padding: var(--spacing-small) 0; +} + +table.memberdecls .memSeparator { + background: var(--page-background-color); + height: var(--spacing-large); + border: 0; + transition: none; +} + +table.memberdecls .groupheader { + margin-bottom: var(--spacing-large); +} + +table.memberdecls .inherit_header td { + padding: 0 0 var(--spacing-medium) 0; + text-indent: -12px; + line-height: 1.5em; + color: var(--page-secondary-foreground-color); +} + +@media screen and (max-width: 767px) { + + table.memberdecls .memItemLeft, table.memberdecls .memItemRight, table.memberdecls .mdescLeft, table.memberdecls .mdescRight { + display: block; + text-align: left; + padding-left: var(--spacing-large); + margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); + border-right: none; + border-left: none; + border-radius: 0; + } + + table.memberdecls .memItemLeft, table.memberdecls .mdescLeft { + border-bottom: 0; + padding-bottom: 0; + } + + table.memberdecls .mdescLeft { + margin-top: calc(0px - var(--page-font-size)); + } + + table.memberdecls .memItemRight, table.memberdecls .mdescRight { + border-top: 0; + padding-top: 0; + padding-right: var(--spacing-large); + } + + table.memberdecls tr[class^='memitem']:not(.inherit) { + display: block; + width: calc(100vw - 2 * var(--spacing-large)); + } + + table.memberdecls .mdescRight { + color: var(--page-foreground-color); + } + + table.memberdecls tr.inherit { + visibility: hidden; + } + + table.memberdecls tr[style="display: table-row;"] { + display: block !important; + visibility: visible; + width: calc(100vw - 2 * var(--spacing-large)); + animation: fade .5s; + } + + @keyframes fade { + 0% { + opacity: 0; + max-height: 0; + } + + 100% { + opacity: 1; + max-height: 200px; + } + } +} + + +/* + Horizontal Rule + */ + +hr { + margin-top: var(--spacing-large); + margin-bottom: var(--spacing-large); + border-top:1px solid var(--separator-color); +} + +.contents hr { + box-shadow: 100px 0 0 0 var(--separator-color), + -100px 0 0 0 var(--separator-color), + var(--content-maxwidth) 0 0 0 var(--separator-color), + calc(0px - var(--content-maxwidth)) 0 0 0 var(--separator-color), + calc(2 * var(--content-maxwidth)) 0 0 0 var(--separator-color), + calc(0px - 2* var(--content-maxwidth)) 0 0 0 var(--separator-color); +} + +.contents img, .contents .center, .contents center, .contents div.image object { + max-width: 100%; + overflow: auto; +} + +/* + Directories + */ +div.directory { + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + width: auto; +} + +table.directory { + font-family: var(--font-family); + font-size: var(--page-font-size); + font-weight: normal; +} + +.directory td.entry { + padding: var(--spacing-small); +} + +.directory tr.even { + background-color: var(--odd-color); +} + +.icona { + width: auto; + height: auto; + margin: 0 var(--spacing-small); +} + +.icon { + background: var(--primary-color); + width: 18px; + height: 18px; + line-height: 18px; +} + +.iconfopen, .icondoc, .iconfclosed { + background-position: center; + margin-bottom: 0; +} + +.icondoc { + filter: saturate(0.2); +} + +@media screen and (max-width: 767px) { + div.directory { + margin-left: calc(0px - var(--spacing-medium)); + margin-right: calc(0px - var(--spacing-medium)); + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode .iconfopen, html.dark-mode .iconfclosed { + filter: hue-rotate(180deg) invert(); +} + +/* + Class list + */ + +.classindex dl.odd { + background: var(--odd-color); + border-radius: var(--border-radius-small); +} + +@media screen and (max-width: 767px) { + .classindex { + margin: 0 calc(0px - var(--spacing-small)); + } +} + +/* + Class Index Doxygen 1.8 +*/ + +table.classindex table div.ah { + background-image: none; + background-color: initial; + border-color: var(--separator-color); + color: var(--page-foreground-color); + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-large); + padding: var(--spacing-small); +} + +div.qindex { + background-color: var(--odd-color); + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + padding: var(--spacing-small) 0; +} + +/* + Footer and nav-path + */ + +#nav-path { + margin-bottom: -1px; + width: 100%; +} + +#nav-path ul { + background-image: none; + background: var(--page-background-color); + border: none; + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + font-size: var(--navigation-font-size); +} + +img.footer { + width: 60px; +} + +.navpath li.footer { + color: var(--page-secondary-foreground-color); +} + +address.footer { + margin-bottom: var(--spacing-large); +} + +#nav-path li.navelem { + background-image: none; + display: flex; + align-items: center; +} + +.navpath li.navelem a { + text-shadow: none; + display: inline-block; + color: var(--primary-color) !important; +} + +.navpath li.navelem b { + color: var(--primary-dark-color); + font-weight: 500; +} + +li.navelem { + padding: 0; + margin-left: -8px; +} + +li.navelem:first-child { + margin-left: var(--spacing-large); +} + +li.navelem:first-child:before { + display: none; +} + +#nav-path li.navelem:after { + content: ''; + border: 5px solid var(--page-background-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: scaleY(4.2); + z-index: 10; + margin-left: 6px; +} + +#nav-path li.navelem:before { + content: ''; + border: 5px solid var(--separator-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: scaleY(3.2); + margin-right: var(--spacing-small); +} + +.navpath li.navelem a:hover { + color: var(--primary-color); +} + +/* + Scrollbars for Webkit +*/ + +#nav-tree::-webkit-scrollbar, +div.fragment::-webkit-scrollbar, +pre.fragment::-webkit-scrollbar, +div.memproto::-webkit-scrollbar, +.contents center::-webkit-scrollbar { + width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); +} + +#nav-tree::-webkit-scrollbar-thumb, +div.fragment::-webkit-scrollbar-thumb, +pre.fragment::-webkit-scrollbar-thumb, +div.memproto::-webkit-scrollbar-thumb, +.contents center::-webkit-scrollbar-thumb { + background-color: transparent; + border: var(--webkit-scrollbar-padding) solid transparent; + border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + background-clip: padding-box; +} + +#nav-tree:hover::-webkit-scrollbar-thumb, +div.fragment:hover::-webkit-scrollbar-thumb, +pre.fragment:hover::-webkit-scrollbar-thumb, +div.memproto:hover::-webkit-scrollbar-thumb, +.contents center:hover::-webkit-scrollbar-thumb { + background-color: var(--webkit-scrollbar-color); +} + +#nav-tree::-webkit-scrollbar-track, +div.fragment::-webkit-scrollbar-track, +pre.fragment::-webkit-scrollbar-track, +div.memproto::-webkit-scrollbar-track, +.contents center::-webkit-scrollbar-track { + background: transparent; +} + +#nav-tree, div.fragment, pre.fragment, div.memproto, .contents center { + overflow-x: overlay; +} + +/* + Scrollbars for Firefox +*/ + +#nav-tree, div.fragment, pre.fragment, div.memproto, .contents center { + scrollbar-width: thin; +} + +/* + Optional Dark mode toggle button +*/ + +doxygen-awesome-dark-mode-toggle { + display: inline-block; + margin: 0 0 0 var(--spacing-small); + padding: 0; + width: var(--searchbar-height); + height: var(--searchbar-height); + background: none; + border: none; + border-radius: var(--searchbar-height); + vertical-align: middle; + text-align: center; + line-height: var(--searchbar-height); + font-size: 22px; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; +} + +doxygen-awesome-dark-mode-toggle > svg { + transition: transform .1s ease-in-out; +} + +doxygen-awesome-dark-mode-toggle:active > svg { + transform: scale(.5); +} + +doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.03); +} + +html.dark-mode doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.18); +} + +/* + Optional fragment copy button +*/ +.doxygen-awesome-fragment-wrapper { + position: relative; +} + +doxygen-awesome-fragment-copy-button { + opacity: 0; + background: var(--fragment-background); + width: 28px; + height: 28px; + position: absolute; + right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + border: 1px solid var(--fragment-foreground); + cursor: pointer; + border-radius: var(--border-radius-small); + display: flex; + justify-content: center; + align-items: center; +} + +.doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { + opacity: .28; +} + +doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { + opacity: 1 !important; +} + +doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { + transform: scale(.91); +} + +doxygen-awesome-fragment-copy-button svg { + fill: var(--fragment-foreground); + width: 18px; + height: 18px; +} + +doxygen-awesome-fragment-copy-button.success svg { + fill: rgb(14, 168, 14); +} + +doxygen-awesome-fragment-copy-button.success { + border-color: rgb(14, 168, 14); +} + +@media screen and (max-width: 767px) { + .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { + right: 0; + } +} + +/* + Optional paragraph link button +*/ + +a.anchorlink { + font-size: 90%; + margin-left: var(--spacing-small); + color: var(--page-foreground-color) !important; + text-decoration: none; + opacity: .15; + display: none; + transition: opacity .1s ease-in-out, color .1s ease-in-out; +} + +a.anchorlink svg { + fill: var(--page-foreground-color); +} + +h3 a.anchorlink svg, h4 a.anchorlink svg { + margin-bottom: -3px; + margin-top: -4px; +} + +a.anchorlink:hover { + opacity: .45; +} + +h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { + display: inline-block; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/mainpage.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,58 @@ +/** + * \mainpage + * \brief Welcome to sci documentation + * + * This is sci, the simple continuous integration framework. + * + * The documentation available here targets users who want to hack on sci code, + * for user general documentation please read the appropriate manual pages. + * + * ## General overview + * + * The framework is split between three applications: + * + * - `scid`: the main daemon providing database access through HTTP (using + * CGI/FastCGI). + * - `scictl`: main user utility to manipulate the sci framework using the + * command line. + * - `sciworkerd`: daemon that fetches jobs, run them and send the result. + * + * ## Data models + * + * Every data model is converted back-and-forth using JSON all over the + * application to avoid converting over and over into native types. Also, + * because the framework uses [mustache][] and Javascript based themes it's + * easier to share the data model using this format. + * + * ### project + * + * - `name` (string): unique project identifir + * - `desc` (string): project description + * - `url` (string): project homepage or repository URL + * - `script` (string): code to execute + * - `date` (int): created timestamp + * + * ### worker + * + * - `name` (string): unique worker identifier + * - `desc` (string): worker description + * + * ### job + * + * - `id` (int): unique job id + * - `tag` (string): job tag (e.g. repository revision) + * - `project_name` (string): project name referenced + * - `date` (int): created timestamp + * + * ### jobresult + * + * - `id` (int): unique jobresult id + * - `job_id` (int): job id referenced + * - `worker_name` (string): worker name referenced + * - `console` (string): script console output + * - `exitcode` (int): script exit code (only if sigcode == 0) + * - `sigcode` (int): termination signal if interrupted (exitcode will be 0) + * - `date` (int): created timestamp (not the worker job timestamp) + * + * [mustache]: https://mustache.github.io/ + */
--- a/lib/apic.c Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -#include <assert.h> -#include <errno.h> -#include <stdarg.h> -#include <stdio.h> -#include <string.h> - -#include <curl/curl.h> - -#include "apic.h" -#include "util.h" - -struct curlpack { - CURL *curl; - CURLcode code; - struct curl_slist *headers; -}; - -struct apiconf apiconf = { - .baseurl = "http://127.0.0.1" -}; - -static size_t -writer(char *in, size_t n, size_t w, FILE *fp) -{ - if (fwrite(in, n, w, fp) != w) - return 0; - - return w; -} - -static inline char * -create_url(const char *fmt, va_list args) -{ - static _Thread_local char ret[1024]; - char page[128]; - va_list ap; - - ret[0] = 0; - va_copy(ap, args); - vsnprintf(page, sizeof (page), fmt, ap); - va_end(ap); - - snprintf(ret, sizeof (ret), "%s/%s", apiconf.baseurl, page); - - return ret; -} - -static inline FILE * -create_file(char **buf, size_t *bufsz) -{ - FILE *fp; - - *buf = NULL; - *bufsz = 0; - - if (!(fp = open_memstream(buf, bufsz))) - util_die("open_memstream: %s\n", strerror(errno)); - - return fp; -} - -static struct curlpack -create_curl(FILE *fp, const char *body, const char *url) -{ - struct curlpack pack = {0}; - - pack.headers = curl_slist_append(pack.headers, "Content-Type: application/json"); - pack.curl = curl_easy_init(); - curl_easy_setopt(pack.curl, CURLOPT_HTTPHEADER, pack.headers); - curl_easy_setopt(pack.curl, CURLOPT_URL, url); - curl_easy_setopt(pack.curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(pack.curl, CURLOPT_TIMEOUT, 3L); - curl_easy_setopt(pack.curl, CURLOPT_WRITEFUNCTION, writer); - curl_easy_setopt(pack.curl, CURLOPT_WRITEDATA, fp); - curl_easy_setopt(pack.curl, CURLOPT_NOSIGNAL, 1L); - - /* Assume POST request if there is a body. */ - if (body) { - curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDS, body); - curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDSIZE, strlen(body)); - } - - pack.code = curl_easy_perform(pack.curl); - - return pack; -} - -static json_t * -perform(struct apic *req, const char *body, const char *fmt, va_list ap) -{ - FILE *fp; - char *response, *url; - size_t responsesz; - json_t *doc = NULL; - json_error_t error; - struct curlpack curl; - - memset(req, 0, sizeof (*req)); - - url = create_url(fmt, ap); - fp = create_file(&response, &responsesz); - curl = create_curl(fp, body, url); - - /* Perform that request now. */ - fclose(fp); - - if (curl.code != CURLE_OK) - snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(curl.code)); - else { - curl_easy_getinfo(curl.curl, CURLINFO_RESPONSE_CODE, &req->status); - - if (req->status != 200) - snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status); - if (response[0] && !(doc = json_loads(response, 0, &error))) - snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text); - } - - curl_easy_cleanup(curl.curl); - curl_slist_free_all(curl.headers); - - free(response); - - return doc; -} - -static json_t * -get(struct apic *req, const char *fmt, ...) -{ - va_list ap; - json_t *ret; - - va_start(ap, fmt); - ret = perform(req, NULL, fmt, ap); - va_end(ap); - - if (!ret || (!json_is_object(ret) && !json_is_array(ret))) - snprintf(req->error, sizeof (req->error), "invalid JSON document received"); - - return ret; -} - -static int -create(struct apic *req, json_t *doc, const char *fmt, ...) -{ - va_list ap; - json_t *ret; - char *body; - - memset(req, 0, sizeof (*req)); - - if (!(body = json_dumps(doc, JSON_COMPACT))) { - json_decref(doc); - return snprintf(req->error, sizeof (req->error), "%s", strerror(errno)), -1; - } - - va_start(ap, fmt); - ret = perform(req, body, fmt, ap); - va_end(ap); - - /* TODO: update id. */ - (void)ret; - - free(body); - - return 0; -} - -json_t * -apic_get(struct apic *req, const char *fmt, ...) -{ - assert(req); - assert(fmt); - - va_list ap; - json_t *ret; - - va_start(ap, fmt); - ret = perform(req, NULL, fmt, ap); - va_end(ap); - - return ret; -} - -json_t * -apic_post(struct apic *req, const json_t *doc, const char *fmt, ...) -{ - assert(req); - assert(fmt); - - va_list ap; - json_t *ret; - char *body; - - if (!(body = json_dumps(doc, JSON_COMPACT))) - util_die("%s", strerror(ENOMEM)); - - va_start(ap, fmt); - ret = perform(req, body, fmt, ap); - va_end(ap); - - free(body); - - return ret; -} - -int -apic_job_add(struct apic *req, json_t *job) -{ - assert(req); - assert(job); - - return create(req, job, "api/v1/jobs"); -} - -json_t * -apic_job_todo(struct apic *req, const char *worker_name) -{ - assert(req); - assert(worker_name); - - return get(req, "api/v1/todo/%s", worker_name); -} - -int -apic_jobresult_add(struct apic *req, json_t *res) -{ - assert(req); - assert(res); - - return create(req, res, "api/v1/jobresults"); -} - -int -apic_project_save(struct apic *req, json_t *p) -{ - assert(req); - assert(p); - - return create(req, p, "api/v1/projects"); -} - -json_t * -apic_project_list(struct apic *req) -{ - assert(req); - - return get(req, "api/v1/projects"); -} - -json_t * -apic_project_find(struct apic *req, const char *name) -{ - assert(req); - assert(name); - - return get(req, "api/v1/projects/%s", name); -} - -int -apic_worker_save(struct apic *req, json_t *wk) -{ - assert(req); - assert(wk); - - return create(req, wk, "api/v1/workers"); -} - -json_t * -apic_worker_list(struct apic *req) -{ - assert(req); - - return get(req, "api/v1/workers"); -} - -json_t * -apic_worker_find(struct apic *req, const char *name) -{ - assert(req); - assert(name); - - return get(req, "api/v1/workers/%s", name); -}
--- a/lib/apic.h Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -#ifndef SCI_APIC_H -#define SCI_APIC_H - -#include <jansson.h> - -#define APIC_ERR_MAX 128 - -struct apic { - char error[APIC_ERR_MAX]; - long status; -}; - -extern struct apiconf { - char baseurl[512]; -} apiconf; - -/* Generic HTTP commands using JSON. */ - -/* Perform GET request. */ -json_t * -apic_get(struct apic *, const char *, ...); - -/* Perform POST request with JSON body. */ -json_t * -apic_post(struct apic *, const json_t *, const char *, ...); - -/* --- */ - -int -apic_job_add(struct apic *, json_t *); - -json_t * -apic_job_todo(struct apic *, const char *); - -int -apic_jobresult_add(struct apic *, json_t *); - -int -apic_project_save(struct apic *, json_t *); - -json_t * -apic_project_list(struct apic *); - -json_t * -apic_project_find(struct apic *, const char *); - -int -apic_worker_save(struct apic *, json_t *); - -json_t * -apic_worker_list(struct apic *); - -json_t * -apic_worker_find(struct apic *, const char *); - -#endif /* !SCI_APIC_H */
--- a/lib/log.c Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* - * log.c -- logging routines - * - * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <assert.h> -#include <stdio.h> -#include <syslog.h> -#include <time.h> - -#include "log.h" - -/* TODO: replace. */ -static struct { - enum log_level verbosity; -} config = { - .verbosity = LOG_LEVEL_DEBUG -}; - -static int syslog_levels[] = { - [LOG_LEVEL_DEBUG] = LOG_DEBUG, - [LOG_LEVEL_INFO] = LOG_INFO, - [LOG_LEVEL_WARNING] = LOG_WARNING -}; - -static const char * const levelsyms[] = { - [LOG_LEVEL_DEBUG] = "D", - [LOG_LEVEL_INFO] = "I", - [LOG_LEVEL_WARNING] = "W" -}; - -void -log_open(const char *name) -{ - openlog(name, 0, LOG_USER); -} - -void -log_write(enum log_level level, const char *fmt, ...) -{ - assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); - assert(fmt); - - if (config.verbosity >= level) { - va_list ap; - - va_start(ap, fmt); - log_vwrite(level, fmt, ap); - va_end(ap); - } -} - -void -log_vwrite(enum log_level level, const char *fmt, va_list ap) -{ - assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); - assert(fmt); - - char line[BUFSIZ] = {0}, timebuf[32] = {0}; - time_t timestamp = time(NULL); - struct tm *tm = localtime(×tamp); - - vsnprintf(line, sizeof (line), fmt, ap); - syslog(syslog_levels[level], "%s", line); - - strftime(timebuf, sizeof (timebuf), "%F %T", tm); - printf("%s %s %s\n", timebuf, levelsyms[level], line); -} - -void -log_finish(void) -{ - if (config.verbosity > 0) - closelog(); -}
--- a/lib/log.h Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/* - * log.h -- logging routines - * - * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SCI_LOG_H -#define SCI_LOG_H - -#include <stdarg.h> -#include <stdlib.h> - -enum log_level { - LOG_LEVEL_WARNING = 1, - LOG_LEVEL_INFO, - LOG_LEVEL_DEBUG -}; - -#define log_debug(...) log_write(LOG_LEVEL_DEBUG, __VA_ARGS__) -#define log_warn(...) log_write(LOG_LEVEL_WARNING, __VA_ARGS__) -#define log_info(...) log_write(LOG_LEVEL_INFO, __VA_ARGS__) -#define log_die(...) do { \ - log_write(LOG_LEVEL_WARNING, __VA_ARGS__); \ - exit(1); \ -} while (0) - -void -log_open(const char *); - -void -log_write(enum log_level, const char *, ...); - -void -log_vwrite(enum log_level, const char *, va_list); - -void -log_finish(void); - -#endif /* !SCI_LOG_H */
--- a/lib/strlcpy.c Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */ - -/* - * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sys/types.h> - -/* - * Copy string src to buffer dst of size dsize. At most dsize-1 - * chars will be copied. Always NUL terminates (unless dsize == 0). - * Returns strlen(src); if retval >= dsize, truncation occurred. - */ -size_t -util_strlcpy(char *dst, const char *src, size_t dsize) -{ - const char *osrc = src; - size_t nleft = dsize; - - /* Copy as many bytes as will fit. */ - if (nleft != 0) { - while (--nleft != 0) { - if ((*dst++ = *src++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src. */ - if (nleft == 0) { - if (dsize != 0) - *dst = '\0'; /* NUL-terminate dst */ - while (*src++) - ; - } - - return(src - osrc - 1); /* count does not include NUL */ -}
--- a/lib/strtonum.c Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ - -/* - * Copyright (c) 2004 Ted Unangst and Todd Miller - * All rights reserved. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <errno.h> -#include <limits.h> -#include <stdlib.h> - -#define INVALID 1 -#define TOOSMALL 2 -#define TOOLARGE 3 - -long long -util_strtonum(const char *numstr, long long minval, long long maxval, - const char **errstrp) -{ - long long ll = 0; - int error = 0; - char *ep; - struct errval { - const char *errstr; - int err; - } ev[4] = { - { NULL, 0 }, - { "invalid", EINVAL }, - { "too small", ERANGE }, - { "too large", ERANGE }, - }; - - ev[0].err = errno; - errno = 0; - if (minval > maxval) { - error = INVALID; - } else { - ll = strtoll(numstr, &ep, 10); - if (numstr == ep || *ep != '\0') - error = INVALID; - else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) - error = TOOSMALL; - else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) - error = TOOLARGE; - } - if (errstrp != NULL) - *errstrp = ev[error].errstr; - errno = ev[error].err; - if (error) - ll = 0; - - return (ll); -}
--- a/lib/util.c Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -/* - * util.c -- miscellaneous utilities - * - * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sys/stat.h> -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <libgen.h> -#include <limits.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "util.h" - -void * -util_malloc(size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - util_die("malloc: %s\n", strerror(errno)); - - return ret; -} - -void * -util_calloc(size_t n, size_t size) -{ - void *ret; - - if (!(ret = calloc(n, size))) - util_die("calloc: %s\n", strerror(errno)); - - return ret; -} - -void * -util_realloc(void *ptr, size_t size) -{ - void *ret; - - if (!(ret = realloc(ptr, size)) && size) - util_die("realloc: %s\n", strerror(errno)); - - return ret; -} - -void * -util_memdup(const void *ptr, size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - util_die("malloc: %s\n", strerror(errno)); - - return memcpy(ret, ptr, size); -} - -char * -util_strdup(const char *src) -{ - char *ret; - - if (!(ret = strdup(src))) - util_die("strdup: %s\n", strerror(errno)); - - return ret; -} - -char * -util_strndup(const char *src, size_t n) -{ - assert(src); - - char *ret; - - if (!(ret = strndup(src, n))) - util_die("strndup: %s\n", strerror(errno)); - - return ret; -} - -char * -util_basename(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - util_strlcpy(tmp, str, sizeof (tmp)); - util_strlcpy(ret, basename(tmp), sizeof (ret)); - - return ret; -} - -char * -util_dirname(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - util_strlcpy(tmp, str, sizeof (tmp)); - util_strlcpy(ret, dirname(tmp), sizeof (ret)); - - return ret; -} - -FILE * -util_fmemopen(void *buf, size_t size, const char *mode) -{ - FILE *fp; - - if (!(fp = fmemopen(buf, size, mode))) - util_die("fmemopen: %s\n", strerror(errno)); - - return fp; -} - -FILE * -util_open_memstream(char **out, size_t *outsz) -{ - assert(out); - assert(outsz); - - FILE *fp; - - if (!(fp = open_memstream(out, outsz))) - util_die("open_memstream: %s\n", strerror(errno)); - - return fp; -} - -char * -util_read(const char *path) -{ - int fd; - struct stat st; - char *ret; - - if ((fd = open(path, O_RDONLY)) < 0) - return NULL; - if (fstat(fd, &st) < 0) - return close(fd), NULL; - - ret = util_calloc(1, st.st_size + 1); - - if (read(fd, ret, st.st_size) != st.st_size) { - free(ret); - ret = NULL; - } - - close(fd); - - return ret; -} - -void -util_die(const char *fmt, ...) -{ - assert(fmt); - - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - exit(1); -} - -json_t * -util_json_pack(const char *fmt, ...) -{ - va_list ap; - json_t *doc; - json_error_t err; - - va_start(ap, fmt); - doc = json_vpack_ex(&err, 0, fmt, ap); - va_end(ap); - - if (!doc) - util_die("json_vpack_ex: %s\n", err.text); - - return doc; -} - -char * -util_json_dump(const json_t *json) -{ - assert(json); - - char *ret; - - if (!(ret = json_dumps(json, JSON_COMPACT))) - util_die("json_dump: %s\n", strerror(ENOMEM)); - - return ret; -}
--- a/lib/util.h Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/* - * util.h -- miscellaneous utilities - * - * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SCI_UTIL_H -#define SCI_UTIL_H - -#include <stddef.h> -#include <stdio.h> - -#include <jansson.h> - -#define UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) - -void * -util_malloc(size_t); - -void * -util_calloc(size_t, size_t); - -void * -util_realloc(void *, size_t); - -void * -util_reallocarray(void *, size_t, size_t); - -void * -util_memdup(const void *, size_t); - -char * -util_strdup(const char *); - -char * -util_strndup(const char *, size_t); - -char * -util_basename(const char *); - -char * -util_dirname(const char *); - -FILE * -util_fmemopen(void *, size_t, const char *); - -FILE * -util_open_memstream(char **, size_t *); - -char * -util_read(const char *); - -void -util_die(const char *, ...); - -json_t * -util_json_pack(const char *, ...); - -char * -util_json_dump(const json_t *); - -/* defined in extern/ */ - -size_t -util_strlcpy(char *, const char *, size_t); - -long long -util_strtonum(const char *, long long, long long, const char **); - -#endif /* !SCI_UTIL_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/apic.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,298 @@ +/* + * apic.c -- synchronous HTTP request + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <curl/curl.h> + +#include "apic.h" +#include "util.h" + +struct curlpack { + CURL *curl; + CURLcode code; + struct curl_slist *headers; +}; + +struct apiconf apiconf = { + .baseurl = "http://127.0.0.1" +}; + +static size_t +writer(char *in, size_t n, size_t w, FILE *fp) +{ + if (fwrite(in, n, w, fp) != w) + return 0; + + return w; +} + +static inline char * +create_url(const char *fmt, va_list args) +{ + static _Thread_local char ret[1024]; + char page[128]; + va_list ap; + + ret[0] = 0; + va_copy(ap, args); + vsnprintf(page, sizeof (page), fmt, ap); + va_end(ap); + + snprintf(ret, sizeof (ret), "%s/%s", apiconf.baseurl, page); + + return ret; +} + +static inline FILE * +create_file(char **buf, size_t *bufsz) +{ + FILE *fp; + + *buf = NULL; + *bufsz = 0; + + if (!(fp = open_memstream(buf, bufsz))) + util_die("open_memstream: %s\n", strerror(errno)); + + return fp; +} + +static struct curlpack +create_curl(FILE *fp, const char *body, const char *url) +{ + struct curlpack pack = {0}; + + pack.headers = curl_slist_append(pack.headers, "Content-Type: application/json"); + pack.curl = curl_easy_init(); + curl_easy_setopt(pack.curl, CURLOPT_HTTPHEADER, pack.headers); + curl_easy_setopt(pack.curl, CURLOPT_URL, url); + curl_easy_setopt(pack.curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(pack.curl, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(pack.curl, CURLOPT_WRITEFUNCTION, writer); + curl_easy_setopt(pack.curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(pack.curl, CURLOPT_NOSIGNAL, 1L); + + /* Assume POST request if there is a body. */ + if (body) { + curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDS, body); + curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDSIZE, strlen(body)); + } + + pack.code = curl_easy_perform(pack.curl); + + return pack; +} + +static json_t * +perform(struct apic *req, const char *body, const char *fmt, va_list ap) +{ + FILE *fp; + char *response, *url; + size_t responsesz; + json_t *doc = NULL; + json_error_t error; + struct curlpack curl; + + memset(req, 0, sizeof (*req)); + + url = create_url(fmt, ap); + fp = create_file(&response, &responsesz); + curl = create_curl(fp, body, url); + + /* Perform that request now. */ + fclose(fp); + + if (curl.code != CURLE_OK) + snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(curl.code)); + else { + curl_easy_getinfo(curl.curl, CURLINFO_RESPONSE_CODE, &req->status); + + if (req->status != 200) + snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status); + if (response[0] && !(doc = json_loads(response, 0, &error))) + snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text); + } + + curl_easy_cleanup(curl.curl); + curl_slist_free_all(curl.headers); + + free(response); + + return doc; +} + +static json_t * +get(struct apic *req, const char *fmt, ...) +{ + va_list ap; + json_t *ret; + + va_start(ap, fmt); + ret = perform(req, NULL, fmt, ap); + va_end(ap); + + if (!ret || (!json_is_object(ret) && !json_is_array(ret))) + snprintf(req->error, sizeof (req->error), "invalid JSON document received"); + + return ret; +} + +static int +create(struct apic *req, json_t *doc, const char *fmt, ...) +{ + va_list ap; + json_t *ret; + char *body; + + memset(req, 0, sizeof (*req)); + body = util_json_dump(doc); + + va_start(ap, fmt); + ret = perform(req, body, fmt, ap); + va_end(ap); + + /* TODO: update id. */ + (void)ret; + + free(body); + json_decref(ret); + + return 0; +} + +json_t * +apic_get(struct apic *req, const char *fmt, ...) +{ + assert(req); + assert(fmt); + + va_list ap; + json_t *ret; + + va_start(ap, fmt); + ret = perform(req, NULL, fmt, ap); + va_end(ap); + + return ret; +} + +json_t * +apic_post(struct apic *req, const json_t *doc, const char *fmt, ...) +{ + assert(req); + assert(fmt); + + va_list ap; + json_t *ret; + char *body; + + if (!(body = json_dumps(doc, JSON_COMPACT))) + util_die("%s", strerror(ENOMEM)); + + va_start(ap, fmt); + ret = perform(req, body, fmt, ap); + va_end(ap); + + free(body); + + return ret; +} + +int +apic_job_add(struct apic *req, json_t *job) +{ + assert(req); + assert(job); + + return create(req, job, "api/v1/jobs"); +} + +json_t * +apic_job_todo(struct apic *req, const char *worker_name) +{ + assert(req); + assert(worker_name); + + return get(req, "api/v1/todo/%s", worker_name); +} + +int +apic_jobresult_add(struct apic *req, json_t *res) +{ + assert(req); + assert(res); + + return create(req, res, "api/v1/jobresults"); +} + +int +apic_project_save(struct apic *req, json_t *p) +{ + assert(req); + assert(p); + + return create(req, p, "api/v1/projects"); +} + +json_t * +apic_project_list(struct apic *req) +{ + assert(req); + + return get(req, "api/v1/projects"); +} + +json_t * +apic_project_find(struct apic *req, const char *name) +{ + assert(req); + assert(name); + + return get(req, "api/v1/projects/%s", name); +} + +int +apic_worker_save(struct apic *req, json_t *wk) +{ + assert(req); + assert(wk); + + return create(req, wk, "api/v1/workers"); +} + +json_t * +apic_worker_list(struct apic *req) +{ + assert(req); + + return get(req, "api/v1/workers"); +} + +json_t * +apic_worker_find(struct apic *req, const char *name) +{ + assert(req); + assert(name); + + return get(req, "api/v1/workers/%s", name); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/apic.h Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,68 @@ +/* + * apic.h -- synchronous HTTP request + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCI_APIC_H +#define SCI_APIC_H + +#include <jansson.h> + +#define APIC_ERR_MAX 128 + +struct apic { + char error[APIC_ERR_MAX]; + long status; +}; + +extern struct apiconf { + char baseurl[512]; +} apiconf /*! Global variable. */; + +json_t * +apic_get(struct apic *, const char *, ...); + +json_t * +apic_post(struct apic *, const json_t *, const char *, ...); + +int +apic_job_add(struct apic *, json_t *); + +json_t * +apic_job_todo(struct apic *, const char *); + +int +apic_jobresult_add(struct apic *, json_t *); + +int +apic_project_save(struct apic *, json_t *); + +json_t * +apic_project_list(struct apic *); + +json_t * +apic_project_find(struct apic *, const char *); + +int +apic_worker_save(struct apic *, json_t *); + +json_t * +apic_worker_list(struct apic *); + +json_t * +apic_worker_find(struct apic *, const char *); + +#endif /* !SCI_APIC_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/log.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,88 @@ +/* + * log.c -- logging routines + * + * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <stdio.h> +#include <syslog.h> +#include <time.h> + +#include "log.h" + +/* TODO: replace. */ +static struct { + enum log_level verbosity; +} config = { + .verbosity = LOG_LEVEL_DEBUG +}; + +static int syslog_levels[] = { + [LOG_LEVEL_DEBUG] = LOG_DEBUG, + [LOG_LEVEL_INFO] = LOG_INFO, + [LOG_LEVEL_WARNING] = LOG_WARNING +}; + +static const char * const levelsyms[] = { + [LOG_LEVEL_DEBUG] = "D", + [LOG_LEVEL_INFO] = "I", + [LOG_LEVEL_WARNING] = "W" +}; + +void +log_open(const char *name) +{ + openlog(name, 0, LOG_USER); +} + +void +log_write(enum log_level level, const char *fmt, ...) +{ + assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); + assert(fmt); + + if (config.verbosity >= level) { + va_list ap; + + va_start(ap, fmt); + log_vwrite(level, fmt, ap); + va_end(ap); + } +} + +void +log_vwrite(enum log_level level, const char *fmt, va_list ap) +{ + assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); + assert(fmt); + + char line[BUFSIZ] = {0}, timebuf[32] = {0}; + time_t timestamp = time(NULL); + struct tm *tm = localtime(×tamp); + + vsnprintf(line, sizeof (line), fmt, ap); + syslog(syslog_levels[level], "%s", line); + + strftime(timebuf, sizeof (timebuf), "%F %T", tm); + printf("%s %s %s\n", timebuf, levelsyms[level], line); +} + +void +log_finish(void) +{ + if (config.verbosity > 0) + closelog(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/log.h Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,51 @@ +/* + * log.h -- logging routines + * + * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCI_LOG_H +#define SCI_LOG_H + +#include <stdarg.h> +#include <stdlib.h> + +enum log_level { + LOG_LEVEL_WARNING = 1, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG +}; + +#define log_debug(...) log_write(LOG_LEVEL_DEBUG, __VA_ARGS__) +#define log_warn(...) log_write(LOG_LEVEL_WARNING, __VA_ARGS__) +#define log_info(...) log_write(LOG_LEVEL_INFO, __VA_ARGS__) +#define log_die(...) do { \ + log_write(LOG_LEVEL_WARNING, __VA_ARGS__); \ + exit(1); \ +} while (0) + +void +log_open(const char *); + +void +log_write(enum log_level, const char *, ...); + +void +log_vwrite(enum log_level, const char *, va_list); + +void +log_finish(void); + +#endif /* !SCI_LOG_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/strlcpy.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,49 @@ +/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t +util_strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/strtonum.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,65 @@ +/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +util_strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/util.c Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,215 @@ +/* + * util.c -- miscellaneous utilities + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/stat.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +void * +util_malloc(size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + util_die("malloc: %s\n", strerror(errno)); + + return ret; +} + +void * +util_calloc(size_t n, size_t size) +{ + void *ret; + + if (!(ret = calloc(n, size))) + util_die("calloc: %s\n", strerror(errno)); + + return ret; +} + +void * +util_realloc(void *ptr, size_t size) +{ + void *ret; + + if (!(ret = realloc(ptr, size)) && size) + util_die("realloc: %s\n", strerror(errno)); + + return ret; +} + +void * +util_memdup(const void *ptr, size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + util_die("malloc: %s\n", strerror(errno)); + + return memcpy(ret, ptr, size); +} + +char * +util_strdup(const char *src) +{ + char *ret; + + if (!(ret = strdup(src))) + util_die("strdup: %s\n", strerror(errno)); + + return ret; +} + +char * +util_strndup(const char *src, size_t n) +{ + assert(src); + + char *ret; + + if (!(ret = strndup(src, n))) + util_die("strndup: %s\n", strerror(errno)); + + return ret; +} + +char * +util_basename(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + util_strlcpy(tmp, str, sizeof (tmp)); + util_strlcpy(ret, basename(tmp), sizeof (ret)); + + return ret; +} + +char * +util_dirname(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + util_strlcpy(tmp, str, sizeof (tmp)); + util_strlcpy(ret, dirname(tmp), sizeof (ret)); + + return ret; +} + +FILE * +util_fmemopen(void *buf, size_t size, const char *mode) +{ + FILE *fp; + + if (!(fp = fmemopen(buf, size, mode))) + util_die("fmemopen: %s\n", strerror(errno)); + + return fp; +} + +FILE * +util_open_memstream(char **out, size_t *outsz) +{ + assert(out); + assert(outsz); + + FILE *fp; + + if (!(fp = open_memstream(out, outsz))) + util_die("open_memstream: %s\n", strerror(errno)); + + return fp; +} + +char * +util_read(const char *path) +{ + int fd; + struct stat st; + char *ret; + + if ((fd = open(path, O_RDONLY)) < 0) + return NULL; + if (fstat(fd, &st) < 0) + return close(fd), NULL; + + ret = util_calloc(1, st.st_size + 1); + + if (read(fd, ret, st.st_size) != st.st_size) { + free(ret); + ret = NULL; + } + + close(fd); + + return ret; +} + +void +util_die(const char *fmt, ...) +{ + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(1); +} + +json_t * +util_json_pack(const char *fmt, ...) +{ + va_list ap; + json_t *doc; + json_error_t err; + + va_start(ap, fmt); + doc = json_vpack_ex(&err, 0, fmt, ap); + va_end(ap); + + if (!doc) + util_die("json_vpack_ex: %s\n", err.text); + + return doc; +} + +char * +util_json_dump(const json_t *json) +{ + assert(json); + + char *ret; + + if (!(ret = json_dumps(json, JSON_COMPACT))) + util_die("json_dump: %s\n", strerror(ENOMEM)); + + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libsci/util.h Thu Aug 04 16:47:10 2022 +0200 @@ -0,0 +1,82 @@ +/* + * util.h -- miscellaneous utilities + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCI_UTIL_H +#define SCI_UTIL_H + +#include <stddef.h> +#include <stdio.h> + +#include <jansson.h> + +#define UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) + +void * +util_malloc(size_t); + +void * +util_calloc(size_t, size_t); + +void * +util_realloc(void *, size_t); + +void * +util_reallocarray(void *, size_t, size_t); + +void * +util_memdup(const void *, size_t); + +char * +util_strdup(const char *); + +char * +util_strndup(const char *, size_t); + +char * +util_basename(const char *); + +char * +util_dirname(const char *); + +FILE * +util_fmemopen(void *, size_t, const char *); + +FILE * +util_open_memstream(char **, size_t *); + +char * +util_read(const char *); + +void +util_die(const char *, ...); + +json_t * +util_json_pack(const char *, ...); + +char * +util_json_dump(const json_t *); + +/* defined in extern/ */ + +size_t +util_strlcpy(char *, const char *, size_t); + +long long +util_strtonum(const char *, long long, long long, const char **); + +#endif /* !SCI_UTIL_H */
--- a/scid/crud.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/crud.c Thu Aug 04 16:47:10 2022 +0200 @@ -30,10 +30,10 @@ int ret = -1; if (!(doc = json_loads(r->fields[0].val, 0, &err))) - log_warn("%s: invalid JSON input: %s", topic, err.text); + log_warn("crud: %s: invalid JSON input: %s", topic, err.text); else { if (saver(doc) < 0) - log_warn("%s: database insertion failed", topic); + log_warn("crud: %s: database insertion failed", topic); else ret = 0;
--- a/scid/crud.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/crud.h Thu Aug 04 16:47:10 2022 +0200 @@ -19,14 +19,47 @@ #ifndef SCID_CRUD_H #define SCID_CRUD_H +/** + * \file crud.h + * \brief Convenient helpers for API pages. + * + * This module uses the database to fetch or insert data and finally show the + * result as HTTP response. + * + * This module logs message with tag `crud`. + */ + #include <jansson.h> struct kreq; +/** + * Decode the JSON input and insert them using the appropriate database + * function provided. + * + * \pre r != NULL + * \pre saver != NULL + * \pre topic != NULL + * \param r the request + * \param saver the function to save in database + * \param topic the log topic for diagnostic + */ void -crud_insert(struct kreq *, int (*)(json_t *), const char *); +crud_insert(struct kreq *r, int (*saver)(json_t *), const char *topic); +/** + * Convert the JSON provided value. + * + * For convenience, if the document is NULL (which happen if the database + * access failed) it will send a HTTP 500 error. Otherwise it is listed and + * free'd. + * + * \pre r != NULL + * \param r the request + * \param doc the document to send (maybe NULL) + * \warning The document will be destroyed using json_decref. + */ void -crud_list(struct kreq *, json_t *); +crud_list(struct kreq *r, json_t *doc); #endif /* !SCID_CRUD_H */
--- a/scid/db.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/db.h Thu Aug 04 16:47:10 2022 +0200 @@ -19,52 +19,175 @@ #ifndef SCI_DB_H #define SCI_DB_H +/** + * \file db.h + * \brief Database access. + * + * This module access the SQLite database. + * + * A global variable is used to access the database, the function \ref db_open + * must be called before any other functions. + * + * Many of the function that insert or update model will also update the JSON + * object to fill the generated row id if any. + * + * This module logs message with tag `db`. + */ + #include <stdint.h> #include <jansson.h> -int -db_open(const char *); - +/** + * Open database specified by path. + * + * \pre path != NULL + * \param path path to the database + */ int -db_job_add(json_t *); +db_open(const char *path); + +/** + * Add a new job. + * + * \pre job != NULL + * \param job the job to add + * \return 0 on success or -1 on error + */ +int +db_job_add(json_t *job); +/** + * Get a list of jobs to perform for this worker. + * + * The returned list will only contain jobs to perform if their date are + * greater or equal to the worker creation date to avoid running jobs that are + * older and probably too numerous. + * + * \pre worker != NULL + * \param worker the worker name + * \return the JSON model or NULL on failure + */ json_t * -db_job_todo(const char *); +db_job_todo(const char *worker); +/** + * Return the whole list of jobs for the given project. + * + * \pre project != NULL + * \param project the project name + * \return the JSON model or NULL on failure + */ json_t * -db_job_list(const char *); +db_job_list(const char *project); +/** + * Add a new job result. + * + * \pre result != NULL + * \param result the job result to add + * \return 0 on success or -1 on error + */ int -db_jobresult_add(json_t *); +db_jobresult_add(json_t *result); +/** + * Get a list of job results by job id. + * + * This will contain every job done by all workers for this job id. + * + * \param job_id the job id + * \return the JSON model or NULL on failure + */ json_t * -db_jobresult_list_by_job(intmax_t); +db_jobresult_list_by_job(intmax_t job_id); +/** + * Get a list of job results for this job id but only the more recent per each + * worker. + * + * This function is handy if you want to retrieve all last jobs realized by all + * workers. + * + * \param job_id the job id + * \return the JSON model or NULL on failure + */ json_t * db_jobresult_list_by_job_group(intmax_t); +/** + * Get a list of job results realized by this worker. + * + * \pre worker != NULL + * \param worker the worker name + * \return the JSON model or NULL on failure + */ json_t * db_jobresult_list_by_worker(const char *); +/** + * Add or update the given project. + * + * In case of update, all fields must be present anyway. + * + * \pre project != NULL + * \param project the project to update + * \return 0 on success or -1 on error + */ int -db_project_save(json_t *); +db_project_save(json_t *project); +/** + * Get a list of all projects. + * + * \return the JSON model or NULL on failure + */ json_t * db_project_list(void); +/** + * Find a project by name. + * + * \pre name != NULL + * \param name the project name + * \return the JSON model or NULL on failure + */ json_t * -db_project_find(const char *); +db_project_find(const char *name); +/** + * Add or update the given worker. + * + * In case of update, all fields must be present anyway. + * + * \pre worker != NULL + * \param worker the worker to update + * \return 0 on success or -1 on error + */ int -db_worker_save(json_t *); +db_worker_save(json_t *worker); +/** + * Get a list of all workers. + * + * \return the JSON model or NULL on failure + */ json_t * db_worker_list(void); +/** + * Find a worker by name. + * + * \pre name != NULL + * \param name the worker name + * \return the JSON model or NULL on failure + */ json_t * db_worker_find(const char *); +/** + * Close database and associated resources. + */ void db_finish(void);
--- a/scid/http.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/http.h Thu Aug 04 16:47:10 2022 +0200 @@ -19,9 +19,24 @@ #ifndef SCI_HTTP_H #define SCI_HTTP_H +/** + * \file http.h + * \brief HTTP parser. + * + * This module uses kcgi to run as simple CGI or FastCGI. + * + * This module logs message with tag `http`. + */ + +/** + * Loop forever for new requests as a FastCGI program. + */ void http_fcgi_run(void); +/** + * Run one time as simple CGI program. + */ void http_cgi_run(void);
--- a/scid/main.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/main.c Thu Aug 04 16:47:10 2022 +0200 @@ -31,24 +31,11 @@ exit(1); } -static void -run_cgi(void) -{ - http_cgi_run(); -} - -static void -run_fcgi(void) -{ - for (;;) - http_fcgi_run(); -} - int main(int argc, char **argv) { int ch; - void (*run)(void) = &(run_cgi); + void (*run)(void) = &(http_cgi_run); while ((ch = getopt(argc, argv, "d:ft:")) != -1) { switch (ch) { @@ -56,7 +43,7 @@ util_strlcpy(scid.dbpath, optarg, sizeof (scid.dbpath)); break; case 'f': - run = &(run_fcgi); + run = &(http_fcgi_run); break; case 't': util_strlcpy(scid.themedir, optarg, sizeof (scid.themedir));
--- a/scid/page-api-jobresults.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-jobresults.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-jobresults.c -- /api/v?/jobresults route + * page-api-jobresults.c -- page /api/v?/jobresults route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> *
--- a/scid/page-api-jobresults.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-jobresults.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-jobresults.h -- /api/v?/jobresults route + * page-api-jobresults.h -- page /api/v?/jobresults route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -19,9 +19,22 @@ #ifndef SCI_PAGE_API_JOBRESULTS_H #define SCI_PAGE_API_JOBRESULTS_H +/** + * \file page-api-jobresults.h + * \brief Page /api/v?/jobresults route. + * + * This module logs message with tag `page-api-jobresults`. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_api_v1_jobresults(struct kreq *); +page_api_v1_jobresults(struct kreq *r); #endif /* !SCI_PAGE_API_JOBRESULTS_H */
--- a/scid/page-api-jobs.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-jobs.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-jobs.c -- /api/v?/jobs route + * page-api-jobs.c -- page /api/v?/jobs route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> *
--- a/scid/page-api-jobs.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-jobs.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-jobs.h -- /api/v?/jobs route + * page-api-jobs.h -- page /api/v?/jobs route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -19,9 +19,22 @@ #ifndef SCI_PAGE_API_JOBS_H #define SCI_PAGE_API_JOBS_H +/** + * \file page-api-jobs.h + * \brief Page /api/v?/jobs route. + * + * This module logs message with tag `page-api-jobs`. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_api_v1_jobs(struct kreq *); +page_api_v1_jobs(struct kreq *r); #endif /* !SCI_PAGE_API_JOBS_H */
--- a/scid/page-api-projects.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-projects.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-projects.c -- /api/v?/projects route + * page-api-projects.c -- page /api/v?/projects route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> *
--- a/scid/page-api-projects.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-projects.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-projects.h -- /api/v?/projects route + * page-api-projects.h -- page /api/v?/projects route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -19,9 +19,22 @@ #ifndef SCI_PAGE_API_PROJECTS_H #define SCI_PAGE_API_PROJECTS_H +/** + * \file page-api-projects.h + * \brief Page /api/v?/projects route. + * + * This module logs message with tag `page-api-projects`. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_api_v1_projects(struct kreq *); +page_api_v1_projects(struct kreq *r); #endif /* !SCI_PAGE_API_PROJECTS_H */
--- a/scid/page-api-todo.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-todo.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-todo.c -- /api/v?/todo route + * page-api-todo.c -- page /api/v?/todo route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> *
--- a/scid/page-api-todo.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-todo.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-todo.h -- /api/v?/todo route + * page-api-todo.h -- page /api/v?/todo route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -19,9 +19,22 @@ #ifndef SCI_PAGE_API_TODO_H #define SCI_PAGE_API_TODO_H +/** + * \file page-api-todo.h + * \brief Page /api/v?/todo route. + * + * This module logs message with tag `page-api-todo`. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_api_v1_todo(struct kreq *); +page_api_v1_todo(struct kreq *r); #endif /* !SCI_PAGE_API_TODO_H */
--- a/scid/page-api-workers.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-workers.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-api-workers.c -- /api/v?/workers route + * page-api-workers.c -- page /api/v?/workers route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> *
--- a/scid/page-api-workers.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-api-workers.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,9 +1,40 @@ +/* + * page-api-workers.h -- page /api/v?/workers route + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #ifndef SCI_PAGE_API_WORKERS_H #define SCI_PAGE_API_WORKERS_H +/** + * \file page-api-workers.h + * \brief Page /api/v?/workers route. + * + * This module logs message with tag `page-api-workers`. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_api_v1_workers(struct kreq *); +page_api_v1_workers(struct kreq *r); #endif /* !SCI_PAGE_API_WORKERS_H */
--- a/scid/page-api.h Thu Aug 04 14:59:33 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#ifndef SCID_PAGE_API_H -#define SCID_PAGE_API_H - - - -#endif /* !SCID_PAGE_API_H */
--- a/scid/page-index.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-index.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-index.c -- page / + * page-index.c -- page / route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -20,7 +20,6 @@ #include <string.h> #include "db.h" -#include "log.h" #include "pageutil.h" #include "scid.h" #include "theme.h"
--- a/scid/page-index.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-index.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,5 +1,5 @@ /* - * page-index.h -- / route + * page-index.h -- page / route * * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> * @@ -19,9 +19,22 @@ #ifndef SCI_PAGE_INDEX_H #define SCI_PAGE_INDEX_H +/** + * \file page-index.h + * \brief Page / route. + * + * This module does not log messages. + */ + struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_index(struct kreq *); +page_index(struct kreq *r); #endif /* !SCI_PAGE_INDEX_H */
--- a/scid/page-static.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-static.c Thu Aug 04 16:47:10 2022 +0200 @@ -2,11 +2,11 @@ * page-static.c -- page /static * * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> - * + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
--- a/scid/page-static.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/page-static.h Thu Aug 04 16:47:10 2022 +0200 @@ -2,11 +2,11 @@ * page-static.h -- page /static * * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> - * + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR @@ -16,12 +16,25 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef IMGUP_PAGE_STATIC_H -#define IMGUP_PAGE_STATIC_H +#ifndef SCID_PAGE_STATIC_H +#define SCID_PAGE_STATIC_H + +/** + * \file page-static.h + * \brief Page /static + * + * This module does not log messages. + */ struct kreq; +/** + * Run the page. + * + * \pre r != NULL + * \param r the request + */ void -page_static(struct kreq *); +page_static(struct kreq *r); -#endif /* !IMGUP_PAGE_STATIC_H */ +#endif /* !SCID_PAGE_STATIC_H */
--- a/scid/pageutil.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/pageutil.h Thu Aug 04 16:47:10 2022 +0200 @@ -19,6 +19,16 @@ #ifndef SCI_PAGE_H #define SCI_PAGE_H +/** + * \file pageutil.h + * \brief Page utilities. + * + * This module provides convenient helpers to generate pages, it will call the + * current theme so make sure to initialize before using this module. + * + * This module does not log messages. + */ + #include <sys/types.h> #include <stdarg.h> #include <stdint.h> @@ -27,13 +37,38 @@ #include <jansson.h> -void -pageutil_render(struct kreq *, enum khttp, enum kmime, const char *); - +/** + * Render a page using the given content. + * + * \pre r != NULL + * \param r the request + * \param status the HTTP status code + * \param mime the MIME type + * \param body the body content (maybe NULL) + */ void -pageutil_status(struct kreq *, enum khttp); +pageutil_render(struct kreq *r, enum khttp status, enum kmime mime, const char *body); + +/** + * Render a status code page (e.g. 400). + * + * \pre r != NULL + * \param r the request + * \param status the HTTP status code + */ +void +pageutil_status(struct kreq *r, enum khttp status); +/** + * Render a page with a JSON as MIME type. + * + * \pre r != NULL + * \param r != NULL + * \param status the HTTP status code + * \param doc the JSON document (may be NULL) + * \warning The document will be destroyed using json_decref. + */ void -pageutil_json(struct kreq *req, enum khttp status, json_t *doc); +pageutil_json(struct kreq *r, enum khttp status, json_t *doc); #endif /* !SCI_PAGE_H */
--- a/scid/scid.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/scid.c Thu Aug 04 16:47:10 2022 +0200 @@ -1,3 +1,21 @@ +/* + * scid.c -- main scid file and configuration + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #include <assert.h> #include <stdio.h> @@ -13,11 +31,11 @@ void scid_init(void) { - log_open("scid"); - log_info("opening database %s", scid.dbpath); + log_open("scid: version " VERSION); + log_info("scid: opening database %s", scid.dbpath); if (db_open(scid.dbpath) < 0) - log_die("abort: unable to open database"); + log_die("scid: abort: unable to open database"); theme_open(scid.themedir); }
--- a/scid/scid.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/scid.h Thu Aug 04 16:47:10 2022 +0200 @@ -1,16 +1,51 @@ +/* + * scid.h -- main scid file and configuration + * + * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #ifndef SCID_H #define SCID_H +/** + * \file scid.h + * \brief Main scid file and configuration. + * + * This module logs message with tag `scid`. + */ + #include <limits.h> +/** + * \struct scid + * \brief Main scid structure for configuration. + */ extern struct scid { - char themedir[PATH_MAX]; - char dbpath[PATH_MAX]; -} scid; + char themedir[PATH_MAX]; /*!< Path to the theme. */ + char dbpath[PATH_MAX]; /*!< Path to the database file. */ +} scid /*! Global variable. */; +/** + * Initialize scid. + */ void scid_init(void); +/** + * Cleanup scid. + */ void scid_finish(void);
--- a/scid/theme.c Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/theme.c Thu Aug 04 16:47:10 2022 +0200 @@ -26,15 +26,13 @@ #include <mustache.h> #include "log.h" -#include "scid.h" #include "theme.h" #include "util.h" #define SIGNATURE DUK_HIDDEN_SYMBOL("File") -static struct { - duk_context *ctx; -} theme; +static duk_context *context; +static char base[PATH_MAX]; /* {{{ mustache support */ @@ -289,24 +287,24 @@ size_t outsz = 0; FILE *fp; - duk_get_global_string(theme.ctx, function); + duk_get_global_string(context, function); - if (duk_is_callable(theme.ctx, -1)) { + if (duk_is_callable(context, -1)) { fp = util_open_memstream(&out, &outsz); dump = util_json_dump(json); - duk_push_pointer(theme.ctx, fp); - duk_push_string(theme.ctx, dump); - duk_json_decode(theme.ctx, -1); + duk_push_pointer(context, fp); + duk_push_string(context, dump); + duk_json_decode(context, -1); - if (duk_pcall(theme.ctx, 2) != 0) - log_warn("theme: %s", duk_safe_to_string(theme.ctx, -1)); + if (duk_pcall(context, 2) != 0) + log_warn("theme: %s", duk_safe_to_string(context, -1)); - duk_pop(theme.ctx); + duk_pop(context); fclose(fp); free(dump); } else - duk_pop(theme.ctx); + duk_pop(context); if (!out) out = util_strdup(""); @@ -322,19 +320,20 @@ const char *path; char *data; - theme.ctx = duk_create_heap_default(); + util_strlcpy(base, directory, sizeof (base)); + context = duk_create_heap_default(); path = theme_path("theme.js"); if (!(data = util_read(path))) log_warn("theme: %s: %s", path, strerror(errno)); else { - if (duk_peval_string(theme.ctx, data) != 0) - log_warn("theme: %s", duk_safe_to_string(theme.ctx, -1)); + if (duk_peval_string(context, data) != 0) + log_warn("theme: %s", duk_safe_to_string(context, -1)); - duk_pop(theme.ctx); - duk_push_object(theme.ctx); - duk_put_function_list(theme.ctx, -1, functions); - duk_put_global_string(theme.ctx, "Scid"); + duk_pop(context); + duk_push_object(context); + duk_put_function_list(context, -1, functions); + duk_put_global_string(context, "Scid"); free(data); } } @@ -347,7 +346,7 @@ /* Build path to the template file. */ static _Thread_local char path[PATH_MAX]; - snprintf(path, sizeof (path), "%s/%s", scid.themedir, filename); + snprintf(path, sizeof (path), "%s/%s", base, filename); return path; } @@ -377,5 +376,5 @@ void theme_free(void) { - duk_destroy_heap(theme.ctx); + duk_destroy_heap(context); }
--- a/scid/theme.h Thu Aug 04 14:59:33 2022 +0200 +++ b/scid/theme.h Thu Aug 04 16:47:10 2022 +0200 @@ -34,6 +34,10 @@ * This module uses a global Javascript context to perform rendering, you must * call theme_open before doing anything else. Once done, use theme_finish to * cleanup resources. + * + * This module logs message with tag `theme`. + * + * [mustache]: https://mustache.github.io/ */ #include <jansson.h> @@ -91,7 +95,6 @@ * \return a newly allocated rendered string * \note You must free the return value */ -[[nodiscard]] char * theme_page_index(const json_t *doc); @@ -110,7 +113,6 @@ * \return a newly allocated rendered string * \note You must free the return value */ -[[nodiscard]] char * theme_page_status(enum khttp status);