PHP familiarity shortcuts
This commit is contained in:
parent
af642c8167
commit
dc05f9faa5
42
README.md
42
README.md
@ -247,11 +247,11 @@ The runtime reads its server settings from:
|
||||
The shipped example contains the important filesystem and FastCGI settings:
|
||||
|
||||
```ini
|
||||
BIN_DIRECTORY=/tmp/uce/work
|
||||
TMP_UPLOAD_PATH=/tmp/uce/uploads
|
||||
SESSION_PATH=/tmp/uce/sessions
|
||||
BIN_DIRECTORY=/var/cache/uce/work
|
||||
TMP_UPLOAD_PATH=/var/lib/uce/uploads
|
||||
SESSION_PATH=/var/lib/uce/sessions
|
||||
|
||||
FCGI_SOCKET_PATH=/run/uce.sock
|
||||
FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
|
||||
FCGI_PORT=9993
|
||||
|
||||
PRECOMPILE_FILES_IN=
|
||||
@ -265,7 +265,7 @@ SESSION_TIME=2592000
|
||||
|
||||
For nginx deployments, the most important setting is:
|
||||
|
||||
- `FCGI_SOCKET_PATH=/run/uce.sock`
|
||||
- `FCGI_SOCKET_PATH=/run/uce/fastcgi.sock`
|
||||
|
||||
That is the Unix socket nginx should use for normal `.uce` requests.
|
||||
|
||||
@ -317,11 +317,35 @@ scripts/systemd/manage-uce-service.sh logs 200
|
||||
|
||||
The unit currently:
|
||||
|
||||
- creates `/tmp/uce/work`, `/tmp/uce/uploads`, and `/tmp/uce/sessions`
|
||||
- removes any stale `/run/uce.sock`
|
||||
- uses systemd-managed runtime/state/cache roots under:
|
||||
- `/run/uce`
|
||||
- `/var/lib/uce`
|
||||
- `/var/cache/uce`
|
||||
- prepares:
|
||||
- `/var/cache/uce/work`
|
||||
- `/var/lib/uce/uploads`
|
||||
- `/var/lib/uce/sessions`
|
||||
- removes any stale `/run/uce/fastcgi.sock`
|
||||
- rebuilds the runtime before start
|
||||
- runs the binary from the repo root so `COMPILER_SYS_PATH` resolves correctly
|
||||
|
||||
### Debian package build
|
||||
|
||||
To build a Debian package from the repository root:
|
||||
|
||||
```bash
|
||||
bash scripts/make_deb.sh 0.1.2
|
||||
```
|
||||
|
||||
That script:
|
||||
|
||||
- rebuilds the runtime first
|
||||
- stages the current runtime tree under `/usr/lib/uce`
|
||||
- installs `/etc/uce/settings.cfg` as a package conffile
|
||||
- installs a packaged `uce.service` under `/lib/systemd/system/`
|
||||
- writes Debian maintainer scripts for systemd reload/enable handling
|
||||
- follows a more PHP-like/FHS deployment shape with immutable runtime files under `/usr/lib`, config under `/etc`, cache/state under `/var`, and the FastCGI socket under `/run/uce/`
|
||||
|
||||
### 5. Configure nginx for `.uce` and `.ws.uce`
|
||||
|
||||
You need two nginx paths for `.ws.uce` endpoints:
|
||||
@ -359,7 +383,7 @@ server {
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param DOCUMENT_URI $uri;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_pass unix:/run/uce.sock;
|
||||
fastcgi_pass unix:/run/uce/fastcgi.sock;
|
||||
}
|
||||
|
||||
location ~ \.ws\.uce$ {
|
||||
@ -374,7 +398,7 @@ server {
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param DOCUMENT_URI $uri;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_pass unix:/run/uce.sock;
|
||||
fastcgi_pass unix:/run/uce/fastcgi.sock;
|
||||
}
|
||||
|
||||
location @uce_websocket {
|
||||
|
||||
BIN
dist/uce_0.1.2-1_amd64.deb
vendored
Normal file
BIN
dist/uce_0.1.2-1_amd64.deb
vendored
Normal file
Binary file not shown.
BIN
dist/uce_0.1.3-1_amd64.deb
vendored
Normal file
BIN
dist/uce_0.1.3-1_amd64.deb
vendored
Normal file
Binary file not shown.
BIN
dist/uce_0.1.4-1_amd64.deb
vendored
Normal file
BIN
dist/uce_0.1.4-1_amd64.deb
vendored
Normal file
Binary file not shown.
@ -1,10 +1,10 @@
|
||||
# UCE WORKING DIRECTORIES
|
||||
BIN_DIRECTORY=/tmp/uce/work
|
||||
TMP_UPLOAD_PATH=/tmp/uce/uploads
|
||||
SESSION_PATH=/tmp/uce/sessions
|
||||
BIN_DIRECTORY=/var/cache/uce/work
|
||||
TMP_UPLOAD_PATH=/var/lib/uce/uploads
|
||||
SESSION_PATH=/var/lib/uce/sessions
|
||||
|
||||
# LISTEN ON SOCKETS
|
||||
FCGI_SOCKET_PATH=/run/uce.sock
|
||||
FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
|
||||
FCGI_PORT=9993
|
||||
|
||||
# OPTIONAL PROACTIVE COMPILE ROOT
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
Package: uce
|
||||
Section: web
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends: clang (>= 1:13.0)
|
||||
Maintainer: Udo Schroeter <udo@openfu.com>
|
||||
Description: Write server-side web stuff in C++
|
||||
1
scripts/deb/conffiles
Normal file
1
scripts/deb/conffiles
Normal file
@ -0,0 +1 @@
|
||||
/etc/uce/settings.cfg
|
||||
11
scripts/deb/control.in
Normal file
11
scripts/deb/control.in
Normal file
@ -0,0 +1,11 @@
|
||||
Package: @PACKAGE_NAME@
|
||||
Version: @VERSION@
|
||||
Section: web
|
||||
Priority: optional
|
||||
Architecture: @ARCH@
|
||||
Depends: bash, clang, libmariadb-dev | default-libmysqlclient-dev, systemd
|
||||
Maintainer: Udo Schroeter <udo@openfu.com>
|
||||
Installed-Size: @INSTALLED_SIZE@
|
||||
Description: UCE FastCGI runtime and live-compiling C++ web environment
|
||||
UCE is an experimental C/C++ web runtime with FastCGI request handling,
|
||||
on-demand page compilation, and a published site/doc/test tree.
|
||||
11
scripts/deb/postinst
Normal file
11
scripts/deb/postinst
Normal file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
if [ "$1" = "configure" ]; then
|
||||
systemctl enable uce.service >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
8
scripts/deb/postrm
Normal file
8
scripts/deb/postrm
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
10
scripts/deb/prerm
Normal file
10
scripts/deb/prerm
Normal file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl disable --now uce.service >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
25
scripts/deb/uce.service
Normal file
25
scripts/deb/uce.service
Normal file
@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=UCE FastCGI Runtime
|
||||
After=network-online.target mariadb.service memcached.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/usr/lib/uce
|
||||
RuntimeDirectory=uce
|
||||
StateDirectory=uce
|
||||
CacheDirectory=uce
|
||||
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
||||
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
|
||||
ExecStartPre=/usr/bin/bash /usr/lib/uce/scripts/build_linux.sh
|
||||
ExecStart=/usr/lib/uce/bin/uce_fastcgi.linux.bin
|
||||
ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
TimeoutStopSec=15
|
||||
KillMode=mixed
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1,46 +1,137 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Usage: make_deb.sh VERSION"
|
||||
exit
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
DEB_ASSET_DIR="$SCRIPT_DIR/deb"
|
||||
PACKAGE_NAME="uce"
|
||||
REVISION="${UCE_DEB_REVISION:-1}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/make_deb.sh VERSION
|
||||
|
||||
Environment:
|
||||
UCE_DEB_REVISION Debian package revision suffix (default: 1)
|
||||
UCE_DEB_ARCH Override package architecture
|
||||
EOF
|
||||
}
|
||||
|
||||
require_command() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Required command not found: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_arch() {
|
||||
if [[ -n "${UCE_DEB_ARCH:-}" ]]; then
|
||||
printf '%s\n' "$UCE_DEB_ARCH"
|
||||
return
|
||||
fi
|
||||
if command -v dpkg-architecture >/dev/null 2>&1; then
|
||||
dpkg-architecture -qDEB_HOST_ARCH
|
||||
return
|
||||
fi
|
||||
dpkg --print-architecture
|
||||
}
|
||||
|
||||
validate_version() {
|
||||
local version="$1"
|
||||
if [[ ! "$version" =~ ^[0-9][A-Za-z0-9.+:~]*$ ]]; then
|
||||
echo "Invalid Debian version string: $version" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
copy_payload() {
|
||||
local destination="$1"
|
||||
local path
|
||||
for path in LICENSE README.md codesearch bin scripts site src; do
|
||||
cp -a "$REPO_ROOT/$path" "$destination/"
|
||||
done
|
||||
mkdir -p "$destination/etc"
|
||||
cp -a "$REPO_ROOT/etc/uce" "$destination/etc/"
|
||||
}
|
||||
|
||||
write_control_file() {
|
||||
local output_file="$1"
|
||||
local package_version="$2"
|
||||
local arch="$3"
|
||||
local installed_size="$4"
|
||||
|
||||
sed \
|
||||
-e "s/@PACKAGE_NAME@/$PACKAGE_NAME/g" \
|
||||
-e "s/@VERSION@/$package_version/g" \
|
||||
-e "s/@ARCH@/$arch/g" \
|
||||
-e "s/@INSTALLED_SIZE@/$installed_size/g" \
|
||||
"$DEB_ASSET_DIR/control.in" > "$output_file"
|
||||
}
|
||||
|
||||
write_md5sums() {
|
||||
local stage_dir="$1"
|
||||
(
|
||||
cd "$stage_dir"
|
||||
find usr etc lib -type f -print0 | sort -z | xargs -0 md5sum > DEBIAN/md5sums
|
||||
)
|
||||
}
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
REV=1
|
||||
PROJECT="uce"
|
||||
VERSION="$1"
|
||||
validate_version "$VERSION"
|
||||
|
||||
PKG_NAME="${PROJECT}_$VERSION.$REV"
|
||||
require_command bash
|
||||
require_command clang++
|
||||
require_command dpkg-deb
|
||||
require_command dpkg
|
||||
require_command install
|
||||
require_command find
|
||||
require_command xargs
|
||||
require_command md5sum
|
||||
require_command mysql_config
|
||||
require_command du
|
||||
require_command sed
|
||||
require_command awk
|
||||
require_command cp
|
||||
require_command sort
|
||||
|
||||
echo "Making package $PKG_NAME"
|
||||
ARCH="$(resolve_arch)"
|
||||
PACKAGE_VERSION="${VERSION}-${REVISION}"
|
||||
PACKAGE_BASENAME="${PACKAGE_NAME}_${PACKAGE_VERSION}_${ARCH}"
|
||||
STAGE_DIR="$REPO_ROOT/pkg/$PACKAGE_BASENAME"
|
||||
DEBIAN_DIR="$STAGE_DIR/DEBIAN"
|
||||
INSTALL_ROOT="$STAGE_DIR/usr/lib/uce"
|
||||
DIST_DIR="$REPO_ROOT/dist"
|
||||
OUTPUT_DEB="$DIST_DIR/$PACKAGE_BASENAME.deb"
|
||||
|
||||
echo "Making package $PACKAGE_BASENAME"
|
||||
echo "==================================="
|
||||
|
||||
scripts/build_linux.sh
|
||||
bash "$REPO_ROOT/scripts/build_linux.sh"
|
||||
|
||||
rm -r "pkg/$PKG_NAME"
|
||||
mkdir -p "pkg/$PKG_NAME/opt/uce/"
|
||||
mkdir -p "pkg/$PKG_NAME/DEBIAN/"
|
||||
mkdir -p "pkg/$PKG_NAME/etc/uce/"
|
||||
mkdir -p "dist"
|
||||
rm -rf -- "$STAGE_DIR"
|
||||
mkdir -p "$DEBIAN_DIR" "$INSTALL_ROOT" "$STAGE_DIR/etc/uce" "$STAGE_DIR/lib/systemd/system" "$DIST_DIR"
|
||||
|
||||
cp scripts/control "pkg/$PKG_NAME/DEBIAN/"
|
||||
echo "Version: $VERSION" >> "pkg/$PKG_NAME/DEBIAN/control"
|
||||
echo "" >> "pkg/$PKG_NAME/DEBIAN/control"
|
||||
copy_payload "$INSTALL_ROOT"
|
||||
|
||||
cp -r bin "pkg/$PKG_NAME/opt/uce/"
|
||||
cp -r doc "pkg/$PKG_NAME/opt/uce/"
|
||||
cp -r examples "pkg/$PKG_NAME/opt/uce/"
|
||||
cp -r scripts "pkg/$PKG_NAME/opt/uce/"
|
||||
cp -r test "pkg/$PKG_NAME/opt/uce/"
|
||||
cp -r etc "pkg/$PKG_NAME/"
|
||||
install -m 0644 "$REPO_ROOT/etc/uce/settings.cfg" "$STAGE_DIR/etc/uce/settings.cfg"
|
||||
install -m 0644 "$DEB_ASSET_DIR/uce.service" "$STAGE_DIR/lib/systemd/system/uce.service"
|
||||
install -m 0644 "$DEB_ASSET_DIR/conffiles" "$DEBIAN_DIR/conffiles"
|
||||
install -m 0755 "$DEB_ASSET_DIR/postinst" "$DEBIAN_DIR/postinst"
|
||||
install -m 0755 "$DEB_ASSET_DIR/prerm" "$DEBIAN_DIR/prerm"
|
||||
install -m 0755 "$DEB_ASSET_DIR/postrm" "$DEBIAN_DIR/postrm"
|
||||
|
||||
du -sh "pkg/$PKG_NAME/"*
|
||||
INSTALLED_SIZE="$(du -sk "$STAGE_DIR" | awk '{print $1}')"
|
||||
write_control_file "$DEBIAN_DIR/control" "$PACKAGE_VERSION" "$ARCH" "$INSTALLED_SIZE"
|
||||
write_md5sums "$STAGE_DIR"
|
||||
|
||||
cd pkg
|
||||
dpkg-deb --build "$PKG_NAME"
|
||||
mv "$PKG_NAME.deb" "../dist/"
|
||||
cd ..
|
||||
dpkg-deb --root-owner-group --build "$STAGE_DIR" "$OUTPUT_DEB"
|
||||
|
||||
ls -lh "dist/" | grep "$PKG_NAME"
|
||||
dpkg-deb -I "$OUTPUT_DEB" | sed -n '1,80p'
|
||||
echo
|
||||
dpkg-deb -c "$OUTPUT_DEB" | sed -n '1,60p'
|
||||
|
||||
@ -10,6 +10,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
UNIT_NAME="uce.service"
|
||||
UNIT_SOURCE="$SCRIPT_DIR/uce.service"
|
||||
if [[ ( "$REPO_ROOT" == "/usr/lib/uce" || "$REPO_ROOT" == "/opt/uce" ) && -f "$REPO_ROOT/scripts/deb/uce.service" ]]; then
|
||||
UNIT_SOURCE="$REPO_ROOT/scripts/deb/uce.service"
|
||||
fi
|
||||
UNIT_DEST="/etc/systemd/system/$UNIT_NAME"
|
||||
CONFIG_SOURCE="$REPO_ROOT/etc/uce/settings.cfg"
|
||||
CONFIG_DEST="/etc/uce/settings.cfg"
|
||||
|
||||
@ -6,11 +6,14 @@ Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/Code/uce.openfu.com/uce
|
||||
ExecStartPre=/usr/bin/mkdir -p /tmp/uce/work /tmp/uce/uploads /tmp/uce/sessions
|
||||
ExecStartPre=/usr/bin/rm -f /run/uce.sock
|
||||
RuntimeDirectory=uce
|
||||
StateDirectory=uce
|
||||
CacheDirectory=uce
|
||||
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
||||
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
|
||||
ExecStartPre=/usr/bin/bash /Code/uce.openfu.com/uce/scripts/build_linux.sh
|
||||
ExecStart=/Code/uce.openfu.com/uce/bin/uce_fastcgi.linux.bin
|
||||
ExecStopPost=/usr/bin/rm -f /run/uce.sock
|
||||
ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
TimeoutStopSec=15
|
||||
|
||||
@ -188,6 +188,10 @@ RENDER(Request& context)
|
||||
nibble(s, "-");
|
||||
?><li><?= (s) ?></li><?
|
||||
}
|
||||
else if(layout_class == "sig")
|
||||
{
|
||||
?><pre><? print(s); ?></pre><?
|
||||
}
|
||||
else if(layout_class == "params")
|
||||
{
|
||||
?><div><b><?= trim(nibble(s, ":")) ?></b> : <?= trim(s) ?></div><?
|
||||
|
||||
24
site/doc/pages/array_merge.txt
Normal file
24
site/doc/pages/array_merge.txt
Normal file
@ -0,0 +1,24 @@
|
||||
:sig
|
||||
StringMap array_merge(StringMap a, StringMap b)
|
||||
DTree array_merge(DTree a, DTree b)
|
||||
|
||||
:params
|
||||
a : left-hand source map or tree
|
||||
b : right-hand source map or tree
|
||||
return value : merged result
|
||||
|
||||
:desc
|
||||
Merges two maps or trees using PHP-like merge behavior.
|
||||
|
||||
For `StringMap`, keys from `b` overwrite keys from `a`.
|
||||
|
||||
For `DTree`, string keys from `b` overwrite keys from `a`. Numeric keys are appended and reindexed when either side behaves like a list.
|
||||
|
||||
This helper is intended as the closest UCE equivalent to PHP `array_merge()` for common request, config, and JSON-shaped data.
|
||||
|
||||
:see
|
||||
>types
|
||||
|
||||
:related
|
||||
**PHP:** `array_merge()`
|
||||
**JavaScript / Node.js:** Object spread, `Object.assign()`, or array concatenation depending on whether the data is map-like or list-like
|
||||
19
site/doc/pages/contains.txt
Normal file
19
site/doc/pages/contains.txt
Normal file
@ -0,0 +1,19 @@
|
||||
:sig
|
||||
bool contains(String haystack, String needle)
|
||||
|
||||
:params
|
||||
haystack : string to search in
|
||||
needle : substring to look for
|
||||
return value : `true` when `needle` appears in `haystack`, otherwise `false`
|
||||
|
||||
:desc
|
||||
Returns whether `haystack` contains `needle`.
|
||||
|
||||
An empty `needle` always returns `true`.
|
||||
|
||||
:see
|
||||
>string
|
||||
|
||||
:related
|
||||
**PHP:** `str_contains()`
|
||||
**JavaScript / Node.js:** `String.prototype.includes()`
|
||||
19
site/doc/pages/redirect.txt
Normal file
19
site/doc/pages/redirect.txt
Normal file
@ -0,0 +1,19 @@
|
||||
:sig
|
||||
void redirect(String url, s32 code = 302)
|
||||
|
||||
:params
|
||||
url : target URL for the redirect
|
||||
code : optional HTTP redirect status, defaults to `302`
|
||||
|
||||
:desc
|
||||
Sets the `Location` response header and updates the current HTTP status code.
|
||||
|
||||
Use this helper instead of manually assigning `context.header["Location"]` and `context.set_status(...)` when you want to redirect the current response.
|
||||
|
||||
:see
|
||||
set_status
|
||||
0_context
|
||||
|
||||
:related
|
||||
**PHP:** `header("Location: ...")` together with `http_response_code()` or implicit redirect semantics
|
||||
**JavaScript / Node.js:** `res.redirect(...)`, setting `Location`, or returning a redirect `Response`
|
||||
17
site/doc/pages/str_ends_with.txt
Normal file
17
site/doc/pages/str_ends_with.txt
Normal file
@ -0,0 +1,17 @@
|
||||
:sig
|
||||
bool str_ends_with(String haystack, String needle)
|
||||
|
||||
:params
|
||||
haystack : string to inspect
|
||||
needle : suffix to compare against the end of `haystack`
|
||||
return value : `true` when `haystack` ends with `needle`, otherwise `false`
|
||||
|
||||
:desc
|
||||
Checks whether `haystack` ends with the given suffix.
|
||||
|
||||
:see
|
||||
>string
|
||||
|
||||
:related
|
||||
**PHP:** `str_ends_with()`
|
||||
**JavaScript / Node.js:** `String.prototype.endsWith()`
|
||||
17
site/doc/pages/str_starts_with.txt
Normal file
17
site/doc/pages/str_starts_with.txt
Normal file
@ -0,0 +1,17 @@
|
||||
:sig
|
||||
bool str_starts_with(String haystack, String needle)
|
||||
|
||||
:params
|
||||
haystack : string to inspect
|
||||
needle : prefix to compare against the start of `haystack`
|
||||
return value : `true` when `haystack` begins with `needle`, otherwise `false`
|
||||
|
||||
:desc
|
||||
Checks whether `haystack` starts with the given prefix.
|
||||
|
||||
:see
|
||||
>string
|
||||
|
||||
:related
|
||||
**PHP:** `str_starts_with()`
|
||||
**JavaScript / Node.js:** `String.prototype.startsWith()`
|
||||
22
site/doc/pages/strpos.txt
Normal file
22
site/doc/pages/strpos.txt
Normal file
@ -0,0 +1,22 @@
|
||||
:sig
|
||||
s64 strpos(String haystack, String needle, s64 offset = 0)
|
||||
|
||||
:params
|
||||
haystack : string to search in
|
||||
needle : substring to search for
|
||||
offset : optional start offset; negative values count from the end of the string
|
||||
return value : zero-based position of the first match, or `-1` if `needle` is not found
|
||||
|
||||
:desc
|
||||
Finds the first occurrence of `needle` inside `haystack`.
|
||||
|
||||
This is the closest UCE equivalent to PHP `strpos()`, but it returns `-1` instead of `false` when no match is found.
|
||||
|
||||
If `needle` is an empty string, `strpos()` returns the normalized start offset.
|
||||
|
||||
:see
|
||||
>string
|
||||
|
||||
:related
|
||||
**PHP:** `strpos()`
|
||||
**JavaScript / Node.js:** `String.prototype.indexOf()`
|
||||
25
site/doc/pages/substr.txt
Normal file
25
site/doc/pages/substr.txt
Normal file
@ -0,0 +1,25 @@
|
||||
:sig
|
||||
String substr(String s, s64 start_pos)
|
||||
String substr(String s, s64 start_pos, s64 length)
|
||||
|
||||
:params
|
||||
s : source string
|
||||
start_pos : zero-based start position; negative values count from the end of the string
|
||||
length : optional substring length; negative values trim bytes from the end of the result range
|
||||
return value : extracted substring, or an empty string if the requested range is outside the source string
|
||||
|
||||
:desc
|
||||
Extracts part of a string using PHP-style start and length semantics.
|
||||
|
||||
Use the two-argument form to return everything from `start_pos` to the end of the string.
|
||||
|
||||
If `start_pos` is negative, counting starts from the end of the string.
|
||||
|
||||
If `length` is negative, the returned range stops that many bytes before the end of the string.
|
||||
|
||||
:see
|
||||
>string
|
||||
|
||||
:related
|
||||
**PHP:** `substr()`
|
||||
**JavaScript / Node.js:** `String.prototype.slice()` or `substring()`
|
||||
@ -1,10 +1,4 @@
|
||||
|
||||
// Returns whether haystack contains needle (case-sensitive, both should be pre-lowercased)
|
||||
bool str_contains(String haystack, String needle)
|
||||
{
|
||||
return replace(haystack, needle, "\x01") != haystack;
|
||||
}
|
||||
|
||||
void render_hit(String ft, String name, String badge, String snippet)
|
||||
{
|
||||
<><div class="search-hit">
|
||||
@ -51,8 +45,8 @@ RENDER(Request& context)
|
||||
}
|
||||
|
||||
String content = file_get_contents("pages/" + ft + ".txt");
|
||||
bool name_match = str_contains(to_lower(name), q);
|
||||
bool content_match = str_contains(to_lower(content), q);
|
||||
bool name_match = contains(to_lower(name), q);
|
||||
bool content_match = contains(to_lower(content), q);
|
||||
|
||||
if(!name_match && !content_match)
|
||||
continue;
|
||||
@ -64,7 +58,7 @@ RENDER(Request& context)
|
||||
for(auto line : split(content, "\n"))
|
||||
{
|
||||
String t = trim(line);
|
||||
if(t.length() > 4 && t.substr(0, 1) != ":" && str_contains(to_lower(t), q))
|
||||
if(t.length() > 4 && t.substr(0, 1) != ":" && contains(to_lower(t), q))
|
||||
{
|
||||
snippet = t;
|
||||
if(snippet.length() > 120)
|
||||
|
||||
@ -111,13 +111,14 @@ void render_see_section(String name)
|
||||
RENDER(Request& context)
|
||||
{
|
||||
|
||||
already_shown_items = new StringMap();
|
||||
StringMap shown_items;
|
||||
already_shown_items = &shown_items;
|
||||
|
||||
String page = first(context.get["p"], "index");
|
||||
|
||||
<><html>
|
||||
<head>
|
||||
<!--<link rel="stylesheet" href='style.css?v=<?= time() ?>'></link>-->
|
||||
<link rel="stylesheet" href='style.css?v=<?= time() ?>'></link>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
|
||||
@ -1,24 +1,29 @@
|
||||
/* UCE Documentation — Theme
|
||||
Color palette: #139 deep-blue base · white text · yellow accents */
|
||||
Color palette: deep blue gradient · warm gold accents · frosted glass surfaces */
|
||||
|
||||
:root {
|
||||
--bg: #113399;
|
||||
--bg-surface: rgba(255, 255, 255, 0.065);
|
||||
--bg-surface-hover: rgba(255, 255, 255, 0.11);
|
||||
--bg-inset: rgba(0, 0, 0, 0.22);
|
||||
--bg-code: rgba(0, 0, 0, 0.28);
|
||||
--text: #e8ecf4;
|
||||
--text-dim: rgba(255, 255, 255, 0.55);
|
||||
--text-muted: rgba(255, 255, 255, 0.35);
|
||||
--accent: #ffd644;
|
||||
--accent-dim: rgba(255, 214, 68, 0.1);
|
||||
--accent-hover: #ffe680;
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--border-strong: rgba(255, 255, 255, 0.18);
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||||
--radius: 8px;
|
||||
--radius-lg: 14px;
|
||||
--bg: #0b1d4e;
|
||||
--bg-grad: linear-gradient(168deg, #0f2462 0%, #0b1d4e 40%, #091840 100%);
|
||||
--bg-surface: rgba(255, 255, 255, 0.05);
|
||||
--bg-surface-hover: rgba(255, 255, 255, 0.09);
|
||||
--bg-inset: rgba(0, 0, 0, 0.3);
|
||||
--bg-code: rgba(0, 0, 0, 0.32);
|
||||
--text: #dfe5f2;
|
||||
--text-dim: rgba(200, 210, 235, 0.6);
|
||||
--text-muted: rgba(200, 210, 235, 0.35);
|
||||
--accent: #f0c430;
|
||||
--accent-dim: rgba(240, 196, 48, 0.08);
|
||||
--accent-hover: #ffd95c;
|
||||
--accent-glow: rgba(240, 196, 48, 0.15);
|
||||
--border: rgba(255, 255, 255, 0.06);
|
||||
--border-strong: rgba(255, 255, 255, 0.14);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
--shadow-glow: 0 0 20px rgba(240, 196, 48, 0.06);
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||||
--radius: 10px;
|
||||
--radius-lg: 16px;
|
||||
--ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@ -52,8 +57,10 @@ body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
background: var(--bg);
|
||||
background: var(--bg-grad);
|
||||
background-attachment: fixed;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Typography ── */
|
||||
@ -63,8 +70,9 @@ h1 {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
padding: 28px 0 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 32px 0 20px;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-image: linear-gradient(90deg, var(--accent) 0%, rgba(240, 196, 48, 0.15) 100%) 1;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
@ -131,15 +139,18 @@ a:hover {
|
||||
|
||||
.category {
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 18px;
|
||||
margin-bottom: 10px;
|
||||
transition: border-color 150ms var(--ease);
|
||||
transition: border-color 200ms var(--ease), box-shadow 200ms var(--ease);
|
||||
}
|
||||
|
||||
.category:hover {
|
||||
border-color: var(--border-strong);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.category h3 {
|
||||
@ -190,23 +201,25 @@ a:hover {
|
||||
display: block;
|
||||
padding: 7px 12px;
|
||||
border-radius: 6px;
|
||||
transition: background 150ms var(--ease);
|
||||
transition: background 150ms var(--ease), transform 150ms var(--ease);
|
||||
}
|
||||
|
||||
.func-item a:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
text-decoration: none;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 500;
|
||||
padding: 1px 7px;
|
||||
border-radius: 4px;
|
||||
margin-left: 6px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text-dim);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.dim {
|
||||
@ -221,10 +234,13 @@ a:hover {
|
||||
|
||||
.doc-section {
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.doc-section h3 {
|
||||
@ -240,12 +256,22 @@ a:hover {
|
||||
line-height: 1.5;
|
||||
background: var(--bg-code);
|
||||
border-color: var(--border-strong);
|
||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sig div {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.sig pre {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.params div {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
@ -261,6 +287,23 @@ a:hover {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.related {
|
||||
background: var(--accent-dim);
|
||||
border: 1px solid rgba(240, 196, 48, 0.12);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 20px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.related div {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.related strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.see {
|
||||
background: transparent;
|
||||
border: none;
|
||||
@ -333,9 +376,10 @@ pre {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.55;
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
code {
|
||||
@ -365,9 +409,12 @@ li {
|
||||
|
||||
details {
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
summary {
|
||||
@ -421,7 +468,7 @@ details pre {
|
||||
#doc-search:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(255, 214, 68, 0.12);
|
||||
box-shadow: 0 0 0 3px var(--accent-glow), var(--shadow-glow);
|
||||
}
|
||||
|
||||
.search-count {
|
||||
@ -445,13 +492,14 @@ details pre {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
padding: 5px 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
transition: background 120ms var(--ease);
|
||||
transition: background 120ms var(--ease), transform 120ms var(--ease);
|
||||
}
|
||||
|
||||
.search-hit:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.search-hit a {
|
||||
|
||||
@ -3,12 +3,6 @@
|
||||
RENDER(Request& context)
|
||||
{
|
||||
DTree t;
|
||||
DTree assoc_left;
|
||||
DTree assoc_right;
|
||||
DTree assoc_merged;
|
||||
DTree list_left;
|
||||
DTree list_right;
|
||||
DTree list_merged;
|
||||
|
||||
<>
|
||||
<link rel="stylesheet" href='style.css'></link>
|
||||
@ -17,25 +11,25 @@ RENDER(Request& context)
|
||||
DTree
|
||||
</h1>
|
||||
|
||||
Value Types:
|
||||
<h2>Value Types</h2>
|
||||
|
||||
<pre><?
|
||||
|
||||
t.set("String test");
|
||||
print(String("String test: ") + t.to_string()+"\n");
|
||||
print("String: ", t.to_string(), "\n");
|
||||
|
||||
t.set(true);
|
||||
print(String("bool test: ") + t.to_string()+"\n");
|
||||
print("bool: ", t.to_string(), "\n");
|
||||
|
||||
t.set(1234.5678);
|
||||
print(String("float test: ") + t.to_string()+"\n");
|
||||
print("float: ", t.to_string(), "\n");
|
||||
|
||||
t.set(&t);
|
||||
print(String("pointer test: ") + t.to_string()+"\n");
|
||||
print("pointer: ", t.to_string(), "\n");
|
||||
|
||||
?></pre>
|
||||
|
||||
Tree:
|
||||
<h2>Nested Tree</h2>
|
||||
|
||||
<pre><?
|
||||
|
||||
@ -53,42 +47,48 @@ RENDER(Request& context)
|
||||
|
||||
?></pre>
|
||||
|
||||
Array Merge
|
||||
<h2>Array Merge — Associative</h2>
|
||||
|
||||
<pre><?
|
||||
|
||||
DTree assoc_left;
|
||||
assoc_left["name"] = "left";
|
||||
assoc_left["shared"] = "left-value";
|
||||
|
||||
DTree assoc_right;
|
||||
assoc_right["shared"] = "right-value";
|
||||
assoc_right["extra"] = "new-value";
|
||||
assoc_merged = array_merge(assoc_left, assoc_right);
|
||||
print("Associative merge:\n");
|
||||
print(var_dump(assoc_merged));
|
||||
|
||||
list_left.set_array();
|
||||
DTree a;
|
||||
a = "A";
|
||||
list_left.push(a);
|
||||
DTree b;
|
||||
b = "B";
|
||||
list_left.push(b);
|
||||
|
||||
list_right.set_array();
|
||||
DTree c;
|
||||
c = "C";
|
||||
list_right.push(c);
|
||||
DTree d;
|
||||
d = "D";
|
||||
list_right.push(d);
|
||||
|
||||
list_merged = array_merge(list_left, list_right);
|
||||
print("\nList merge:\n");
|
||||
print(var_dump(list_merged));
|
||||
print("left:\n", var_dump(assoc_left), "\n");
|
||||
print("right:\n", var_dump(assoc_right), "\n");
|
||||
print("merged:\n", var_dump(array_merge(assoc_left, assoc_right)));
|
||||
|
||||
?></pre>
|
||||
|
||||
Params
|
||||
<pre><?= var_dump(context.params) ?></pre>
|
||||
<h2>Array Merge — List</h2>
|
||||
|
||||
<pre><?
|
||||
|
||||
DTree list_left;
|
||||
list_left.set_array();
|
||||
DTree a; a = "A"; list_left.push(a);
|
||||
DTree b; b = "B"; list_left.push(b);
|
||||
|
||||
DTree list_right;
|
||||
list_right.set_array();
|
||||
DTree c; c = "C"; list_right.push(c);
|
||||
DTree d; d = "D"; list_right.push(d);
|
||||
|
||||
print("left:\n", var_dump(list_left), "\n");
|
||||
print("right:\n", var_dump(list_right), "\n");
|
||||
print("merged:\n", var_dump(array_merge(list_left, list_right)));
|
||||
|
||||
?></pre>
|
||||
|
||||
<details>
|
||||
<summary>Request Parameters</summary>
|
||||
<pre><?= var_dump(context.params) ?></pre>
|
||||
</details>
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
@ -3,32 +3,100 @@
|
||||
|
||||
|
||||
|
||||
void test_row(String label, String result)
|
||||
{
|
||||
<><tr><td class="test-label"><?= label ?></td><td class="test-result"><code><?= result ?></code></td></tr></>
|
||||
}
|
||||
|
||||
void test_row(String label, String result, String expect)
|
||||
{
|
||||
bool pass = (result == expect);
|
||||
<><tr>
|
||||
<td class="test-label"><?= label ?></td>
|
||||
<td class="test-result"><code><?= result ?></code></td>
|
||||
<td class="test-status <?= pass ? "pass" : "fail" ?>"><?= pass ? "✓" : "✗ expected: " + expect ?></td>
|
||||
</tr></>
|
||||
}
|
||||
|
||||
void test_num(String label, s64 result, s64 expect)
|
||||
{
|
||||
bool pass = (result == expect);
|
||||
<><tr>
|
||||
<td class="test-label"><?= label ?></td>
|
||||
<td class="test-result"><code><? print(result); ?></code></td>
|
||||
<td class="test-status <?= pass ? "pass" : "fail" ?>"><? if(!pass) { print("✗ expected: "); print(expect); } else { print("✓"); } ?></td>
|
||||
</tr></>
|
||||
}
|
||||
|
||||
RENDER(Request& context)
|
||||
{
|
||||
String sample = " Hello UCE World ";
|
||||
|
||||
<>
|
||||
<link rel="stylesheet" href='style.css'></link>
|
||||
<style>
|
||||
table.tests { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
|
||||
table.tests td { padding: 6px 12px; border-bottom: 1px solid rgba(255,255,255,0.06); font-family: var(--font-mono); font-size: 0.88rem; }
|
||||
.test-label { color: var(--text-dim); white-space: nowrap; width: 1%; }
|
||||
.test-result code { background: var(--bg-code); padding: 2px 8px; border-radius: 4px; }
|
||||
.test-status.pass { color: #6f6; width: 1%; }
|
||||
.test-status.fail { color: #f66; }
|
||||
</style>
|
||||
<h1>
|
||||
<a href="index.uce">UCE Test</a>:
|
||||
Strings
|
||||
</h1>
|
||||
|
||||
<pre style="white-space: pre-wrap"><?
|
||||
<p>sample = <code>"<?= sample ?>"</code></p>
|
||||
|
||||
print("sample: [", sample, "]\n");
|
||||
print("trim(sample): [", trim(sample), "]\n");
|
||||
print("substr(sample, 2, 5): [", substr(sample, 2, 5), "]\n");
|
||||
print("substr(sample, -7): [", substr(sample, -7), "]\n");
|
||||
print("strpos(sample, \"UCE\"): ", strpos(sample, "UCE"), "\n");
|
||||
print("contains(sample, \"World\"): ", contains(sample, "World") ? "true" : "false", "\n");
|
||||
print("str_starts_with(trim(sample), \"Hello\"): ", str_starts_with(trim(sample), "Hello") ? "true" : "false", "\n");
|
||||
print("str_ends_with(trim(sample), \"World\"): ", str_ends_with(trim(sample), "World") ? "true" : "false", "\n");
|
||||
<h2>trim</h2>
|
||||
<table class="tests"><?
|
||||
test_row("trim(sample)", "[" + trim(sample) + "]", "[Hello UCE World]");
|
||||
?></table>
|
||||
|
||||
?></pre>
|
||||
<h2>substr</h2>
|
||||
<table class="tests"><?
|
||||
test_row("substr(sample, 2, 5)", substr(sample, 2, 5), "Hello");
|
||||
test_row("substr(sample, -7)", substr(sample, -7), "World ");
|
||||
test_row("substr(sample, 2, -2)", substr(sample, 2, -2), "Hello UCE World");
|
||||
?></table>
|
||||
|
||||
Params
|
||||
<pre><?= var_dump(context.params) ?></pre>
|
||||
<h2>strpos</h2>
|
||||
<table class="tests"><?
|
||||
test_num("strpos(sample, \"UCE\")", strpos(sample, "UCE"), 8);
|
||||
test_num("strpos(sample, \"missing\")", strpos(sample, "missing"), -1);
|
||||
?></table>
|
||||
|
||||
<h2>contains</h2>
|
||||
<table class="tests"><?
|
||||
test_row("contains(sample, \"World\")", contains(sample, "World") ? "true" : "false", "true");
|
||||
test_row("contains(sample, \"xyz\")", contains(sample, "xyz") ? "true" : "false", "false");
|
||||
?></table>
|
||||
|
||||
<h2>str_starts_with / str_ends_with</h2>
|
||||
<table class="tests"><?
|
||||
test_row("str_starts_with(trim(sample), \"Hello\")", str_starts_with(trim(sample), "Hello") ? "true" : "false", "true");
|
||||
test_row("str_starts_with(trim(sample), \"World\")", str_starts_with(trim(sample), "World") ? "true" : "false", "false");
|
||||
test_row("str_ends_with(trim(sample), \"World\")", str_ends_with(trim(sample), "World") ? "true" : "false", "true");
|
||||
test_row("str_ends_with(trim(sample), \"Hello\")", str_ends_with(trim(sample), "Hello") ? "true" : "false", "false");
|
||||
?></table>
|
||||
|
||||
<h2>replace / split / join</h2>
|
||||
<table class="tests"><?
|
||||
test_row("replace(sample, \"UCE\", \"C++\")", replace(sample, "UCE", "C++"), " Hello C++ World ");
|
||||
test_row("join(split(trim(sample), \" \"), \"-\")", join(split(trim(sample), " "), "-"), "Hello-UCE-World");
|
||||
?></table>
|
||||
|
||||
<h2>to_upper / to_lower</h2>
|
||||
<table class="tests"><?
|
||||
test_row("to_upper(trim(sample))", to_upper(trim(sample)), "HELLO UCE WORLD");
|
||||
test_row("to_lower(trim(sample))", to_lower(trim(sample)), "hello uce world");
|
||||
?></table>
|
||||
|
||||
<details>
|
||||
<summary>Request Parameters</summary>
|
||||
<pre><?= var_dump(context.params) ?></pre>
|
||||
</details>
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
@ -1,24 +1,28 @@
|
||||
/* UCE Test Suite — Theme
|
||||
Color palette: #139 deep-blue base · white text · yellow accents */
|
||||
Color palette: deep blue gradient · warm gold accents · frosted glass surfaces */
|
||||
|
||||
:root {
|
||||
--bg: #113399;
|
||||
--bg-surface: rgba(255, 255, 255, 0.065);
|
||||
--bg-surface-hover: rgba(255, 255, 255, 0.11);
|
||||
--bg-inset: rgba(0, 0, 0, 0.22);
|
||||
--bg-code: rgba(0, 0, 0, 0.28);
|
||||
--text: #e8ecf4;
|
||||
--text-dim: rgba(255, 255, 255, 0.55);
|
||||
--text-muted: rgba(255, 255, 255, 0.35);
|
||||
--accent: #ffd644;
|
||||
--accent-dim: rgba(255, 214, 68, 0.1);
|
||||
--accent-hover: #ffe680;
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--border-strong: rgba(255, 255, 255, 0.18);
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||||
--radius: 8px;
|
||||
--radius-lg: 14px;
|
||||
--bg: #0b1d4e;
|
||||
--bg-grad: linear-gradient(168deg, #0f2462 0%, #0b1d4e 40%, #091840 100%);
|
||||
--bg-surface: rgba(255, 255, 255, 0.05);
|
||||
--bg-surface-hover: rgba(255, 255, 255, 0.09);
|
||||
--bg-inset: rgba(0, 0, 0, 0.3);
|
||||
--bg-code: rgba(0, 0, 0, 0.32);
|
||||
--text: #dfe5f2;
|
||||
--text-dim: rgba(200, 210, 235, 0.6);
|
||||
--text-muted: rgba(200, 210, 235, 0.35);
|
||||
--accent: #f0c430;
|
||||
--accent-dim: rgba(240, 196, 48, 0.08);
|
||||
--accent-hover: #ffd95c;
|
||||
--accent-glow: rgba(240, 196, 48, 0.15);
|
||||
--border: rgba(255, 255, 255, 0.06);
|
||||
--border-strong: rgba(255, 255, 255, 0.14);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||||
--radius: 10px;
|
||||
--radius-lg: 16px;
|
||||
--ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@ -52,8 +56,10 @@ body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
background: var(--bg);
|
||||
background: var(--bg-grad);
|
||||
background-attachment: fixed;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Typography ── */
|
||||
@ -63,8 +69,9 @@ h1 {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
padding: 28px 0 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 32px 0 20px;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-image: linear-gradient(90deg, var(--accent) 0%, rgba(240, 196, 48, 0.15) 100%) 1;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
@ -117,10 +124,13 @@ body > section,
|
||||
body > div:not(.test-grid):not(.system-info):not(.grid-heading),
|
||||
body > p {
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* ── Test Card Grid ── */
|
||||
@ -138,18 +148,22 @@ body > p {
|
||||
gap: 4px;
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
transition: all 150ms var(--ease);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 200ms var(--ease);
|
||||
}
|
||||
|
||||
.test-card:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
border-color: var(--border-strong);
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.test-card strong {
|
||||
@ -263,8 +277,9 @@ pre {
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.55;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
code {
|
||||
@ -302,8 +317,11 @@ li a {
|
||||
margin-top: 32px;
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-code);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.system-info h3 {
|
||||
@ -322,9 +340,12 @@ li a {
|
||||
|
||||
details {
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
summary {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user