25 Commits

Author SHA1 Message Date
13d43d8319 Let's release 0.0.16. 2024-02-28 18:24:12 -05:00
7bcdbd3813 Revert "Update commonmark.js to 0.31.0."
This reverts commit 165f25db69.
2024-02-26 12:34:50 -05:00
60ada22674 Add a button to toggle visible whitespace for now. Not yet persisted. 2024-02-25 22:31:31 -05:00
637119d46d ideviceinstaller makes this unnecessary. 2024-02-25 21:41:32 -05:00
40f3da6a65 Fix a leak in returning HTTP responses. 2024-02-25 19:38:00 -05:00
f4697fe7f7 Update CodeMirror. 2024-02-25 18:54:35 -05:00
3bc18b9021 Docs for util.js.h. 2024-02-25 18:52:34 -05:00
c21581aefa Use zipalign w/zopfli for APKs to save a little on size. 2024-02-25 18:29:10 -05:00
165f25db69 Update commonmark.js to 0.31.0. 2024-02-25 16:25:23 -05:00
9aa0617aa1 Fix android argv. 2024-02-25 16:02:56 -05:00
ddce88dce6 Merge branch 'tasiaiso-wiki-improvements' 2024-02-25 15:37:56 -05:00
6aa2bce2be Merge branch 'wiki-improvements' of https://dev.tildefriends.net/tasiaiso/tildefriends into tasiaiso-wiki-improvements 2024-02-25 15:37:13 -05:00
a43c1d3d1e Format. 2024-02-25 15:03:43 -05:00
1ed0e817e8 BSD compile fix. 2024-02-25 14:57:14 -05:00
709ca55e65 Fix overbuild on macos. 2024-02-25 14:52:35 -05:00
8c13f5dbba xopt => getopt_long. I give up on xopt. It didn't help me as much as I had hoped, and I had problems building for mingw with only some versions of GCC. Not worth any further time. 2024-02-25 14:45:31 -05:00
4cb82d81b7 apps/gg doesn't belong here and isn't ready for prime time.. 2024-02-24 11:19:36 -05:00
0c42921387 Appease prettier in index.html. 2024-02-24 11:16:07 -05:00
2d3e108fd9 Reapply "build: Add prettier to the project"
This reverts commit 7822b30dcb.
2024-02-23 10:29:46 +01:00
7822b30dcb Revert "build: Add prettier to the project"
This reverts commit 41024ddb79.
2024-02-23 10:25:51 +01:00
e361c3f975 chore(wiki): the button class is now optional for input elements 2024-02-22 22:34:11 +01:00
1d5cdf9607 feat(wiki): improvements to the wiki's UI 2024-02-22 18:39:53 +01:00
a4bf3542e0 Merge branch 'main' into wiki-improvements 2024-02-22 15:14:49 +00:00
df82cfe66b chore rename core.css to tildefriends.css, remove license from tildefriends.css 2024-02-22 16:11:49 +01:00
53f9547cc5 style(wiki): use core.js 2024-02-22 13:03:21 +01:00
62 changed files with 985 additions and 8577 deletions

View File

@ -4,8 +4,8 @@ MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 16
VERSION_NUMBER := 0.0.16-wip
VERSION_NAME := Medium English breakfast tea.
VERSION_NUMBER := 0.0.16
VERSION_NAME := Now with 38% more process.
PROJECT = tildefriends
BUILD_DIR ?= out
@ -245,7 +245,6 @@ $(APP_OBJS): CFLAGS += \
-Ideps/quickjs \
-Ideps/sqlite \
-Ideps/valgrind \
-Ideps/xopt \
-Wdouble-promotion \
-Werror
ifeq ($(UNAME_M),x86_64)
@ -497,18 +496,6 @@ $(SQLITE_OBJS): CFLAGS += \
-Wno-unused-function \
-Wno-unused-variable
XOPT_SOURCES := deps/xopt/xopt.c
XOPT_OBJS := $(call get_objs,XOPT_SOURCES)
$(filter $(BUILD_DIR)/win%,$(XOPT_OBJS)): CFLAGS += \
-DHAVE_SNPRINTF \
-DHAVE_VSNPRINTF \
-DHAVE_VASNPRINTF \
-DHAVE_VASPRINTF \
-Dvsnprintf=rpl_vsnprintf
$(XOPT_OBJS): CFLAGS += \
-Wno-implicit-const-int-float-conversion \
-Wno-pointer-to-int-cast
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
@ -637,8 +624,7 @@ ALL_APP_OBJS := \
$(QUICKJS_OBJS) \
$(SODIUM_OBJS) \
$(SQLITE_OBJS) \
$(UV_OBJS) \
$(XOPT_OBJS)
$(UV_OBJS)
DEPS = $(ALL_APP_OBJS:.o=.d)
-include $(DEPS)
@ -717,7 +703,7 @@ PACKAGE_DIRS := \
deps/prettier/ \
deps/lit/
RAW_FILES := $(filter-out apps/blog% apps/gg% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
RAW_FILES := $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release
@ -736,10 +722,11 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
@cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@cp out/apk/res.apk $@
@cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 $(RAW_FILES)
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@rm -f $@ && $(ANDROID_BUILD_TOOLS)/zipalign -z 4 $@.zip $@
out/apk/TildeFriends-x86-%.unsigned.apk:
@mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/
@ -748,14 +735,15 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@cp out/apk/res.apk $@
@cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 $(RAW_FILES)
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@rm -f $@ && $(ANDROID_BUILD_TOOLS)/zipalign -z 4 $@.zip $@
out/%.apk: out/apk/%.unsigned.apk
@echo "[apksigner] $(notdir $@)"
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
release-apk: out/TildeFriends-arm-release.apk out/TildeFriends-x86-release.apk
.PHONY: release-apk
@ -839,7 +827,7 @@ $(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
endif
ifeq ($(UNAME_S),Darwin)
IOS_DEPS := deps/openssl/ios/usr/local/lib/libssl.a
IOS_DEPS := deps/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
$(IOS_DEPS):
+@tools/ssl-ios
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
@ -855,7 +843,6 @@ dist: release-apk iosrelease-ipa
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \
--exclude=apps/gg* \
--exclude=apps/welcome* \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
--exclude=deps/libsodium/builds/msvc/vs* \
@ -871,7 +858,6 @@ dist: release-apk iosrelease-ipa
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
#@rm -rf out/tildefriends-$(VERSION_NUMBER)
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"

View File

@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🗺",
"previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256"
}

View File

@ -1,85 +0,0 @@
import * as tfrpc from '/tfrpc.js';
import * as strava from './strava.js';
let g_database;
let g_shared_database;
tfrpc.register(async function createIdentity() {
return ssb.createIdentity();
});
tfrpc.register(async function appendMessage(id, message) {
print('APPEND', JSON.stringify(message));
return ssb.appendMessageWithIdentity(id, message);
});
tfrpc.register(function url() {
return core.url;
});
tfrpc.register(async function getUser() {
return core.user;
});
tfrpc.register(function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function databaseGet(key) {
return g_database ? g_database.get(key) : undefined;
});
tfrpc.register(async function databaseSet(key, value) {
return g_database ? g_database.set(key, value) : undefined;
});
tfrpc.register(async function databaseRemove(key, value) {
return g_database ? g_database.remove(key, value) : undefined;
});
tfrpc.register(async function sharedDatabaseGet(key) {
return g_shared_database ? g_shared_database.get(key) : undefined;
});
tfrpc.register(async function sharedDatabaseSet(key, value) {
return g_shared_database ? g_shared_database.set(key, value) : undefined;
});
tfrpc.register(async function sharedDatabaseRemove(key, value) {
return g_shared_database ? g_shared_database.remove(key, value) : undefined;
});
tfrpc.register(async function query(sql, args) {
let result = [];
await ssb.sqlAsync(sql, args, function callback(row) {
result.push(row);
});
return result;
});
tfrpc.register(async function store_blob(blob) {
if (typeof blob == 'string') {
blob = utf8Encode(blob);
}
if (Array.isArray(blob)) {
blob = Uint8Array.from(blob);
}
return await ssb.blobStore(blob);
});
tfrpc.register(async function get_blob(id) {
return utf8Decode(await ssb.blobGet(id));
});
tfrpc.register(strava.refresh_token);
async function main() {
g_shared_database = await shared_database('state');
if (core.user.credentials?.session?.name) {
g_database = await database('state');
}
let attempt;
if (core.user.credentials?.session?.name) {
let shared_db = await shared_database('state');
attempt = await shared_db.get(core.user.credentials.session.name);
}
app.setDocument(
utf8Decode(getFile('index.html')).replace(
'${data}',
JSON.stringify({
attempt: attempt,
state: core.user?.credentials?.session?.name,
})
)
);
}
main();

File diff suppressed because one or more lines are too long

View File

@ -1,84 +0,0 @@
function xml_parse(xml) {
let result;
let path = [];
let tag_begin;
let text_begin;
for (let i = 0; i < xml.length; i++) {
let c = xml.charAt(i);
if (!tag_begin && c == '<') {
if (i > text_begin && path.length) {
let value = xml.substring(text_begin, i);
if (!/^\s*$/.test(value)) {
path[path.length - 1].value = value;
}
}
tag_begin = i + 1;
} else if (tag_begin && c == '>') {
let tag = xml.substring(tag_begin, i).trim();
if (tag.startsWith('?') && tag.endsWith('?')) {
/* Ignore directives. */
} else if (tag.startsWith('/')) {
path.pop();
} else {
let parts = tag.split(' ');
let attributes = {};
for (let j = 1; j < parts.length; j++) {
let eq = parts[j].indexOf('=');
let value = parts[j].substring(eq + 1);
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}
attributes[parts[j].substring(0, eq)] = value;
}
let next = {name: parts[0], children: [], attributes: attributes};
if (path.length) {
path[path.length - 1].children.push(next);
} else {
result = next;
}
if (!tag.endsWith('/')) {
path.push(next);
}
}
tag_begin = undefined;
text_begin = i + 1;
}
}
return result;
}
function* xml_each(node, name) {
for (let child of node.children) {
if (child.name == name) {
yield child;
}
}
}
export function gpx_parse(xml) {
let result = {segments: []};
let tree = xml_parse(xml);
if (tree?.name == 'gpx') {
for (let trk of xml_each(tree, 'trk')) {
for (let trkseg of xml_each(trk, 'trkseg')) {
let segment = [];
for (let trkpt of xml_each(trkseg, 'trkpt')) {
segment.push({
lat: parseFloat(trkpt.attributes.lat),
lon: parseFloat(trkpt.attributes.lon),
});
}
result.segments.push(segment);
}
}
}
for (let metadata of xml_each(tree, 'metadata')) {
for (let link of xml_each(metadata, 'link')) {
result.link = link.attributes.href;
}
for (let time of xml_each(metadata, 'time')) {
result.time = time.value;
}
}
return result;
}

View File

@ -1,21 +0,0 @@
import * as strava from './strava.js';
async function main() {
print('handler running');
let r = await strava.authorization_code(request.query.code);
print('state =', request.query.state);
print('body = ', r.body);
if (request.query.state && r.body) {
let shared_db = await shared_database('state');
await shared_db.set(request.query.state, utf8Decode(r.body));
}
await respond({
data: r.body,
content_type: 'text/plain',
headers: {
Location: 'https://tildefriends.net/~cory/gg/',
},
status_code: 307,
});
}
main();

View File

@ -1,26 +0,0 @@
<!doctype html>
<html style="width: 100%; height: 100%; margin: 0; padding: 0">
<head>
<script>
window.litDisableBundleWarning = true;
</script>
<script>
let g_data = ${data};
</script>
<script src="script.js" type="module"></script>
<script src="leaflet.js"></script>
</head>
<body
style="
color: #fff;
display: flex;
flex-flow: column;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
"
>
<gg-app style="width: 100%; height: 100%" id="ggapp"></gg-app>
</body>
</html>

View File

@ -1,661 +0,0 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

120
apps/gg/lit-all.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,162 +0,0 @@
/**
* Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
*
* Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
* by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
*
* @module polyline
*/
var polyline = {};
function py2_round(value) {
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
}
function encode(current, previous, factor) {
current = py2_round(current * factor);
previous = py2_round(previous * factor);
var coordinate = (current - previous) * 2;
if (coordinate < 0) {
coordinate = -coordinate - 1;
}
var output = '';
while (coordinate >= 0x20) {
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
coordinate /= 32;
}
output += String.fromCharCode((coordinate | 0) + 63);
return output;
}
/**
* Decodes to a [latitude, longitude] coordinates array.
*
* This is adapted from the implementation in Project-OSRM.
*
* @param {String} str
* @param {Number} precision
* @returns {Array}
*
* @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
*/
polyline.decode = function (str, precision) {
var index = 0,
lat = 0,
lng = 0,
coordinates = [],
shift = 0,
result = 0,
byte = null,
latitude_change,
longitude_change,
factor = Math.pow(10, Number.isInteger(precision) ? precision : 5);
// Coordinates have variable length when encoded, so just keep
// track of whether we've hit the end of the string. In each
// loop iteration, a single coordinate is decoded.
while (index < str.length) {
// Reset shift, result, and byte
byte = null;
shift = 1;
result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result += (byte & 0x1f) * shift;
shift *= 32;
} while (byte >= 0x20);
latitude_change = result & 1 ? (-result - 1) / 2 : result / 2;
shift = 1;
result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result += (byte & 0x1f) * shift;
shift *= 32;
} while (byte >= 0x20);
longitude_change = result & 1 ? (-result - 1) / 2 : result / 2;
lat += latitude_change;
lng += longitude_change;
coordinates.push([lat / factor, lng / factor]);
}
return coordinates;
};
/**
* Encodes the given [latitude, longitude] coordinates array.
*
* @param {Array.<Array.<Number>>} coordinates
* @param {Number} precision
* @returns {String}
*/
polyline.encode = function (coordinates, precision) {
if (!coordinates.length) {
return '';
}
var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5),
output =
encode(coordinates[0][0], 0, factor) +
encode(coordinates[0][1], 0, factor);
for (var i = 1; i < coordinates.length; i++) {
var a = coordinates[i],
b = coordinates[i - 1];
output += encode(a[0], b[0], factor);
output += encode(a[1], b[1], factor);
}
return output;
};
function flipped(coords) {
var flipped = [];
for (var i = 0; i < coords.length; i++) {
var coord = coords[i].slice();
flipped.push([coord[1], coord[0]]);
}
return flipped;
}
/**
* Encodes a GeoJSON LineString feature/geometry.
*
* @param {Object} geojson
* @param {Number} precision
* @returns {String}
*/
polyline.fromGeoJSON = function (geojson, precision) {
if (geojson && geojson.type === 'Feature') {
geojson = geojson.geometry;
}
if (!geojson || geojson.type !== 'LineString') {
throw new Error('Input must be a GeoJSON LineString');
}
return polyline.encode(flipped(geojson.coordinates), precision);
};
/**
* Decodes to a GeoJSON LineString geometry.
*
* @param {String} str
* @param {Number} precision
* @returns {Object}
*/
polyline.toGeoJSON = function (str, precision) {
var coords = polyline.decode(str, precision);
return {
type: 'LineString',
coordinates: flipped(coords),
};
};
let polyline_decode = polyline.decode;
export {polyline_decode as decode};

View File

@ -1,909 +0,0 @@
import {
LitElement,
html,
unsafeHTML,
css,
guard,
until,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as polyline from './polyline.js';
import {gpx_parse} from './gpx.js';
const k_client_id = '28276';
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
const k_color_snow = [128, 128, 255, 255];
const k_color_ice = [160, 160, 255, 255];
const k_color_water = [0, 0, 255, 255];
const k_color_dirt = [128, 129, 130, 255];
const k_color_pavement = [32, 32, 32, 255];
const k_color_grass = [0, 255, 0, 255];
const k_color_default = [128, 128, 128, 255];
const k_store = {
'🦞': 15,
'🛶': 10,
'🏠': 10,
'⛰': 10,
'🐠': 10,
};
const k_marker_snap = {x: 5, y: 4};
class GgAppElement extends LitElement {
static get properties() {
return {
user: {type: Object},
strava: {type: Object},
activities: {type: Array},
activity: {type: Object},
world: {type: Object},
whoami: {type: String},
status: {type: Object},
tab: {type: String},
url: {type: String},
currency: {type: Number},
to_build: {type: String},
emoji_of_the_day: {type: String},
};
}
constructor() {
super();
this.activities = [];
this.activity = {};
this.loaded_activities = [];
this.placed_emojis = [];
this.strava = {};
this.min_lat = Number.MAX_VALUE;
this.min_lon = Number.MAX_VALUE;
this.max_lat = -Number.MAX_VALUE;
this.max_lon = -Number.MAX_VALUE;
this.focus = undefined;
this.status = undefined;
this.tab = 'map';
this.load().catch(function (e) {
console.log('load error', e);
});
this.to_build = '🏠';
}
async load() {
console.log('load');
let emojis = await (await fetch('emojis.json')).json();
emojis = Object.values(emojis)
.map((x) => Object.values(x))
.flat();
let today = new Date();
let date_index =
today.getYear() * 356 + today.getMonth() * 31 + today.getDate();
this.emoji_of_the_day = emojis[(date_index * 123457) % emojis.length];
this.user = await tfrpc.rpc.getUser();
this.url = (await tfrpc.rpc.url()).split('?')[0];
try {
await this.update_credentials();
} catch (e) {
console.log('update_credentials failed', e);
}
try {
await this.update_activities();
} catch (e) {
console.log('update_activities failed', e);
}
await this.acquire_ssb_identity();
if (this.whoami && this.activities?.length) {
await this.sync_activities();
}
await this.get_activities_from_ssb();
}
/* https://gist.github.com/jcouyang/632709f30e12a7879a73e9e132c0d56b?permalink_comment_id=3591045#gistcomment-3591045 */
async promise_all(promises, max_concurrent) {
let index = 0;
let results = [];
async function exec_thread() {
while (index < promises.length) {
const current = index++;
results[current] = await promises[current];
}
}
const threads = [];
for (let thread = 0; thread < max_concurrent; thread++) {
threads.push(exec_thread());
}
await Promise.all(threads);
return results;
}
async get_activities_from_ssb() {
this.status = {text: 'loading activities'};
this.loaded_activities = [];
let rows = await tfrpc.rpc.query(
`
SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id
FROM messages_fts('"gg-activity"')
JOIN messages ON messages.rowid = messages_fts.rowid,
json_each(messages.content, '$.mentions') as mention
WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND
json_extract(mention.value, '$.name') = 'activity_data'
ORDER BY messages.timestamp DESC
`,
[]
);
this.status = {text: 'loading activity data'};
let authors = rows.map((x) => x.author);
let blobs = await this.promise_all(
rows.map((x) => tfrpc.rpc.get_blob(x.blob_id)),
8
);
this.status = {text: 'processing activity data'};
for (let [index, blob] of blobs.entries()) {
let activity;
try {
activity = JSON.parse(blob);
} catch {
activity = gpx_parse(blob);
}
if (activity) {
activity.author = authors[index];
this.loaded_activities.push(activity);
}
}
this.status = {text: 'calculating balance'};
rows = await tfrpc.rpc.query(
`
SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity'
`,
[this.whoami]
);
let currency = rows[0].currency;
rows = await tfrpc.rpc.query(
`
SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place'
`,
[this.whoami]
);
let spent = rows[0].cost;
this.currency = currency - spent;
this.status = {text: 'getting placed emojis'};
rows = await tfrpc.rpc.query(`
SELECT messages.content
FROM messages_fts('"gg-place"')
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json_extract(messages.content, '$.type') = 'gg-place'
ORDER BY messages.timestamp
`);
for (let row of rows) {
console.log(row.content);
let content = JSON.parse(row.content);
this.placed_emojis.push({
position: content.position,
emoji: content.emoji,
});
}
console.log(this.placed_emojis);
this.status = undefined;
this.update_map();
}
async sync_activities() {
let ids = this.activities.map(
(x) => `https://www.strava.com/activities/${x.id}`
);
let missing = await tfrpc.rpc.query(
`
WITH my_activities AS (
SELECT json_extract(mention.value, '$.link') AS url
FROM messages, json_each(messages.content, '$.mentions') AS mention
WHERE
author = ? AND
json_extract(messages.content, '$.type') = 'gg-activity' AND
json_extract(mention.value, '$.name') = 'activity_url')
SELECT from_strava.value FROM json_each(?) AS from_strava
LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url
WHERE my_activities.url IS NULL
`,
[this.whoami, JSON.stringify(ids)]
);
console.log('missing = ', missing);
for (let [index, row] of missing.entries()) {
this.status = {
text: 'syncing from strava',
value: index,
max: missing.length,
};
let url = row.value;
let id = url.match(/.*\/(\d+)/)[1];
let response = await fetch(
`https://www.strava.com/api/v3/activities/${id}`,
{
headers: {
Authorization: `Bearer ${this.strava.access_token}`,
},
}
);
let activity = await response.json();
let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity));
let message = {
type: 'gg-activity',
mentions: [
{
link: url,
name: 'activity_url',
},
{
link: blob_id,
name: 'activity_data',
},
],
};
await tfrpc.rpc.appendMessage(this.whoami, message);
}
this.status = undefined;
}
async acquire_ssb_identity() {
let user = await tfrpc.rpc.getUser();
if (!user?.credentials?.session?.name) {
return;
}
let ids = await tfrpc.rpc.getIdentities();
let players = ids.length
? (
await tfrpc.rpc.query(
`
SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value
WHERE
json_extract(messages.content, '$.type') = 'gg-player' AND
json_extract(messages.content, '$.active')
ORDER BY timestamp DESC limit 1
`,
[JSON.stringify(ids)]
)
).map((row) => row.author)
: [];
if (!players.length) {
this.whoami = await tfrpc.rpc.createIdentity();
if (this.whoami) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'gg-player',
active: true,
});
}
} else {
players.sort();
this.whoami = players[0];
}
}
async update_credentials() {
let name = this.user?.credentials?.session?.name;
if (!name) {
return;
}
let shared = await tfrpc.rpc.sharedDatabaseGet(name);
if (shared) {
await tfrpc.rpc.databaseSet('strava', shared);
await tfrpc.rpc.sharedDatabaseRemove(name);
}
this.strava = JSON.parse((await tfrpc.rpc.databaseGet('strava')) || '{}');
if (new Date().valueOf() / 1000 > this.strava.expires_at) {
console.log(
'this looks expired',
new Date().valueOf() / 1000,
'>',
this.strava.expires_at
);
let x = await tfrpc.rpc.refresh_token(this.strava);
if (x) {
this.strava = x;
await tfrpc.rpc.databaseSet('strava', JSON.stringify(x));
} else {
this.strava = null;
}
}
}
async update_activities() {
if (this?.strava?.access_token) {
let response = await fetch(
'https://www.strava.com/api/v3/athlete/activities',
{
headers: {
Authorization: `Bearer ${this.strava.access_token}`,
},
}
);
this.activities = await response.json();
this.activities.sort((a, b) => a.id - b.id);
}
}
color_to_emoji(color) {
const k_map = [
[k_color_snow, '⬜'],
[k_color_ice, '🟦'],
[k_color_water, '🟦'],
[k_color_dirt, '🟫'],
[k_color_pavement, '⬛'],
[k_color_grass, '🟩'],
[k_color_default, '🟧'],
];
for (let m of k_map) {
if (
m[0][0] == color[0] &&
m[0][1] == color[1] &&
m[0][2] == color[2] &&
m[0][3] == color[3]
) {
return m[1];
}
}
}
activity_bounds(activity) {
let min_lat = Number.MAX_VALUE;
let min_lon = Number.MAX_VALUE;
let max_lat = -Number.MAX_VALUE;
let max_lon = -Number.MAX_VALUE;
if (activity?.map?.polyline) {
for (let pt of polyline.decode(activity.map.polyline)) {
min_lat = Math.min(min_lat, pt[0]);
min_lon = Math.min(min_lon, pt[1]);
max_lat = Math.max(max_lat, pt[0]);
max_lon = Math.max(max_lon, pt[1]);
}
}
if (activity?.segments) {
for (let segment of activity.segments) {
for (let pt of segment) {
min_lat = Math.min(min_lat, pt.lat);
min_lon = Math.min(min_lon, pt.lon);
max_lat = Math.max(max_lat, pt.lat);
max_lon = Math.max(max_lon, pt.lon);
}
}
}
return {
min: {
lat: min_lat,
lng: min_lon,
},
max: {
lat: max_lat,
lng: max_lon,
},
};
}
on_click(event) {
let popup = L.popup()
.setLatLng(event.latlng)
.setContent(
`
<div><a target="_top" href="https://www.google.com/maps/search/?api=1&query=${event.latlng.lat},${event.latlng.lng}">${event.latlng.lat}, ${event.latlng.lng}</a></div>
`
)
.openOn(this.leaflet);
}
async build() {
if (this.popup) {
this.popup.remove();
}
if (!this.marker) {
return;
}
let latlng = this.marker.getLatLng();
let cost = k_store[this.to_build];
if (cost > this.currency) {
alert('Insufficient funds.');
return;
}
let message = {
type: 'gg-place',
position: {lat: latlng.lat, lng: latlng.lng},
emoji: this.to_build,
cost: cost,
};
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
this.marker.remove();
this.placed_emojis.push({
position: {lat: latlng.lat, lng: latlng.lng},
emoji: this.to_build,
});
this.currency -= cost;
return this.update_map();
}
on_marker_click(event) {
this.popup = L.popup()
.setLatLng(event.latlng)
.setContent(
`
${this.to_build} (-${k_store[this.to_build]}) <input type="button" value="Build" onclick="document.getElementById('ggapp').build()"></input>
`
)
.openOn(this.leaflet);
}
snap_to_grid(latlng, fudge, zoom) {
let position = this.leaflet.options.crs.latLngToPoint(
latlng,
zoom ?? this.leaflet.getZoom()
);
position.x = Math.round(position.x / 16) * 16 + (fudge?.x ?? 0);
position.y = Math.round(position.y / 16) * 16 + (fudge?.y ?? 0);
position = this.leaflet.options.crs.pointToLatLng(
position,
zoom ?? this.leaflet.getZoom()
);
return position;
}
on_marker_move(event) {
if (!this.no_snap && this.marker) {
this.no_snap = true;
this.marker.setLatLng(
this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)
);
this.no_snap = false;
}
}
on_zoom(event) {
if (this.marker) {
this.marker.setLatLng(
this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)
);
}
}
on_mouse_down(event) {
if (this.marker) {
this.marker.remove();
this.marker = undefined;
}
if (this.to_build) {
this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), {
icon: L.divIcon({className: 'build-icon'}),
draggable: true,
}).addTo(this.leaflet);
this.marker.on({click: this.on_marker_click.bind(this)});
this.marker.on({drag: this.on_marker_move.bind(this)});
}
}
async update_map() {
let map = this.shadowRoot.getElementById('map');
if (!map || !this.loaded_activities.length) {
this.leaflet = undefined;
this.grid_layer = undefined;
return;
}
if (!this.leaflet) {
this.leaflet = L.map(map, {
attributionControl: false,
maxZoom: 16,
bounceAtZoomLimits: false,
});
this.leaflet.on({contextmenu: this.on_click.bind(this)});
this.leaflet.on({click: this.on_mouse_down.bind(this)});
this.leaflet.on({zoom: this.on_zoom.bind(this)});
}
let self = this;
let grid_layer = L.GridLayer.extend({
createTile: function (coords) {
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
var size = this.getTileSize();
tile.width = size.x;
tile.height = size.y;
var context = tile.getContext('2d');
context.font = '10pt sans';
let bounds = this._tileCoordsToBounds(coords);
let degrees = 360.0 / 2 ** coords.z;
let ul = bounds.getNorthWest();
let lr = bounds.getSouthEast();
let mini = document.createElement('canvas');
mini.width = Math.floor(size.x / 16.0);
mini.height = Math.floor(size.y / 16.0);
let mini_context = mini.getContext('2d');
let image_data = context.getImageData(0, 0, mini.width, mini.height);
for (let activity of self.loaded_activities) {
self.draw_activity_to_tile(
image_data,
mini.width,
mini.height,
ul,
lr,
activity
);
}
context.textAlign = 'left';
context.textBaseline = 'bottom';
for (let x = 0; x < mini.width; x++) {
for (let y = 0; y < mini.height; y++) {
let start = (y * mini.width + x) * 4;
let pixel = self.color_to_emoji(
image_data.data.slice(start, start + 4)
);
if (pixel) {
//context.fillRect(x * size.x / mini.width, y * size.y / mini.height, size.x / mini.width, size.y / mini.height);
context.fillText(
pixel,
(x * size.x) / mini.width,
(y * size.y) / mini.height + mini.height
);
}
}
}
for (let placed of self.placed_emojis) {
let position = self.leaflet.options.crs.latLngToPoint(
self.snap_to_grid(placed.position, undefined, coords.z),
coords.z
);
let tile_x = Math.floor(position.x / size.x);
let tile_y = Math.floor(position.y / size.y);
position.x = position.x - tile_x * size.x;
position.y = position.y - tile_y * size.y;
if (tile_x == coords.x && tile_y == coords.y) {
//context.fillRect(position.x, position.y, size.x / mini.width, size.y / mini.height);
context.fillText(
placed.emoji,
position.x,
position.y + mini.height
);
}
}
return tile;
},
});
if (this.grid_layer) {
this.grid_layer.redraw();
} else {
this.grid_layer = new grid_layer();
this.grid_layer.addTo(this.leaflet);
}
for (let activity of this.loaded_activities) {
let bounds = this.activity_bounds(activity);
this.min_lat = Math.min(this.min_lat, bounds.min.lat);
this.min_lon = Math.min(this.min_lon, bounds.min.lng);
this.max_lat = Math.max(this.max_lat, bounds.max.lat);
this.max_lon = Math.max(this.max_lon, bounds.max.lng);
}
if (this.focus) {
this.leaflet.fitBounds([this.focus.min, this.focus.max]);
this.focus = undefined;
} else {
this.leaflet.fitBounds([
[this.min_lat, this.min_lon],
[this.max_lat, this.max_lon],
]);
}
}
activity_to_color(activity) {
let color = [0, 0, 0, 255];
switch (activity.sport_type) {
/* Implies snow. */
case 'AlpineSki':
case 'BackcountrySki':
case 'NordicSki':
case 'Snowshoe':
case 'Snowboard':
color = k_color_snow;
break;
/* Implies ice. */
case 'IceSkate':
case 'InlineSkate':
color = k_color_ice;
break;
/* Implies water. */
case 'Canoeing':
case 'Kayaking':
case 'Kitesurf':
case 'Rowing':
case 'Sail':
case 'StandUpPaddling':
case 'Surfing':
case 'Swim':
case 'Windsurf':
color = k_color_water;
break;
/* Implies dirt. */
case 'EMountainBikeRide':
case 'Hike':
case 'MountainBikeRide':
case 'RockClimbing':
case 'TrailRun':
color = k_color_dirt;
break;
/* Implies pavement. */
case 'EBikeRide':
case 'GravelRide':
case 'Handcycle':
case 'Ride':
case 'RollerSki':
case 'Run':
case 'Skateboard':
case 'Badminton':
case 'Tennis':
case 'Velomobile':
case 'Walk':
case 'Wheelchair':
color = k_color_pavement;
break;
/* Grass, maybe? */
case 'Golf':
case 'Soccer':
case 'Squash':
color = k_color_grass;
break;
// Crossfit,
// Elliptical
// HighIntensityIntervalTraining
// Pickleball
// Pilates
// Racquetball
// StairStepper
// TableTennis,
// VirtualRide
// VirtualRow
// VirtualRun
// WeightTraining
// Workout
// Yoga
default:
color = k_color_default;
}
return color;
}
line(image_data, x0, y0, x1, y1, value) {
/* <3 https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
let dx = Math.abs(x1 - x0);
let sx = x0 < x1 ? 1 : -1;
let dy = -Math.abs(y1 - y0);
let sy = y0 < y1 ? 1 : -1;
let error = dx + dy;
while (true) {
if (
x0 >= 0 &&
y0 >= 0 &&
x0 < image_data.width &&
y0 < image_data.height
) {
let base = (y0 * image_data.width + x0) * 4;
image_data.data[base + 0] = value[0];
image_data.data[base + 1] = value[1];
image_data.data[base + 2] = value[2];
image_data.data[base + 3] = value[3];
}
if (x0 == x1 && y0 == y1) {
break;
}
let e2 = 2 * error;
if (e2 >= dy) {
if (x0 == x1) {
break;
}
error += dy;
x0 = Math.round(x0 + sx);
}
if (e2 <= dx) {
if (y0 == y1) {
break;
}
error += dx;
y0 = Math.round(y0 + sy);
}
}
}
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
let color = this.activity_to_color(activity);
if (activity?.map?.polyline) {
let last;
for (let pt of polyline.decode(activity.map.polyline)) {
let px = [
Math.floor((width * (pt[1] - ul.lng)) / (lr.lng - ul.lng)),
Math.floor((height * (pt[0] - ul.lat)) / (lr.lat - ul.lat)),
];
if (last) {
this.line(image_data, last[0], last[1], px[0], px[1], color);
}
last = px;
}
}
if (activity?.segments) {
for (let segment of activity.segments) {
let last;
for (let pt of segment) {
let px = [
Math.floor((width * (pt.lon - ul.lng)) / (lr.lng - ul.lng)),
Math.floor((height * (pt.lat - ul.lat)) / (lr.lat - ul.lat)),
];
if (last) {
this.line(image_data, last[0], last[1], px[0], px[1], color);
}
last = px;
}
}
}
}
async on_upload(event) {
try {
let file = event.srcElement.files[0];
let xml = await file.text();
let gpx = gpx_parse(xml);
let blob_id = await tfrpc.rpc.store_blob(xml);
console.log('blob_id = ', blob_id);
console.log(gpx);
let message = {
type: 'gg-activity',
mentions: [
{
link: `https://${gpx.link}/activity/${gpx.time}`,
name: 'activity_url',
},
{
link: blob_id,
name: 'activity_data',
},
],
};
console.log('id =', this.whoami, 'message = ', message);
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
console.log('appended message', id);
alert('Activity uploaded.');
await this.get_activities_from_ssb();
} catch (e) {
alert(`Error: ${JSON.stringify(e, null, 2)}`);
}
}
upload() {
let input = document.createElement('input');
input.type = 'file';
input.onchange = (event) => this.on_upload(event);
input.click();
}
updated() {
this.update_map();
}
focus_map(activity) {
let bounds = this.activity_bounds(activity);
if (bounds.min.lat < bounds.max.lat && bounds.min.lng < bounds.max.lng) {
this.tab = 'map';
this.focus = bounds;
}
}
render_news() {
return html`
<ul>
${this.loaded_activities.map(
(x) => html`
<li style="cursor: pointer" @click=${() => this.focus_map(x)}>
${x.author} ${x.name ?? x.time}
</li>
`
)}
</ul>
`;
}
render_store_item(item) {
let [emoji, cost] = item;
return html`
<div>
<input type="button" value="${emoji}" @click=${() => (this.to_build = emoji)}></input> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined}
</div>
`;
}
render_store() {
let store = Object.assign({}, k_store);
store[this.emoji_of_the_day] = 5;
return html`
<h2>Store</h2>
<div><b>Your balance:</b> ${this.currency}</div>
${Object.entries(store).map(this.render_store_item.bind(this))}
`;
}
render() {
let header;
if (!this.user?.credentials?.session?.name) {
header = html`<div style="flex: 1 0">
Please <a target="_top" href="/login?return=${this.url}">login</a> to
Tilde Friends, first.
</div>`;
} else if (!this.strava?.access_token) {
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
header = html`
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
<input type="button" value="📁" @click=${this.upload}></input>
</div>
`;
} else {
header = html`
<div>
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<h1>Welcome, ${this.user.credentials.session.name}</h1>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
<input type="button" value="📁" @click=${this.upload}></input>
</div>
<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
</div>
`;
}
let navigation = html`
<style>
#navigation input[type="button"] {
min-width: 3em;
min-height: 3em;
flex: 1 0;
font-size: large;
}
</style>
<div id="navigation" style="display: flex; flex-direction: row">
<input type="button" id="button_map" @click=${() => (this.tab = 'map')} value="🗺Map"></input>
<input type="button" id="button_news" @click=${() => (this.tab = 'news')} value="🏃News"></input>
<input type="button" id="button_friends" @click=${() => (this.tab = 'friends')} value="👫Friends"></input>
<input type="button" id="button_store" @click=${() => (this.tab = 'store')} value="🏗Store"></input>
</div>
`;
let content;
switch (this.tab) {
case 'map':
content = html`<div id="map" style="width: 100%; height: 100%"></div>`;
break;
case 'news':
content = this.render_news();
break;
case 'friends':
content = html`<div>Friends</div>`;
break;
case 'store':
content = this.render_store();
break;
}
return html`
<style>
.build-icon::before {
content: '📍';
border: 2px solid red;
}
</style>
<link rel="stylesheet" href="leaflet.css" />
<div
style="width: 100%; height: 100%; display: flex; flex-direction: column"
>
${header}
<div style="flex: 1 0; overflow: scroll">${content}</div>
${navigation}
</div>
`;
}
}
customElements.define('gg-app', GgAppElement);

View File

@ -1,20 +0,0 @@
const k_client_id = '28276';
const k_client_secret = '3123f1f5afe132d9731111066d1d17bdb22ef27e';
const k_access_token = 'f753e77764c26252bd2d80e7c5cc17ace51a8864';
const k_refresh_token = 'f58d8e1b5a3ec3bf96e681589d5014f9a294f5a4';
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
export async function refresh_token(token) {
let r = await fetch('https://www.strava.com/api/v3/oauth/token', {
method: 'POST',
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&refresh_token=${token.refresh_token}&grant_type=refresh_token`,
});
return r?.body ? JSON.parse(utf8Decode(r.body)) : undefined;
}
export async function authorization_code(code) {
return await fetch('https://www.strava.com/api/v3/oauth/token', {
method: 'POST',
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&code=${code}&grant_type=authorization_code`,
});
}

View File

@ -2,8 +2,9 @@
<html>
<head>
<base target="_top" />
<link rel="stylesheet" href="tildefriends.css" />
</head>
<body style="color: #fff">
<body>
<tf-collections-app></tf-collections-app>
<script>
window.litDisableBundleWarning = true;

View File

@ -5,6 +5,7 @@ class TfCollectionElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
category: {type: String},
collection: {type: Object},
selected_id: {type: String},
is_creating: {type: Boolean},
@ -75,9 +76,10 @@ class TfCollectionElement extends LitElement {
render() {
let self = this;
return html`
<span style="display: inline-flex; flex-direction: row">
<link rel="stylesheet" href="tildefriends.css"/>
<span class="inline-flex-row">
<select @change=${this.on_selected} id="select" value=${this.selected_id}>
<option value="" ?selected=${this.selected_id === ''} disabled hidden>(select)</option>
<option value="" ?selected=${this.selected_id === ''} disabled hidden>(select ${this.category})</option>
${Object.values(this.collection ?? {})
.sort((x, y) => x.name.localeCompare(y.name))
.map(
@ -91,22 +93,22 @@ class TfCollectionElement extends LitElement {
)}
</select>
<span ?hidden=${!this.is_renaming || !this.whoami}>
<span style="display: inline-flex; flex-direction: row; margin-left: 8px; margin-right: 8px">
<span class="inline-flex-row" style="margin-left: 8px; margin-right: 8px">
<label for="rename_name">🏷Rename to:</label>
<input type="text" id="rename_name"></input>
<button @click=${this.on_rename}>Rename ${this.type}</button>
<button @click=${() => (self.is_renaming = false)}>x</button>
</span>
</span>
<button @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button>
<button @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button>
<button class="yellow" @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button>
<button class="red" @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button>
<span ?hidden=${!this.is_creating || !this.whoami}>
<label for="create_name">New ${this.type} name:</label>
<input type="text" id="create_name"></input>
<button @click=${this.on_create}>Create ${this.type}</button>
<button @click=${() => (self.is_creating = false)}>x</button>
</span>
<button @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button>
<button class="green" @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button>
</span>
`;
}

View File

@ -28,6 +28,7 @@ class TfIdentityPickerElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="tildefriends.css" />
<select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map(
(id) =>

View File

@ -255,12 +255,22 @@ class TfCollectionsAppElement extends LitElement {
render() {
let self = this;
return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style>
.toc:hover {
.toc-item {
white-space: nowrap;
cursor: pointer;
}
.toc-item:hover {
background-color: #0cc;
}
.toc.selected {
.toc-item.selected {
background-color: #088;
font-weight: bold;
}
.table-of-contents {
flex: 0 0;
margin-right: 16px;
}
</style>
<div>
@ -272,6 +282,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection
.collection=${this.wikis}
whoami=${this.whoami}
category="wiki"
selected_id=${this.wiki?.id}
@create=${this.on_wiki_create}
@rename=${this.on_wiki_rename}
@ -284,6 +295,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection
.collection=${this.wiki_docs}
whoami=${this.whoami}
category="document"
selected_id=${this.wiki_doc &&
this.wiki_doc?.parent == this.wiki?.id
? this.wiki_doc?.id
@ -298,9 +310,9 @@ class TfCollectionsAppElement extends LitElement {
<div ?hidden=${!this.wiki?.editors || !this.expand_editors}>
<div>
<ul>
${this.wiki?.editors.map((id) => html`<li><button ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
${this.wiki?.editors.map((id) => html`<li><button class="red" ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
<li>
<button @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<button class="green" @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<div ?hidden=${!this.adding_editor}>
<label for="add_editor">Add Editor:</label>
<input type="text" id="add_editor"></input>
@ -312,18 +324,19 @@ class TfCollectionsAppElement extends LitElement {
</div>
</div>
</div>
<div style="display: flex; flex-direction: row">
<div style="flex: 0 0">
<div class="flex-row">
<div class="box table-of-contents">
${Object.values(this.wikis || {})
.sort((x, y) => x.name.localeCompare(y.name))
.map(
(wiki) => html`
<div
class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}"
style="white-space: nowrap; cursor: pointer"
class="toc-item ${self.wiki?.id === wiki.id
? 'selected'
: ''}"
@click=${() => self.on_wiki_changed({detail: {value: wiki}})}
>
${wiki.name}
${self.wiki?.id === wiki.id ? '' : '>'} ${wiki.name}
</div>
<ul>
${Object.values(self.wiki_docs || {})
@ -332,10 +345,10 @@ class TfCollectionsAppElement extends LitElement {
.map(
(doc) => html`
<li
class="toc ${self.wiki_doc?.id === doc.id
class="toc-item ${self.wiki_doc?.id === doc.id
? 'selected'
: ''}"
style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem"
style="list-style: none; text-indent: -1rem"
@click=${() =>
self.on_wiki_doc_changed({detail: {value: doc}})}
>

View File

@ -84,7 +84,11 @@ class TfWikiDocElement extends LitElement {
async load_blob() {
let blob = await tfrpc.rpc.get_blob(this.value?.blob);
if (blob.endsWith('.box')) {
if (!blob) {
console.warn(
"no blob found, we're going to assume the document is empty (load_blob())"
);
} else if (blob.endsWith('.box')) {
let d = await tfrpc.rpc.try_decrypt(this.whoami, blob);
if (d) {
blob = d;
@ -253,85 +257,43 @@ class TfWikiDocElement extends LitElement {
let self = this;
let thumbnail_ref = this.thumbnail(this.blob);
return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style>
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
a:link { color: #268bd2 }
a:visited { color: #6c71c4 }
a:hover { color: #859900 }
a:active { color: #2aa198 }
#editor-text-area {
background-color: #00000040;
color: white;
style="flex: 1 1;
min-height: 10em;
font-size: larger;
${this.value?.private ? 'border: 4px solid #800' : ''}
</style>
<div style="display: inline-flex; flex-direction: row">
<button
?disabled=${!this.whoami || this.is_editing}
@click=${() => (self.is_editing = true)}
>
Edit
</button>
<button
?disabled=${this.blob == this.blob_original}
@click=${this.on_save_draft}
>
Save Draft
</button>
<button
?disabled=${this.blob == this.blob_original && !this.value?.draft}
@click=${this.on_publish}
>
Publish
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>
Discard
</button>
<button
?disabled=${!this.is_editing}
@click=${() =>
(self.value = Object.assign({}, self.value, {
private: !self.value.private,
}))}
>
${this.value?.private ? 'Make Public' : 'Make Private'}
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>
Publish Blog
</button>
<div class="inline-flex-row">
<button ?disabled=${!this.whoami || this.is_editing} @click=${() => (self.is_editing = true)}>Edit</button>
<button ?disabled=${this.blob == this.blob_original} @click=${this.on_save_draft}>Save Draft</button>
<button ?disabled=${this.blob == this.blob_original && !this.value?.draft} @click=${this.on_publish}>Publish</button>
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>Discard</button>
<button ?disabled=${!this.is_editing} @click=${() => (self.value = Object.assign({}, self.value, {private: !self.value.private}))}>${this.value?.private ? 'Make Public' : 'Make Private'}</button>
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>Publish Blog</button>
</div>
<div ?hidden=${!this.value?.private} style="color: #800">
🔒 document is private
</div>
<div
style="display: flex; flex-direction: row; ${this.value?.private
? 'border-top: 4px solid #800'
: ''}"
>
<div ?hidden=${!this.value?.private} style="color: #800">🔒 document is private</div>
<div class="flex-column" ${this.value?.private ? 'border-top: 4px solid #800' : ''}">
<textarea
rows="25"
?hidden=${!this.is_editing}
style="flex: 1 1; min-height: 10em; ${this.value?.private
? 'border: 4px solid #800'
: ''}"
id="editor-text-area"
@input=${this.on_edit}
@paste=${this.paste}
.value=${this.blob ?? ''}
></textarea>
<div style="flex: 1 1">
<div
?hidden=${!this.is_editing}
style="border: 1px solid #fff; border-radius: 1em; padding: 0.5em"
>
<img
?hidden=${!thumbnail_ref}
style="max-width: 128px; max-height: 128px; float: right"
src="/${thumbnail_ref}/view"
/>
<h1 ?hidden=${!this.title(this.blob)}>
${unsafeHTML(this.markdown(this.title(this.blob)))}
</h1>
.value=${this.blob ?? ''}></textarea>
<div style="flex: 1 1; margin-top: 16px">
<div ?hidden=${!this.is_editing} class="box">
Summary
<img ?hidden=${!thumbnail_ref} style="max-width: 128px; max-height: 128px; float: right" src="/${thumbnail_ref}/view">
<h1 ?hidden=${!this.title(this.blob)}>${unsafeHTML(this.markdown(this.title(this.blob)))}</h1>
${unsafeHTML(this.markdown(this.summary(this.blob)))}
</div>
${unsafeHTML(this.markdown(this.blob))}

115
apps/wiki/tildefriends.css Normal file
View File

@ -0,0 +1,115 @@
/*
* Tilde Friends core stylesheet
* This is a prototype; things may change based on feedback.
*
* This Software is an external library that is part of
* Tilde Friends and is shared under the MIT license.
*
* Inject this file in your app at tildefriends.css
* and use this tag to import it:
* <link rel="stylesheet" href="tildefriends.css"/>
*
* Revision 0 / 2024 M02 19
*/
body {
color: white;
font-family: sans-serif;
}
button,
.button,
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px;
&.red {
background-color: #bd1e24;
color: white;
}
&.green {
background-color: #18922d;
color: white;
}
&.blue {
background-color: #0067a7;
color: white;
}
&.yellow {
background-color: #ee9600;
color: black;
}
&:hover {
filter: brightness(0.75);
}
}
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ffffff40;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #ffffff20;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
flex-direction: row;
}
.inline-flex-row {
display: inline-flex;
flex-direction: row;
}
.box {
background-color: #00000020;
border: 1px solid grey;
border-radius: 8px;
padding: 16px;
margin: 4px;
}

View File

@ -1662,6 +1662,29 @@ async function sourcePretty() {
}
}
function toggleVisibleWhitespace() {
let editor_style = document.getElementById('editor_style');
/*
* There is likely a better way to do this, but stomping on the CSS was
* the easiest to wrangle at the time.
*/
if (editor_style.innerHTML.length) {
editor_style.innerHTML = '';
} else {
editor_style.innerHTML = css`
.cm-trailingSpace {
background-color: unset !important;
}
.cm-highlightTab {
background-image: unset !important;
}
.cm-highlightSpace:before {
content: unset !important;
}
`;
}
}
// TODOC
window.addEventListener('load', function () {
window.addEventListener('hashchange', hashChange);
@ -1687,6 +1710,9 @@ window.addEventListener('load', function () {
document
.getElementById('pretty')
.addEventListener('click', () => sourcePretty());
document
.getElementById('whitespace')
.addEventListener('click', () => toggleVisibleWhitespace());
document
.getElementById('trace_button')
.addEventListener('click', function (event) {

View File

@ -1,46 +1,156 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>Tilde Friends</title>
<link type="text/css" rel="stylesheet" href="/static/style.css">
<link type="text/css" rel="stylesheet" href="/static/w3.css">
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" href="/static/style.css" />
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
function set_access_key_title(event) {
if (!event.srcElement.title) {
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || ('⌨️' + event.srcElement.accessKey)).toUpperCase()}]`;
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || '⌨️' + event.srcElement.accessKey).toUpperCase()}]`;
}
}
</script>
</head>
<body style="display: flex; flex-flow: column; width: 100vw; height: 100vh; position: absolute; max-width: 100%; max-height: 100%">
<body
style="
display: flex;
flex-flow: column;
width: 100vw;
height: 100vh;
position: absolute;
max-width: 100%;
max-height: 100%;
"
>
<tf-navigation></tf-navigation>
<div id="content" class="hbox" style="flex: 1 0; overflow: auto">
<div id="editPane" class="vbox" style="flex: 0 1 100%; display: none; overflow: auto">
<div
id="editPane"
class="vbox"
style="flex: 0 1 100%; display: none; overflow: auto"
>
<div class="navigation w3-bar" style="display: flex">
<button class="w3-bar-item w3-button w3-blue" id="closeEditor" name="closeEditor" accesskey="c" onmouseover="set_access_key_title(event)" data-tip="Close the editor">Close</button>
<button class="w3-bar-item w3-button w3-blue" id="save" name="save" accesskey="s" onmouseover="set_access_key_title(event)" data-tip="Save the app under the given path">Save</button>
<button class="w3-bar-item w3-button w3-blue" id="icon" name="icon" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Set an icon/emoji for the app">📦</button>
<button class="w3-bar-item w3-button w3-blue" id="export" name="export" accesskey="e" onmouseover="set_access_key_title(event)" data-tip="Export app to .zip file">Export</button>
<button class="w3-bar-item w3-button w3-blue" id="import" name="import" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Import app from .zip file">Import</button>
<button class="w3-bar-item w3-button w3-blue" id="pretty" name="pretty" accesskey="p" onmouseover="set_access_key_title(event)" data-tip="Clean up source formatting">🧼</button>
<input class="w3-bar-item w3-input w3-border w3-blue" type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
<button class="w3-bar-item w3-button w3-blue" id="delete" name="delete" accesskey="d" onmouseover="set_access_key_title(event)" data-tip="Delete the app">Delete</button>
<button class="w3-bar-item w3-button w3-blue" id="trace_button" accesskey="t" onmouseover="set_access_key_title(event)" data-tip="Open a performance trace for the server">Trace</button>
<button
class="w3-bar-item w3-button w3-blue"
id="closeEditor"
name="closeEditor"
accesskey="c"
onmouseover="set_access_key_title(event)"
data-tip="Close the editor"
>
Close
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="save"
name="save"
accesskey="s"
onmouseover="set_access_key_title(event)"
data-tip="Save the app under the given path"
>
Save
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="icon"
name="icon"
accesskey="j"
onmouseover="set_access_key_title(event)"
data-tip="Set an icon/emoji for the app"
>
📦
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="export"
name="export"
accesskey="e"
onmouseover="set_access_key_title(event)"
data-tip="Export app to .zip file"
>
Export
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="import"
name="import"
accesskey="i"
onmouseover="set_access_key_title(event)"
data-tip="Import app from .zip file"
>
Import
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="pretty"
name="pretty"
accesskey="p"
onmouseover="set_access_key_title(event)"
data-tip="Clean up source formatting"
>
🧼
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="whitespace"
name="whitespace"
accesskey="w"
onmouseover="set_access_key_title(event)"
data-tip="Toggle visible whitespace"
>
</button>
<input
class="w3-bar-item w3-input w3-border w3-blue"
type="text"
id="name"
name="name"
style="flex: 1 1; min-width: 1em"
/>
<button
class="w3-bar-item w3-button w3-blue"
id="delete"
name="delete"
accesskey="d"
onmouseover="set_access_key_title(event)"
data-tip="Delete the app"
>
Delete
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="trace_button"
accesskey="t"
onmouseover="set_access_key_title(event)"
data-tip="Open a performance trace for the server"
>
Trace
</button>
</div>
<div class="hbox" style="flex: 1 1; overflow: auto">
<div style="overflow: auto">
<tf-files-pane style="overflow: auto"></tf-files-pane>
</div>
<div style="flex: 1 1; overflow: auto"><div id="editor" style="width: 100%; height: 100%"></div></div>
<div style="flex: 1 1; overflow: auto">
<style id="editor_style"></style>
<div id="editor" style="width: 100%; height: 100%"></div>
</div>
</div>
</div>
<div id="viewPane" class="vbox" style="flex: 0 1 100%; overflow: auto">
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
<iframe
id="document"
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
style="width: 100%; height: 100%; border: 0"
></iframe>
</div>
</div>
<script>window.litDisableBundleWarning = true;</script>
<script>
window.litDisableBundleWarning = true;
</script>
<script src="/static/client.js" type="module"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

547
deps/codemirror_src/package-lock.json generated vendored
View File

@ -75,9 +75,9 @@
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.1.tgz",
"integrity": "sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
@ -88,24 +88,6 @@
"@lezer/javascript": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/javascript": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
"integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
@ -128,19 +110,6 @@
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/language/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/language/node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
},
"node_modules/@codemirror/lint": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
@ -151,11 +120,6 @@
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/lint/node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
@ -166,11 +130,6 @@
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search/node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
@ -188,24 +147,72 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz",
"integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==",
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.1.tgz",
"integrity": "sha512-sBfP4rniPBRQzNakwuQEqjEuiJDWJyF2kqLLqij4WXRoVwPPJfjx966Eq3F7+OPQxDtMt/Q9MWLoZLWjeveBlg==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/view/node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz",
"integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@codemirror/view/node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz",
"integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lezer/common": {
"version": "1.2.1",
@ -222,14 +229,6 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/css/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/highlight": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
@ -239,21 +238,23 @@
}
},
"node_modules/@lezer/html": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.8.tgz",
"integrity": "sha512-EXseJ3pUzWxE6XQBQdqWHZqqlGQRSuNMBcLb6mZWS2J2v+QZhOObD+3ZIKIcm59ntTzyor4LqFTb72iJc3k23Q==",
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.9.tgz",
"integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/html/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"node_modules/@lezer/javascript": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
"integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
"dependencies": {
"@lezer/common": "^1.0.0"
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/json": {
@ -266,7 +267,7 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/json/node_modules/@lezer/lr": {
"node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
@ -341,27 +342,6 @@
}
}
},
"node_modules/@rollup/pluginutils/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
@ -458,6 +438,30 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
@ -494,11 +498,45 @@
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
@ -513,6 +551,17 @@
"@codemirror/view": "^6.0.0"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@ -521,6 +570,11 @@
"node": ">=0.10.0"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -534,6 +588,25 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
@ -548,15 +621,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-builtin-module/node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"engines": {
"node": ">=6"
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-module": {
@ -564,6 +637,31 @@
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -580,52 +678,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/hasown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/resolve/node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/resolve/node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
@ -657,54 +709,7 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/serialize-javascript/node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/serialize-javascript/node_modules/safe-buffer": {
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
@ -724,16 +729,60 @@
}
]
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/smob": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
"dev": true
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/terser": {
"version": "5.27.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz",
"integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==",
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
"integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
@ -748,106 +797,10 @@
"node": ">=10"
}
},
"node_modules/terser/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/terser/node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/terser/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/terser/node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/terser/node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/terser/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/terser/node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
}
}
}

View File

@ -1,17 +0,0 @@
FROM alpine:3.7 AS base
WORKDIR /src
RUN apk --update add alpine-sdk cmake bash
COPY . ./
WORKDIR /src/build
# Debug
FROM base
RUN cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On
RUN cmake --build .
RUN ctest -VV
# Release
FROM base
RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=On
RUN cmake --build .
RUN ctest -VV

View File

@ -1,7 +0,0 @@
version: 2
jobs:
build:
machine: true
steps:
- checkout
- run: docker build -f .circleci/Dockerfile.test .

View File

@ -1 +0,0 @@
.gitignore

View File

@ -1,18 +0,0 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.coffee]
indent_style = space
[{package.json,*.yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@ -1,6 +0,0 @@
*.sw[a-p]
/bin/
/build/
/.tup/
/*.o
/*.a

View File

@ -1,43 +0,0 @@
cmake_minimum_required (VERSION 3.8)
project (xopt)
add_library (xopt STATIC "${CMAKE_CURRENT_SOURCE_DIR}/xopt.c")
if (APPLE OR UNIX)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic -std=c11 -D_XOPEN_SOURCE=600 -fdiagnostics-color=always -fvisibility=hidden")
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -O0")
set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast")
endif ()
function (_xopt_test name)
if (NOT TARGET "xopt-test-${name}")
add_executable ("xopt-test-${name}" "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.c")
target_link_libraries ("xopt-test-${name}" xopt)
endif ()
set (testname "xopt-${name}-test")
set (testnum 1)
while (TEST "${testname}-${testnum}")
math (EXPR testnum "${testnum}+1")
endwhile ()
add_test (
NAME "${testname}-${testnum}"
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test/test-case.sh" $<TARGET_FILE:xopt-test-${name}> "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}-${testnum}.out" ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test")
endfunction ()
if (BUILD_TESTING)
enable_testing ()
_xopt_test (simple --some-int=10 --some-double=14.5 foo bar -- --some-other=20)
_xopt_test (macro --some-int=10 --some-double=14.5 foo bar -- --some-other=20)
_xopt_test (required --some-int=10 --some-double=14.5 --some-required=1337 foo bar -- --some-other=20)
_xopt_test (optional-longarg -i 10 -d 14.5 foo bar -- --some-other=20)
_xopt_test (autohelp --help -- --is-not-passed ignoreme)
_xopt_test (sloppyshorts -i10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 -s "some string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -m -m -m -m -m foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
endif ()

9
deps/xopt/README.md vendored
View File

@ -1,9 +0,0 @@
# XOpt [![CircleCI](https://circleci.com/gh/Qix-/xopt.svg?style=svg)](https://circleci.com/gh/Qix-/xopt)
The sane answer to POpt.
XOpt is a command line argument parsing library written in ANSI C. XOpt
accepts arguments in GNU format and focuses on clean definition, taking stress
off the implementation.
# License
Originally by Josh Junon, released under [CC0](https://creativecommons.org/publicdomain/zero/1.0/). Go nuts.

2099
deps/xopt/snprintf.c vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
/*-test
/*.o

View File

@ -1,12 +0,0 @@
args: «--help» «--» «--is-not-passed» «ignoreme»
usage: autohelp-test-case [opts...] [--] [extras...]
Tests the simple parser macro
-i, --some-int=n Some integer value. Can set to whatever number you like.
-f Some float value.
--some-double=n Some double value.
-?, --help Shows this help message
[end of arguments]

View File

@ -1,100 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
0,
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
0,
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
"autohelp-test-case",
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (!err) {
err = config.help
? "--help was passed but autohelp didn't fire"
: "--help was not passed - it is required for this test case";
}
fprintf(stderr, "Error: %s\n", err);
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 0;
goto exit;
}

View File

@ -1,11 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

116
deps/xopt/test/macro.c vendored
View File

@ -1,116 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short option parameters must be separated, not condensed: -ssome string

View File

@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-s» «some string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short options cannot be combined: -mm

View File

@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short options cannot be combined: -mm

View File

@ -1,14 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-m» «-m» «-m» «-m» «-m» «foo» «bar» «--» «--is-not-passed» «ignoreme»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someString: some string
multiCount: 6
help: 0
extra count: 4
- foo
- bar
- --is-not-passed
- ignoreme

View File

@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_SLOPPYSHORTS | XOPT_CTX_NOCONDENSE,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_NOCONDENSE,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,11 +0,0 @@
args: «-i» «10» «-d» «14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@ -1,116 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
0,
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
0,
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
0,
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"macro-test [opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,12 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «--some-required=1337» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someRequired: 1337
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@ -1,128 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
int someRequired;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-required",
'r',
offsetof(SimpleConfig, someRequired),
0,
XOPT_TYPE_INT | XOPT_REQUIRED,
"n",
"Some value"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someRequired = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someRequired, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,11 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@ -1,128 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int result;
const char *err;
xoptContext *ctx;
SimpleConfig config;
const char **extras = 0;
const char **extrasPtr = 0;
int extraCount;
result = 0;
err = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
/* create context */
ctx = xopt_context("xopt-test", options,
XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, &err);
if (err) {
fprintf(stderr, "Error: %s\n", err);
result = 1;
goto exit;
}
/* parse */
extraCount = xopt_parse(ctx, argc, argv, &config, &extras, &err);
if (err) {
fprintf(stderr, "Error: %s\n", err);
result = 2;
goto exit;
}
/* help? */
if (config.help) {
xoptAutohelpOptions opts;
opts.usage = "[options] [extras...]";
opts.prefix = "A simple demonstration of the XOpt options parser library.";
opts.suffix = "End argument list.";
opts.spacer = 10;
xopt_autohelp(ctx, stderr, &opts, &err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit:
if (extras) free(extras); /* DO NOT free individual strings */
if (ctx) free(ctx); /* they point to argv strings */
return result;
}

View File

@ -1,14 +0,0 @@
args: «-i10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someString: some string
multiCount: 6
help: 0
extra count: 4
- foo
- bar
- --is-not-passed
- ignoreme

View File

@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_SLOPPYSHORTS,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@ -1,46 +0,0 @@
#!/bin/bash
#
# To be used with CMake builds located in <xopt>/build/
#
set -uo pipefail
exec >&2
casebin="$1"
caseout="$2"
shift 2
function die {
echo -e "error: $*"
exit 1
}
cd "$(dirname "$0")"
if [ -z "$casebin" ]; then
die 'no test case executable specified'
fi
if [ -z "$caseout" ]; then
die 'no test case output (some-case.out) specified'
fi
if [ ! -x "$casebin" ]; then
die "test case does not exist or is not executable: $casebin"
fi
if [ ! -f "$caseout" ]; then
die "test case expected output file does not exist: $caseout"
fi
output="$("$casebin" "$@" 2>&1)"
r=$?
if [ $r -eq 139 ]; then
die "xopt test case failed with SEGFAULT ($r)"
fi
diff="$(diff -U0 -d -t "$caseout" - <<< "$output" 2>&1)"
if [ ! -z "$diff" ]; then
die "xopt test case didn't match expected output: '$caseout'\n$diff"
fi

584
deps/xopt/xopt.c vendored
View File

@ -1,584 +0,0 @@
/**
* XOpt - command line parsing library
*
* Copyright (c) 2015-2019 Josh Junon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef XOPT_NOSTANDARD
# define HAVE_STDARG_H 1
# define HAVE_STDLIB_H 1
# define HAVE_ASPRINTF_H 1
# define vasprintf rpl_vasprintf
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
#endif
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "./xopt.h"
#include "./snprintf.c"
#define EXTRAS_INIT 10
#define ERRBUF_SIZE 1024 * 4
static char errbuf[ERRBUF_SIZE];
struct xoptContext {
const xoptOption *options;
long flags;
const char *name;
bool doubledash;
size_t options_count;
bool *required;
jmp_buf *jmp;
};
static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...);
static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv,
int *argi, void *data, const char **err);
static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount,
size_t *extrasCapac, const char **err);
static int _xopt_get_size(const char *arg);
static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len,
int size, const xoptOption **option, size_t *option_index);
static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val,
bool longArg, const char **err);
static void _xopt_default_callback(const char *value, void *data,
const xoptOption *option, bool longArg, const char **err);
xoptContext* xopt_context(const char *name, const xoptOption *options, long flags,
const char **err) {
xoptContext* ctx;
*err = 0;
/* malloc context and check */
ctx = malloc(sizeof(xoptContext));
if (!ctx) {
ctx = 0;
_xopt_set_err(NULL, err, "could not allocate context");
} else {
const xoptOption *cur;
ctx->options = options;
ctx->flags = flags;
ctx->name = name;
ctx->doubledash = false;
ctx->required = NULL;
ctx->jmp = NULL;
ctx->options_count = 0;
cur = options;
for (; cur->longArg || cur->shortArg; cur++) ++ctx->options_count;
}
return ctx;
}
static int _xopt_parse_impl(xoptContext *ctx, int argc, const char **argv, void *data,
const char ***inextras, const char **err, int *extrasCount, size_t *extrasCapac,
const char ***extras, int *argi) {
int parseResult;
size_t i;
*err = 0;
*argi = 0;
*extrasCount = 0;
*extrasCapac = EXTRAS_INIT;
*extras = malloc(sizeof(**extras) * EXTRAS_INIT);
jmp_buf jmp;
ctx->jmp = &jmp;
if (setjmp(jmp)) {
goto end;
}
/* check if extras malloc'd okay */
if (!*extras) {
_xopt_set_err(ctx, err, "could not allocate extras array");
}
/* increment argument counter if we aren't
instructed to check argv[0] */
if (!(ctx->flags & XOPT_CTX_KEEPFIRST)) {
++(*argi);
}
/* set up required parameters list */
ctx->required = malloc(sizeof(*ctx->required) * ctx->options_count);
for (i = 0; i < ctx->options_count; i++) {
ctx->required[i] = (ctx->options[i].options & XOPT_REQUIRED) > 0;
}
/* iterate over passed command line arguments */
for (; *argi < argc; (*argi)++) {
/* parse, breaking if there was a failure
parseResult is 0 if option, 1 if extra, or 2 if double-dash was encountered */
parseResult = _xopt_parse_arg(ctx, argc, argv, argi, data, err);
/* is the argument an extra? */
switch (parseResult) {
case 0: /* option */
/* make sure we're super-posix'd if specified to be
(check that no extras have been specified when an option is parsed,
enforcing options to be specific before [extra] arguments */
if ((ctx->flags & XOPT_CTX_POSIXMEHARDER) && *extrasCount) {
_xopt_set_err(ctx, err, "options cannot be specified after arguments: %s", argv[*argi]);
goto end;
}
break;
case 1: /* extra */
/* make sure we have enough room, or realloc if we don't -
check that it succeeded */
_xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err);
/* add extra to list */
(*extras)[(*extrasCount)++] = argv[*argi];
break;
case 2: /* "--" was encountered */
/* nothing to do here - "--" was already handled for us */
break;
}
}
end:
if (!*err) {
for (i = 0; i < ctx->options_count; i++) {
if (ctx->required[i]) {
const xoptOption *opt = &ctx->options[i];
if (opt->longArg) {
_xopt_set_err(ctx, err, "missing required option: --%s", opt->longArg);
} else {
_xopt_set_err(ctx, err, "missing required option: -%c", opt->shortArg);
}
break;
}
}
}
free(ctx->required);
if (!*err) {
/* append null terminator to extras */
_xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err);
if (!*err) {
(*extras)[*extrasCount] = 0;
}
}
if (*err) {
free(*extras);
*inextras = 0;
return 0;
}
*inextras = *extras;
return *extrasCount;
}
int xopt_parse(xoptContext *ctx, int argc, const char **argv, void *data,
const char ***inextras, const char **err) {
/* avoid longjmp clobbering */
int extrasCount;
size_t extrasCapac;
const char **extras;
int argi;
return _xopt_parse_impl(ctx, argc, argv, data, inextras, err, &extrasCount, &extrasCapac, &extras, &argi);
}
void xopt_autohelp(xoptContext *ctx, FILE *stream, const xoptAutohelpOptions *options,
const char **err) {
const xoptOption *o;
size_t i, width = 0, twidth;
const char *nl = "";
size_t spacer = options ? options->spacer : 2;
*err = 0;
/* make sure that if we ever write a call to _set_err() in the future here,
that we won't accidentally cause segfaults - we have an assertion in place
for ctx->jmp != NULL, so we make sure we'd trigger that assertion */
ctx->jmp = NULL;
if (options && options->usage) {
fprintf(stream, "%susage: %s %s\n", nl, ctx->name, options->usage);
nl = "\n";
}
if (options && options->prefix) {
fprintf(stream, "%s%s\n\n", nl, options->prefix);
nl = "\n";
}
/* find max width */
for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) {
o = &ctx->options[i];
twidth = 0;
if (o->longArg) {
twidth += 2 + strlen(o->longArg);
if (o->argDescrip) {
twidth += 1 + strlen(o->argDescrip);
}
}
if (ctx->options[i].shortArg) {
twidth += 2;
}
if (ctx->options[i].shortArg && ctx->options[i].longArg) {
twidth += 2; /* `, ` */
}
width = width > twidth ? width : twidth;
}
/* print */
for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) {
o = &ctx->options[i];
twidth = 0;
if (o->shortArg) {
fprintf(stream, "-%c", o->shortArg);
twidth += 2;
}
if (o->shortArg && o->longArg) {
fprintf(stream, ", ");
twidth += 2;
}
if (o->longArg) {
fprintf(stream, "--%s", o->longArg);
twidth += 2 + strlen(o->longArg);
if (o->argDescrip) {
fprintf(stream, "=%s", o->argDescrip);
twidth += 1 + strlen(o->argDescrip);
}
}
if (o->descrip) {
for (; twidth < (width + spacer); twidth++) {
fprintf(stream, " ");
}
if (o->options & XOPT_REQUIRED) {
fprintf(stream, "(Required) %s\n", o->descrip);
} else {
fprintf(stream, "%s\n", o->descrip);
}
}
}
if (options && options->suffix) {
fprintf(stream, "%s%s\n", nl, options->suffix);
}
}
static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...) {
va_list list;
va_start(list, fmt);
rpl_vsnprintf(&errbuf[0], ERRBUF_SIZE, fmt, list);
va_end(list);
*err = &errbuf[0];
if (ctx != NULL) {
assert(ctx->jmp != NULL);
longjmp(*ctx->jmp, 1);
}
}
static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv,
int *argi, void *data, const char **err) {
int size;
size_t length;
bool isExtra = false;
const xoptOption *option = NULL;
size_t option_index = 0;
const char* arg = argv[*argi];
/* are we in doubledash mode? */
if (ctx->doubledash) {
return 1;
}
/* get argument 'size' (long/short/extra) */
size = _xopt_get_size(arg);
/* adjust to parse from beginning of actual content */
arg += size;
length = strlen(arg);
if (size == 1 && length == 0) {
/* it's just a singular dash - treat it as an extra arg */
return 1;
}
if (size == 2 && length == 0) {
/* double-dash - everything after this is an extra */
ctx->doubledash = 1;
return 2;
}
switch (size) {
int argRequirement;
char *valStart;
case 1: /* short */
/* parse all */
while (length--) {
/* get argument or error if not found and strict mode enabled. */
argRequirement = _xopt_get_arg(ctx, arg++, 1, size, &option, &option_index);
if (!option) {
if (ctx->flags & XOPT_CTX_STRICT) {
_xopt_set_err(ctx, err, "invalid option: -%c", arg[-1]);
}
break;
}
if (argRequirement > 0 && length > 0 && !(ctx->flags & XOPT_CTX_SLOPPYSHORTS)) {
_xopt_set_err(ctx, err, "short option parameters must be separated, not condensed: %s", argv[*argi]);
}
switch (argRequirement) {
case 0: /* flag; doesn't take an argument */
if (length > 0 && (ctx->flags & XOPT_CTX_NOCONDENSE)) {
_xopt_set_err(ctx, err, "short options cannot be combined: %s", argv[*argi]);
}
_xopt_set(ctx, data, option, 0, false, err);
break;
case 1: /* argument is optional */
/* is there another argument, and is it a non-option? */
if (*argi + 1 < argc && _xopt_get_size(argv[*argi + 1]) == 0) {
_xopt_set(ctx, data, option, argv[++*argi], false, err);
} else {
_xopt_set(ctx, data, option, 0, false, err);
}
break;
case 2: /* requires an argument */
/* is it the last in a set of condensed options? */
if (length == 0) {
/* is there another argument? */
if (*argi + 1 < argc) {
/* is the next argument actually an option?
this indicates no value was passed */
if (_xopt_get_size(argv[*argi + 1])) {
_xopt_set_err(ctx, err, "missing option value: -%c",
option->shortArg);
} else {
_xopt_set(ctx, data, option, argv[++*argi], false, err);
}
} else {
_xopt_set_err(ctx, err, "missing option value: -%c",
option->shortArg);
}
} else {
_xopt_set(ctx, data, option, arg, false, err);
length = 0;
}
break;
}
}
break;
case 2: /* long */
/* find first equals sign */
valStart = strchr(arg, '=');
/* is there a value? */
if (valStart) {
/* we also increase valStart here in order to lop off
the equals sign */
length = valStart++ - arg;
/* but not really, if it's null */
if (!*valStart) {
valStart = 0;
}
}
/* get the option */
argRequirement = _xopt_get_arg(ctx, arg, length, size, &option, &option_index);
if (!option) {
_xopt_set_err(ctx, err, "invalid option: --%.*s", length, arg);
} else {
switch (argRequirement) {
case 0: /* flag; doesn't take an argument */
if (valStart) {
_xopt_set_err(ctx, err, "option doesn't take a value: --%s", arg);
}
_xopt_set(ctx, data, option, valStart, true, err);
break;
case 2: /* requires an argument */
if (!valStart) {
_xopt_set_err(ctx, err, "missing option value: --%s", arg);
}
break;
}
_xopt_set(ctx, data, option, valStart, true, err);
}
break;
case 0: /* extra */
isExtra = true;
break;
}
if (option) {
/* indicate that we've seen this option and thus is no longer required */
ctx->required[option_index] = false;
}
return isExtra ? 1 : 0;
}
static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount,
size_t *extrasCapac, const char **err) {
/* have we hit the list size limit? */
if ((size_t) extrasCount == *extrasCapac) {
/* increase capcity, realloc, and check for success */
*extrasCapac += EXTRAS_INIT;
*extras = realloc(*extras, sizeof(**extras) * *extrasCapac);
if (!*extras) {
_xopt_set_err(ctx, err, "could not realloc arguments array");
}
}
}
static int _xopt_get_size(const char *arg) {
int size;
for (size = 0; size < 2; size++) {
if (arg[size] != '-') {
break;
}
}
return size;
}
static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len,
int size, const xoptOption **option, size_t *option_index) {
size_t i;
*option = 0;
/* find the argument */
for (i = 0; i < ctx->options_count; i++) {
const xoptOption *opt = &ctx->options[i];
if ((size == 1 && opt->shortArg == arg[0])
|| (opt->longArg && strlen(opt->longArg) == len && !strncmp(opt->longArg, arg, len))) {
*option_index = i;
*option = opt;
break;
}
}
/* determine the optionality of a value */
if (!*option || (*option)->options & XOPT_TYPE_BOOL) {
return 0;
} else if ((*option)->options & XOPT_PARAM_OPTIONAL) {
return 1;
} else {
return 2;
}
}
static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val,
bool longArg, const char **err) {
/* determine callback */
xoptCallback callback = option->callback ? option->callback : &_xopt_default_callback;
/* dispatch callback */
callback(val, data, option, longArg, err);
/* we check err here instead of relying upon longjmp()
since we can't call _set_err() with a context */
if (*err) {
assert(ctx->jmp != NULL);
longjmp(*ctx->jmp, 1);
}
}
static void _xopt_default_callback(const char *value, void *data,
const xoptOption *option, bool longArg, const char **err) {
void *target;
char *parsePtr = 0;
/* is a value specified? */
if ((!value || !strlen(value)) && !(option->options & XOPT_TYPE_BOOL)) {
/* we reach this point when they specified an optional, non-boolean
option but didn't specify a custom handler (therefore, it's not
optional).
to fix, just remove the optional flag or specify a callback to handle
it yourself.
*/
return;
}
/* get location */
target = ((char*) data) + option->offset;
/* switch on the type */
switch (option->options & 0x3F) {
case XOPT_TYPE_BOOL:
/* booleans are special in that they won't have an argument passed
into this callback */
*((_Bool*) target) = true;
break;
case XOPT_TYPE_STRING:
/* lifetime here works out fine; argv can usually be assumed static-like
in nature */
*((const char**) target) = value;
break;
case XOPT_TYPE_INT:
*((int*) target) = (int) strtol(value, &parsePtr, 0);
break;
case XOPT_TYPE_LONG:
*((long*) target) = strtol(value, &parsePtr, 0);
break;
case XOPT_TYPE_FLOAT:
*((float*) target) = (float) strtod(value, &parsePtr);
break;
case XOPT_TYPE_DOUBLE:
*((double*) target) = strtod(value, &parsePtr);
break;
default: /* something wonky, or the implementation specifies two types */
fprintf(stderr, "warning: XOpt argument type invalid: %ld\n",
option->options & 0x2F);
break;
}
/* check that our parsing functions worked */
if (parsePtr && *parsePtr) {
if (longArg) {
_xopt_set_err(NULL, err, "value isn't a valid number: --%s=%s",
(void*) option->longArg, value);
} else {
_xopt_set_err(NULL, err, "value isn't a valid number: -%c %s",
option->shortArg, value);
}
}
}

225
deps/xopt/xopt.h vendored
View File

@ -1,225 +0,0 @@
/**
* XOpt - command line parsing library
*
* Copyright (c) 2015 Josh Junon.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef XOPT_H__
#define XOPT_H__
#pragma once
#include <stdio.h>
#include <stdbool.h>
struct xoptOption;
#ifndef offsetof
# define offsetof(T, member) (size_t)(&(((T*)0)->member))
#endif
/**
* Callback type for handling values.
* Called when a command line argument has been
* processed.
*/
typedef void (*xoptCallback)(
const char *value, /* string cmd line option */
void *data, /* custom data structure */
const struct xoptOption *option, /* detected option */
bool longArg, /* true if the long-arg version
was used */
const char **err); /* err output */
enum xoptOptionFlag {
XOPT_TYPE_STRING = 0x1, /* const char* type */
XOPT_TYPE_INT = 0x2, /* int type */
XOPT_TYPE_LONG = 0x4, /* long type */
XOPT_TYPE_FLOAT = 0x8, /* float type */
XOPT_TYPE_DOUBLE = 0x10, /* double type */
XOPT_TYPE_BOOL = 0x20, /* boolean (int) type */
XOPT_PARAM_OPTIONAL = 0x40, /* whether the argument value is
optional */
XOPT_REQUIRED = 0x80 /* indicates the flag must be
present on the command line */
};
enum xoptContextFlag {
XOPT_CTX_KEEPFIRST = 0x1, /* don't ignore argv[0] */
XOPT_CTX_POSIXMEHARDER = 0x2, /* options cannot come after
extra arguments */
XOPT_CTX_NOCONDENSE = 0x4, /* don't allow short args to be
condensed (i.e. `ls -laF') */
XOPT_CTX_SLOPPYSHORTS = 0x8, /* allow short arg values to be
directly after the character */
XOPT_CTX_STRICT = 0x10 /* fails on invalid arguments */
};
typedef struct xoptOption {
const char *longArg; /* --long-arg-name, or 0 for short
arg only */
const char shortArg; /* -s hort arg character, or '\0'
for long arg only */
size_t offset; /* offsetof(type, property) for
automatic configuration handler */
xoptCallback callback; /* callback for resolved option
handling */
long options; /* xoptOptionFlag options */
const char *argDescrip; /* --argument=argDescrip (autohelp) */
const char *descrip; /* argument explanation (autohelp) */
} xoptOption;
/* option list terminator */
#define XOPT_NULLOPTION {0, 0, 0, 0, 0, 0, 0}
typedef struct xoptContext xoptContext;
typedef struct xoptAutohelpOptions {
const char *usage; /* usage string, or null */
const char *prefix; /* printed before options, or null */
const char *suffix; /* printed after options, or null */
size_t spacer; /* number of spaces between option and
description */
} xoptAutohelpOptions;
#ifdef __cplusplus
extern "C" {
#endif
/**
* Creates an XOpt context to be used with
* subsequent calls to XOpt functions
*/
xoptContext*
xopt_context(
const char *name, /* name of the argument set (usually
name of the cli binary file/cmd */
const xoptOption *options, /* list of xoptOption objects,
terminated with XOPT_NULLOPTION */
long flags, /* xoptContextFlag flags */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Parses the command line of a program
* and returns the number of non-options
* returned to the `extras' pointer (see
* below)
*/
int
xopt_parse(
xoptContext *ctx, /* previously created XOpt context */
int argc, /* argc, from int main() */
const char **argv, /* argv, from int main() */
void *data, /* a custom data object whos type
corresponds to `.offset' values
specified in the options list;
populated with values interpreted
from the command line */
const char ***extras, /* receives a list of extra non-option
arguments (i.e. files, subcommands,
etc.) - length of which is returned
by the function call */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Generates and prints a help message
* and prints it to a FILE stream.
* If `defaults' is supplied, uses
* offsets (values) defined by the options
* list to show default options
*/
void
xopt_autohelp(
xoptContext *ctx, /* previously created XOpt context */
FILE *stream, /* a stream to print to - if 0,
defaults to `stderr'. */
const xoptAutohelpOptions *options, /* configuration options to tailor
autohelp output */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Generates a default option parser that's sane for most cases.
*
* Assumes there's a `help` property that is boolean-checkable that exists on the
* config pointer passed to `config_ptr` (i.e. does a lookup of `config_ptr->help`).
*
* In the event help is invoked, xopt will `goto xopt_help`. It is up to you to define such
* a label in order to recover. In this case, extrav will still be allocated and will still need to be
* freed.
*
* To be extra clear, you need to free `extrav_ptr` is if `*err_ptr` is not `NULL`.
*
* `name` is the name of the binary you'd like to pass to the context (welcome to use `argv[0]` here),
* `options` is a reference to the xoptOptions array you've specified,
* `config_ptr` is a *pointer* to your configuration instance,
* `argc` and `argv` are the int/const char ** passed into main,
* `extrac_ptr` and `extrav_ptr` are pointers to an `int`/`const char **`
* (so `int*` and `const char ***`, respectively) that receive the parsed extra args
* (note that, unless there is an error, `extrav_ptr` is owned by your program and must
* be `free()`'d when you're done using it, even if there are zero extra arguments),
* and `err_ptr` is a pointer to a `const char *` (so a `const char **`) that receives any error
* strings in the event of a problem. These errors are statically allocated so no need to
* free them. This variable should be initialized to NULL and checked after calling
* `XOPT_SIMPLE_PARSE()`.
*
* `autohelp_file`, `autohelp_usage`, `autohelp_prefix`, `autohelp_suffix` and `autohelp_spacer` are all
* parameters to the `xoptAutohelpOptions` struct (with the exception of `autohelp_file`, which must be a
* `FILE*` reference (e.g. `stdout` or `stderr`) which receives the rendered autohelp text). Consult the
* `xoptAutohelpOptions` struct above for documentation as to valid values for each of these properties.
*/
#define XOPT_SIMPLE_PARSE(name, flags, options, config_ptr, argc, argv, extrac_ptr, extrav_ptr, err_ptr, autohelp_file, autohelp_usage, autohelp_prefix, autohelp_suffix, autohelp_spacer) do { \
xoptContext *_xopt_ctx; \
*(err_ptr) = NULL; \
_xopt_ctx = xopt_context((name), (options), ((flags) ^ XOPT_CTX_POSIXMEHARDER ^ XOPT_CTX_STRICT), (err_ptr)); \
if (*(err_ptr)) break; \
*extrac_ptr = xopt_parse(_xopt_ctx, (argc), (argv), (config_ptr), (extrav_ptr), (err_ptr)); \
if ((config_ptr)->help) { \
xoptAutohelpOptions __xopt_autohelp_opts; \
__xopt_autohelp_opts.usage = (autohelp_usage); \
__xopt_autohelp_opts.prefix = (autohelp_prefix); \
__xopt_autohelp_opts.suffix = (autohelp_suffix); \
__xopt_autohelp_opts.spacer = (autohelp_spacer); \
xopt_autohelp(_xopt_ctx, (autohelp_file), &__xopt_autohelp_opts, (err_ptr)); \
if (*(err_ptr)) goto __xopt_end_free_extrav; \
goto xopt_help; \
} \
if (*(err_ptr)) goto __xopt_end_free_ctx; \
__xopt_end_free_ctx: \
free(_xopt_ctx); \
break; \
__xopt_end_free_extrav: \
free(*(extrav_ptr)); \
free(_xopt_ctx); \
break; \
} while (false)
#ifdef __cplusplus
}
#endif
#endif

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="16"
android:versionName="0.0.16-wip">
android:versionName="0.0.16">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -80,11 +80,13 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in
{
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
size_t length = 0;
const char* cstring = NULL;
const void* data = NULL;
JSValue buffer = JS_UNDEFINED;
if (JS_IsString(argv[0]))
{
data = JS_ToCStringLen(context, &length, argv[0]);
cstring = JS_ToCStringLen(context, &length, argv[0]);
data = cstring;
}
else if ((data = tf_util_try_get_array_buffer(context, &length, argv[0])) != 0)
{
@ -117,6 +119,10 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in
JS_FreeCString(context, headers[i]);
}
JS_FreeValue(context, buffer);
if (cstring)
{
JS_FreeCString(context, cstring);
}
return JS_UNDEFINED;
}

View File

@ -11,8 +11,8 @@
#include "backtrace.h"
#include "sqlite3.h"
#include "xopt.h"
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
@ -41,47 +41,13 @@ struct backtrace_state* g_backtrace_state;
const char* k_db_path_default = "db.sqlite";
#define XOPT_PARSE( \
name, flags, options, config_ptr, argc, argv, extrac_ptr, extrav_ptr, err_ptr, autohelp_file, autohelp_usage, autohelp_prefix, autohelp_suffix, autohelp_spacer) \
do \
{ \
xoptContext* _xopt_ctx; \
*(err_ptr) = NULL; \
_xopt_ctx = xopt_context((name), (options), ((flags) ^ XOPT_CTX_POSIXMEHARDER), (err_ptr)); \
if (*(err_ptr)) \
break; \
*extrac_ptr = xopt_parse(_xopt_ctx, (argc), (argv), (config_ptr), (extrav_ptr), (err_ptr)); \
if ((config_ptr)->help) \
{ \
xoptAutohelpOptions __xopt_autohelp_opts; \
__xopt_autohelp_opts.usage = (autohelp_usage); \
__xopt_autohelp_opts.prefix = (autohelp_prefix); \
__xopt_autohelp_opts.suffix = (autohelp_suffix); \
__xopt_autohelp_opts.spacer = (autohelp_spacer); \
xopt_autohelp(_xopt_ctx, (autohelp_file), &__xopt_autohelp_opts, (err_ptr)); \
if (*(err_ptr)) \
goto __xopt_end_free_extrav; \
free(_xopt_ctx); \
goto xopt_help; \
} \
if (*(err_ptr)) \
goto __xopt_end_free_ctx; \
__xopt_end_free_ctx: \
free(_xopt_ctx); \
break; \
__xopt_end_free_extrav: \
free(*(extrav_ptr)); \
free(_xopt_ctx); \
break; \
} while (false)
#if !TARGET_OS_IPHONE && !defined(__ANDROID__)
static int _tf_command_test(const char* file, int argc, char* argv[]);
static int _tf_command_import(const char* file, int argc, char* argv[]);
static int _tf_command_export(const char* file, int argc, char* argv[]);
static int _tf_command_run(const char* file, int argc, char* argv[]);
static int _tf_command_sandbox(const char* file, int argc, char* argv[]);
static int _tf_command_usage(const char* file, int argc, char* argv[]);
static int _tf_command_usage(const char* file);
typedef struct _command_t
{
@ -101,140 +67,178 @@ const command_t k_commands[] = {
static int _tf_command_test(const char* file, int argc, char* argv[])
{
#if !defined(__ANDROID__)
typedef struct args_t
{
const char* tests;
bool help;
} args_t;
xoptOption options[] = {
{ "tests", 't', offsetof(args_t, tests), NULL, XOPT_TYPE_STRING, NULL, "Comma-separated list of test names to run." },
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
XOPT_NULLOPTION,
};
args_t args = { 0 };
const char** extras = NULL;
int extra_count = 0;
const char* err = NULL;
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "test [options]", "options:", NULL, 15);
if (err)
{
fprintf(stderr, "Error: %s\n", err);
return 2;
}
tf_test_options_t test_options = {
.exe_path = file,
.tests = args.tests,
};
bool show_usage = false;
while (!show_usage)
{
static const struct option k_options[] = {
{ "tests", required_argument, NULL, 't' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "t:h", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 't':
test_options.tests = optarg;
break;
}
}
for (int i = optind; i < argc; i++)
{
tf_printf("Unexpected argument: %s\n", argv[i]);
show_usage = true;
}
if (show_usage)
{
tf_printf("\n%s test [options]\n\n", file);
tf_printf("options\n");
tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n");
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}
tf_tests(&test_options);
if (extras)
{
free((void*)extras);
}
return 0;
xopt_help:
if (extras)
{
free((void*)extras);
}
return EXIT_SUCCESS;
#else
return EXIT_FAILURE;
#endif
return 1;
}
static int _tf_command_import(const char* file, int argc, char* argv[])
{
typedef struct args_t
{
const char* user;
const char* db_path;
bool help;
} args_t;
const char* user = "import";
const char* db_path = k_db_path_default;
bool show_usage = false;
xoptOption options[] = {
{ "user", 'u', offsetof(args_t, user), NULL, XOPT_TYPE_STRING, NULL, "User into whose account apps will be imported (default: \"import\")." },
{ "db-path", 'd', offsetof(args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." },
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
XOPT_NULLOPTION,
};
args_t args = { .user = "import", .db_path = k_db_path_default };
const char** extras = NULL;
int extra_count = 0;
const char* err = NULL;
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr,
"import [options] [paths] ...", "options:", NULL, 15);
if (err)
while (!show_usage)
{
fprintf(stderr, "Error: %s\n", err);
return 2;
static const struct option k_options[] = {
{ "user", required_argument, NULL, 'u' },
{ "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "u:d:h", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 'u':
user = optarg;
break;
case 'd':
db_path = optarg;
break;
}
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, args.db_path);
if (extra_count)
if (show_usage)
{
for (int i = 0; i < extra_count; i++)
tf_printf("\n%s import [options] [paths...]\n\n", file);
tf_printf("options:\n");
tf_printf(" -u, --user user User into whose account apps will be imported (default: \"import\").\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path);
if (optind < argc)
{
for (int i = optind; i < argc; i++)
{
tf_printf("Importing %s...\n", extras[i]);
tf_ssb_import(ssb, args.user, extras[i]);
tf_printf("Importing %s...\n", argv[i]);
tf_ssb_import(ssb, user, argv[i]);
}
}
else
{
tf_printf("Importing %s...\n", "apps");
tf_ssb_import(ssb, args.user, "apps");
tf_ssb_import(ssb, user, "apps");
}
tf_ssb_destroy(ssb);
if (extras)
{
free((void*)extras);
}
return 0;
xopt_help:
if (extras)
{
free((void*)extras);
}
return 1;
return EXIT_SUCCESS;
}
static int _tf_command_export(const char* file, int argc, char* argv[])
{
typedef struct args_t
{
const char* user;
const char* db_path;
bool help;
} args_t;
const char* user = "core";
const char* db_path = k_db_path_default;
bool show_usage = false;
xoptOption options[] = {
{ "db-path", 'd', offsetof(args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." },
{ "user", 'u', offsetof(args_t, user), NULL, XOPT_TYPE_STRING, NULL, "User into whose apps will be exported (default: \"core\")." },
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
XOPT_NULLOPTION,
};
args_t args = { .user = "core", .db_path = k_db_path_default };
const char** extras = NULL;
int extra_count = 0;
const char* err = NULL;
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr,
"export [options] [paths] ...", "options:", NULL, 15);
if (err)
while (!show_usage)
{
fprintf(stderr, "Error: %s\n", err);
return 2;
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, args.db_path);
if (extra_count)
{
for (int i = 0; i < extra_count; i++)
static const struct option k_options[] = {
{ "user", required_argument, NULL, 'u' },
{ "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "u:d:h", k_options, NULL);
if (c == -1)
{
tf_printf("Exporting %s...\n", extras[i]);
tf_ssb_export(ssb, extras[i]);
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 'u':
user = optarg;
break;
case 'd':
db_path = optarg;
break;
}
}
if (show_usage)
{
tf_printf("\n%s export [options] [paths...]\n\n", file);
tf_printf("options:\n");
tf_printf(" -u, --user user User from whose account apps will be exported (default: \"core\").\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -h, --help Show this usage information.\n");
tf_printf("\n");
tf_printf("paths Paths of apps to export (example: /~core/ssb /~user/app).\n");
return EXIT_FAILURE;
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path);
if (optind < argc)
{
for (int i = optind; i < argc; i++)
{
tf_printf("Exporting %s...\n", argv[i]);
tf_ssb_export(ssb, argv[i]);
}
}
else
@ -253,25 +257,13 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
for (int i = 0; i < (int)_countof(k_export); i++)
{
char buffer[256];
snprintf(buffer, sizeof(buffer), "/~%s/%s", args.user, k_export[i]);
snprintf(buffer, sizeof(buffer), "/~%s/%s", user, k_export[i]);
tf_printf("Exporting %s...\n", buffer);
tf_ssb_export(ssb, buffer);
}
}
tf_ssb_destroy(ssb);
if (extras)
{
free((void*)extras);
}
return 0;
xopt_help:
if (extras)
{
free((void*)extras);
}
return 1;
return EXIT_SUCCESS;
}
#endif
@ -412,21 +404,6 @@ static void _shed_privileges()
static int _tf_command_run(const char* file, int argc, char* argv[])
{
xoptOption options[] = {
{ "script", 's', offsetof(tf_run_args_t, script), NULL, XOPT_TYPE_STRING, NULL, "Script to run (default: core/core.js)." },
{ "ssb-port", 'b', offsetof(tf_run_args_t, ssb_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run SSB (default: 8008)." },
{ "http-port", 'p', offsetof(tf_run_args_t, http_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run Tilde Friends web server (default: 12345)." },
{ "https-port", 'q', offsetof(tf_run_args_t, https_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run secure Tilde Friends web server (default: 12346)." },
{ "db-path", 'd', offsetof(tf_run_args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." },
{ "count", 'n', offsetof(tf_run_args_t, count), NULL, XOPT_TYPE_INT, NULL, "Number of instances to run." },
{ "args", 'a', offsetof(tf_run_args_t, args), NULL, XOPT_TYPE_STRING, NULL, "Arguments of the form key=value,foo=bar,verbose=true." },
{ "one-proc", 'o', offsetof(tf_run_args_t, one_proc), NULL, XOPT_TYPE_BOOL, NULL, "Run everything in one process (unsafely!)." },
{ "zip", 'z', offsetof(tf_run_args_t, zip), NULL, XOPT_TYPE_STRING, NULL, "Zip archive from which to load files." },
{ "verbose", 'v', offsetof(tf_run_args_t, verbose), NULL, XOPT_TYPE_BOOL, NULL, "Log raw messages." },
{ "help", 'h', offsetof(tf_run_args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
XOPT_NULLOPTION,
};
tf_run_args_t args = {
.count = 1,
.script = "core/core.js",
@ -435,16 +412,85 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
.ssb_port = 8008,
.db_path = k_db_path_default,
};
const char** extras = NULL;
int extra_count = 0;
const char* err = NULL;
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr,
"run [options] [paths] ...", "options:", NULL, 15);
bool show_usage = false;
if (err)
while (!show_usage)
{
fprintf(stderr, "Error: %s\n", err);
return 2;
static const struct option k_options[] = {
{ "script", required_argument, NULL, 's' },
{ "ssb-port", required_argument, NULL, 'b' },
{ "http-port", required_argument, NULL, 'p' },
{ "https-port", required_argument, NULL, 'q' },
{ "db-path", required_argument, NULL, 'd' },
{ "count", required_argument, NULL, 'n' },
{ "args", required_argument, NULL, 'a' },
{ "one-proc", no_argument, NULL, 'o' },
{ "zip", required_argument, NULL, 'z' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
};
int c = getopt_long(argc, argv, "s:b:p:q:d:n:a:oz:vh", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 's':
args.script = optarg;
break;
case 'b':
args.ssb_port = atoi(optarg);
break;
case 'p':
args.http_port = atoi(optarg);
break;
case 'q':
args.https_port = atoi(optarg);
break;
case 'd':
args.db_path = optarg;
break;
case 'n':
args.count = atoi(optarg);
break;
case 'a':
args.args = optarg;
break;
case 'o':
args.one_proc = true;
break;
case 'z':
args.zip = optarg;
break;
case 'v':
args.verbose = true;
break;
}
}
if (show_usage)
{
tf_printf("\n%s run [options]\n\n", file);
tf_printf("options\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -n, --count count Number of instances to run.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}
int result = 0;
@ -479,44 +525,40 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
tf_free(data);
tf_free(threads);
}
if (extras)
{
free((void*)extras);
}
return result;
xopt_help:
if (extras)
{
free((void*)extras);
}
return 1;
}
static int _tf_command_sandbox(const char* file, int argc, char* argv[])
{
typedef struct args_t
{
const char* script;
bool help;
} args_t;
bool show_usage = false;
xoptOption options[] = {
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
XOPT_NULLOPTION,
};
args_t args = { 0 };
const char** extras = NULL;
int extra_count = 0;
const char* err = NULL;
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr,
"sandbox [options]", "options:", NULL, 15);
if (err)
while (!show_usage)
{
fprintf(stderr, "Error: %s\n", err);
return 2;
static const struct option k_options[] = {
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "h", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
}
}
if (show_usage)
{
tf_printf("\nUsage: %s sandbox [options]\n\n", file);
tf_printf("options:\n");
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}
#if defined(__linux__)
@ -528,22 +570,11 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
/* The caller will trigger tf_task_activate with a message. */
tf_task_run(task);
tf_task_destroy(task);
if (extras)
{
free((void*)extras);
}
return 0;
xopt_help:
if (extras)
{
free((void*)extras);
}
return 1;
return EXIT_SUCCESS;
}
#if !defined(__ANDROID__)
static int _tf_command_usage(const char* file, int argc, char* argv[])
static int _tf_command_usage(const char* file)
{
tf_printf("Usage: %s command [command-options]\n", file);
tf_printf("commands:\n");
@ -631,11 +662,11 @@ int main(int argc, char* argv[])
{
if (strcmp(argv[1], "run") == 0)
{
result = _tf_command_run(argv[0], argc - 2, argv + 2);
result = _tf_command_run(argv[0], argc - 1, argv + 1);
}
else if (strcmp(argv[1], "sandbox") == 0)
{
result = _tf_command_sandbox(argv[0], argc - 2, argv + 2);
result = _tf_command_sandbox(argv[0], argc - 1, argv + 1);
}
}
tf_mem_shutdown();
@ -675,15 +706,15 @@ int main(int argc, char* argv[])
const command_t* command = &k_commands[i];
if (strcmp(argv[1], command->name) == 0)
{
result = command->callback(argv[0], argc - 2, argv + 2);
result = command->callback(argv[0], argc - 1, argv + 1);
goto done;
}
}
result = _tf_command_usage(argv[0], argc, argv);
result = _tf_command_usage(argv[0]);
}
else
{
result = _tf_command_run(argv[0], argc - 1, argv + 1);
result = _tf_command_run(argv[0], argc, argv);
}
done:
tf_mem_shutdown();

View File

@ -32,6 +32,7 @@
#if !defined(_WIN32)
#include <unistd.h>
#include <sys/wait.h>
#endif
#if defined(_WIN32)

View File

@ -10,24 +10,134 @@
#include <stdbool.h>
/**
** Register utility script functions.
** @param context The JS context.
*/
void tf_util_register(JSContext* context);
/**
** Convert UTF-8 bytes in a buffer or Uint8Array or similar to a String.
** @param context The JS context.
** @param value The UTF-8 bytes.
** @return A string representation of the data interpreted as UTF-8 bytes.
*/
JSValue tf_util_utf8_decode(JSContext* context, JSValue value);
/**
** Get the data from what might be an ArrayBuffer.
** @param context The JS context.
** @param[out] psize The size of the data in bytes.
** @param obj The object which might be an ArrayBuffer.
** @return The ArrayBuffer's data.
*/
uint8_t* tf_util_try_get_array_buffer(JSContext* context, size_t* psize, JSValueConst obj);
/**
** Get the ArrayBuffer from what might be a typed ArrayBuffer.
** @param context The JS context.
** @param obj The object which might be a typed ArrayBuffer.
** @param[out] pbyte_offset The offset into the buffer at which the typed ArrayBuffer starts.
** @param[out] pbyte_length The length of the buffer.
** @param[out] pbytes_per_element Element size in bytes.
** @return An ArrayBuffer if obj was a typed ArrayBuffer.
*/
JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element);
/**
** Print an error and message the owning task if possible.
** @param context The JS context.
** @param value The value which might be an exception.
** @return true If the value was an exception and an error was reported.
*/
bool tf_util_report_error(JSContext* context, JSValue value);
/**
** Get the length of an array.
** @param context The JS context.
** @param value An array with a "length" field.
** @return The array's length.
*/
int tf_util_get_length(JSContext* context, JSValue value);
/**
** Get the index at which to insert into an array in order to preserve sorted order.
** @param key The key being inserted.
** @param base The beginning of the array.
** @param count The number of elements in the array.
** @param size The size of a single element of the array.
** @param compare A comparison function comparing key and an element of the array.
** @return The index at which to insert key in the array, between 0 and count inclusive.
*/
int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*));
/**
** Create a Uint8Array from bytes.
** @param context The JS context.
** @param data The bytes.
** @param size The number of bytes in data.
** @return The created array.
*/
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size);
/**
** Base64-encode data.
** @param source The source data.
** @param source_length The length of the source data.
** @param[out] out A buffer to receive the encoded data.
** @param out_length The size of the buffer to receive encoded data.
** @return The size of the encoded data.
*/
size_t tf_base64_encode(const uint8_t* source, size_t source_length, char* out, size_t out_length);
/**
** Base64-decode data.
** @param source The source data.
** @param source_length The length of the source data.
** @param[out] out A buffer to receipve the decoded data.
** @param out_length The size of the buffer to receive decoded data.
** @return The size of the decoded data.
*/
size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out, size_t out_length);
/**
** Capture a stack backtrace of the calling thread.
** @param[out] buffer A buffer with at least count element to receive the backtrace.
** @param count The size of buffer.
** @return The numbef of captured frames.
*/
int tf_util_backtrace(void** buffer, int count);
/**
** Convert a stack backtrace to string.
** @param buffer A stack backtrace.
** @param count The number of elements in the backtrace.
** @return A string representation of the stack backtrace with function names,
** files, and line numbers, as possible. Must be freed with tf_free() by the
** caller.
*/
const char* tf_util_backtrace_to_string(void* const* buffer, int count);
/**
** Capture a stack backtrace of the calling thread and convert it immediately to string.
** @return A string representation of the stack backtrace with function names,
** files, and line numbers, as possible. Must be freed with tf_free() by the
** caller.
*/
const char* tf_util_backtrace_string();
/**
** Convert a function pointer to its name, if possible.
** @return The function name or null.
*/
const char* tf_util_function_to_string(void* function);
/**
** Get the minimum of two values.
** @param a The first value.
** @param b The second value.
** @return The minimum of a and b.
*/
#define tf_min(a, b) \
({ \
__typeof__(a) _a = (a); \

View File

@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.16-wip"
#define VERSION_NAME "Medium English breakfast tea."
#define VERSION_NUMBER "0.0.16"
#define VERSION_NAME "Now with 38% more process."

File diff suppressed because it is too large Load Diff