forked from cory/tildefriends
Compare commits
93 Commits
390668ec34
...
user_setti
Author | SHA1 | Date | |
---|---|---|---|
58dbf42a3a | |||
a1f221879b | |||
2a928dcafc | |||
5474c5a101 | |||
4b7261fa20 | |||
4992ff3a2d | |||
88ee0aa6f0 | |||
fdda628be8 | |||
2b45d8aa05 | |||
0e2fc65301 | |||
392206c19e | |||
e8ef7e74de | |||
c32e1b9583 | |||
9d0f6ec155 | |||
855d603795 | |||
af25782185 | |||
e5ba51b80a | |||
5e240de677 | |||
418cfac0e3 | |||
9d09607013 | |||
eddf25b622 | |||
537a8654fa | |||
9de33d06d2 | |||
0e5f320664 | |||
88d8e60511 | |||
439f07162e | |||
efe2b6cbd9 | |||
0aa1ed9464 | |||
cb94ed6a2a | |||
cf187ee46b | |||
3e71fc20fd | |||
f3601321f7 | |||
540059368c | |||
7ce89123f7 | |||
e3c7c86212 | |||
794804e27f | |||
6d89c1da6e | |||
d059554464 | |||
3a392d4a9f | |||
e3071b372a | |||
18bd279b0c | |||
5b93db7463 | |||
5b7e5eb91b | |||
78ca383e3c | |||
c1eed9ada3 | |||
8d6feb5394 | |||
42994f8977 | |||
f0a871e1f8 | |||
a710c30572 | |||
c991763b00 | |||
72dae14f87 | |||
5800340762 | |||
c5f5adcac6 | |||
591642efb3 | |||
6182ffa1d4 | |||
402a898d96 | |||
13d43d8319 | |||
7bcdbd3813 | |||
60ada22674 | |||
637119d46d | |||
40f3da6a65 | |||
f4697fe7f7 | |||
3bc18b9021 | |||
c21581aefa | |||
165f25db69 | |||
9aa0617aa1 | |||
ddce88dce6 | |||
6aa2bce2be | |||
a43c1d3d1e | |||
1ed0e817e8 | |||
709ca55e65 | |||
8c13f5dbba | |||
4cb82d81b7 | |||
0c42921387 | |||
70a3e7fc7d | |||
d5267be38c | |||
8e7e0ed490 | |||
8cf2837725 | |||
63ae186c76 | |||
dbf5c7b832 | |||
bfbfc01e99 | |||
8fa9d0e843 | |||
2d3e108fd9 | |||
7822b30dcb | |||
2701b7d04e | |||
f9e95e5733
|
|||
1444c945de
|
|||
e361c3f975 | |||
260706c172 | |||
1d5cdf9607 | |||
a4bf3542e0 | |||
df82cfe66b | |||
53f9547cc5 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,8 +1,10 @@
|
||||
**/node_modules
|
||||
.keys
|
||||
.zsign_cache/
|
||||
db.*
|
||||
deps/ios_toolchain/
|
||||
deps/openssl/
|
||||
dist/
|
||||
.keys
|
||||
**/node_modules
|
||||
out
|
||||
*.swo
|
||||
*.swp
|
||||
.zsign_cache/
|
||||
|
@@ -3,8 +3,3 @@ useTabs: true
|
||||
semi: true
|
||||
singleQuote: true
|
||||
bracketSpacing: false
|
||||
# overrides:
|
||||
# - files: '**/*.json'
|
||||
# options:
|
||||
# useTabs: false
|
||||
# tabWidth: 2
|
||||
|
37
CONTRIBUTING.md
Normal file
37
CONTRIBUTING.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Contributing to Tilde Friends
|
||||
|
||||
Thank you for your interest in Tilde Friends.
|
||||
|
||||
Above all, Tilde Friends aims to be a fun, safe place to play. When that is at
|
||||
odds with the course of development, we will work through it with respectful
|
||||
communication.
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
The nature of Tilde Friends makes for a wide range of ways to contribute
|
||||
|
||||
- Just use it. Really, just kicking the tires will probably shake out issues
|
||||
in useful ways at this point.
|
||||
- Report and comment on bugs: https://dev.tildefriends.net/issues.
|
||||
- Make apps. You don't need my permission to make and share apps with Tilde
|
||||
Friends. I hope that an ecosystem of good apps grows outside of this
|
||||
repository. If you want to recreate better versions of the stock apps, just
|
||||
do it. If you make a better ssb app or whatever and drop me a line however
|
||||
is most convenient for you, I will probably take a look and consider
|
||||
replacing the stock one with it.
|
||||
- Write about it. Docs in the git repository, blog posts, private messages to
|
||||
me with ideas...really there is no wrong answer. Just make some noise, and
|
||||
I'll do my best to incorporate or otherwise link your feedback and make the
|
||||
most of it.
|
||||
- Write C code in the git repository. I'm really striving for it to be the
|
||||
case that other people don't really need to meddle in there, but if you can
|
||||
help out, I will gladly review your pull requests via
|
||||
https://dev.tildefriends.net/pulls.
|
||||
|
||||
## Best practices
|
||||
|
||||
- The C code is formatted with clang-format. Run `make format`.
|
||||
- The rest is formatted with prettier. Run `npm run prettier`.
|
||||
- We strive to have code compile on all platforms with no warnings and run with
|
||||
no sanitizer issues.
|
||||
- There are tests. Run `out/debug/tildefriends test`.
|
102
GNUmakefile
102
GNUmakefile
@@ -3,9 +3,12 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
VERSION_CODE := 16
|
||||
VERSION_NUMBER := 0.0.16-wip
|
||||
VERSION_NAME := Medium English breakfast tea.
|
||||
VERSION_CODE := 17
|
||||
VERSION_NUMBER := 0.0.17-wip
|
||||
VERSION_NAME := Please enjoy responsibly.
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450200.zip
|
||||
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
|
||||
|
||||
PROJECT = tildefriends
|
||||
BUILD_DIR ?= out
|
||||
@@ -14,6 +17,18 @@ UNAME_M := $(shell uname -m)
|
||||
|
||||
ANDROID_SDK ?= ~/Android/Sdk
|
||||
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
@@ -55,7 +70,7 @@ CFLAGS += \
|
||||
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.1.10909125
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
|
||||
ANDROID_MIN_SDK_VERSION := 24
|
||||
ANDROID_TARGET_SDK_VERSION := 34
|
||||
|
||||
@@ -207,18 +222,6 @@ $(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
|
||||
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
|
||||
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
|
||||
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
|
||||
get_objs = \
|
||||
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
|
||||
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
@@ -245,7 +248,6 @@ $(APP_OBJS): CFLAGS += \
|
||||
-Ideps/quickjs \
|
||||
-Ideps/sqlite \
|
||||
-Ideps/valgrind \
|
||||
-Ideps/xopt \
|
||||
-Wdouble-promotion \
|
||||
-Werror
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
@@ -497,18 +499,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 +627,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 +706,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 +725,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)
|
||||
@$(ANDROID_BUILD_TOOLS)/zipalign -f 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,16 +738,22 @@ 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)
|
||||
@$(ANDROID_BUILD_TOOLS)/zipalign -f 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
|
||||
out/%.zopfli.apk: out/%.apk
|
||||
@echo "[zopfli] $(notdir $@)"
|
||||
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
||||
@$(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 $@ $@.zopfli
|
||||
|
||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
|
||||
.PHONY: release-apk
|
||||
|
||||
releaseapkgo: out/TildeFriends-arm-release.apk
|
||||
@@ -814,11 +810,13 @@ apklog:
|
||||
|
||||
fetchdeps:
|
||||
@echo "[fetch] libuv"
|
||||
@test -f out/deps/libuv.tar.gz || (mkdir -p out/deps/ && curl -q https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz -o out/deps/libuv.tar.gz)
|
||||
@test -d deps/libuv/ || (mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
|
||||
@test -f out/deps/libuv.tar.gz && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (mkdir -p out/deps/ && curl -q $(LIBUV_URL) -o out/deps/libuv.tar.gz)
|
||||
@test -d deps/libuv/ && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (rm -rf deps/libuv/ && mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
|
||||
@echo -n $(LIBUV_URL) > out/deps/libuv.txt
|
||||
@echo "[fetch] sqlite"
|
||||
@test -f out/deps/sqlite.zip || (mkdir -p out/deps/ && curl -q https://www.sqlite.org/2024/sqlite-amalgamation-3450100.zip -o out/deps/sqlite.zip)
|
||||
@test -d deps/sqlite/ || (mkdir -p deps/sqlite/ && unzip -qDj -d deps/sqlite/ out/deps/sqlite.zip)
|
||||
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
|
||||
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
|
||||
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
|
||||
@echo "[fetch] prettier"
|
||||
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
|
||||
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
|
||||
@@ -839,7 +837,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 +853,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* \
|
||||
@@ -870,12 +867,13 @@ dist: release-apk iosrelease-ipa
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--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)
|
||||
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
|
||||
-C out/ \
|
||||
tildefriends-$(VERSION_NUMBER)
|
||||
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-x86-release.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-arm-release.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
.PHONY: dist
|
||||
@@ -891,6 +889,10 @@ format:
|
||||
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
|
||||
.PHONY: format
|
||||
|
||||
prettier:
|
||||
@npm run prettier
|
||||
.PHONY: prettier
|
||||
|
||||
docs:
|
||||
@doxygen
|
||||
.PHONY: docs
|
||||
|
32
README.md
32
README.md
@@ -4,34 +4,46 @@ Tilde Friends is a tool for making and sharing.
|
||||
|
||||
A public instance lives at https://www.tildefriends.net/.
|
||||
|
||||
It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
It is both a peer-to-peer social network client, participating in Secure
|
||||
Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Make it easy and fun to run all sorts of web applications.
|
||||
2. Provide security that is easy to understand and protects your data.
|
||||
3. Make creating and sharing web applications accessible to anyone with a browser.
|
||||
3. Make creating and sharing web applications accessible to anyone with a
|
||||
browser.
|
||||
|
||||
## Building
|
||||
|
||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for all of those host platforms plus mingw64, iOS, and android.
|
||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
|
||||
all of those host platforms plus mingw64, iOS, and android.
|
||||
|
||||
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies are kept up to date in the tree.
|
||||
2. To build, run `make debug` or `make release`. An executable will be generated in a subdirectory of `out/`.
|
||||
3. It's possible to build for Android, iOS, and Windows on Linux, if you have the right dependencies in the right places. `make windebug winrelease iosdebug-ipa iosrelease-ipa release-apk`.
|
||||
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
|
||||
are kept up to date in the tree.
|
||||
2. To build, run `make debug` or `make release`. An executable will be
|
||||
generated in a subdirectory of `out/`.
|
||||
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
|
||||
the right dependencies in the right places. `make windebug winrelease
|
||||
iosdebug-ipa iosrelease-ipa release-apk`.
|
||||
4. To build in docker, `docker build .`.
|
||||
5. `make format` will normalize formatting to the coding standard.
|
||||
|
||||
## Running
|
||||
|
||||
By default, running the built `tildefriends` executable will start a web server at <http://localhost:12345/>. `tildefriends -h` lists further options.
|
||||
By default, running the built `tildefriends` executable will start a web server
|
||||
at <http://localhost:12345/>. `tildefriends -h` lists further options.
|
||||
|
||||
The first user to create an account and log in will be granted administrative privileges. Further administration can be done at <http://localhost:12345/~core/admin/>.
|
||||
The first user to create an account and log in will be granted administrative
|
||||
privileges. Further administration can be done at
|
||||
<http://localhost:12345/~core/admin/>.
|
||||
|
||||
## Documentation
|
||||
|
||||
Docs are a work in progress: <https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
|
||||
Docs are a work in progress:
|
||||
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
|
||||
|
||||
## License
|
||||
|
||||
All code unless otherwise noted in is provided under the [MIT](https://opensource.org/licenses/MIT) license.
|
||||
All code unless otherwise noted in is provided under the
|
||||
[MIT](https://opensource.org/licenses/MIT) license.
|
||||
|
@@ -87,6 +87,6 @@ window.addEventListener('load', function () {
|
||||
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
||||
</div>
|
||||
${users_template(data.users)}
|
||||
</div>`;
|
||||
</div> `;
|
||||
render(page_template(g_data), document.body);
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💻",
|
||||
"previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256"
|
||||
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
|
||||
}
|
||||
|
@@ -28,10 +28,10 @@ async function fetch_shared_apps() {
|
||||
|
||||
await ssb.sqlAsync(
|
||||
`
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts('"application/tildefriends"')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
ORDER BY timestamp
|
||||
ORDER BY messages.timestamp
|
||||
`,
|
||||
[],
|
||||
function (row) {
|
||||
|
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🗺",
|
||||
"previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256"
|
||||
}
|
@@ -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
@@ -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;
|
||||
}
|
@@ -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();
|
@@ -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>
|
@@ -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
@@ -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};
|
@@ -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);
|
@@ -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`,
|
||||
});
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🪪",
|
||||
"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
tfrpc.register(async function get_private_key(id) {
|
||||
return bip39Words(await ssb.getPrivateKey(id));
|
||||
});
|
||||
tfrpc.register(async function create_id(id) {
|
||||
return await ssb.createIdentity();
|
||||
});
|
||||
tfrpc.register(async function add_id(id) {
|
||||
return await ssb.addIdentity(bip39Bytes(id));
|
||||
});
|
||||
tfrpc.register(async function delete_id(id) {
|
||||
return await ssb.deleteIdentity(id);
|
||||
});
|
||||
tfrpc.register(async function reload() {
|
||||
await main();
|
||||
});
|
||||
|
||||
async function main() {
|
||||
let ids = await ssb.getIdentities();
|
||||
await app.setDocument(
|
||||
`<body style="color: #fff">
|
||||
<script>const handler = {};</script>
|
||||
<script type="module">
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
handler.export_id = async function export_id(event) {
|
||||
let id = event.srcElement.dataset.id;
|
||||
let element = document.createElement('textarea');
|
||||
element.value = await tfrpc.rpc.get_private_key(id);
|
||||
element.style = 'width: 100%; read-only: true';
|
||||
element.readOnly = true;
|
||||
event.srcElement.parentElement.appendChild(element);
|
||||
event.srcElement.onclick = event => handler.hide_id(event, element);
|
||||
}
|
||||
handler.add_id = async function add_id(event) {
|
||||
let id = document.getElementById('add_id').value;
|
||||
try {
|
||||
let new_id = await tfrpc.rpc.add_id(id);
|
||||
alert('Successfully imported: ' + new_id);
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (e) {
|
||||
alert('Error importing identity: ' + e);
|
||||
}
|
||||
}
|
||||
handler.create_id = async function create_id(event) {
|
||||
try {
|
||||
let id = await tfrpc.rpc.create_id();
|
||||
alert('Successfully created: ' + id);
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (e) {
|
||||
alert('Error creating identity: ' + e);
|
||||
}
|
||||
}
|
||||
handler.hide_id = function hide_id(event, element) {
|
||||
element.parentNode.removeChild(element);
|
||||
event.srcElement.onclick = handler.export_id;
|
||||
}
|
||||
handler.delete_id = async function delete_id(event) {
|
||||
let id = event.srcElement.dataset.id;
|
||||
try {
|
||||
if (prompt('Are you sure you want to delete "' + id + '"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
|
||||
if (await tfrpc.rpc.delete_id(id)) {
|
||||
alert('Successfully deleted ID: ' + id);
|
||||
}
|
||||
await tfrpc.rpc.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error deleting ID: ' + e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<h1>SSB Identity Management</h1>
|
||||
<h2>Create a new identity</h2>
|
||||
<button id="create_id" onclick="handler.create_id()">Create Identity</button>
|
||||
<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
|
||||
<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
|
||||
<h2>Identities</h2>
|
||||
<ul>` +
|
||||
ids
|
||||
.map(
|
||||
(id) => `<li>
|
||||
<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
|
||||
<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
|
||||
${id}
|
||||
</li>`
|
||||
)
|
||||
.join('\n') +
|
||||
` </ul>
|
||||
</body>`
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📝",
|
||||
"previous": "&2hdIDbBrAg63T2X1MzdGSF7yiqHvlnfF0PnInQLp0DA=.sha256"
|
||||
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
|
||||
}
|
||||
|
5
apps/room.json
Normal file
5
apps/room.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📦",
|
||||
"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
|
||||
}
|
13
apps/room/app.js
Normal file
13
apps/room/app.js
Normal file
@@ -0,0 +1,13 @@
|
||||
async function main() {
|
||||
let host = core.url.match(/.*\/\/(.*?)\//)[1];
|
||||
let id = (await ssb.getServerIdentity()).substring(1);
|
||||
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
await app.setDocument(`
|
||||
<body style="color: #fff">
|
||||
<h1>Server</h1>
|
||||
<div>The local server address is:</div>
|
||||
<div><input type="text" readonly value="${room}" style="width: 100%"></input></div>
|
||||
</body>
|
||||
`);
|
||||
}
|
||||
main();
|
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👟"
|
||||
"emoji": "👟",
|
||||
"previous": "&lYZRnT2UGQxXxYISbuaZewik9AuxBpcJumakwrePw5c=.sha256"
|
||||
}
|
||||
|
@@ -38,10 +38,11 @@ class TfSneakerAppElement extends LitElement {
|
||||
}
|
||||
|
||||
format_message(message) {
|
||||
const k_flag_sequence_before_author = 1;
|
||||
let out = {
|
||||
previous: message.previous ?? null,
|
||||
};
|
||||
if (message.sequence_before_author) {
|
||||
if (message.flags & k_flag_sequence_before_author) {
|
||||
out.sequence = message.sequence;
|
||||
out.author = message.author;
|
||||
} else {
|
||||
@@ -72,25 +73,104 @@ class TfSneakerAppElement extends LitElement {
|
||||
return true;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
if (startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
|
||||
startsWith(data, [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]) ||
|
||||
if (
|
||||
startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
|
||||
startsWith(
|
||||
data,
|
||||
[0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]
|
||||
) ||
|
||||
startsWith(data, [0xff, 0xd8, 0xff, 0xee]) ||
|
||||
startsWith(data, [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
|
||||
startsWith(data, [
|
||||
0xff,
|
||||
0xd8,
|
||||
0xff,
|
||||
0xe1,
|
||||
null,
|
||||
null,
|
||||
0x45,
|
||||
0x78,
|
||||
0x69,
|
||||
0x66,
|
||||
0x00,
|
||||
0x00,
|
||||
])
|
||||
) {
|
||||
return '.jpg';
|
||||
} else if (startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
||||
} else if (
|
||||
startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
||||
) {
|
||||
return '.png';
|
||||
} else if (startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
|
||||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
|
||||
} else if (
|
||||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
|
||||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])
|
||||
) {
|
||||
return '.gif';
|
||||
} else if (startsWith(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
|
||||
} else if (
|
||||
startsWith(data, [
|
||||
0x52,
|
||||
0x49,
|
||||
0x46,
|
||||
0x46,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x57,
|
||||
0x45,
|
||||
0x42,
|
||||
0x50,
|
||||
])
|
||||
) {
|
||||
return '.webp';
|
||||
} else if (startsWith(data, [0x3c, 0x73, 0x76, 0x67])) {
|
||||
return '.svg';
|
||||
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
||||
} else if (
|
||||
startsWith(data, [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
])
|
||||
) {
|
||||
return '.mp3';
|
||||
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]) ||
|
||||
startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
||||
} else if (
|
||||
startsWith(data, [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x69,
|
||||
0x73,
|
||||
0x6f,
|
||||
0x6d,
|
||||
]) ||
|
||||
startsWith(data, [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
])
|
||||
) {
|
||||
return '.mp4';
|
||||
} else {
|
||||
return '.bin';
|
||||
@@ -109,7 +189,12 @@ class TfSneakerAppElement extends LitElement {
|
||||
)[0].total;
|
||||
while (true) {
|
||||
let messages = await tfrpc.rpc.query(
|
||||
'SELECT * FROM messages WHERE author = ? AND SEQUENCE > ? ORDER BY sequence LIMIT 100',
|
||||
`
|
||||
SELECT author, id, sequence, timestamp, hash, json(content) AS content, signature, flags
|
||||
FROM messages
|
||||
WHERE author = ? AND SEQUENCE > ?
|
||||
ORDER BY sequence LIMIT 100
|
||||
`,
|
||||
[id, sequence]
|
||||
);
|
||||
if (messages?.length) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🐌",
|
||||
"previous": "&DUxMMCJcuhm6S9jg/eKgEyWodkITu6Tg9g5I5wgLWFU=.sha256"
|
||||
"previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.sha256"
|
||||
}
|
||||
|
@@ -107,7 +107,7 @@ class TfElement extends LitElement {
|
||||
let abouts = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT
|
||||
messages.*
|
||||
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM
|
||||
messages,
|
||||
json_each(?1) AS following
|
||||
@@ -118,7 +118,7 @@ class TfElement extends LitElement {
|
||||
json_extract(messages.content, '$.type') = 'about'
|
||||
UNION
|
||||
SELECT
|
||||
messages.*
|
||||
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM
|
||||
messages,
|
||||
json_each(?2) AS following
|
||||
@@ -158,7 +158,7 @@ class TfElement extends LitElement {
|
||||
async fetch_new_message(id) {
|
||||
let messages = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.*
|
||||
SELECT messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.id = ?
|
||||
@@ -221,7 +221,7 @@ class TfElement extends LitElement {
|
||||
this.tags = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
recent AS (SELECT id, content FROM messages
|
||||
recent AS (SELECT id, json(content) AS content FROM messages
|
||||
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
|
||||
ORDER BY timestamp DESC LIMIT 1024),
|
||||
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
|
||||
|
@@ -262,7 +262,7 @@ class TfComposeElement extends LitElement {
|
||||
try {
|
||||
let rows = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.content FROM messages_fts(?)
|
||||
SELECT json(messages.content) FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE messages.content LIKE ?
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
|
@@ -3,7 +3,7 @@ import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
/*
|
||||
* Provide a list of IDs, and this lets the user pick one.
|
||||
** Provide a list of IDs, and this lets the user pick one.
|
||||
*/
|
||||
class TfIdentityPickerElement extends LitElement {
|
||||
static get properties() {
|
||||
|
@@ -265,10 +265,7 @@ ${JSON.stringify(mention, null, 2)}</pre
|
||||
new CustomEvent('tf-expand', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: (this.message.id || '') + (tag || ''),
|
||||
expanded: expanded,
|
||||
},
|
||||
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -497,7 +494,7 @@ ${JSON.stringify(mention, null, 2)}</pre
|
||||
<tf-compose
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
root=${this.message.content.root || this.message.id}
|
||||
root=${content.root || this.message.id}
|
||||
branch=${this.message.id}
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}
|
||||
@@ -684,7 +681,7 @@ ${JSON.stringify(content, null, 2)}</pre
|
||||
<tf-compose
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
root=${this.message.content.root || this.message.id}
|
||||
root=${content.root || this.message.id}
|
||||
branch=${this.message.id}
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ class TfTabMentionsElement extends LitElement {
|
||||
console.log('Loading...', this.whoami);
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
|
@@ -33,12 +33,12 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
if (this.hash.startsWith('#@')) {
|
||||
let r = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH mine AS (SELECT messages.*
|
||||
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE messages.author = ?
|
||||
ORDER BY sequence DESC
|
||||
LIMIT 20)
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM mine
|
||||
JOIN messages_refs ON mine.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
@@ -51,11 +51,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
} else if (this.hash.startsWith('#%')) {
|
||||
return await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.*
|
||||
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE id = ?1
|
||||
UNION
|
||||
SELECT messages.*
|
||||
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages JOIN messages_refs
|
||||
ON messages.id = messages_refs.message
|
||||
WHERE messages_refs.ref = ?1
|
||||
@@ -69,17 +69,17 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
promises.push(
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
WITH news AS (SELECT messages.*
|
||||
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp > ? AND messages.timestamp < ?
|
||||
ORDER BY messages.timestamp DESC)
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.message
|
||||
JOIN messages ON messages_refs.ref = messages.id
|
||||
@@ -107,18 +107,18 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
|
||||
let more = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH news AS (SELECT messages.*
|
||||
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp > ?
|
||||
AND messages.timestamp <= ?
|
||||
ORDER BY messages.timestamp DESC)
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.message
|
||||
JOIN messages ON messages_refs.ref = messages.id
|
||||
|
@@ -35,7 +35,7 @@ class TfTabSearchElement extends LitElement {
|
||||
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.*
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
|
1
apps/user_settings.json
Normal file
1
apps/user_settings.json
Normal file
@@ -0,0 +1 @@
|
||||
{"type": "tildefriends-app", "emoji": "⚙️"}
|
60
apps/user_settings/app.js
Normal file
60
apps/user_settings/app.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
tfrpc.register(async function getIdentities() {
|
||||
return ssb.getIdentities();
|
||||
});
|
||||
tfrpc.register(async function createID(id) {
|
||||
return await ssb.createIdentity();
|
||||
});
|
||||
tfrpc.register(async function getPrivateKey(id) {
|
||||
return bip39Words(await ssb.getPrivateKey(id));
|
||||
});
|
||||
tfrpc.register(async function addID(id) {
|
||||
return await ssb.addIdentity(bip39Bytes(id));
|
||||
});
|
||||
tfrpc.register(async function deleteID(id) {
|
||||
return await ssb.deleteIdentity(id);
|
||||
});
|
||||
tfrpc.register(async function getThemes() {
|
||||
// TODO
|
||||
return ['solarized', 'gruvbox', 'light'];
|
||||
});
|
||||
tfrpc.register(async function getTheme() {
|
||||
// TODO
|
||||
return 'gruvbox';
|
||||
});
|
||||
tfrpc.register(async function setTheme() {
|
||||
// TODO
|
||||
console.warn('setTheme called - not implemented');
|
||||
return null;
|
||||
});
|
||||
tfrpc.register(async function reload() {
|
||||
await main();
|
||||
});
|
||||
|
||||
async function main() {
|
||||
// Get body.html
|
||||
const body = utf8Decode(await getFile('body.html'));
|
||||
|
||||
// Build the document
|
||||
const document = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
|
||||
<script src="tf-theme-picker.js" type="module"></script>
|
||||
<script src="tf-password-form.js" type="module"></script>
|
||||
<script src="tf-delete-account-btn.js" type="module"></script>
|
||||
<script src="tf-identity-manager.js" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body class="flex-column">
|
||||
${body}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// Send it to the browser
|
||||
app.setDocument(document);
|
||||
}
|
||||
|
||||
main();
|
20
apps/user_settings/body.html
Normal file
20
apps/user_settings/body.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<h1>Your settings</h1>
|
||||
|
||||
<div class="box flex-column">
|
||||
<h2>Appearance</h2>
|
||||
|
||||
<tf-theme-picker></tf-theme-picker>
|
||||
</div>
|
||||
|
||||
<div class="box flex-column">
|
||||
<h2>Danger Zone</h2>
|
||||
|
||||
<h3>Manage your identities</h3>
|
||||
<tf-identity-manager></tf-identity-manager>
|
||||
|
||||
<h3>Change my password</h3>
|
||||
<tf-password-form></tf-password-form>
|
||||
|
||||
<h3>Delete your account</h3>
|
||||
<tf-delete-account-btn></tf-delete-account-btn>
|
||||
</div>
|
36
apps/user_settings/tf-delete-account-btn.js
Normal file
36
apps/user_settings/tf-delete-account-btn.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
class TfDeleteAccountButtonElement extends LitElement {
|
||||
static get properties() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
deleteAccount() {
|
||||
const res = confirm(
|
||||
'Are you really sure you want to delete your account ?'
|
||||
);
|
||||
|
||||
if (!res) return;
|
||||
|
||||
console.warn('TODO');
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
|
||||
|
||||
<span>This action is irreversible !</span>
|
||||
|
||||
<button class="red" @click=${this.deleteAccount}>
|
||||
[Not implemented] Delete my Tilde Friends account
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-delete-account-btn', TfDeleteAccountButtonElement);
|
118
apps/user_settings/tf-identity-manager.js
Normal file
118
apps/user_settings/tf-identity-manager.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
class TfIdentityManagerElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
ids: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ids = [];
|
||||
this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.ids = await tfrpc.rpc.getIdentities();
|
||||
}
|
||||
|
||||
async createIdentity() {
|
||||
try {
|
||||
const id = await tfrpc.rpc.createID();
|
||||
alert('Successfully created: ' + id);
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (err) {
|
||||
alert('Error creating identity: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
async importIdentity() {
|
||||
const words = this.renderRoot?.querySelector('#import-id-textarea').value;
|
||||
if (!words) return;
|
||||
|
||||
try {
|
||||
const newID = await tfrpc.rpc.addID(words);
|
||||
|
||||
if (newID) alert('Successfully imported a new identity.');
|
||||
else alert('This identity already exists or is invalid.');
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (err) {
|
||||
alert('Error importing identity: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
async exportIdentity(id) {
|
||||
alert(
|
||||
'Your private key is:\n' +
|
||||
(await tfrpc.rpc.getPrivateKey(id)) +
|
||||
'\nDo not share it with anyone!'
|
||||
);
|
||||
}
|
||||
|
||||
async deleteIdentity(id) {
|
||||
try {
|
||||
if (
|
||||
prompt(
|
||||
'Are you sure you want to delete "' +
|
||||
id +
|
||||
'"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.'
|
||||
) === 'DELETE'
|
||||
) {
|
||||
if (await tfrpc.rpc.deleteID(id)) {
|
||||
alert('Successfully deleted ID: ' + id);
|
||||
}
|
||||
await tfrpc.rpc.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error deleting ID: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <link rel="stylesheet" href="/static/tildefriends-v1.css" />
|
||||
<style>
|
||||
.id-span {
|
||||
font-family: monospace;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h4>Create a new identity</h4>
|
||||
<button id="create-id" class="green" @click=${this.createIdentity}>
|
||||
Create Identity
|
||||
</button>
|
||||
|
||||
<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
|
||||
<textarea id="import-id-textarea" style="width: 100%" rows="4"></textarea>
|
||||
<button class="green" @click=${this.importIdentity}>
|
||||
Import Identity
|
||||
</button>
|
||||
|
||||
<h4>Warning !</h4>
|
||||
<strong
|
||||
>Anybody that knows your private key can gain total access over your
|
||||
account.</strong
|
||||
>
|
||||
<br /><br />
|
||||
Tilde Friends' contributors will never ask you for your private key !
|
||||
|
||||
<ul>
|
||||
${this.ids.map(
|
||||
(id) =>
|
||||
html` <li>
|
||||
<button class="blue" @click=${() => this.exportIdentity(id)}>
|
||||
Export Identity
|
||||
</button>
|
||||
<button class="red" @click=${() => this.deleteIdentity(id)}>
|
||||
Delete Identity
|
||||
</button>
|
||||
<span class="id-span">${id}</span>
|
||||
</li>`
|
||||
)}
|
||||
</ul>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-identity-manager', TfIdentityManagerElement);
|
82
apps/user_settings/tf-password-form.js
Normal file
82
apps/user_settings/tf-password-form.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
class TfPasswordFormElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
//selected: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a password against different requirements
|
||||
* @param {string} password the password to validate
|
||||
* @returns
|
||||
*/
|
||||
validatePassword(password) {
|
||||
// TODO(tasiaiso)
|
||||
return true;
|
||||
}
|
||||
|
||||
submitPassword() {
|
||||
const currentPwd = this.shadowRoot.getElementById('current').value;
|
||||
const newPwd = this.shadowRoot.getElementById('new').value;
|
||||
const repeatPwd = this.shadowRoot.getElementById('Repeat').value;
|
||||
|
||||
if (!(newPwd === repeatPwd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// tfrpc.changePassword()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
|
||||
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="grid">
|
||||
<label for="current">Current password:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="current"
|
||||
name="current"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
|
||||
<label for="new">Enter new password:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="new"
|
||||
name="new"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
|
||||
<label for="repeat">Repeat new password:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="repeat"
|
||||
name="repeat"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button @click=${this.submitPassword} class="red">
|
||||
[Not implemented] Change my password
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-password-form', TfPasswordFormElement);
|
51
apps/user_settings/tf-theme-picker.js
Normal file
51
apps/user_settings/tf-theme-picker.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import {LitElement, html, nothing} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
class TfThemePickerElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selected: {type: String},
|
||||
themes: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.themes = await tfrpc.rpc.getThemes();
|
||||
this.selected = await tfrpc.rpc.getTheme();
|
||||
|
||||
let select = this.renderRoot?.querySelector('#theme-select');
|
||||
select.value = this.selected;
|
||||
}
|
||||
|
||||
changed(event) {
|
||||
this.selected = event.srcElement.value;
|
||||
console.log('selected theme', this.selected);
|
||||
// TODO
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
|
||||
|
||||
<label for="theme">[Not implemented] Choose your theme:</label>
|
||||
|
||||
<select
|
||||
name="theme"
|
||||
id="theme-select"
|
||||
?hidden=${!this.themes?.length}
|
||||
@change=${this.changed}
|
||||
>
|
||||
${(this.themes ?? []).map(
|
||||
(name) => html`<option value=${name}>${name}</option>`
|
||||
)}
|
||||
</select>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-theme-picker', TfThemePickerElement);
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📝",
|
||||
"previous": "&/wl8HE2jZShRXTYEVYRrK3pjHwi41Wbxl9HoSJaQP6Y=.sha256"
|
||||
"previous": "&DnfuAUGzzalSh9NgZXnzDc9Ru5aM0omfRJ4h27jYw4k=.sha256"
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
@@ -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) =>
|
||||
|
@@ -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,14 +345,12 @@ 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},
|
||||
})}
|
||||
self.on_wiki_doc_changed({detail: {value: doc}})}
|
||||
>
|
||||
${doc?.private ? '🔒' : '📄'} ${doc.name}
|
||||
</li>
|
||||
|
@@ -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
115
apps/wiki/tildefriends.css
Normal 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;
|
||||
}
|
BIN
bleh.tar.xz
Normal file
BIN
bleh.tar.xz
Normal file
Binary file not shown.
@@ -211,7 +211,6 @@ function isNameValid(name) {
|
||||
function handler(request, response) {
|
||||
// TODO(tasiaiso): split this function
|
||||
let session = getCookies(request.headers).session;
|
||||
|
||||
if (request.uri == '/login') {
|
||||
let formData = form.decodeForm(request.query);
|
||||
if (query(request.headers)?.permissions?.authenticated) {
|
||||
|
@@ -171,9 +171,7 @@ class TfNavigationElement extends LitElement {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
/* prettier-ignore */
|
||||
${k_global_style}
|
||||
.tooltip {
|
||||
${k_global_style} .tooltip {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
@@ -1647,7 +1645,11 @@ async function sourcePretty() {
|
||||
let formatted = await prettier.format(source, {
|
||||
parser: 'babel',
|
||||
plugins: [babel, estree],
|
||||
trailingComma: 'es5',
|
||||
useTabs: true,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
bracketSpacing: false,
|
||||
});
|
||||
if (source !== formatted) {
|
||||
gEditor.dispatch({
|
||||
@@ -1660,6 +1662,31 @@ 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 = '';
|
||||
window.localStorage.setItem('visible_whitespace', '0');
|
||||
} else {
|
||||
editor_style.innerHTML = css`
|
||||
.cm-trailingSpace {
|
||||
background-color: unset !important;
|
||||
}
|
||||
.cm-highlightTab {
|
||||
background-image: unset !important;
|
||||
}
|
||||
.cm-highlightSpace:before {
|
||||
content: unset !important;
|
||||
}
|
||||
`;
|
||||
window.localStorage.setItem('visible_whitespace', '1');
|
||||
}
|
||||
}
|
||||
|
||||
// TODOC
|
||||
window.addEventListener('load', function () {
|
||||
window.addEventListener('hashchange', hashChange);
|
||||
@@ -1685,6 +1712,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) {
|
||||
@@ -1698,4 +1728,7 @@ window.addEventListener('load', function () {
|
||||
} else {
|
||||
closeEditor();
|
||||
}
|
||||
if (window.localStorage.getItem('visible_whitespace') == '1') {
|
||||
toggleVisibleWhitespace();
|
||||
}
|
||||
});
|
||||
|
99
core/core.js
99
core/core.js
@@ -18,20 +18,104 @@ const k_mime_types = {
|
||||
svg: 'image/svg+xml',
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const k_magic_bytes = [
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01], type: 'image/jpeg'},
|
||||
{
|
||||
bytes: [
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
|
||||
],
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00], type: 'image/jpeg'},
|
||||
{
|
||||
bytes: [
|
||||
0xff,
|
||||
0xd8,
|
||||
0xff,
|
||||
0xe1,
|
||||
null,
|
||||
null,
|
||||
0x45,
|
||||
0x78,
|
||||
0x69,
|
||||
0x66,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
|
||||
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
|
||||
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
|
||||
{bytes: [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50], type: 'image/webp'},
|
||||
{
|
||||
bytes: [
|
||||
0x52,
|
||||
0x49,
|
||||
0x46,
|
||||
0x46,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x57,
|
||||
0x45,
|
||||
0x42,
|
||||
0x50,
|
||||
],
|
||||
type: 'image/webp',
|
||||
},
|
||||
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32], type: 'audio/mpeg'},
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d], type: 'video/mp4'},
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32], type: 'video/mp4'},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
],
|
||||
type: 'audio/mpeg',
|
||||
},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x69,
|
||||
0x73,
|
||||
0x6f,
|
||||
0x6d,
|
||||
],
|
||||
type: 'video/mp4',
|
||||
},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
],
|
||||
type: 'video/mp4',
|
||||
},
|
||||
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
|
||||
];
|
||||
|
||||
@@ -494,6 +578,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
imports.ssb = Object.fromEntries(
|
||||
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
||||
);
|
||||
imports.ssb.port = tildefriends.ssb_port;
|
||||
imports.ssb.createIdentity = function () {
|
||||
if (
|
||||
process.credentials &&
|
||||
|
@@ -57,7 +57,7 @@
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="icon"
|
||||
name="icon"
|
||||
accesskey="i"
|
||||
accesskey="j"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Set an icon/emoji for the app"
|
||||
>
|
||||
@@ -93,6 +93,16 @@
|
||||
>
|
||||
🧼
|
||||
</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"
|
||||
@@ -125,6 +135,7 @@
|
||||
<tf-files-pane style="overflow: auto"></tf-files-pane>
|
||||
</div>
|
||||
<div style="flex: 1 1; overflow: auto">
|
||||
<style id="editor_style"></style>
|
||||
<div id="editor" style="width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
114
core/tildefriends-v1.css
Normal file
114
core/tildefriends-v1.css
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Tilde Friends core stylesheet
|
||||
*
|
||||
* 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="/static/tildefriends-v1.css"/>
|
||||
*
|
||||
* v1.0 / 2024 M03 21
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
655
deps/codemirror_src/package-lock.json
generated
vendored
655
deps/codemirror_src/package-lock.json
generated
vendored
@@ -7,21 +7,21 @@
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.2.1",
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"rollup": "^4.9.6"
|
||||
"rollup": "^4.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.12.0.tgz",
|
||||
"integrity": "sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz",
|
||||
"integrity": "sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -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.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.1.tgz",
|
||||
"integrity": "sha512-2LXLxsQnHDdfGzDvjzAwZh2ZviNJm7im6tGpa0IONIDnFd8RZ80D2SNi8PDi6YjKcMoMRK20v6OmKIdsrwsyoQ==",
|
||||
"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.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"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=="
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"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.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"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.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"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,31 +342,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
|
||||
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -375,9 +355,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
|
||||
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -387,9 +367,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
|
||||
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -399,9 +379,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
|
||||
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
|
||||
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -411,9 +391,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
|
||||
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
|
||||
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -423,9 +403,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
|
||||
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -435,9 +415,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
|
||||
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
|
||||
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -447,9 +427,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
|
||||
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -458,10 +438,34 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
|
||||
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
|
||||
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
|
||||
"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",
|
||||
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
|
||||
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -471,9 +475,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
|
||||
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -483,9 +487,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
|
||||
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -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.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"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,56 +678,10 @@
|
||||
"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",
|
||||
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
|
||||
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
},
|
||||
@@ -641,70 +693,23 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.12.0",
|
||||
"@rollup/rollup-android-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-x64": "4.12.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.12.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.12.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.12.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.13.0",
|
||||
"@rollup/rollup-android-arm64": "4.13.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.13.0",
|
||||
"@rollup/rollup-darwin-x64": "4.13.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.13.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.13.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.13.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.13.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.13.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.13.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.13.0",
|
||||
"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.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||
},
|
||||
"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.29.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
|
||||
"integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
deps/codemirror_src/package.json
vendored
4
deps/codemirror_src/package.json
vendored
@@ -5,12 +5,12 @@
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.2.1",
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"rollup": "^4.9.6"
|
||||
"rollup": "^4.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4.4"
|
||||
|
93
deps/sqlite/shell.c
vendored
93
deps/sqlite/shell.c
vendored
@@ -580,6 +580,9 @@ zSkipValidUtf8(const char *z, int nAccept, long ccm);
|
||||
#ifndef HAVE_CONSOLE_IO_H
|
||||
# include "console_io.h"
|
||||
#endif
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(disable : 4204)
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_CIO_NO_TRANSLATE
|
||||
# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
|
||||
@@ -678,6 +681,10 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){
|
||||
# endif
|
||||
}
|
||||
|
||||
# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
|
||||
# endif
|
||||
|
||||
# if CIO_WIN_WC_XLATE
|
||||
/* Define console modes for use with the Windows Console API. */
|
||||
# define SHELL_CONI_MODE \
|
||||
@@ -1228,6 +1235,10 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
|
||||
}
|
||||
#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(default : 4204)
|
||||
#endif
|
||||
|
||||
#undef SHELL_INVALID_FILE_PTR
|
||||
|
||||
/************************* End ../ext/consio/console_io.c ********************/
|
||||
@@ -20619,6 +20630,7 @@ static void exec_prepared_stmt_columnar(
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc!=SQLITE_ROW ) return;
|
||||
nColumn = sqlite3_column_count(pStmt);
|
||||
if( nColumn==0 ) goto columnar_end;
|
||||
nAlloc = nColumn*4;
|
||||
if( nAlloc<=0 ) nAlloc = 1;
|
||||
azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
|
||||
@@ -20704,7 +20716,6 @@ static void exec_prepared_stmt_columnar(
|
||||
if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
|
||||
}
|
||||
if( seenInterrupt ) goto columnar_end;
|
||||
if( nColumn==0 ) goto columnar_end;
|
||||
switch( p->cMode ){
|
||||
case MODE_Column: {
|
||||
colSep = " ";
|
||||
@@ -25553,16 +25564,15 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
#ifndef SQLITE_SHELL_FIDDLE
|
||||
if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
|
||||
char *zTable = 0; /* Insert data into this table */
|
||||
char *zSchema = 0; /* within this schema (may default to "main") */
|
||||
char *zSchema = 0; /* Schema of zTable */
|
||||
char *zFile = 0; /* Name of file to extra content from */
|
||||
sqlite3_stmt *pStmt = NULL; /* A statement */
|
||||
int nCol; /* Number of columns in the table */
|
||||
int nByte; /* Number of bytes in an SQL string */
|
||||
i64 nByte; /* Number of bytes in an SQL string */
|
||||
int i, j; /* Loop counters */
|
||||
int needCommit; /* True to COMMIT or ROLLBACK at end */
|
||||
int nSep; /* Number of bytes in p->colSeparator[] */
|
||||
char *zSql; /* An SQL statement */
|
||||
char *zFullTabName; /* Table name with schema if applicable */
|
||||
char *zSql = 0; /* An SQL statement */
|
||||
ImportCtx sCtx; /* Reader context */
|
||||
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
|
||||
int eVerbose = 0; /* Larger for more console output */
|
||||
@@ -25696,24 +25706,14 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
while( (nSkip--)>0 ){
|
||||
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
|
||||
}
|
||||
if( zSchema!=0 ){
|
||||
zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
|
||||
}else{
|
||||
zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
|
||||
}
|
||||
zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
|
||||
if( zSql==0 || zFullTabName==0 ){
|
||||
import_cleanup(&sCtx);
|
||||
shell_out_of_memory();
|
||||
}
|
||||
nByte = strlen30(zSql);
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
|
||||
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
|
||||
if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){
|
||||
/* Table does not exist. Create it. */
|
||||
sqlite3 *dbCols = 0;
|
||||
char *zRenames = 0;
|
||||
char *zColDefs;
|
||||
zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
|
||||
zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
|
||||
zSchema ? zSchema : "main", zTable);
|
||||
while( xRead(&sCtx) ){
|
||||
zAutoColumn(sCtx.z, &dbCols, 0);
|
||||
if( sCtx.cTerm!=sCtx.cColSep ) break;
|
||||
@@ -25728,34 +25728,50 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
assert(dbCols==0);
|
||||
if( zColDefs==0 ){
|
||||
eputf("%s: empty file\n", sCtx.zFile);
|
||||
import_fail:
|
||||
sqlite3_free(zCreate);
|
||||
sqlite3_free(zSql);
|
||||
sqlite3_free(zFullTabName);
|
||||
import_cleanup(&sCtx);
|
||||
rc = 1;
|
||||
goto meta_command_exit;
|
||||
}
|
||||
zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
|
||||
if( zCreate==0 ){
|
||||
import_cleanup(&sCtx);
|
||||
shell_out_of_memory();
|
||||
}
|
||||
if( eVerbose>=1 ){
|
||||
oputf("%s\n", zCreate);
|
||||
}
|
||||
rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
|
||||
if( rc ){
|
||||
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
|
||||
goto import_fail;
|
||||
}
|
||||
sqlite3_free(zCreate);
|
||||
zCreate = 0;
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
if( rc ){
|
||||
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
|
||||
import_cleanup(&sCtx);
|
||||
rc = 1;
|
||||
goto meta_command_exit;
|
||||
}
|
||||
}
|
||||
zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);",
|
||||
zTable, zSchema);
|
||||
if( zSql==0 ){
|
||||
import_cleanup(&sCtx);
|
||||
shell_out_of_memory();
|
||||
}
|
||||
nByte = strlen(zSql);
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
zSql = 0;
|
||||
if( rc ){
|
||||
if (pStmt) sqlite3_finalize(pStmt);
|
||||
eputf("Error: %s\n", sqlite3_errmsg(p->db));
|
||||
goto import_fail;
|
||||
import_cleanup(&sCtx);
|
||||
rc = 1;
|
||||
goto meta_command_exit;
|
||||
}
|
||||
if( sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
nCol = sqlite3_column_int(pStmt, 0);
|
||||
}else{
|
||||
nCol = 0;
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
nCol = sqlite3_column_count(pStmt);
|
||||
sqlite3_finalize(pStmt);
|
||||
pStmt = 0;
|
||||
if( nCol==0 ) return 0; /* no columns, no error */
|
||||
@@ -25764,7 +25780,12 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
import_cleanup(&sCtx);
|
||||
shell_out_of_memory();
|
||||
}
|
||||
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
|
||||
if( zSchema ){
|
||||
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?",
|
||||
zSchema, zTable);
|
||||
}else{
|
||||
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
|
||||
}
|
||||
j = strlen30(zSql);
|
||||
for(i=1; i<nCol; i++){
|
||||
zSql[j++] = ',';
|
||||
@@ -25776,13 +25797,15 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
oputf("Insert using: %s\n", zSql);
|
||||
}
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
zSql = 0;
|
||||
if( rc ){
|
||||
eputf("Error: %s\n", sqlite3_errmsg(p->db));
|
||||
if (pStmt) sqlite3_finalize(pStmt);
|
||||
goto import_fail;
|
||||
import_cleanup(&sCtx);
|
||||
rc = 1;
|
||||
goto meta_command_exit;
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
sqlite3_free(zFullTabName);
|
||||
needCommit = sqlite3_get_autocommit(p->db);
|
||||
if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
|
||||
do{
|
||||
|
259
deps/sqlite/sqlite3.c
vendored
259
deps/sqlite/sqlite3.c
vendored
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
** This file is an amalgamation of many separate C source files from SQLite
|
||||
** version 3.45.1. By combining all the individual C code files into this
|
||||
** version 3.45.2. By combining all the individual C code files into this
|
||||
** single large file, the entire code can be compiled as a single translation
|
||||
** unit. This allows many compilers to do optimizations that would not be
|
||||
** possible if the files were compiled separately. Performance improvements
|
||||
@@ -18,7 +18,7 @@
|
||||
** separate file. This file contains only code for the core SQLite library.
|
||||
**
|
||||
** The content in this amalgamation comes from Fossil check-in
|
||||
** e876e51a0ed5c5b3126f52e532044363a014.
|
||||
** d8cd6d49b46a395b13955387d05e9e1a2a47.
|
||||
*/
|
||||
#define SQLITE_CORE 1
|
||||
#define SQLITE_AMALGAMATION 1
|
||||
@@ -459,9 +459,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.45.1"
|
||||
#define SQLITE_VERSION_NUMBER 3045001
|
||||
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
|
||||
#define SQLITE_VERSION "3.45.2"
|
||||
#define SQLITE_VERSION_NUMBER 3045002
|
||||
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -733,6 +733,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
|
||||
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
|
||||
** <li> The application must not modify the SQL statement text passed into
|
||||
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
|
||||
** <li> The application must not dereference the arrays or string pointers
|
||||
** passed as the 3rd and 4th callback parameters after it returns.
|
||||
** </ul>
|
||||
*/
|
||||
SQLITE_API int sqlite3_exec(
|
||||
@@ -15097,6 +15099,7 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace;
|
||||
** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing
|
||||
** 0x00020000 Transform DISTINCT into GROUP BY
|
||||
** 0x00040000 SELECT tree dump after all code has been generated
|
||||
** 0x00080000 NOT NULL strength reduction
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -19346,6 +19349,7 @@ struct NameContext {
|
||||
#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */
|
||||
#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */
|
||||
#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */
|
||||
#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */
|
||||
#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */
|
||||
|
||||
/*
|
||||
@@ -19369,6 +19373,7 @@ struct Upsert {
|
||||
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
|
||||
Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
|
||||
u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */
|
||||
u8 isDup; /* True if 2nd or later with same pUpsertIdx */
|
||||
/* Above this point is the parse tree for the ON CONFLICT clauses.
|
||||
** The next group of fields stores intermediate data. */
|
||||
void *pToFree; /* Free memory when deleting the Upsert object */
|
||||
@@ -21444,7 +21449,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8);
|
||||
SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
|
||||
SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*);
|
||||
SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
|
||||
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
|
||||
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*);
|
||||
SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
|
||||
SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*);
|
||||
SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*);
|
||||
@@ -31309,6 +31314,7 @@ SQLITE_API void sqlite3_str_vappendf(
|
||||
if( xtype==etFLOAT ){
|
||||
iRound = -precision;
|
||||
}else if( xtype==etGENERIC ){
|
||||
if( precision==0 ) precision = 1;
|
||||
iRound = precision;
|
||||
}else{
|
||||
iRound = precision+1;
|
||||
@@ -35199,6 +35205,9 @@ do_atof_calc:
|
||||
u64 s2;
|
||||
rr[0] = (double)s;
|
||||
s2 = (u64)rr[0];
|
||||
#if defined(_MSC_VER) && _MSC_VER<1700
|
||||
if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
|
||||
#endif
|
||||
rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
|
||||
if( e>0 ){
|
||||
while( e>=100 ){
|
||||
@@ -35641,7 +35650,7 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou
|
||||
assert( p->n>0 );
|
||||
assert( p->n<sizeof(p->zBuf) );
|
||||
p->iDP = p->n + exp;
|
||||
if( iRound<0 ){
|
||||
if( iRound<=0 ){
|
||||
iRound = p->iDP - iRound;
|
||||
if( iRound==0 && p->zBuf[i+1]>='5' ){
|
||||
iRound = 1;
|
||||
@@ -53262,6 +53271,14 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
||||
pOut = 0;
|
||||
}else{
|
||||
sz = sqlite3_column_int64(pStmt, 0)*szPage;
|
||||
if( sz==0 ){
|
||||
sqlite3_reset(pStmt);
|
||||
sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0);
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
sz = sqlite3_column_int64(pStmt, 0)*szPage;
|
||||
}
|
||||
}
|
||||
if( piSize ) *piSize = sz;
|
||||
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
|
||||
pOut = 0;
|
||||
@@ -77088,7 +77105,10 @@ static int fillInCell(
|
||||
n = nHeader + nPayload;
|
||||
testcase( n==3 );
|
||||
testcase( n==4 );
|
||||
if( n<4 ) n = 4;
|
||||
if( n<4 ){
|
||||
n = 4;
|
||||
pPayload[nPayload] = 0;
|
||||
}
|
||||
*pnSize = n;
|
||||
assert( nSrc<=nPayload );
|
||||
testcase( nSrc<nPayload );
|
||||
@@ -79534,7 +79554,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
||||
if( flags & BTREE_PREFORMAT ){
|
||||
rc = SQLITE_OK;
|
||||
szNew = p->pBt->nPreformatSize;
|
||||
if( szNew<4 ) szNew = 4;
|
||||
if( szNew<4 ){
|
||||
szNew = 4;
|
||||
newCell[3] = 0;
|
||||
}
|
||||
if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){
|
||||
CellInfo info;
|
||||
pPage->xParseCell(pPage, newCell, &info);
|
||||
@@ -88379,6 +88402,23 @@ static void serialGet(
|
||||
pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real;
|
||||
}
|
||||
}
|
||||
static int serialGet7(
|
||||
const unsigned char *buf, /* Buffer to deserialize from */
|
||||
Mem *pMem /* Memory cell to write value into */
|
||||
){
|
||||
u64 x = FOUR_BYTE_UINT(buf);
|
||||
u32 y = FOUR_BYTE_UINT(buf+4);
|
||||
x = (x<<32) + y;
|
||||
assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 );
|
||||
swapMixedEndianFloat(x);
|
||||
memcpy(&pMem->u.r, &x, sizeof(x));
|
||||
if( IsNaN(x) ){
|
||||
pMem->flags = MEM_Null;
|
||||
return 1;
|
||||
}
|
||||
pMem->flags = MEM_Real;
|
||||
return 0;
|
||||
}
|
||||
SQLITE_PRIVATE void sqlite3VdbeSerialGet(
|
||||
const unsigned char *buf, /* Buffer to deserialize from */
|
||||
u32 serial_type, /* Serial type to deserialize */
|
||||
@@ -89058,7 +89098,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
|
||||
}else if( serial_type==0 ){
|
||||
rc = -1;
|
||||
}else if( serial_type==7 ){
|
||||
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
|
||||
serialGet7(&aKey1[d1], &mem1);
|
||||
rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r);
|
||||
}else{
|
||||
i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]);
|
||||
@@ -89083,14 +89123,18 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
|
||||
}else if( serial_type==0 ){
|
||||
rc = -1;
|
||||
}else{
|
||||
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
|
||||
if( serial_type==7 ){
|
||||
if( mem1.u.r<pRhs->u.r ){
|
||||
if( serialGet7(&aKey1[d1], &mem1) ){
|
||||
rc = -1; /* mem1 is a NaN */
|
||||
}else if( mem1.u.r<pRhs->u.r ){
|
||||
rc = -1;
|
||||
}else if( mem1.u.r>pRhs->u.r ){
|
||||
rc = +1;
|
||||
}else{
|
||||
assert( rc==0 );
|
||||
}
|
||||
}else{
|
||||
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
|
||||
rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r);
|
||||
}
|
||||
}
|
||||
@@ -89160,7 +89204,14 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
|
||||
/* RHS is null */
|
||||
else{
|
||||
serial_type = aKey1[idx1];
|
||||
rc = (serial_type!=0 && serial_type!=10);
|
||||
if( serial_type==0
|
||||
|| serial_type==10
|
||||
|| (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0)
|
||||
){
|
||||
assert( rc==0 );
|
||||
}else{
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=0 ){
|
||||
@@ -94858,7 +94909,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
|
||||
}
|
||||
}
|
||||
}else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){
|
||||
if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
|
||||
if( (flags1 & MEM_Str)!=0 ){
|
||||
pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
|
||||
}else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
|
||||
testcase( pIn1->flags & MEM_Int );
|
||||
testcase( pIn1->flags & MEM_Real );
|
||||
testcase( pIn1->flags & MEM_IntReal );
|
||||
@@ -94867,7 +94920,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
|
||||
flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask);
|
||||
if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str;
|
||||
}
|
||||
if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
|
||||
if( (flags3 & MEM_Str)!=0 ){
|
||||
pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
|
||||
}else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
|
||||
testcase( pIn3->flags & MEM_Int );
|
||||
testcase( pIn3->flags & MEM_Real );
|
||||
testcase( pIn3->flags & MEM_IntReal );
|
||||
@@ -106212,6 +106267,8 @@ static void resolveAlias(
|
||||
assert( iCol>=0 && iCol<pEList->nExpr );
|
||||
pOrig = pEList->a[iCol].pExpr;
|
||||
assert( pOrig!=0 );
|
||||
assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) );
|
||||
if( pExpr->pAggInfo ) return;
|
||||
db = pParse->db;
|
||||
pDup = sqlite3ExprDup(db, pOrig, 0);
|
||||
if( db->mallocFailed ){
|
||||
@@ -107097,6 +107154,19 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
** resolved. This prevents "column" from being counted as having been
|
||||
** referenced, which might prevent a SELECT from being erroneously
|
||||
** marked as correlated.
|
||||
**
|
||||
** 2024-03-28: Beware of aggregates. A bare column of aggregated table
|
||||
** can still evaluate to NULL even though it is marked as NOT NULL.
|
||||
** Example:
|
||||
**
|
||||
** CREATE TABLE t1(a INT NOT NULL);
|
||||
** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1;
|
||||
**
|
||||
** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized
|
||||
** here because at the time this case is hit, we do not yet know whether
|
||||
** or not t1 is being aggregated. We have to assume the worst and omit
|
||||
** the optimization. The only time it is safe to apply this optimization
|
||||
** is within the WHERE clause.
|
||||
*/
|
||||
case TK_NOTNULL:
|
||||
case TK_ISNULL: {
|
||||
@@ -107107,19 +107177,36 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
anRef[i] = p->nRef;
|
||||
}
|
||||
sqlite3WalkExpr(pWalker, pExpr->pLeft);
|
||||
if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){
|
||||
if( IN_RENAME_OBJECT ) return WRC_Prune;
|
||||
if( sqlite3ExprCanBeNull(pExpr->pLeft) ){
|
||||
/* The expression can be NULL. So the optimization does not apply */
|
||||
return WRC_Prune;
|
||||
}
|
||||
|
||||
for(i=0, p=pNC; p; p=p->pNext, i++){
|
||||
if( (p->ncFlags & NC_Where)==0 ){
|
||||
return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */
|
||||
}
|
||||
}
|
||||
testcase( ExprHasProperty(pExpr, EP_OuterON) );
|
||||
assert( !ExprHasProperty(pExpr, EP_IntValue) );
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x80000 ){
|
||||
sqlite3DebugPrintf(
|
||||
"NOT NULL strength reduction converts the following to %d:\n",
|
||||
pExpr->op==TK_NOTNULL
|
||||
);
|
||||
sqlite3ShowExpr(pExpr);
|
||||
}
|
||||
#endif /* TREETRACE_ENABLED */
|
||||
pExpr->u.iValue = (pExpr->op==TK_NOTNULL);
|
||||
pExpr->flags |= EP_IntValue;
|
||||
pExpr->op = TK_INTEGER;
|
||||
|
||||
for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
|
||||
p->nRef = anRef[i];
|
||||
}
|
||||
sqlite3ExprDelete(pParse->db, pExpr->pLeft);
|
||||
pExpr->pLeft = 0;
|
||||
}
|
||||
return WRC_Prune;
|
||||
}
|
||||
|
||||
@@ -108019,7 +108106,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
|
||||
}
|
||||
if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
|
||||
}
|
||||
sNC.ncFlags |= NC_Where;
|
||||
if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
|
||||
sNC.ncFlags &= ~NC_Where;
|
||||
|
||||
/* Resolve names in table-valued-function arguments */
|
||||
for(i=0; i<p->pSrc->nSrc; i++){
|
||||
@@ -128947,13 +129036,13 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){
|
||||
double r1, r2;
|
||||
const char *zVal;
|
||||
r1 = sqlite3_value_double(pValue);
|
||||
sqlite3_str_appendf(pStr, "%!.15g", r1);
|
||||
sqlite3_str_appendf(pStr, "%!0.15g", r1);
|
||||
zVal = sqlite3_str_value(pStr);
|
||||
if( zVal ){
|
||||
sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8);
|
||||
if( r1!=r2 ){
|
||||
sqlite3_str_reset(pStr);
|
||||
sqlite3_str_appendf(pStr, "%!.20e", r1);
|
||||
sqlite3_str_appendf(pStr, "%!0.20e", r1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -129255,7 +129344,7 @@ static void replaceFunc(
|
||||
}
|
||||
if( zPattern[0]==0 ){
|
||||
assert( sqlite3_value_type(argv[1])!=SQLITE_NULL );
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
sqlite3_result_text(context, (const char*)zStr, nStr, SQLITE_TRANSIENT);
|
||||
return;
|
||||
}
|
||||
nPattern = sqlite3_value_bytes(argv[1]);
|
||||
@@ -133175,7 +133264,7 @@ SQLITE_PRIVATE void sqlite3Insert(
|
||||
pNx->iDataCur = iDataCur;
|
||||
pNx->iIdxCur = iIdxCur;
|
||||
if( pNx->pUpsertTarget ){
|
||||
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){
|
||||
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){
|
||||
goto insert_cleanup;
|
||||
}
|
||||
}
|
||||
@@ -139474,31 +139563,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
|
||||
int mxCol; /* Maximum non-virtual column number */
|
||||
|
||||
if( pObjTab && pObjTab!=pTab ) continue;
|
||||
if( !IsOrdinaryTable(pTab) ){
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
sqlite3_vtab *pVTab;
|
||||
int a1;
|
||||
if( !IsVirtual(pTab) ) continue;
|
||||
if( pTab->nCol<=0 ){
|
||||
const char *zMod = pTab->u.vtab.azArg[0];
|
||||
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
|
||||
}
|
||||
sqlite3ViewGetColumnNames(pParse, pTab);
|
||||
if( pTab->u.vtab.p==0 ) continue;
|
||||
pVTab = pTab->u.vtab.p->pVtab;
|
||||
if( NEVER(pVTab==0) ) continue;
|
||||
if( NEVER(pVTab->pModule==0) ) continue;
|
||||
if( pVTab->pModule->iVersion<4 ) continue;
|
||||
if( pVTab->pModule->xIntegrity==0 ) continue;
|
||||
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
|
||||
pTab->nTabRef++;
|
||||
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
|
||||
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
|
||||
integrityCheckResultRow(v);
|
||||
sqlite3VdbeJumpHere(v, a1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if( !IsOrdinaryTable(pTab) ) continue;
|
||||
if( isQuick || HasRowid(pTab) ){
|
||||
pPk = 0;
|
||||
r2 = 0;
|
||||
@@ -139633,6 +139698,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
|
||||
** is REAL, we have to load the actual data using OP_Column
|
||||
** to reliably determine if the value is a NULL. */
|
||||
sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3);
|
||||
sqlite3ColumnDefault(v, pTab, j, 3);
|
||||
jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk);
|
||||
VdbeCoverage(v);
|
||||
}
|
||||
@@ -139823,6 +139889,38 @@ SQLITE_PRIVATE void sqlite3Pragma(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
/* Second pass to invoke the xIntegrity method on all virtual
|
||||
** tables.
|
||||
*/
|
||||
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
|
||||
Table *pTab = sqliteHashData(x);
|
||||
sqlite3_vtab *pVTab;
|
||||
int a1;
|
||||
if( pObjTab && pObjTab!=pTab ) continue;
|
||||
if( IsOrdinaryTable(pTab) ) continue;
|
||||
if( !IsVirtual(pTab) ) continue;
|
||||
if( pTab->nCol<=0 ){
|
||||
const char *zMod = pTab->u.vtab.azArg[0];
|
||||
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
|
||||
}
|
||||
sqlite3ViewGetColumnNames(pParse, pTab);
|
||||
if( pTab->u.vtab.p==0 ) continue;
|
||||
pVTab = pTab->u.vtab.p->pVtab;
|
||||
if( NEVER(pVTab==0) ) continue;
|
||||
if( NEVER(pVTab->pModule==0) ) continue;
|
||||
if( pVTab->pModule->iVersion<4 ) continue;
|
||||
if( pVTab->pModule->xIntegrity==0 ) continue;
|
||||
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
|
||||
pTab->nTabRef++;
|
||||
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
|
||||
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
|
||||
integrityCheckResultRow(v);
|
||||
sqlite3VdbeJumpHere(v, a1);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
{
|
||||
static const int iLn = VDBE_OFFSET_LINENO(2);
|
||||
@@ -153460,7 +153558,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew(
|
||||
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
|
||||
Parse *pParse, /* The parsing context */
|
||||
SrcList *pTabList, /* Table into which we are inserting */
|
||||
Upsert *pUpsert /* The ON CONFLICT clauses */
|
||||
Upsert *pUpsert, /* The ON CONFLICT clauses */
|
||||
Upsert *pAll /* Complete list of all ON CONFLICT clauses */
|
||||
){
|
||||
Table *pTab; /* That table into which we are inserting */
|
||||
int rc; /* Result code */
|
||||
@@ -153563,6 +153662,14 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
|
||||
continue;
|
||||
}
|
||||
pUpsert->pUpsertIdx = pIdx;
|
||||
if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){
|
||||
/* Really this should be an error. The isDup ON CONFLICT clause will
|
||||
** never fire. But this problem was not discovered until three years
|
||||
** after multi-CONFLICT upsert was added, and so we silently ignore
|
||||
** the problem to prevent breaking applications that might actually
|
||||
** have redundant ON CONFLICT clauses. */
|
||||
pUpsert->isDup = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if( pUpsert->pUpsertIdx==0 ){
|
||||
@@ -153589,9 +153696,13 @@ SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){
|
||||
Upsert *pNext;
|
||||
if( NEVER(pUpsert==0) ) return 0;
|
||||
pNext = pUpsert->pNextUpsert;
|
||||
while( 1 /*exit-by-return*/ ){
|
||||
if( pNext==0 ) return 1;
|
||||
if( pNext->pUpsertTarget==0 ) return 1;
|
||||
if( pNext->pUpsertIdx==0 ) return 1;
|
||||
if( !pNext->isDup ) return 0;
|
||||
pNext = pNext->pNextUpsert;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -204785,6 +204896,7 @@ json_parse_restart:
|
||||
case '[': {
|
||||
/* Parse array */
|
||||
iThis = pParse->nBlob;
|
||||
assert( i<=(u32)pParse->nJson );
|
||||
jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0);
|
||||
iStart = pParse->nBlob;
|
||||
if( pParse->oom ) return -1;
|
||||
@@ -205183,6 +205295,10 @@ static void jsonReturnStringAsBlob(JsonString *pStr){
|
||||
JsonParse px;
|
||||
memset(&px, 0, sizeof(px));
|
||||
jsonStringTerminate(pStr);
|
||||
if( pStr->eErr ){
|
||||
sqlite3_result_error_nomem(pStr->pCtx);
|
||||
return;
|
||||
}
|
||||
px.zJson = pStr->zBuf;
|
||||
px.nJson = pStr->nUsed;
|
||||
px.db = sqlite3_context_db_handle(pStr->pCtx);
|
||||
@@ -206508,8 +206624,9 @@ rebuild_from_cache:
|
||||
}
|
||||
p->zJson = (char*)sqlite3_value_text(pArg);
|
||||
p->nJson = sqlite3_value_bytes(pArg);
|
||||
if( db->mallocFailed ) goto json_pfa_oom;
|
||||
if( p->nJson==0 ) goto json_pfa_malformed;
|
||||
if( NEVER(p->zJson==0) ) goto json_pfa_oom;
|
||||
assert( p->zJson!=0 );
|
||||
if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){
|
||||
if( flgs & JSON_KEEPERROR ){
|
||||
p->nErr = 1;
|
||||
@@ -206675,10 +206792,10 @@ static void jsonDebugPrintBlob(
|
||||
if( sz==0 && x<=JSONB_FALSE ){
|
||||
sqlite3_str_append(pOut, "\n", 1);
|
||||
}else{
|
||||
u32 i;
|
||||
u32 j;
|
||||
sqlite3_str_appendall(pOut, ": \"");
|
||||
for(i=iStart+n; i<iStart+n+sz; i++){
|
||||
u8 c = pParse->aBlob[i];
|
||||
for(j=iStart+n; j<iStart+n+sz; j++){
|
||||
u8 c = pParse->aBlob[j];
|
||||
if( c<0x20 || c>=0x7f ) c = '.';
|
||||
sqlite3_str_append(pOut, (char*)&c, 1);
|
||||
}
|
||||
@@ -208086,6 +208203,9 @@ static int jsonEachColumn(
|
||||
case JEACH_VALUE: {
|
||||
u32 i = jsonSkipLabel(p);
|
||||
jsonReturnFromBlob(&p->sParse, i, ctx, 1);
|
||||
if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){
|
||||
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JEACH_TYPE: {
|
||||
@@ -208132,9 +208252,9 @@ static int jsonEachColumn(
|
||||
case JEACH_JSON: {
|
||||
if( p->sParse.zJson==0 ){
|
||||
sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob,
|
||||
SQLITE_STATIC);
|
||||
SQLITE_TRANSIENT);
|
||||
}else{
|
||||
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC);
|
||||
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -209160,11 +209280,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
|
||||
** Clear the Rtree.pNodeBlob object
|
||||
*/
|
||||
static void nodeBlobReset(Rtree *pRtree){
|
||||
if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
|
||||
sqlite3_blob *pBlob = pRtree->pNodeBlob;
|
||||
pRtree->pNodeBlob = 0;
|
||||
sqlite3_blob_close(pBlob);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -209208,7 +209326,6 @@ static int nodeAcquire(
|
||||
&pRtree->pNodeBlob);
|
||||
}
|
||||
if( rc ){
|
||||
nodeBlobReset(pRtree);
|
||||
*ppNode = 0;
|
||||
/* If unable to open an sqlite3_blob on the desired row, that can only
|
||||
** be because the shadow tables hold erroneous data. */
|
||||
@@ -209268,6 +209385,7 @@ static int nodeAcquire(
|
||||
}
|
||||
*ppNode = pNode;
|
||||
}else{
|
||||
nodeBlobReset(pRtree);
|
||||
if( pNode ){
|
||||
pRtree->nNodeRef--;
|
||||
sqlite3_free(pNode);
|
||||
@@ -209412,6 +209530,7 @@ static void nodeGetCoord(
|
||||
int iCoord, /* Which coordinate to extract */
|
||||
RtreeCoord *pCoord /* OUT: Space to write result to */
|
||||
){
|
||||
assert( iCell<NCELL(pNode) );
|
||||
readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord);
|
||||
}
|
||||
|
||||
@@ -209601,7 +209720,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
|
||||
sqlite3_finalize(pCsr->pReadAux);
|
||||
sqlite3_free(pCsr);
|
||||
pRtree->nCursor--;
|
||||
if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){
|
||||
nodeBlobReset(pRtree);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -210186,8 +210307,12 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
|
||||
int rc = SQLITE_OK;
|
||||
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
|
||||
if( rc==SQLITE_OK && ALWAYS(p) ){
|
||||
if( p->iCell>=NCELL(pNode) ){
|
||||
rc = SQLITE_ABORT;
|
||||
}else{
|
||||
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -210204,6 +210329,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
|
||||
|
||||
if( rc ) return rc;
|
||||
if( NEVER(p==0) ) return SQLITE_OK;
|
||||
if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT;
|
||||
if( i==0 ){
|
||||
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
|
||||
}else if( i<=pRtree->nDim2 ){
|
||||
@@ -211685,8 +211811,7 @@ constraint:
|
||||
*/
|
||||
static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
|
||||
Rtree *pRtree = (Rtree *)pVtab;
|
||||
assert( pRtree->inWrTrans==0 );
|
||||
pRtree->inWrTrans++;
|
||||
pRtree->inWrTrans = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -211700,6 +211825,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){
|
||||
nodeBlobReset(pRtree);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int rtreeRollback(sqlite3_vtab *pVtab){
|
||||
return rtreeEndTransaction(pVtab);
|
||||
}
|
||||
|
||||
/*
|
||||
** The xRename method for rtree module virtual tables.
|
||||
@@ -211818,7 +211946,7 @@ static sqlite3_module rtreeModule = {
|
||||
rtreeBeginTransaction, /* xBegin - begin transaction */
|
||||
rtreeEndTransaction, /* xSync - sync transaction */
|
||||
rtreeEndTransaction, /* xCommit - commit transaction */
|
||||
rtreeEndTransaction, /* xRollback - rollback transaction */
|
||||
rtreeRollback, /* xRollback - rollback transaction */
|
||||
0, /* xFindFunction - function overloading */
|
||||
rtreeRename, /* xRename - rename the table */
|
||||
rtreeSavepoint, /* xSavepoint */
|
||||
@@ -245377,23 +245505,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
|
||||
static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){
|
||||
int ii;
|
||||
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
|
||||
Fts5Index *pIndex = pIter->pIndex;
|
||||
|
||||
for(ii=0; ii<pT->nIter; ii++){
|
||||
Fts5Iter *p = pT->apIter[ii];
|
||||
if( p->base.bEof==0
|
||||
&& (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowid<iFrom))
|
||||
){
|
||||
fts5MultiIterNext(p->pIndex, p, bFrom, iFrom);
|
||||
fts5MultiIterNext(pIndex, p, bFrom, iFrom);
|
||||
while( bFrom && p->base.bEof==0
|
||||
&& p->base.iRowid<iFrom
|
||||
&& p->pIndex->rc==SQLITE_OK
|
||||
&& pIndex->rc==SQLITE_OK
|
||||
){
|
||||
fts5MultiIterNext(p->pIndex, p, 0, 0);
|
||||
fts5MultiIterNext(pIndex, p, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( pIndex->rc==SQLITE_OK ){
|
||||
fts5IterSetOutputsTokendata(pIter);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -250547,7 +250678,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
UNUSED_PARAM2(nArg, apUnused);
|
||||
sqlite3_result_text(pCtx, "fts5: 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a", -1, SQLITE_TRANSIENT);
|
||||
sqlite3_result_text(pCtx, "fts5: 2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77", -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
8
deps/sqlite/sqlite3.h
vendored
8
deps/sqlite/sqlite3.h
vendored
@@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.45.1"
|
||||
#define SQLITE_VERSION_NUMBER 3045001
|
||||
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
|
||||
#define SQLITE_VERSION "3.45.2"
|
||||
#define SQLITE_VERSION_NUMBER 3045002
|
||||
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -420,6 +420,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
|
||||
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
|
||||
** <li> The application must not modify the SQL statement text passed into
|
||||
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
|
||||
** <li> The application must not dereference the arrays or string pointers
|
||||
** passed as the 3rd and 4th callback parameters after it returns.
|
||||
** </ul>
|
||||
*/
|
||||
SQLITE_API int sqlite3_exec(
|
||||
|
17
deps/xopt/.circleci/Dockerfile.test
vendored
17
deps/xopt/.circleci/Dockerfile.test
vendored
@@ -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
|
7
deps/xopt/.circleci/config.yml
vendored
7
deps/xopt/.circleci/config.yml
vendored
@@ -1,7 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run: docker build -f .circleci/Dockerfile.test .
|
1
deps/xopt/.dockerignore
vendored
1
deps/xopt/.dockerignore
vendored
@@ -1 +0,0 @@
|
||||
.gitignore
|
18
deps/xopt/.editorconfig
vendored
18
deps/xopt/.editorconfig
vendored
@@ -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
|
6
deps/xopt/.gitignore
vendored
6
deps/xopt/.gitignore
vendored
@@ -1,6 +0,0 @@
|
||||
*.sw[a-p]
|
||||
/bin/
|
||||
/build/
|
||||
/.tup/
|
||||
/*.o
|
||||
/*.a
|
43
deps/xopt/CMakeLists.txt
vendored
43
deps/xopt/CMakeLists.txt
vendored
@@ -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
9
deps/xopt/README.md
vendored
@@ -1,9 +0,0 @@
|
||||
# XOpt [](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
2099
deps/xopt/snprintf.c
vendored
File diff suppressed because it is too large
Load Diff
2
deps/xopt/test/.gitignore
vendored
2
deps/xopt/test/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/*-test
|
||||
/*.o
|
12
deps/xopt/test/autohelp-1.out
vendored
12
deps/xopt/test/autohelp-1.out
vendored
@@ -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]
|
100
deps/xopt/test/autohelp.c
vendored
100
deps/xopt/test/autohelp.c
vendored
@@ -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;
|
||||
}
|
11
deps/xopt/test/macro-1.out
vendored
11
deps/xopt/test/macro-1.out
vendored
@@ -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
116
deps/xopt/test/macro.c
vendored
@@ -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;
|
||||
}
|
3
deps/xopt/test/nocondense-1.out
vendored
3
deps/xopt/test/nocondense-1.out
vendored
@@ -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
|
3
deps/xopt/test/nocondense-sloppy-1.out
vendored
3
deps/xopt/test/nocondense-sloppy-1.out
vendored
@@ -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
|
3
deps/xopt/test/nocondense-sloppy-2.out
vendored
3
deps/xopt/test/nocondense-sloppy-2.out
vendored
@@ -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
|
14
deps/xopt/test/nocondense-sloppy-3.out
vendored
14
deps/xopt/test/nocondense-sloppy-3.out
vendored
@@ -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
|
149
deps/xopt/test/nocondense-sloppy.c
vendored
149
deps/xopt/test/nocondense-sloppy.c
vendored
@@ -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;
|
||||
}
|
149
deps/xopt/test/nocondense.c
vendored
149
deps/xopt/test/nocondense.c
vendored
@@ -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;
|
||||
}
|
11
deps/xopt/test/optional-longarg-1.out
vendored
11
deps/xopt/test/optional-longarg-1.out
vendored
@@ -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
|
116
deps/xopt/test/optional-longarg.c
vendored
116
deps/xopt/test/optional-longarg.c
vendored
@@ -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;
|
||||
}
|
12
deps/xopt/test/required-1.out
vendored
12
deps/xopt/test/required-1.out
vendored
@@ -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
|
128
deps/xopt/test/required.c
vendored
128
deps/xopt/test/required.c
vendored
@@ -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;
|
||||
}
|
11
deps/xopt/test/simple-1.out
vendored
11
deps/xopt/test/simple-1.out
vendored
@@ -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
|
128
deps/xopt/test/simple.c
vendored
128
deps/xopt/test/simple.c
vendored
@@ -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;
|
||||
}
|
14
deps/xopt/test/sloppyshorts-1.out
vendored
14
deps/xopt/test/sloppyshorts-1.out
vendored
@@ -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
|
149
deps/xopt/test/sloppyshorts.c
vendored
149
deps/xopt/test/sloppyshorts.c
vendored
@@ -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;
|
||||
}
|
46
deps/xopt/test/test-case.sh
vendored
46
deps/xopt/test/test-case.sh
vendored
@@ -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
584
deps/xopt/xopt.c
vendored
@@ -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
225
deps/xopt/xopt.h
vendored
@@ -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
|
147
docs/guide.md
147
docs/guide.md
@@ -2,93 +2,114 @@
|
||||
|
||||
Tilde Friends is a platform for making, running, and sharing web applications.
|
||||
|
||||
When you visit Tilde Friends in a web browser, you are presented with a terminal interface, typically with a big text output box covering most of the page and an input box at the bottom, into which text or commands can be entered. A script runs to produce text output and consume user input.
|
||||
When you visit Tilde Friends in a web browser, you are presented with a
|
||||
terminal interface, typically with a big text output box covering most of the
|
||||
page and an input box at the bottom, into which text or commands can be
|
||||
entered. A script runs to produce text output and consume user input.
|
||||
|
||||
The script is a Tilde Friends application, and it runs on the server, which means that unlike client-side JavaScript, it can have the ability to read and write files on the server or create network connections to other machines. Unlike node.js or other server-side runtime environments, applications are limited for security reasons to not interfere with each other or bring the entire server down.
|
||||
The script is a Tilde Friends application, and it runs on the server, which
|
||||
means that unlike client-side JavaScript, it can have the ability to read and
|
||||
write files on the server or create network connections to other machines.
|
||||
Unlike node.js or other server-side runtime environments, applications are
|
||||
limited for security reasons to not interfere with each other or bring the
|
||||
entire server down.
|
||||
|
||||
Above the terminal, an "Edit" link brings a visitor to the source code for the current Tilde Friends application, which they can then edit, save as their own, and run.
|
||||
Above the terminal, an "Edit" link brings a visitor to the source code for the
|
||||
current Tilde Friends application, which they can then edit, save as their own,
|
||||
and run.
|
||||
|
||||
# Architecture
|
||||
|
||||
Tilde Friends is a C++ application with a JavaScript runtime that provides restricted access to filesystem, network, and other system resources. The core process runs a core set of scripts that implement a web server, typically starting a new process for each visitor's session which runs scripts for the active application and stopping it when the visitor leaves.
|
||||
Tilde Friends is a C++ application with a JavaScript runtime that provides
|
||||
restricted access to filesystem, network, and other system resources. The core
|
||||
process runs a core set of scripts that implement a web server, typically
|
||||
starting a new process for each visitor's session which runs scripts for the
|
||||
active application and stopping it when the visitor leaves.
|
||||
|
||||
Only the core process has access to most system resources, but session processes can be given accesss through the core process.
|
||||
Only the core process has access to most system resources, but session
|
||||
processes can be given accesss through the core process.
|
||||
|
||||
Service processes are identical to session processes, but they are not tied to a user session.
|
||||
Service processes are identical to session processes, but they are not tied to
|
||||
a user session.
|
||||
|
||||
## Communication
|
||||
|
||||
In the same way that web browsers expose APIs for scripts running in the browser to modify the document, play sounds and video, and draw, Tilde Friends exposes APIs for scripts running on a Tilde Friends server to interact with a visitor's web browser, read and write files on the server, and otherwise interact with the world.
|
||||
In the same way that web browsers expose APIs for scripts running in the
|
||||
browser to modify the document, play sounds and video, and draw, Tilde Friends
|
||||
exposes APIs for scripts running on a Tilde Friends server to interact with a
|
||||
visitor's web browser, read and write files on the server, and otherwise
|
||||
interact with the world.
|
||||
|
||||
There are several distinct classes of APIs.
|
||||
|
||||
First, there are low-level functions exposed from C++ to JavaScript. Most of these are only available to the core process. These typically only go through a basic JavaScript to C++ transition and are relatively fast and immediate.
|
||||
First, there are low-level functions exposed from C++ to JavaScript. Most of
|
||||
these are only available to the core process. These typically only go through
|
||||
a basic JavaScript to C++ transition and are relatively fast and immediate.
|
||||
|
||||
```js
|
||||
// Displays some text to the server's console.
|
||||
print('Hello, world!');
|
||||
```
|
||||
// Displays some text to the server's console.
|
||||
print("Hello, world!");
|
||||
|
||||
There is a mechanism for communicating between processes. Functions can be exported and called across process boundaries. When this is done, any arguments are serialized to a network protocol, deserialized by the other process, the function called, and finally any return value is passed back in the same way. Any functions referenced by the arguments or return value are also exported and can be subsequently called across process boundaries. Functions called across process boundaries are always asynchronous, returning a Promise. Care must be taken for security reasons to not pass dangerous functions ("deleteAllMydata()") to untrusted processes, and it is best for performance reasons to minimize the data size transferred between processes.
|
||||
There is a mechanism for communicating between processes. Functions can be
|
||||
exported and called across process boundaries. When this is done, any
|
||||
arguments are serialized to a network protocol, deserialized by the other
|
||||
process, the function called, and finally any return value is passed back in
|
||||
the same way. Any functions referenced by the arguments or return value are
|
||||
also exported and can be subsequently called across process boundaries.
|
||||
Functions called across process boundaries are always asynchronous, returning a
|
||||
Promise. Care must be taken for security reasons to not pass dangerous
|
||||
functions ("deleteAllMydata()") to untrusted processes, and it is best for
|
||||
performance reasons to minimize the data size transferred between processes.
|
||||
|
||||
```js
|
||||
// Send an "add" function to any other running processes. When called, it
|
||||
// will run in this process.
|
||||
core.broadcast({
|
||||
add: function (x, y) {
|
||||
return x + y;
|
||||
},
|
||||
});
|
||||
// Send an "add" function to any other running processes. When called, it
|
||||
// will run in this process.
|
||||
core.broadcast({add: function(x, y) { return x + y; }});
|
||||
|
||||
// Receive the above message and call the function.
|
||||
core.register('onMessage', function (sender, message) {
|
||||
message.add(3, 4).then((x) => terminal.print(x.toString()));
|
||||
});
|
||||
```
|
||||
// Receive the above message and call the function.
|
||||
core.register("onMessage", function(sender, message) {
|
||||
message.add(3, 4).then(x => terminal.print(x.toString()));
|
||||
});
|
||||
|
||||
Finally, there is a core web interface that runs on the client's browser that
|
||||
extends access to a running Tilde Friends script.
|
||||
|
||||
```js
|
||||
// Displays a message in the client's browser.
|
||||
terminal.print('Hello, world!');
|
||||
```
|
||||
// Displays a message in the client's browser.
|
||||
terminal.print("Hello, world!");
|
||||
|
||||
## API Documentation
|
||||
|
||||
The Tilde Friends API is very much evolving.
|
||||
|
||||
All currently registered methods can be explored in the [documentation](https://www.tildefriends.net/~cory/documentation) app.
|
||||
All currently registered methods can be explored in the
|
||||
[documentation](https://www.tildefriends.net/~cory/documentation) app.
|
||||
|
||||
All browser-facing methods are implemented in [client.js](core/client.js). Most process-related methods are implemented in [core.js](core/core.js).
|
||||
All browser-facing methods are implemented in [client.js](core/client.js).
|
||||
Most process-related methods are implemented in [core.js](core/core.js).
|
||||
|
||||
Higher-level behaviors are often implemented within library-style apps themselves and are beyond the scope of this document.
|
||||
Higher-level behaviors are often implemented within library-style apps
|
||||
themselves and are beyond the scope of this document.
|
||||
|
||||
### Terminal
|
||||
|
||||
All interaction with a human user is through a terminal-like interface. Though it is somewhat limiting, it makes simple things easy, and it is possible to construct complicated interfaces by creating and interacting with an iframe.
|
||||
All interaction with a human user is through a terminal-like interface. Though
|
||||
it is somewhat limiting, it makes simple things easy, and it is possible to
|
||||
construct complicated interfaces by creating and interacting with an iframe.
|
||||
|
||||
#### terminal.print(arguments...)
|
||||
|
||||
Print to the terminal. Arguments and lists are recursively expanded. Numerous special values are supported as implemented in client.cs.
|
||||
Print to the terminal. Arguments and lists are recursively expanded. Numerous
|
||||
special values are supported as implemented in client.cs.
|
||||
|
||||
```js
|
||||
// Create a link.
|
||||
terminal.print({href: 'http://www.tildefriends.net/', value: 'Tilde Friends!'});
|
||||
// Create a link.
|
||||
terminal.print({href: "http://www.tildefriends.net/", value: "Tilde Friends!"});
|
||||
|
||||
// Create an iframe.
|
||||
terminal.print({
|
||||
iframe: '<b>Hello, world!</b>',
|
||||
width: 640,
|
||||
height: 480,
|
||||
});
|
||||
// Create an iframe.
|
||||
terminal.print({iframe: "<b>Hello, world!</b>", width: 640, height: 480});
|
||||
|
||||
// Use style.
|
||||
terminal.print({style: 'color: #f00', value: 'Hello, world!'});
|
||||
// Use style.
|
||||
terminal.print({style: "color: #f00", value: "Hello, world!"});
|
||||
|
||||
// Create a link that when clicked will act as if the user typed a command.
|
||||
terminal.print({command: 'exit', value: 'Get out of here.'});
|
||||
```
|
||||
// Create a link that when clicked will act as if the user typed a command.
|
||||
terminal.print({command: "exit", value: "Get out of here."});
|
||||
|
||||
#### terminal.clear()
|
||||
|
||||
@@ -114,18 +135,16 @@ Sets the browser window/tab title.
|
||||
|
||||
Reconfigures the terminal layout, potentially into multiple split panes.
|
||||
|
||||
```js
|
||||
terminal.split([
|
||||
terminal.split([
|
||||
{
|
||||
type: 'horizontal',
|
||||
type: "horizontal",
|
||||
children: [
|
||||
{name: 'left', basis: '2in', grow: 0, shrink: 0},
|
||||
{name: 'middle', grow: 1},
|
||||
{name: 'right', basis: '2in', grow: 0, shrink: 0},
|
||||
{name: "left", basis: "2in", grow: 0, shrink: 0},
|
||||
{name: "middle", grow: 1},
|
||||
{name: "right", basis: "2in", grow: 0, shrink: 0},
|
||||
],
|
||||
},
|
||||
]);
|
||||
```
|
||||
]);
|
||||
|
||||
#### terminal.select(name)
|
||||
|
||||
@@ -133,11 +152,14 @@ Directs subsequent output to the named terminal.
|
||||
|
||||
#### terminal.postMessageToIframe(iframeName, message)
|
||||
|
||||
Sends a message to the iframe that was created with the given name, using the browser's window.postMessage.
|
||||
Sends a message to the iframe that was created with the given name, using the
|
||||
browser's window.postMessage.
|
||||
|
||||
### Database
|
||||
|
||||
Tilde Friends uses lmdb as a basic key value store. Keys and values are all expected to be of type String. Each application gets its own isolated database.
|
||||
Tilde Friends uses lmdb as a basic key value store. Keys and values are all
|
||||
expected to be of type String. Each application gets its own isolated
|
||||
database.
|
||||
|
||||
#### database.get(key)
|
||||
|
||||
@@ -159,13 +181,12 @@ Retrieve a list of all key names.
|
||||
|
||||
Network access is generally not extended to untrusted users.
|
||||
|
||||
It is necessary to grant network permissions to an app owner through the administration app.
|
||||
It is necessary to grant network permissions to an app owner through the
|
||||
administration app.
|
||||
|
||||
Apps that require network access must declare it like this:
|
||||
|
||||
```json
|
||||
{"permissions": ["network"]}
|
||||
```
|
||||
//! { "permissions": ["network"] }
|
||||
|
||||
#### network.newConnection()
|
||||
|
||||
|
3
package-lock.json
generated
3
package-lock.json
generated
@@ -12,8 +12,7 @@
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.unprompted.tildefriends"
|
||||
android:versionCode="16"
|
||||
android:versionName="0.0.16-wip">
|
||||
android:versionCode="17"
|
||||
android:versionName="0.0.17-wip">
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
@@ -7,8 +7,13 @@
|
||||
** @{
|
||||
*/
|
||||
|
||||
/** A JS context. */
|
||||
typedef struct JSContext JSContext;
|
||||
|
||||
/**
|
||||
** Register the bcrypt script interface.
|
||||
** @param context The JS context.
|
||||
*/
|
||||
void tf_bcrypt_register(JSContext* context);
|
||||
|
||||
/** @} */
|
||||
|
16
src/bip39.h
16
src/bip39.h
@@ -11,7 +11,23 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
** Convert a key from bytes to words.
|
||||
** @param bytes A raw binary representation of a key.
|
||||
** @param bytes_size The size of bytes.
|
||||
** @param[out] out_words A human-readable English word representation of a key.
|
||||
** @param words_size The size of the out_words buffer.
|
||||
** @return True if the key was successfully converted.
|
||||
*/
|
||||
bool tf_bip39_bytes_to_words(const uint8_t* bytes, size_t bytes_size, char* out_words, size_t words_size);
|
||||
|
||||
/**
|
||||
** Convert a key from words to bytes.
|
||||
** @param words A space-separated list of English words forming a key.
|
||||
** @param[out] out_bytes A buffer to receive the raw binary form of the key.
|
||||
** @param bytes_size The size of the out_bytes buffer.
|
||||
** @return True if the key was successfully converted.
|
||||
*/
|
||||
bool tf_bip39_words_to_bytes(const char* words, uint8_t* out_bytes, size_t bytes_size);
|
||||
|
||||
/** @} */
|
||||
|
@@ -10,6 +10,8 @@ enum
|
||||
{
|
||||
k_bip39_words_count = 2048
|
||||
};
|
||||
|
||||
/** An array of words used for BIP39-encoding a key. */
|
||||
extern const char* k_bip39_words[k_bip39_words_count];
|
||||
|
||||
/** @} */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user