Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd44db2213 | ||
|
|
e12936444d | ||
|
|
4ac3d8d53d | ||
|
|
e291ca8438 | ||
|
|
e697057b86 | ||
|
|
7d65ff813f | ||
|
|
51dfbe8cd8 | ||
|
|
e7243f6f26 | ||
|
|
4c2e04e78b | ||
|
|
a2266662ad | ||
|
|
b7e0bd0d92 | ||
|
|
f4fb3c550b | ||
|
|
93505e0e48 | ||
|
|
6ba93b2a92 | ||
|
|
db63a29365 | ||
|
|
bc30f2900b | ||
|
|
18d04e2c4a |
No files matched your search
@@ -6,3 +6,12 @@ sessions/*.db-shm
|
|||||||
# macOS-Artefakte
|
# macOS-Artefakte
|
||||||
.DS_Store
|
.DS_Store
|
||||||
sessions/*.txt
|
sessions/*.txt
|
||||||
|
|
||||||
|
# Update-Dienst (Auftrag, Status, Protokoll — je Rechner)
|
||||||
|
sessions/update/
|
||||||
|
|
||||||
|
# Für den Bau auf dem Pi nicht nötig: Pakete installiert Docker selbst
|
||||||
|
# (npm ci im Dockerfile), Sicherungen und Uploads entstehen je Rechner.
|
||||||
|
node_modules/
|
||||||
|
sessions/backups/
|
||||||
|
sessions/uploads/
|
||||||
+9
-5
@@ -7,10 +7,10 @@ WORKDIR /app
|
|||||||
|
|
||||||
# package.json und package-lock.json zuerst kopieren, damit npm-Installation gecacht wird
|
# package.json und package-lock.json zuerst kopieren, damit npm-Installation gecacht wird
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --production
|
RUN npm config set fetch-retries 6 && npm config set fetch-retry-maxtimeout 180000 && npm config set fetch-timeout 600000 && npm ci --omit=dev
|
||||||
|
|
||||||
# Nur die Laufzeit-Dateien kopieren
|
# Nur die Laufzeit-Dateien kopieren
|
||||||
COPY server.js ./
|
COPY server.js pdf-arbeit.js ./
|
||||||
COPY public ./public
|
COPY public ./public
|
||||||
|
|
||||||
# Sessions-Ordner fuer die SQLite-Datenbank sicherstellen
|
# Sessions-Ordner fuer die SQLite-Datenbank sicherstellen
|
||||||
@@ -20,7 +20,11 @@ USER node
|
|||||||
|
|
||||||
EXPOSE 3003
|
EXPOSE 3003
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
# wget (busybox) statt eines eigenen Node-Prozesses je Prüfung: auf dem
|
||||||
CMD node -e "require('http').get('http://localhost:3003/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"
|
# Pi Zero kostete jeder Healthcheck sonst ~40 MB und eine Sekunde CPU.
|
||||||
|
HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD wget -qO- http://127.0.0.1:3003/health >/dev/null || exit 1
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
# --max-old-space-size: Node räumt früher auf, statt bis an die
|
||||||
|
# Container-Grenze zu wachsen (Pi Zero 2, 512 MB). Siehe docker-compose.yml.
|
||||||
|
CMD ["node", "--max-old-space-size=160", "server.js"]
|
||||||
Executable
+236
@@ -0,0 +1,236 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# pi-einrichten.sh — macht Ubuntu Server auf dem Raspberry Pi Zero 2 (512 MB)
|
||||||
|
# stabil für die Projektübersicht. Mehrfach ausführbar (prüft jeden Schritt).
|
||||||
|
#
|
||||||
|
# sudo bash Docker/pi-einrichten.sh # Standard
|
||||||
|
# sudo bash Docker/pi-einrichten.sh --snapd-aus # zusätzlich snapd abschalten
|
||||||
|
#
|
||||||
|
# Was es tut (Einzelheiten in CLAUDE.md, "Betrieb auf dem Pi"):
|
||||||
|
# 1. Hardware-Watchdog: hängt das System, startet der Pi selbst neu
|
||||||
|
# 2. zram: komprimierter Auslagerungsspeicher im RAM (statt SD-Karte)
|
||||||
|
# 3. Speichergrenzen für Container erlauben (cgroup memory)
|
||||||
|
# 4. gpu_mem=16 — kein Bildschirm, mehr RAM für Linux
|
||||||
|
# 5. Unnötige Dienste aus (multipathd, ModemManager, udisks2 …)
|
||||||
|
# 6. earlyoom: greift ein, BEVOR das System im Speichermangel festfriert
|
||||||
|
# 7. Systemprotokoll auf 50 MB begrenzen
|
||||||
|
# 8. Nächtlicher Neustart des Containers (3:30 Uhr)
|
||||||
|
# 9. Prüfung der Stromversorgung (Unterspannung)
|
||||||
|
# 10. WLAN: Energiesparen aus, Wächter verbindet neu und startet den Pi
|
||||||
|
# neu, wenn das Netz 10 Minuten lang weg bleibt
|
||||||
|
set -u
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then echo "Bitte mit sudo ausführen."; exit 1; fi
|
||||||
|
|
||||||
|
SNAPD_AUS=0
|
||||||
|
[ "${1:-}" = "--snapd-aus" ] && SNAPD_AUS=1
|
||||||
|
|
||||||
|
NEUSTART_NOETIG=0
|
||||||
|
FIRMWARE=/boot/firmware
|
||||||
|
[ -d "$FIRMWARE" ] || FIRMWARE=/boot
|
||||||
|
ok() { echo " ✓ $*"; }
|
||||||
|
info() { echo " • $*"; }
|
||||||
|
warn() { echo " ! $*"; }
|
||||||
|
|
||||||
|
echo "== Projektübersicht: Pi einrichten =="
|
||||||
|
echo "System: $(. /etc/os-release; echo "$PRETTY_NAME"), $(uname -m), $(free -m | awk '/Mem:/{print $2}') MB RAM"
|
||||||
|
|
||||||
|
# 1. Hardware-Watchdog ------------------------------------------------------
|
||||||
|
echo "[1] Hardware-Watchdog"
|
||||||
|
mkdir -p /etc/systemd/system.conf.d
|
||||||
|
cat > /etc/systemd/system.conf.d/10-watchdog.conf <<'EOF'
|
||||||
|
# Hängt das System, löst der Watchdog des Pi nach 14 s einen Neustart aus
|
||||||
|
# (der bcm2835-Watchdog kann höchstens ~15 s).
|
||||||
|
[Manager]
|
||||||
|
RuntimeWatchdogSec=14s
|
||||||
|
RebootWatchdogSec=2min
|
||||||
|
EOF
|
||||||
|
if [ -e /dev/watchdog ]; then ok "Watchdog aktiv nach Neustart (/dev/watchdog vorhanden)"
|
||||||
|
else warn "/dev/watchdog fehlt — Kernelmodul bcm2835_wdt wird beim Start geladen"; echo bcm2835_wdt > /etc/modules-load.d/watchdog.conf; NEUSTART_NOETIG=1; fi
|
||||||
|
systemctl daemon-reexec
|
||||||
|
|
||||||
|
# 2. zram -------------------------------------------------------------------
|
||||||
|
echo "[2] zram (komprimierter Auslagerungsspeicher)"
|
||||||
|
apt-get update -qq
|
||||||
|
if apt-cache show systemd-zram-generator >/dev/null 2>&1; then
|
||||||
|
apt-get install -y -qq systemd-zram-generator >/dev/null
|
||||||
|
cat > /etc/systemd/zram-generator.conf <<'EOF'
|
||||||
|
[zram0]
|
||||||
|
zram-size = ram / 2
|
||||||
|
compression-algorithm = zstd
|
||||||
|
swap-priority = 100
|
||||||
|
EOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl restart systemd-zram-setup@zram0.service 2>/dev/null || true
|
||||||
|
ok "systemd-zram-generator eingerichtet (halber RAM, zstd)"
|
||||||
|
else
|
||||||
|
apt-get install -y -qq zram-tools >/dev/null
|
||||||
|
printf 'ALGO=zstd\nPERCENT=50\nPRIORITY=100\n' > /etc/default/zramswap
|
||||||
|
systemctl restart zramswap 2>/dev/null || true
|
||||||
|
ok "zram-tools eingerichtet (halber RAM, zstd)"
|
||||||
|
fi
|
||||||
|
cat > /etc/sysctl.d/90-zram.conf <<'EOF'
|
||||||
|
# zram ist schnell: früh auslagern, Seiten einzeln holen
|
||||||
|
vm.swappiness=100
|
||||||
|
vm.page-cluster=0
|
||||||
|
vm.vfs_cache_pressure=200
|
||||||
|
EOF
|
||||||
|
sysctl -q --system
|
||||||
|
swapon --show | sed 's/^/ /'
|
||||||
|
|
||||||
|
# 3. Speichergrenzen für Container -------------------------------------------
|
||||||
|
echo "[3] Speichergrenzen für Container (cgroup memory)"
|
||||||
|
if grep -qw memory /sys/fs/cgroup/cgroup.controllers 2>/dev/null; then
|
||||||
|
ok "bereits verfügbar"
|
||||||
|
else
|
||||||
|
CMDLINE="$FIRMWARE/cmdline.txt"
|
||||||
|
if [ -f "$CMDLINE" ] && ! grep -q "cgroup_enable=memory" "$CMDLINE"; then
|
||||||
|
cp "$CMDLINE" "$CMDLINE.vor-projektuebersicht"
|
||||||
|
sed -i '1 s/$/ cgroup_enable=memory cgroup_memory=1/' "$CMDLINE"
|
||||||
|
ok "in $CMDLINE ergänzt (Sicherung: cmdline.txt.vor-projektuebersicht)"
|
||||||
|
NEUSTART_NOETIG=1
|
||||||
|
else
|
||||||
|
warn "nicht verfügbar — $CMDLINE prüfen"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. GPU-Speicher -----------------------------------------------------------
|
||||||
|
echo "[4] GPU-Speicher"
|
||||||
|
CONFIG="$FIRMWARE/config.txt"
|
||||||
|
if [ -f "$CONFIG" ]; then
|
||||||
|
if grep -q "^gpu_mem=" "$CONFIG"; then sed -i 's/^gpu_mem=.*/gpu_mem=16/' "$CONFIG"
|
||||||
|
else printf '\n# Projektübersicht: kein Bildschirm, mehr RAM für Linux\ngpu_mem=16\n' >> "$CONFIG"; fi
|
||||||
|
ok "gpu_mem=16"
|
||||||
|
NEUSTART_NOETIG=1
|
||||||
|
else warn "$CONFIG nicht gefunden"; fi
|
||||||
|
|
||||||
|
# 5. Unnötige Dienste -------------------------------------------------------
|
||||||
|
echo "[5] Unnötige Dienste abschalten"
|
||||||
|
for d in multipathd multipathd.socket ModemManager udisks2 packagekit bluetooth avahi-daemon avahi-daemon.socket; do
|
||||||
|
if systemctl list-unit-files "$d.service" "$d" 2>/dev/null | grep -q "$d"; then
|
||||||
|
systemctl disable --now "$d" >/dev/null 2>&1 && ok "$d aus"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$SNAPD_AUS" = 1 ]; then
|
||||||
|
if command -v snap >/dev/null && snap list docker >/dev/null 2>&1; then
|
||||||
|
warn "Docker ist als Snap installiert — snapd bleibt an"
|
||||||
|
else
|
||||||
|
systemctl disable --now snapd.service snapd.socket snapd.seeded.service >/dev/null 2>&1 && ok "snapd aus"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "snapd bleibt an (spart ~40 MB mit --snapd-aus)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. earlyoom ---------------------------------------------------------------
|
||||||
|
echo "[6] earlyoom"
|
||||||
|
apt-get install -y -qq earlyoom >/dev/null
|
||||||
|
cat > /etc/default/earlyoom <<'EOF'
|
||||||
|
# Bei unter 5 % freiem RAM UND unter 10 % freiem Swap den größten Prozess
|
||||||
|
# beenden — nie SSH, Docker oder systemd. Der App-Container startet dann neu.
|
||||||
|
# Ohne innere Anführungszeichen: systemd reicht sie sonst wörtlich weiter.
|
||||||
|
EARLYOOM_ARGS="-m 5 -s 10 -r 3600 --avoid ^(sshd|dockerd|containerd|systemd|systemd-journal)$ --prefer ^node$"
|
||||||
|
EOF
|
||||||
|
systemctl enable --now earlyoom >/dev/null 2>&1
|
||||||
|
systemctl restart earlyoom
|
||||||
|
ok "aktiv"
|
||||||
|
|
||||||
|
# 7. Systemprotokoll --------------------------------------------------------
|
||||||
|
echo "[7] Systemprotokoll begrenzen"
|
||||||
|
mkdir -p /etc/systemd/journald.conf.d
|
||||||
|
printf '[Journal]\nSystemMaxUse=50M\n' > /etc/systemd/journald.conf.d/10-groesse.conf
|
||||||
|
systemctl restart systemd-journald
|
||||||
|
ok "höchstens 50 MB"
|
||||||
|
|
||||||
|
# 8. Nächtlicher Neustart ---------------------------------------------------
|
||||||
|
echo "[8] Nächtlicher Neustart des Containers"
|
||||||
|
cat > /etc/cron.d/projektuebersicht <<'EOF'
|
||||||
|
# Projektübersicht: frischer Speicher jede Nacht (gegen schleichende Lecks)
|
||||||
|
30 3 * * * root docker restart projektuebersicht >/dev/null 2>&1
|
||||||
|
EOF
|
||||||
|
systemctl enable docker >/dev/null 2>&1
|
||||||
|
ok "täglich 3:30 Uhr; Docker startet beim Booten"
|
||||||
|
|
||||||
|
# 9. Stromversorgung --------------------------------------------------------
|
||||||
|
echo "[9] Stromversorgung"
|
||||||
|
T=""
|
||||||
|
[ -r /sys/devices/platform/soc/soc:firmware/get_throttled ] && T=$(cat /sys/devices/platform/soc/soc:firmware/get_throttled)
|
||||||
|
command -v vcgencmd >/dev/null && T=$(vcgencmd get_throttled | cut -d= -f2)
|
||||||
|
if [ -z "$T" ]; then info "nicht auslesbar (Paket libraspberrypi-bin installieren)"
|
||||||
|
elif [ "$((T))" -eq 0 ]; then ok "keine Unterspannung seit dem Start ($T)"
|
||||||
|
else warn "Unterspannung/Drosselung erkannt ($T) — besseres Netzteil (5 V / 2,5 A) verwenden!"; fi
|
||||||
|
|
||||||
|
# 10. WLAN ------------------------------------------------------------------
|
||||||
|
# Befund 29.10.: Der Pi "stürzte" nicht ab, sondern verlor das WLAN
|
||||||
|
# (wlan0: Lost carrier) und kam danach nie wieder in das Netz
|
||||||
|
# (ASSOC-REJECT in Schleife) — erreichbar war er erst nach Stecker ziehen.
|
||||||
|
echo "[10] WLAN stabil halten"
|
||||||
|
apt-get install -y -qq iw >/dev/null 2>&1
|
||||||
|
cat > /etc/systemd/system/wlan-energiesparen-aus.service <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=WLAN-Energiesparen aus (Pi Zero 2 verliert sonst die Verbindung)
|
||||||
|
After=sys-subsystem-net-devices-wlan0.device
|
||||||
|
Wants=sys-subsystem-net-devices-wlan0.device
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/sbin/iw dev wlan0 set power_save off
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
cat > /usr/local/sbin/wlan-waechter <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
# Prüft jede Minute, ob das Gateway erreichbar ist. Ohne Netz: alle 2
|
||||||
|
# Minuten WLAN neu verbinden, nach 10 Minuten den Pi neu starten.
|
||||||
|
ZAEHLER=/run/wlan-waechter.zaehler
|
||||||
|
GW=$(ip route | awk '/^default/{print $3; exit}')
|
||||||
|
if [ -n "$GW" ] && ping -c 2 -W 3 "$GW" >/dev/null 2>&1; then
|
||||||
|
rm -f "$ZAEHLER"; exit 0
|
||||||
|
fi
|
||||||
|
N=$(( $(cat "$ZAEHLER" 2>/dev/null || echo 0) + 1 ))
|
||||||
|
echo "$N" > "$ZAEHLER"
|
||||||
|
logger -t wlan-waechter "kein Netz seit $N Minute(n)"
|
||||||
|
if [ "$N" -ge 10 ]; then
|
||||||
|
logger -t wlan-waechter "Neustart des Pi"; systemctl reboot
|
||||||
|
elif [ $((N % 2)) -eq 0 ]; then
|
||||||
|
logger -t wlan-waechter "WLAN neu verbinden"
|
||||||
|
wpa_cli -i wlan0 reassociate >/dev/null 2>&1
|
||||||
|
if [ "$N" -ge 4 ]; then
|
||||||
|
ip link set wlan0 down; sleep 2; ip link set wlan0 up
|
||||||
|
networkctl reconfigure wlan0 >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod 755 /usr/local/sbin/wlan-waechter
|
||||||
|
cat > /etc/systemd/system/wlan-waechter.service <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=WLAN-Wächter der Projektübersicht
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/sbin/wlan-waechter
|
||||||
|
EOF
|
||||||
|
cat > /etc/systemd/system/wlan-waechter.timer <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=WLAN-Wächter jede Minute
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=3min
|
||||||
|
OnUnitActiveSec=1min
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now wlan-energiesparen-aus.service >/dev/null 2>&1
|
||||||
|
systemctl enable --now wlan-waechter.timer >/dev/null 2>&1
|
||||||
|
if iw dev wlan0 get power_save 2>/dev/null | grep -q off; then ok "Energiesparen aus"; else warn "Energiesparen ließ sich nicht abschalten"; fi
|
||||||
|
ok "Wächter aktiv (neu verbinden ab 2 min, Neustart nach 10 min ohne Netz)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ "$NEUSTART_NOETIG" = 1 ]; then
|
||||||
|
echo "Fertig. Bitte den Pi jetzt neu starten: sudo reboot"
|
||||||
|
else
|
||||||
|
echo "Fertig."
|
||||||
|
fi
|
||||||
|
echo "Danach prüfen: bash Docker/pi-pruefen.sh"
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# pi-pruefen.sh — zeigt, ob der Pi und die Projektübersicht stabil laufen.
|
||||||
|
# sudo bash Docker/pi-pruefen.sh (ohne sudo fehlen Docker und Strom)
|
||||||
|
echo "== Speicher =="
|
||||||
|
free -m
|
||||||
|
echo
|
||||||
|
echo "== Auslagerung (zram) =="
|
||||||
|
swapon --show 2>/dev/null || echo "keine"
|
||||||
|
echo
|
||||||
|
echo "== Watchdog =="
|
||||||
|
systemctl show -p RuntimeWatchdogUSec 2>/dev/null
|
||||||
|
[ -e /dev/watchdog ] && echo "/dev/watchdog vorhanden" || echo "/dev/watchdog FEHLT"
|
||||||
|
echo
|
||||||
|
echo "== Speichergrenzen für Container =="
|
||||||
|
grep -qw memory /sys/fs/cgroup/cgroup.controllers 2>/dev/null && echo "verfügbar" || echo "NICHT verfügbar (cmdline.txt, siehe pi-einrichten.sh)"
|
||||||
|
echo
|
||||||
|
echo "== Container =="
|
||||||
|
docker ps -a --filter name=projektuebersicht --format 'Status: {{.Status}}'
|
||||||
|
docker inspect projektuebersicht --format 'Neustarts: {{.RestartCount}} Grenze: {{.HostConfig.Memory}} Byte zuletzt gestartet: {{.State.StartedAt}} OOM: {{.State.OOMKilled}}' 2>/dev/null
|
||||||
|
docker stats --no-stream --format 'Verbrauch: {{.MemUsage}} ({{.MemPerc}}) CPU: {{.CPUPerc}}' projektuebersicht 2>/dev/null
|
||||||
|
echo
|
||||||
|
echo "== App =="
|
||||||
|
curl -s --max-time 5 http://localhost:3003/health || wget -qO- http://localhost:3003/health || echo "antwortet NICHT"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo "== Neustarts der App (letzte 10 Meldungen) =="
|
||||||
|
docker logs projektuebersicht 2>&1 | grep -E "Wächter|Unerwarteter Fehler|server running" | tail -10
|
||||||
|
echo
|
||||||
|
echo "== Speichermangel im System (earlyoom / Kernel) =="
|
||||||
|
journalctl -u earlyoom --since "-7 days" --no-pager 2>/dev/null | grep -i "sending\|killing" | tail -5
|
||||||
|
journalctl -k --since "-7 days" --no-pager 2>/dev/null | grep -i "out of memory\|oom-kill" | tail -5
|
||||||
|
echo
|
||||||
|
echo "== Stromversorgung =="
|
||||||
|
if command -v vcgencmd >/dev/null; then vcgencmd get_throttled
|
||||||
|
elif [ -r /sys/devices/platform/soc/soc:firmware/get_throttled ]; then echo "throttled=$(cat /sys/devices/platform/soc/soc:firmware/get_throttled) (0 = gut)"
|
||||||
|
else echo "nicht auslesbar"; fi
|
||||||
|
echo
|
||||||
|
echo "== WLAN =="
|
||||||
|
iw dev wlan0 get power_save 2>/dev/null || echo "nicht auslesbar"
|
||||||
|
systemctl is-active wlan-waechter.timer 2>/dev/null | sed 's/^/Wächter: /'
|
||||||
|
journalctl -t wlan-waechter --since "-7 days" --no-pager 2>/dev/null | tail -5
|
||||||
|
echo
|
||||||
|
echo "== Letzte Systemstarts =="
|
||||||
|
last -x reboot 2>/dev/null | head -5
|
||||||
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# update-ausfuehren.sh — führt einen Update-Auftrag aus der App aus
|
||||||
|
# (Admin → Update, POST /admin/update legt sessions/update/auftrag.json an).
|
||||||
|
# Gestartet vom systemd-Dienst projektuebersicht-update (eingerichtet von
|
||||||
|
# Docker/update-einrichten.sh), läuft als root auf dem Rechner, nicht im
|
||||||
|
# Container: Version vom Git-Server holen, auschecken, Container neu bauen
|
||||||
|
# und starten. Fortschritt in sessions/update/status.json, Protokoll in
|
||||||
|
# sessions/update/update.log, die laufende Version in installiert.json.
|
||||||
|
#
|
||||||
|
# Alles steht in main(): bash liest ein Skript sonst zeilenweise beim
|
||||||
|
# Ausführen — und dieses Skript wird mitten im Lauf durch den Checkout ersetzt.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
main(){
|
||||||
|
local REPO DIR AUFTRAG LOG VERSION EIGNER VORHER COMPOSE
|
||||||
|
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
DIR="$REPO/sessions/update"
|
||||||
|
AUFTRAG="$DIR/auftrag.json"
|
||||||
|
LOG="$DIR/update.log"
|
||||||
|
[ -f "$AUFTRAG" ] || return 0
|
||||||
|
|
||||||
|
VERSION=$(sed -n 's/.*"version":"\(v[0-9]\{1,4\}\.[0-9]\{1,4\}\.[0-9]\{1,4\}\)".*/\1/p' "$AUFTRAG")
|
||||||
|
rm -f "$AUFTRAG"
|
||||||
|
: > "$LOG"
|
||||||
|
EIGNER=$(stat -c %U "$REPO")
|
||||||
|
git_(){ runuser -u "$EIGNER" -- git -C "$REPO" "$@"; }
|
||||||
|
status(){ # $1 Schritt, $2 läuft (true/false), $3 Fehlertext (fest, ohne Anführungszeichen)
|
||||||
|
printf '{"version":"%s","schritt":"%s","laeuft":%s,"fehler":"%s","am":"%s"}\n' \
|
||||||
|
"$VERSION" "$1" "$2" "${3:-}" "$(date -Iseconds)" > "$DIR/status.tmp" && mv "$DIR/status.tmp" "$DIR/status.json"
|
||||||
|
}
|
||||||
|
fehler(){ status "$1" false "$2"; echo "FEHLER: $2" >> "$LOG"; }
|
||||||
|
|
||||||
|
if [ -z "$VERSION" ]; then fehler "Auftrag" "Der Auftrag enthält keine gültige Version."; return 1; fi
|
||||||
|
echo "Update auf $VERSION, $(date)" >> "$LOG"
|
||||||
|
|
||||||
|
status "Version holen" true
|
||||||
|
if ! git_ fetch --tags --force origin >> "$LOG" 2>&1; then fehler "Version holen" "Der Git-Server ist nicht erreichbar."; return 1; fi
|
||||||
|
if ! git_ rev-parse -q --verify "refs/tags/$VERSION^{commit}" >/dev/null; then fehler "Version holen" "Die Version gibt es auf dem Git-Server nicht."; return 1; fi
|
||||||
|
# Lokale Änderungen am Code (sessions/ ausgenommen) nie überschreiben.
|
||||||
|
if [ -n "$(git_ status --porcelain --untracked-files=no -- . ':!sessions')" ]; then
|
||||||
|
git_ status --short -- . ':!sessions' >> "$LOG" 2>&1
|
||||||
|
fehler "Prüfen" "Auf dem Rechner gibt es lokale Änderungen am Code. Bitte von Hand klären."; return 1
|
||||||
|
fi
|
||||||
|
VORHER=$(git_ rev-parse HEAD)
|
||||||
|
if ! git_ checkout -q --detach "$VERSION" >> "$LOG" 2>&1; then fehler "Auschecken" "Auschecken fehlgeschlagen."; return 1; fi
|
||||||
|
|
||||||
|
if docker compose version >/dev/null 2>&1; then COMPOSE="docker compose"; else COMPOSE="docker-compose"; fi
|
||||||
|
status "Container bauen" true
|
||||||
|
# Gebaut wird, während die alte Version weiterläuft; erst danach Neustart.
|
||||||
|
if ! $COMPOSE --project-directory "$REPO" -f "$REPO/docker-compose.yml" build >> "$LOG" 2>&1; then
|
||||||
|
git_ checkout -q --detach "$VORHER" >> "$LOG" 2>&1
|
||||||
|
fehler "Container bauen" "Bauen fehlgeschlagen, die bisherige Version läuft weiter."; return 1
|
||||||
|
fi
|
||||||
|
status "Neustart" true
|
||||||
|
if ! $COMPOSE --project-directory "$REPO" -f "$REPO/docker-compose.yml" up -d >> "$LOG" 2>&1; then
|
||||||
|
fehler "Neustart" "Der Container ließ sich nicht starten."; return 1
|
||||||
|
fi
|
||||||
|
local i gesund=0
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
if wget -qO- http://127.0.0.1:3003/health >/dev/null 2>&1 || curl -fs http://127.0.0.1:3003/health >/dev/null 2>&1; then gesund=1; break; fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
if [ "$gesund" != 1 ]; then
|
||||||
|
$COMPOSE --project-directory "$REPO" -f "$REPO/docker-compose.yml" logs --tail 40 >> "$LOG" 2>&1
|
||||||
|
fehler "Neustart" "Die neue Version antwortet nach 5 Minuten nicht. Protokoll prüfen."; return 1
|
||||||
|
fi
|
||||||
|
printf '{"version":"%s","commit":"%s","am":"%s"}\n' "$VERSION" "$(git_ rev-parse --short HEAD)" "$(date -Iseconds)" > "$DIR/installiert.json"
|
||||||
|
status "Fertig" false
|
||||||
|
echo "Fertig, $(date)" >> "$LOG"
|
||||||
|
}
|
||||||
|
main "$@"
|
||||||
|
exit $?
|
||||||
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# update-einrichten.sh — richtet auf dem Rechner (Pi) den Dienst ein, der
|
||||||
|
# Updates aus der App ausführt (Admin → Update). Einmal ausführen, im
|
||||||
|
# geklonten Repo, mit sudo. Mehrfach ausführbar.
|
||||||
|
#
|
||||||
|
# sudo bash Docker/update-einrichten.sh
|
||||||
|
#
|
||||||
|
# Danach: systemd beobachtet sessions/update/auftrag.json; legt die App dort
|
||||||
|
# einen Auftrag ab, startet Docker/update-ausfuehren.sh. Der Container selbst
|
||||||
|
# bekommt keinen Zugriff auf Docker.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then echo "Bitte mit sudo ausführen."; exit 1; fi
|
||||||
|
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
DIR="$REPO/sessions/update"
|
||||||
|
EIGNER=$(stat -c %U "$REPO")
|
||||||
|
|
||||||
|
if ! runuser -u "$EIGNER" -- git -C "$REPO" remote get-url origin >/dev/null 2>&1; then
|
||||||
|
echo "Kein Git-Repo mit 'origin' in $REPO — die App muss aus einem Klon des Git-Servers laufen."; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DIR"
|
||||||
|
# Der Container läuft als Benutzer node (uid 1000) und legt hier den Auftrag ab.
|
||||||
|
chown 1000:1000 "$DIR"
|
||||||
|
|
||||||
|
# Laufende Version vermerken: der nächstliegende Tag.
|
||||||
|
VERSION=$(runuser -u "$EIGNER" -- git -C "$REPO" describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
|
COMMIT=$(runuser -u "$EIGNER" -- git -C "$REPO" rev-parse --short HEAD)
|
||||||
|
printf '{"version":"%s","commit":"%s","am":"%s"}\n' "$VERSION" "$COMMIT" "$(date -Iseconds)" > "$DIR/installiert.json"
|
||||||
|
printf '{"eingerichtet":"%s"}\n' "$(date -Iseconds)" > "$DIR/dienst.json"
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/projektuebersicht-update.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Projektübersicht: Update aus der App ausführen
|
||||||
|
After=docker.service network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash $REPO/Docker/update-ausfuehren.sh
|
||||||
|
TimeoutStartSec=3600
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/projektuebersicht-update.path <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Projektübersicht: auf Update-Auftrag aus der App warten
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathExists=$DIR/auftrag.json
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now projektuebersicht-update.path
|
||||||
|
echo " ✓ Update-Dienst eingerichtet (laufende Version: ${VERSION:-unbekannt}, $COMMIT)"
|
||||||
|
echo " • Die App braucht UPDATE_REPO_API in docker-compose.yml; danach einmal: docker compose up -d --build"
|
||||||
@@ -1,157 +1,129 @@
|
|||||||
# Projektübersicht — README
|
# Projektübersicht
|
||||||
|
|
||||||
## Übersicht
|
Werkzeug für ein Ingenieurbüro der technischen Gebäudeausrüstung (TGA), um
|
||||||
Dieses Projekt ist eine lokale Weboberfläche zur Verwaltung von TGA-Projekten (Elektro, HLS, Architektur) über wöchentliche Sitzungen. Die Anwendung besteht aus:
|
Projekte über wöchentliche Sitzungen hinweg zu verfolgen: Leistungsphasen nach
|
||||||
|
HOAI, Termine, Personaleinsatz, die wöchentliche Teambesprechung und die
|
||||||
|
Dokumentenfreigabe. Single-Page-App ohne Build-Schritt: Vanilla JS/HTML/CSS
|
||||||
|
im Frontend, Node.js/Express im Backend, Daten in SQLite.
|
||||||
|
|
||||||
- einem Frontend mit `Test.html` als der einen Anwendungsseite — sie enthält alle Bereiche einschließlich des Projektbereichs eines einzelnen Projekts (Terminplan/GANTT, Aufgaben, Dateien, Einstellungen) — dazu `styles.css`, `plan-styles.css` und den JS-Modulen unter `public/js/`
|
## Technologie
|
||||||
- einem einfachen Node.js-/Express-Server in `server.js`
|
|
||||||
- einem Datenspeicher in einer SQLite-Datenbank (`node:sqlite`, benötigt Node ≥ 22.13) unter `sessions/projektuebersicht.db`
|
|
||||||
- einem Docker-Setup (`Docker/Dockerfile` + `docker-compose.yml` im Projektstamm)
|
|
||||||
|
|
||||||
## Datei- und Komponentenübersicht
|
- **Frontend**: klassische, unmodulare `<script>`-Dateien unter `public/js/`
|
||||||
|
— alle Funktionen liegen global auf `window`, kein Bundler, kein Framework.
|
||||||
|
**Eine** HTML-Datei, `public/App.html`, bildet die gesamte Oberfläche ab
|
||||||
|
(Seitenleiste + alle Bereiche inklusive des Projektbereichs eines einzelnen
|
||||||
|
Projekts).
|
||||||
|
- **Backend**: Node.js (≥ 22.13, wegen `node:sqlite`) + Express, alles in
|
||||||
|
einer einzigen `server.js`.
|
||||||
|
- **Datenbank**: SQLite über das eingebaute `node:sqlite`-Modul (WAL-Modus,
|
||||||
|
vollständig relational, keine Einzeldatei je Woche), Datei unter
|
||||||
|
`sessions/projektuebersicht.db`.
|
||||||
|
- Kein Linter, kein Formatter. Tests: `npm test` (Zusammenführen, Rechteprüfung). Sonst läuft die Verifikation manuell:
|
||||||
|
Server starten, Bereich im Browser ausprobieren, SQLite-Datei prüfen.
|
||||||
|
|
||||||
### Wichtige Laufzeit-Dateien
|
## Starten
|
||||||
- `server.js`
|
|
||||||
- Node.js/Express-Server
|
|
||||||
- dient als API für das Frontend
|
|
||||||
- stellt statische Dateien (`Test.html`, die Module unter `js/`, `styles.css`, `plan-styles.css`, etc.) bereit; statische Assets werden mit `Cache-Control: no-store` ausgeliefert
|
|
||||||
- implementiert Endpunkte:
|
|
||||||
- `GET /` → liefert `Test.html`
|
|
||||||
- `GET /load-all?department=…` → lädt alle Sitzungen plus globale Personenliste einer Abteilung
|
|
||||||
- `POST /save-week {department, week, baseUpdatedAt}` → speichert/aktualisiert eine Sitzung; mit `baseUpdatedAt` (die vom Client gehaltene Revision = `updatedAt` aus `load-all`) erkennt der Server veraltete Speicherungen und antwortet dann mit HTTP 409 `{ok:false, conflict:true, …}`; auch gelöschte Sitzungen werden mit nicht-leerer Basis nicht wiederbelebt (409). Erfolgreiche Saves antworten `{ok:true, updatedAt}` (neue Revision)
|
|
||||||
- `POST /save-people {department, people}` → ersetzt die globale Personenliste einer Abteilung
|
|
||||||
- `POST /delete-week {department, label}` → löscht eine Sitzung
|
|
||||||
- `GET /health` → Gesundheitscheck (`{ok:true}`)
|
|
||||||
- Deep Links je Abteilung + Tab, z. B. `GET /HLS/Auslastung` oder `GET /ELT/Projektübersicht` → liefert `Test.html`; gültige Tab-Slugs sind `Projektübersicht | Auslastung | Projekte | Projektbeteiligte`, alles andere ergibt 404
|
|
||||||
- `GET /Projektplan(/<ProjName>)?` → liefert ebenfalls `Test.html`; der Projektbereich ist seit dem Umbau kein eigenes Dokument mehr, sondern ein Bereich dieser Seite, den `applyUrlRoute()` (`public/js/ui.js`) aus der Adresse auswählt. Die Identifikation erfolgt über die Query-Parameter `weekLabel` und `projNr`, der Projektname ist nur ein lesbares Pfad-Segment. Alte Links auf `/project-plan.html` werden per 301 auf dieses Schema umgeleitet
|
|
||||||
- validiert Abteilungen gegen `ELT | HLS | ARCH` (unbekannte fallen stillschweigend auf `ELT` zurück)
|
|
||||||
- legt das `sessions`-Verzeichnis beim Start an, falls es nicht existiert
|
|
||||||
- JSON-Body-Limit von 2 MB
|
|
||||||
- beendet sich mit aussagekräftiger Fehlermeldung, wenn der Port belegt ist
|
|
||||||
|
|
||||||
- `package.json`
|
### Lokal (Node.js ≥ 22.13)
|
||||||
- definiert den Node.js-Startbefehl `npm start`
|
|
||||||
- enthält Produktionsabhängigkeiten `express` und `cors`
|
|
||||||
|
|
||||||
- `package-lock.json`
|
|
||||||
- wird für reproduzierbare Docker-Builds verwendet (`npm ci`)
|
|
||||||
|
|
||||||
- `public/js/` (Module)
|
|
||||||
- Die Client-Logik ist in kleine klassische Script-Dateien aufgeteilt (alle Funktionen global auf `window`, kein Bundler):
|
|
||||||
- gemeinsame Helfer: `globals.js`, `utils.js`, `ui.js`, `date-picker.js`, `server.js`, `store.js`, `weeks.js`, `gantt.js`, `showmode.js`, `tabs-auth.js`, `pdf.js`, `init.js`
|
|
||||||
- tab-spezifisch in eigenen Ordnern: `board/board.js` (Projektboard), `people/people.js` (Projektbeteiligte), `projects/projects.js` (Projekte), `load/load.js` (Auslastung)
|
|
||||||
- Planungs-Module unter `js/plan/` (`plan-state`, `plan-build-gantt`, `plan-gantt`, `plan-drag`, `plan-modals`, `plan-schedule`)
|
|
||||||
- rendert das Projektboard, die Projekt- und Personenverwaltung sowie die Auslastungsansicht
|
|
||||||
- lädt Daten über den Server und speichert Änderungen als JSON-Objekte in die SQLite-Datenbank zurück
|
|
||||||
- verwendet `currentDepartment` zur Trennung von Abteilungen (`ELT`, `HLS`, `ARCH`)
|
|
||||||
- speichert:
|
|
||||||
- alle Sitzungen und die globale Personenliste pro Abteilung in `sessions/projektuebersicht.db`
|
|
||||||
- sortiert Sitzungen nach Kalenderwoche, nicht alphabetisch
|
|
||||||
- sperrt historische Sitzungen automatisch (bis auf die chronologisch letzte)
|
|
||||||
- Personen sind global pro Abteilung verwaltet; historische Sitzungen behalten ihre lokalen Personenkopien
|
|
||||||
- speichert keine Daten in `localStorage` (dort liegt nur UI-Zustand wie Abteilung, Theme und Header-Collapse)
|
|
||||||
|
|
||||||
- `Test.html`
|
|
||||||
- die Haupt-Benutzeroberfläche der Anwendung (Tabs: Projektboard, Auslastung, Projekte, Projektbeteiligte)
|
|
||||||
- lädt `styles.css` und die JS-Module aus `js/` in fester Reihenfolge (siehe `AGENTS.md`)
|
|
||||||
- enthält die Abteilungsauswahl, Sitzungsnavigation, Tab-Navigation, das Passwort-Modal sowie PDF-Export
|
|
||||||
- lädt CryptoJS (Passwortprüfung) und pdfmake (PDF-Export) von `cdnjs.cloudflare.com` — diese Funktionen benötigen daher Internetzugang
|
|
||||||
|
|
||||||
- Projektbereich (Bereich `#plan` in `Test.html`, Module unter `js/plan/`)
|
|
||||||
- der eigene Bereich EINES Projekts mit Dashboard, Terminplan (GANTT/TODOs/Meilensteine), Aufgaben, Dateien und Einstellungen
|
|
||||||
- wird über `openProjectPlan()` (`js/gantt.js`) geöffnet — ein gewöhnlicher Bereichswechsel über `showTab('plan')`, genau wie vom Dashboard ins Projektboard; Menüband und Seitenleiste bleiben stehen
|
|
||||||
- war bis zum Umbau ein eigenes Dokument `project-plan.html` mit nachgebauter, abgespeckter Seitenleiste; die Datei ist entfallen, ihre Stile liegen jetzt in `plan-styles.css`
|
|
||||||
|
|
||||||
- `plan-styles.css`
|
|
||||||
- die Stile des Projektbereichs (früher ein `<style>`-Block in `project-plan.html`)
|
|
||||||
|
|
||||||
- Bereich „Einstellungen" (`js/settings/settings.js`)
|
|
||||||
- gehört dem angemeldeten Konto selbst: Stammdaten zur Ansicht und der Passwortwechsel (`POST /change-password`). Der Dialog erscheint auch erzwungen direkt nach der Anmeldung, solange das Konto `must_change_password` trägt
|
|
||||||
|
|
||||||
- Bereich „Admin" (`js/admin/admin.js`, nur für Administratoren)
|
|
||||||
- gegliedert in einzelne Abschnitte, zwischen denen die aufklappbare Gruppe „Admin" in der Seitenleiste umschaltet; sichtbar ist immer genau einer, der gewählte steht im URL-Fragment (z. B. `/Admin#auslastung`) und ist damit verlinkbar
|
|
||||||
- Abschnitte: „Benutzerverwaltung" (Konten, Rollen, Wochenstunden, Abwesenheiten — bis zum Umbau die eigene Seite `admin-users.html`, alte Links werden per 301 umgeleitet), „Honorar" (Stundensatz, Anteile je Leistungsphase), „Auslastung" (Wochenstunden einer vollen Stelle, Ampel-Schwellen, Balkenskala, Aufwand je Leistungsphase und Rolle), „Termine & Sitzungen" (Arbeitsstunden je Werktag, automatische Sperre), „Feiertage" (die gesetzlichen Feiertage eines Jahres werden berechnet — feste Termine plus alles, was am Osterdatum hängt — und auf Knopfdruck übernommen; kein Netzdienst, kein Internetzugang nötig), „Aufgaben-Vorlagen", „Betrieb & Sicherheit" (aufbewahrte Backups, Gültigkeit einer Anmeldung, Anmeldebremse) und „Datenbank & Sitzungen" (DB Backup, DB Import, Sitzung löschen)
|
|
||||||
- all diese Werte waren vorher Konstanten im Quelltext; gelesen werden sie über `GET /app-settings` (jedes Konto — das Frontend rechnet damit), geschrieben über `PUT /app-settings` (nur Administratoren, serverseitig über `requireRole('admin')` abgesichert)
|
|
||||||
- die vertraglichen Wochenstunden je Person stehen am Benutzerkonto (Benutzerverwaltung) und sind die Bezugsgröße der Auslastungs-Ampel: Eine halbe Stelle gilt schon bei halber Last als voll ausgelastet
|
|
||||||
|
|
||||||
- `styles.css`
|
|
||||||
- Layout und Styling der Oberfläche
|
|
||||||
|
|
||||||
### Docker-bezogene Dateien
|
|
||||||
- `Docker/Dockerfile`
|
|
||||||
- Basis-Image: `node:22-alpine` (nötig, da `node:sqlite` erst ab Node 22.13 ohne Flag stabil verfügbar ist)
|
|
||||||
- kopiert `package.json` und `package-lock.json`, installiert Produktionsabhängigkeiten mit `npm ci`
|
|
||||||
- kopiert `server.js` und `public/` nach `/app`
|
|
||||||
- legt sicherheitshalber `/app/sessions` an
|
|
||||||
- läuft als nicht-root-User `node`
|
|
||||||
- enthält einen `HEALTHCHECK` auf `/health`
|
|
||||||
- startet `node server.js`
|
|
||||||
|
|
||||||
- `docker-compose.yml` (im Projektstamm)
|
|
||||||
- definiert den Service `projektuebersicht`
|
|
||||||
- baut das Image mit Build-Kontext `.` und `Docker/Dockerfile`
|
|
||||||
- mappt Port `3003:3003`
|
|
||||||
- bindet das Host-Verzeichnis `./sessions` in den Container nach `/app/sessions`
|
|
||||||
- setzt `restart: unless-stopped`
|
|
||||||
- definiert einen `healthcheck`
|
|
||||||
|
|
||||||
- `.dockerignore`
|
|
||||||
- schließt unnötige Dateien aus dem Docker-Build aus: `node_modules/`, `npm-debug.log`, `.git`, `.gitignore`, `Docker/*` (außer `Docker/Dockerfile`)
|
|
||||||
|
|
||||||
### Sonstige Dateien
|
|
||||||
- `dein_bild.ico`
|
|
||||||
- optionales Icon, wird vom Server statisch ausgeliefert, wenn angefragt
|
|
||||||
|
|
||||||
- `AGENTS.md`
|
|
||||||
- Projektnotizen / Dokumentation, aber nicht Teil des Anwendungslaufs
|
|
||||||
|
|
||||||
## Persistenz und Datenfluss
|
|
||||||
|
|
||||||
### Speicherung
|
|
||||||
- Alle persistierten Daten werden in einer SQLite-Datenbank (`node:sqlite`, `DatabaseSync`, WAL-Modus, `PRAGMA foreign_keys = ON`) unter `sessions/projektuebersicht.db` gespeichert.
|
|
||||||
- Das Schema ist vollständig relational (19 Tabellen): `weeks`, `week_people` (+ `week_people_roles`), `attendance`, `projects`, `project_lph`, `project_assign`, `project_aufwand`, `project_aktuelle_lp`, `project_plan`, `plan_people`, `plan_todos` (+ `plan_todo_assignees`, `plan_todo_deps`), `plan_milestones` (+ `plan_milestone_deps`), `people` (+ `person_roles`, `person_absences`). Projekt-/Personen-IDs bleiben dadurch über Neustarts stabil.
|
|
||||||
- Der Datenbankpfad ist über die Umgebungsvariable `DB_PATH` übersteuerbar (für Tests/Backups), Standard ist `sessions/projektuebersicht.db`.
|
|
||||||
|
|
||||||
### Server-API
|
|
||||||
- Der Server wird in `server.js` erstellt und startet auf Port `3003` oder dem Wert von `process.env.PORT`.
|
|
||||||
- Das Frontend ruft den Server über `window.location.origin` an (Fallback `http://localhost:3003` bei `file://`-Öffnung).
|
|
||||||
|
|
||||||
### Wo werden die Daten gespeichert?
|
|
||||||
- Das Frontend schreibt über den Server in die SQLite-Datenbank.
|
|
||||||
- In `js/store.js` speichert die Anwendung nur **geänderte** Sitzungen (Dirty-Tracking über `markWeekDirty()`/`markPeopleDirty()`) via `/save-week` (Sitzungen) bzw. `/save-people` (globale Personen); ein In-Flight-Guard verhindert Doppel-Speicherungen. Jede Sitzung führt einen Revisions-Zeitstempel (`updated_at`, übertragen als `updatedAt`); speichert ein Client mit veralteter Revision (`baseUpdatedAt`), antwortet der Server mit HTTP 409 und der Client lädt nur diese eine Sitzung neu (andere ungespeicherte Änderungen bleiben erhalten).
|
|
||||||
- Der Server zerlegt die JSON-Objekte in die relationalen Tabellen (`serializeWeek()`/`loadWeeks()`) in einer Transaktion pro Sitzung. Beim Start migriert `migrateLegacySchema()` idempotent ein altes Single-JSON-Dokument-Schema in das relationale Schema; anschließend ergänzt `migrateWeeksUpdatedAt()` die `updated_at`-Spalte der `weeks`-Tabelle (idempotent). `PRAGMA busy_timeout = 5000` verhindert Fehler bei parallelen Schreibzugriffen.
|
|
||||||
|
|
||||||
## Betrieb
|
|
||||||
|
|
||||||
### Lokal mit Node.js (Node ≥ 22.13)
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
Danach ist die Anwendung erreichbar unter `http://localhost:3003`.
|
Erreichbar unter `http://localhost:3003`. Anderer Port:
|
||||||
|
|
||||||
Falls der Port belegt ist:
|
|
||||||
```bash
|
```bash
|
||||||
PORT=3004 npm start
|
PORT=3004 npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mit Docker
|
### Mit Docker
|
||||||
Im Projektstamm ausführen:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up --build
|
docker compose up --build
|
||||||
```
|
```
|
||||||
|
`docker-compose.yml` liegt im Projektstamm, baut aus `Docker/Dockerfile`
|
||||||
|
(`node:22-alpine`), mappt Port `3003` und bindet `./sessions` in den
|
||||||
|
Container nach `/app/sessions` — die Datenbank und alle Uploads bleiben damit
|
||||||
|
auf dem Host erhalten. `TZ=Europe/Berlin` ist gesetzt, weil mehrere
|
||||||
|
Datumsvergleiche im Server von der lokalen Zeitzone abhängen.
|
||||||
|
|
||||||
Danach ist die Anwendung erreichbar unter:
|
### Erste Anmeldung
|
||||||
|
Die gesamte Anwendung verlangt eine Anmeldung. Ist die `users`-Tabelle beim
|
||||||
|
ersten Start leer, legt der Server automatisch ein Admin-Konto
|
||||||
|
(`admin`) mit einem zufälligen Passwort an, gibt es einmalig auf der Konsole
|
||||||
|
aus und schreibt es zusätzlich nach `sessions/INITIAL_ADMIN_PASSWORD.txt`
|
||||||
|
(liegt außerhalb von `public/`, wird also nie ausgeliefert). Das Konto muss
|
||||||
|
sein Passwort beim ersten Login ändern.
|
||||||
|
|
||||||
```
|
## Bereiche der Anwendung
|
||||||
http://localhost:3003
|
|
||||||
```
|
|
||||||
|
|
||||||
## Wichtige Hinweise
|
Seitenleiste, in dieser Reihenfolge:
|
||||||
- Der Server benötigt Lese-/Schreibzugriff auf `sessions/` (für die SQLite-Datenbank und das Docker-Bind-Mount).
|
|
||||||
- Die Docker-Compose-Konfiguration liegt im Projektstamm und mountet `./sessions` in den Container. Achte darauf, dass der relative Pfad im Kontext korrekt ist, wenn du `docker compose` aus einem anderen Verzeichnis startest.
|
- **Meine Aufgaben** — Dashboard und Startansicht nach dem Login: offene
|
||||||
- `js/store.js` und `js/server.js` arbeiten mit Abteilungsparametern und speichern Daten abteilungsweise getrennt.
|
persönliche Aufgaben, Termine der eigenen Projekte, ausstehende
|
||||||
- Die Passwortfunktion (Tabs „Projekte“/„Projektbeteiligte“, Sitzungslöschung, Entsperren) ist nur ein einfacher Client-Mechanismus im Frontend (SHA256-Vergleich) und kein echter Sicherheitsmechanismus.
|
Datei-Freigaben, Abwesenheiten von Kollegen.
|
||||||
- Globale Personen werden pro Abteilung in der Datenbank verwaltet. Wird eine Person global gelöscht, bleibt sie in bereits existierenden Sitzungen sichtbar (Wochen-Personenkopien).
|
- **Projekte** — die eine Projektliste (Board), gruppiert, per Drag-and-Drop
|
||||||
- CryptoJS und pdfmake werden von `cdnjs.cloudflare.com` geladen — PDF-Export und Passwortprüfung funktionieren ohne Internet nicht.
|
sortierbar.
|
||||||
|
- **Wochenbericht** — die Tagesordnung der wöchentlichen Teambesprechung, je
|
||||||
|
Gewerk (ELT/HLS/ARCH) gefiltert: jedes Projekt dieses Gewerks wird
|
||||||
|
angesehen, mit Vergleich zur Vorwoche, Anwesenheitserfassung und PDF-Export.
|
||||||
|
- **Auslastung** — Personalauslastung in tatsächlichen Wochenstunden,
|
||||||
|
aufgeteilt nach Projekten (Lastband).
|
||||||
|
- **Projektbeteiligte** — schreibgeschützte Übersicht der aktiven
|
||||||
|
Benutzerkonten; Teammitglieder pflegt ausschließlich ein Administrator
|
||||||
|
(Admin → Benutzerverwaltung).
|
||||||
|
- **Planung** — ein von einem konkreten Projekt unabhängiger
|
||||||
|
Terminplan-Sandkasten, z. B. um einen Zeitplan vorab durchzurechnen.
|
||||||
|
- **Einstellungen** — das eigene Konto: Stammdaten, E-Mail, Passwort, eigene
|
||||||
|
KI-Zugangsdaten für die Protokoll-Erkennung (siehe unten).
|
||||||
|
- **Admin** *(nur Administratoren)* — Benutzerverwaltung, Honorar-Vorgaben,
|
||||||
|
Auslastungs-Schwellen, Sitzungsverwaltung, Feiertage, Aufgaben-Vorlagen,
|
||||||
|
Betrieb & Sicherheit, Datenbank-Backup/-Import.
|
||||||
|
|
||||||
|
### Projektbereich
|
||||||
|
|
||||||
|
Aus der Projektliste heraus öffnet sich zu einem Projekt ein eigener Bereich
|
||||||
|
mit sieben Abschnitten: Dashboard, Terminplan (GANTT mit Aufgaben und
|
||||||
|
Meilensteinen), Aufgaben, Leistung (erbrachter Leistungswert je Phase),
|
||||||
|
Dateien, Protokolle (Besprechungsprotokolle) und Einstellungen
|
||||||
|
(Projektstammdaten, Honorar, beauftragte Leistungsphasen).
|
||||||
|
|
||||||
|
## Datenhaltung
|
||||||
|
|
||||||
|
- Vollständig relationales SQLite-Schema statt eines JSON-Dokuments je Woche;
|
||||||
|
Projekt- und Personen-IDs bleiben über Neustarts stabil.
|
||||||
|
- Eine Sitzung ("KW XX / YYYY") entspricht einer Kalenderwoche. Der Wechsel
|
||||||
|
in eine neue Sitzung läuft automatisch im Hintergrund (stündliche Prüfung)
|
||||||
|
— es gibt keine "Neue Sitzung"-Schaltfläche mehr.
|
||||||
|
- Der Server legt bei jedem Start automatisch einen Zeitstempel-Snapshot
|
||||||
|
unter `sessions/backups/` an und behält nur die neuesten (Anzahl einstellbar
|
||||||
|
unter Admin → Betrieb & Sicherheit). Ein Admin kann zusätzlich jederzeit
|
||||||
|
ein Backup herunterladen oder eine Sicherung zurückspielen.
|
||||||
|
- Hochgeladene Dateien (Freigabe-Workflow, importierte Protokolle) liegen
|
||||||
|
unter `sessions/uploads/<uuid>.<endung>`, referenziert über die Datenbank.
|
||||||
|
|
||||||
|
## Authentifizierung & Rollen
|
||||||
|
|
||||||
|
Echte serverseitige Sitzungen (Cookie, `scrypt`-gehashte Passwörter) — kein
|
||||||
|
clientseitiger Passwortschutz. Zwei Rollen: `admin` und `member`; nur
|
||||||
|
wirklich destruktive bzw. administrative Aktionen (Benutzerverwaltung,
|
||||||
|
Sitzung löschen/entsperren, DB-Import/-Backup) sind serverseitig auf Admins
|
||||||
|
beschränkt. Zu viele Fehlversuche sperren ein Konto vorübergehend.
|
||||||
|
|
||||||
|
## Besonderheiten
|
||||||
|
|
||||||
|
- **Keine Abteilungstrennung mehr**: Alle Sitzungen, Projekte und Personen
|
||||||
|
liegen in einer gemeinsamen Liste. `ELT`/`HLS`/`ARCH` bezeichnen heute nur
|
||||||
|
noch die Fachrichtung eines Benutzerkontos bzw. die Gewerke eines Projekts.
|
||||||
|
- **Protokoll-Import** kann ein hochgeladenes PDF/Word-Dokument
|
||||||
|
KI-gestützt (eigener OpenAI- oder Claude-Schlüssel je Konto, auch ein
|
||||||
|
lokal laufendes Modell möglich) in Besprechungspunkte umwandeln und fällt
|
||||||
|
ohne Schlüssel oder bei einem Fehler automatisch auf eine
|
||||||
|
Text-Heuristik zurück.
|
||||||
|
- Der PDF-Export lädt `pdfmake` von `cdnjs.cloudflare.com` — benötigt
|
||||||
|
Internetzugang.
|
||||||
|
- Feiertage werden serverseitig berechnet (keine externe Abhängigkeit), die
|
||||||
|
KI-gestützte Protokoll-Erkennung braucht dagegen Internetzugang bzw. ein
|
||||||
|
erreichbares lokales Modell.
|
||||||
|
|
||||||
|
## Weiterführende Dokumentation
|
||||||
|
|
||||||
|
`CLAUDE.md` im Projektstamm enthält die ausführliche technische
|
||||||
|
Dokumentation: Server-API, vollständiges Datenmodell, Architekturentscheidungen
|
||||||
|
und ihre Begründung. Ausgangspunkt für tiefere Änderungen am Code.
|
||||||
+29
-5
@@ -14,10 +14,34 @@ services:
|
|||||||
# Sommerzeit gälte damit zwischen 00:00 und 02:00 Ortszeit noch der
|
# Sommerzeit gälte damit zwischen 00:00 und 02:00 Ortszeit noch der
|
||||||
# Vortag, und Termine von heute erschienen als überfällig.
|
# Vortag, und Termine von heute erschienen als überfällig.
|
||||||
TZ: Europe/Berlin
|
TZ: Europe/Berlin
|
||||||
restart: unless-stopped
|
# Speicher-Wächter: darüber startet der Server sich selbst sauber neu,
|
||||||
|
# bevor die Grenze von 320 MB (mem_limit) erreicht ist.
|
||||||
|
SPEICHER_MAX_MB: "220"
|
||||||
|
# PDF-Kindprozess (Protokoll-Import, Plan Checker): bricht darüber ab.
|
||||||
|
PDF_KIND_MAX_MB: "220"
|
||||||
|
PDF_KIND_HEAP_MB: "160"
|
||||||
|
# Hänger-Wächter: reagiert der Server so lange nicht, Neustart.
|
||||||
|
HAENGER_MAX_S: "90"
|
||||||
|
# Admin → Update: Versionen (Tags) vom Git-Server. Ausgeführt wird ein
|
||||||
|
# Update vom Dienst auf dem Rechner (Docker/update-einrichten.sh).
|
||||||
|
UPDATE_REPO_API: "http://192.168.3.187:3000/api/v1/repos/erik/projektuebersicht"
|
||||||
|
restart: always
|
||||||
|
# Betrieb auf dem Pi Zero 2 (512 MB), siehe CLAUDE.md "Betrieb auf dem Pi":
|
||||||
|
# tini als PID 1 — Signale kommen sauber bei Node an (DB wird geschlossen).
|
||||||
|
init: true
|
||||||
|
# Feste Speichergrenze: Läuft die App über, trifft es nur diesen
|
||||||
|
# Container (restart: always holt ihn zurück), nicht SSH oder Docker.
|
||||||
|
mem_limit: 320m
|
||||||
|
memswap_limit: 512m
|
||||||
|
# Die Protokolle wuchsen sonst unbegrenzt auf der SD-Karte.
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "5m"
|
||||||
|
max-file: "3"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3003/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"]
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3003/health >/dev/null || exit 1"]
|
||||||
interval: 30s
|
interval: 60s
|
||||||
timeout: 5s
|
timeout: 10s
|
||||||
start_period: 10s
|
start_period: 60s
|
||||||
retries: 3
|
retries: 3
|
||||||
BIN
Binary file not shown.
-1
@@ -1 +0,0 @@
|
|||||||
../mammoth/bin/mammoth
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
../mime/cli.js
|
|
||||||
-1084
File diff suppressed because it is too large.
Load diff
-533
@@ -1,533 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [0.8.15](https://github.com/xmldom/xmldom/compare/0.8.14...0.8.15)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Security: parsing a deeply or repeatedly namespaced document no longer consumes quadratic memory; the in-scope namespace map is inherited through the prototype chain instead of being copied for every prefix-declaring element (O(N) instead of O(N²)), preventing a denial-of-service reachable from `DOMParser.parseFromString` with default options. Serialized output is byte-identical. [`GHSA-965w-775f-mr7g`](https://github.com/xmldom/xmldom/security/advisories/GHSA-965w-775f-mr7g)
|
|
||||||
- Security: attribute de-duplication during parsing is now O(M) instead of O(M²); the `NamedNodeMap` parse-time dedup path uses a null-prototype membership index, so a well-formed document with a hostile number of duplicate attributes can no longer wedge the parse. Attribute order and duplicate resolution (last value wins, first position kept) are byte-identical, preserving the XML [no-duplicate-attributes well-formedness constraint](https://www.w3.org/TR/xml/#uniqattspec). [`GHSA-8344-3jmq-59r6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6)
|
|
||||||
- Security: trimming trailing whitespace from an XML end tag ([`ETag`](https://www.w3.org/TR/xml/#NT-ETag)) is now anchored so it runs in linear time instead of backtracking quadratically on a long whitespace run, preventing a ReDoS reachable from `DOMParser.parseFromString`. Trimmed output is byte-identical. [`GHSA-x4fp-j954-r2f4`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x4fp-j954-r2f4)
|
|
||||||
- Security: malformed-input recovery is now linear instead of quadratic — the malformed tag-name scan terminates at an embedded `<`, and `Node.prototype.normalize()` merges adjacent text nodes in O(K) instead of O(K²) (also reachable programmatically), per [`normalize()`](https://dom.spec.whatwg.org/#dom-node-normalize) in the WHATWG DOM spec. DOM output is unchanged; only the reported error text differs. [`GHSA-93r5-fhx6-vmg9`](https://github.com/xmldom/xmldom/security/advisories/GHSA-93r5-fhx6-vmg9)
|
|
||||||
- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now rejects a DocType `name` that is not a valid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name), throwing `InvalidStateError` — matching the sibling `publicId`/`systemId`/`internalSubset` checks and preventing XML injection via `DocumentType.name`. [`GHSA-27p8-2357-5qqv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv)
|
|
||||||
- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now validates a processing-instruction target as an XML [`NCName`](https://www.w3.org/TR/xml-names/#NT-NCName) and rejects a case-insensitive `xml`, throwing `InvalidStateError` — a check `0.8.x` did not previously perform, preventing PI-target injection via `>`, `?`, or whitespace. [`GHSA-c7q8-3ch8-vqpv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-c7q8-3ch8-vqpv)
|
|
||||||
- Security: `Document.createEntityReference()` now rejects an invalid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name) at creation, and `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` validates an `EntityReference` `nodeName` as an XML `Name`, throwing `InvalidStateError` — preventing XML injection via an entity-reference name. [`GHSA-6gmq-8vp8-gcm6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6gmq-8vp8-gcm6)
|
|
||||||
- Security: the parser now reports a not-well-formed end tag whose valid name is followed by trailing content as a recoverable `error` instead of accepting it silently, per the XML [`ETag`](https://www.w3.org/TR/xml/#NT-ETag) production; parsing recovers to the byte-identical DOM. Consumers that want strict rejection can escalate the reported `error` to fatal via the parser's `errorHandler`. [`GHSA-6h8r-xr42-gp59`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6h8r-xr42-gp59)
|
|
||||||
|
|
||||||
Thank you,
|
|
||||||
[@ericchiang](https://github.com/ericchiang),
|
|
||||||
[@bhaswanthc](https://github.com/bhaswanthc),
|
|
||||||
[@arpitjain099](https://github.com/arpitjain099),
|
|
||||||
[@Paranoidgrinch](https://github.com/Paranoidgrinch),
|
|
||||||
for your contributions
|
|
||||||
|
|
||||||
## [0.8.14](https://github.com/xmldom/xmldom/compare/0.8.13...0.8.14)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Security: `XMLSerializer.serializeToString()` now also rejects invalid element and attribute names when `{ requireWellFormed: true }` is passed, throwing `InvalidStateError` for a name that is not a valid XML [`QName`](https://www.w3.org/TR/xml-names/#NT-QName) (this covers the namespace prefix, which surfaces in the element qualified name or in a synthesized `xmlns:` declaration). This prevents XML injection via `createElement()` / `setAttribute()`, extending the existing `requireWellFormed` checks to the serialized name set. [`GHSA-w2rr-34g9-rvrj`](https://github.com/xmldom/xmldom/security/advisories/GHSA-w2rr-34g9-rvrj) [`GHSA-4w3w-2rp5-g8jm`](https://github.com/xmldom/xmldom/security/advisories/GHSA-4w3w-2rp5-g8jm)
|
|
||||||
|
|
||||||
Thank you,
|
|
||||||
[@bhaswanthc](https://github.com/bhaswanthc),
|
|
||||||
[@jmestwa-coder](https://github.com/jmestwa-coder),
|
|
||||||
for your contributions
|
|
||||||
|
|
||||||
## [0.8.13](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Security: `XMLSerializer.serializeToString()` (and `Node.toString()`, `NodeList.toString()`) now accept a `requireWellFormed` option (fourth argument, after `isHtml` and `nodeFilter`). When `{ requireWellFormed: true }` is passed, the serializer throws `InvalidStateError` for injection-prone node content, preventing XML injection via attacker-controlled node data. [`GHSA-j759-j44w-7fr8`](https://github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8) [`GHSA-x6wf-f3px-wcqx`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x6wf-f3px-wcqx) [`GHSA-f6ww-3ggp-fr8h`](https://github.com/xmldom/xmldom/security/advisories/GHSA-f6ww-3ggp-fr8h)
|
|
||||||
- Comment: throws when `data` contains `-->`
|
|
||||||
- ProcessingInstruction: throws when `data` contains `?>`
|
|
||||||
- DocumentType: throws when `publicId` fails `PubidLiteral`, `systemId` fails `SystemLiteral`, or `internalSubset` contains `]>`
|
|
||||||
- Security: DOM traversal operations (`XMLSerializer.serializeToString()`, `Node.prototype.normalize()`, `Node.prototype.cloneNode(true)`, `Document.prototype.importNode(node, true)`, `node.textContent` getter, `getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()`) are now iterative. Previously, deeply nested DOM trees would exhaust the JavaScript call stack and throw an unrecoverable `RangeError`. [`GHSA-2v35-w6hq-6mfw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-2v35-w6hq-6mfw)
|
|
||||||
|
|
||||||
Thank you,
|
|
||||||
[@Jvr2022](https://github.com/Jvr2022),
|
|
||||||
[@praveen-kv](https://github.com/praveen-kv),
|
|
||||||
[@TharVid](https://github.com/TharVid),
|
|
||||||
[@decsecre583](https://github.com/decsecre583),
|
|
||||||
[@tlsbollei](https://github.com/tlsbollei),
|
|
||||||
[@KarimTantawey](https://github.com/KarimTantawey),
|
|
||||||
for your contributions
|
|
||||||
|
|
||||||
## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- preserve trailing whitespace in ProcessingInstruction data [`#962`](https://github.com/xmldom/xmldom/pull/962) / [`#42`](https://github.com/xmldom/xmldom/issues/42)
|
|
||||||
- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
|
|
||||||
- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
|
|
||||||
|
|
||||||
Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data.
|
|
||||||
|
|
||||||
Thank you,
|
|
||||||
[@thesmartshadow](https://github.com/thesmartshadow),
|
|
||||||
[@stevenobiajulu](https://github.com/stevenobiajulu),
|
|
||||||
for your contributions
|
|
||||||
|
|
||||||
## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- update `ownerDocument` when moving nodes between documents [`#933`](https://github.com/xmldom/xmldom/pull/933) / [`#932`](https://github.com/xmldom/xmldom/issues/932)
|
|
||||||
|
|
||||||
Thank you, [@shunkica](https://github.com/shunkica), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.10](https://github.com/xmldom/xmldom/compare/0.8.9...0.8.10)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- dom: prevent iteration over deleted items [`#514`](https://github.com/xmldom/xmldom/pull/514)/ [`#499`](https://github.com/xmldom/xmldom/issues/499)
|
|
||||||
|
|
||||||
Thank you, [@qtow](https://github.com/qtow), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.9](https://github.com/xmldom/xmldom/compare/0.8.8...0.8.9)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Set nodeName property in ProcessingInstruction [`#509`](https://github.com/xmldom/xmldom/pull/509) / [`#505`](https://github.com/xmldom/xmldom/issues/505)
|
|
||||||
|
|
||||||
Thank you, [@cjbarth](https://github.com/cjbarth), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.8](https://github.com/xmldom/xmldom/compare/0.8.7...0.8.8)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- extend list of HTML entities [`#489`](https://github.com/xmldom/xmldom/pull/489)
|
|
||||||
|
|
||||||
Thank you, [@zorkow](https://github.com/zorkow), for your contributions
|
|
||||||
|
|
||||||
## [0.8.7](https://github.com/xmldom/xmldom/compare/0.8.6...0.8.7)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486)
|
|
||||||
|
|
||||||
Thank you, [@bulandent](https://github.com/bulandent), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.7.10](https://github.com/xmldom/xmldom/compare/0.7.9...0.7.10)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486)
|
|
||||||
|
|
||||||
Thank you, [@bulandent](https://github.com/bulandent), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.6](https://github.com/xmldom/xmldom/compare/0.8.5...0.8.6)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456)
|
|
||||||
|
|
||||||
Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453)
|
|
||||||
|
|
||||||
Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883)
|
|
||||||
In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly.
|
|
||||||
In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead.
|
|
||||||
This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior.
|
|
||||||
Related Spec: <https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity>
|
|
||||||
|
|
||||||
Thank you, [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.3](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Avoid iterating over prototype properties [`#437`](https://github.com/xmldom/xmldom/pull/437) / [`#436`](https://github.com/xmldom/xmldom/issues/436)
|
|
||||||
|
|
||||||
Thank you, [@Supraja9726](https://github.com/Supraja9726) for your contributions
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.2](https://github.com/xmldom/xmldom/compare/0.8.1...0.8.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- fix(dom): Serialize `>` as specified (#395) [`#58`](https://github.com/xmldom/xmldom/issues/58)
|
|
||||||
|
|
||||||
### Other
|
|
||||||
- docs: Add `nodeType` values to public interface description [`#396`](https://github.com/xmldom/xmldom/pull/396)
|
|
||||||
- test: Add executable examples for node and typescript [`#317`](https://github.com/xmldom/xmldom/pull/317)
|
|
||||||
- fix(dom): Serialize `>` as specified [`#395`](https://github.com/xmldom/xmldom/pull/395)
|
|
||||||
- chore: Add minimal `Object.assign` ponyfill [`#379`](https://github.com/xmldom/xmldom/pull/379)
|
|
||||||
- docs: Refine release documentation [`#378`](https://github.com/xmldom/xmldom/pull/378)
|
|
||||||
- chore: update various dev dependencies
|
|
||||||
|
|
||||||
Thank you [@niklasl](https://github.com/niklasl), [@cburatto](https://github.com/cburatto), [@SheetJSDev](https://github.com/SheetJSDev), [@pyrsmk](https://github.com/pyrsmk) for your contributions
|
|
||||||
|
|
||||||
## [0.8.1](https://github.com/xmldom/xmldom/compare/0.8.0...0.8.1)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- Only use own properties in entityMap [`#374`](https://github.com/xmldom/xmldom/pull/374)
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
- Add security policy [`#365`](https://github.com/xmldom/xmldom/pull/365)
|
|
||||||
- changelog: Correct contributor name and link [`#366`](https://github.com/xmldom/xmldom/pull/366)
|
|
||||||
- Describe release/publish steps [`#358`](https://github.com/xmldom/xmldom/pull/358), [`#376`](https://github.com/xmldom/xmldom/pull/376)
|
|
||||||
- Add snyk package health badge [`#360`](https://github.com/xmldom/xmldom/pull/360)
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.0](https://github.com/xmldom/xmldom/compare/0.7.5...0.8.0)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Normalize all line endings according to XML specs [1.0](https://w3.org/TR/xml/#sec-line-ends) and [1.1](https://www.w3.org/TR/xml11/#sec-line-ends) \
|
|
||||||
BREAKING CHANGE: Certain combination of line break characters are normalized to a single `\n` before parsing takes place and will no longer be preserved.
|
|
||||||
- [`#303`](https://github.com/xmldom/xmldom/issues/303) / [`#307`](https://github.com/xmldom/xmldom/pull/307)
|
|
||||||
- [`#49`](https://github.com/xmldom/xmldom/issues/49), [`#97`](https://github.com/xmldom/xmldom/issues/97), [`#324`](https://github.com/xmldom/xmldom/issues/324) / [`#314`](https://github.com/xmldom/xmldom/pull/314)
|
|
||||||
- XMLSerializer: Preserve whitespace character references [`#284`](https://github.com/xmldom/xmldom/issues/284) / [`#310`](https://github.com/xmldom/xmldom/pull/310) \
|
|
||||||
BREAKING CHANGE: If you relied on the not spec compliant preservation of literal `\t`, `\n` or `\r` in **attribute values**.
|
|
||||||
To preserve those you will have to create XML that instead contains the correct numerical (or hexadecimal) equivalent (e.g. `	`, `
`, `
`).
|
|
||||||
- Drop deprecated exports `DOMImplementation` and `XMLSerializer` from `lib/dom-parser.js` [#53](https://github.com/xmldom/xmldom/issues/53) / [`#309`](https://github.com/xmldom/xmldom/pull/309)
|
|
||||||
BREAKING CHANGE: Use the one provided by the main package export.
|
|
||||||
- dom: Remove all links as part of `removeChild` [`#343`](https://github.com/xmldom/xmldom/issues/343) / [`#355`](https://github.com/xmldom/xmldom/pull/355)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- ci: Restore latest tested node version to 16.x [`#325`](https://github.com/xmldom/xmldom/pull/325)
|
|
||||||
- ci: Split test and lint steps into jobs [`#111`](https://github.com/xmldom/xmldom/issues/111) / [`#304`](https://github.com/xmldom/xmldom/pull/304)
|
|
||||||
- Pinned and updated devDependencies
|
|
||||||
|
|
||||||
Thank you [@marrus-sh](https://github.com/marrus-sh), [@victorandree](https://github.com/victorandree), [@mdierolf](https://github.com/mdierolf), [@tsabbay](https://github.com/tsabbay), [@fatihpense](https://github.com/fatihpense) for your contributions
|
|
||||||
|
|
||||||
## 0.7.5
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.7.4...0.7.5)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Preserve default namespace when serializing [`#319`](https://github.com/xmldom/xmldom/issues/319) / [`#321`](https://github.com/xmldom/xmldom/pull/321)
|
|
||||||
Thank you, [@lupestro](https://github.com/lupestro)
|
|
||||||
|
|
||||||
## 0.7.4
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.7.3...0.7.4)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Restore ability to parse `__prototype__` attributes [`#315`](https://github.com/xmldom/xmldom/pull/315)
|
|
||||||
Thank you, [@dsimpsonOMF](https://github.com/dsimpsonOMF)
|
|
||||||
|
|
||||||
## 0.7.3
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.7.2...0.7.3)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Add doctype when parsing from string [`#277`](https://github.com/xmldom/xmldom/issues/277) / [`#301`](https://github.com/xmldom/xmldom/pull/301)
|
|
||||||
- Correct typo in error message [`#294`](https://github.com/xmldom/xmldom/pull/294)
|
|
||||||
Thank you, [@rrthomas](https://github.com/rrthomas)
|
|
||||||
|
|
||||||
### Refactor:
|
|
||||||
|
|
||||||
- Improve exports & require statements, new main package entry [`#233`](https://github.com/xmldom/xmldom/pull/233)
|
|
||||||
|
|
||||||
### Docs:
|
|
||||||
|
|
||||||
- Fix Stryker badge [`#298`](https://github.com/xmldom/xmldom/pull/298)
|
|
||||||
- Fix link to help-wanted issues [`#299`](https://github.com/xmldom/xmldom/pull/299)
|
|
||||||
|
|
||||||
### Chore:
|
|
||||||
|
|
||||||
- Execute stryker:dry-run on branches [`#302`](https://github.com/xmldom/xmldom/pull/302)
|
|
||||||
- Fix stryker config [`#300`](https://github.com/xmldom/xmldom/pull/300)
|
|
||||||
- Split test and lint scripts [`#297`](https://github.com/xmldom/xmldom/pull/297)
|
|
||||||
- Switch to stryker dashboard owned by org [`#292`](https://github.com/xmldom/xmldom/pull/292)
|
|
||||||
|
|
||||||
## 0.7.2
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.7.1...0.7.2)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Types: Add index.d.ts to packaged files [`#288`](https://github.com/xmldom/xmldom/pull/288)
|
|
||||||
Thank you, [@forty](https://github.com/forty)
|
|
||||||
|
|
||||||
## 0.7.1
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.7.0...0.7.1)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Types: Copy types from DefinitelyTyped [`#283`](https://github.com/xmldom/xmldom/pull/283)
|
|
||||||
Thank you, [@kachkaev](https://github.com/kachkaev)
|
|
||||||
|
|
||||||
### Chore:
|
|
||||||
- package.json: remove author, maintainers, etc. [`#279`](https://github.com/xmldom/xmldom/pull/279)
|
|
||||||
|
|
||||||
## 0.7.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.6.0...0.7.0)
|
|
||||||
|
|
||||||
Due to [`#271`](https://github.com/xmldom/xmldom/issue/271) this version was published as
|
|
||||||
- unscoped `xmldom` package to github (git tags [`0.7.0`](https://github.com/xmldom/xmldom/tree/0.7.0) and [`0.7.0+unscoped`](https://github.com/xmldom/xmldom/tree/0.7.0%2Bunscoped))
|
|
||||||
- scoped `@xmldom/xmldom` package to npm (git tag `0.7.0+scoped`)
|
|
||||||
For more details look at [`#278`](https://github.com/xmldom/xmldom/pull/278#issuecomment-902172483)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- Security: Misinterpretation of malicious XML input [`CVE-2021-32796`](https://github.com/xmldom/xmldom/security/advisories/GHSA-5fg8-2547-mr8q)
|
|
||||||
- Implement `Document.getElementsByClassName` as specified [`#213`](https://github.com/xmldom/xmldom/pull/213), thank you, [@ChALkeR](https://github.com/ChALkeR)
|
|
||||||
- Inherit namespace prefix from parent when required [`#268`](https://github.com/xmldom/xmldom/pull/268)
|
|
||||||
- Handle whitespace in closing tags [`#267`](https://github.com/xmldom/xmldom/pull/267)
|
|
||||||
- Update `DOMImplementation` according to recent specs [`#210`](https://github.com/xmldom/xmldom/pull/210)
|
|
||||||
BREAKING CHANGE: Only if you "passed features to be marked as available as a constructor arguments" and expected it to "magically work".
|
|
||||||
- No longer serializes any namespaces with an empty URI [`#244`](https://github.com/xmldom/xmldom/pull/244)
|
|
||||||
(related to [`#168`](https://github.com/xmldom/xmldom/pull/168) released in 0.6.0)
|
|
||||||
BREAKING CHANGE: Only if you rely on ["unsetting" a namespace prefix](https://github.com/xmldom/xmldom/pull/168#issuecomment-886984994) by setting it to an empty string
|
|
||||||
- Set `localName` as part of `Document.createElement` [`#229`](https://github.com/xmldom/xmldom/pull/229), thank you, [@rrthomas](https://github.com/rrthomas)
|
|
||||||
|
|
||||||
### CI
|
|
||||||
|
|
||||||
- We are now additionally running tests against node v16
|
|
||||||
- Stryker tests on the master branch now run against node v14
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
- Describe relations with and between specs: [`#211`](https://github.com/xmldom/xmldom/pull/211), [`#247`](https://github.com/xmldom/xmldom/pull/247)
|
|
||||||
|
|
||||||
## 0.6.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.5.0...0.6.0)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- Stop serializing empty namespace values like `xmlns:ds=""` [`#168`](https://github.com/xmldom/xmldom/pull/168)
|
|
||||||
BREAKING CHANGE: If your code expected empty namespaces attributes to be serialized.
|
|
||||||
Thank you, [@pdecat](https://github.com/pdecat) and [@FranckDepoortere](https://github.com/FranckDepoortere)
|
|
||||||
- Escape `<` to `<` when serializing attribute values [`#198`](https://github.com/xmldom/xmldom/issues/198) / [`#199`](https://github.com/xmldom/xmldom/pull/199)
|
|
||||||
|
|
||||||
## 0.5.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.4.0...0.5.0)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- Avoid misinterpretation of malicious XML input - [`GHSA-h6q6-9hqw-rwfv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-h6q6-9hqw-rwfv) (CVE-2021-21366)
|
|
||||||
- Improve error reporting; throw on duplicate attribute\
|
|
||||||
BREAKING CHANGE: It is currently not clear how to consistently deal with duplicate attributes, so it's also safer for our users to fail when detecting them.
|
|
||||||
It's possible to configure the `DOMParser.errorHandler` before parsing, to handle those errors differently.
|
|
||||||
|
|
||||||
To accomplish this and also be able to verify it in tests I needed to
|
|
||||||
- create a new `Error` type `ParseError` and export it
|
|
||||||
- Throw `ParseError` from `errorHandler.fatalError` and prevent those from being caught in `XMLReader`.
|
|
||||||
- export `DOMHandler` constructor as `__DOMHandler`
|
|
||||||
- Preserve quotes in DOCTYPE declaration
|
|
||||||
Since the only purpose of parsing the DOCTYPE is to be able to restore it when serializing, we decided that it would be best to leave the parsed `publicId` and `systemId` as is, including any quotes.
|
|
||||||
BREAKING CHANGE: If somebody relies on the actual unquoted values of those ids, they will need to take care of either single or double quotes and the right escaping.
|
|
||||||
(Without this change this would not have been possible because the SAX parser already dropped the information about the quotes that have been used in the source.)
|
|
||||||
|
|
||||||
https://www.w3.org/TR/2006/REC-xml11-20060816/#dtd
|
|
||||||
https://www.w3.org/TR/2006/REC-xml11-20060816/#IDAX1KS (External Entity Declaration)
|
|
||||||
|
|
||||||
- Fix breaking preprocessors' directives when parsing attributes [`#171`](https://github.com/xmldom/xmldom/pull/171)
|
|
||||||
- fix(dom): Escape `]]>` when serializing CharData [`#181`](https://github.com/xmldom/xmldom/pull/181)
|
|
||||||
- Switch to (only) MIT license (drop problematic LGPL license option) [`#178`](https://github.com/xmldom/xmldom/pull/178)
|
|
||||||
- Export DOMException; remove custom assertions; etc. [`#174`](https://github.com/xmldom/xmldom/pull/174)
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
- Update MDN links in `readme.md` [`#188`](https://github.com/xmldom/xmldom/pull/188)
|
|
||||||
|
|
||||||
## 0.4.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.3.0...0.4.0)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- **BREAKING** Restore ` ` behavior from v0.1.27 [`#67`](https://github.com/xmldom/xmldom/pull/67)
|
|
||||||
- **BREAKING** Typecheck source param before parsing [`#113`](https://github.com/xmldom/xmldom/pull/113)
|
|
||||||
- Include documents in package files list [`#156`](https://github.com/xmldom/xmldom/pull/156)
|
|
||||||
- Preserve doctype with sysid [`#144`](https://github.com/xmldom/xmldom/pull/144)
|
|
||||||
- Remove ES6 syntax from getElementsByClassName [`#91`](https://github.com/xmldom/xmldom/pull/91)
|
|
||||||
- Revert "Add lowercase of åäö in entityMap" due to duplicate entries [`#84`](https://github.com/xmldom/xmldom/pull/84)
|
|
||||||
- fix: Convert all line separators to LF [`#66`](https://github.com/xmldom/xmldom/pull/66)
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
- Update CHANGELOG.md through version 0.3.0 [`#63`](https://github.com/xmldom/xmldom/pull/63)
|
|
||||||
- Update badges [`#78`](https://github.com/xmldom/xmldom/pull/78)
|
|
||||||
- Add .editorconfig file [`#104`](https://github.com/xmldom/xmldom/pull/104)
|
|
||||||
- Add note about import [`#79`](https://github.com/xmldom/xmldom/pull/79)
|
|
||||||
- Modernize & improve the example in readme.md [`#81`](https://github.com/xmldom/xmldom/pull/81)
|
|
||||||
|
|
||||||
### CI
|
|
||||||
- Add Stryker Mutator [`#70`](https://github.com/xmldom/xmldom/pull/70)
|
|
||||||
- Add Stryker action to update dashboard [`#77`](https://github.com/xmldom/xmldom/pull/77)
|
|
||||||
- Add Node GitHub action workflow [`#64`](https://github.com/xmldom/xmldom/pull/64)
|
|
||||||
- add & enable eslint [`#106`](https://github.com/xmldom/xmldom/pull/106)
|
|
||||||
- Use eslint-plugin-es5 to enforce ES5 syntax [`#107`](https://github.com/xmldom/xmldom/pull/107)
|
|
||||||
- Recover `vows` tests, drop `proof` tests [`#59`](https://github.com/xmldom/xmldom/pull/59)
|
|
||||||
- Add jest tessuite and first tests [`#114`](https://github.com/xmldom/xmldom/pull/114)
|
|
||||||
- Add jest testsuite with `xmltest` cases [`#112`](https://github.com/xmldom/xmldom/pull/112)
|
|
||||||
- Configure Renovate [`#108`](https://github.com/xmldom/xmldom/pull/108)
|
|
||||||
- Test European HTML entities [`#86`](https://github.com/xmldom/xmldom/pull/86)
|
|
||||||
- Updated devDependencies
|
|
||||||
|
|
||||||
### Other
|
|
||||||
- Remove files that are not of any use [`#131`](https://github.com/xmldom/xmldom/pull/131), [`#65`](https://github.com/xmldom/xmldom/pull/65), [`#33`](https://github.com/xmldom/xmldom/pull/33)
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.2.1...0.3.0)
|
|
||||||
|
|
||||||
- **BREAKING** Node >=10.x is now required.
|
|
||||||
- **BREAKING** Remove `component.json` (deprecated package manager https://github.com/componentjs/guide)
|
|
||||||
- **BREAKING** Move existing sources into `lib` subdirectory.
|
|
||||||
- **POSSIBLY BREAKING** Introduce `files` entry in `package.json` and remove use of `.npmignore`.
|
|
||||||
- [Add `Document.getElementsByClassName`](https://github.com/xmldom/xmldom/issues/24).
|
|
||||||
- [Add `Node` to the list of exports](https://github.com/xmldom/xmldom/pull/27)
|
|
||||||
- [Add lowercase of åäö in `entityMap`](https://github.com/xmldom/xmldom/pull/23).
|
|
||||||
- Move CHANGELOG to markdown file.
|
|
||||||
- Move LICENSE to markdown file.
|
|
||||||
|
|
||||||
## 0.2.1
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/0.2.0...0.2.1)
|
|
||||||
|
|
||||||
- Correct `homepage`, `repository` and `bugs` URLs in `package.json`.
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...0.2.0)
|
|
||||||
|
|
||||||
- Includes all **BREAKING** changes introduced in [`xmldom-alpha@v0.1.28`](#0128) by the original authors.
|
|
||||||
- **POSSIBLY BREAKING** [remove the `Object.create` check from the `_extends` method of `dom.js` that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/0be2ae910a8a22c9ec2cac042e04de4c04317d2a#diff-7d1c5d97786fdf9af5446a241d0b6d56L19-L22) ().
|
|
||||||
- **POSSIBLY BREAKING** [remove code that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/366159a76a181ce9a0d83f5dc48205686cfaf9cc)
|
|
||||||
- formatting/corrections in `package.json`
|
|
||||||
|
|
||||||
## 0.1.31
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...v0.1.31)
|
|
||||||
|
|
||||||
The patch versions (`v0.1.29` - `v0.1.31`) that have been released on the [v0.1.x branch](https://github.com/xmldom/xmldom/tree/0.1.x), to reflect the changed maintainers, **are branched off from [`v0.1.27`](#0127) so they don't include the breaking changes introduced in [`xmldom-alpha@v0.1.28`](#0128)**:
|
|
||||||
|
|
||||||
## Maintainer changes
|
|
||||||
|
|
||||||
After the last commit to the original repository <https://github.com/jindw/xmldom> on the 9th of May 2017, the first commit to <https://github.com/xmldom/xmldom> is from the 19th of December 2019. [The fork has been announced in the original repository on the 2nd of March 2020.](https://github.com/jindw/xmldom/issues/259)
|
|
||||||
|
|
||||||
The versions listed below have been published to one or both of the following packages:
|
|
||||||
- <https://www.npmjs.com/package/xmldom-alpha>
|
|
||||||
- <https://www.npmjs.com/package/xmldom>
|
|
||||||
|
|
||||||
It is currently not planned to continue publishing the `xmldom-alpha` package.
|
|
||||||
|
|
||||||
The new maintainers did not invest time to understand changes that led to the last `xmldom` version [`0.1.27`](#0127) published by the original maintainer, but consider it the basis for their work.
|
|
||||||
A timeline of all the changes that happened from that version until `0.3.0` is available in <https://github.com/xmldom/xmldom/issues/62>. Any related questions should be asked there.
|
|
||||||
|
|
||||||
## 0.1.28
|
|
||||||
|
|
||||||
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...xmldom-alpha@v0.1.28)
|
|
||||||
|
|
||||||
Published by @jindw on the 9th of May 2017 as
|
|
||||||
- `xmldom-alpha@0.1.28`
|
|
||||||
|
|
||||||
- **BREAKING** includes [regression regarding ` ` (issue #57)](https://github.com/xmldom/xmldom/issues/57)
|
|
||||||
- [Fix `license` field in `package.json`](https://github.com/jindw/xmldom/pull/178)
|
|
||||||
- [Conditional converting of HTML entities](https://github.com/jindw/xmldom/pull/80)
|
|
||||||
- Fix `dom.js` serialization issue for missing document element ([example that failed on `toString()` before this change](https://github.com/xmldom/xmldom/blob/a58dcf7a265522e80ce520fe3be0cddb1b976f6f/test/parse/unclosedcomment.js#L10-L11))
|
|
||||||
- Add new module `entities.js`
|
|
||||||
|
|
||||||
## 0.1.27
|
|
||||||
|
|
||||||
Published by @jindw on the 28th of Nov 2016 as
|
|
||||||
- `xmldom@0.1.27`
|
|
||||||
- `xmldom-alpha@0.1.27`
|
|
||||||
|
|
||||||
- Various bug fixes.
|
|
||||||
|
|
||||||
## 0.1.26
|
|
||||||
|
|
||||||
Published on the 18th of Nov 2016
|
|
||||||
as `xmldom@0.1.26`
|
|
||||||
|
|
||||||
- Details unknown
|
|
||||||
|
|
||||||
## 0.1.25
|
|
||||||
|
|
||||||
Published on the 18th of Nov 2016 as
|
|
||||||
- `xmldom@0.1.25`
|
|
||||||
|
|
||||||
- Details unknown
|
|
||||||
|
|
||||||
## 0.1.24
|
|
||||||
|
|
||||||
Published on the 27th of November 2016 as
|
|
||||||
- `xmldom@0.1.24`
|
|
||||||
- `xmldom-alpha@0.1.24`
|
|
||||||
|
|
||||||
- Added node filter.
|
|
||||||
|
|
||||||
## 0.1.23
|
|
||||||
|
|
||||||
Published on the 5th of May 2016 as
|
|
||||||
- `xmldom-alpha@0.1.23`
|
|
||||||
|
|
||||||
- Add namespace support for nest node serialize.
|
|
||||||
- Various other bug fixes.
|
|
||||||
|
|
||||||
## 0.1.22
|
|
||||||
|
|
||||||
- Merge XMLNS serialization.
|
|
||||||
- Remove \r from source string.
|
|
||||||
- Print namespaces for child elements.
|
|
||||||
- Switch references to nodeType to use named constants.
|
|
||||||
- Add nodelist toString support.
|
|
||||||
|
|
||||||
## 0.1.21
|
|
||||||
|
|
||||||
- Fix serialize bug.
|
|
||||||
|
|
||||||
## 0.1.20
|
|
||||||
|
|
||||||
- Optimize invalid XML support.
|
|
||||||
- Add toString sorter for attributes output.
|
|
||||||
- Add html self closed node button.
|
|
||||||
- Add `*` NS support for getElementsByTagNameNS.
|
|
||||||
- Convert attribute's value to string in setAttributeNS.
|
|
||||||
- Add support for HTML entities for HTML docs only.
|
|
||||||
- Fix TypeError when Document is created with DocumentType.
|
|
||||||
|
|
||||||
## 0.1.19
|
|
||||||
|
|
||||||
- Fix [infinite loop on unclosed comment (jindw/xmldom#68)](https://github.com/jindw/xmldom/issues/68)
|
|
||||||
- Add error report for unclosed tag.
|
|
||||||
- Various other fixes.
|
|
||||||
|
|
||||||
## 0.1.18
|
|
||||||
|
|
||||||
- Add default `ns` support.
|
|
||||||
- parseFromString now renders entirely plain text documents as textNode.
|
|
||||||
- Enable option to ignore white space on parsing.
|
|
||||||
|
|
||||||
## 0.1.17
|
|
||||||
|
|
||||||
**Details missing for this and potential earlier version**
|
|
||||||
|
|
||||||
## 0.1.16
|
|
||||||
|
|
||||||
- Correctly handle multibyte Unicode greater than two byts. #57. #56.
|
|
||||||
- Initial unit testing and test coverage. #53. #46. #19.
|
|
||||||
- Create Bower `component.json` #52.
|
|
||||||
|
|
||||||
## 0.1.8
|
|
||||||
|
|
||||||
- Add: some test case from node-o3-xml(excludes xpath support)
|
|
||||||
- Fix: remove existed attribute before setting (bug introduced in v0.1.5)
|
|
||||||
- Fix: index direct access for childNodes and any NodeList collection(not w3c standard)
|
|
||||||
- Fix: remove last child bug
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
Copyright 2019 - present Christopher J. Brody and other contributors, as listed in: https://github.com/xmldom/xmldom/graphs/contributors
|
|
||||||
Copyright 2012 - 2017 @jindw <jindw@xidea.org> and other contributors, as listed in: https://github.com/jindw/xmldom/graphs/contributors
|
|
||||||
|
|
||||||
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.
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
The most up-to-date version of this document can be found at <https://github.com/xmldom/xmldom/security/policy>.
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
This repository contains the code for the libraries `xmldom` and `@xmldom/xmldom` on npm.
|
|
||||||
|
|
||||||
As long as we didn't publish v1, we aim to maintain the last two minor versions with security fixes. If it is possible we provide security fixes as path versions.
|
|
||||||
If you think there is a good reason to also patch an earlier version let us know in a github issue or the release discussion once the fix has been provided.
|
|
||||||
The maintainers will consider it and if we agree and have/find the required resources, a patch for that version will be provided.
|
|
||||||
|
|
||||||
Please notice that [we are no longer able to publish the (unscoped) `xmldom` package](https://github.com/xmldom/xmldom/issues/271),
|
|
||||||
and that all existing versions of `xmldom` are affected by at least one security vulnerability and should be considered deprecated.
|
|
||||||
You can still report issues regarding `xmldom` as described below.
|
|
||||||
|
|
||||||
If you need help with migrating from `xmldom` to `@xmldom/xmldom`, file a github issue or PR in the affected repository and mention @karfau.
|
|
||||||
|
|
||||||
## Reporting vulnerabilities
|
|
||||||
|
|
||||||
Please email reports about any security related issues you find to `security@xmldom.org`, which will forward it to the list of maintainers.
|
|
||||||
The maintainers will try to respond within 7 calendar days. (If nobody peplies after 7 days, please us send a reminder!)
|
|
||||||
As part of you communication please make sure to always hit "Reply all", so all maintainers are kept in the loop.
|
|
||||||
|
|
||||||
In addition, please include the following information along with your report:
|
|
||||||
|
|
||||||
- Your name and affiliation (if any).
|
|
||||||
- A description of the technical details of the vulnerabilities. It is very important to let us know how we can reproduce your findings.
|
|
||||||
- An explanation who can exploit this vulnerability, and what they gain when doing so -- write an attack scenario. This will help us evaluate your report quickly, especially if the issue is complex.
|
|
||||||
- Whether this vulnerability public or known to third parties. If it is, please provide details.
|
|
||||||
|
|
||||||
If you believe that an existing (public) issue is security-related, please send an email to `security@xmldom.org`.
|
|
||||||
The email should include the issue URL and a short description of why it should be handled according to this security policy.
|
|
||||||
|
|
||||||
Once an issue is reported, the maintainers use the following disclosure process:
|
|
||||||
|
|
||||||
- When a report is received, we confirm the issue, determine its severity and the affected versions.
|
|
||||||
- If we know of specific third-party services or software based on xmldom that require mitigation before publication, those projects will be notified.
|
|
||||||
- A [github security advisory](https://docs.github.com/en/code-security/security-advisories/about-github-security-advisories) is [created](https://docs.github.com/en/code-security/security-advisories/creating-a-security-advisory) (but not published) which details the problem and steps for mitigation.
|
|
||||||
- If the reporter provides a github account and agrees to it, we (add that github account as a collaborator on the advisuory)[https://docs.github.com/en/code-security/security-advisories/adding-a-collaborator-to-a-security-advisory].
|
|
||||||
- The vulnerability is fixed in a [private fork](https://docs.github.com/en/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability) and potential workarounds are identified.
|
|
||||||
- The maintainers audit the existing code to find any potential similar problems.
|
|
||||||
- The release for the current minor version and the [security advisory are published](https://docs.github.com/en/code-security/security-advisories/publishing-a-security-advisory).
|
|
||||||
- The release(s) for previous minor version(s) are published.
|
|
||||||
|
|
||||||
We credit reporters for identifying security issues, if they confirm that they want to.
|
|
||||||
|
|
||||||
## Known vulnerabilities
|
|
||||||
|
|
||||||
See https://github.com/xmldom/xmldom/security/advisories?state=published
|
|
||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
/// <reference lib="dom" />
|
|
||||||
|
|
||||||
declare module "@xmldom/xmldom" {
|
|
||||||
var DOMParser: DOMParserStatic;
|
|
||||||
var XMLSerializer: XMLSerializerStatic;
|
|
||||||
var DOMImplementation: DOMImplementationStatic;
|
|
||||||
|
|
||||||
interface DOMImplementationStatic {
|
|
||||||
new(): DOMImplementation;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMParserStatic {
|
|
||||||
new (): DOMParser;
|
|
||||||
new (options: Options): DOMParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface XMLSerializerStatic {
|
|
||||||
new (): XMLSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMParser {
|
|
||||||
parseFromString(xmlsource: string, mimeType?: string): Document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Options accepted by `XMLSerializer.prototype.serializeToString`. */
|
|
||||||
interface XMLSerializerOptions {
|
|
||||||
/**
|
|
||||||
* When `true`, the serializer throws a DOMException with code `INVALID_STATE_ERR` if:
|
|
||||||
* - A CDATASection node's data contains `"]]>"`.
|
|
||||||
* - A Comment node's data contains `"-->"` (the injection sequence that terminates a
|
|
||||||
* comment). Comments whose data contains `"--"` but not `"-->"` are accepted on this
|
|
||||||
* branch — the 0.8.x parser does not validate bare `"--"` in comment content.
|
|
||||||
* - A ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon)
|
|
||||||
* or is an ASCII case-insensitive match for `"xml"`, or its data contains `"?>"`
|
|
||||||
* (W3C DOM Parsing §3.2.1.7).
|
|
||||||
* - An EntityReference's `nodeName` is not a valid XML `Name`.
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
requireWellFormed?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface XMLSerializer {
|
|
||||||
/**
|
|
||||||
* Returns the result of serializing `node` to XML.
|
|
||||||
*
|
|
||||||
* When `options.requireWellFormed` is `true`, the serializer throws for content that would
|
|
||||||
* produce ill-formed XML.
|
|
||||||
*
|
|
||||||
* __This implementation differs from the specification:__
|
|
||||||
* - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
|
|
||||||
* at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
|
|
||||||
* default behaviour) unless `requireWellFormed` is `true`.
|
|
||||||
* - when `requireWellFormed` is `true`, `DOMException` with code `INVALID_STATE_ERR`
|
|
||||||
* is only thrown to prevent injection vectors, not for all the spec mandated checks.
|
|
||||||
*
|
|
||||||
* @throws {DOMException}
|
|
||||||
* With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and:
|
|
||||||
* - an Element's qualified name (including any namespace prefix) is not a valid XML QName,
|
|
||||||
* - an attribute's qualified name (including a synthesized `xmlns:` namespace declaration)
|
|
||||||
* is not a valid XML QName,
|
|
||||||
* - a CDATASection node's data contains `"]]>"`,
|
|
||||||
* - a Comment node's data contains `"-->"` (bare `"--"` does not throw on this branch),
|
|
||||||
* - a ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon) or
|
|
||||||
* is an ASCII case-insensitive match for `"xml"`, or its data contains `"?>"`,
|
|
||||||
* - a DocumentType's `name` is not a valid XML `Name` (XML 1.0 production [5]),
|
|
||||||
* - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral`
|
|
||||||
* production,
|
|
||||||
* - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral`
|
|
||||||
* production,
|
|
||||||
* - a DocumentType's `internalSubset` contains `"]>"`, or
|
|
||||||
* - an EntityReference's `nodeName` is not a valid XML `Name` (XML 1.0 production [5]).
|
|
||||||
* Note: xmldom does not enforce `readonly` on DocumentType fields — direct property
|
|
||||||
* writes succeed and are covered by the serializer-level checks above.
|
|
||||||
* @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
|
|
||||||
* @see https://w3c.github.io/DOM-Parsing/#xml-serialization
|
|
||||||
* @see https://github.com/w3c/DOM-Parsing/issues/84
|
|
||||||
*/
|
|
||||||
serializeToString(node: Node, isHtml?: boolean, nodeFilter?: (node: Node) => Node | null | undefined, options?: XMLSerializerOptions): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Options {
|
|
||||||
locator?: any;
|
|
||||||
errorHandler?: ErrorHandlerFunction | ErrorHandlerObject | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorHandlerFunction {
|
|
||||||
(level: string, msg: any): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorHandlerObject {
|
|
||||||
warning?: ((msg: any) => any) | undefined;
|
|
||||||
error?: ((msg: any) => any) | undefined;
|
|
||||||
fatalError?: ((msg: any) => any) | undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
extends:
|
|
||||||
- 'plugin:es5/no-es2015'
|
|
||||||
-214
@@ -1,214 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
|
|
||||||
*
|
|
||||||
* Works with anything that has a `length` property and index access properties, including NodeList.
|
|
||||||
*
|
|
||||||
* @template {unknown} T
|
|
||||||
* @param {Array<T> | ({length:number, [number]: T})} list
|
|
||||||
* @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
|
|
||||||
* @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
|
|
||||||
* allows injecting a custom implementation in tests
|
|
||||||
* @returns {T | undefined}
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
|
||||||
* @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
|
|
||||||
*/
|
|
||||||
function find(list, predicate, ac) {
|
|
||||||
if (ac === undefined) {
|
|
||||||
ac = Array.prototype;
|
|
||||||
}
|
|
||||||
if (list && typeof ac.find === 'function') {
|
|
||||||
return ac.find.call(list, predicate);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(list, i)) {
|
|
||||||
var item = list[i];
|
|
||||||
if (predicate.call(undefined, item, i, list)) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Shallow freezes" an object to render it immutable.
|
|
||||||
* Uses `Object.freeze` if available,
|
|
||||||
* otherwise the immutability is only in the type.
|
|
||||||
*
|
|
||||||
* Is used to create "enum like" objects.
|
|
||||||
*
|
|
||||||
* @template T
|
|
||||||
* @param {T} object the object to freeze
|
|
||||||
* @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
|
|
||||||
* allows to inject custom object constructor for tests
|
|
||||||
* @returns {Readonly<T>}
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
|
|
||||||
*/
|
|
||||||
function freeze(object, oc) {
|
|
||||||
if (oc === undefined) {
|
|
||||||
oc = Object
|
|
||||||
}
|
|
||||||
return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since we can not rely on `Object.assign` we provide a simplified version
|
|
||||||
* that is sufficient for our needs.
|
|
||||||
*
|
|
||||||
* @param {Object} target
|
|
||||||
* @param {Object | null | undefined} source
|
|
||||||
*
|
|
||||||
* @returns {Object} target
|
|
||||||
* @throws TypeError if target is not an object
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
|
||||||
* @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
|
|
||||||
*/
|
|
||||||
function assign(target, source) {
|
|
||||||
if (target === null || typeof target !== 'object') {
|
|
||||||
throw new TypeError('target is not an object')
|
|
||||||
}
|
|
||||||
for (var key in source) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
||||||
target[key] = source[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All mime types that are allowed as input to `DOMParser.parseFromString`
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
|
|
||||||
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
|
|
||||||
* @see DOMParser.prototype.parseFromString
|
|
||||||
*/
|
|
||||||
var MIME_TYPE = freeze({
|
|
||||||
/**
|
|
||||||
* `text/html`, the only mime type that triggers treating an XML document as HTML.
|
|
||||||
*
|
|
||||||
* @see DOMParser.SupportedType.isHTML
|
|
||||||
* @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
|
|
||||||
* @see https://en.wikipedia.org/wiki/HTML Wikipedia
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
|
|
||||||
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
|
|
||||||
*/
|
|
||||||
HTML: 'text/html',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to check a mime type if it indicates an HTML document
|
|
||||||
*
|
|
||||||
* @param {string} [value]
|
|
||||||
* @returns {boolean}
|
|
||||||
*
|
|
||||||
* @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
|
|
||||||
* @see https://en.wikipedia.org/wiki/HTML Wikipedia
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
|
|
||||||
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */
|
|
||||||
isHTML: function (value) {
|
|
||||||
return value === MIME_TYPE.HTML
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `application/xml`, the standard mime type for XML documents.
|
|
||||||
*
|
|
||||||
* @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
|
|
||||||
* @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
|
|
||||||
* @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
|
|
||||||
*/
|
|
||||||
XML_APPLICATION: 'application/xml',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `text/html`, an alias for `application/xml`.
|
|
||||||
*
|
|
||||||
* @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
|
|
||||||
* @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
|
|
||||||
* @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
|
|
||||||
*/
|
|
||||||
XML_TEXT: 'text/xml',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
|
|
||||||
* but is parsed as an XML document.
|
|
||||||
*
|
|
||||||
* @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
|
|
||||||
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
|
|
||||||
* @see https://en.wikipedia.org/wiki/XHTML Wikipedia
|
|
||||||
*/
|
|
||||||
XML_XHTML_APPLICATION: 'application/xhtml+xml',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `image/svg+xml`,
|
|
||||||
*
|
|
||||||
* @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
|
|
||||||
* @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
|
|
||||||
* @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
|
|
||||||
*/
|
|
||||||
XML_SVG_IMAGE: 'image/svg+xml',
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespaces that are used in this code base.
|
|
||||||
*
|
|
||||||
* @see http://www.w3.org/TR/REC-xml-names
|
|
||||||
*/
|
|
||||||
var NAMESPACE = freeze({
|
|
||||||
/**
|
|
||||||
* The XHTML namespace.
|
|
||||||
*
|
|
||||||
* @see http://www.w3.org/1999/xhtml
|
|
||||||
*/
|
|
||||||
HTML: 'http://www.w3.org/1999/xhtml',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if `uri` equals `NAMESPACE.HTML`.
|
|
||||||
*
|
|
||||||
* @param {string} [uri]
|
|
||||||
*
|
|
||||||
* @see NAMESPACE.HTML
|
|
||||||
*/
|
|
||||||
isHTML: function (uri) {
|
|
||||||
return uri === NAMESPACE.HTML
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The SVG namespace.
|
|
||||||
*
|
|
||||||
* @see http://www.w3.org/2000/svg
|
|
||||||
*/
|
|
||||||
SVG: 'http://www.w3.org/2000/svg',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `xml:` namespace.
|
|
||||||
*
|
|
||||||
* @see http://www.w3.org/XML/1998/namespace
|
|
||||||
*/
|
|
||||||
XML: 'http://www.w3.org/XML/1998/namespace',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `xmlns:` namespace
|
|
||||||
*
|
|
||||||
* @see https://www.w3.org/2000/xmlns/
|
|
||||||
*/
|
|
||||||
XMLNS: 'http://www.w3.org/2000/xmlns/',
|
|
||||||
})
|
|
||||||
|
|
||||||
//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
|
|
||||||
//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
|
|
||||||
//[5] Name ::= NameStartChar (NameChar)*
|
|
||||||
var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF
|
|
||||||
var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
|
|
||||||
var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$');
|
|
||||||
//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
|
|
||||||
|
|
||||||
exports.assign = assign;
|
|
||||||
exports.find = find;
|
|
||||||
exports.freeze = freeze;
|
|
||||||
exports.MIME_TYPE = MIME_TYPE;
|
|
||||||
exports.NAMESPACE = NAMESPACE;
|
|
||||||
exports.nameStartChar = nameStartChar;
|
|
||||||
exports.nameChar = nameChar;
|
|
||||||
exports.tagNamePattern = tagNamePattern;
|
|
||||||
-322
@@ -1,322 +0,0 @@
|
|||||||
var conventions = require("./conventions");
|
|
||||||
var dom = require('./dom')
|
|
||||||
var entities = require('./entities');
|
|
||||||
var sax = require('./sax');
|
|
||||||
|
|
||||||
var DOMImplementation = dom.DOMImplementation;
|
|
||||||
|
|
||||||
var NAMESPACE = conventions.NAMESPACE;
|
|
||||||
|
|
||||||
var ParseError = sax.ParseError;
|
|
||||||
var XMLReader = sax.XMLReader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
|
|
||||||
*
|
|
||||||
* > XML parsed entities are often stored in computer files which,
|
|
||||||
* > for editing convenience, are organized into lines.
|
|
||||||
* > These lines are typically separated by some combination
|
|
||||||
* > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
|
|
||||||
* >
|
|
||||||
* > To simplify the tasks of applications, the XML processor must behave
|
|
||||||
* > as if it normalized all line breaks in external parsed entities (including the document entity)
|
|
||||||
* > on input, before parsing, by translating all of the following to a single #xA character:
|
|
||||||
* >
|
|
||||||
* > 1. the two-character sequence #xD #xA
|
|
||||||
* > 2. the two-character sequence #xD #x85
|
|
||||||
* > 3. the single character #x85
|
|
||||||
* > 4. the single character #x2028
|
|
||||||
* > 5. any #xD character that is not immediately followed by #xA or #x85.
|
|
||||||
*
|
|
||||||
* @param {string} input
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function normalizeLineEndings(input) {
|
|
||||||
return input
|
|
||||||
.replace(/\r[\n\u0085]/g, '\n')
|
|
||||||
.replace(/[\r\u0085\u2028]/g, '\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef Locator
|
|
||||||
* @property {number} [columnNumber]
|
|
||||||
* @property {number} [lineNumber]
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef DOMParserOptions
|
|
||||||
* @property {DOMHandler} [domBuilder]
|
|
||||||
* @property {Function} [errorHandler]
|
|
||||||
* @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
|
|
||||||
* defaults to `normalizeLineEndings`
|
|
||||||
* @property {Locator} [locator]
|
|
||||||
* @property {Record<string, string>} [xmlns]
|
|
||||||
*
|
|
||||||
* @see normalizeLineEndings
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DOMParser interface provides the ability to parse XML or HTML source code
|
|
||||||
* from a string into a DOM `Document`.
|
|
||||||
*
|
|
||||||
* _xmldom is different from the spec in that it allows an `options` parameter,
|
|
||||||
* to override the default behavior._
|
|
||||||
*
|
|
||||||
* @param {DOMParserOptions} [options]
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
|
|
||||||
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
|
|
||||||
*/
|
|
||||||
function DOMParser(options){
|
|
||||||
this.options = options ||{locator:{}};
|
|
||||||
}
|
|
||||||
|
|
||||||
DOMParser.prototype.parseFromString = function(source,mimeType){
|
|
||||||
var options = this.options;
|
|
||||||
var sax = new XMLReader();
|
|
||||||
var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler
|
|
||||||
var errorHandler = options.errorHandler;
|
|
||||||
var locator = options.locator;
|
|
||||||
var defaultNSMap = options.xmlns||{};
|
|
||||||
var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1;
|
|
||||||
var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
|
|
||||||
if(locator){
|
|
||||||
domBuilder.setDocumentLocator(locator)
|
|
||||||
}
|
|
||||||
|
|
||||||
sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator);
|
|
||||||
sax.domBuilder = options.domBuilder || domBuilder;
|
|
||||||
if(isHTML){
|
|
||||||
defaultNSMap[''] = NAMESPACE.HTML;
|
|
||||||
}
|
|
||||||
defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
|
|
||||||
var normalize = options.normalizeLineEndings || normalizeLineEndings;
|
|
||||||
if (source && typeof source === 'string') {
|
|
||||||
sax.parse(
|
|
||||||
normalize(source),
|
|
||||||
defaultNSMap,
|
|
||||||
entityMap
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
sax.errorHandler.error('invalid doc source')
|
|
||||||
}
|
|
||||||
return domBuilder.doc;
|
|
||||||
}
|
|
||||||
function buildErrorHandler(errorImpl,domBuilder,locator){
|
|
||||||
if(!errorImpl){
|
|
||||||
if(domBuilder instanceof DOMHandler){
|
|
||||||
return domBuilder;
|
|
||||||
}
|
|
||||||
errorImpl = domBuilder ;
|
|
||||||
}
|
|
||||||
var errorHandler = {}
|
|
||||||
var isCallback = errorImpl instanceof Function;
|
|
||||||
locator = locator||{}
|
|
||||||
function build(key){
|
|
||||||
var fn = errorImpl[key];
|
|
||||||
if(!fn && isCallback){
|
|
||||||
fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl;
|
|
||||||
}
|
|
||||||
errorHandler[key] = fn && function(msg){
|
|
||||||
fn('[xmldom '+key+']\t'+msg+_locator(locator));
|
|
||||||
}||function(){};
|
|
||||||
}
|
|
||||||
build('warning');
|
|
||||||
build('error');
|
|
||||||
build('fatalError');
|
|
||||||
return errorHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log('#\n\n\n\n\n\n\n####')
|
|
||||||
/**
|
|
||||||
* +ContentHandler+ErrorHandler
|
|
||||||
* +LexicalHandler+EntityResolver2
|
|
||||||
* -DeclHandler-DTDHandler
|
|
||||||
*
|
|
||||||
* DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
|
|
||||||
* DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
|
|
||||||
*/
|
|
||||||
function DOMHandler() {
|
|
||||||
this.cdata = false;
|
|
||||||
}
|
|
||||||
function position(locator,node){
|
|
||||||
node.lineNumber = locator.lineNumber;
|
|
||||||
node.columnNumber = locator.columnNumber;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @see org.xml.sax.ContentHandler#startDocument
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
|
|
||||||
*/
|
|
||||||
DOMHandler.prototype = {
|
|
||||||
startDocument : function() {
|
|
||||||
this.doc = new DOMImplementation().createDocument(null, null, null);
|
|
||||||
if (this.locator) {
|
|
||||||
this.doc.documentURI = this.locator.systemId;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startElement:function(namespaceURI, localName, qName, attrs) {
|
|
||||||
var doc = this.doc;
|
|
||||||
var el = doc.createElementNS(namespaceURI, qName||localName);
|
|
||||||
var len = attrs.length;
|
|
||||||
appendElement(this, el);
|
|
||||||
this.currentElement = el;
|
|
||||||
|
|
||||||
this.locator && position(this.locator,el)
|
|
||||||
for (var i = 0 ; i < len; i++) {
|
|
||||||
var namespaceURI = attrs.getURI(i);
|
|
||||||
var value = attrs.getValue(i);
|
|
||||||
var qName = attrs.getQName(i);
|
|
||||||
var attr = doc.createAttributeNS(namespaceURI, qName);
|
|
||||||
this.locator &&position(attrs.getLocator(i),attr);
|
|
||||||
attr.value = attr.nodeValue = value;
|
|
||||||
el.setAttributeNode(attr)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
endElement:function(namespaceURI, localName, qName) {
|
|
||||||
var current = this.currentElement
|
|
||||||
var tagName = current.tagName;
|
|
||||||
this.currentElement = current.parentNode;
|
|
||||||
},
|
|
||||||
startPrefixMapping:function(prefix, uri) {
|
|
||||||
},
|
|
||||||
endPrefixMapping:function(prefix) {
|
|
||||||
},
|
|
||||||
processingInstruction:function(target, data) {
|
|
||||||
var ins = this.doc.createProcessingInstruction(target, data);
|
|
||||||
this.locator && position(this.locator,ins)
|
|
||||||
appendElement(this, ins);
|
|
||||||
},
|
|
||||||
ignorableWhitespace:function(ch, start, length) {
|
|
||||||
},
|
|
||||||
characters:function(chars, start, length) {
|
|
||||||
chars = _toString.apply(this,arguments)
|
|
||||||
//console.log(chars)
|
|
||||||
if(chars){
|
|
||||||
if (this.cdata) {
|
|
||||||
var charNode = this.doc.createCDATASection(chars);
|
|
||||||
} else {
|
|
||||||
var charNode = this.doc.createTextNode(chars);
|
|
||||||
}
|
|
||||||
if(this.currentElement){
|
|
||||||
this.currentElement.appendChild(charNode);
|
|
||||||
}else if(/^\s*$/.test(chars)){
|
|
||||||
this.doc.appendChild(charNode);
|
|
||||||
//process xml
|
|
||||||
}
|
|
||||||
this.locator && position(this.locator,charNode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skippedEntity:function(name) {
|
|
||||||
},
|
|
||||||
endDocument:function() {
|
|
||||||
this.doc.normalize();
|
|
||||||
},
|
|
||||||
setDocumentLocator:function (locator) {
|
|
||||||
if(this.locator = locator){// && !('lineNumber' in locator)){
|
|
||||||
locator.lineNumber = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//LexicalHandler
|
|
||||||
comment:function(chars, start, length) {
|
|
||||||
chars = _toString.apply(this,arguments)
|
|
||||||
var comm = this.doc.createComment(chars);
|
|
||||||
this.locator && position(this.locator,comm)
|
|
||||||
appendElement(this, comm);
|
|
||||||
},
|
|
||||||
|
|
||||||
startCDATA:function() {
|
|
||||||
//used in characters() methods
|
|
||||||
this.cdata = true;
|
|
||||||
},
|
|
||||||
endCDATA:function() {
|
|
||||||
this.cdata = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
startDTD:function(name, publicId, systemId) {
|
|
||||||
var impl = this.doc.implementation;
|
|
||||||
if (impl && impl.createDocumentType) {
|
|
||||||
var dt = impl.createDocumentType(name, publicId, systemId);
|
|
||||||
this.locator && position(this.locator,dt)
|
|
||||||
appendElement(this, dt);
|
|
||||||
this.doc.doctype = dt;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @see org.xml.sax.ErrorHandler
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
|
|
||||||
*/
|
|
||||||
warning:function(error) {
|
|
||||||
console.warn('[xmldom warning]\t'+error,_locator(this.locator));
|
|
||||||
},
|
|
||||||
error:function(error) {
|
|
||||||
console.error('[xmldom error]\t'+error,_locator(this.locator));
|
|
||||||
},
|
|
||||||
fatalError:function(error) {
|
|
||||||
throw new ParseError(error, this.locator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function _locator(l){
|
|
||||||
if(l){
|
|
||||||
return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function _toString(chars,start,length){
|
|
||||||
if(typeof chars == 'string'){
|
|
||||||
return chars.substr(start,length)
|
|
||||||
}else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
|
|
||||||
if(chars.length >= start+length || start){
|
|
||||||
return new java.lang.String(chars,start,length)+'';
|
|
||||||
}
|
|
||||||
return chars;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
|
|
||||||
* used method of org.xml.sax.ext.LexicalHandler:
|
|
||||||
* #comment(chars, start, length)
|
|
||||||
* #startCDATA()
|
|
||||||
* #endCDATA()
|
|
||||||
* #startDTD(name, publicId, systemId)
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* IGNORED method of org.xml.sax.ext.LexicalHandler:
|
|
||||||
* #endDTD()
|
|
||||||
* #startEntity(name)
|
|
||||||
* #endEntity(name)
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
|
|
||||||
* IGNORED method of org.xml.sax.ext.DeclHandler
|
|
||||||
* #attributeDecl(eName, aName, type, mode, value)
|
|
||||||
* #elementDecl(name, model)
|
|
||||||
* #externalEntityDecl(name, publicId, systemId)
|
|
||||||
* #internalEntityDecl(name, value)
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
|
|
||||||
* IGNORED method of org.xml.sax.EntityResolver2
|
|
||||||
* #resolveEntity(String name,String publicId,String baseURI,String systemId)
|
|
||||||
* #resolveEntity(publicId, systemId)
|
|
||||||
* #getExternalSubset(name, baseURI)
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
|
|
||||||
* IGNORED method of org.xml.sax.DTDHandler
|
|
||||||
* #notationDecl(name, publicId, systemId) {};
|
|
||||||
* #unparsedEntityDecl(name, publicId, systemId, notationName) {};
|
|
||||||
*/
|
|
||||||
"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){
|
|
||||||
DOMHandler.prototype[key] = function(){return null}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */
|
|
||||||
function appendElement (hander,node) {
|
|
||||||
if (!hander.currentElement) {
|
|
||||||
hander.doc.appendChild(node);
|
|
||||||
} else {
|
|
||||||
hander.currentElement.appendChild(node);
|
|
||||||
}
|
|
||||||
}//appendChild and setAttributeNS are preformance key
|
|
||||||
|
|
||||||
exports.__DOMHandler = DOMHandler;
|
|
||||||
exports.normalizeLineEndings = normalizeLineEndings;
|
|
||||||
exports.DOMParser = DOMParser;
|
|
||||||
-2266
File diff suppressed because it is too large.
Load diff
-2166
File diff suppressed because it is too large.
Load diff
-4
@@ -1,4 +0,0 @@
|
|||||||
var dom = require('./dom')
|
|
||||||
exports.DOMImplementation = dom.DOMImplementation
|
|
||||||
exports.XMLSerializer = dom.XMLSerializer
|
|
||||||
exports.DOMParser = require('./dom-parser').DOMParser
|
|
||||||
-683
@@ -1,683 +0,0 @@
|
|||||||
var NAMESPACE = require("./conventions").NAMESPACE;
|
|
||||||
var tagNamePattern = require("./conventions").tagNamePattern;
|
|
||||||
|
|
||||||
//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
|
|
||||||
|
|
||||||
//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
|
|
||||||
//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
|
|
||||||
var S_TAG = 0;//tag name offerring
|
|
||||||
var S_ATTR = 1;//attr name offerring
|
|
||||||
var S_ATTR_SPACE=2;//attr name end and space offer
|
|
||||||
var S_EQ = 3;//=space?
|
|
||||||
var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only)
|
|
||||||
var S_ATTR_END = 5;//attr value end and no space(quot end)
|
|
||||||
var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer)
|
|
||||||
var S_TAG_CLOSE = 7;//closed el<el />
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an error that will not be caught by XMLReader aka the SAX parser.
|
|
||||||
*
|
|
||||||
* @param {string} message
|
|
||||||
* @param {any?} locator Optional, can provide details about the location in the source
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ParseError(message, locator) {
|
|
||||||
this.message = message
|
|
||||||
this.locator = locator
|
|
||||||
if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError);
|
|
||||||
}
|
|
||||||
ParseError.prototype = new Error();
|
|
||||||
ParseError.prototype.name = ParseError.name
|
|
||||||
|
|
||||||
function XMLReader(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
XMLReader.prototype = {
|
|
||||||
parse:function(source,defaultNSMap,entityMap){
|
|
||||||
var domBuilder = this.domBuilder;
|
|
||||||
domBuilder.startDocument();
|
|
||||||
_copy(defaultNSMap ,defaultNSMap = {})
|
|
||||||
parse(source,defaultNSMap,entityMap,
|
|
||||||
domBuilder,this.errorHandler);
|
|
||||||
domBuilder.endDocument();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){
|
|
||||||
function fixedFromCharCode(code) {
|
|
||||||
// String.prototype.fromCharCode does not supports
|
|
||||||
// > 2 bytes unicode chars directly
|
|
||||||
if (code > 0xffff) {
|
|
||||||
code -= 0x10000;
|
|
||||||
var surrogate1 = 0xd800 + (code >> 10)
|
|
||||||
, surrogate2 = 0xdc00 + (code & 0x3ff);
|
|
||||||
|
|
||||||
return String.fromCharCode(surrogate1, surrogate2);
|
|
||||||
} else {
|
|
||||||
return String.fromCharCode(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function entityReplacer(a){
|
|
||||||
var k = a.slice(1,-1);
|
|
||||||
if (Object.hasOwnProperty.call(entityMap, k)) {
|
|
||||||
return entityMap[k];
|
|
||||||
}else if(k.charAt(0) === '#'){
|
|
||||||
return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x')))
|
|
||||||
}else{
|
|
||||||
errorHandler.error('entity not found:'+a);
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function appendText(end){//has some bugs
|
|
||||||
if(end>start){
|
|
||||||
var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
|
|
||||||
locator&&position(start);
|
|
||||||
domBuilder.characters(xt,0,end-start);
|
|
||||||
start = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function position(p,m){
|
|
||||||
while(p>=lineEnd && (m = linePattern.exec(source))){
|
|
||||||
lineStart = m.index;
|
|
||||||
lineEnd = lineStart + m[0].length;
|
|
||||||
locator.lineNumber++;
|
|
||||||
//console.log('line++:',locator,startPos,endPos)
|
|
||||||
}
|
|
||||||
locator.columnNumber = p-lineStart+1;
|
|
||||||
}
|
|
||||||
var lineStart = 0;
|
|
||||||
var lineEnd = 0;
|
|
||||||
var linePattern = /.*(?:\r\n?|\n)|.*$/g
|
|
||||||
var locator = domBuilder.locator;
|
|
||||||
|
|
||||||
var parseStack = [{currentNSMap:defaultNSMapCopy}]
|
|
||||||
var closeMap = {};
|
|
||||||
var start = 0;
|
|
||||||
while(true){
|
|
||||||
try{
|
|
||||||
var tagStart = source.indexOf('<',start);
|
|
||||||
if(tagStart<0){
|
|
||||||
if(!source.substr(start).match(/^\s*$/)){
|
|
||||||
var doc = domBuilder.doc;
|
|
||||||
var text = doc.createTextNode(source.substr(start));
|
|
||||||
doc.appendChild(text);
|
|
||||||
domBuilder.currentElement = text;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(tagStart>start){
|
|
||||||
appendText(tagStart);
|
|
||||||
}
|
|
||||||
switch(source.charAt(tagStart+1)){
|
|
||||||
case '/':
|
|
||||||
var end = source.indexOf('>',tagStart+3);
|
|
||||||
// Anchored trailing-whitespace trim. The former `/[ \t\n\r]+$/g` backtracked
|
|
||||||
// quadratically on a name shaped `whitespace-run + non-whitespace char`
|
|
||||||
// (an end tag `</ … x>`): from every start position it extended `[ws]+`
|
|
||||||
// to the end and then failed the `$` anchor. This anchored form matches the
|
|
||||||
// whole string once and captures everything up to the last non-whitespace
|
|
||||||
// character, so it trims in linear time with byte-identical output.
|
|
||||||
var tagName = source.substring(tagStart + 2, end).replace(/^([\s\S]*?[^ \t\n\r])?[ \t\n\r]*$/, '$1');
|
|
||||||
var config = parseStack.pop();
|
|
||||||
if(end<0){
|
|
||||||
|
|
||||||
tagName = source.substring(tagStart+2).replace(/[\s<].*/,'');
|
|
||||||
errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName);
|
|
||||||
end = tagStart+1+tagName.length;
|
|
||||||
}else if(tagName.match(/\s</)){
|
|
||||||
tagName = tagName.replace(/[\s<].*/,'');
|
|
||||||
errorHandler.error("end tag name: "+tagName+' maybe not complete');
|
|
||||||
end = tagStart+1+tagName.length;
|
|
||||||
}else if(/[ \t\n\r]/.test(tagName) && tagNamePattern.test(tagName.split(/[ \t\n\r]/)[0])){
|
|
||||||
// The XML `ETag` production is `'</' Name S? '>'`: only optional whitespace may follow
|
|
||||||
// the `Name`. A valid `Name` followed by whitespace and non-whitespace residue (e.g.
|
|
||||||
// `</a\nbogus>` or `</a bogus>`) is not well-formed, but historically it was silently
|
|
||||||
// accepted (the malformed end tag ignored, the residue dropped, the element left on the
|
|
||||||
// stack). Report it as a recoverable `error` (which a custom errorHandler may escalate to
|
|
||||||
// fatal) while keeping the existing recovery so the parsed DOM stays byte-identical.
|
|
||||||
errorHandler.error('end tag name is followed by whitespace and trailing content: "'+tagName+'"');
|
|
||||||
}
|
|
||||||
var localNSMap = config.localNSMap;
|
|
||||||
var endMatch = config.tagName == tagName;
|
|
||||||
var endIgnoreCaseMach = endMatch || config.tagName&&config.tagName.toLowerCase() == tagName.toLowerCase()
|
|
||||||
if(endIgnoreCaseMach){
|
|
||||||
domBuilder.endElement(config.uri,config.localName,tagName);
|
|
||||||
if(localNSMap){
|
|
||||||
for (var prefix in localNSMap) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
|
|
||||||
domBuilder.endPrefixMapping(prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!endMatch){
|
|
||||||
errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); // No known test case
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
parseStack.push(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
end++;
|
|
||||||
break;
|
|
||||||
// end elment
|
|
||||||
case '?':// <?...?>
|
|
||||||
locator&&position(tagStart);
|
|
||||||
end = parseInstruction(source,tagStart,domBuilder);
|
|
||||||
break;
|
|
||||||
case '!':// <!doctype,<![CDATA,<!--
|
|
||||||
locator&&position(tagStart);
|
|
||||||
end = parseDCC(source,tagStart,domBuilder,errorHandler);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
locator&&position(tagStart);
|
|
||||||
var el = new ElementAttributes();
|
|
||||||
var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
|
|
||||||
//elStartEnd
|
|
||||||
var end = parseElementStartPart(source,tagStart,el,currentNSMap,entityReplacer,errorHandler);
|
|
||||||
var len = el.length;
|
|
||||||
|
|
||||||
|
|
||||||
if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
|
|
||||||
el.closed = true;
|
|
||||||
if(!entityMap.nbsp){
|
|
||||||
errorHandler.warning('unclosed xml attribute');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(locator && len){
|
|
||||||
var locator2 = copyLocator(locator,{});
|
|
||||||
//try{//attribute position fixed
|
|
||||||
for(var i = 0;i<len;i++){
|
|
||||||
var a = el[i];
|
|
||||||
position(a.offset);
|
|
||||||
a.locator = copyLocator(locator,{});
|
|
||||||
}
|
|
||||||
domBuilder.locator = locator2
|
|
||||||
if(appendElement(el,domBuilder,currentNSMap)){
|
|
||||||
parseStack.push(el)
|
|
||||||
}
|
|
||||||
domBuilder.locator = locator;
|
|
||||||
}else{
|
|
||||||
if(appendElement(el,domBuilder,currentNSMap)){
|
|
||||||
parseStack.push(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NAMESPACE.isHTML(el.uri) && !el.closed) {
|
|
||||||
end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder)
|
|
||||||
} else {
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
if (e instanceof ParseError) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
errorHandler.error('element parse error: '+e)
|
|
||||||
end = -1;
|
|
||||||
}
|
|
||||||
if(end>start){
|
|
||||||
start = end;
|
|
||||||
}else{
|
|
||||||
//TODO: 这里有可能sax回退,有位置错误风险
|
|
||||||
appendText(Math.max(tagStart,start)+1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function copyLocator(f,t){
|
|
||||||
t.lineNumber = f.lineNumber;
|
|
||||||
t.columnNumber = f.columnNumber;
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
|
|
||||||
* @return end of the elementStartPart(end of elementEndPart for selfClosed el)
|
|
||||||
*/
|
|
||||||
function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} qname
|
|
||||||
* @param {string} value
|
|
||||||
* @param {number} startIndex
|
|
||||||
*/
|
|
||||||
function addAttribute(qname, value, startIndex) {
|
|
||||||
if (el.attributeNames.hasOwnProperty(qname)) {
|
|
||||||
errorHandler.fatalError('Attribute ' + qname + ' redefined')
|
|
||||||
}
|
|
||||||
el.addValue(
|
|
||||||
qname,
|
|
||||||
// @see https://www.w3.org/TR/xml/#AVNormalize
|
|
||||||
// since the xmldom sax parser does not "interpret" DTD the following is not implemented:
|
|
||||||
// - recursive replacement of (DTD) entity references
|
|
||||||
// - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
|
|
||||||
value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer),
|
|
||||||
startIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var attrName;
|
|
||||||
var value;
|
|
||||||
var p = ++start;
|
|
||||||
var s = S_TAG;//status
|
|
||||||
while(true){
|
|
||||||
var c = source.charAt(p);
|
|
||||||
if (s === S_TAG && c === '<') {
|
|
||||||
// A `<` can never occur inside a tag name. Without this guard the scan runs
|
|
||||||
// on to the next `>` (or EOF) before the tag name is rejected, so a document
|
|
||||||
// with many `<` inside a malformed tag makes each one-character recovery step
|
|
||||||
// re-scan to the distant `>` — O(n^2). Stopping at the `<` keeps each recovery
|
|
||||||
// step bounded. The candidate scanned so far is reported raw, consistent with
|
|
||||||
// the sibling invalid-tag-name throw below.
|
|
||||||
throw new Error('unexpected < in tag name: ' + source.slice(start, p));
|
|
||||||
}
|
|
||||||
switch(c){
|
|
||||||
case '=':
|
|
||||||
if(s === S_ATTR){//attrName
|
|
||||||
attrName = source.slice(start,p);
|
|
||||||
s = S_EQ;
|
|
||||||
}else if(s === S_ATTR_SPACE){
|
|
||||||
s = S_EQ;
|
|
||||||
}else{
|
|
||||||
//fatalError: equal must after attrName or space after attrName
|
|
||||||
throw new Error('attribute equal must after attrName'); // No known test case
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '\'':
|
|
||||||
case '"':
|
|
||||||
if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
|
|
||||||
){//equal
|
|
||||||
if(s === S_ATTR){
|
|
||||||
errorHandler.warning('attribute value must after "="')
|
|
||||||
attrName = source.slice(start,p)
|
|
||||||
}
|
|
||||||
start = p+1;
|
|
||||||
p = source.indexOf(c,start)
|
|
||||||
if(p>0){
|
|
||||||
value = source.slice(start, p);
|
|
||||||
addAttribute(attrName, value, start-1);
|
|
||||||
s = S_ATTR_END;
|
|
||||||
}else{
|
|
||||||
//fatalError: no end quot match
|
|
||||||
throw new Error('attribute value no end \''+c+'\' match');
|
|
||||||
}
|
|
||||||
}else if(s == S_ATTR_NOQUOT_VALUE){
|
|
||||||
value = source.slice(start, p);
|
|
||||||
addAttribute(attrName, value, start);
|
|
||||||
errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!');
|
|
||||||
start = p+1;
|
|
||||||
s = S_ATTR_END
|
|
||||||
}else{
|
|
||||||
//fatalError: no equal before
|
|
||||||
throw new Error('attribute value must after "="'); // No known test case
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
switch(s){
|
|
||||||
case S_TAG:
|
|
||||||
el.setTagName(source.slice(start,p));
|
|
||||||
case S_ATTR_END:
|
|
||||||
case S_TAG_SPACE:
|
|
||||||
case S_TAG_CLOSE:
|
|
||||||
s =S_TAG_CLOSE;
|
|
||||||
el.closed = true;
|
|
||||||
case S_ATTR_NOQUOT_VALUE:
|
|
||||||
case S_ATTR:
|
|
||||||
break;
|
|
||||||
case S_ATTR_SPACE:
|
|
||||||
el.closed = true;
|
|
||||||
break;
|
|
||||||
//case S_EQ:
|
|
||||||
default:
|
|
||||||
throw new Error("attribute invalid close char('/')") // No known test case
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ''://end document
|
|
||||||
errorHandler.error('unexpected end of input');
|
|
||||||
if(s == S_TAG){
|
|
||||||
el.setTagName(source.slice(start,p));
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
case '>':
|
|
||||||
switch(s){
|
|
||||||
case S_TAG:
|
|
||||||
el.setTagName(source.slice(start,p));
|
|
||||||
case S_ATTR_END:
|
|
||||||
case S_TAG_SPACE:
|
|
||||||
case S_TAG_CLOSE:
|
|
||||||
break;//normal
|
|
||||||
case S_ATTR_NOQUOT_VALUE://Compatible state
|
|
||||||
case S_ATTR:
|
|
||||||
value = source.slice(start,p);
|
|
||||||
if(value.slice(-1) === '/'){
|
|
||||||
el.closed = true;
|
|
||||||
value = value.slice(0,-1)
|
|
||||||
}
|
|
||||||
case S_ATTR_SPACE:
|
|
||||||
if(s === S_ATTR_SPACE){
|
|
||||||
value = attrName;
|
|
||||||
}
|
|
||||||
if(s == S_ATTR_NOQUOT_VALUE){
|
|
||||||
errorHandler.warning('attribute "'+value+'" missed quot(")!');
|
|
||||||
addAttribute(attrName, value, start)
|
|
||||||
}else{
|
|
||||||
if(!NAMESPACE.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)){
|
|
||||||
errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!')
|
|
||||||
}
|
|
||||||
addAttribute(value, value, start)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case S_EQ:
|
|
||||||
throw new Error('attribute value missed!!');
|
|
||||||
}
|
|
||||||
// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
|
|
||||||
return p;
|
|
||||||
/*xml space '\x20' | #x9 | #xD | #xA; */
|
|
||||||
case '\u0080':
|
|
||||||
c = ' ';
|
|
||||||
default:
|
|
||||||
if(c<= ' '){//space
|
|
||||||
switch(s){
|
|
||||||
case S_TAG:
|
|
||||||
el.setTagName(source.slice(start,p));//tagName
|
|
||||||
s = S_TAG_SPACE;
|
|
||||||
break;
|
|
||||||
case S_ATTR:
|
|
||||||
attrName = source.slice(start,p)
|
|
||||||
s = S_ATTR_SPACE;
|
|
||||||
break;
|
|
||||||
case S_ATTR_NOQUOT_VALUE:
|
|
||||||
var value = source.slice(start, p);
|
|
||||||
errorHandler.warning('attribute "'+value+'" missed quot(")!!');
|
|
||||||
addAttribute(attrName, value, start)
|
|
||||||
case S_ATTR_END:
|
|
||||||
s = S_TAG_SPACE;
|
|
||||||
break;
|
|
||||||
//case S_TAG_SPACE:
|
|
||||||
//case S_EQ:
|
|
||||||
//case S_ATTR_SPACE:
|
|
||||||
// void();break;
|
|
||||||
//case S_TAG_CLOSE:
|
|
||||||
//ignore warning
|
|
||||||
}
|
|
||||||
}else{//not space
|
|
||||||
//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
|
|
||||||
//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
|
|
||||||
switch(s){
|
|
||||||
//case S_TAG:void();break;
|
|
||||||
//case S_ATTR:void();break;
|
|
||||||
//case S_ATTR_NOQUOT_VALUE:void();break;
|
|
||||||
case S_ATTR_SPACE:
|
|
||||||
var tagName = el.tagName;
|
|
||||||
if (!NAMESPACE.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
|
|
||||||
errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!')
|
|
||||||
}
|
|
||||||
addAttribute(attrName, attrName, start);
|
|
||||||
start = p;
|
|
||||||
s = S_ATTR;
|
|
||||||
break;
|
|
||||||
case S_ATTR_END:
|
|
||||||
errorHandler.warning('attribute space is required"'+attrName+'"!!')
|
|
||||||
case S_TAG_SPACE:
|
|
||||||
s = S_ATTR;
|
|
||||||
start = p;
|
|
||||||
break;
|
|
||||||
case S_EQ:
|
|
||||||
s = S_ATTR_NOQUOT_VALUE;
|
|
||||||
start = p;
|
|
||||||
break;
|
|
||||||
case S_TAG_CLOSE:
|
|
||||||
throw new Error("elements closed character '/' and '>' must be connected to");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}//end outer switch
|
|
||||||
//console.log('p++',p)
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return true if has new namespace define
|
|
||||||
*/
|
|
||||||
function appendElement(el,domBuilder,currentNSMap){
|
|
||||||
var tagName = el.tagName;
|
|
||||||
var localNSMap = null;
|
|
||||||
//var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
|
|
||||||
var i = el.length;
|
|
||||||
while(i--){
|
|
||||||
var a = el[i];
|
|
||||||
var qName = a.qName;
|
|
||||||
var value = a.value;
|
|
||||||
var nsp = qName.indexOf(':');
|
|
||||||
if(nsp>0){
|
|
||||||
var prefix = a.prefix = qName.slice(0,nsp);
|
|
||||||
var localName = qName.slice(nsp+1);
|
|
||||||
var nsPrefix = prefix === 'xmlns' && localName
|
|
||||||
}else{
|
|
||||||
localName = qName;
|
|
||||||
prefix = null
|
|
||||||
nsPrefix = qName === 'xmlns' && ''
|
|
||||||
}
|
|
||||||
//can not set prefix,because prefix !== ''
|
|
||||||
a.localName = localName ;
|
|
||||||
//prefix == null for no ns prefix attribute
|
|
||||||
if(nsPrefix !== false){//hack!!
|
|
||||||
if(localNSMap == null){
|
|
||||||
localNSMap = {}
|
|
||||||
// Derive the child scope's namespace map by prototype-chain inheritance
|
|
||||||
// instead of a flat copy: lookups inherit ancestor prefixes transparently,
|
|
||||||
// so a document nesting N scopes retains O(N) map entries rather than
|
|
||||||
// sum(1..N) = O(N^2). localNSMap stays a flat own-only record of the
|
|
||||||
// prefixes declared at THIS element, so own-property enumeration
|
|
||||||
// (endPrefixMapping below) still reports only local declarations.
|
|
||||||
currentNSMap = Object.create(currentNSMap)
|
|
||||||
}
|
|
||||||
currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
|
|
||||||
a.uri = NAMESPACE.XMLNS
|
|
||||||
domBuilder.startPrefixMapping(nsPrefix, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var i = el.length;
|
|
||||||
while(i--){
|
|
||||||
a = el[i];
|
|
||||||
var prefix = a.prefix;
|
|
||||||
if(prefix){//no prefix attribute has no namespace
|
|
||||||
if(prefix === 'xml'){
|
|
||||||
a.uri = NAMESPACE.XML;
|
|
||||||
}if(prefix !== 'xmlns'){
|
|
||||||
a.uri = currentNSMap[prefix || '']
|
|
||||||
|
|
||||||
//{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var nsp = tagName.indexOf(':');
|
|
||||||
if(nsp>0){
|
|
||||||
prefix = el.prefix = tagName.slice(0,nsp);
|
|
||||||
localName = el.localName = tagName.slice(nsp+1);
|
|
||||||
}else{
|
|
||||||
prefix = null;//important!!
|
|
||||||
localName = el.localName = tagName;
|
|
||||||
}
|
|
||||||
//no prefix element has default namespace
|
|
||||||
var ns = el.uri = currentNSMap[prefix || ''];
|
|
||||||
domBuilder.startElement(ns,localName,tagName,el);
|
|
||||||
//endPrefixMapping and startPrefixMapping have not any help for dom builder
|
|
||||||
//localNSMap = null
|
|
||||||
if(el.closed){
|
|
||||||
domBuilder.endElement(ns,localName,tagName);
|
|
||||||
if(localNSMap){
|
|
||||||
for (prefix in localNSMap) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
|
|
||||||
domBuilder.endPrefixMapping(prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
el.currentNSMap = currentNSMap;
|
|
||||||
el.localNSMap = localNSMap;
|
|
||||||
//parseStack.push(el);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){
|
|
||||||
if(/^(?:script|textarea)$/i.test(tagName)){
|
|
||||||
var elEndStart = source.indexOf('</'+tagName+'>',elStartEnd);
|
|
||||||
var text = source.substring(elStartEnd+1,elEndStart);
|
|
||||||
if(/[&<]/.test(text)){
|
|
||||||
if(/^script$/i.test(tagName)){
|
|
||||||
//if(!/\]\]>/.test(text)){
|
|
||||||
//lexHandler.startCDATA();
|
|
||||||
domBuilder.characters(text,0,text.length);
|
|
||||||
//lexHandler.endCDATA();
|
|
||||||
return elEndStart;
|
|
||||||
//}
|
|
||||||
}//}else{//text area
|
|
||||||
text = text.replace(/&#?\w+;/g,entityReplacer);
|
|
||||||
domBuilder.characters(text,0,text.length);
|
|
||||||
return elEndStart;
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return elStartEnd+1;
|
|
||||||
}
|
|
||||||
function fixSelfClosed(source,elStartEnd,tagName,closeMap){
|
|
||||||
//if(tagName in closeMap){
|
|
||||||
var pos = closeMap[tagName];
|
|
||||||
if(pos == null){
|
|
||||||
//console.log(tagName)
|
|
||||||
pos = source.lastIndexOf('</'+tagName+'>')
|
|
||||||
if(pos<elStartEnd){//忘记闭合
|
|
||||||
pos = source.lastIndexOf('</'+tagName)
|
|
||||||
}
|
|
||||||
closeMap[tagName] =pos
|
|
||||||
}
|
|
||||||
return pos<elStartEnd;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _copy (source, target) {
|
|
||||||
for (var n in source) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(source, n)) {
|
|
||||||
target[n] = source[n];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
|
|
||||||
var next= source.charAt(start+2)
|
|
||||||
switch(next){
|
|
||||||
case '-':
|
|
||||||
if(source.charAt(start + 3) === '-'){
|
|
||||||
var end = source.indexOf('-->',start+4);
|
|
||||||
//append comment source.substring(4,end)//<!--
|
|
||||||
if(end>start){
|
|
||||||
domBuilder.comment(source,start+4,end-start-4);
|
|
||||||
return end+3;
|
|
||||||
}else{
|
|
||||||
errorHandler.error("Unclosed comment");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
//error
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if(source.substr(start+3,6) == 'CDATA['){
|
|
||||||
var end = source.indexOf(']]>',start+9);
|
|
||||||
domBuilder.startCDATA();
|
|
||||||
domBuilder.characters(source,start+9,end-start-9);
|
|
||||||
domBuilder.endCDATA()
|
|
||||||
return end+3;
|
|
||||||
}
|
|
||||||
//<!DOCTYPE
|
|
||||||
//startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
|
|
||||||
var matchs = split(source,start);
|
|
||||||
var len = matchs.length;
|
|
||||||
if(len>1 && /!doctype/i.test(matchs[0][0])){
|
|
||||||
var name = matchs[1][0];
|
|
||||||
var pubid = false;
|
|
||||||
var sysid = false;
|
|
||||||
if(len>3){
|
|
||||||
if(/^public$/i.test(matchs[2][0])){
|
|
||||||
pubid = matchs[3][0];
|
|
||||||
sysid = len>4 && matchs[4][0];
|
|
||||||
}else if(/^system$/i.test(matchs[2][0])){
|
|
||||||
sysid = matchs[3][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var lastMatch = matchs[len-1]
|
|
||||||
domBuilder.startDTD(name, pubid, sysid);
|
|
||||||
domBuilder.endDTD();
|
|
||||||
|
|
||||||
return lastMatch.index+lastMatch[0].length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function parseInstruction(source,start,domBuilder){
|
|
||||||
var end = source.indexOf('?>',start);
|
|
||||||
if(end){
|
|
||||||
var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)$/);
|
|
||||||
if(match){
|
|
||||||
var len = match[0].length;
|
|
||||||
domBuilder.processingInstruction(match[1], match[2]) ;
|
|
||||||
return end+2;
|
|
||||||
}else{//error
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ElementAttributes(){
|
|
||||||
this.attributeNames = {}
|
|
||||||
}
|
|
||||||
ElementAttributes.prototype = {
|
|
||||||
setTagName:function(tagName){
|
|
||||||
if(!tagNamePattern.test(tagName)){
|
|
||||||
throw new Error('invalid tagName:'+tagName)
|
|
||||||
}
|
|
||||||
this.tagName = tagName
|
|
||||||
},
|
|
||||||
addValue:function(qName, value, offset) {
|
|
||||||
if(!tagNamePattern.test(qName)){
|
|
||||||
throw new Error('invalid attribute:'+qName)
|
|
||||||
}
|
|
||||||
this.attributeNames[qName] = this.length;
|
|
||||||
this[this.length++] = {qName:qName,value:value,offset:offset}
|
|
||||||
},
|
|
||||||
length:0,
|
|
||||||
getLocalName:function(i){return this[i].localName},
|
|
||||||
getLocator:function(i){return this[i].locator},
|
|
||||||
getQName:function(i){return this[i].qName},
|
|
||||||
getURI:function(i){return this[i].uri},
|
|
||||||
getValue:function(i){return this[i].value}
|
|
||||||
// ,getIndex:function(uri, localName)){
|
|
||||||
// if(localName){
|
|
||||||
//
|
|
||||||
// }else{
|
|
||||||
// var qName = uri
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
|
|
||||||
// getType:function(uri,localName){}
|
|
||||||
// getType:function(i){},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function split(source,start){
|
|
||||||
var match;
|
|
||||||
var buf = [];
|
|
||||||
var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
|
|
||||||
reg.lastIndex = start;
|
|
||||||
reg.exec(source);//skip <
|
|
||||||
while(match = reg.exec(source)){
|
|
||||||
buf.push(match);
|
|
||||||
if(match[1])return buf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.XMLReader = XMLReader;
|
|
||||||
exports.ParseError = ParseError;
|
|
||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@xmldom/xmldom",
|
|
||||||
"version": "0.8.15",
|
|
||||||
"description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
|
|
||||||
"keywords": [
|
|
||||||
"w3c",
|
|
||||||
"dom",
|
|
||||||
"xml",
|
|
||||||
"parser",
|
|
||||||
"javascript",
|
|
||||||
"DOMParser",
|
|
||||||
"XMLSerializer",
|
|
||||||
"ponyfill"
|
|
||||||
],
|
|
||||||
"homepage": "https://github.com/xmldom/xmldom",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/xmldom/xmldom.git"
|
|
||||||
},
|
|
||||||
"main": "lib/index.js",
|
|
||||||
"types": "index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"CHANGELOG.md",
|
|
||||||
"LICENSE",
|
|
||||||
"readme.md",
|
|
||||||
"SECURITY.md",
|
|
||||||
"index.d.ts",
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"config": {
|
|
||||||
"test_stack_size": 256
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint lib test",
|
|
||||||
"format": "prettier --write test",
|
|
||||||
"format:check": "prettier --check test",
|
|
||||||
"changelog": "auto-changelog --unreleased-only",
|
|
||||||
"start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
|
|
||||||
"stryker": "stryker run",
|
|
||||||
"stryker:dry-run": "stryker run -m '' --reporters progress",
|
|
||||||
"test": "node --stack-size=$npm_package_config_test_stack_size ./node_modules/.bin/jest",
|
|
||||||
"testrelease": "npm test && eslint lib",
|
|
||||||
"version": "./changelog-has-version.sh",
|
|
||||||
"release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"@stryker-mutator/core": "5.6.1",
|
|
||||||
"auto-changelog": "2.4.0",
|
|
||||||
"eslint": "8.25.0",
|
|
||||||
"eslint-config-prettier": "8.5.0",
|
|
||||||
"eslint-plugin-es5": "1.5.0",
|
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
|
||||||
"get-stream": "6.0.1",
|
|
||||||
"jest": "27.5.1",
|
|
||||||
"nodemon": "2.0.20",
|
|
||||||
"np": "9.2.0",
|
|
||||||
"prettier": "2.7.1",
|
|
||||||
"xmltest": "1.5.0",
|
|
||||||
"yauzl": "2.10.0"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/xmldom/xmldom/issues"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"auto-changelog": {
|
|
||||||
"prepend": true,
|
|
||||||
"remote": "upstream",
|
|
||||||
"tagPrefix": "",
|
|
||||||
"template": "./auto-changelog.hbs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-356
@@ -1,356 +0,0 @@
|
|||||||
# @xmldom/xmldom
|
|
||||||
|
|
||||||
***Since version 0.7.0 this package is published to npm as [`@xmldom/xmldom`](https://www.npmjs.com/package/@xmldom/xmldom) and no longer as [`xmldom`](https://www.npmjs.com/package/xmldom), because [we are no longer able to publish `xmldom`](https://github.com/xmldom/xmldom/issues/271).***
|
|
||||||
*For better readability in the docs we will continue to talk about this library as "xmldom".*
|
|
||||||
|
|
||||||
[](https://github.com/xmldom/xmldom/blob/master/LICENSE)
|
|
||||||
[](https://www.npmjs.com/package/@xmldom/xmldom)
|
|
||||||
[](https://snyk.io/advisor/npm-package/@xmldom/xmldom)
|
|
||||||
[](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
||||||
[](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted)
|
|
||||||
[](https://dashboard.stryker-mutator.io/reports/github.com/xmldom/xmldom/master)
|
|
||||||
|
|
||||||
|
|
||||||
xmldom is a javascript [ponyfill](https://ponyfill.com/) to provide the following APIs [that are present in modern browsers](https://caniuse.com/xml-serializer) to other runtimes:
|
|
||||||
- convert an XML string into a DOM tree
|
|
||||||
```
|
|
||||||
new DOMParser().parseFromString(xml, mimeType) => Document
|
|
||||||
```
|
|
||||||
- create, access and modify a DOM tree
|
|
||||||
```
|
|
||||||
new DOMImplementation().createDocument(...) => Document
|
|
||||||
```
|
|
||||||
- serialize a DOM tree back into an XML string
|
|
||||||
```
|
|
||||||
new XMLSerializer().serializeToString(node) => string
|
|
||||||
```
|
|
||||||
|
|
||||||
The target runtimes `xmldom` supports are currently Node >= v10 (ES5) and Rhino ([not tested as part of CI](https://github.com/xmldom/xmldom/discussions/214)).
|
|
||||||
|
|
||||||
When deciding how to fix bugs or implement features, `xmldom` tries to stay as close as possible to the various [related specifications/standards](#specs).
|
|
||||||
As indicated by the version starting with `0.`, this implementation is not feature complete and some implemented features differ from what the specifications describe.
|
|
||||||
**Issues and PRs for such differences are always welcome, even when they only provide a failing test case.**
|
|
||||||
|
|
||||||
This project was forked from it's [original source](https://github.com/jindw/xmldom) in 2019, more details about that transition can be found in the [CHANGELOG](CHANGELOG.md#maintainer-changes).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Install:
|
|
||||||
|
|
||||||
> npm install @xmldom/xmldom
|
|
||||||
|
|
||||||
### Example:
|
|
||||||
|
|
||||||
[In NodeJS](examples/nodejs/src/index.js)
|
|
||||||
```javascript
|
|
||||||
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom')
|
|
||||||
|
|
||||||
const source = `<xml xmlns="a">
|
|
||||||
<child>test</child>
|
|
||||||
<child/>
|
|
||||||
</xml>`
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(source, 'text/xml')
|
|
||||||
|
|
||||||
const serialized = new XMLSerializer().serializeToString(doc)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: in Typescript ~and ES6~(see #316) you can use the `import` approach, as follows:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { DOMParser } from '@xmldom/xmldom'
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
* [DOMParser](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
parseFromString(xmlsource,mimeType)
|
|
||||||
```
|
|
||||||
* **options extension** _by xmldom_ (not DOM standard!!)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
//added the options argument
|
|
||||||
new DOMParser(options)
|
|
||||||
|
|
||||||
//errorHandler is supported
|
|
||||||
new DOMParser({
|
|
||||||
/**
|
|
||||||
* locator is always need for error position info
|
|
||||||
*/
|
|
||||||
locator:{},
|
|
||||||
/**
|
|
||||||
* you can override the errorHandler for xml parser
|
|
||||||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
|
|
||||||
*/
|
|
||||||
errorHandler:{warning:function(w){console.warn(w)},error:callback,fatalError:callback}
|
|
||||||
//only callback model
|
|
||||||
//errorHandler:function(level,msg){console.log(level,msg)}
|
|
||||||
})
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
* [XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
serializeToString(node)
|
|
||||||
```
|
|
||||||
### DOM level2 method and attribute:
|
|
||||||
|
|
||||||
* [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247)
|
|
||||||
|
|
||||||
readonly class properties (aka `NodeType`),
|
|
||||||
these can be accessed from any `Node` instance `node`:
|
|
||||||
`if (node.nodeType === node.ELEMENT_NODE) {...`
|
|
||||||
|
|
||||||
1. `ELEMENT_NODE` (`1`)
|
|
||||||
2. `ATTRIBUTE_NODE` (`2`)
|
|
||||||
3. `TEXT_NODE` (`3`)
|
|
||||||
4. `CDATA_SECTION_NODE` (`4`)
|
|
||||||
5. `ENTITY_REFERENCE_NODE` (`5`)
|
|
||||||
6. `ENTITY_NODE` (`6`)
|
|
||||||
7. `PROCESSING_INSTRUCTION_NODE` (`7`)
|
|
||||||
8. `COMMENT_NODE` (`8`)
|
|
||||||
9. `DOCUMENT_NODE` (`9`)
|
|
||||||
10. `DOCUMENT_TYPE_NODE` (`10`)
|
|
||||||
11. `DOCUMENT_FRAGMENT_NODE` (`11`)
|
|
||||||
12. `NOTATION_NODE` (`12`)
|
|
||||||
|
|
||||||
attribute:
|
|
||||||
- `nodeValue` | `prefix`
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `nodeName` | `nodeType` | `parentNode` | `childNodes` | `firstChild` | `lastChild` | `previousSibling` | `nextSibling` | `attributes` | `ownerDocument` | `namespaceURI` | `localName`
|
|
||||||
|
|
||||||
method:
|
|
||||||
* `insertBefore(newChild, refChild)`
|
|
||||||
* `replaceChild(newChild, oldChild)`
|
|
||||||
* `removeChild(oldChild)`
|
|
||||||
* `appendChild(newChild)`
|
|
||||||
* `hasChildNodes()`
|
|
||||||
* `cloneNode(deep)`
|
|
||||||
* `normalize()`
|
|
||||||
* `isSupported(feature, version)`
|
|
||||||
* `hasAttributes()`
|
|
||||||
* [DOMException](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html)
|
|
||||||
|
|
||||||
extends the Error type thrown as part of DOM API.
|
|
||||||
|
|
||||||
readonly class properties:
|
|
||||||
- `INDEX_SIZE_ERR` (`1`)
|
|
||||||
- `DOMSTRING_SIZE_ERR` (`2`)
|
|
||||||
- `HIERARCHY_REQUEST_ERR` (`3`)
|
|
||||||
- `WRONG_DOCUMENT_ERR` (`4`)
|
|
||||||
- `INVALID_CHARACTER_ERR` (`5`)
|
|
||||||
- `NO_DATA_ALLOWED_ERR` (`6`)
|
|
||||||
- `NO_MODIFICATION_ALLOWED_ERR` (`7`)
|
|
||||||
- `NOT_FOUND_ERR` (`8`)
|
|
||||||
- `NOT_SUPPORTED_ERR` (`9`)
|
|
||||||
- `INUSE_ATTRIBUTE_ERR` (`10`)
|
|
||||||
- `INVALID_STATE_ERR` (`11`)
|
|
||||||
- `SYNTAX_ERR` (`12`)
|
|
||||||
- `INVALID_MODIFICATION_ERR` (`13`)
|
|
||||||
- `NAMESPACE_ERR` (`14`)
|
|
||||||
- `INVALID_ACCESS_ERR` (`15`)
|
|
||||||
|
|
||||||
attributes:
|
|
||||||
- `code` with a value matching one of the above constants.
|
|
||||||
|
|
||||||
* [DOMImplementation](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-102161490)
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `hasFeature(feature, version)`
|
|
||||||
- `createDocumentType(qualifiedName, publicId, systemId)`
|
|
||||||
- `createDocument(namespaceURI, qualifiedName, doctype)`
|
|
||||||
|
|
||||||
* [Document](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document) : Node
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `doctype` | `implementation` | `documentElement`
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `createElement(tagName)`
|
|
||||||
- `createDocumentFragment()`
|
|
||||||
- `createTextNode(data)`
|
|
||||||
- `createComment(data)`
|
|
||||||
- `createCDATASection(data)`
|
|
||||||
- `createProcessingInstruction(target, data)`
|
|
||||||
- `createAttribute(name)`
|
|
||||||
- `createEntityReference(name)`
|
|
||||||
- `getElementsByTagName(tagname)`
|
|
||||||
- `importNode(importedNode, deep)`
|
|
||||||
- `createElementNS(namespaceURI, qualifiedName)`
|
|
||||||
- `createAttributeNS(namespaceURI, qualifiedName)`
|
|
||||||
- `getElementsByTagNameNS(namespaceURI, localName)`
|
|
||||||
- `getElementById(elementId)`
|
|
||||||
|
|
||||||
* [DocumentFragment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-B63ED1A3) : Node
|
|
||||||
* [Element](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-745549614) : Node
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `tagName`
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `getAttribute(name)`
|
|
||||||
- `setAttribute(name, value)`
|
|
||||||
- `removeAttribute(name)`
|
|
||||||
- `getAttributeNode(name)`
|
|
||||||
- `setAttributeNode(newAttr)`
|
|
||||||
- `removeAttributeNode(oldAttr)`
|
|
||||||
- `getElementsByTagName(name)`
|
|
||||||
- `getAttributeNS(namespaceURI, localName)`
|
|
||||||
- `setAttributeNS(namespaceURI, qualifiedName, value)`
|
|
||||||
- `removeAttributeNS(namespaceURI, localName)`
|
|
||||||
- `getAttributeNodeNS(namespaceURI, localName)`
|
|
||||||
- `setAttributeNodeNS(newAttr)`
|
|
||||||
- `getElementsByTagNameNS(namespaceURI, localName)`
|
|
||||||
- `hasAttribute(name)`
|
|
||||||
- `hasAttributeNS(namespaceURI, localName)`
|
|
||||||
|
|
||||||
* [Attr](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-637646024) : Node
|
|
||||||
|
|
||||||
attribute:
|
|
||||||
- `value`
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `name` | `specified` | `ownerElement`
|
|
||||||
|
|
||||||
* [NodeList](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177)
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `length`
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `item(index)`
|
|
||||||
|
|
||||||
* [NamedNodeMap](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1780488922)
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `length`
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `getNamedItem(name)`
|
|
||||||
- `setNamedItem(arg)`
|
|
||||||
- `removeNamedItem(name)`
|
|
||||||
- `item(index)`
|
|
||||||
- `getNamedItemNS(namespaceURI, localName)`
|
|
||||||
- `setNamedItemNS(arg)`
|
|
||||||
- `removeNamedItemNS(namespaceURI, localName)`
|
|
||||||
|
|
||||||
* [CharacterData](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-FF21A306) : Node
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `substringData(offset, count)`
|
|
||||||
- `appendData(arg)`
|
|
||||||
- `insertData(offset, arg)`
|
|
||||||
- `deleteData(offset, count)`
|
|
||||||
- `replaceData(offset, count, arg)`
|
|
||||||
|
|
||||||
* [Text](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1312295772) : CharacterData
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `splitText(offset)`
|
|
||||||
|
|
||||||
* [CDATASection](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-667469212)
|
|
||||||
* [Comment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1728279322) : CharacterData
|
|
||||||
|
|
||||||
* [DocumentType](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-412266927)
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `name` | `entities` | `notations` | `publicId` | `systemId` | `internalSubset`
|
|
||||||
|
|
||||||
* Notation : Node
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `publicId` | `systemId`
|
|
||||||
|
|
||||||
* Entity : Node
|
|
||||||
|
|
||||||
readonly attribute:
|
|
||||||
- `publicId` | `systemId` | `notationName`
|
|
||||||
|
|
||||||
* EntityReference : Node
|
|
||||||
* ProcessingInstruction : Node
|
|
||||||
|
|
||||||
attribute:
|
|
||||||
- `data`
|
|
||||||
readonly attribute:
|
|
||||||
- `target`
|
|
||||||
|
|
||||||
### DOM level 3 support:
|
|
||||||
|
|
||||||
* [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent)
|
|
||||||
|
|
||||||
attribute:
|
|
||||||
- `textContent`
|
|
||||||
|
|
||||||
method:
|
|
||||||
- `isDefaultNamespace(namespaceURI)`
|
|
||||||
- `lookupNamespaceURI(prefix)`
|
|
||||||
|
|
||||||
### DOM extension by xmldom
|
|
||||||
|
|
||||||
* [Node] Source position extension;
|
|
||||||
|
|
||||||
attribute:
|
|
||||||
- `lineNumber` //number starting from `1`
|
|
||||||
- `columnNumber` //number starting from `1`
|
|
||||||
|
|
||||||
## Specs
|
|
||||||
|
|
||||||
The implementation is based on several specifications:
|
|
||||||
|
|
||||||
<!-- Should open in new tab and the links in the SVG should be clickable there! -->
|
|
||||||
<a href="https://raw.githubusercontent.com/xmldom/xmldom/master/docs/specs.svg" target="_blank" rel="noopener noreferrer nofollow" ></a>
|
|
||||||
|
|
||||||
### DOM Parsing and Serialization
|
|
||||||
|
|
||||||
From the [W3C DOM Parsing and Serialization (WD 2016)](https://www.w3.org/TR/2016/WD-DOM-Parsing-20160517/) `xmldom` provides an implementation for the interfaces:
|
|
||||||
- `DOMParser`
|
|
||||||
- `XMLSerializer`
|
|
||||||
|
|
||||||
Note that there are some known deviations between this implementation and the W3 specifications.
|
|
||||||
|
|
||||||
Note: [The latest version of this spec](https://w3c.github.io/DOM-Parsing/) has the status "Editors Draft", since it is under active development. One major change is that [the definition of the `DOMParser` interface has been moved to the HTML spec](https://w3c.github.io/DOM-Parsing/#the-domparser-interface)
|
|
||||||
|
|
||||||
|
|
||||||
### DOM
|
|
||||||
|
|
||||||
The original author claims that xmldom implements [DOM Level 2] in a "fully compatible" way and some parts of [DOM Level 3], but there are not enough tests to prove this. Both Specifications are now superseded by the [DOM Level 4 aka Living standard] wich has a much broader scope than xmldom.
|
|
||||||
|
|
||||||
xmldom implements the following interfaces (most constructors are currently not exposed):
|
|
||||||
- `Attr`
|
|
||||||
- `CDATASection`
|
|
||||||
- `CharacterData`
|
|
||||||
- `Comment`
|
|
||||||
- `Document`
|
|
||||||
- `DocumentFragment`
|
|
||||||
- `DocumentType`
|
|
||||||
- `DOMException` (constructor exposed)
|
|
||||||
- `DOMImplementation` (constructor exposed)
|
|
||||||
- `Element`
|
|
||||||
- `Entity`
|
|
||||||
- `EntityReference`
|
|
||||||
- `LiveNodeList`
|
|
||||||
- `NamedNodeMap`
|
|
||||||
- `Node` (constructor exposed)
|
|
||||||
- `NodeList`
|
|
||||||
- `Notation`
|
|
||||||
- `ProcessingInstruction`
|
|
||||||
- `Text`
|
|
||||||
|
|
||||||
more details are available in the (incomplete) [API Reference](#api-reference) section.
|
|
||||||
|
|
||||||
### HTML
|
|
||||||
|
|
||||||
xmldom does not have any goal of supporting the full spec, but it has some capability to parse, report and serialize things differently when "detecting HTML" (by checking the default namespace).
|
|
||||||
There is an upcoming change to better align the implementation with the latest specs, related to <https://github.com/xmldom/xmldom/issues/203>.
|
|
||||||
|
|
||||||
### SAX, XML, XMLNS
|
|
||||||
|
|
||||||
xmldom has an own SAX parser implementation to do the actual parsing, which implements some interfaces in alignment with the Java interfaces SAX defines:
|
|
||||||
- `XMLReader`
|
|
||||||
- `DOMHandler`
|
|
||||||
|
|
||||||
There is an idea/proposal to make it possible to replace it with something else in <https://github.com/xmldom/xmldom/issues/55>
|
|
||||||
-243
@@ -1,243 +0,0 @@
|
|||||||
1.3.8 / 2022-02-02
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.34
|
|
||||||
- deps: mime-db@~1.51.0
|
|
||||||
* deps: negotiator@0.6.3
|
|
||||||
|
|
||||||
1.3.7 / 2019-04-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.6.2
|
|
||||||
- Fix sorting charset, encoding, and language with extra parameters
|
|
||||||
|
|
||||||
1.3.6 / 2019-04-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.24
|
|
||||||
- deps: mime-db@~1.40.0
|
|
||||||
|
|
||||||
1.3.5 / 2018-02-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.18
|
|
||||||
- deps: mime-db@~1.33.0
|
|
||||||
|
|
||||||
1.3.4 / 2017-08-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.16
|
|
||||||
- deps: mime-db@~1.29.0
|
|
||||||
|
|
||||||
1.3.3 / 2016-05-02
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.11
|
|
||||||
- deps: mime-db@~1.23.0
|
|
||||||
* deps: negotiator@0.6.1
|
|
||||||
- perf: improve `Accept` parsing speed
|
|
||||||
- perf: improve `Accept-Charset` parsing speed
|
|
||||||
- perf: improve `Accept-Encoding` parsing speed
|
|
||||||
- perf: improve `Accept-Language` parsing speed
|
|
||||||
|
|
||||||
1.3.2 / 2016-03-08
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.10
|
|
||||||
- Fix extension of `application/dash+xml`
|
|
||||||
- Update primary extension for `audio/mp4`
|
|
||||||
- deps: mime-db@~1.22.0
|
|
||||||
|
|
||||||
1.3.1 / 2016-01-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.9
|
|
||||||
- deps: mime-db@~1.21.0
|
|
||||||
|
|
||||||
1.3.0 / 2015-09-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.7
|
|
||||||
- deps: mime-db@~1.19.0
|
|
||||||
* deps: negotiator@0.6.0
|
|
||||||
- Fix including type extensions in parameters in `Accept` parsing
|
|
||||||
- Fix parsing `Accept` parameters with quoted equals
|
|
||||||
- Fix parsing `Accept` parameters with quoted semicolons
|
|
||||||
- Lazy-load modules from main entry point
|
|
||||||
- perf: delay type concatenation until needed
|
|
||||||
- perf: enable strict mode
|
|
||||||
- perf: hoist regular expressions
|
|
||||||
- perf: remove closures getting spec properties
|
|
||||||
- perf: remove a closure from media type parsing
|
|
||||||
- perf: remove property delete from media type parsing
|
|
||||||
|
|
||||||
1.2.13 / 2015-09-06
|
|
||||||
===================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.6
|
|
||||||
- deps: mime-db@~1.18.0
|
|
||||||
|
|
||||||
1.2.12 / 2015-07-30
|
|
||||||
===================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.4
|
|
||||||
- deps: mime-db@~1.16.0
|
|
||||||
|
|
||||||
1.2.11 / 2015-07-16
|
|
||||||
===================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.3
|
|
||||||
- deps: mime-db@~1.15.0
|
|
||||||
|
|
||||||
1.2.10 / 2015-07-01
|
|
||||||
===================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.2
|
|
||||||
- deps: mime-db@~1.14.0
|
|
||||||
|
|
||||||
1.2.9 / 2015-06-08
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.1
|
|
||||||
- perf: fix deopt during mapping
|
|
||||||
|
|
||||||
1.2.8 / 2015-06-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.1.0
|
|
||||||
- deps: mime-db@~1.13.0
|
|
||||||
* perf: avoid argument reassignment & argument slice
|
|
||||||
* perf: avoid negotiator recursive construction
|
|
||||||
* perf: enable strict mode
|
|
||||||
* perf: remove unnecessary bitwise operator
|
|
||||||
|
|
||||||
1.2.7 / 2015-05-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.5.3
|
|
||||||
- Fix media type parameter matching to be case-insensitive
|
|
||||||
|
|
||||||
1.2.6 / 2015-05-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.11
|
|
||||||
- deps: mime-db@~1.9.1
|
|
||||||
* deps: negotiator@0.5.2
|
|
||||||
- Fix comparing media types with quoted values
|
|
||||||
- Fix splitting media types with quoted commas
|
|
||||||
|
|
||||||
1.2.5 / 2015-03-13
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.10
|
|
||||||
- deps: mime-db@~1.8.0
|
|
||||||
|
|
||||||
1.2.4 / 2015-02-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Support Node.js 0.6
|
|
||||||
* deps: mime-types@~2.0.9
|
|
||||||
- deps: mime-db@~1.7.0
|
|
||||||
* deps: negotiator@0.5.1
|
|
||||||
- Fix preference sorting to be stable for long acceptable lists
|
|
||||||
|
|
||||||
1.2.3 / 2015-01-31
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.8
|
|
||||||
- deps: mime-db@~1.6.0
|
|
||||||
|
|
||||||
1.2.2 / 2014-12-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.7
|
|
||||||
- deps: mime-db@~1.5.0
|
|
||||||
|
|
||||||
1.2.1 / 2014-12-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.5
|
|
||||||
- deps: mime-db@~1.3.1
|
|
||||||
|
|
||||||
1.2.0 / 2014-12-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.5.0
|
|
||||||
- Fix list return order when large accepted list
|
|
||||||
- Fix missing identity encoding when q=0 exists
|
|
||||||
- Remove dynamic building of Negotiator class
|
|
||||||
|
|
||||||
1.1.4 / 2014-12-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.4
|
|
||||||
- deps: mime-db@~1.3.0
|
|
||||||
|
|
||||||
1.1.3 / 2014-11-09
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.3
|
|
||||||
- deps: mime-db@~1.2.0
|
|
||||||
|
|
||||||
1.1.2 / 2014-10-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.4.9
|
|
||||||
- Fix error when media type has invalid parameter
|
|
||||||
|
|
||||||
1.1.1 / 2014-09-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: mime-types@~2.0.2
|
|
||||||
- deps: mime-db@~1.1.0
|
|
||||||
* deps: negotiator@0.4.8
|
|
||||||
- Fix all negotiations to be case-insensitive
|
|
||||||
- Stable sort preferences of same quality according to client order
|
|
||||||
|
|
||||||
1.1.0 / 2014-09-02
|
|
||||||
==================
|
|
||||||
|
|
||||||
* update `mime-types`
|
|
||||||
|
|
||||||
1.0.7 / 2014-07-04
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fix wrong type returned from `type` when match after unknown extension
|
|
||||||
|
|
||||||
1.0.6 / 2014-06-24
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.4.7
|
|
||||||
|
|
||||||
1.0.5 / 2014-06-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
* fix crash when unknown extension given
|
|
||||||
|
|
||||||
1.0.4 / 2014-06-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* use `mime-types`
|
|
||||||
|
|
||||||
1.0.3 / 2014-06-11
|
|
||||||
==================
|
|
||||||
|
|
||||||
* deps: negotiator@0.4.6
|
|
||||||
- Order by specificity when quality is the same
|
|
||||||
|
|
||||||
1.0.2 / 2014-05-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fix interpretation when header not in request
|
|
||||||
* deps: pin negotiator@0.4.5
|
|
||||||
|
|
||||||
1.0.1 / 2014-01-18
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Identity encoding isn't always acceptable
|
|
||||||
* deps: negotiator@~0.4.0
|
|
||||||
|
|
||||||
1.0.0 / 2013-12-27
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Genesis
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
|
||||||
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
|
||||||
|
|
||||||
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.
|
|
||||||
-140
@@ -1,140 +0,0 @@
|
|||||||
# accepts
|
|
||||||
|
|
||||||
[![NPM Version][npm-version-image]][npm-url]
|
|
||||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
|
||||||
[![Node.js Version][node-version-image]][node-version-url]
|
|
||||||
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
|
||||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
||||||
|
|
||||||
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
|
|
||||||
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
|
|
||||||
|
|
||||||
In addition to negotiator, it allows:
|
|
||||||
|
|
||||||
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
|
|
||||||
as well as `('text/html', 'application/json')`.
|
|
||||||
- Allows type shorthands such as `json`.
|
|
||||||
- Returns `false` when no types match
|
|
||||||
- Treats non-existent headers as `*`
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
This is a [Node.js](https://nodejs.org/en/) module available through the
|
|
||||||
[npm registry](https://www.npmjs.com/). Installation is done using the
|
|
||||||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm install accepts
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```js
|
|
||||||
var accepts = require('accepts')
|
|
||||||
```
|
|
||||||
|
|
||||||
### accepts(req)
|
|
||||||
|
|
||||||
Create a new `Accepts` object for the given `req`.
|
|
||||||
|
|
||||||
#### .charset(charsets)
|
|
||||||
|
|
||||||
Return the first accepted charset. If nothing in `charsets` is accepted,
|
|
||||||
then `false` is returned.
|
|
||||||
|
|
||||||
#### .charsets()
|
|
||||||
|
|
||||||
Return the charsets that the request accepts, in the order of the client's
|
|
||||||
preference (most preferred first).
|
|
||||||
|
|
||||||
#### .encoding(encodings)
|
|
||||||
|
|
||||||
Return the first accepted encoding. If nothing in `encodings` is accepted,
|
|
||||||
then `false` is returned.
|
|
||||||
|
|
||||||
#### .encodings()
|
|
||||||
|
|
||||||
Return the encodings that the request accepts, in the order of the client's
|
|
||||||
preference (most preferred first).
|
|
||||||
|
|
||||||
#### .language(languages)
|
|
||||||
|
|
||||||
Return the first accepted language. If nothing in `languages` is accepted,
|
|
||||||
then `false` is returned.
|
|
||||||
|
|
||||||
#### .languages()
|
|
||||||
|
|
||||||
Return the languages that the request accepts, in the order of the client's
|
|
||||||
preference (most preferred first).
|
|
||||||
|
|
||||||
#### .type(types)
|
|
||||||
|
|
||||||
Return the first accepted type (and it is returned as the same text as what
|
|
||||||
appears in the `types` array). If nothing in `types` is accepted, then `false`
|
|
||||||
is returned.
|
|
||||||
|
|
||||||
The `types` array can contain full MIME types or file extensions. Any value
|
|
||||||
that is not a full MIME types is passed to `require('mime-types').lookup`.
|
|
||||||
|
|
||||||
#### .types()
|
|
||||||
|
|
||||||
Return the types that the request accepts, in the order of the client's
|
|
||||||
preference (most preferred first).
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Simple type negotiation
|
|
||||||
|
|
||||||
This simple example shows how to use `accepts` to return a different typed
|
|
||||||
respond body based on what the client wants to accept. The server lists it's
|
|
||||||
preferences in order and will get back the best match between the client and
|
|
||||||
server.
|
|
||||||
|
|
||||||
```js
|
|
||||||
var accepts = require('accepts')
|
|
||||||
var http = require('http')
|
|
||||||
|
|
||||||
function app (req, res) {
|
|
||||||
var accept = accepts(req)
|
|
||||||
|
|
||||||
// the order of this list is significant; should be server preferred order
|
|
||||||
switch (accept.type(['json', 'html'])) {
|
|
||||||
case 'json':
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
|
||||||
res.write('{"hello":"world!"}')
|
|
||||||
break
|
|
||||||
case 'html':
|
|
||||||
res.setHeader('Content-Type', 'text/html')
|
|
||||||
res.write('<b>hello, world!</b>')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// the fallback is text/plain, so no need to specify it above
|
|
||||||
res.setHeader('Content-Type', 'text/plain')
|
|
||||||
res.write('hello, world!')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
res.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
http.createServer(app).listen(3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
You can test this out with the cURL program:
|
|
||||||
```sh
|
|
||||||
curl -I -H'Accept: text/html' http://localhost:3000/
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
||||||
|
|
||||||
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
|
|
||||||
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
|
|
||||||
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
|
|
||||||
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
|
|
||||||
[node-version-image]: https://badgen.net/npm/node/accepts
|
|
||||||
[node-version-url]: https://nodejs.org/en/download
|
|
||||||
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
|
|
||||||
[npm-url]: https://npmjs.org/package/accepts
|
|
||||||
[npm-version-image]: https://badgen.net/npm/v/accepts
|
|
||||||
-238
@@ -1,238 +0,0 @@
|
|||||||
/*!
|
|
||||||
* accepts
|
|
||||||
* Copyright(c) 2014 Jonathan Ong
|
|
||||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Negotiator = require('negotiator')
|
|
||||||
var mime = require('mime-types')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = Accepts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new Accepts object for the given req.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function Accepts (req) {
|
|
||||||
if (!(this instanceof Accepts)) {
|
|
||||||
return new Accepts(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.headers = req.headers
|
|
||||||
this.negotiator = new Negotiator(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the given `type(s)` is acceptable, returning
|
|
||||||
* the best match when true, otherwise `undefined`, in which
|
|
||||||
* case you should respond with 406 "Not Acceptable".
|
|
||||||
*
|
|
||||||
* The `type` value may be a single mime type string
|
|
||||||
* such as "application/json", the extension name
|
|
||||||
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
|
|
||||||
* or array is given the _best_ match, if any is returned.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* // Accept: text/html
|
|
||||||
* this.types('html');
|
|
||||||
* // => "html"
|
|
||||||
*
|
|
||||||
* // Accept: text/*, application/json
|
|
||||||
* this.types('html');
|
|
||||||
* // => "html"
|
|
||||||
* this.types('text/html');
|
|
||||||
* // => "text/html"
|
|
||||||
* this.types('json', 'text');
|
|
||||||
* // => "json"
|
|
||||||
* this.types('application/json');
|
|
||||||
* // => "application/json"
|
|
||||||
*
|
|
||||||
* // Accept: text/*, application/json
|
|
||||||
* this.types('image/png');
|
|
||||||
* this.types('png');
|
|
||||||
* // => undefined
|
|
||||||
*
|
|
||||||
* // Accept: text/*;q=.5, application/json
|
|
||||||
* this.types(['html', 'json']);
|
|
||||||
* this.types('html', 'json');
|
|
||||||
* // => "json"
|
|
||||||
*
|
|
||||||
* @param {String|Array} types...
|
|
||||||
* @return {String|Array|Boolean}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Accepts.prototype.type =
|
|
||||||
Accepts.prototype.types = function (types_) {
|
|
||||||
var types = types_
|
|
||||||
|
|
||||||
// support flattened arguments
|
|
||||||
if (types && !Array.isArray(types)) {
|
|
||||||
types = new Array(arguments.length)
|
|
||||||
for (var i = 0; i < types.length; i++) {
|
|
||||||
types[i] = arguments[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no types, return all requested types
|
|
||||||
if (!types || types.length === 0) {
|
|
||||||
return this.negotiator.mediaTypes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// no accept header, return first given type
|
|
||||||
if (!this.headers.accept) {
|
|
||||||
return types[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var mimes = types.map(extToMime)
|
|
||||||
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
|
|
||||||
var first = accepts[0]
|
|
||||||
|
|
||||||
return first
|
|
||||||
? types[mimes.indexOf(first)]
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return accepted encodings or best fit based on `encodings`.
|
|
||||||
*
|
|
||||||
* Given `Accept-Encoding: gzip, deflate`
|
|
||||||
* an array sorted by quality is returned:
|
|
||||||
*
|
|
||||||
* ['gzip', 'deflate']
|
|
||||||
*
|
|
||||||
* @param {String|Array} encodings...
|
|
||||||
* @return {String|Array}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Accepts.prototype.encoding =
|
|
||||||
Accepts.prototype.encodings = function (encodings_) {
|
|
||||||
var encodings = encodings_
|
|
||||||
|
|
||||||
// support flattened arguments
|
|
||||||
if (encodings && !Array.isArray(encodings)) {
|
|
||||||
encodings = new Array(arguments.length)
|
|
||||||
for (var i = 0; i < encodings.length; i++) {
|
|
||||||
encodings[i] = arguments[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no encodings, return all requested encodings
|
|
||||||
if (!encodings || encodings.length === 0) {
|
|
||||||
return this.negotiator.encodings()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.negotiator.encodings(encodings)[0] || false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return accepted charsets or best fit based on `charsets`.
|
|
||||||
*
|
|
||||||
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
|
||||||
* an array sorted by quality is returned:
|
|
||||||
*
|
|
||||||
* ['utf-8', 'utf-7', 'iso-8859-1']
|
|
||||||
*
|
|
||||||
* @param {String|Array} charsets...
|
|
||||||
* @return {String|Array}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Accepts.prototype.charset =
|
|
||||||
Accepts.prototype.charsets = function (charsets_) {
|
|
||||||
var charsets = charsets_
|
|
||||||
|
|
||||||
// support flattened arguments
|
|
||||||
if (charsets && !Array.isArray(charsets)) {
|
|
||||||
charsets = new Array(arguments.length)
|
|
||||||
for (var i = 0; i < charsets.length; i++) {
|
|
||||||
charsets[i] = arguments[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no charsets, return all requested charsets
|
|
||||||
if (!charsets || charsets.length === 0) {
|
|
||||||
return this.negotiator.charsets()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.negotiator.charsets(charsets)[0] || false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return accepted languages or best fit based on `langs`.
|
|
||||||
*
|
|
||||||
* Given `Accept-Language: en;q=0.8, es, pt`
|
|
||||||
* an array sorted by quality is returned:
|
|
||||||
*
|
|
||||||
* ['es', 'pt', 'en']
|
|
||||||
*
|
|
||||||
* @param {String|Array} langs...
|
|
||||||
* @return {Array|String}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Accepts.prototype.lang =
|
|
||||||
Accepts.prototype.langs =
|
|
||||||
Accepts.prototype.language =
|
|
||||||
Accepts.prototype.languages = function (languages_) {
|
|
||||||
var languages = languages_
|
|
||||||
|
|
||||||
// support flattened arguments
|
|
||||||
if (languages && !Array.isArray(languages)) {
|
|
||||||
languages = new Array(arguments.length)
|
|
||||||
for (var i = 0; i < languages.length; i++) {
|
|
||||||
languages[i] = arguments[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no languages, return all requested languages
|
|
||||||
if (!languages || languages.length === 0) {
|
|
||||||
return this.negotiator.languages()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.negotiator.languages(languages)[0] || false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert extnames to mime.
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @return {String}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function extToMime (type) {
|
|
||||||
return type.indexOf('/') === -1
|
|
||||||
? mime.lookup(type)
|
|
||||||
: type
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if mime is valid.
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @return {String}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function validMime (type) {
|
|
||||||
return typeof type === 'string'
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "accepts",
|
|
||||||
"description": "Higher-level content negotiation",
|
|
||||||
"version": "1.3.8",
|
|
||||||
"contributors": [
|
|
||||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
|
||||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": "jshttp/accepts",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-types": "~2.1.34",
|
|
||||||
"negotiator": "0.6.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"deep-equal": "1.0.1",
|
|
||||||
"eslint": "7.32.0",
|
|
||||||
"eslint-config-standard": "14.1.1",
|
|
||||||
"eslint-plugin-import": "2.25.4",
|
|
||||||
"eslint-plugin-markdown": "2.2.1",
|
|
||||||
"eslint-plugin-node": "11.1.0",
|
|
||||||
"eslint-plugin-promise": "4.3.1",
|
|
||||||
"eslint-plugin-standard": "4.1.0",
|
|
||||||
"mocha": "9.2.0",
|
|
||||||
"nyc": "15.1.0"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"LICENSE",
|
|
||||||
"HISTORY.md",
|
|
||||||
"index.js"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint .",
|
|
||||||
"test": "mocha --reporter spec --check-leaks --bail test/",
|
|
||||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
|
||||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"content",
|
|
||||||
"negotiation",
|
|
||||||
"accept",
|
|
||||||
"accepts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-185
@@ -1,185 +0,0 @@
|
|||||||
1.0.10 / 2018-02-15
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Use .concat instead of + for arrays, #122.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.9 / 2016-09-29
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Rerelease after 1.0.8 - deps cleanup.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.8 / 2016-09-29
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Maintenance (deps bump, fix node 6.5+ tests, coverage report).
|
|
||||||
|
|
||||||
|
|
||||||
1.0.7 / 2016-03-17
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Teach `addArgument` to accept string arg names. #97, @tomxtobin.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.6 / 2016-02-06
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Maintenance: moved to eslint & updated CS.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.5 / 2016-02-05
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Removed lodash dependency to significantly reduce install size.
|
|
||||||
Thanks to @mourner.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.4 / 2016-01-17
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Maintenance: lodash update to 4.0.0.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.3 / 2015-10-27
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.2 / 2015-03-22
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Relaxed lodash version dependency.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.1 / 2015-02-20
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Changed dependencies to be compatible with ancient nodejs.
|
|
||||||
|
|
||||||
|
|
||||||
1.0.0 / 2015-02-19
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Maintenance release.
|
|
||||||
- Replaced `underscore` with `lodash`.
|
|
||||||
- Bumped version to 1.0.0 to better reflect semver meaning.
|
|
||||||
- HISTORY.md -> CHANGELOG.md
|
|
||||||
|
|
||||||
|
|
||||||
0.1.16 / 2013-12-01
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Maintenance release. Updated dependencies and docs.
|
|
||||||
|
|
||||||
|
|
||||||
0.1.15 / 2013-05-13
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Fixed #55, @trebor89
|
|
||||||
|
|
||||||
|
|
||||||
0.1.14 / 2013-05-12
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Fixed #62, @maxtaco
|
|
||||||
|
|
||||||
|
|
||||||
0.1.13 / 2013-04-08
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Added `.npmignore` to reduce package size
|
|
||||||
|
|
||||||
|
|
||||||
0.1.12 / 2013-02-10
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Fixed conflictHandler (#46), @hpaulj
|
|
||||||
|
|
||||||
|
|
||||||
0.1.11 / 2013-02-07
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Multiple bugfixes, @hpaulj
|
|
||||||
- Added 70+ tests (ported from python), @hpaulj
|
|
||||||
- Added conflictHandler, @applepicke
|
|
||||||
- Added fromfilePrefixChar, @hpaulj
|
|
||||||
|
|
||||||
|
|
||||||
0.1.10 / 2012-12-30
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion)
|
|
||||||
support, thanks to @hpaulj
|
|
||||||
- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj
|
|
||||||
|
|
||||||
|
|
||||||
0.1.9 / 2012-12-27
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj
|
|
||||||
- Fixed default value behavior with `*` positionals, thanks to @hpaulj
|
|
||||||
- Improve `getDefault()` behavior, thanks to @hpaulj
|
|
||||||
- Imrove negative argument parsing, thanks to @hpaulj
|
|
||||||
|
|
||||||
|
|
||||||
0.1.8 / 2012-12-01
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed parser parents (issue #19), thanks to @hpaulj
|
|
||||||
- Fixed negative argument parse (issue #20), thanks to @hpaulj
|
|
||||||
|
|
||||||
|
|
||||||
0.1.7 / 2012-10-14
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed 'choices' argument parse (issue #16)
|
|
||||||
- Fixed stderr output (issue #15)
|
|
||||||
|
|
||||||
|
|
||||||
0.1.6 / 2012-09-09
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed check for conflict of options (thanks to @tomxtobin)
|
|
||||||
|
|
||||||
|
|
||||||
0.1.5 / 2012-09-03
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fix parser #setDefaults method (thanks to @tomxtobin)
|
|
||||||
|
|
||||||
|
|
||||||
0.1.4 / 2012-07-30
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed pseudo-argument support (thanks to @CGamesPlay)
|
|
||||||
- Fixed addHelp default (should be true), if not set (thanks to @benblank)
|
|
||||||
|
|
||||||
|
|
||||||
0.1.3 / 2012-06-27
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed formatter api name: Formatter -> HelpFormatter
|
|
||||||
|
|
||||||
|
|
||||||
0.1.2 / 2012-05-29
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Added basic tests
|
|
||||||
- Removed excess whitespace in help
|
|
||||||
- Fixed error reporting, when parcer with subcommands
|
|
||||||
called with empty arguments
|
|
||||||
|
|
||||||
|
|
||||||
0.1.1 / 2012-05-23
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Fixed line wrapping in help formatter
|
|
||||||
- Added better error reporting on invalid arguments
|
|
||||||
|
|
||||||
|
|
||||||
0.1.0 / 2012-05-16
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- First release.
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (C) 2012 by Vitaly Puzrin
|
|
||||||
|
|
||||||
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.
|
|
||||||
-257
@@ -1,257 +0,0 @@
|
|||||||
argparse
|
|
||||||
========
|
|
||||||
|
|
||||||
[](http://travis-ci.org/nodeca/argparse)
|
|
||||||
[](https://www.npmjs.org/package/argparse)
|
|
||||||
|
|
||||||
CLI arguments parser for node.js. Javascript port of python's
|
|
||||||
[argparse](http://docs.python.org/dev/library/argparse.html) module
|
|
||||||
(original version 3.2). That's a full port, except some very rare options,
|
|
||||||
recorded in issue tracker.
|
|
||||||
|
|
||||||
**NB. Difference with original.**
|
|
||||||
|
|
||||||
- Method names changed to camelCase. See [generated docs](http://nodeca.github.com/argparse/).
|
|
||||||
- Use `defaultValue` instead of `default`.
|
|
||||||
- Use `argparse.Const.REMAINDER` instead of `argparse.REMAINDER`, and
|
|
||||||
similarly for constant values `OPTIONAL`, `ZERO_OR_MORE`, and `ONE_OR_MORE`
|
|
||||||
(aliases for `nargs` values `'?'`, `'*'`, `'+'`, respectively), and
|
|
||||||
`SUPPRESS`.
|
|
||||||
|
|
||||||
|
|
||||||
Example
|
|
||||||
=======
|
|
||||||
|
|
||||||
test.js file:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
#!/usr/bin/env node
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var ArgumentParser = require('../lib/argparse').ArgumentParser;
|
|
||||||
var parser = new ArgumentParser({
|
|
||||||
version: '0.0.1',
|
|
||||||
addHelp:true,
|
|
||||||
description: 'Argparse example'
|
|
||||||
});
|
|
||||||
parser.addArgument(
|
|
||||||
[ '-f', '--foo' ],
|
|
||||||
{
|
|
||||||
help: 'foo bar'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
parser.addArgument(
|
|
||||||
[ '-b', '--bar' ],
|
|
||||||
{
|
|
||||||
help: 'bar foo'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
parser.addArgument(
|
|
||||||
'--baz',
|
|
||||||
{
|
|
||||||
help: 'baz bar'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
var args = parser.parseArgs();
|
|
||||||
console.dir(args);
|
|
||||||
```
|
|
||||||
|
|
||||||
Display help:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./test.js -h
|
|
||||||
usage: example.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ]
|
|
||||||
|
|
||||||
Argparse example
|
|
||||||
|
|
||||||
Optional arguments:
|
|
||||||
-h, --help Show this help message and exit.
|
|
||||||
-v, --version Show program's version number and exit.
|
|
||||||
-f FOO, --foo FOO foo bar
|
|
||||||
-b BAR, --bar BAR bar foo
|
|
||||||
--baz BAZ baz bar
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse arguments:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./test.js -f=3 --bar=4 --baz 5
|
|
||||||
{ foo: '3', bar: '4', baz: '5' }
|
|
||||||
```
|
|
||||||
|
|
||||||
More [examples](https://github.com/nodeca/argparse/tree/master/examples).
|
|
||||||
|
|
||||||
|
|
||||||
ArgumentParser objects
|
|
||||||
======================
|
|
||||||
|
|
||||||
```
|
|
||||||
new ArgumentParser({parameters hash});
|
|
||||||
```
|
|
||||||
|
|
||||||
Creates a new ArgumentParser object.
|
|
||||||
|
|
||||||
**Supported params:**
|
|
||||||
|
|
||||||
- ```description``` - Text to display before the argument help.
|
|
||||||
- ```epilog``` - Text to display after the argument help.
|
|
||||||
- ```addHelp``` - Add a -h/–help option to the parser. (default: true)
|
|
||||||
- ```argumentDefault``` - Set the global default value for arguments. (default: null)
|
|
||||||
- ```parents``` - A list of ArgumentParser objects whose arguments should also be included.
|
|
||||||
- ```prefixChars``` - The set of characters that prefix optional arguments. (default: ‘-‘)
|
|
||||||
- ```formatterClass``` - A class for customizing the help output.
|
|
||||||
- ```prog``` - The name of the program (default: `path.basename(process.argv[1])`)
|
|
||||||
- ```usage``` - The string describing the program usage (default: generated)
|
|
||||||
- ```conflictHandler``` - Usually unnecessary, defines strategy for resolving conflicting optionals.
|
|
||||||
|
|
||||||
**Not supported yet**
|
|
||||||
|
|
||||||
- ```fromfilePrefixChars``` - The set of characters that prefix files from which additional arguments should be read.
|
|
||||||
|
|
||||||
|
|
||||||
Details in [original ArgumentParser guide](http://docs.python.org/dev/library/argparse.html#argumentparser-objects)
|
|
||||||
|
|
||||||
|
|
||||||
addArgument() method
|
|
||||||
====================
|
|
||||||
|
|
||||||
```
|
|
||||||
ArgumentParser.addArgument(name or flag or [name] or [flags...], {options})
|
|
||||||
```
|
|
||||||
|
|
||||||
Defines how a single command-line argument should be parsed.
|
|
||||||
|
|
||||||
- ```name or flag or [name] or [flags...]``` - Either a positional name
|
|
||||||
(e.g., `'foo'`), a single option (e.g., `'-f'` or `'--foo'`), an array
|
|
||||||
of a single positional name (e.g., `['foo']`), or an array of options
|
|
||||||
(e.g., `['-f', '--foo']`).
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
- ```action``` - The basic type of action to be taken when this argument is encountered at the command line.
|
|
||||||
- ```nargs```- The number of command-line arguments that should be consumed.
|
|
||||||
- ```constant``` - A constant value required by some action and nargs selections.
|
|
||||||
- ```defaultValue``` - The value produced if the argument is absent from the command line.
|
|
||||||
- ```type``` - The type to which the command-line argument should be converted.
|
|
||||||
- ```choices``` - A container of the allowable values for the argument.
|
|
||||||
- ```required``` - Whether or not the command-line option may be omitted (optionals only).
|
|
||||||
- ```help``` - A brief description of what the argument does.
|
|
||||||
- ```metavar``` - A name for the argument in usage messages.
|
|
||||||
- ```dest``` - The name of the attribute to be added to the object returned by parseArgs().
|
|
||||||
|
|
||||||
Details in [original add_argument guide](http://docs.python.org/dev/library/argparse.html#the-add-argument-method)
|
|
||||||
|
|
||||||
|
|
||||||
Action (some details)
|
|
||||||
================
|
|
||||||
|
|
||||||
ArgumentParser objects associate command-line arguments with actions.
|
|
||||||
These actions can do just about anything with the command-line arguments associated
|
|
||||||
with them, though most actions simply add an attribute to the object returned by
|
|
||||||
parseArgs(). The action keyword argument specifies how the command-line arguments
|
|
||||||
should be handled. The supported actions are:
|
|
||||||
|
|
||||||
- ```store``` - Just stores the argument’s value. This is the default action.
|
|
||||||
- ```storeConst``` - Stores value, specified by the const keyword argument.
|
|
||||||
(Note that the const keyword argument defaults to the rather unhelpful None.)
|
|
||||||
The 'storeConst' action is most commonly used with optional arguments, that
|
|
||||||
specify some sort of flag.
|
|
||||||
- ```storeTrue``` and ```storeFalse``` - Stores values True and False
|
|
||||||
respectively. These are special cases of 'storeConst'.
|
|
||||||
- ```append``` - Stores a list, and appends each argument value to the list.
|
|
||||||
This is useful to allow an option to be specified multiple times.
|
|
||||||
- ```appendConst``` - Stores a list, and appends value, specified by the
|
|
||||||
const keyword argument to the list. (Note, that the const keyword argument defaults
|
|
||||||
is None.) The 'appendConst' action is typically used when multiple arguments need
|
|
||||||
to store constants to the same list.
|
|
||||||
- ```count``` - Counts the number of times a keyword argument occurs. For example,
|
|
||||||
used for increasing verbosity levels.
|
|
||||||
- ```help``` - Prints a complete help message for all the options in the current
|
|
||||||
parser and then exits. By default a help action is automatically added to the parser.
|
|
||||||
See ArgumentParser for details of how the output is created.
|
|
||||||
- ```version``` - Prints version information and exit. Expects a `version=`
|
|
||||||
keyword argument in the addArgument() call.
|
|
||||||
|
|
||||||
Details in [original action guide](http://docs.python.org/dev/library/argparse.html#action)
|
|
||||||
|
|
||||||
|
|
||||||
Sub-commands
|
|
||||||
============
|
|
||||||
|
|
||||||
ArgumentParser.addSubparsers()
|
|
||||||
|
|
||||||
Many programs split their functionality into a number of sub-commands, for
|
|
||||||
example, the svn program can invoke sub-commands like `svn checkout`, `svn update`,
|
|
||||||
and `svn commit`. Splitting up functionality this way can be a particularly good
|
|
||||||
idea when a program performs several different functions which require different
|
|
||||||
kinds of command-line arguments. `ArgumentParser` supports creation of such
|
|
||||||
sub-commands with `addSubparsers()` method. The `addSubparsers()` method is
|
|
||||||
normally called with no arguments and returns an special action object.
|
|
||||||
This object has a single method `addParser()`, which takes a command name and
|
|
||||||
any `ArgumentParser` constructor arguments, and returns an `ArgumentParser` object
|
|
||||||
that can be modified as usual.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
sub_commands.js
|
|
||||||
```javascript
|
|
||||||
#!/usr/bin/env node
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var ArgumentParser = require('../lib/argparse').ArgumentParser;
|
|
||||||
var parser = new ArgumentParser({
|
|
||||||
version: '0.0.1',
|
|
||||||
addHelp:true,
|
|
||||||
description: 'Argparse examples: sub-commands',
|
|
||||||
});
|
|
||||||
|
|
||||||
var subparsers = parser.addSubparsers({
|
|
||||||
title:'subcommands',
|
|
||||||
dest:"subcommand_name"
|
|
||||||
});
|
|
||||||
|
|
||||||
var bar = subparsers.addParser('c1', {addHelp:true});
|
|
||||||
bar.addArgument(
|
|
||||||
[ '-f', '--foo' ],
|
|
||||||
{
|
|
||||||
action: 'store',
|
|
||||||
help: 'foo3 bar3'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
var bar = subparsers.addParser(
|
|
||||||
'c2',
|
|
||||||
{aliases:['co'], addHelp:true}
|
|
||||||
);
|
|
||||||
bar.addArgument(
|
|
||||||
[ '-b', '--bar' ],
|
|
||||||
{
|
|
||||||
action: 'store',
|
|
||||||
type: 'int',
|
|
||||||
help: 'foo3 bar3'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
var args = parser.parseArgs();
|
|
||||||
console.dir(args);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Details in [original sub-commands guide](http://docs.python.org/dev/library/argparse.html#sub-commands)
|
|
||||||
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
============
|
|
||||||
|
|
||||||
- [Eugene Shkuropat](https://github.com/shkuropat)
|
|
||||||
- [Paul Jacobson](https://github.com/hpaulj)
|
|
||||||
|
|
||||||
[others](https://github.com/nodeca/argparse/graphs/contributors)
|
|
||||||
|
|
||||||
License
|
|
||||||
=======
|
|
||||||
|
|
||||||
Copyright (c) 2012 [Vitaly Puzrin](https://github.com/puzrin).
|
|
||||||
Released under the MIT license. See
|
|
||||||
[LICENSE](https://github.com/nodeca/argparse/blob/master/LICENSE) for details.
|
|
||||||
|
|
||||||
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = require('./lib/argparse');
|
|
||||||
-146
@@ -1,146 +0,0 @@
|
|||||||
/**
|
|
||||||
* class Action
|
|
||||||
*
|
|
||||||
* Base class for all actions
|
|
||||||
* Do not call in your code, use this class only for inherits your own action
|
|
||||||
*
|
|
||||||
* Information about how to convert command line strings to Javascript objects.
|
|
||||||
* Action objects are used by an ArgumentParser to represent the information
|
|
||||||
* needed to parse a single argument from one or more strings from the command
|
|
||||||
* line. The keyword arguments to the Action constructor are also all attributes
|
|
||||||
* of Action instances.
|
|
||||||
*
|
|
||||||
* ##### Allowed keywords:
|
|
||||||
*
|
|
||||||
* - `store`
|
|
||||||
* - `storeConstant`
|
|
||||||
* - `storeTrue`
|
|
||||||
* - `storeFalse`
|
|
||||||
* - `append`
|
|
||||||
* - `appendConstant`
|
|
||||||
* - `count`
|
|
||||||
* - `help`
|
|
||||||
* - `version`
|
|
||||||
*
|
|
||||||
* Information about action options see [[Action.new]]
|
|
||||||
*
|
|
||||||
* See also [original guide](http://docs.python.org/dev/library/argparse.html#action)
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('./const');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new Action(options)
|
|
||||||
*
|
|
||||||
* Base class for all actions. Used only for inherits
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* ##### Options:
|
|
||||||
*
|
|
||||||
* - `optionStrings` A list of command-line option strings for the action.
|
|
||||||
* - `dest` Attribute to hold the created object(s)
|
|
||||||
* - `nargs` The number of command-line arguments that should be consumed.
|
|
||||||
* By default, one argument will be consumed and a single value will be
|
|
||||||
* produced.
|
|
||||||
* - `constant` Default value for an action with no value.
|
|
||||||
* - `defaultValue` The value to be produced if the option is not specified.
|
|
||||||
* - `type` Cast to 'string'|'int'|'float'|'complex'|function (string). If
|
|
||||||
* None, 'string'.
|
|
||||||
* - `choices` The choices available.
|
|
||||||
* - `required` True if the action must always be specified at the command
|
|
||||||
* line.
|
|
||||||
* - `help` The help describing the argument.
|
|
||||||
* - `metavar` The name to be used for the option's argument with the help
|
|
||||||
* string. If None, the 'dest' value will be used as the name.
|
|
||||||
*
|
|
||||||
* ##### nargs supported values:
|
|
||||||
*
|
|
||||||
* - `N` (an integer) consumes N arguments (and produces a list)
|
|
||||||
* - `?` consumes zero or one arguments
|
|
||||||
* - `*` consumes zero or more arguments (and produces a list)
|
|
||||||
* - `+` consumes one or more arguments (and produces a list)
|
|
||||||
*
|
|
||||||
* Note: that the difference between the default and nargs=1 is that with the
|
|
||||||
* default, a single value will be produced, while with nargs=1, a list
|
|
||||||
* containing a single value will be produced.
|
|
||||||
**/
|
|
||||||
var Action = module.exports = function Action(options) {
|
|
||||||
options = options || {};
|
|
||||||
this.optionStrings = options.optionStrings || [];
|
|
||||||
this.dest = options.dest;
|
|
||||||
this.nargs = typeof options.nargs !== 'undefined' ? options.nargs : null;
|
|
||||||
this.constant = typeof options.constant !== 'undefined' ? options.constant : null;
|
|
||||||
this.defaultValue = options.defaultValue;
|
|
||||||
this.type = typeof options.type !== 'undefined' ? options.type : null;
|
|
||||||
this.choices = typeof options.choices !== 'undefined' ? options.choices : null;
|
|
||||||
this.required = typeof options.required !== 'undefined' ? options.required : false;
|
|
||||||
this.help = typeof options.help !== 'undefined' ? options.help : null;
|
|
||||||
this.metavar = typeof options.metavar !== 'undefined' ? options.metavar : null;
|
|
||||||
|
|
||||||
if (!(this.optionStrings instanceof Array)) {
|
|
||||||
throw new Error('optionStrings should be an array');
|
|
||||||
}
|
|
||||||
if (typeof this.required !== 'undefined' && typeof this.required !== 'boolean') {
|
|
||||||
throw new Error('required should be a boolean');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action#getName -> String
|
|
||||||
*
|
|
||||||
* Tells action name
|
|
||||||
**/
|
|
||||||
Action.prototype.getName = function () {
|
|
||||||
if (this.optionStrings.length > 0) {
|
|
||||||
return this.optionStrings.join('/');
|
|
||||||
} else if (this.metavar !== null && this.metavar !== c.SUPPRESS) {
|
|
||||||
return this.metavar;
|
|
||||||
} else if (typeof this.dest !== 'undefined' && this.dest !== c.SUPPRESS) {
|
|
||||||
return this.dest;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action#isOptional -> Boolean
|
|
||||||
*
|
|
||||||
* Return true if optional
|
|
||||||
**/
|
|
||||||
Action.prototype.isOptional = function () {
|
|
||||||
return !this.isPositional();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action#isPositional -> Boolean
|
|
||||||
*
|
|
||||||
* Return true if positional
|
|
||||||
**/
|
|
||||||
Action.prototype.isPositional = function () {
|
|
||||||
return (this.optionStrings.length === 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Should be implemented in inherited classes
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* ActionCount.prototype.call = function (parser, namespace, values, optionString) {
|
|
||||||
* namespace.set(this.dest, (namespace[this.dest] || 0) + 1);
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
Action.prototype.call = function () {
|
|
||||||
throw new Error('.call() not defined');// Not Implemented error
|
|
||||||
};
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionAppend
|
|
||||||
*
|
|
||||||
* This action stores a list, and appends each argument value to the list.
|
|
||||||
* This is useful to allow an option to be specified multiple times.
|
|
||||||
* This class inherided from [[Action]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionAppend(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
* Note: options.nargs should be optional for constants
|
|
||||||
* and more then zero for other
|
|
||||||
**/
|
|
||||||
var ActionAppend = module.exports = function ActionAppend(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (this.nargs <= 0) {
|
|
||||||
throw new Error('nargs for append actions must be > 0; if arg ' +
|
|
||||||
'strings are not supplying the value to append, ' +
|
|
||||||
'the append const action may be more appropriate');
|
|
||||||
}
|
|
||||||
if (!!this.constant && this.nargs !== c.OPTIONAL) {
|
|
||||||
throw new Error('nargs must be OPTIONAL to supply const');
|
|
||||||
}
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionAppend, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionAppend#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Save result in namespace object
|
|
||||||
**/
|
|
||||||
ActionAppend.prototype.call = function (parser, namespace, values) {
|
|
||||||
var items = (namespace[this.dest] || []).slice();
|
|
||||||
items.push(values);
|
|
||||||
namespace.set(this.dest, items);
|
|
||||||
};
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionAppendConstant
|
|
||||||
*
|
|
||||||
* This stores a list, and appends the value specified by
|
|
||||||
* the const keyword argument to the list.
|
|
||||||
* (Note that the const keyword argument defaults to null.)
|
|
||||||
* The 'appendConst' action is typically useful when multiple
|
|
||||||
* arguments need to store constants to the same list.
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../../action');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionAppendConstant(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionAppendConstant = module.exports = function ActionAppendConstant(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.nargs = 0;
|
|
||||||
if (typeof options.constant === 'undefined') {
|
|
||||||
throw new Error('constant option is required for appendAction');
|
|
||||||
}
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionAppendConstant, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionAppendConstant#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Save result in namespace object
|
|
||||||
**/
|
|
||||||
ActionAppendConstant.prototype.call = function (parser, namespace) {
|
|
||||||
var items = [].concat(namespace[this.dest] || []);
|
|
||||||
items.push(this.constant);
|
|
||||||
namespace.set(this.dest, items);
|
|
||||||
};
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionCount
|
|
||||||
*
|
|
||||||
* This counts the number of times a keyword argument occurs.
|
|
||||||
* For example, this is useful for increasing verbosity levels
|
|
||||||
*
|
|
||||||
* This class inherided from [[Action]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionCount(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionCount = module.exports = function ActionCount(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.nargs = 0;
|
|
||||||
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionCount, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionCount#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Save result in namespace object
|
|
||||||
**/
|
|
||||||
ActionCount.prototype.call = function (parser, namespace) {
|
|
||||||
namespace.set(this.dest, (namespace[this.dest] || 0) + 1);
|
|
||||||
};
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionHelp
|
|
||||||
*
|
|
||||||
* Support action for printing help
|
|
||||||
* This class inherided from [[Action]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionHelp(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionHelp = module.exports = function ActionHelp(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (options.defaultValue !== null) {
|
|
||||||
options.defaultValue = options.defaultValue;
|
|
||||||
} else {
|
|
||||||
options.defaultValue = c.SUPPRESS;
|
|
||||||
}
|
|
||||||
options.dest = (options.dest !== null ? options.dest : c.SUPPRESS);
|
|
||||||
options.nargs = 0;
|
|
||||||
Action.call(this, options);
|
|
||||||
|
|
||||||
};
|
|
||||||
util.inherits(ActionHelp, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionHelp#call(parser, namespace, values, optionString)
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Print help and exit
|
|
||||||
**/
|
|
||||||
ActionHelp.prototype.call = function (parser) {
|
|
||||||
parser.printHelp();
|
|
||||||
parser.exit();
|
|
||||||
};
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionStore
|
|
||||||
*
|
|
||||||
* This action just stores the argument’s value. This is the default action.
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionStore(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionStore = module.exports = function ActionStore(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (this.nargs <= 0) {
|
|
||||||
throw new Error('nargs for store actions must be > 0; if you ' +
|
|
||||||
'have nothing to store, actions such as store ' +
|
|
||||||
'true or store const may be more appropriate');
|
|
||||||
|
|
||||||
}
|
|
||||||
if (typeof this.constant !== 'undefined' && this.nargs !== c.OPTIONAL) {
|
|
||||||
throw new Error('nargs must be OPTIONAL to supply const');
|
|
||||||
}
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionStore, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionStore#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Save result in namespace object
|
|
||||||
**/
|
|
||||||
ActionStore.prototype.call = function (parser, namespace, values) {
|
|
||||||
namespace.set(this.dest, values);
|
|
||||||
};
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionStoreConstant
|
|
||||||
*
|
|
||||||
* This action stores the value specified by the const keyword argument.
|
|
||||||
* (Note that the const keyword argument defaults to the rather unhelpful null.)
|
|
||||||
* The 'store_const' action is most commonly used with optional
|
|
||||||
* arguments that specify some sort of flag.
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../../action');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionStoreConstant(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionStoreConstant = module.exports = function ActionStoreConstant(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.nargs = 0;
|
|
||||||
if (typeof options.constant === 'undefined') {
|
|
||||||
throw new Error('constant option is required for storeAction');
|
|
||||||
}
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionStoreConstant, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionStoreConstant#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Save result in namespace object
|
|
||||||
**/
|
|
||||||
ActionStoreConstant.prototype.call = function (parser, namespace) {
|
|
||||||
namespace.set(this.dest, this.constant);
|
|
||||||
};
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionStoreFalse
|
|
||||||
*
|
|
||||||
* This action store the values False respectively.
|
|
||||||
* This is special cases of 'storeConst'
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var ActionStoreConstant = require('./constant');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionStoreFalse(options)
|
|
||||||
* - options (object): hash of options see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionStoreFalse = module.exports = function ActionStoreFalse(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.constant = false;
|
|
||||||
options.defaultValue = options.defaultValue !== null ? options.defaultValue : true;
|
|
||||||
ActionStoreConstant.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionStoreFalse, ActionStoreConstant);
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionStoreTrue
|
|
||||||
*
|
|
||||||
* This action store the values True respectively.
|
|
||||||
* This isspecial cases of 'storeConst'
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var ActionStoreConstant = require('./constant');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionStoreTrue(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionStoreTrue = module.exports = function ActionStoreTrue(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.constant = true;
|
|
||||||
options.defaultValue = options.defaultValue !== null ? options.defaultValue : false;
|
|
||||||
ActionStoreConstant.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionStoreTrue, ActionStoreConstant);
|
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
/** internal
|
|
||||||
* class ActionSubparsers
|
|
||||||
*
|
|
||||||
* Support the creation of such sub-commands with the addSubparsers()
|
|
||||||
*
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
var format = require('util').format;
|
|
||||||
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
var argumentErrorHelper = require('../argument/error');
|
|
||||||
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ChoicesPseudoAction(name, help)
|
|
||||||
*
|
|
||||||
* Create pseudo action for correct help text
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
function ChoicesPseudoAction(name, help) {
|
|
||||||
var options = {
|
|
||||||
optionStrings: [],
|
|
||||||
dest: name,
|
|
||||||
help: help
|
|
||||||
};
|
|
||||||
|
|
||||||
Action.call(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(ChoicesPseudoAction, Action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new ActionSubparsers(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
function ActionSubparsers(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.dest = options.dest || c.SUPPRESS;
|
|
||||||
options.nargs = c.PARSER;
|
|
||||||
|
|
||||||
this.debug = (options.debug === true);
|
|
||||||
|
|
||||||
this._progPrefix = options.prog;
|
|
||||||
this._parserClass = options.parserClass;
|
|
||||||
this._nameParserMap = {};
|
|
||||||
this._choicesActions = [];
|
|
||||||
|
|
||||||
options.choices = this._nameParserMap;
|
|
||||||
Action.call(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(ActionSubparsers, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionSubparsers#addParser(name, options) -> ArgumentParser
|
|
||||||
* - name (string): sub-command name
|
|
||||||
* - options (object): see [[ArgumentParser.new]]
|
|
||||||
*
|
|
||||||
* Note:
|
|
||||||
* addParser supports an additional aliases option,
|
|
||||||
* which allows multiple strings to refer to the same subparser.
|
|
||||||
* This example, like svn, aliases co as a shorthand for checkout
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
ActionSubparsers.prototype.addParser = function (name, options) {
|
|
||||||
var parser;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
options.debug = (this.debug === true);
|
|
||||||
|
|
||||||
// set program from the existing prefix
|
|
||||||
if (!options.prog) {
|
|
||||||
options.prog = this._progPrefix + ' ' + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var aliases = options.aliases || [];
|
|
||||||
|
|
||||||
// create a pseudo-action to hold the choice help
|
|
||||||
if (!!options.help || typeof options.help === 'string') {
|
|
||||||
var help = options.help;
|
|
||||||
delete options.help;
|
|
||||||
|
|
||||||
var choiceAction = new ChoicesPseudoAction(name, help);
|
|
||||||
this._choicesActions.push(choiceAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the parser and add it to the map
|
|
||||||
parser = new this._parserClass(options);
|
|
||||||
this._nameParserMap[name] = parser;
|
|
||||||
|
|
||||||
// make parser available under aliases also
|
|
||||||
aliases.forEach(function (alias) {
|
|
||||||
self._nameParserMap[alias] = parser;
|
|
||||||
});
|
|
||||||
|
|
||||||
return parser;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionSubparsers.prototype._getSubactions = function () {
|
|
||||||
return this._choicesActions;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionSubparsers#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Call the action. Parse input aguments
|
|
||||||
**/
|
|
||||||
ActionSubparsers.prototype.call = function (parser, namespace, values) {
|
|
||||||
var parserName = values[0];
|
|
||||||
var argStrings = values.slice(1);
|
|
||||||
|
|
||||||
// set the parser name if requested
|
|
||||||
if (this.dest !== c.SUPPRESS) {
|
|
||||||
namespace[this.dest] = parserName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// select the parser
|
|
||||||
if (this._nameParserMap[parserName]) {
|
|
||||||
parser = this._nameParserMap[parserName];
|
|
||||||
} else {
|
|
||||||
throw argumentErrorHelper(format(
|
|
||||||
'Unknown parser "%s" (choices: [%s]).',
|
|
||||||
parserName,
|
|
||||||
Object.keys(this._nameParserMap).join(', ')
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse all the remaining options into the namespace
|
|
||||||
parser.parseArgs(argStrings, namespace);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = ActionSubparsers;
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
/*:nodoc:*
|
|
||||||
* class ActionVersion
|
|
||||||
*
|
|
||||||
* Support action for printing program version
|
|
||||||
* This class inherited from [[Action]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var Action = require('../action');
|
|
||||||
|
|
||||||
//
|
|
||||||
// Constants
|
|
||||||
//
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* new ActionVersion(options)
|
|
||||||
* - options (object): options hash see [[Action.new]]
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ActionVersion = module.exports = function ActionVersion(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.defaultValue = (options.defaultValue ? options.defaultValue : c.SUPPRESS);
|
|
||||||
options.dest = (options.dest || c.SUPPRESS);
|
|
||||||
options.nargs = 0;
|
|
||||||
this.version = options.version;
|
|
||||||
Action.call(this, options);
|
|
||||||
};
|
|
||||||
util.inherits(ActionVersion, Action);
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* ActionVersion#call(parser, namespace, values, optionString) -> Void
|
|
||||||
* - parser (ArgumentParser): current parser
|
|
||||||
* - namespace (Namespace): namespace for output data
|
|
||||||
* - values (Array): parsed values
|
|
||||||
* - optionString (Array): input option string(not parsed)
|
|
||||||
*
|
|
||||||
* Print version and exit
|
|
||||||
**/
|
|
||||||
ActionVersion.prototype.call = function (parser) {
|
|
||||||
var version = this.version || parser.version;
|
|
||||||
var formatter = parser._getFormatter();
|
|
||||||
formatter.addText(version);
|
|
||||||
parser.exit(0, formatter.formatHelp());
|
|
||||||
};
|
|
||||||
-482
@@ -1,482 +0,0 @@
|
|||||||
/** internal
|
|
||||||
* class ActionContainer
|
|
||||||
*
|
|
||||||
* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]
|
|
||||||
**/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var format = require('util').format;
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('./const');
|
|
||||||
|
|
||||||
var $$ = require('./utils');
|
|
||||||
|
|
||||||
//Actions
|
|
||||||
var ActionHelp = require('./action/help');
|
|
||||||
var ActionAppend = require('./action/append');
|
|
||||||
var ActionAppendConstant = require('./action/append/constant');
|
|
||||||
var ActionCount = require('./action/count');
|
|
||||||
var ActionStore = require('./action/store');
|
|
||||||
var ActionStoreConstant = require('./action/store/constant');
|
|
||||||
var ActionStoreTrue = require('./action/store/true');
|
|
||||||
var ActionStoreFalse = require('./action/store/false');
|
|
||||||
var ActionVersion = require('./action/version');
|
|
||||||
var ActionSubparsers = require('./action/subparsers');
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
var argumentErrorHelper = require('./argument/error');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new ActionContainer(options)
|
|
||||||
*
|
|
||||||
* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]
|
|
||||||
*
|
|
||||||
* ##### Options:
|
|
||||||
*
|
|
||||||
* - `description` -- A description of what the program does
|
|
||||||
* - `prefixChars` -- Characters that prefix optional arguments
|
|
||||||
* - `argumentDefault` -- The default value for all arguments
|
|
||||||
* - `conflictHandler` -- The conflict handler to use for duplicate arguments
|
|
||||||
**/
|
|
||||||
var ActionContainer = module.exports = function ActionContainer(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this.description = options.description;
|
|
||||||
this.argumentDefault = options.argumentDefault;
|
|
||||||
this.prefixChars = options.prefixChars || '';
|
|
||||||
this.conflictHandler = options.conflictHandler;
|
|
||||||
|
|
||||||
// set up registries
|
|
||||||
this._registries = {};
|
|
||||||
|
|
||||||
// register actions
|
|
||||||
this.register('action', null, ActionStore);
|
|
||||||
this.register('action', 'store', ActionStore);
|
|
||||||
this.register('action', 'storeConst', ActionStoreConstant);
|
|
||||||
this.register('action', 'storeTrue', ActionStoreTrue);
|
|
||||||
this.register('action', 'storeFalse', ActionStoreFalse);
|
|
||||||
this.register('action', 'append', ActionAppend);
|
|
||||||
this.register('action', 'appendConst', ActionAppendConstant);
|
|
||||||
this.register('action', 'count', ActionCount);
|
|
||||||
this.register('action', 'help', ActionHelp);
|
|
||||||
this.register('action', 'version', ActionVersion);
|
|
||||||
this.register('action', 'parsers', ActionSubparsers);
|
|
||||||
|
|
||||||
// raise an exception if the conflict handler is invalid
|
|
||||||
this._getHandler();
|
|
||||||
|
|
||||||
// action storage
|
|
||||||
this._actions = [];
|
|
||||||
this._optionStringActions = {};
|
|
||||||
|
|
||||||
// groups
|
|
||||||
this._actionGroups = [];
|
|
||||||
this._mutuallyExclusiveGroups = [];
|
|
||||||
|
|
||||||
// defaults storage
|
|
||||||
this._defaults = {};
|
|
||||||
|
|
||||||
// determines whether an "option" looks like a negative number
|
|
||||||
// -1, -1.5 -5e+4
|
|
||||||
this._regexpNegativeNumber = new RegExp('^[-]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$');
|
|
||||||
|
|
||||||
// whether or not there are any optionals that look like negative
|
|
||||||
// numbers -- uses a list so it can be shared and edited
|
|
||||||
this._hasNegativeNumberOptionals = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Groups must be required, then ActionContainer already defined
|
|
||||||
var ArgumentGroup = require('./argument/group');
|
|
||||||
var MutuallyExclusiveGroup = require('./argument/exclusive');
|
|
||||||
|
|
||||||
//
|
|
||||||
// Registration methods
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#register(registryName, value, object) -> Void
|
|
||||||
* - registryName (String) : object type action|type
|
|
||||||
* - value (string) : keyword
|
|
||||||
* - object (Object|Function) : handler
|
|
||||||
*
|
|
||||||
* Register handlers
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.register = function (registryName, value, object) {
|
|
||||||
this._registries[registryName] = this._registries[registryName] || {};
|
|
||||||
this._registries[registryName][value] = object;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._registryGet = function (registryName, value, defaultValue) {
|
|
||||||
if (arguments.length < 3) {
|
|
||||||
defaultValue = null;
|
|
||||||
}
|
|
||||||
return this._registries[registryName][value] || defaultValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Namespace default accessor methods
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#setDefaults(options) -> Void
|
|
||||||
* - options (object):hash of options see [[Action.new]]
|
|
||||||
*
|
|
||||||
* Set defaults
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.setDefaults = function (options) {
|
|
||||||
options = options || {};
|
|
||||||
for (var property in options) {
|
|
||||||
if ($$.has(options, property)) {
|
|
||||||
this._defaults[property] = options[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if these defaults match any existing arguments, replace the previous
|
|
||||||
// default on the object with the new one
|
|
||||||
this._actions.forEach(function (action) {
|
|
||||||
if ($$.has(options, action.dest)) {
|
|
||||||
action.defaultValue = options[action.dest];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#getDefault(dest) -> Mixed
|
|
||||||
* - dest (string): action destination
|
|
||||||
*
|
|
||||||
* Return action default value
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.getDefault = function (dest) {
|
|
||||||
var result = $$.has(this._defaults, dest) ? this._defaults[dest] : null;
|
|
||||||
|
|
||||||
this._actions.forEach(function (action) {
|
|
||||||
if (action.dest === dest && $$.has(action, 'defaultValue')) {
|
|
||||||
result = action.defaultValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// Adding argument actions
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#addArgument(args, options) -> Object
|
|
||||||
* - args (String|Array): argument key, or array of argument keys
|
|
||||||
* - options (Object): action objects see [[Action.new]]
|
|
||||||
*
|
|
||||||
* #### Examples
|
|
||||||
* - addArgument([ '-f', '--foo' ], { action: 'store', defaultValue: 1, ... })
|
|
||||||
* - addArgument([ 'bar' ], { action: 'store', nargs: 1, ... })
|
|
||||||
* - addArgument('--baz', { action: 'store', nargs: 1, ... })
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.addArgument = function (args, options) {
|
|
||||||
args = args;
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
if (typeof args === 'string') {
|
|
||||||
args = [ args ];
|
|
||||||
}
|
|
||||||
if (!Array.isArray(args)) {
|
|
||||||
throw new TypeError('addArgument first argument should be a string or an array');
|
|
||||||
}
|
|
||||||
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
||||||
throw new TypeError('addArgument second argument should be a hash');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no positional args are supplied or only one is supplied and
|
|
||||||
// it doesn't look like an option string, parse a positional argument
|
|
||||||
if (!args || args.length === 1 && this.prefixChars.indexOf(args[0][0]) < 0) {
|
|
||||||
if (args && !!options.dest) {
|
|
||||||
throw new Error('dest supplied twice for positional argument');
|
|
||||||
}
|
|
||||||
options = this._getPositional(args, options);
|
|
||||||
|
|
||||||
// otherwise, we're adding an optional argument
|
|
||||||
} else {
|
|
||||||
options = this._getOptional(args, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no default was supplied, use the parser-level default
|
|
||||||
if (typeof options.defaultValue === 'undefined') {
|
|
||||||
var dest = options.dest;
|
|
||||||
if ($$.has(this._defaults, dest)) {
|
|
||||||
options.defaultValue = this._defaults[dest];
|
|
||||||
} else if (typeof this.argumentDefault !== 'undefined') {
|
|
||||||
options.defaultValue = this.argumentDefault;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the action object, and add it to the parser
|
|
||||||
var ActionClass = this._popActionClass(options);
|
|
||||||
if (typeof ActionClass !== 'function') {
|
|
||||||
throw new Error(format('Unknown action "%s".', ActionClass));
|
|
||||||
}
|
|
||||||
var action = new ActionClass(options);
|
|
||||||
|
|
||||||
// throw an error if the action type is not callable
|
|
||||||
var typeFunction = this._registryGet('type', action.type, action.type);
|
|
||||||
if (typeof typeFunction !== 'function') {
|
|
||||||
throw new Error(format('"%s" is not callable', typeFunction));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._addAction(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#addArgumentGroup(options) -> ArgumentGroup
|
|
||||||
* - options (Object): hash of options see [[ArgumentGroup.new]]
|
|
||||||
*
|
|
||||||
* Create new arguments groups
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.addArgumentGroup = function (options) {
|
|
||||||
var group = new ArgumentGroup(this, options);
|
|
||||||
this._actionGroups.push(group);
|
|
||||||
return group;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionContainer#addMutuallyExclusiveGroup(options) -> ArgumentGroup
|
|
||||||
* - options (Object): {required: false}
|
|
||||||
*
|
|
||||||
* Create new mutual exclusive groups
|
|
||||||
**/
|
|
||||||
ActionContainer.prototype.addMutuallyExclusiveGroup = function (options) {
|
|
||||||
var group = new MutuallyExclusiveGroup(this, options);
|
|
||||||
this._mutuallyExclusiveGroups.push(group);
|
|
||||||
return group;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._addAction = function (action) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// resolve any conflicts
|
|
||||||
this._checkConflict(action);
|
|
||||||
|
|
||||||
// add to actions list
|
|
||||||
this._actions.push(action);
|
|
||||||
action.container = this;
|
|
||||||
|
|
||||||
// index the action by any option strings it has
|
|
||||||
action.optionStrings.forEach(function (optionString) {
|
|
||||||
self._optionStringActions[optionString] = action;
|
|
||||||
});
|
|
||||||
|
|
||||||
// set the flag if any option strings look like negative numbers
|
|
||||||
action.optionStrings.forEach(function (optionString) {
|
|
||||||
if (optionString.match(self._regexpNegativeNumber)) {
|
|
||||||
if (!self._hasNegativeNumberOptionals.some(Boolean)) {
|
|
||||||
self._hasNegativeNumberOptionals.push(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// return the created action
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._removeAction = function (action) {
|
|
||||||
var actionIndex = this._actions.indexOf(action);
|
|
||||||
if (actionIndex >= 0) {
|
|
||||||
this._actions.splice(actionIndex, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._addContainerActions = function (container) {
|
|
||||||
// collect groups by titles
|
|
||||||
var titleGroupMap = {};
|
|
||||||
this._actionGroups.forEach(function (group) {
|
|
||||||
if (titleGroupMap[group.title]) {
|
|
||||||
throw new Error(format('Cannot merge actions - two groups are named "%s".', group.title));
|
|
||||||
}
|
|
||||||
titleGroupMap[group.title] = group;
|
|
||||||
});
|
|
||||||
|
|
||||||
// map each action to its group
|
|
||||||
var groupMap = {};
|
|
||||||
function actionHash(action) {
|
|
||||||
// unique (hopefully?) string suitable as dictionary key
|
|
||||||
return action.getName();
|
|
||||||
}
|
|
||||||
container._actionGroups.forEach(function (group) {
|
|
||||||
// if a group with the title exists, use that, otherwise
|
|
||||||
// create a new group matching the container's group
|
|
||||||
if (!titleGroupMap[group.title]) {
|
|
||||||
titleGroupMap[group.title] = this.addArgumentGroup({
|
|
||||||
title: group.title,
|
|
||||||
description: group.description
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// map the actions to their new group
|
|
||||||
group._groupActions.forEach(function (action) {
|
|
||||||
groupMap[actionHash(action)] = titleGroupMap[group.title];
|
|
||||||
});
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
// add container's mutually exclusive groups
|
|
||||||
// NOTE: if add_mutually_exclusive_group ever gains title= and
|
|
||||||
// description= then this code will need to be expanded as above
|
|
||||||
var mutexGroup;
|
|
||||||
container._mutuallyExclusiveGroups.forEach(function (group) {
|
|
||||||
mutexGroup = this.addMutuallyExclusiveGroup({
|
|
||||||
required: group.required
|
|
||||||
});
|
|
||||||
// map the actions to their new mutex group
|
|
||||||
group._groupActions.forEach(function (action) {
|
|
||||||
groupMap[actionHash(action)] = mutexGroup;
|
|
||||||
});
|
|
||||||
}, this); // forEach takes a 'this' argument
|
|
||||||
|
|
||||||
// add all actions to this container or their group
|
|
||||||
container._actions.forEach(function (action) {
|
|
||||||
var key = actionHash(action);
|
|
||||||
if (groupMap[key]) {
|
|
||||||
groupMap[key]._addAction(action);
|
|
||||||
} else {
|
|
||||||
this._addAction(action);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._getPositional = function (dest, options) {
|
|
||||||
if (Array.isArray(dest)) {
|
|
||||||
dest = dest[0];
|
|
||||||
}
|
|
||||||
// make sure required is not specified
|
|
||||||
if (options.required) {
|
|
||||||
throw new Error('"required" is an invalid argument for positionals.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark positional arguments as required if at least one is
|
|
||||||
// always required
|
|
||||||
if (options.nargs !== c.OPTIONAL && options.nargs !== c.ZERO_OR_MORE) {
|
|
||||||
options.required = true;
|
|
||||||
}
|
|
||||||
if (options.nargs === c.ZERO_OR_MORE && typeof options.defaultValue === 'undefined') {
|
|
||||||
options.required = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the keyword arguments with no option strings
|
|
||||||
options.dest = dest;
|
|
||||||
options.optionStrings = [];
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._getOptional = function (args, options) {
|
|
||||||
var prefixChars = this.prefixChars;
|
|
||||||
var optionStrings = [];
|
|
||||||
var optionStringsLong = [];
|
|
||||||
|
|
||||||
// determine short and long option strings
|
|
||||||
args.forEach(function (optionString) {
|
|
||||||
// error on strings that don't start with an appropriate prefix
|
|
||||||
if (prefixChars.indexOf(optionString[0]) < 0) {
|
|
||||||
throw new Error(format('Invalid option string "%s": must start with a "%s".',
|
|
||||||
optionString,
|
|
||||||
prefixChars
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// strings starting with two prefix characters are long options
|
|
||||||
optionStrings.push(optionString);
|
|
||||||
if (optionString.length > 1 && prefixChars.indexOf(optionString[1]) >= 0) {
|
|
||||||
optionStringsLong.push(optionString);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// infer dest, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
|
||||||
var dest = options.dest || null;
|
|
||||||
delete options.dest;
|
|
||||||
|
|
||||||
if (!dest) {
|
|
||||||
var optionStringDest = optionStringsLong.length ? optionStringsLong[0] : optionStrings[0];
|
|
||||||
dest = $$.trimChars(optionStringDest, this.prefixChars);
|
|
||||||
|
|
||||||
if (dest.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
format('dest= is required for options like "%s"', optionStrings.join(', '))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
dest = dest.replace(/-/g, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the updated keyword arguments
|
|
||||||
options.dest = dest;
|
|
||||||
options.optionStrings = optionStrings;
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._popActionClass = function (options, defaultValue) {
|
|
||||||
defaultValue = defaultValue || null;
|
|
||||||
|
|
||||||
var action = (options.action || defaultValue);
|
|
||||||
delete options.action;
|
|
||||||
|
|
||||||
var actionClass = this._registryGet('action', action, action);
|
|
||||||
return actionClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._getHandler = function () {
|
|
||||||
var handlerString = this.conflictHandler;
|
|
||||||
var handlerFuncName = '_handleConflict' + $$.capitalize(handlerString);
|
|
||||||
var func = this[handlerFuncName];
|
|
||||||
if (typeof func === 'undefined') {
|
|
||||||
var msg = 'invalid conflict resolution value: ' + handlerString;
|
|
||||||
throw new Error(msg);
|
|
||||||
} else {
|
|
||||||
return func;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._checkConflict = function (action) {
|
|
||||||
var optionStringActions = this._optionStringActions;
|
|
||||||
var conflictOptionals = [];
|
|
||||||
|
|
||||||
// find all options that conflict with this option
|
|
||||||
// collect pairs, the string, and an existing action that it conflicts with
|
|
||||||
action.optionStrings.forEach(function (optionString) {
|
|
||||||
var conflOptional = optionStringActions[optionString];
|
|
||||||
if (typeof conflOptional !== 'undefined') {
|
|
||||||
conflictOptionals.push([ optionString, conflOptional ]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (conflictOptionals.length > 0) {
|
|
||||||
var conflictHandler = this._getHandler();
|
|
||||||
conflictHandler.call(this, action, conflictOptionals);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._handleConflictError = function (action, conflOptionals) {
|
|
||||||
var conflicts = conflOptionals.map(function (pair) { return pair[0]; });
|
|
||||||
conflicts = conflicts.join(', ');
|
|
||||||
throw argumentErrorHelper(
|
|
||||||
action,
|
|
||||||
format('Conflicting option string(s): %s', conflicts)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionContainer.prototype._handleConflictResolve = function (action, conflOptionals) {
|
|
||||||
// remove all conflicting options
|
|
||||||
var self = this;
|
|
||||||
conflOptionals.forEach(function (pair) {
|
|
||||||
var optionString = pair[0];
|
|
||||||
var conflictingAction = pair[1];
|
|
||||||
// remove the conflicting option string
|
|
||||||
var i = conflictingAction.optionStrings.indexOf(optionString);
|
|
||||||
if (i >= 0) {
|
|
||||||
conflictingAction.optionStrings.splice(i, 1);
|
|
||||||
}
|
|
||||||
delete self._optionStringActions[optionString];
|
|
||||||
// if the option now has no option string, remove it from the
|
|
||||||
// container holding it
|
|
||||||
if (conflictingAction.optionStrings.length === 0) {
|
|
||||||
conflictingAction.container._removeAction(conflictingAction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports.ArgumentParser = require('./argument_parser.js');
|
|
||||||
module.exports.Namespace = require('./namespace');
|
|
||||||
module.exports.Action = require('./action');
|
|
||||||
module.exports.HelpFormatter = require('./help/formatter.js');
|
|
||||||
module.exports.Const = require('./const.js');
|
|
||||||
|
|
||||||
module.exports.ArgumentDefaultsHelpFormatter =
|
|
||||||
require('./help/added_formatters.js').ArgumentDefaultsHelpFormatter;
|
|
||||||
module.exports.RawDescriptionHelpFormatter =
|
|
||||||
require('./help/added_formatters.js').RawDescriptionHelpFormatter;
|
|
||||||
module.exports.RawTextHelpFormatter =
|
|
||||||
require('./help/added_formatters.js').RawTextHelpFormatter;
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
var format = require('util').format;
|
|
||||||
|
|
||||||
|
|
||||||
var ERR_CODE = 'ARGError';
|
|
||||||
|
|
||||||
/*:nodoc:*
|
|
||||||
* argumentError(argument, message) -> TypeError
|
|
||||||
* - argument (Object): action with broken argument
|
|
||||||
* - message (String): error message
|
|
||||||
*
|
|
||||||
* Error format helper. An error from creating or using an argument
|
|
||||||
* (optional or positional). The string value of this exception
|
|
||||||
* is the message, augmented with information
|
|
||||||
* about the argument that caused it.
|
|
||||||
*
|
|
||||||
* #####Example
|
|
||||||
*
|
|
||||||
* var argumentErrorHelper = require('./argument/error');
|
|
||||||
* if (conflictOptionals.length > 0) {
|
|
||||||
* throw argumentErrorHelper(
|
|
||||||
* action,
|
|
||||||
* format('Conflicting option string(s): %s', conflictOptionals.join(', '))
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
module.exports = function (argument, message) {
|
|
||||||
var argumentName = null;
|
|
||||||
var errMessage;
|
|
||||||
var err;
|
|
||||||
|
|
||||||
if (argument.getName) {
|
|
||||||
argumentName = argument.getName();
|
|
||||||
} else {
|
|
||||||
argumentName = '' + argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!argumentName) {
|
|
||||||
errMessage = message;
|
|
||||||
} else {
|
|
||||||
errMessage = format('argument "%s": %s', argumentName, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
err = new TypeError(errMessage);
|
|
||||||
err.code = ERR_CODE;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
/** internal
|
|
||||||
* class MutuallyExclusiveGroup
|
|
||||||
*
|
|
||||||
* Group arguments.
|
|
||||||
* By default, ArgumentParser groups command-line arguments
|
|
||||||
* into “positional arguments” and “optional arguments”
|
|
||||||
* when displaying help messages. When there is a better
|
|
||||||
* conceptual grouping of arguments than this default one,
|
|
||||||
* appropriate groups can be created using the addArgumentGroup() method
|
|
||||||
*
|
|
||||||
* This class inherited from [[ArgumentContainer]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var ArgumentGroup = require('./group');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new MutuallyExclusiveGroup(container, options)
|
|
||||||
* - container (object): main container
|
|
||||||
* - options (object): options.required -> true/false
|
|
||||||
*
|
|
||||||
* `required` could be an argument itself, but making it a property of
|
|
||||||
* the options argument is more consistent with the JS adaptation of the Python)
|
|
||||||
**/
|
|
||||||
var MutuallyExclusiveGroup = module.exports = function MutuallyExclusiveGroup(container, options) {
|
|
||||||
var required;
|
|
||||||
options = options || {};
|
|
||||||
required = options.required || false;
|
|
||||||
ArgumentGroup.call(this, container);
|
|
||||||
this.required = required;
|
|
||||||
|
|
||||||
};
|
|
||||||
util.inherits(MutuallyExclusiveGroup, ArgumentGroup);
|
|
||||||
|
|
||||||
|
|
||||||
MutuallyExclusiveGroup.prototype._addAction = function (action) {
|
|
||||||
var msg;
|
|
||||||
if (action.required) {
|
|
||||||
msg = 'mutually exclusive arguments must be optional';
|
|
||||||
throw new Error(msg);
|
|
||||||
}
|
|
||||||
action = this._container._addAction(action);
|
|
||||||
this._groupActions.push(action);
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
MutuallyExclusiveGroup.prototype._removeAction = function (action) {
|
|
||||||
this._container._removeAction(action);
|
|
||||||
this._groupActions.remove(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
/** internal
|
|
||||||
* class ArgumentGroup
|
|
||||||
*
|
|
||||||
* Group arguments.
|
|
||||||
* By default, ArgumentParser groups command-line arguments
|
|
||||||
* into “positional arguments” and “optional arguments”
|
|
||||||
* when displaying help messages. When there is a better
|
|
||||||
* conceptual grouping of arguments than this default one,
|
|
||||||
* appropriate groups can be created using the addArgumentGroup() method
|
|
||||||
*
|
|
||||||
* This class inherited from [[ArgumentContainer]]
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var ActionContainer = require('../action_container');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new ArgumentGroup(container, options)
|
|
||||||
* - container (object): main container
|
|
||||||
* - options (object): hash of group options
|
|
||||||
*
|
|
||||||
* #### options
|
|
||||||
* - **prefixChars** group name prefix
|
|
||||||
* - **argumentDefault** default argument value
|
|
||||||
* - **title** group title
|
|
||||||
* - **description** group description
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var ArgumentGroup = module.exports = function ArgumentGroup(container, options) {
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// add any missing keyword arguments by checking the container
|
|
||||||
options.conflictHandler = (options.conflictHandler || container.conflictHandler);
|
|
||||||
options.prefixChars = (options.prefixChars || container.prefixChars);
|
|
||||||
options.argumentDefault = (options.argumentDefault || container.argumentDefault);
|
|
||||||
|
|
||||||
ActionContainer.call(this, options);
|
|
||||||
|
|
||||||
// group attributes
|
|
||||||
this.title = options.title;
|
|
||||||
this._groupActions = [];
|
|
||||||
|
|
||||||
// share most attributes with the container
|
|
||||||
this._container = container;
|
|
||||||
this._registries = container._registries;
|
|
||||||
this._actions = container._actions;
|
|
||||||
this._optionStringActions = container._optionStringActions;
|
|
||||||
this._defaults = container._defaults;
|
|
||||||
this._hasNegativeNumberOptionals = container._hasNegativeNumberOptionals;
|
|
||||||
this._mutuallyExclusiveGroups = container._mutuallyExclusiveGroups;
|
|
||||||
};
|
|
||||||
util.inherits(ArgumentGroup, ActionContainer);
|
|
||||||
|
|
||||||
|
|
||||||
ArgumentGroup.prototype._addAction = function (action) {
|
|
||||||
// Parent add action
|
|
||||||
action = ActionContainer.prototype._addAction.call(this, action);
|
|
||||||
this._groupActions.push(action);
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
ArgumentGroup.prototype._removeAction = function (action) {
|
|
||||||
// Parent remove action
|
|
||||||
ActionContainer.prototype._removeAction.call(this, action);
|
|
||||||
var actionIndex = this._groupActions.indexOf(action);
|
|
||||||
if (actionIndex >= 0) {
|
|
||||||
this._groupActions.splice(actionIndex, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
-1161
File diff suppressed because it is too large.
Load diff
-21
@@ -1,21 +0,0 @@
|
|||||||
//
|
|
||||||
// Constants
|
|
||||||
//
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports.EOL = '\n';
|
|
||||||
|
|
||||||
module.exports.SUPPRESS = '==SUPPRESS==';
|
|
||||||
|
|
||||||
module.exports.OPTIONAL = '?';
|
|
||||||
|
|
||||||
module.exports.ZERO_OR_MORE = '*';
|
|
||||||
|
|
||||||
module.exports.ONE_OR_MORE = '+';
|
|
||||||
|
|
||||||
module.exports.PARSER = 'A...';
|
|
||||||
|
|
||||||
module.exports.REMAINDER = '...';
|
|
||||||
|
|
||||||
module.exports._UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args';
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
var $$ = require('../utils');
|
|
||||||
var HelpFormatter = require('./formatter.js');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new RawDescriptionHelpFormatter(options)
|
|
||||||
* new ArgumentParser({formatterClass: argparse.RawDescriptionHelpFormatter, ...})
|
|
||||||
*
|
|
||||||
* Help message formatter which adds default values to argument help.
|
|
||||||
*
|
|
||||||
* Only the name of this class is considered a public API. All the methods
|
|
||||||
* provided by the class are considered an implementation detail.
|
|
||||||
**/
|
|
||||||
|
|
||||||
function ArgumentDefaultsHelpFormatter(options) {
|
|
||||||
HelpFormatter.call(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(ArgumentDefaultsHelpFormatter, HelpFormatter);
|
|
||||||
|
|
||||||
ArgumentDefaultsHelpFormatter.prototype._getHelpString = function (action) {
|
|
||||||
var help = action.help;
|
|
||||||
if (action.help.indexOf('%(defaultValue)s') === -1) {
|
|
||||||
if (action.defaultValue !== c.SUPPRESS) {
|
|
||||||
var defaulting_nargs = [ c.OPTIONAL, c.ZERO_OR_MORE ];
|
|
||||||
if (action.isOptional() || (defaulting_nargs.indexOf(action.nargs) >= 0)) {
|
|
||||||
help += ' (default: %(defaultValue)s)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return help;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.ArgumentDefaultsHelpFormatter = ArgumentDefaultsHelpFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new RawDescriptionHelpFormatter(options)
|
|
||||||
* new ArgumentParser({formatterClass: argparse.RawDescriptionHelpFormatter, ...})
|
|
||||||
*
|
|
||||||
* Help message formatter which retains any formatting in descriptions.
|
|
||||||
*
|
|
||||||
* Only the name of this class is considered a public API. All the methods
|
|
||||||
* provided by the class are considered an implementation detail.
|
|
||||||
**/
|
|
||||||
|
|
||||||
function RawDescriptionHelpFormatter(options) {
|
|
||||||
HelpFormatter.call(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(RawDescriptionHelpFormatter, HelpFormatter);
|
|
||||||
|
|
||||||
RawDescriptionHelpFormatter.prototype._fillText = function (text, width, indent) {
|
|
||||||
var lines = text.split('\n');
|
|
||||||
lines = lines.map(function (line) {
|
|
||||||
return $$.trimEnd(indent + line);
|
|
||||||
});
|
|
||||||
return lines.join('\n');
|
|
||||||
};
|
|
||||||
module.exports.RawDescriptionHelpFormatter = RawDescriptionHelpFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new RawTextHelpFormatter(options)
|
|
||||||
* new ArgumentParser({formatterClass: argparse.RawTextHelpFormatter, ...})
|
|
||||||
*
|
|
||||||
* Help message formatter which retains formatting of all help text.
|
|
||||||
*
|
|
||||||
* Only the name of this class is considered a public API. All the methods
|
|
||||||
* provided by the class are considered an implementation detail.
|
|
||||||
**/
|
|
||||||
|
|
||||||
function RawTextHelpFormatter(options) {
|
|
||||||
RawDescriptionHelpFormatter.call(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(RawTextHelpFormatter, RawDescriptionHelpFormatter);
|
|
||||||
|
|
||||||
RawTextHelpFormatter.prototype._splitLines = function (text) {
|
|
||||||
return text.split('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.RawTextHelpFormatter = RawTextHelpFormatter;
|
|
||||||
-795
@@ -1,795 +0,0 @@
|
|||||||
/**
|
|
||||||
* class HelpFormatter
|
|
||||||
*
|
|
||||||
* Formatter for generating usage messages and argument help strings. Only the
|
|
||||||
* name of this class is considered a public API. All the methods provided by
|
|
||||||
* the class are considered an implementation detail.
|
|
||||||
*
|
|
||||||
* Do not call in your code, use this class only for inherits your own forvatter
|
|
||||||
*
|
|
||||||
* ToDo add [additonal formatters][1]
|
|
||||||
*
|
|
||||||
* [1]:http://docs.python.org/dev/library/argparse.html#formatter-class
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var sprintf = require('sprintf-js').sprintf;
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
var c = require('../const');
|
|
||||||
|
|
||||||
var $$ = require('../utils');
|
|
||||||
|
|
||||||
|
|
||||||
/*:nodoc:* internal
|
|
||||||
* new Support(parent, heding)
|
|
||||||
* - parent (object): parent section
|
|
||||||
* - heading (string): header string
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
function Section(parent, heading) {
|
|
||||||
this._parent = parent;
|
|
||||||
this._heading = heading;
|
|
||||||
this._items = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*:nodoc:* internal
|
|
||||||
* Section#addItem(callback) -> Void
|
|
||||||
* - callback (array): tuple with function and args
|
|
||||||
*
|
|
||||||
* Add function for single element
|
|
||||||
**/
|
|
||||||
Section.prototype.addItem = function (callback) {
|
|
||||||
this._items.push(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*:nodoc:* internal
|
|
||||||
* Section#formatHelp(formatter) -> string
|
|
||||||
* - formatter (HelpFormatter): current formatter
|
|
||||||
*
|
|
||||||
* Form help section string
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
Section.prototype.formatHelp = function (formatter) {
|
|
||||||
var itemHelp, heading;
|
|
||||||
|
|
||||||
// format the indented section
|
|
||||||
if (this._parent) {
|
|
||||||
formatter._indent();
|
|
||||||
}
|
|
||||||
|
|
||||||
itemHelp = this._items.map(function (item) {
|
|
||||||
var obj, func, args;
|
|
||||||
|
|
||||||
obj = formatter;
|
|
||||||
func = item[0];
|
|
||||||
args = item[1];
|
|
||||||
return func.apply(obj, args);
|
|
||||||
});
|
|
||||||
itemHelp = formatter._joinParts(itemHelp);
|
|
||||||
|
|
||||||
if (this._parent) {
|
|
||||||
formatter._dedent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// return nothing if the section was empty
|
|
||||||
if (!itemHelp) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the heading if the section was non-empty
|
|
||||||
heading = '';
|
|
||||||
if (this._heading && this._heading !== c.SUPPRESS) {
|
|
||||||
var currentIndent = formatter.currentIndent;
|
|
||||||
heading = $$.repeat(' ', currentIndent) + this._heading + ':' + c.EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// join the section-initialize newline, the heading and the help
|
|
||||||
return formatter._joinParts([ c.EOL, heading, itemHelp, c.EOL ]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new HelpFormatter(options)
|
|
||||||
*
|
|
||||||
* #### Options:
|
|
||||||
* - `prog`: program name
|
|
||||||
* - `indentIncriment`: indent step, default value 2
|
|
||||||
* - `maxHelpPosition`: max help position, default value = 24
|
|
||||||
* - `width`: line width
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var HelpFormatter = module.exports = function HelpFormatter(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this._prog = options.prog;
|
|
||||||
|
|
||||||
this._maxHelpPosition = options.maxHelpPosition || 24;
|
|
||||||
this._width = (options.width || ((process.env.COLUMNS || 80) - 2));
|
|
||||||
|
|
||||||
this._currentIndent = 0;
|
|
||||||
this._indentIncriment = options.indentIncriment || 2;
|
|
||||||
this._level = 0;
|
|
||||||
this._actionMaxLength = 0;
|
|
||||||
|
|
||||||
this._rootSection = new Section(null);
|
|
||||||
this._currentSection = this._rootSection;
|
|
||||||
|
|
||||||
this._whitespaceMatcher = new RegExp('\\s+', 'g');
|
|
||||||
this._longBreakMatcher = new RegExp(c.EOL + c.EOL + c.EOL + '+', 'g');
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._indent = function () {
|
|
||||||
this._currentIndent += this._indentIncriment;
|
|
||||||
this._level += 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._dedent = function () {
|
|
||||||
this._currentIndent -= this._indentIncriment;
|
|
||||||
this._level -= 1;
|
|
||||||
if (this._currentIndent < 0) {
|
|
||||||
throw new Error('Indent decreased below 0.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._addItem = function (func, args) {
|
|
||||||
this._currentSection.addItem([ func, args ]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Message building methods
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#startSection(heading) -> Void
|
|
||||||
* - heading (string): header string
|
|
||||||
*
|
|
||||||
* Start new help section
|
|
||||||
*
|
|
||||||
* See alse [code example][1]
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.startSection(actionGroup.title);
|
|
||||||
* formatter.addText(actionGroup.description);
|
|
||||||
* formatter.addArguments(actionGroup._groupActions);
|
|
||||||
* formatter.endSection();
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.startSection = function (heading) {
|
|
||||||
this._indent();
|
|
||||||
var section = new Section(this._currentSection, heading);
|
|
||||||
var func = section.formatHelp.bind(section);
|
|
||||||
this._addItem(func, [ this ]);
|
|
||||||
this._currentSection = section;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#endSection -> Void
|
|
||||||
*
|
|
||||||
* End help section
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.startSection(actionGroup.title);
|
|
||||||
* formatter.addText(actionGroup.description);
|
|
||||||
* formatter.addArguments(actionGroup._groupActions);
|
|
||||||
* formatter.endSection();
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.endSection = function () {
|
|
||||||
this._currentSection = this._currentSection._parent;
|
|
||||||
this._dedent();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#addText(text) -> Void
|
|
||||||
* - text (string): plain text
|
|
||||||
*
|
|
||||||
* Add plain text into current section
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.startSection(actionGroup.title);
|
|
||||||
* formatter.addText(actionGroup.description);
|
|
||||||
* formatter.addArguments(actionGroup._groupActions);
|
|
||||||
* formatter.endSection();
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.addText = function (text) {
|
|
||||||
if (text && text !== c.SUPPRESS) {
|
|
||||||
this._addItem(this._formatText, [ text ]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#addUsage(usage, actions, groups, prefix) -> Void
|
|
||||||
* - usage (string): usage text
|
|
||||||
* - actions (array): actions list
|
|
||||||
* - groups (array): groups list
|
|
||||||
* - prefix (string): usage prefix
|
|
||||||
*
|
|
||||||
* Add usage data into current section
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.addUsage(this.usage, this._actions, []);
|
|
||||||
* return formatter.formatHelp();
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.addUsage = function (usage, actions, groups, prefix) {
|
|
||||||
if (usage !== c.SUPPRESS) {
|
|
||||||
this._addItem(this._formatUsage, [ usage, actions, groups, prefix ]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#addArgument(action) -> Void
|
|
||||||
* - action (object): action
|
|
||||||
*
|
|
||||||
* Add argument into current section
|
|
||||||
*
|
|
||||||
* Single variant of [[HelpFormatter#addArguments]]
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.addArgument = function (action) {
|
|
||||||
if (action.help !== c.SUPPRESS) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// find all invocations
|
|
||||||
var invocations = [ this._formatActionInvocation(action) ];
|
|
||||||
var invocationLength = invocations[0].length;
|
|
||||||
|
|
||||||
var actionLength;
|
|
||||||
|
|
||||||
if (action._getSubactions) {
|
|
||||||
this._indent();
|
|
||||||
action._getSubactions().forEach(function (subaction) {
|
|
||||||
|
|
||||||
var invocationNew = self._formatActionInvocation(subaction);
|
|
||||||
invocations.push(invocationNew);
|
|
||||||
invocationLength = Math.max(invocationLength, invocationNew.length);
|
|
||||||
|
|
||||||
});
|
|
||||||
this._dedent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the maximum item length
|
|
||||||
actionLength = invocationLength + this._currentIndent;
|
|
||||||
this._actionMaxLength = Math.max(this._actionMaxLength, actionLength);
|
|
||||||
|
|
||||||
// add the item to the list
|
|
||||||
this._addItem(this._formatAction, [ action ]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#addArguments(actions) -> Void
|
|
||||||
* - actions (array): actions list
|
|
||||||
*
|
|
||||||
* Mass add arguments into current section
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.startSection(actionGroup.title);
|
|
||||||
* formatter.addText(actionGroup.description);
|
|
||||||
* formatter.addArguments(actionGroup._groupActions);
|
|
||||||
* formatter.endSection();
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.addArguments = function (actions) {
|
|
||||||
var self = this;
|
|
||||||
actions.forEach(function (action) {
|
|
||||||
self.addArgument(action);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Help-formatting methods
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelpFormatter#formatHelp -> string
|
|
||||||
*
|
|
||||||
* Format help
|
|
||||||
*
|
|
||||||
* ##### Example
|
|
||||||
*
|
|
||||||
* formatter.addText(this.epilog);
|
|
||||||
* return formatter.formatHelp();
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
HelpFormatter.prototype.formatHelp = function () {
|
|
||||||
var help = this._rootSection.formatHelp(this);
|
|
||||||
if (help) {
|
|
||||||
help = help.replace(this._longBreakMatcher, c.EOL + c.EOL);
|
|
||||||
help = $$.trimChars(help, c.EOL) + c.EOL;
|
|
||||||
}
|
|
||||||
return help;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._joinParts = function (partStrings) {
|
|
||||||
return partStrings.filter(function (part) {
|
|
||||||
return (part && part !== c.SUPPRESS);
|
|
||||||
}).join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatUsage = function (usage, actions, groups, prefix) {
|
|
||||||
if (!prefix && typeof prefix !== 'string') {
|
|
||||||
prefix = 'usage: ';
|
|
||||||
}
|
|
||||||
|
|
||||||
actions = actions || [];
|
|
||||||
groups = groups || [];
|
|
||||||
|
|
||||||
|
|
||||||
// if usage is specified, use that
|
|
||||||
if (usage) {
|
|
||||||
usage = sprintf(usage, { prog: this._prog });
|
|
||||||
|
|
||||||
// if no optionals or positionals are available, usage is just prog
|
|
||||||
} else if (!usage && actions.length === 0) {
|
|
||||||
usage = this._prog;
|
|
||||||
|
|
||||||
// if optionals and positionals are available, calculate usage
|
|
||||||
} else if (!usage) {
|
|
||||||
var prog = this._prog;
|
|
||||||
var optionals = [];
|
|
||||||
var positionals = [];
|
|
||||||
var actionUsage;
|
|
||||||
var textWidth;
|
|
||||||
|
|
||||||
// split optionals from positionals
|
|
||||||
actions.forEach(function (action) {
|
|
||||||
if (action.isOptional()) {
|
|
||||||
optionals.push(action);
|
|
||||||
} else {
|
|
||||||
positionals.push(action);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// build full usage string
|
|
||||||
actionUsage = this._formatActionsUsage([].concat(optionals, positionals), groups);
|
|
||||||
usage = [ prog, actionUsage ].join(' ');
|
|
||||||
|
|
||||||
// wrap the usage parts if it's too long
|
|
||||||
textWidth = this._width - this._currentIndent;
|
|
||||||
if ((prefix.length + usage.length) > textWidth) {
|
|
||||||
|
|
||||||
// break usage into wrappable parts
|
|
||||||
var regexpPart = new RegExp('\\(.*?\\)+|\\[.*?\\]+|\\S+', 'g');
|
|
||||||
var optionalUsage = this._formatActionsUsage(optionals, groups);
|
|
||||||
var positionalUsage = this._formatActionsUsage(positionals, groups);
|
|
||||||
|
|
||||||
|
|
||||||
var optionalParts = optionalUsage.match(regexpPart);
|
|
||||||
var positionalParts = positionalUsage.match(regexpPart) || [];
|
|
||||||
|
|
||||||
if (optionalParts.join(' ') !== optionalUsage) {
|
|
||||||
throw new Error('assert "optionalParts.join(\' \') === optionalUsage"');
|
|
||||||
}
|
|
||||||
if (positionalParts.join(' ') !== positionalUsage) {
|
|
||||||
throw new Error('assert "positionalParts.join(\' \') === positionalUsage"');
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper for wrapping lines
|
|
||||||
/*eslint-disable func-style*/ // node 0.10 compat
|
|
||||||
var _getLines = function (parts, indent, prefix) {
|
|
||||||
var lines = [];
|
|
||||||
var line = [];
|
|
||||||
|
|
||||||
var lineLength = prefix ? prefix.length - 1 : indent.length - 1;
|
|
||||||
|
|
||||||
parts.forEach(function (part) {
|
|
||||||
if (lineLength + 1 + part.length > textWidth) {
|
|
||||||
lines.push(indent + line.join(' '));
|
|
||||||
line = [];
|
|
||||||
lineLength = indent.length - 1;
|
|
||||||
}
|
|
||||||
line.push(part);
|
|
||||||
lineLength += part.length + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (line) {
|
|
||||||
lines.push(indent + line.join(' '));
|
|
||||||
}
|
|
||||||
if (prefix) {
|
|
||||||
lines[0] = lines[0].substr(indent.length);
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
var lines, indent, parts;
|
|
||||||
// if prog is short, follow it with optionals or positionals
|
|
||||||
if (prefix.length + prog.length <= 0.75 * textWidth) {
|
|
||||||
indent = $$.repeat(' ', (prefix.length + prog.length + 1));
|
|
||||||
if (optionalParts) {
|
|
||||||
lines = [].concat(
|
|
||||||
_getLines([ prog ].concat(optionalParts), indent, prefix),
|
|
||||||
_getLines(positionalParts, indent)
|
|
||||||
);
|
|
||||||
} else if (positionalParts) {
|
|
||||||
lines = _getLines([ prog ].concat(positionalParts), indent, prefix);
|
|
||||||
} else {
|
|
||||||
lines = [ prog ];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if prog is long, put it on its own line
|
|
||||||
} else {
|
|
||||||
indent = $$.repeat(' ', prefix.length);
|
|
||||||
parts = optionalParts.concat(positionalParts);
|
|
||||||
lines = _getLines(parts, indent);
|
|
||||||
if (lines.length > 1) {
|
|
||||||
lines = [].concat(
|
|
||||||
_getLines(optionalParts, indent),
|
|
||||||
_getLines(positionalParts, indent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
lines = [ prog ].concat(lines);
|
|
||||||
}
|
|
||||||
// join lines into usage
|
|
||||||
usage = lines.join(c.EOL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefix with 'usage:'
|
|
||||||
return prefix + usage + c.EOL + c.EOL;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatActionsUsage = function (actions, groups) {
|
|
||||||
// find group indices and identify actions in groups
|
|
||||||
var groupActions = [];
|
|
||||||
var inserts = [];
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
groups.forEach(function (group) {
|
|
||||||
var end;
|
|
||||||
var i;
|
|
||||||
|
|
||||||
var start = actions.indexOf(group._groupActions[0]);
|
|
||||||
if (start >= 0) {
|
|
||||||
end = start + group._groupActions.length;
|
|
||||||
|
|
||||||
//if (actions.slice(start, end) === group._groupActions) {
|
|
||||||
if ($$.arrayEqual(actions.slice(start, end), group._groupActions)) {
|
|
||||||
group._groupActions.forEach(function (action) {
|
|
||||||
groupActions.push(action);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!group.required) {
|
|
||||||
if (inserts[start]) {
|
|
||||||
inserts[start] += ' [';
|
|
||||||
} else {
|
|
||||||
inserts[start] = '[';
|
|
||||||
}
|
|
||||||
inserts[end] = ']';
|
|
||||||
} else {
|
|
||||||
if (inserts[start]) {
|
|
||||||
inserts[start] += ' (';
|
|
||||||
} else {
|
|
||||||
inserts[start] = '(';
|
|
||||||
}
|
|
||||||
inserts[end] = ')';
|
|
||||||
}
|
|
||||||
for (i = start + 1; i < end; i += 1) {
|
|
||||||
inserts[i] = '|';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// collect all actions format strings
|
|
||||||
var parts = [];
|
|
||||||
|
|
||||||
actions.forEach(function (action, actionIndex) {
|
|
||||||
var part;
|
|
||||||
var optionString;
|
|
||||||
var argsDefault;
|
|
||||||
var argsString;
|
|
||||||
|
|
||||||
// suppressed arguments are marked with None
|
|
||||||
// remove | separators for suppressed arguments
|
|
||||||
if (action.help === c.SUPPRESS) {
|
|
||||||
parts.push(null);
|
|
||||||
if (inserts[actionIndex] === '|') {
|
|
||||||
inserts.splice(actionIndex, actionIndex);
|
|
||||||
} else if (inserts[actionIndex + 1] === '|') {
|
|
||||||
inserts.splice(actionIndex + 1, actionIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// produce all arg strings
|
|
||||||
} else if (!action.isOptional()) {
|
|
||||||
part = self._formatArgs(action, action.dest);
|
|
||||||
|
|
||||||
// if it's in a group, strip the outer []
|
|
||||||
if (groupActions.indexOf(action) >= 0) {
|
|
||||||
if (part[0] === '[' && part[part.length - 1] === ']') {
|
|
||||||
part = part.slice(1, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add the action string to the list
|
|
||||||
parts.push(part);
|
|
||||||
|
|
||||||
// produce the first way to invoke the option in brackets
|
|
||||||
} else {
|
|
||||||
optionString = action.optionStrings[0];
|
|
||||||
|
|
||||||
// if the Optional doesn't take a value, format is: -s or --long
|
|
||||||
if (action.nargs === 0) {
|
|
||||||
part = '' + optionString;
|
|
||||||
|
|
||||||
// if the Optional takes a value, format is: -s ARGS or --long ARGS
|
|
||||||
} else {
|
|
||||||
argsDefault = action.dest.toUpperCase();
|
|
||||||
argsString = self._formatArgs(action, argsDefault);
|
|
||||||
part = optionString + ' ' + argsString;
|
|
||||||
}
|
|
||||||
// make it look optional if it's not required or in a group
|
|
||||||
if (!action.required && groupActions.indexOf(action) < 0) {
|
|
||||||
part = '[' + part + ']';
|
|
||||||
}
|
|
||||||
// add the action string to the list
|
|
||||||
parts.push(part);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// insert things at the necessary indices
|
|
||||||
for (var i = inserts.length - 1; i >= 0; --i) {
|
|
||||||
if (inserts[i] !== null) {
|
|
||||||
parts.splice(i, 0, inserts[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// join all the action items with spaces
|
|
||||||
var text = parts.filter(function (part) {
|
|
||||||
return !!part;
|
|
||||||
}).join(' ');
|
|
||||||
|
|
||||||
// clean up separators for mutually exclusive groups
|
|
||||||
text = text.replace(/([\[(]) /g, '$1'); // remove spaces
|
|
||||||
text = text.replace(/ ([\])])/g, '$1');
|
|
||||||
text = text.replace(/\[ *\]/g, ''); // remove empty groups
|
|
||||||
text = text.replace(/\( *\)/g, '');
|
|
||||||
text = text.replace(/\(([^|]*)\)/g, '$1'); // remove () from single action groups
|
|
||||||
|
|
||||||
text = text.trim();
|
|
||||||
|
|
||||||
// return the text
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatText = function (text) {
|
|
||||||
text = sprintf(text, { prog: this._prog });
|
|
||||||
var textWidth = this._width - this._currentIndent;
|
|
||||||
var indentIncriment = $$.repeat(' ', this._currentIndent);
|
|
||||||
return this._fillText(text, textWidth, indentIncriment) + c.EOL + c.EOL;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatAction = function (action) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var helpText;
|
|
||||||
var helpLines;
|
|
||||||
var parts;
|
|
||||||
var indentFirst;
|
|
||||||
|
|
||||||
// determine the required width and the entry label
|
|
||||||
var helpPosition = Math.min(this._actionMaxLength + 2, this._maxHelpPosition);
|
|
||||||
var helpWidth = this._width - helpPosition;
|
|
||||||
var actionWidth = helpPosition - this._currentIndent - 2;
|
|
||||||
var actionHeader = this._formatActionInvocation(action);
|
|
||||||
|
|
||||||
// no help; start on same line and add a final newline
|
|
||||||
if (!action.help) {
|
|
||||||
actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
|
|
||||||
|
|
||||||
// short action name; start on the same line and pad two spaces
|
|
||||||
} else if (actionHeader.length <= actionWidth) {
|
|
||||||
actionHeader = $$.repeat(' ', this._currentIndent) +
|
|
||||||
actionHeader +
|
|
||||||
' ' +
|
|
||||||
$$.repeat(' ', actionWidth - actionHeader.length);
|
|
||||||
indentFirst = 0;
|
|
||||||
|
|
||||||
// long action name; start on the next line
|
|
||||||
} else {
|
|
||||||
actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
|
|
||||||
indentFirst = helpPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect the pieces of the action help
|
|
||||||
parts = [ actionHeader ];
|
|
||||||
|
|
||||||
// if there was help for the action, add lines of help text
|
|
||||||
if (action.help) {
|
|
||||||
helpText = this._expandHelp(action);
|
|
||||||
helpLines = this._splitLines(helpText, helpWidth);
|
|
||||||
parts.push($$.repeat(' ', indentFirst) + helpLines[0] + c.EOL);
|
|
||||||
helpLines.slice(1).forEach(function (line) {
|
|
||||||
parts.push($$.repeat(' ', helpPosition) + line + c.EOL);
|
|
||||||
});
|
|
||||||
|
|
||||||
// or add a newline if the description doesn't end with one
|
|
||||||
} else if (actionHeader.charAt(actionHeader.length - 1) !== c.EOL) {
|
|
||||||
parts.push(c.EOL);
|
|
||||||
}
|
|
||||||
// if there are any sub-actions, add their help as well
|
|
||||||
if (action._getSubactions) {
|
|
||||||
this._indent();
|
|
||||||
action._getSubactions().forEach(function (subaction) {
|
|
||||||
parts.push(self._formatAction(subaction));
|
|
||||||
});
|
|
||||||
this._dedent();
|
|
||||||
}
|
|
||||||
// return a single string
|
|
||||||
return this._joinParts(parts);
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatActionInvocation = function (action) {
|
|
||||||
if (!action.isOptional()) {
|
|
||||||
var format_func = this._metavarFormatter(action, action.dest);
|
|
||||||
var metavars = format_func(1);
|
|
||||||
return metavars[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = [];
|
|
||||||
var argsDefault;
|
|
||||||
var argsString;
|
|
||||||
|
|
||||||
// if the Optional doesn't take a value, format is: -s, --long
|
|
||||||
if (action.nargs === 0) {
|
|
||||||
parts = parts.concat(action.optionStrings);
|
|
||||||
|
|
||||||
// if the Optional takes a value, format is: -s ARGS, --long ARGS
|
|
||||||
} else {
|
|
||||||
argsDefault = action.dest.toUpperCase();
|
|
||||||
argsString = this._formatArgs(action, argsDefault);
|
|
||||||
action.optionStrings.forEach(function (optionString) {
|
|
||||||
parts.push(optionString + ' ' + argsString);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return parts.join(', ');
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._metavarFormatter = function (action, metavarDefault) {
|
|
||||||
var result;
|
|
||||||
|
|
||||||
if (action.metavar || action.metavar === '') {
|
|
||||||
result = action.metavar;
|
|
||||||
} else if (action.choices) {
|
|
||||||
var choices = action.choices;
|
|
||||||
|
|
||||||
if (typeof choices === 'string') {
|
|
||||||
choices = choices.split('').join(', ');
|
|
||||||
} else if (Array.isArray(choices)) {
|
|
||||||
choices = choices.join(',');
|
|
||||||
} else {
|
|
||||||
choices = Object.keys(choices).join(',');
|
|
||||||
}
|
|
||||||
result = '{' + choices + '}';
|
|
||||||
} else {
|
|
||||||
result = metavarDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (size) {
|
|
||||||
if (Array.isArray(result)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var metavars = [];
|
|
||||||
for (var i = 0; i < size; i += 1) {
|
|
||||||
metavars.push(result);
|
|
||||||
}
|
|
||||||
return metavars;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._formatArgs = function (action, metavarDefault) {
|
|
||||||
var result;
|
|
||||||
var metavars;
|
|
||||||
|
|
||||||
var buildMetavar = this._metavarFormatter(action, metavarDefault);
|
|
||||||
|
|
||||||
switch (action.nargs) {
|
|
||||||
/*eslint-disable no-undefined*/
|
|
||||||
case undefined:
|
|
||||||
case null:
|
|
||||||
metavars = buildMetavar(1);
|
|
||||||
result = '' + metavars[0];
|
|
||||||
break;
|
|
||||||
case c.OPTIONAL:
|
|
||||||
metavars = buildMetavar(1);
|
|
||||||
result = '[' + metavars[0] + ']';
|
|
||||||
break;
|
|
||||||
case c.ZERO_OR_MORE:
|
|
||||||
metavars = buildMetavar(2);
|
|
||||||
result = '[' + metavars[0] + ' [' + metavars[1] + ' ...]]';
|
|
||||||
break;
|
|
||||||
case c.ONE_OR_MORE:
|
|
||||||
metavars = buildMetavar(2);
|
|
||||||
result = '' + metavars[0] + ' [' + metavars[1] + ' ...]';
|
|
||||||
break;
|
|
||||||
case c.REMAINDER:
|
|
||||||
result = '...';
|
|
||||||
break;
|
|
||||||
case c.PARSER:
|
|
||||||
metavars = buildMetavar(1);
|
|
||||||
result = metavars[0] + ' ...';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
metavars = buildMetavar(action.nargs);
|
|
||||||
result = metavars.join(' ');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._expandHelp = function (action) {
|
|
||||||
var params = { prog: this._prog };
|
|
||||||
|
|
||||||
Object.keys(action).forEach(function (actionProperty) {
|
|
||||||
var actionValue = action[actionProperty];
|
|
||||||
|
|
||||||
if (actionValue !== c.SUPPRESS) {
|
|
||||||
params[actionProperty] = actionValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (params.choices) {
|
|
||||||
if (typeof params.choices === 'string') {
|
|
||||||
params.choices = params.choices.split('').join(', ');
|
|
||||||
} else if (Array.isArray(params.choices)) {
|
|
||||||
params.choices = params.choices.join(', ');
|
|
||||||
} else {
|
|
||||||
params.choices = Object.keys(params.choices).join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf(this._getHelpString(action), params);
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._splitLines = function (text, width) {
|
|
||||||
var lines = [];
|
|
||||||
var delimiters = [ ' ', '.', ',', '!', '?' ];
|
|
||||||
var re = new RegExp('[' + delimiters.join('') + '][^' + delimiters.join('') + ']*$');
|
|
||||||
|
|
||||||
text = text.replace(/[\n\|\t]/g, ' ');
|
|
||||||
|
|
||||||
text = text.trim();
|
|
||||||
text = text.replace(this._whitespaceMatcher, ' ');
|
|
||||||
|
|
||||||
// Wraps the single paragraph in text (a string) so every line
|
|
||||||
// is at most width characters long.
|
|
||||||
text.split(c.EOL).forEach(function (line) {
|
|
||||||
if (width >= line.length) {
|
|
||||||
lines.push(line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var wrapStart = 0;
|
|
||||||
var wrapEnd = width;
|
|
||||||
var delimiterIndex = 0;
|
|
||||||
while (wrapEnd <= line.length) {
|
|
||||||
if (wrapEnd !== line.length && delimiters.indexOf(line[wrapEnd] < -1)) {
|
|
||||||
delimiterIndex = (re.exec(line.substring(wrapStart, wrapEnd)) || {}).index;
|
|
||||||
wrapEnd = wrapStart + delimiterIndex + 1;
|
|
||||||
}
|
|
||||||
lines.push(line.substring(wrapStart, wrapEnd));
|
|
||||||
wrapStart = wrapEnd;
|
|
||||||
wrapEnd += width;
|
|
||||||
}
|
|
||||||
if (wrapStart < line.length) {
|
|
||||||
lines.push(line.substring(wrapStart, wrapEnd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._fillText = function (text, width, indent) {
|
|
||||||
var lines = this._splitLines(text, width);
|
|
||||||
lines = lines.map(function (line) {
|
|
||||||
return indent + line;
|
|
||||||
});
|
|
||||||
return lines.join(c.EOL);
|
|
||||||
};
|
|
||||||
|
|
||||||
HelpFormatter.prototype._getHelpString = function (action) {
|
|
||||||
return action.help;
|
|
||||||
};
|
|
||||||
-76
@@ -1,76 +0,0 @@
|
|||||||
/**
|
|
||||||
* class Namespace
|
|
||||||
*
|
|
||||||
* Simple object for storing attributes. Implements equality by attribute names
|
|
||||||
* and values, and provides a simple string representation.
|
|
||||||
*
|
|
||||||
* See also [original guide][1]
|
|
||||||
*
|
|
||||||
* [1]:http://docs.python.org/dev/library/argparse.html#the-namespace-object
|
|
||||||
**/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var $$ = require('./utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* new Namespace(options)
|
|
||||||
* - options(object): predefined propertis for result object
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var Namespace = module.exports = function Namespace(options) {
|
|
||||||
$$.extend(this, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace#isset(key) -> Boolean
|
|
||||||
* - key (string|number): property name
|
|
||||||
*
|
|
||||||
* Tells whenever `namespace` contains given `key` or not.
|
|
||||||
**/
|
|
||||||
Namespace.prototype.isset = function (key) {
|
|
||||||
return $$.has(this, key);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace#set(key, value) -> self
|
|
||||||
* -key (string|number|object): propery name
|
|
||||||
* -value (mixed): new property value
|
|
||||||
*
|
|
||||||
* Set the property named key with value.
|
|
||||||
* If key object then set all key properties to namespace object
|
|
||||||
**/
|
|
||||||
Namespace.prototype.set = function (key, value) {
|
|
||||||
if (typeof (key) === 'object') {
|
|
||||||
$$.extend(this, key);
|
|
||||||
} else {
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace#get(key, defaultValue) -> mixed
|
|
||||||
* - key (string|number): property name
|
|
||||||
* - defaultValue (mixed): default value
|
|
||||||
*
|
|
||||||
* Return the property key or defaulValue if not set
|
|
||||||
**/
|
|
||||||
Namespace.prototype.get = function (key, defaultValue) {
|
|
||||||
return !this[key] ? defaultValue : this[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace#unset(key, defaultValue) -> mixed
|
|
||||||
* - key (string|number): property name
|
|
||||||
* - defaultValue (mixed): default value
|
|
||||||
*
|
|
||||||
* Return data[key](and delete it) or defaultValue
|
|
||||||
**/
|
|
||||||
Namespace.prototype.unset = function (key, defaultValue) {
|
|
||||||
var value = this[key];
|
|
||||||
if (value !== null) {
|
|
||||||
delete this[key];
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
};
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
exports.repeat = function (str, num) {
|
|
||||||
var result = '';
|
|
||||||
for (var i = 0; i < num; i++) { result += str; }
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.arrayEqual = function (a, b) {
|
|
||||||
if (a.length !== b.length) { return false; }
|
|
||||||
for (var i = 0; i < a.length; i++) {
|
|
||||||
if (a[i] !== b[i]) { return false; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.trimChars = function (str, chars) {
|
|
||||||
var start = 0;
|
|
||||||
var end = str.length - 1;
|
|
||||||
while (chars.indexOf(str.charAt(start)) >= 0) { start++; }
|
|
||||||
while (chars.indexOf(str.charAt(end)) >= 0) { end--; }
|
|
||||||
return str.slice(start, end + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.capitalize = function (str) {
|
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.arrayUnion = function () {
|
|
||||||
var result = [];
|
|
||||||
for (var i = 0, values = {}; i < arguments.length; i++) {
|
|
||||||
var arr = arguments[i];
|
|
||||||
for (var j = 0; j < arr.length; j++) {
|
|
||||||
if (!values[arr[j]]) {
|
|
||||||
values[arr[j]] = true;
|
|
||||||
result.push(arr[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
function has(obj, key) {
|
|
||||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.has = has;
|
|
||||||
|
|
||||||
exports.extend = function (dest, src) {
|
|
||||||
for (var i in src) {
|
|
||||||
if (has(src, i)) { dest[i] = src[i]; }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.trimEnd = function (str) {
|
|
||||||
return str.replace(/\s+$/g, '');
|
|
||||||
};
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "argparse",
|
|
||||||
"description": "Very powerful CLI arguments parser. Native port of argparse - python's options parsing library",
|
|
||||||
"version": "1.0.10",
|
|
||||||
"keywords": [
|
|
||||||
"cli",
|
|
||||||
"parser",
|
|
||||||
"argparse",
|
|
||||||
"option",
|
|
||||||
"args"
|
|
||||||
],
|
|
||||||
"contributors": [
|
|
||||||
"Eugene Shkuropat",
|
|
||||||
"Paul Jacobson"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"index.js",
|
|
||||||
"lib/"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": "nodeca/argparse",
|
|
||||||
"scripts": {
|
|
||||||
"test": "make test"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "^2.13.1",
|
|
||||||
"istanbul": "^0.4.5",
|
|
||||||
"mocha": "^3.1.0",
|
|
||||||
"ndoc": "^5.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
|
|
||||||
|
|
||||||
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.
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
# Array Flatten
|
|
||||||
|
|
||||||
[![NPM version][npm-image]][npm-url]
|
|
||||||
[![NPM downloads][downloads-image]][downloads-url]
|
|
||||||
[![Build status][travis-image]][travis-url]
|
|
||||||
[![Test coverage][coveralls-image]][coveralls-url]
|
|
||||||
|
|
||||||
> Flatten an array of nested arrays into a single flat array. Accepts an optional depth.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install array-flatten --save
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
var flatten = require('array-flatten')
|
|
||||||
|
|
||||||
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9])
|
|
||||||
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
||||||
|
|
||||||
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9], 2)
|
|
||||||
//=> [1, 2, 3, [4, [5], 6], 7, 8, 9]
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
flatten(arguments) //=> [1, 2, 3]
|
|
||||||
})(1, [2, 3])
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
[npm-image]: https://img.shields.io/npm/v/array-flatten.svg?style=flat
|
|
||||||
[npm-url]: https://npmjs.org/package/array-flatten
|
|
||||||
[downloads-image]: https://img.shields.io/npm/dm/array-flatten.svg?style=flat
|
|
||||||
[downloads-url]: https://npmjs.org/package/array-flatten
|
|
||||||
[travis-image]: https://img.shields.io/travis/blakeembrey/array-flatten.svg?style=flat
|
|
||||||
[travis-url]: https://travis-ci.org/blakeembrey/array-flatten
|
|
||||||
[coveralls-image]: https://img.shields.io/coveralls/blakeembrey/array-flatten.svg?style=flat
|
|
||||||
[coveralls-url]: https://coveralls.io/r/blakeembrey/array-flatten?branch=master
|
|
||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `arrayFlatten`.
|
|
||||||
*/
|
|
||||||
module.exports = arrayFlatten
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursive flatten function with depth.
|
|
||||||
*
|
|
||||||
* @param {Array} array
|
|
||||||
* @param {Array} result
|
|
||||||
* @param {Number} depth
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
function flattenWithDepth (array, result, depth) {
|
|
||||||
for (var i = 0; i < array.length; i++) {
|
|
||||||
var value = array[i]
|
|
||||||
|
|
||||||
if (depth > 0 && Array.isArray(value)) {
|
|
||||||
flattenWithDepth(value, result, depth - 1)
|
|
||||||
} else {
|
|
||||||
result.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursive flatten function. Omitting depth is slightly faster.
|
|
||||||
*
|
|
||||||
* @param {Array} array
|
|
||||||
* @param {Array} result
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
function flattenForever (array, result) {
|
|
||||||
for (var i = 0; i < array.length; i++) {
|
|
||||||
var value = array[i]
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
flattenForever(value, result)
|
|
||||||
} else {
|
|
||||||
result.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flatten an array, with the ability to define a depth.
|
|
||||||
*
|
|
||||||
* @param {Array} array
|
|
||||||
* @param {Number} depth
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
function arrayFlatten (array, depth) {
|
|
||||||
if (depth == null) {
|
|
||||||
return flattenForever(array, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
return flattenWithDepth(array, [], depth)
|
|
||||||
}
|
|
||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "array-flatten",
|
|
||||||
"version": "1.1.1",
|
|
||||||
"description": "Flatten an array of nested arrays into a single flat array",
|
|
||||||
"main": "array-flatten.js",
|
|
||||||
"files": [
|
|
||||||
"array-flatten.js",
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"test": "istanbul cover _mocha -- -R spec"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/blakeembrey/array-flatten.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"array",
|
|
||||||
"flatten",
|
|
||||||
"arguments",
|
|
||||||
"depth"
|
|
||||||
],
|
|
||||||
"author": {
|
|
||||||
"name": "Blake Embrey",
|
|
||||||
"email": "hello@blakeembrey.com",
|
|
||||||
"url": "http://blakeembrey.me"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/blakeembrey/array-flatten/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/blakeembrey/array-flatten",
|
|
||||||
"devDependencies": {
|
|
||||||
"istanbul": "^0.3.13",
|
|
||||||
"mocha": "^2.2.4",
|
|
||||||
"pre-commit": "^1.0.7",
|
|
||||||
"standard": "^3.7.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Jameson Little
|
|
||||||
|
|
||||||
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.
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
base64-js
|
|
||||||
=========
|
|
||||||
|
|
||||||
`base64-js` does basic base64 encoding/decoding in pure JS.
|
|
||||||
|
|
||||||
[](http://travis-ci.org/beatgammit/base64-js)
|
|
||||||
|
|
||||||
Many browsers already have base64 encoding/decoding functionality, but it is for text data, not all-purpose binary data.
|
|
||||||
|
|
||||||
Sometimes encoding/decoding binary data in the browser is useful, and that is what this module does.
|
|
||||||
|
|
||||||
## install
|
|
||||||
|
|
||||||
With [npm](https://npmjs.org) do:
|
|
||||||
|
|
||||||
`npm install base64-js` and `var base64js = require('base64-js')`
|
|
||||||
|
|
||||||
For use in web browsers do:
|
|
||||||
|
|
||||||
`<script src="base64js.min.js"></script>`
|
|
||||||
|
|
||||||
[Get supported base64-js with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-base64-js?utm_source=npm-base64-js&utm_medium=referral&utm_campaign=readme)
|
|
||||||
|
|
||||||
## methods
|
|
||||||
|
|
||||||
`base64js` has three exposed functions, `byteLength`, `toByteArray` and `fromByteArray`, which both take a single argument.
|
|
||||||
|
|
||||||
* `byteLength` - Takes a base64 string and returns length of byte array
|
|
||||||
* `toByteArray` - Takes a base64 string and returns a byte array
|
|
||||||
* `fromByteArray` - Takes a byte array and returns a base64 string
|
|
||||||
|
|
||||||
## license
|
|
||||||
|
|
||||||
MIT
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
(function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,b.base64js=a()}})(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;c<g.length;c++)a(g[c]);return a}return b}()({"/":[function(a,b,c){'use strict';function d(a){var b=a.length;if(0<b%4)throw new Error("Invalid string. Length must be a multiple of 4");var c=a.indexOf("=");-1===c&&(c=b);var d=c===b?0:4-c%4;return[c,d]}function e(a,b,c){return 3*(b+c)/4-c}function f(a){var b,c,f=d(a),g=f[0],h=f[1],j=new m(e(a,g,h)),k=0,n=0<h?g-4:g;for(c=0;c<n;c+=4)b=l[a.charCodeAt(c)]<<18|l[a.charCodeAt(c+1)]<<12|l[a.charCodeAt(c+2)]<<6|l[a.charCodeAt(c+3)],j[k++]=255&b>>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a.charCodeAt(c)]<<2|l[a.charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a.charCodeAt(c)]<<10|l[a.charCodeAt(c+1)]<<4|l[a.charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}function g(a){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;f<c;f+=3)d=(16711680&a[f]<<16)+(65280&a[f+1]<<8)+(255&a[f+2]),e.push(g(d));return e.join("")}function j(a){for(var b,c=a.length,d=c%3,e=[],f=16383,g=0,j=c-d;g<j;g+=f)e.push(h(a,g,g+f>j?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+"==")):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+"=")),e.join("")}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m="undefined"==typeof Uint8Array?Array:Uint8Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,p=n.length;o<p;++o)k[o]=n[o],l[n.charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])("/")});
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
export function byteLength(b64: string): number;
|
|
||||||
export function toByteArray(b64: string): Uint8Array;
|
|
||||||
export function fromByteArray(uint8: Uint8Array): string;
|
|
||||||
-150
@@ -1,150 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
exports.byteLength = byteLength
|
|
||||||
exports.toByteArray = toByteArray
|
|
||||||
exports.fromByteArray = fromByteArray
|
|
||||||
|
|
||||||
var lookup = []
|
|
||||||
var revLookup = []
|
|
||||||
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
|
|
||||||
|
|
||||||
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
||||||
for (var i = 0, len = code.length; i < len; ++i) {
|
|
||||||
lookup[i] = code[i]
|
|
||||||
revLookup[code.charCodeAt(i)] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support decoding URL-safe base64 strings, as Node.js does.
|
|
||||||
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
|
|
||||||
revLookup['-'.charCodeAt(0)] = 62
|
|
||||||
revLookup['_'.charCodeAt(0)] = 63
|
|
||||||
|
|
||||||
function getLens (b64) {
|
|
||||||
var len = b64.length
|
|
||||||
|
|
||||||
if (len % 4 > 0) {
|
|
||||||
throw new Error('Invalid string. Length must be a multiple of 4')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim off extra bytes after placeholder bytes are found
|
|
||||||
// See: https://github.com/beatgammit/base64-js/issues/42
|
|
||||||
var validLen = b64.indexOf('=')
|
|
||||||
if (validLen === -1) validLen = len
|
|
||||||
|
|
||||||
var placeHoldersLen = validLen === len
|
|
||||||
? 0
|
|
||||||
: 4 - (validLen % 4)
|
|
||||||
|
|
||||||
return [validLen, placeHoldersLen]
|
|
||||||
}
|
|
||||||
|
|
||||||
// base64 is 4/3 + up to two characters of the original data
|
|
||||||
function byteLength (b64) {
|
|
||||||
var lens = getLens(b64)
|
|
||||||
var validLen = lens[0]
|
|
||||||
var placeHoldersLen = lens[1]
|
|
||||||
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
|
|
||||||
}
|
|
||||||
|
|
||||||
function _byteLength (b64, validLen, placeHoldersLen) {
|
|
||||||
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
|
|
||||||
}
|
|
||||||
|
|
||||||
function toByteArray (b64) {
|
|
||||||
var tmp
|
|
||||||
var lens = getLens(b64)
|
|
||||||
var validLen = lens[0]
|
|
||||||
var placeHoldersLen = lens[1]
|
|
||||||
|
|
||||||
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
|
|
||||||
|
|
||||||
var curByte = 0
|
|
||||||
|
|
||||||
// if there are placeholders, only get up to the last complete 4 chars
|
|
||||||
var len = placeHoldersLen > 0
|
|
||||||
? validLen - 4
|
|
||||||
: validLen
|
|
||||||
|
|
||||||
var i
|
|
||||||
for (i = 0; i < len; i += 4) {
|
|
||||||
tmp =
|
|
||||||
(revLookup[b64.charCodeAt(i)] << 18) |
|
|
||||||
(revLookup[b64.charCodeAt(i + 1)] << 12) |
|
|
||||||
(revLookup[b64.charCodeAt(i + 2)] << 6) |
|
|
||||||
revLookup[b64.charCodeAt(i + 3)]
|
|
||||||
arr[curByte++] = (tmp >> 16) & 0xFF
|
|
||||||
arr[curByte++] = (tmp >> 8) & 0xFF
|
|
||||||
arr[curByte++] = tmp & 0xFF
|
|
||||||
}
|
|
||||||
|
|
||||||
if (placeHoldersLen === 2) {
|
|
||||||
tmp =
|
|
||||||
(revLookup[b64.charCodeAt(i)] << 2) |
|
|
||||||
(revLookup[b64.charCodeAt(i + 1)] >> 4)
|
|
||||||
arr[curByte++] = tmp & 0xFF
|
|
||||||
}
|
|
||||||
|
|
||||||
if (placeHoldersLen === 1) {
|
|
||||||
tmp =
|
|
||||||
(revLookup[b64.charCodeAt(i)] << 10) |
|
|
||||||
(revLookup[b64.charCodeAt(i + 1)] << 4) |
|
|
||||||
(revLookup[b64.charCodeAt(i + 2)] >> 2)
|
|
||||||
arr[curByte++] = (tmp >> 8) & 0xFF
|
|
||||||
arr[curByte++] = tmp & 0xFF
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
function tripletToBase64 (num) {
|
|
||||||
return lookup[num >> 18 & 0x3F] +
|
|
||||||
lookup[num >> 12 & 0x3F] +
|
|
||||||
lookup[num >> 6 & 0x3F] +
|
|
||||||
lookup[num & 0x3F]
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeChunk (uint8, start, end) {
|
|
||||||
var tmp
|
|
||||||
var output = []
|
|
||||||
for (var i = start; i < end; i += 3) {
|
|
||||||
tmp =
|
|
||||||
((uint8[i] << 16) & 0xFF0000) +
|
|
||||||
((uint8[i + 1] << 8) & 0xFF00) +
|
|
||||||
(uint8[i + 2] & 0xFF)
|
|
||||||
output.push(tripletToBase64(tmp))
|
|
||||||
}
|
|
||||||
return output.join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromByteArray (uint8) {
|
|
||||||
var tmp
|
|
||||||
var len = uint8.length
|
|
||||||
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
|
|
||||||
var parts = []
|
|
||||||
var maxChunkLength = 16383 // must be multiple of 3
|
|
||||||
|
|
||||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
|
||||||
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
|
|
||||||
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
|
||||||
if (extraBytes === 1) {
|
|
||||||
tmp = uint8[len - 1]
|
|
||||||
parts.push(
|
|
||||||
lookup[tmp >> 2] +
|
|
||||||
lookup[(tmp << 4) & 0x3F] +
|
|
||||||
'=='
|
|
||||||
)
|
|
||||||
} else if (extraBytes === 2) {
|
|
||||||
tmp = (uint8[len - 2] << 8) + uint8[len - 1]
|
|
||||||
parts.push(
|
|
||||||
lookup[tmp >> 10] +
|
|
||||||
lookup[(tmp >> 4) & 0x3F] +
|
|
||||||
lookup[(tmp << 2) & 0x3F] +
|
|
||||||
'='
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join('')
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "base64-js",
|
|
||||||
"description": "Base64 encoding/decoding in pure JS",
|
|
||||||
"version": "1.5.1",
|
|
||||||
"author": "T. Jameson Little <t.jameson.little@gmail.com>",
|
|
||||||
"typings": "index.d.ts",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/beatgammit/base64-js/issues"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-minify": "^0.5.1",
|
|
||||||
"benchmark": "^2.1.4",
|
|
||||||
"browserify": "^16.3.0",
|
|
||||||
"standard": "*",
|
|
||||||
"tape": "4.x"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/beatgammit/base64-js",
|
|
||||||
"keywords": [
|
|
||||||
"base64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "index.js",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/beatgammit/base64-js.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "browserify -s base64js -r ./ | minify > base64js.min.js",
|
|
||||||
"lint": "standard",
|
|
||||||
"test": "npm run lint && npm run unit",
|
|
||||||
"unit": "tape test/*.js"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-703
@@ -1,703 +0,0 @@
|
|||||||
1.20.8
|
|
||||||
===================
|
|
||||||
|
|
||||||
* Same code base as 1.20.7. This was created to test the new release process.
|
|
||||||
|
|
||||||
1.20.7
|
|
||||||
===================
|
|
||||||
|
|
||||||
* deps: qs@~6.16.0
|
|
||||||
|
|
||||||
1.20.6
|
|
||||||
======
|
|
||||||
* Security fix for [GHSA-v422-hmwv-36x6](https://github.com/expressjs/body-parser/security/advisories/GHSA-v422-hmwv-36x6)
|
|
||||||
* fix: improve `limit` option validation (#698)
|
|
||||||
* Invalid `limit` values (e.g. unparseable strings or `NaN`) now throw instead of being silently ignored, which previously disabled size limit enforcement
|
|
||||||
* `null` and `undefined` fall back to the default 100kb limit
|
|
||||||
|
|
||||||
1.20.5
|
|
||||||
======
|
|
||||||
* refactor(json): simplify strict mode error string construction
|
|
||||||
* fix: extended urlencoded parsing of arrays with >100 elements (#716)
|
|
||||||
* deps: qs@~6.15.1
|
|
||||||
|
|
||||||
1.20.4
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@~6.14.0
|
|
||||||
* deps: use tilde notation for dependencies
|
|
||||||
* deps: http-errors@~2.0.1
|
|
||||||
* deps: raw-body@~2.5.3
|
|
||||||
|
|
||||||
1.20.3
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@6.13.0
|
|
||||||
* add `depth` option to customize the depth level in the parser
|
|
||||||
* IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`)
|
|
||||||
|
|
||||||
1.20.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix strict json error message on Node.js 19+
|
|
||||||
* deps: content-type@~1.0.5
|
|
||||||
- perf: skip value escaping when unnecessary
|
|
||||||
* deps: raw-body@2.5.2
|
|
||||||
|
|
||||||
1.20.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@6.11.0
|
|
||||||
* perf: remove unnecessary object clone
|
|
||||||
|
|
||||||
1.20.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix error message for json parse whitespace in `strict`
|
|
||||||
* Fix internal error when inflated body exceeds limit
|
|
||||||
* Prevent loss of async hooks context
|
|
||||||
* Prevent hanging when request already read
|
|
||||||
* deps: depd@2.0.0
|
|
||||||
- Replace internal `eval` usage with `Function` constructor
|
|
||||||
- Use instance methods on `process` to check for listeners
|
|
||||||
* deps: http-errors@2.0.0
|
|
||||||
- deps: depd@2.0.0
|
|
||||||
- deps: statuses@2.0.1
|
|
||||||
* deps: on-finished@2.4.1
|
|
||||||
* deps: qs@6.10.3
|
|
||||||
* deps: raw-body@2.5.1
|
|
||||||
- deps: http-errors@2.0.0
|
|
||||||
|
|
||||||
1.19.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@3.1.2
|
|
||||||
* deps: qs@6.9.7
|
|
||||||
* Fix handling of `__proto__` keys
|
|
||||||
* deps: raw-body@2.4.3
|
|
||||||
- deps: bytes@3.1.2
|
|
||||||
|
|
||||||
1.19.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@3.1.1
|
|
||||||
* deps: http-errors@1.8.1
|
|
||||||
- deps: inherits@2.0.4
|
|
||||||
- deps: toidentifier@1.0.1
|
|
||||||
- deps: setprototypeof@1.2.0
|
|
||||||
* deps: qs@6.9.6
|
|
||||||
* deps: raw-body@2.4.2
|
|
||||||
- deps: bytes@3.1.1
|
|
||||||
- deps: http-errors@1.8.1
|
|
||||||
* deps: safe-buffer@5.2.1
|
|
||||||
* deps: type-is@~1.6.18
|
|
||||||
|
|
||||||
1.19.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@3.1.0
|
|
||||||
- Add petabyte (`pb`) support
|
|
||||||
* deps: http-errors@1.7.2
|
|
||||||
- Set constructor name when possible
|
|
||||||
- deps: setprototypeof@1.1.1
|
|
||||||
- deps: statuses@'>= 1.5.0 < 2'
|
|
||||||
* deps: iconv-lite@0.4.24
|
|
||||||
- Added encoding MIK
|
|
||||||
* deps: qs@6.7.0
|
|
||||||
- Fix parsing array brackets after index
|
|
||||||
* deps: raw-body@2.4.0
|
|
||||||
- deps: bytes@3.1.0
|
|
||||||
- deps: http-errors@1.7.2
|
|
||||||
- deps: iconv-lite@0.4.24
|
|
||||||
* deps: type-is@~1.6.17
|
|
||||||
- deps: mime-types@~2.1.24
|
|
||||||
- perf: prevent internal `throw` on invalid type
|
|
||||||
|
|
||||||
1.18.3
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix stack trace for strict json parse error
|
|
||||||
* deps: depd@~1.1.2
|
|
||||||
- perf: remove argument reassignment
|
|
||||||
* deps: http-errors@~1.6.3
|
|
||||||
- deps: depd@~1.1.2
|
|
||||||
- deps: setprototypeof@1.1.0
|
|
||||||
- deps: statuses@'>= 1.3.1 < 2'
|
|
||||||
* deps: iconv-lite@0.4.23
|
|
||||||
- Fix loading encoding with year appended
|
|
||||||
- Fix deprecation warnings on Node.js 10+
|
|
||||||
* deps: qs@6.5.2
|
|
||||||
* deps: raw-body@2.3.3
|
|
||||||
- deps: http-errors@1.6.3
|
|
||||||
- deps: iconv-lite@0.4.23
|
|
||||||
* deps: type-is@~1.6.16
|
|
||||||
- deps: mime-types@~2.1.18
|
|
||||||
|
|
||||||
1.18.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@2.6.9
|
|
||||||
* perf: remove argument reassignment
|
|
||||||
|
|
||||||
1.18.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: content-type@~1.0.4
|
|
||||||
- perf: remove argument reassignment
|
|
||||||
- perf: skip parameter parsing when no parameters
|
|
||||||
* deps: iconv-lite@0.4.19
|
|
||||||
- Fix ISO-8859-1 regression
|
|
||||||
- Update Windows-1255
|
|
||||||
* deps: qs@6.5.1
|
|
||||||
- Fix parsing & compacting very deep objects
|
|
||||||
* deps: raw-body@2.3.2
|
|
||||||
- deps: iconv-lite@0.4.19
|
|
||||||
|
|
||||||
1.18.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix JSON strict violation error to match native parse error
|
|
||||||
* Include the `body` property on verify errors
|
|
||||||
* Include the `type` property on all generated errors
|
|
||||||
* Use `http-errors` to set status code on errors
|
|
||||||
* deps: bytes@3.0.0
|
|
||||||
* deps: debug@2.6.8
|
|
||||||
* deps: depd@~1.1.1
|
|
||||||
- Remove unnecessary `Buffer` loading
|
|
||||||
* deps: http-errors@~1.6.2
|
|
||||||
- deps: depd@1.1.1
|
|
||||||
* deps: iconv-lite@0.4.18
|
|
||||||
- Add support for React Native
|
|
||||||
- Add a warning if not loaded as utf-8
|
|
||||||
- Fix CESU-8 decoding in Node.js 8
|
|
||||||
- Improve speed of ISO-8859-1 encoding
|
|
||||||
* deps: qs@6.5.0
|
|
||||||
* deps: raw-body@2.3.1
|
|
||||||
- Use `http-errors` for standard emitted errors
|
|
||||||
- deps: bytes@3.0.0
|
|
||||||
- deps: iconv-lite@0.4.18
|
|
||||||
- perf: skip buffer decoding on overage chunk
|
|
||||||
* perf: prevent internal `throw` when missing charset
|
|
||||||
|
|
||||||
1.17.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@2.6.7
|
|
||||||
- Fix `DEBUG_MAX_ARRAY_LENGTH`
|
|
||||||
- deps: ms@2.0.0
|
|
||||||
* deps: type-is@~1.6.15
|
|
||||||
- deps: mime-types@~2.1.15
|
|
||||||
|
|
||||||
1.17.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@6.4.0
|
|
||||||
- Fix regression parsing keys starting with `[`
|
|
||||||
|
|
||||||
1.17.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: http-errors@~1.6.1
|
|
||||||
- Make `message` property enumerable for `HttpError`s
|
|
||||||
- deps: setprototypeof@1.0.3
|
|
||||||
* deps: qs@6.3.1
|
|
||||||
- Fix compacting nested arrays
|
|
||||||
|
|
||||||
1.16.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@2.6.1
|
|
||||||
- Fix deprecation messages in WebStorm and other editors
|
|
||||||
- Undeprecate `DEBUG_FD` set to `1` or `2`
|
|
||||||
|
|
||||||
1.16.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@2.6.0
|
|
||||||
- Allow colors in workers
|
|
||||||
- Deprecated `DEBUG_FD` environment variable
|
|
||||||
- Fix error when running under React Native
|
|
||||||
- Use same color for same namespace
|
|
||||||
- deps: ms@0.7.2
|
|
||||||
* deps: http-errors@~1.5.1
|
|
||||||
- deps: inherits@2.0.3
|
|
||||||
- deps: setprototypeof@1.0.2
|
|
||||||
- deps: statuses@'>= 1.3.1 < 2'
|
|
||||||
* deps: iconv-lite@0.4.15
|
|
||||||
- Added encoding MS-31J
|
|
||||||
- Added encoding MS-932
|
|
||||||
- Added encoding MS-936
|
|
||||||
- Added encoding MS-949
|
|
||||||
- Added encoding MS-950
|
|
||||||
- Fix GBK/GB18030 handling of Euro character
|
|
||||||
* deps: qs@6.2.1
|
|
||||||
- Fix array parsing from skipping empty values
|
|
||||||
* deps: raw-body@~2.2.0
|
|
||||||
- deps: iconv-lite@0.4.15
|
|
||||||
* deps: type-is@~1.6.14
|
|
||||||
- deps: mime-types@~2.1.13
|
|
||||||
|
|
||||||
1.15.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@2.4.0
|
|
||||||
* deps: content-type@~1.0.2
|
|
||||||
- perf: enable strict mode
|
|
||||||
* deps: http-errors@~1.5.0
|
|
||||||
- Use `setprototypeof` module to replace `__proto__` setting
|
|
||||||
- deps: statuses@'>= 1.3.0 < 2'
|
|
||||||
- perf: enable strict mode
|
|
||||||
* deps: qs@6.2.0
|
|
||||||
* deps: raw-body@~2.1.7
|
|
||||||
- deps: bytes@2.4.0
|
|
||||||
- perf: remove double-cleanup on happy path
|
|
||||||
* deps: type-is@~1.6.13
|
|
||||||
- deps: mime-types@~2.1.11
|
|
||||||
|
|
||||||
1.15.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@2.3.0
|
|
||||||
- Drop partial bytes on all parsed units
|
|
||||||
- Fix parsing byte string that looks like hex
|
|
||||||
* deps: raw-body@~2.1.6
|
|
||||||
- deps: bytes@2.3.0
|
|
||||||
* deps: type-is@~1.6.12
|
|
||||||
- deps: mime-types@~2.1.10
|
|
||||||
|
|
||||||
1.15.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: http-errors@~1.4.0
|
|
||||||
- Add `HttpError` export, for `err instanceof createError.HttpError`
|
|
||||||
- deps: inherits@2.0.1
|
|
||||||
- deps: statuses@'>= 1.2.1 < 2'
|
|
||||||
* deps: qs@6.1.0
|
|
||||||
* deps: type-is@~1.6.11
|
|
||||||
- deps: mime-types@~2.1.9
|
|
||||||
|
|
||||||
1.14.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: bytes@2.2.0
|
|
||||||
* deps: iconv-lite@0.4.13
|
|
||||||
* deps: qs@5.2.0
|
|
||||||
* deps: raw-body@~2.1.5
|
|
||||||
- deps: bytes@2.2.0
|
|
||||||
- deps: iconv-lite@0.4.13
|
|
||||||
* deps: type-is@~1.6.10
|
|
||||||
- deps: mime-types@~2.1.8
|
|
||||||
|
|
||||||
1.14.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix issue where invalid charset results in 400 when `verify` used
|
|
||||||
* deps: iconv-lite@0.4.12
|
|
||||||
- Fix CESU-8 decoding in Node.js 4.x
|
|
||||||
* deps: raw-body@~2.1.4
|
|
||||||
- Fix masking critical errors from `iconv-lite`
|
|
||||||
- deps: iconv-lite@0.4.12
|
|
||||||
* deps: type-is@~1.6.9
|
|
||||||
- deps: mime-types@~2.1.7
|
|
||||||
|
|
||||||
1.14.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* Fix JSON strict parse error to match syntax errors
|
|
||||||
* Provide static `require` analysis in `urlencoded` parser
|
|
||||||
* deps: depd@~1.1.0
|
|
||||||
- Support web browser loading
|
|
||||||
* deps: qs@5.1.0
|
|
||||||
* deps: raw-body@~2.1.3
|
|
||||||
- Fix sync callback when attaching data listener causes sync read
|
|
||||||
* deps: type-is@~1.6.8
|
|
||||||
- Fix type error when given invalid type to match against
|
|
||||||
- deps: mime-types@~2.1.6
|
|
||||||
|
|
||||||
1.13.3
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: type-is@~1.6.6
|
|
||||||
- deps: mime-types@~2.1.4
|
|
||||||
|
|
||||||
1.13.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: iconv-lite@0.4.11
|
|
||||||
* deps: qs@4.0.0
|
|
||||||
- Fix dropping parameters like `hasOwnProperty`
|
|
||||||
- Fix user-visible incompatibilities from 3.1.0
|
|
||||||
- Fix various parsing edge cases
|
|
||||||
* deps: raw-body@~2.1.2
|
|
||||||
- Fix error stack traces to skip `makeError`
|
|
||||||
- deps: iconv-lite@0.4.11
|
|
||||||
* deps: type-is@~1.6.4
|
|
||||||
- deps: mime-types@~2.1.2
|
|
||||||
- perf: enable strict mode
|
|
||||||
- perf: remove argument reassignment
|
|
||||||
|
|
||||||
1.13.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@2.4.2
|
|
||||||
- Downgraded from 3.1.0 because of user-visible incompatibilities
|
|
||||||
|
|
||||||
1.13.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* Add `statusCode` property on `Error`s, in addition to `status`
|
|
||||||
* Change `type` default to `application/json` for JSON parser
|
|
||||||
* Change `type` default to `application/x-www-form-urlencoded` for urlencoded parser
|
|
||||||
* Provide static `require` analysis
|
|
||||||
* Use the `http-errors` module to generate errors
|
|
||||||
* deps: bytes@2.1.0
|
|
||||||
- Slight optimizations
|
|
||||||
* deps: iconv-lite@0.4.10
|
|
||||||
- The encoding UTF-16 without BOM now defaults to UTF-16LE when detection fails
|
|
||||||
- Leading BOM is now removed when decoding
|
|
||||||
* deps: on-finished@~2.3.0
|
|
||||||
- Add defined behavior for HTTP `CONNECT` requests
|
|
||||||
- Add defined behavior for HTTP `Upgrade` requests
|
|
||||||
- deps: ee-first@1.1.1
|
|
||||||
* deps: qs@3.1.0
|
|
||||||
- Fix dropping parameters like `hasOwnProperty`
|
|
||||||
- Fix various parsing edge cases
|
|
||||||
- Parsed object now has `null` prototype
|
|
||||||
* deps: raw-body@~2.1.1
|
|
||||||
- Use `unpipe` module for unpiping requests
|
|
||||||
- deps: iconv-lite@0.4.10
|
|
||||||
* deps: type-is@~1.6.3
|
|
||||||
- deps: mime-types@~2.1.1
|
|
||||||
- perf: reduce try block size
|
|
||||||
- perf: remove bitwise operations
|
|
||||||
* perf: enable strict mode
|
|
||||||
* perf: remove argument reassignment
|
|
||||||
* perf: remove delete call
|
|
||||||
|
|
||||||
1.12.4
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@~2.2.0
|
|
||||||
* deps: qs@2.4.2
|
|
||||||
- Fix allowing parameters like `constructor`
|
|
||||||
* deps: on-finished@~2.2.1
|
|
||||||
* deps: raw-body@~2.0.1
|
|
||||||
- Fix a false-positive when unpiping in Node.js 0.8
|
|
||||||
- deps: bytes@2.0.1
|
|
||||||
* deps: type-is@~1.6.2
|
|
||||||
- deps: mime-types@~2.0.11
|
|
||||||
|
|
||||||
1.12.3
|
|
||||||
======
|
|
||||||
|
|
||||||
* Slight efficiency improvement when not debugging
|
|
||||||
* deps: depd@~1.0.1
|
|
||||||
* deps: iconv-lite@0.4.8
|
|
||||||
- Add encoding alias UNICODE-1-1-UTF-7
|
|
||||||
* deps: raw-body@1.3.4
|
|
||||||
- Fix hanging callback if request aborts during read
|
|
||||||
- deps: iconv-lite@0.4.8
|
|
||||||
|
|
||||||
1.12.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: qs@2.4.1
|
|
||||||
- Fix error when parameter `hasOwnProperty` is present
|
|
||||||
|
|
||||||
1.12.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: debug@~2.1.3
|
|
||||||
- Fix high intensity foreground color for bold
|
|
||||||
- deps: ms@0.7.0
|
|
||||||
* deps: type-is@~1.6.1
|
|
||||||
- deps: mime-types@~2.0.10
|
|
||||||
|
|
||||||
1.12.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* add `debug` messages
|
|
||||||
* accept a function for the `type` option
|
|
||||||
* use `content-type` to parse `Content-Type` headers
|
|
||||||
* deps: iconv-lite@0.4.7
|
|
||||||
- Gracefully support enumerables on `Object.prototype`
|
|
||||||
* deps: raw-body@1.3.3
|
|
||||||
- deps: iconv-lite@0.4.7
|
|
||||||
* deps: type-is@~1.6.0
|
|
||||||
- fix argument reassignment
|
|
||||||
- fix false-positives in `hasBody` `Transfer-Encoding` check
|
|
||||||
- support wildcard for both type and subtype (`*/*`)
|
|
||||||
- deps: mime-types@~2.0.9
|
|
||||||
|
|
||||||
1.11.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* make internal `extended: true` depth limit infinity
|
|
||||||
* deps: type-is@~1.5.6
|
|
||||||
- deps: mime-types@~2.0.8
|
|
||||||
|
|
||||||
1.10.2
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: iconv-lite@0.4.6
|
|
||||||
- Fix rare aliases of single-byte encodings
|
|
||||||
* deps: raw-body@1.3.2
|
|
||||||
- deps: iconv-lite@0.4.6
|
|
||||||
|
|
||||||
1.10.1
|
|
||||||
======
|
|
||||||
|
|
||||||
* deps: on-finished@~2.2.0
|
|
||||||
* deps: type-is@~1.5.5
|
|
||||||
- deps: mime-types@~2.0.7
|
|
||||||
|
|
||||||
1.10.0
|
|
||||||
======
|
|
||||||
|
|
||||||
* make internal `extended: true` array limit dynamic
|
|
||||||
|
|
||||||
1.9.3
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: iconv-lite@0.4.5
|
|
||||||
- Fix Windows-31J and X-SJIS encoding support
|
|
||||||
* deps: qs@2.3.3
|
|
||||||
- Fix `arrayLimit` behavior
|
|
||||||
* deps: raw-body@1.3.1
|
|
||||||
- deps: iconv-lite@0.4.5
|
|
||||||
* deps: type-is@~1.5.3
|
|
||||||
- deps: mime-types@~2.0.3
|
|
||||||
|
|
||||||
1.9.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@2.3.2
|
|
||||||
- Fix parsing of mixed objects and values
|
|
||||||
|
|
||||||
1.9.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: on-finished@~2.1.1
|
|
||||||
- Fix handling of pipelined requests
|
|
||||||
* deps: qs@2.3.0
|
|
||||||
- Fix parsing of mixed implicit and explicit arrays
|
|
||||||
* deps: type-is@~1.5.2
|
|
||||||
- deps: mime-types@~2.0.2
|
|
||||||
|
|
||||||
1.9.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* include the charset in "unsupported charset" error message
|
|
||||||
* include the encoding in "unsupported content encoding" error message
|
|
||||||
* deps: depd@~1.0.0
|
|
||||||
|
|
||||||
1.8.4
|
|
||||||
=====
|
|
||||||
|
|
||||||
* fix content encoding to be case-insensitive
|
|
||||||
|
|
||||||
1.8.3
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@2.2.4
|
|
||||||
- Fix issue with object keys starting with numbers truncated
|
|
||||||
|
|
||||||
1.8.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: depd@0.4.5
|
|
||||||
|
|
||||||
1.8.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: media-typer@0.3.0
|
|
||||||
* deps: type-is@~1.5.1
|
|
||||||
|
|
||||||
1.8.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* make empty-body-handling consistent between chunked requests
|
|
||||||
- empty `json` produces `{}`
|
|
||||||
- empty `raw` produces `new Buffer(0)`
|
|
||||||
- empty `text` produces `''`
|
|
||||||
- empty `urlencoded` produces `{}`
|
|
||||||
* deps: qs@2.2.3
|
|
||||||
- Fix issue where first empty value in array is discarded
|
|
||||||
* deps: type-is@~1.5.0
|
|
||||||
- fix `hasbody` to be true for `content-length: 0`
|
|
||||||
|
|
||||||
1.7.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* add `parameterLimit` option to `urlencoded` parser
|
|
||||||
* change `urlencoded` extended array limit to 100
|
|
||||||
* respond with 413 when over `parameterLimit` in `urlencoded`
|
|
||||||
|
|
||||||
1.6.7
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@2.2.2
|
|
||||||
- Remove unnecessary cloning
|
|
||||||
|
|
||||||
1.6.6
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@2.2.0
|
|
||||||
- Array parsing fix
|
|
||||||
- Performance improvements
|
|
||||||
|
|
||||||
1.6.5
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: on-finished@2.1.0
|
|
||||||
|
|
||||||
1.6.4
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@1.2.2
|
|
||||||
|
|
||||||
1.6.3
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@1.2.1
|
|
||||||
|
|
||||||
1.6.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@1.2.0
|
|
||||||
- Fix parsing array of objects
|
|
||||||
|
|
||||||
1.6.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@1.1.0
|
|
||||||
- Accept urlencoded square brackets
|
|
||||||
- Accept empty values in implicit array notation
|
|
||||||
|
|
||||||
1.6.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: qs@1.0.2
|
|
||||||
- Complete rewrite
|
|
||||||
- Limits array length to 20
|
|
||||||
- Limits object depth to 5
|
|
||||||
- Limits parameters to 1,000
|
|
||||||
|
|
||||||
1.5.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: depd@0.4.4
|
|
||||||
- Work-around v8 generating empty stack traces
|
|
||||||
|
|
||||||
1.5.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: depd@0.4.3
|
|
||||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
|
||||||
|
|
||||||
1.5.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: depd@0.4.2
|
|
||||||
- Add `TRACE_DEPRECATION` environment variable
|
|
||||||
- Remove non-standard grey color from color output
|
|
||||||
- Support `--no-deprecation` argument
|
|
||||||
- Support `--trace-deprecation` argument
|
|
||||||
* deps: iconv-lite@0.4.4
|
|
||||||
- Added encoding UTF-7
|
|
||||||
* deps: raw-body@1.3.0
|
|
||||||
- deps: iconv-lite@0.4.4
|
|
||||||
- Added encoding UTF-7
|
|
||||||
- Fix `Cannot switch to old mode now` error on Node.js 0.10+
|
|
||||||
* deps: type-is@~1.3.2
|
|
||||||
|
|
||||||
1.4.3
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: type-is@1.3.1
|
|
||||||
- fix global variable leak
|
|
||||||
|
|
||||||
1.4.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: type-is@1.3.0
|
|
||||||
- improve type parsing
|
|
||||||
|
|
||||||
1.4.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* fix urlencoded extended deprecation message
|
|
||||||
|
|
||||||
1.4.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* add `text` parser
|
|
||||||
* add `raw` parser
|
|
||||||
* check accepted charset in content-type (accepts utf-8)
|
|
||||||
* check accepted encoding in content-encoding (accepts identity)
|
|
||||||
* deprecate `bodyParser()` middleware; use `.json()` and `.urlencoded()` as needed
|
|
||||||
* deprecate `urlencoded()` without provided `extended` option
|
|
||||||
* lazy-load urlencoded parsers
|
|
||||||
* parsers split into files for reduced mem usage
|
|
||||||
* support gzip and deflate bodies
|
|
||||||
- set `inflate: false` to turn off
|
|
||||||
* deps: raw-body@1.2.2
|
|
||||||
- Support all encodings from `iconv-lite`
|
|
||||||
|
|
||||||
1.3.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: type-is@1.2.1
|
|
||||||
- Switch dependency from mime to mime-types@1.0.0
|
|
||||||
|
|
||||||
1.3.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* add `extended` option to urlencoded parser
|
|
||||||
|
|
||||||
1.2.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* deps: raw-body@1.1.6
|
|
||||||
- assert stream encoding on node.js 0.8
|
|
||||||
- assert stream encoding on node.js < 0.10.6
|
|
||||||
- deps: bytes@1
|
|
||||||
|
|
||||||
1.2.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* invoke `next(err)` after request fully read
|
|
||||||
- prevents hung responses and socket hang ups
|
|
||||||
|
|
||||||
1.2.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* add `verify` option
|
|
||||||
* deps: type-is@1.2.0
|
|
||||||
- support suffix matching
|
|
||||||
|
|
||||||
1.1.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* improve json parser speed
|
|
||||||
|
|
||||||
1.1.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* fix repeated limit parsing with every request
|
|
||||||
|
|
||||||
1.1.0
|
|
||||||
=====
|
|
||||||
|
|
||||||
* add `type` option
|
|
||||||
* deps: pin for safety and consistency
|
|
||||||
|
|
||||||
1.0.2
|
|
||||||
=====
|
|
||||||
|
|
||||||
* use `type-is` module
|
|
||||||
|
|
||||||
1.0.1
|
|
||||||
=====
|
|
||||||
|
|
||||||
* lower default limits to 100kb
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
|
||||||
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
|
||||||
|
|
||||||
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.
|
|
||||||
-476
@@ -1,476 +0,0 @@
|
|||||||
# body-parser
|
|
||||||
|
|
||||||
[![NPM Version][npm-version-image]][npm-url]
|
|
||||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
|
||||||
[![Build Status][ci-image]][ci-url]
|
|
||||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
||||||
[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer]
|
|
||||||
|
|
||||||
Node.js body parsing middleware.
|
|
||||||
|
|
||||||
Parse incoming request bodies in a middleware before your handlers, available
|
|
||||||
under the `req.body` property.
|
|
||||||
|
|
||||||
**Note** As `req.body`'s shape is based on user-controlled input, all
|
|
||||||
properties and values in this object are untrusted and should be validated
|
|
||||||
before trusting. For example, `req.body.foo.toString()` may fail in multiple
|
|
||||||
ways, for example the `foo` property may not be there or may not be a string,
|
|
||||||
and `toString` may not be a function and instead a string or other user input.
|
|
||||||
|
|
||||||
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).
|
|
||||||
|
|
||||||
_This does not handle multipart bodies_, due to their complex and typically
|
|
||||||
large nature. For multipart bodies, you may be interested in the following
|
|
||||||
modules:
|
|
||||||
|
|
||||||
* [busboy](https://www.npmjs.org/package/busboy#readme) and
|
|
||||||
[connect-busboy](https://www.npmjs.org/package/connect-busboy#readme)
|
|
||||||
* [multiparty](https://www.npmjs.org/package/multiparty#readme) and
|
|
||||||
[connect-multiparty](https://www.npmjs.org/package/connect-multiparty#readme)
|
|
||||||
* [formidable](https://www.npmjs.org/package/formidable#readme)
|
|
||||||
* [multer](https://www.npmjs.org/package/multer#readme)
|
|
||||||
|
|
||||||
This module provides the following parsers:
|
|
||||||
|
|
||||||
* [JSON body parser](#bodyparserjsonoptions)
|
|
||||||
* [Raw body parser](#bodyparserrawoptions)
|
|
||||||
* [Text body parser](#bodyparsertextoptions)
|
|
||||||
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
|
|
||||||
|
|
||||||
Other body parsers you might be interested in:
|
|
||||||
|
|
||||||
- [body](https://www.npmjs.org/package/body#readme)
|
|
||||||
- [co-body](https://www.npmjs.org/package/co-body#readme)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm install body-parser
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```js
|
|
||||||
var bodyParser = require('body-parser')
|
|
||||||
```
|
|
||||||
|
|
||||||
The `bodyParser` object exposes various factories to create middlewares. All
|
|
||||||
middlewares will populate the `req.body` property with the parsed body when
|
|
||||||
the `Content-Type` request header matches the `type` option, or an empty
|
|
||||||
object (`{}`) if there was no body to parse, the `Content-Type` was not matched,
|
|
||||||
or an error occurred.
|
|
||||||
|
|
||||||
The various errors returned by this module are described in the
|
|
||||||
[errors section](#errors).
|
|
||||||
|
|
||||||
### bodyParser.json([options])
|
|
||||||
|
|
||||||
Returns middleware that only parses `json` and only looks at requests where
|
|
||||||
the `Content-Type` header matches the `type` option. This parser accepts any
|
|
||||||
Unicode encoding of the body and supports automatic inflation of `gzip` and
|
|
||||||
`deflate` encodings.
|
|
||||||
|
|
||||||
A new `body` object containing the parsed data is populated on the `request`
|
|
||||||
object after the middleware (i.e. `req.body`).
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
The `json` function takes an optional `options` object that may contain any of
|
|
||||||
the following keys:
|
|
||||||
|
|
||||||
##### inflate
|
|
||||||
|
|
||||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
|
||||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
|
||||||
|
|
||||||
##### limit
|
|
||||||
|
|
||||||
Controls the maximum request body size. If this is a number, then the value
|
|
||||||
specifies the number of bytes; if it is a string, the value is passed to the
|
|
||||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
||||||
to `'100kb'`.
|
|
||||||
|
|
||||||
##### reviver
|
|
||||||
|
|
||||||
The `reviver` option is passed directly to `JSON.parse` as the second
|
|
||||||
argument. You can find more information on this argument
|
|
||||||
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
|
|
||||||
|
|
||||||
##### strict
|
|
||||||
|
|
||||||
When set to `true`, will only accept arrays and objects; when `false` will
|
|
||||||
accept anything `JSON.parse` accepts. Defaults to `true`.
|
|
||||||
|
|
||||||
##### type
|
|
||||||
|
|
||||||
The `type` option is used to determine what media type the middleware will
|
|
||||||
parse. This option can be a string, array of strings, or a function. If not a
|
|
||||||
function, `type` option is passed directly to the
|
|
||||||
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
|
||||||
be an extension name (like `json`), a mime type (like `application/json`), or
|
|
||||||
a mime type with a wildcard (like `*/*` or `*/json`). If a function, the `type`
|
|
||||||
option is called as `fn(req)` and the request is parsed if it returns a truthy
|
|
||||||
value. Defaults to `application/json`.
|
|
||||||
|
|
||||||
##### verify
|
|
||||||
|
|
||||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
|
||||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
|
||||||
encoding of the request. The parsing can be aborted by throwing an error.
|
|
||||||
|
|
||||||
### bodyParser.raw([options])
|
|
||||||
|
|
||||||
Returns middleware that parses all bodies as a `Buffer` and only looks at
|
|
||||||
requests where the `Content-Type` header matches the `type` option. This
|
|
||||||
parser supports automatic inflation of `gzip` and `deflate` encodings.
|
|
||||||
|
|
||||||
A new `body` object containing the parsed data is populated on the `request`
|
|
||||||
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
|
|
||||||
of the body.
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
The `raw` function takes an optional `options` object that may contain any of
|
|
||||||
the following keys:
|
|
||||||
|
|
||||||
##### inflate
|
|
||||||
|
|
||||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
|
||||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
|
||||||
|
|
||||||
##### limit
|
|
||||||
|
|
||||||
Controls the maximum request body size. If this is a number, then the value
|
|
||||||
specifies the number of bytes; if it is a string, the value is passed to the
|
|
||||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
||||||
to `'100kb'`.
|
|
||||||
|
|
||||||
##### type
|
|
||||||
|
|
||||||
The `type` option is used to determine what media type the middleware will
|
|
||||||
parse. This option can be a string, array of strings, or a function.
|
|
||||||
If not a function, `type` option is passed directly to the
|
|
||||||
[type-is](https://www.npmjs.org/package/type-is#readme) library and this
|
|
||||||
can be an extension name (like `bin`), a mime type (like
|
|
||||||
`application/octet-stream`), or a mime type with a wildcard (like `*/*` or
|
|
||||||
`application/*`). If a function, the `type` option is called as `fn(req)`
|
|
||||||
and the request is parsed if it returns a truthy value. Defaults to
|
|
||||||
`application/octet-stream`.
|
|
||||||
|
|
||||||
##### verify
|
|
||||||
|
|
||||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
|
||||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
|
||||||
encoding of the request. The parsing can be aborted by throwing an error.
|
|
||||||
|
|
||||||
### bodyParser.text([options])
|
|
||||||
|
|
||||||
Returns middleware that parses all bodies as a string and only looks at
|
|
||||||
requests where the `Content-Type` header matches the `type` option. This
|
|
||||||
parser supports automatic inflation of `gzip` and `deflate` encodings.
|
|
||||||
|
|
||||||
A new `body` string containing the parsed data is populated on the `request`
|
|
||||||
object after the middleware (i.e. `req.body`). This will be a string of the
|
|
||||||
body.
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
The `text` function takes an optional `options` object that may contain any of
|
|
||||||
the following keys:
|
|
||||||
|
|
||||||
##### defaultCharset
|
|
||||||
|
|
||||||
Specify the default character set for the text content if the charset is not
|
|
||||||
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
|
|
||||||
|
|
||||||
##### inflate
|
|
||||||
|
|
||||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
|
||||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
|
||||||
|
|
||||||
##### limit
|
|
||||||
|
|
||||||
Controls the maximum request body size. If this is a number, then the value
|
|
||||||
specifies the number of bytes; if it is a string, the value is passed to the
|
|
||||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
||||||
to `'100kb'`.
|
|
||||||
|
|
||||||
##### type
|
|
||||||
|
|
||||||
The `type` option is used to determine what media type the middleware will
|
|
||||||
parse. This option can be a string, array of strings, or a function. If not
|
|
||||||
a function, `type` option is passed directly to the
|
|
||||||
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
|
||||||
be an extension name (like `txt`), a mime type (like `text/plain`), or a mime
|
|
||||||
type with a wildcard (like `*/*` or `text/*`). If a function, the `type`
|
|
||||||
option is called as `fn(req)` and the request is parsed if it returns a
|
|
||||||
truthy value. Defaults to `text/plain`.
|
|
||||||
|
|
||||||
##### verify
|
|
||||||
|
|
||||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
|
||||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
|
||||||
encoding of the request. The parsing can be aborted by throwing an error.
|
|
||||||
|
|
||||||
### bodyParser.urlencoded([options])
|
|
||||||
|
|
||||||
Returns middleware that only parses `urlencoded` bodies and only looks at
|
|
||||||
requests where the `Content-Type` header matches the `type` option. This
|
|
||||||
parser accepts only UTF-8 encoding of the body and supports automatic
|
|
||||||
inflation of `gzip` and `deflate` encodings.
|
|
||||||
|
|
||||||
A new `body` object containing the parsed data is populated on the `request`
|
|
||||||
object after the middleware (i.e. `req.body`). This object will contain
|
|
||||||
key-value pairs, where the value can be a string or array (when `extended` is
|
|
||||||
`false`), or any type (when `extended` is `true`).
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
The `urlencoded` function takes an optional `options` object that may contain
|
|
||||||
any of the following keys:
|
|
||||||
|
|
||||||
##### extended
|
|
||||||
|
|
||||||
The `extended` option allows to choose between parsing the URL-encoded data
|
|
||||||
with the `querystring` library (when `false`) or the `qs` library (when
|
|
||||||
`true`). The "extended" syntax allows for rich objects and arrays to be
|
|
||||||
encoded into the URL-encoded format, allowing for a JSON-like experience
|
|
||||||
with URL-encoded. For more information, please
|
|
||||||
[see the qs library](https://www.npmjs.org/package/qs#readme).
|
|
||||||
|
|
||||||
Defaults to `true`, but using the default has been deprecated. Please
|
|
||||||
research into the difference between `qs` and `querystring` and choose the
|
|
||||||
appropriate setting.
|
|
||||||
|
|
||||||
##### inflate
|
|
||||||
|
|
||||||
When set to `true`, then deflated (compressed) bodies will be inflated; when
|
|
||||||
`false`, deflated bodies are rejected. Defaults to `true`.
|
|
||||||
|
|
||||||
##### limit
|
|
||||||
|
|
||||||
Controls the maximum request body size. If this is a number, then the value
|
|
||||||
specifies the number of bytes; if it is a string, the value is passed to the
|
|
||||||
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
||||||
to `'100kb'`.
|
|
||||||
|
|
||||||
##### parameterLimit
|
|
||||||
|
|
||||||
The `parameterLimit` option controls the maximum number of parameters that
|
|
||||||
are allowed in the URL-encoded data. If a request contains more parameters
|
|
||||||
than this value, a 413 will be returned to the client. Defaults to `1000`.
|
|
||||||
|
|
||||||
##### type
|
|
||||||
|
|
||||||
The `type` option is used to determine what media type the middleware will
|
|
||||||
parse. This option can be a string, array of strings, or a function. If not
|
|
||||||
a function, `type` option is passed directly to the
|
|
||||||
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
|
|
||||||
be an extension name (like `urlencoded`), a mime type (like
|
|
||||||
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
|
|
||||||
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
|
|
||||||
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
|
|
||||||
to `application/x-www-form-urlencoded`.
|
|
||||||
|
|
||||||
##### verify
|
|
||||||
|
|
||||||
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
|
|
||||||
where `buf` is a `Buffer` of the raw request body and `encoding` is the
|
|
||||||
encoding of the request. The parsing can be aborted by throwing an error.
|
|
||||||
|
|
||||||
#### depth
|
|
||||||
|
|
||||||
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
|
|
||||||
The middlewares provided by this module create errors using the
|
|
||||||
[`http-errors` module](https://www.npmjs.com/package/http-errors). The errors
|
|
||||||
will typically have a `status`/`statusCode` property that contains the suggested
|
|
||||||
HTTP response code, an `expose` property to determine if the `message` property
|
|
||||||
should be displayed to the client, a `type` property to determine the type of
|
|
||||||
error without matching against the `message`, and a `body` property containing
|
|
||||||
the read body, if available.
|
|
||||||
|
|
||||||
The following are the common errors created, though any error can come through
|
|
||||||
for various reasons.
|
|
||||||
|
|
||||||
### content encoding unsupported
|
|
||||||
|
|
||||||
This error will occur when the request had a `Content-Encoding` header that
|
|
||||||
contained an encoding but the "inflation" option was set to `false`. The
|
|
||||||
`status` property is set to `415`, the `type` property is set to
|
|
||||||
`'encoding.unsupported'`, and the `charset` property will be set to the
|
|
||||||
encoding that is unsupported.
|
|
||||||
|
|
||||||
### entity parse failed
|
|
||||||
|
|
||||||
This error will occur when the request contained an entity that could not be
|
|
||||||
parsed by the middleware. The `status` property is set to `400`, the `type`
|
|
||||||
property is set to `'entity.parse.failed'`, and the `body` property is set to
|
|
||||||
the entity value that failed parsing.
|
|
||||||
|
|
||||||
### entity verify failed
|
|
||||||
|
|
||||||
This error will occur when the request contained an entity that could not be
|
|
||||||
failed verification by the defined `verify` option. The `status` property is
|
|
||||||
set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
|
|
||||||
`body` property is set to the entity value that failed verification.
|
|
||||||
|
|
||||||
### request aborted
|
|
||||||
|
|
||||||
This error will occur when the request is aborted by the client before reading
|
|
||||||
the body has finished. The `received` property will be set to the number of
|
|
||||||
bytes received before the request was aborted and the `expected` property is
|
|
||||||
set to the number of expected bytes. The `status` property is set to `400`
|
|
||||||
and `type` property is set to `'request.aborted'`.
|
|
||||||
|
|
||||||
### request entity too large
|
|
||||||
|
|
||||||
This error will occur when the request body's size is larger than the "limit"
|
|
||||||
option. The `limit` property will be set to the byte limit and the `length`
|
|
||||||
property will be set to the request body's length. The `status` property is
|
|
||||||
set to `413` and the `type` property is set to `'entity.too.large'`.
|
|
||||||
|
|
||||||
### request size did not match content length
|
|
||||||
|
|
||||||
This error will occur when the request's length did not match the length from
|
|
||||||
the `Content-Length` header. This typically occurs when the request is malformed,
|
|
||||||
typically when the `Content-Length` header was calculated based on characters
|
|
||||||
instead of bytes. The `status` property is set to `400` and the `type` property
|
|
||||||
is set to `'request.size.invalid'`.
|
|
||||||
|
|
||||||
### stream encoding should not be set
|
|
||||||
|
|
||||||
This error will occur when something called the `req.setEncoding` method prior
|
|
||||||
to this middleware. This module operates directly on bytes only and you cannot
|
|
||||||
call `req.setEncoding` when using this module. The `status` property is set to
|
|
||||||
`500` and the `type` property is set to `'stream.encoding.set'`.
|
|
||||||
|
|
||||||
### stream is not readable
|
|
||||||
|
|
||||||
This error will occur when the request is no longer readable when this middleware
|
|
||||||
attempts to read it. This typically means something other than a middleware from
|
|
||||||
this module read the request body already and the middleware was also configured to
|
|
||||||
read the same request. The `status` property is set to `500` and the `type`
|
|
||||||
property is set to `'stream.not.readable'`.
|
|
||||||
|
|
||||||
### too many parameters
|
|
||||||
|
|
||||||
This error will occur when the content of the request exceeds the configured
|
|
||||||
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
|
|
||||||
`413` and the `type` property is set to `'parameters.too.many'`.
|
|
||||||
|
|
||||||
### unsupported charset "BOGUS"
|
|
||||||
|
|
||||||
This error will occur when the request had a charset parameter in the
|
|
||||||
`Content-Type` header, but the `iconv-lite` module does not support it OR the
|
|
||||||
parser does not support it. The charset is contained in the message as well
|
|
||||||
as in the `charset` property. The `status` property is set to `415`, the
|
|
||||||
`type` property is set to `'charset.unsupported'`, and the `charset` property
|
|
||||||
is set to the charset that is unsupported.
|
|
||||||
|
|
||||||
### unsupported content encoding "bogus"
|
|
||||||
|
|
||||||
This error will occur when the request had a `Content-Encoding` header that
|
|
||||||
contained an unsupported encoding. The encoding is contained in the message
|
|
||||||
as well as in the `encoding` property. The `status` property is set to `415`,
|
|
||||||
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
|
|
||||||
property is set to the encoding that is unsupported.
|
|
||||||
|
|
||||||
### The input exceeded the depth
|
|
||||||
|
|
||||||
This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Express/Connect top-level generic
|
|
||||||
|
|
||||||
This example demonstrates adding a generic JSON and URL-encoded parser as a
|
|
||||||
top-level middleware, which will parse the bodies of all incoming requests.
|
|
||||||
This is the simplest setup.
|
|
||||||
|
|
||||||
```js
|
|
||||||
var express = require('express')
|
|
||||||
var bodyParser = require('body-parser')
|
|
||||||
|
|
||||||
var app = express()
|
|
||||||
|
|
||||||
// parse application/x-www-form-urlencoded
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }))
|
|
||||||
|
|
||||||
// parse application/json
|
|
||||||
app.use(bodyParser.json())
|
|
||||||
|
|
||||||
app.use(function (req, res) {
|
|
||||||
res.setHeader('Content-Type', 'text/plain')
|
|
||||||
res.write('you posted:\n')
|
|
||||||
res.end(JSON.stringify(req.body, null, 2))
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Express route-specific
|
|
||||||
|
|
||||||
This example demonstrates adding body parsers specifically to the routes that
|
|
||||||
need them. In general, this is the most recommended way to use body-parser with
|
|
||||||
Express.
|
|
||||||
|
|
||||||
```js
|
|
||||||
var express = require('express')
|
|
||||||
var bodyParser = require('body-parser')
|
|
||||||
|
|
||||||
var app = express()
|
|
||||||
|
|
||||||
// create application/json parser
|
|
||||||
var jsonParser = bodyParser.json()
|
|
||||||
|
|
||||||
// create application/x-www-form-urlencoded parser
|
|
||||||
var urlencodedParser = bodyParser.urlencoded({ extended: false })
|
|
||||||
|
|
||||||
// POST /login gets urlencoded bodies
|
|
||||||
app.post('/login', urlencodedParser, function (req, res) {
|
|
||||||
res.send('welcome, ' + req.body.username)
|
|
||||||
})
|
|
||||||
|
|
||||||
// POST /api/users gets JSON bodies
|
|
||||||
app.post('/api/users', jsonParser, function (req, res) {
|
|
||||||
// create user in req.body
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Change accepted type for parsers
|
|
||||||
|
|
||||||
All the parsers accept a `type` option which allows you to change the
|
|
||||||
`Content-Type` that the middleware will parse.
|
|
||||||
|
|
||||||
```js
|
|
||||||
var express = require('express')
|
|
||||||
var bodyParser = require('body-parser')
|
|
||||||
|
|
||||||
var app = express()
|
|
||||||
|
|
||||||
// parse various different custom JSON types as JSON
|
|
||||||
app.use(bodyParser.json({ type: 'application/*+json' }))
|
|
||||||
|
|
||||||
// parse some custom thing into a Buffer
|
|
||||||
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }))
|
|
||||||
|
|
||||||
// parse an HTML body into a string
|
|
||||||
app.use(bodyParser.text({ type: 'text/html' }))
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
||||||
|
|
||||||
[ci-image]: https://badgen.net/github/checks/expressjs/body-parser/master?label=ci
|
|
||||||
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
|
|
||||||
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/body-parser/master
|
|
||||||
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
|
|
||||||
[node-version-image]: https://badgen.net/npm/node/body-parser
|
|
||||||
[node-version-url]: https://nodejs.org/en/download
|
|
||||||
[npm-downloads-image]: https://badgen.net/npm/dm/body-parser
|
|
||||||
[npm-url]: https://npmjs.org/package/body-parser
|
|
||||||
[npm-version-image]: https://badgen.net/npm/v/body-parser
|
|
||||||
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
|
|
||||||
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser
|
|
||||||
-156
@@ -1,156 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var deprecate = require('depd')('body-parser')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache of loaded parsers.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var parsers = Object.create(null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef Parsers
|
|
||||||
* @type {function}
|
|
||||||
* @property {function} json
|
|
||||||
* @property {function} raw
|
|
||||||
* @property {function} text
|
|
||||||
* @property {function} urlencoded
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
* @type {Parsers}
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = deprecate.function(bodyParser,
|
|
||||||
'bodyParser: use individual json/urlencoded middlewares')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON parser.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Object.defineProperty(exports, 'json', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get: createParserGetter('json')
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw parser.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Object.defineProperty(exports, 'raw', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get: createParserGetter('raw')
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text parser.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Object.defineProperty(exports, 'text', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get: createParserGetter('text')
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL-encoded parser.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Object.defineProperty(exports, 'urlencoded', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get: createParserGetter('urlencoded')
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a middleware to parse json and urlencoded bodies.
|
|
||||||
*
|
|
||||||
* @param {object} [options]
|
|
||||||
* @return {function}
|
|
||||||
* @deprecated
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function bodyParser (options) {
|
|
||||||
// use default type for parsers
|
|
||||||
var opts = Object.create(options || null, {
|
|
||||||
type: {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
value: undefined,
|
|
||||||
writable: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var _urlencoded = exports.urlencoded(opts)
|
|
||||||
var _json = exports.json(opts)
|
|
||||||
|
|
||||||
return function bodyParser (req, res, next) {
|
|
||||||
_json(req, res, function (err) {
|
|
||||||
if (err) return next(err)
|
|
||||||
_urlencoded(req, res, next)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a getter for loading a parser.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function createParserGetter (name) {
|
|
||||||
return function get () {
|
|
||||||
return loadParser(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a parser module.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function loadParser (parserName) {
|
|
||||||
var parser = parsers[parserName]
|
|
||||||
|
|
||||||
if (parser !== undefined) {
|
|
||||||
return parser
|
|
||||||
}
|
|
||||||
|
|
||||||
// this uses a switch for static require analysis
|
|
||||||
switch (parserName) {
|
|
||||||
case 'json':
|
|
||||||
parser = require('./lib/types/json')
|
|
||||||
break
|
|
||||||
case 'raw':
|
|
||||||
parser = require('./lib/types/raw')
|
|
||||||
break
|
|
||||||
case 'text':
|
|
||||||
parser = require('./lib/types/text')
|
|
||||||
break
|
|
||||||
case 'urlencoded':
|
|
||||||
parser = require('./lib/types/urlencoded')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// store to prevent invoking require()
|
|
||||||
return (parsers[parserName] = parser)
|
|
||||||
}
|
|
||||||
-205
@@ -1,205 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var createError = require('http-errors')
|
|
||||||
var destroy = require('destroy')
|
|
||||||
var getBody = require('raw-body')
|
|
||||||
var iconv = require('iconv-lite')
|
|
||||||
var onFinished = require('on-finished')
|
|
||||||
var unpipe = require('unpipe')
|
|
||||||
var zlib = require('zlib')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = read
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a request into a buffer and parse.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @param {object} res
|
|
||||||
* @param {function} next
|
|
||||||
* @param {function} parse
|
|
||||||
* @param {function} debug
|
|
||||||
* @param {object} options
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function read (req, res, next, parse, debug, options) {
|
|
||||||
var length
|
|
||||||
var opts = options
|
|
||||||
var stream
|
|
||||||
|
|
||||||
// flag as parsed
|
|
||||||
req._body = true
|
|
||||||
|
|
||||||
// read options
|
|
||||||
var encoding = opts.encoding !== null
|
|
||||||
? opts.encoding
|
|
||||||
: null
|
|
||||||
var verify = opts.verify
|
|
||||||
|
|
||||||
try {
|
|
||||||
// get the content stream
|
|
||||||
stream = contentstream(req, debug, opts.inflate)
|
|
||||||
length = stream.length
|
|
||||||
stream.length = undefined
|
|
||||||
} catch (err) {
|
|
||||||
return next(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set raw-body options
|
|
||||||
opts.length = length
|
|
||||||
opts.encoding = verify
|
|
||||||
? null
|
|
||||||
: encoding
|
|
||||||
|
|
||||||
// assert charset is supported
|
|
||||||
if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
|
|
||||||
return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
|
||||||
charset: encoding.toLowerCase(),
|
|
||||||
type: 'charset.unsupported'
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// read body
|
|
||||||
debug('read body')
|
|
||||||
getBody(stream, opts, function (error, body) {
|
|
||||||
if (error) {
|
|
||||||
var _error
|
|
||||||
|
|
||||||
if (error.type === 'encoding.unsupported') {
|
|
||||||
// echo back charset
|
|
||||||
_error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
|
|
||||||
charset: encoding.toLowerCase(),
|
|
||||||
type: 'charset.unsupported'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// set status code on error
|
|
||||||
_error = createError(400, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpipe from stream and destroy
|
|
||||||
if (stream !== req) {
|
|
||||||
unpipe(req)
|
|
||||||
destroy(stream, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read off entire request
|
|
||||||
dump(req, function onfinished () {
|
|
||||||
next(createError(400, _error))
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify
|
|
||||||
if (verify) {
|
|
||||||
try {
|
|
||||||
debug('verify body')
|
|
||||||
verify(req, res, body, encoding)
|
|
||||||
} catch (err) {
|
|
||||||
next(createError(403, err, {
|
|
||||||
body: body,
|
|
||||||
type: err.type || 'entity.verify.failed'
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse
|
|
||||||
var str = body
|
|
||||||
try {
|
|
||||||
debug('parse body')
|
|
||||||
str = typeof body !== 'string' && encoding !== null
|
|
||||||
? iconv.decode(body, encoding)
|
|
||||||
: body
|
|
||||||
req.body = parse(str)
|
|
||||||
} catch (err) {
|
|
||||||
next(createError(400, err, {
|
|
||||||
body: str,
|
|
||||||
type: err.type || 'entity.parse.failed'
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the content stream of the request.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @param {function} debug
|
|
||||||
* @param {boolean} [inflate=true]
|
|
||||||
* @return {object}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function contentstream (req, debug, inflate) {
|
|
||||||
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
|
|
||||||
var length = req.headers['content-length']
|
|
||||||
var stream
|
|
||||||
|
|
||||||
debug('content-encoding "%s"', encoding)
|
|
||||||
|
|
||||||
if (inflate === false && encoding !== 'identity') {
|
|
||||||
throw createError(415, 'content encoding unsupported', {
|
|
||||||
encoding: encoding,
|
|
||||||
type: 'encoding.unsupported'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (encoding) {
|
|
||||||
case 'deflate':
|
|
||||||
stream = zlib.createInflate()
|
|
||||||
debug('inflate body')
|
|
||||||
req.pipe(stream)
|
|
||||||
break
|
|
||||||
case 'gzip':
|
|
||||||
stream = zlib.createGunzip()
|
|
||||||
debug('gunzip body')
|
|
||||||
req.pipe(stream)
|
|
||||||
break
|
|
||||||
case 'identity':
|
|
||||||
stream = req
|
|
||||||
stream.length = length
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
|
|
||||||
encoding: encoding,
|
|
||||||
type: 'encoding.unsupported'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dump the contents of a request.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @param {function} callback
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function dump (req, callback) {
|
|
||||||
if (onFinished.isFinished(req)) {
|
|
||||||
callback(null)
|
|
||||||
} else {
|
|
||||||
onFinished(req, callback)
|
|
||||||
req.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-247
@@ -1,247 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014 Jonathan Ong
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bytes = require('bytes')
|
|
||||||
var contentType = require('content-type')
|
|
||||||
var createError = require('http-errors')
|
|
||||||
var debug = require('debug')('body-parser:json')
|
|
||||||
var read = require('../read')
|
|
||||||
var typeis = require('type-is')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = json
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RegExp to match the first non-space in a string.
|
|
||||||
*
|
|
||||||
* Allowed whitespace is defined in RFC 7159:
|
|
||||||
*
|
|
||||||
* ws = *(
|
|
||||||
* %x20 / ; Space
|
|
||||||
* %x09 / ; Horizontal tab
|
|
||||||
* %x0A / ; Line feed or New line
|
|
||||||
* %x0D ) ; Carriage return
|
|
||||||
*/
|
|
||||||
|
|
||||||
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
|
|
||||||
|
|
||||||
var JSON_SYNTAX_CHAR = '#'
|
|
||||||
var JSON_SYNTAX_REGEXP = /#+/g
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a middleware to parse JSON bodies.
|
|
||||||
*
|
|
||||||
* @param {object} [options]
|
|
||||||
* @return {function}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function json (options) {
|
|
||||||
var opts = options || {}
|
|
||||||
|
|
||||||
var limit = typeof opts.limit === 'undefined' || opts.limit === null
|
|
||||||
? 102400 // 100kb default
|
|
||||||
: bytes.parse(opts.limit)
|
|
||||||
var inflate = opts.inflate !== false
|
|
||||||
var reviver = opts.reviver
|
|
||||||
var strict = opts.strict !== false
|
|
||||||
var type = opts.type || 'application/json'
|
|
||||||
var verify = opts.verify || false
|
|
||||||
|
|
||||||
if (limit === null) {
|
|
||||||
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verify !== false && typeof verify !== 'function') {
|
|
||||||
throw new TypeError('option verify must be function')
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the appropriate type checking function
|
|
||||||
var shouldParse = typeof type !== 'function'
|
|
||||||
? typeChecker(type)
|
|
||||||
: type
|
|
||||||
|
|
||||||
function parse (body) {
|
|
||||||
if (body.length === 0) {
|
|
||||||
// special-case empty json body, as it's a common client-side mistake
|
|
||||||
// TODO: maybe make this configurable or part of "strict" option
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strict) {
|
|
||||||
var first = firstchar(body)
|
|
||||||
|
|
||||||
if (first !== '{' && first !== '[') {
|
|
||||||
debug('strict violation')
|
|
||||||
throw createStrictSyntaxError(body, first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
debug('parse json')
|
|
||||||
return JSON.parse(body, reviver)
|
|
||||||
} catch (e) {
|
|
||||||
throw normalizeJsonSyntaxError(e, {
|
|
||||||
message: e.message,
|
|
||||||
stack: e.stack
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function jsonParser (req, res, next) {
|
|
||||||
if (req._body) {
|
|
||||||
debug('body already parsed')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body = req.body || {}
|
|
||||||
|
|
||||||
// skip requests without bodies
|
|
||||||
if (!typeis.hasBody(req)) {
|
|
||||||
debug('skip empty body')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('content-type %j', req.headers['content-type'])
|
|
||||||
|
|
||||||
// determine if request should be parsed
|
|
||||||
if (!shouldParse(req)) {
|
|
||||||
debug('skip parsing')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert charset per RFC 7159 sec 8.1
|
|
||||||
var charset = getCharset(req) || 'utf-8'
|
|
||||||
if (charset.slice(0, 4) !== 'utf-') {
|
|
||||||
debug('invalid charset')
|
|
||||||
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
|
|
||||||
charset: charset,
|
|
||||||
type: 'charset.unsupported'
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read
|
|
||||||
read(req, res, next, parse, debug, {
|
|
||||||
encoding: charset,
|
|
||||||
inflate: inflate,
|
|
||||||
limit: limit,
|
|
||||||
verify: verify
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create strict violation syntax error matching native error.
|
|
||||||
*
|
|
||||||
* @param {string} str
|
|
||||||
* @param {string} char
|
|
||||||
* @return {Error}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function createStrictSyntaxError (str, char) {
|
|
||||||
var index = str.indexOf(char)
|
|
||||||
var partial = ''
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
partial = str.substring(0, index) + new Array(str.length - index + 1).join(JSON_SYNTAX_CHAR)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
|
|
||||||
} catch (e) {
|
|
||||||
return normalizeJsonSyntaxError(e, {
|
|
||||||
message: e.message.replace(JSON_SYNTAX_REGEXP, function (placeholder) {
|
|
||||||
return str.substring(index, index + placeholder.length)
|
|
||||||
}),
|
|
||||||
stack: e.stack
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the first non-whitespace character in a string.
|
|
||||||
*
|
|
||||||
* @param {string} str
|
|
||||||
* @return {function}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function firstchar (str) {
|
|
||||||
var match = FIRST_CHAR_REGEXP.exec(str)
|
|
||||||
|
|
||||||
return match
|
|
||||||
? match[1]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the charset of a request.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getCharset (req) {
|
|
||||||
try {
|
|
||||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
|
||||||
} catch (e) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize a SyntaxError for JSON.parse.
|
|
||||||
*
|
|
||||||
* @param {SyntaxError} error
|
|
||||||
* @param {object} obj
|
|
||||||
* @return {SyntaxError}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function normalizeJsonSyntaxError (error, obj) {
|
|
||||||
var keys = Object.getOwnPropertyNames(error)
|
|
||||||
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var key = keys[i]
|
|
||||||
if (key !== 'stack' && key !== 'message') {
|
|
||||||
delete error[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace stack before message for Node.js 0.10 and below
|
|
||||||
error.stack = obj.stack.replace(error.message, obj.message)
|
|
||||||
error.message = obj.message
|
|
||||||
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simple type checker.
|
|
||||||
*
|
|
||||||
* @param {string} type
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function typeChecker (type) {
|
|
||||||
return function checkType (req) {
|
|
||||||
return Boolean(typeis(req, type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-105
@@ -1,105 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bytes = require('bytes')
|
|
||||||
var debug = require('debug')('body-parser:raw')
|
|
||||||
var read = require('../read')
|
|
||||||
var typeis = require('type-is')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = raw
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a middleware to parse raw bodies.
|
|
||||||
*
|
|
||||||
* @param {object} [options]
|
|
||||||
* @return {function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function raw (options) {
|
|
||||||
var opts = options || {}
|
|
||||||
|
|
||||||
var inflate = opts.inflate !== false
|
|
||||||
var limit = typeof opts.limit === 'undefined' || opts.limit === null
|
|
||||||
? 102400 // 100kb default
|
|
||||||
: bytes.parse(opts.limit)
|
|
||||||
var type = opts.type || 'application/octet-stream'
|
|
||||||
var verify = opts.verify || false
|
|
||||||
|
|
||||||
if (limit === null) {
|
|
||||||
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verify !== false && typeof verify !== 'function') {
|
|
||||||
throw new TypeError('option verify must be function')
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the appropriate type checking function
|
|
||||||
var shouldParse = typeof type !== 'function'
|
|
||||||
? typeChecker(type)
|
|
||||||
: type
|
|
||||||
|
|
||||||
function parse (buf) {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
return function rawParser (req, res, next) {
|
|
||||||
if (req._body) {
|
|
||||||
debug('body already parsed')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body = req.body || {}
|
|
||||||
|
|
||||||
// skip requests without bodies
|
|
||||||
if (!typeis.hasBody(req)) {
|
|
||||||
debug('skip empty body')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('content-type %j', req.headers['content-type'])
|
|
||||||
|
|
||||||
// determine if request should be parsed
|
|
||||||
if (!shouldParse(req)) {
|
|
||||||
debug('skip parsing')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read
|
|
||||||
read(req, res, next, parse, debug, {
|
|
||||||
encoding: null,
|
|
||||||
inflate: inflate,
|
|
||||||
limit: limit,
|
|
||||||
verify: verify
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simple type checker.
|
|
||||||
*
|
|
||||||
* @param {string} type
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function typeChecker (type) {
|
|
||||||
return function checkType (req) {
|
|
||||||
return Boolean(typeis(req, type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-125
@@ -1,125 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bytes = require('bytes')
|
|
||||||
var contentType = require('content-type')
|
|
||||||
var debug = require('debug')('body-parser:text')
|
|
||||||
var read = require('../read')
|
|
||||||
var typeis = require('type-is')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = text
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a middleware to parse text bodies.
|
|
||||||
*
|
|
||||||
* @param {object} [options]
|
|
||||||
* @return {function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function text (options) {
|
|
||||||
var opts = options || {}
|
|
||||||
|
|
||||||
var defaultCharset = opts.defaultCharset || 'utf-8'
|
|
||||||
var inflate = opts.inflate !== false
|
|
||||||
var limit = typeof opts.limit === 'undefined' || opts.limit === null
|
|
||||||
? 102400 // 100kb default
|
|
||||||
: bytes.parse(opts.limit)
|
|
||||||
var type = opts.type || 'text/plain'
|
|
||||||
var verify = opts.verify || false
|
|
||||||
|
|
||||||
if (limit === null) {
|
|
||||||
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verify !== false && typeof verify !== 'function') {
|
|
||||||
throw new TypeError('option verify must be function')
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the appropriate type checking function
|
|
||||||
var shouldParse = typeof type !== 'function'
|
|
||||||
? typeChecker(type)
|
|
||||||
: type
|
|
||||||
|
|
||||||
function parse (buf) {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
return function textParser (req, res, next) {
|
|
||||||
if (req._body) {
|
|
||||||
debug('body already parsed')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body = req.body || {}
|
|
||||||
|
|
||||||
// skip requests without bodies
|
|
||||||
if (!typeis.hasBody(req)) {
|
|
||||||
debug('skip empty body')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('content-type %j', req.headers['content-type'])
|
|
||||||
|
|
||||||
// determine if request should be parsed
|
|
||||||
if (!shouldParse(req)) {
|
|
||||||
debug('skip parsing')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get charset
|
|
||||||
var charset = getCharset(req) || defaultCharset
|
|
||||||
|
|
||||||
// read
|
|
||||||
read(req, res, next, parse, debug, {
|
|
||||||
encoding: charset,
|
|
||||||
inflate: inflate,
|
|
||||||
limit: limit,
|
|
||||||
verify: verify
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the charset of a request.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getCharset (req) {
|
|
||||||
try {
|
|
||||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
|
||||||
} catch (e) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simple type checker.
|
|
||||||
*
|
|
||||||
* @param {string} type
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function typeChecker (type) {
|
|
||||||
return function checkType (req) {
|
|
||||||
return Boolean(typeis(req, type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-303
@@ -1,303 +0,0 @@
|
|||||||
/*!
|
|
||||||
* body-parser
|
|
||||||
* Copyright(c) 2014 Jonathan Ong
|
|
||||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bytes = require('bytes')
|
|
||||||
var contentType = require('content-type')
|
|
||||||
var createError = require('http-errors')
|
|
||||||
var debug = require('debug')('body-parser:urlencoded')
|
|
||||||
var deprecate = require('depd')('body-parser')
|
|
||||||
var read = require('../read')
|
|
||||||
var typeis = require('type-is')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = urlencoded
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache of parser modules.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var parsers = Object.create(null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a middleware to parse urlencoded bodies.
|
|
||||||
*
|
|
||||||
* @param {object} [options]
|
|
||||||
* @return {function}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function urlencoded (options) {
|
|
||||||
var opts = options || {}
|
|
||||||
|
|
||||||
// notice because option default will flip in next major
|
|
||||||
if (opts.extended === undefined) {
|
|
||||||
deprecate('undefined extended: provide extended option')
|
|
||||||
}
|
|
||||||
|
|
||||||
var extended = opts.extended !== false
|
|
||||||
var inflate = opts.inflate !== false
|
|
||||||
var limit = typeof opts.limit === 'undefined' || opts.limit === null
|
|
||||||
? 102400 // 100kb default
|
|
||||||
: bytes.parse(opts.limit)
|
|
||||||
var type = opts.type || 'application/x-www-form-urlencoded'
|
|
||||||
var verify = opts.verify || false
|
|
||||||
|
|
||||||
if (limit === null) {
|
|
||||||
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verify !== false && typeof verify !== 'function') {
|
|
||||||
throw new TypeError('option verify must be function')
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the appropriate query parser
|
|
||||||
var queryparse = extended
|
|
||||||
? extendedparser(opts)
|
|
||||||
: simpleparser(opts)
|
|
||||||
|
|
||||||
// create the appropriate type checking function
|
|
||||||
var shouldParse = typeof type !== 'function'
|
|
||||||
? typeChecker(type)
|
|
||||||
: type
|
|
||||||
|
|
||||||
function parse (body) {
|
|
||||||
return body.length
|
|
||||||
? queryparse(body)
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function urlencodedParser (req, res, next) {
|
|
||||||
if (req._body) {
|
|
||||||
debug('body already parsed')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body = req.body || {}
|
|
||||||
|
|
||||||
// skip requests without bodies
|
|
||||||
if (!typeis.hasBody(req)) {
|
|
||||||
debug('skip empty body')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('content-type %j', req.headers['content-type'])
|
|
||||||
|
|
||||||
// determine if request should be parsed
|
|
||||||
if (!shouldParse(req)) {
|
|
||||||
debug('skip parsing')
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert charset
|
|
||||||
var charset = getCharset(req) || 'utf-8'
|
|
||||||
if (charset !== 'utf-8') {
|
|
||||||
debug('invalid charset')
|
|
||||||
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
|
|
||||||
charset: charset,
|
|
||||||
type: 'charset.unsupported'
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read
|
|
||||||
read(req, res, next, parse, debug, {
|
|
||||||
debug: debug,
|
|
||||||
encoding: charset,
|
|
||||||
inflate: inflate,
|
|
||||||
limit: limit,
|
|
||||||
verify: verify
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extended query parser.
|
|
||||||
*
|
|
||||||
* @param {object} options
|
|
||||||
*/
|
|
||||||
|
|
||||||
function extendedparser (options) {
|
|
||||||
var parameterLimit = options.parameterLimit !== undefined
|
|
||||||
? options.parameterLimit
|
|
||||||
: 1000
|
|
||||||
var depth = options.depth !== undefined ? options.depth : 32
|
|
||||||
var parse = parser('qs')
|
|
||||||
|
|
||||||
if (isNaN(parameterLimit) || parameterLimit < 1) {
|
|
||||||
throw new TypeError('option parameterLimit must be a positive number')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(depth) || depth < 0) {
|
|
||||||
throw new TypeError('option depth must be a zero or a positive number')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFinite(parameterLimit)) {
|
|
||||||
parameterLimit = parameterLimit | 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return function queryparse (body) {
|
|
||||||
var paramCount = parameterCount(body, parameterLimit)
|
|
||||||
|
|
||||||
if (paramCount === undefined) {
|
|
||||||
debug('too many parameters')
|
|
||||||
throw createError(413, 'too many parameters', {
|
|
||||||
type: 'parameters.too.many'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var arrayLimit = Math.max(100, paramCount)
|
|
||||||
|
|
||||||
debug('parse extended urlencoding')
|
|
||||||
try {
|
|
||||||
return parse(body, {
|
|
||||||
allowPrototypes: true,
|
|
||||||
arrayLimit: arrayLimit,
|
|
||||||
depth: depth,
|
|
||||||
strictDepth: true,
|
|
||||||
parameterLimit: parameterLimit
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof RangeError) {
|
|
||||||
throw createError(400, 'The input exceeded the depth', {
|
|
||||||
type: 'querystring.parse.rangeError'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the charset of a request.
|
|
||||||
*
|
|
||||||
* @param {object} req
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getCharset (req) {
|
|
||||||
try {
|
|
||||||
return (contentType.parse(req).parameters.charset || '').toLowerCase()
|
|
||||||
} catch (e) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of parameters, stopping once limit reached
|
|
||||||
*
|
|
||||||
* @param {string} body
|
|
||||||
* @param {number} limit
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function parameterCount (body, limit) {
|
|
||||||
var count = 0
|
|
||||||
var index = -1
|
|
||||||
|
|
||||||
do {
|
|
||||||
count++
|
|
||||||
if (count > limit) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
index = body.indexOf('&', index + 1)
|
|
||||||
} while (index !== -1)
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get parser for module name dynamically.
|
|
||||||
*
|
|
||||||
* @param {string} name
|
|
||||||
* @return {function}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function parser (name) {
|
|
||||||
var mod = parsers[name]
|
|
||||||
|
|
||||||
if (mod !== undefined) {
|
|
||||||
return mod.parse
|
|
||||||
}
|
|
||||||
|
|
||||||
// this uses a switch for static require analysis
|
|
||||||
switch (name) {
|
|
||||||
case 'qs':
|
|
||||||
mod = require('qs')
|
|
||||||
break
|
|
||||||
case 'querystring':
|
|
||||||
mod = require('querystring')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// store to prevent invoking require()
|
|
||||||
parsers[name] = mod
|
|
||||||
|
|
||||||
return mod.parse
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simple query parser.
|
|
||||||
*
|
|
||||||
* @param {object} options
|
|
||||||
*/
|
|
||||||
|
|
||||||
function simpleparser (options) {
|
|
||||||
var parameterLimit = options.parameterLimit !== undefined
|
|
||||||
? options.parameterLimit
|
|
||||||
: 1000
|
|
||||||
var parse = parser('querystring')
|
|
||||||
|
|
||||||
if (isNaN(parameterLimit) || parameterLimit < 1) {
|
|
||||||
throw new TypeError('option parameterLimit must be a positive number')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFinite(parameterLimit)) {
|
|
||||||
parameterLimit = parameterLimit | 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return function queryparse (body) {
|
|
||||||
var paramCount = parameterCount(body, parameterLimit)
|
|
||||||
|
|
||||||
if (paramCount === undefined) {
|
|
||||||
debug('too many parameters')
|
|
||||||
throw createError(413, 'too many parameters', {
|
|
||||||
type: 'parameters.too.many'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('parse urlencoding')
|
|
||||||
return parse(body, undefined, undefined, { maxKeys: parameterLimit })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simple type checker.
|
|
||||||
*
|
|
||||||
* @param {string} type
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function typeChecker (type) {
|
|
||||||
return function checkType (req) {
|
|
||||||
return Boolean(typeis(req, type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "body-parser",
|
|
||||||
"description": "Node.js body parsing middleware",
|
|
||||||
"version": "1.20.8",
|
|
||||||
"contributors": [
|
|
||||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
|
||||||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": "expressjs/body-parser",
|
|
||||||
"dependencies": {
|
|
||||||
"bytes": "~3.1.2",
|
|
||||||
"content-type": "~1.0.5",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "2.0.0",
|
|
||||||
"destroy": "~1.2.0",
|
|
||||||
"http-errors": "~2.0.1",
|
|
||||||
"iconv-lite": "~0.4.24",
|
|
||||||
"on-finished": "~2.4.1",
|
|
||||||
"qs": "~6.16.0",
|
|
||||||
"raw-body": "~2.5.3",
|
|
||||||
"type-is": "~1.6.18",
|
|
||||||
"unpipe": "~1.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "8.34.0",
|
|
||||||
"eslint-config-standard": "14.1.1",
|
|
||||||
"eslint-plugin-import": "2.27.5",
|
|
||||||
"eslint-plugin-markdown": "3.0.0",
|
|
||||||
"eslint-plugin-node": "11.1.0",
|
|
||||||
"eslint-plugin-promise": "6.1.1",
|
|
||||||
"eslint-plugin-standard": "4.1.0",
|
|
||||||
"methods": "1.1.2",
|
|
||||||
"mocha": "10.2.0",
|
|
||||||
"nyc": "15.1.0",
|
|
||||||
"safe-buffer": "5.2.1",
|
|
||||||
"supertest": "6.3.3"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"lib/",
|
|
||||||
"LICENSE",
|
|
||||||
"HISTORY.md",
|
|
||||||
"index.js"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8",
|
|
||||||
"npm": "1.2.8000 || >= 1.4.16"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint .",
|
|
||||||
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
|
|
||||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
|
||||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-97
@@ -1,97 +0,0 @@
|
|||||||
3.1.2 / 2022-01-27
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fix return value for un-parsable strings
|
|
||||||
|
|
||||||
3.1.1 / 2021-11-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fix "thousandsSeparator" incorrecting formatting fractional part
|
|
||||||
|
|
||||||
3.1.0 / 2019-01-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Add petabyte (`pb`) support
|
|
||||||
|
|
||||||
3.0.0 / 2017-08-31
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Change "kB" to "KB" in format output
|
|
||||||
* Remove support for Node.js 0.6
|
|
||||||
* Remove support for ComponentJS
|
|
||||||
|
|
||||||
2.5.0 / 2017-03-24
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Add option "unit"
|
|
||||||
|
|
||||||
2.4.0 / 2016-06-01
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Add option "unitSeparator"
|
|
||||||
|
|
||||||
2.3.0 / 2016-02-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Drop partial bytes on all parsed units
|
|
||||||
* Fix non-finite numbers to `.format` to return `null`
|
|
||||||
* Fix parsing byte string that looks like hex
|
|
||||||
* perf: hoist regular expressions
|
|
||||||
|
|
||||||
2.2.0 / 2015-11-13
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add option "decimalPlaces"
|
|
||||||
* add option "fixedDecimals"
|
|
||||||
|
|
||||||
2.1.0 / 2015-05-21
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add `.format` export
|
|
||||||
* add `.parse` export
|
|
||||||
|
|
||||||
2.0.2 / 2015-05-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
* remove map recreation
|
|
||||||
* remove unnecessary object construction
|
|
||||||
|
|
||||||
2.0.1 / 2015-05-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* fix browserify require
|
|
||||||
* remove node.extend dependency
|
|
||||||
|
|
||||||
2.0.0 / 2015-04-12
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add option "case"
|
|
||||||
* add option "thousandsSeparator"
|
|
||||||
* return "null" on invalid parse input
|
|
||||||
* support proper round-trip: bytes(bytes(num)) === num
|
|
||||||
* units no longer case sensitive when parsing
|
|
||||||
|
|
||||||
1.0.0 / 2014-05-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add negative support. fixes #6
|
|
||||||
|
|
||||||
0.3.0 / 2014-03-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* added terabyte support
|
|
||||||
|
|
||||||
0.2.1 / 2013-04-01
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add .component
|
|
||||||
|
|
||||||
0.2.0 / 2012-10-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* bytes(200).should.eql('200b')
|
|
||||||
|
|
||||||
0.1.0 / 2012-07-04
|
|
||||||
==================
|
|
||||||
|
|
||||||
* add bytes to string conversion [yields]
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2012-2014 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
Copyright (c) 2015 Jed Watson <jed.watson@me.com>
|
|
||||||
|
|
||||||
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.
|
|
||||||
-152
@@ -1,152 +0,0 @@
|
|||||||
# Bytes utility
|
|
||||||
|
|
||||||
[![NPM Version][npm-image]][npm-url]
|
|
||||||
[![NPM Downloads][downloads-image]][downloads-url]
|
|
||||||
[![Build Status][ci-image]][ci-url]
|
|
||||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
||||||
|
|
||||||
Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
This is a [Node.js](https://nodejs.org/en/) module available through the
|
|
||||||
[npm registry](https://www.npmjs.com/). Installation is done using the
|
|
||||||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install bytes
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```js
|
|
||||||
var bytes = require('bytes');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### bytes(number|string value, [options]): number|string|null
|
|
||||||
|
|
||||||
Default export function. Delegates to either `bytes.format` or `bytes.parse` based on the type of `value`.
|
|
||||||
|
|
||||||
**Arguments**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------|----------|--------------------|
|
|
||||||
| value | `number`|`string` | Number value to format or string value to parse |
|
|
||||||
| options | `Object` | Conversion options for `format` |
|
|
||||||
|
|
||||||
**Returns**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------|------------------|-------------------------------------------------|
|
|
||||||
| results | `string`|`number`|`null` | Return null upon error. Numeric value in bytes, or string value otherwise. |
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
```js
|
|
||||||
bytes(1024);
|
|
||||||
// output: '1KB'
|
|
||||||
|
|
||||||
bytes('1KB');
|
|
||||||
// output: 1024
|
|
||||||
```
|
|
||||||
|
|
||||||
#### bytes.format(number value, [options]): string|null
|
|
||||||
|
|
||||||
Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is
|
|
||||||
rounded.
|
|
||||||
|
|
||||||
**Arguments**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------|----------|--------------------|
|
|
||||||
| value | `number` | Value in bytes |
|
|
||||||
| options | `Object` | Conversion options |
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
| Property | Type | Description |
|
|
||||||
|-------------------|--------|-----------------------------------------------------------------------------------------|
|
|
||||||
| decimalPlaces | `number`|`null` | Maximum number of decimal places to include in output. Default value to `2`. |
|
|
||||||
| fixedDecimals | `boolean`|`null` | Whether to always display the maximum number of decimal places. Default value to `false` |
|
|
||||||
| thousandsSeparator | `string`|`null` | Example of values: `' '`, `','` and `'.'`... Default value to `''`. |
|
|
||||||
| unit | `string`|`null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). |
|
|
||||||
| unitSeparator | `string`|`null` | Separator to use between number and unit. Default value to `''`. |
|
|
||||||
|
|
||||||
**Returns**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------|------------------|-------------------------------------------------|
|
|
||||||
| results | `string`|`null` | Return null upon error. String value otherwise. |
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
```js
|
|
||||||
bytes.format(1024);
|
|
||||||
// output: '1KB'
|
|
||||||
|
|
||||||
bytes.format(1000);
|
|
||||||
// output: '1000B'
|
|
||||||
|
|
||||||
bytes.format(1000, {thousandsSeparator: ' '});
|
|
||||||
// output: '1 000B'
|
|
||||||
|
|
||||||
bytes.format(1024 * 1.7, {decimalPlaces: 0});
|
|
||||||
// output: '2KB'
|
|
||||||
|
|
||||||
bytes.format(1024, {unitSeparator: ' '});
|
|
||||||
// output: '1 KB'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### bytes.parse(string|number value): number|null
|
|
||||||
|
|
||||||
Parse the string value into an integer in bytes. If no unit is given, or `value`
|
|
||||||
is a number, it is assumed the value is in bytes.
|
|
||||||
|
|
||||||
Supported units and abbreviations are as follows and are case-insensitive:
|
|
||||||
|
|
||||||
* `b` for bytes
|
|
||||||
* `kb` for kilobytes
|
|
||||||
* `mb` for megabytes
|
|
||||||
* `gb` for gigabytes
|
|
||||||
* `tb` for terabytes
|
|
||||||
* `pb` for petabytes
|
|
||||||
|
|
||||||
The units are in powers of two, not ten. This means 1kb = 1024b according to this parser.
|
|
||||||
|
|
||||||
**Arguments**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------------|--------|--------------------|
|
|
||||||
| value | `string`|`number` | String to parse, or number in bytes. |
|
|
||||||
|
|
||||||
**Returns**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|---------|-------------|-------------------------|
|
|
||||||
| results | `number`|`null` | Return null upon error. Value in bytes otherwise. |
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
```js
|
|
||||||
bytes.parse('1KB');
|
|
||||||
// output: 1024
|
|
||||||
|
|
||||||
bytes.parse('1024');
|
|
||||||
// output: 1024
|
|
||||||
|
|
||||||
bytes.parse(1024);
|
|
||||||
// output: 1024
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
||||||
|
|
||||||
[ci-image]: https://badgen.net/github/checks/visionmedia/bytes.js/master?label=ci
|
|
||||||
[ci-url]: https://github.com/visionmedia/bytes.js/actions?query=workflow%3Aci
|
|
||||||
[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master
|
|
||||||
[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master
|
|
||||||
[downloads-image]: https://badgen.net/npm/dm/bytes
|
|
||||||
[downloads-url]: https://npmjs.org/package/bytes
|
|
||||||
[npm-image]: https://badgen.net/npm/v/bytes
|
|
||||||
[npm-url]: https://npmjs.org/package/bytes
|
|
||||||
-170
@@ -1,170 +0,0 @@
|
|||||||
/*!
|
|
||||||
* bytes
|
|
||||||
* Copyright(c) 2012-2014 TJ Holowaychuk
|
|
||||||
* Copyright(c) 2015 Jed Watson
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = bytes;
|
|
||||||
module.exports.format = format;
|
|
||||||
module.exports.parse = parse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module variables.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g;
|
|
||||||
|
|
||||||
var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/;
|
|
||||||
|
|
||||||
var map = {
|
|
||||||
b: 1,
|
|
||||||
kb: 1 << 10,
|
|
||||||
mb: 1 << 20,
|
|
||||||
gb: 1 << 30,
|
|
||||||
tb: Math.pow(1024, 4),
|
|
||||||
pb: Math.pow(1024, 5),
|
|
||||||
};
|
|
||||||
|
|
||||||
var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the given value in bytes into a string or parse to string to an integer in bytes.
|
|
||||||
*
|
|
||||||
* @param {string|number} value
|
|
||||||
* @param {{
|
|
||||||
* case: [string],
|
|
||||||
* decimalPlaces: [number]
|
|
||||||
* fixedDecimals: [boolean]
|
|
||||||
* thousandsSeparator: [string]
|
|
||||||
* unitSeparator: [string]
|
|
||||||
* }} [options] bytes options.
|
|
||||||
*
|
|
||||||
* @returns {string|number|null}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function bytes(value, options) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return parse(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return format(value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the given value in bytes into a string.
|
|
||||||
*
|
|
||||||
* If the value is negative, it is kept as such. If it is a float,
|
|
||||||
* it is rounded.
|
|
||||||
*
|
|
||||||
* @param {number} value
|
|
||||||
* @param {object} [options]
|
|
||||||
* @param {number} [options.decimalPlaces=2]
|
|
||||||
* @param {number} [options.fixedDecimals=false]
|
|
||||||
* @param {string} [options.thousandsSeparator=]
|
|
||||||
* @param {string} [options.unit=]
|
|
||||||
* @param {string} [options.unitSeparator=]
|
|
||||||
*
|
|
||||||
* @returns {string|null}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function format(value, options) {
|
|
||||||
if (!Number.isFinite(value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mag = Math.abs(value);
|
|
||||||
var thousandsSeparator = (options && options.thousandsSeparator) || '';
|
|
||||||
var unitSeparator = (options && options.unitSeparator) || '';
|
|
||||||
var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2;
|
|
||||||
var fixedDecimals = Boolean(options && options.fixedDecimals);
|
|
||||||
var unit = (options && options.unit) || '';
|
|
||||||
|
|
||||||
if (!unit || !map[unit.toLowerCase()]) {
|
|
||||||
if (mag >= map.pb) {
|
|
||||||
unit = 'PB';
|
|
||||||
} else if (mag >= map.tb) {
|
|
||||||
unit = 'TB';
|
|
||||||
} else if (mag >= map.gb) {
|
|
||||||
unit = 'GB';
|
|
||||||
} else if (mag >= map.mb) {
|
|
||||||
unit = 'MB';
|
|
||||||
} else if (mag >= map.kb) {
|
|
||||||
unit = 'KB';
|
|
||||||
} else {
|
|
||||||
unit = 'B';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var val = value / map[unit.toLowerCase()];
|
|
||||||
var str = val.toFixed(decimalPlaces);
|
|
||||||
|
|
||||||
if (!fixedDecimals) {
|
|
||||||
str = str.replace(formatDecimalsRegExp, '$1');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thousandsSeparator) {
|
|
||||||
str = str.split('.').map(function (s, i) {
|
|
||||||
return i === 0
|
|
||||||
? s.replace(formatThousandsRegExp, thousandsSeparator)
|
|
||||||
: s
|
|
||||||
}).join('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return str + unitSeparator + unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the string value into an integer in bytes.
|
|
||||||
*
|
|
||||||
* If no unit is given, it is assumed the value is in bytes.
|
|
||||||
*
|
|
||||||
* @param {number|string} val
|
|
||||||
*
|
|
||||||
* @returns {number|null}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function parse(val) {
|
|
||||||
if (typeof val === 'number' && !isNaN(val)) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof val !== 'string') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if the string passed is valid
|
|
||||||
var results = parseRegExp.exec(val);
|
|
||||||
var floatValue;
|
|
||||||
var unit = 'b';
|
|
||||||
|
|
||||||
if (!results) {
|
|
||||||
// Nothing could be extracted from the given string
|
|
||||||
floatValue = parseInt(val, 10);
|
|
||||||
unit = 'b'
|
|
||||||
} else {
|
|
||||||
// Retrieve the value and the unit
|
|
||||||
floatValue = parseFloat(results[1]);
|
|
||||||
unit = results[4].toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(floatValue)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.floor(map[unit] * floatValue);
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bytes",
|
|
||||||
"description": "Utility to parse a string bytes to bytes and vice-versa",
|
|
||||||
"version": "3.1.2",
|
|
||||||
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
|
|
||||||
"contributors": [
|
|
||||||
"Jed Watson <jed.watson@me.com>",
|
|
||||||
"Théo FIDRY <theo.fidry@gmail.com>"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [
|
|
||||||
"byte",
|
|
||||||
"bytes",
|
|
||||||
"utility",
|
|
||||||
"parse",
|
|
||||||
"parser",
|
|
||||||
"convert",
|
|
||||||
"converter"
|
|
||||||
],
|
|
||||||
"repository": "visionmedia/bytes.js",
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "7.32.0",
|
|
||||||
"eslint-plugin-markdown": "2.2.1",
|
|
||||||
"mocha": "9.2.0",
|
|
||||||
"nyc": "15.1.0"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"History.md",
|
|
||||||
"LICENSE",
|
|
||||||
"Readme.md",
|
|
||||||
"index.js"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint .",
|
|
||||||
"test": "mocha --check-leaks --reporter spec",
|
|
||||||
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
|
||||||
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
|
|
||||||
"extends": "@ljharb",
|
|
||||||
|
|
||||||
"rules": {
|
|
||||||
"func-name-matching": 0,
|
|
||||||
"id-length": 0,
|
|
||||||
"new-cap": [2, {
|
|
||||||
"capIsNewExceptions": [
|
|
||||||
"GetIntrinsic",
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
"no-extra-parens": 0,
|
|
||||||
"no-magic-numbers": 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: [ljharb]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: npm/call-bind-apply-helpers
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"all": true,
|
|
||||||
"check-coverage": false,
|
|
||||||
"reporter": ["text-summary", "text", "html", "json"],
|
|
||||||
"exclude": [
|
|
||||||
"coverage",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [v1.0.2](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.1...v1.0.2) - 2025-02-12
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
|
|
||||||
- [types] improve inferred types [`e6f9586`](https://github.com/ljharb/call-bind-apply-helpers/commit/e6f95860a3c72879cb861a858cdfb8138fbedec1)
|
|
||||||
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`e43d540`](https://github.com/ljharb/call-bind-apply-helpers/commit/e43d5409f97543bfbb11f345d47d8ce4e066d8c1)
|
|
||||||
|
|
||||||
## [v1.0.1](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.0...v1.0.1) - 2024-12-08
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
|
|
||||||
- [types] `reflectApply`: fix types [`4efc396`](https://github.com/ljharb/call-bind-apply-helpers/commit/4efc3965351a4f02cc55e836fa391d3d11ef2ef8)
|
|
||||||
- [Fix] `reflectApply`: oops, Reflect is not a function [`83cc739`](https://github.com/ljharb/call-bind-apply-helpers/commit/83cc7395de6b79b7730bdf092f1436f0b1263c75)
|
|
||||||
- [Dev Deps] update `@arethetypeswrong/cli` [`80bd5d3`](https://github.com/ljharb/call-bind-apply-helpers/commit/80bd5d3ae58b4f6b6995ce439dd5a1bcb178a940)
|
|
||||||
|
|
||||||
## v1.0.0 - 2024-12-05
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
|
|
||||||
- Initial implementation, tests, readme [`7879629`](https://github.com/ljharb/call-bind-apply-helpers/commit/78796290f9b7430c9934d6f33d94ae9bc89fce04)
|
|
||||||
- Initial commit [`3f1dc16`](https://github.com/ljharb/call-bind-apply-helpers/commit/3f1dc164afc43285631b114a5f9dd9137b2b952f)
|
|
||||||
- npm init [`081df04`](https://github.com/ljharb/call-bind-apply-helpers/commit/081df048c312fcee400922026f6e97281200a603)
|
|
||||||
- Only apps should have lockfiles [`5b9ca0f`](https://github.com/ljharb/call-bind-apply-helpers/commit/5b9ca0fe8101ebfaf309c549caac4e0a017ed930)
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 Jordan Harband
|
|
||||||
|
|
||||||
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.
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
# call-bind-apply-helpers <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
|
||||||
|
|
||||||
[![github actions][actions-image]][actions-url]
|
|
||||||
[![coverage][codecov-image]][codecov-url]
|
|
||||||
[![dependency status][deps-svg]][deps-url]
|
|
||||||
[![dev dependency status][dev-deps-svg]][dev-deps-url]
|
|
||||||
[![License][license-image]][license-url]
|
|
||||||
[![Downloads][downloads-image]][downloads-url]
|
|
||||||
|
|
||||||
[![npm badge][npm-badge-png]][package-url]
|
|
||||||
|
|
||||||
Helper functions around Function call/apply/bind, for use in `call-bind`.
|
|
||||||
|
|
||||||
The only packages that should likely ever use this package directly are `call-bind` and `get-intrinsic`.
|
|
||||||
Please use `call-bind` unless you have a very good reason not to.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install --save call-bind-apply-helpers
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage/Examples
|
|
||||||
|
|
||||||
```js
|
|
||||||
const assert = require('assert');
|
|
||||||
const callBindBasic = require('call-bind-apply-helpers');
|
|
||||||
|
|
||||||
function f(a, b) {
|
|
||||||
assert.equal(this, 1);
|
|
||||||
assert.equal(a, 2);
|
|
||||||
assert.equal(b, 3);
|
|
||||||
assert.equal(arguments.length, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fBound = callBindBasic([f, 1]);
|
|
||||||
|
|
||||||
delete Function.prototype.call;
|
|
||||||
delete Function.prototype.bind;
|
|
||||||
|
|
||||||
fBound(2, 3);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Clone the repo, `npm install`, and run `npm test`
|
|
||||||
|
|
||||||
[package-url]: https://npmjs.org/package/call-bind-apply-helpers
|
|
||||||
[npm-version-svg]: https://versionbadg.es/ljharb/call-bind-apply-helpers.svg
|
|
||||||
[deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers.svg
|
|
||||||
[deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers
|
|
||||||
[dev-deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers/dev-status.svg
|
|
||||||
[dev-deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers#info=devDependencies
|
|
||||||
[npm-badge-png]: https://nodei.co/npm/call-bind-apply-helpers.png?downloads=true&stars=true
|
|
||||||
[license-image]: https://img.shields.io/npm/l/call-bind-apply-helpers.svg
|
|
||||||
[license-url]: LICENSE
|
|
||||||
[downloads-image]: https://img.shields.io/npm/dm/call-bind-apply-helpers.svg
|
|
||||||
[downloads-url]: https://npm-stat.com/charts.html?package=call-bind-apply-helpers
|
|
||||||
[codecov-image]: https://codecov.io/gh/ljharb/call-bind-apply-helpers/branch/main/graphs/badge.svg
|
|
||||||
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bind-apply-helpers/
|
|
||||||
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bind-apply-helpers
|
|
||||||
[actions-url]: https://github.com/ljharb/call-bind-apply-helpers/actions
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export = Reflect.apply;
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var bind = require('function-bind');
|
|
||||||
|
|
||||||
var $apply = require('./functionApply');
|
|
||||||
var $call = require('./functionCall');
|
|
||||||
var $reflectApply = require('./reflectApply');
|
|
||||||
|
|
||||||
/** @type {import('./actualApply')} */
|
|
||||||
module.exports = $reflectApply || bind.call($call, $apply);
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
import actualApply from './actualApply';
|
|
||||||
|
|
||||||
type TupleSplitHead<T extends any[], N extends number> = T['length'] extends N
|
|
||||||
? T
|
|
||||||
: T extends [...infer R, any]
|
|
||||||
? TupleSplitHead<R, N>
|
|
||||||
: never
|
|
||||||
|
|
||||||
type TupleSplitTail<T, N extends number, O extends any[] = []> = O['length'] extends N
|
|
||||||
? T
|
|
||||||
: T extends [infer F, ...infer R]
|
|
||||||
? TupleSplitTail<[...R], N, [...O, F]>
|
|
||||||
: never
|
|
||||||
|
|
||||||
type TupleSplit<T extends any[], N extends number> = [TupleSplitHead<T, N>, TupleSplitTail<T, N>]
|
|
||||||
|
|
||||||
declare function applyBind(...args: TupleSplit<Parameters<typeof actualApply>, 2>[1]): ReturnType<typeof actualApply>;
|
|
||||||
|
|
||||||
export = applyBind;
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var bind = require('function-bind');
|
|
||||||
var $apply = require('./functionApply');
|
|
||||||
var actualApply = require('./actualApply');
|
|
||||||
|
|
||||||
/** @type {import('./applyBind')} */
|
|
||||||
module.exports = function applyBind() {
|
|
||||||
return actualApply(bind, $apply, arguments);
|
|
||||||
};
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export = Function.prototype.apply;
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/** @type {import('./functionApply')} */
|
|
||||||
module.exports = Function.prototype.apply;
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export = Function.prototype.call;
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/** @type {import('./functionCall')} */
|
|
||||||
module.exports = Function.prototype.call;
|
|
||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
type RemoveFromTuple<
|
|
||||||
Tuple extends readonly unknown[],
|
|
||||||
RemoveCount extends number,
|
|
||||||
Index extends 1[] = []
|
|
||||||
> = Index["length"] extends RemoveCount
|
|
||||||
? Tuple
|
|
||||||
: Tuple extends [infer First, ...infer Rest]
|
|
||||||
? RemoveFromTuple<Rest, RemoveCount, [...Index, 1]>
|
|
||||||
: Tuple;
|
|
||||||
|
|
||||||
type ConcatTuples<
|
|
||||||
Prefix extends readonly unknown[],
|
|
||||||
Suffix extends readonly unknown[]
|
|
||||||
> = [...Prefix, ...Suffix];
|
|
||||||
|
|
||||||
type ExtractFunctionParams<T> = T extends (this: infer TThis, ...args: infer P extends readonly unknown[]) => infer R
|
|
||||||
? { thisArg: TThis; params: P; returnType: R }
|
|
||||||
: never;
|
|
||||||
|
|
||||||
type BindFunction<
|
|
||||||
T extends (this: any, ...args: any[]) => any,
|
|
||||||
TThis,
|
|
||||||
TBoundArgs extends readonly unknown[],
|
|
||||||
ReceiverBound extends boolean
|
|
||||||
> = ExtractFunctionParams<T> extends {
|
|
||||||
thisArg: infer OrigThis;
|
|
||||||
params: infer P extends readonly unknown[];
|
|
||||||
returnType: infer R;
|
|
||||||
}
|
|
||||||
? ReceiverBound extends true
|
|
||||||
? (...args: RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>) => R extends [OrigThis, ...infer Rest]
|
|
||||||
? [TThis, ...Rest] // Replace `this` with `thisArg`
|
|
||||||
: R
|
|
||||||
: <U, RemainingArgs extends RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>>(
|
|
||||||
thisArg: U,
|
|
||||||
...args: RemainingArgs
|
|
||||||
) => R extends [OrigThis, ...infer Rest]
|
|
||||||
? [U, ...ConcatTuples<TBoundArgs, Rest>] // Preserve bound args in return type
|
|
||||||
: R
|
|
||||||
: never;
|
|
||||||
|
|
||||||
declare function callBind<
|
|
||||||
const T extends (this: any, ...args: any[]) => any,
|
|
||||||
Extracted extends ExtractFunctionParams<T>,
|
|
||||||
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[],
|
|
||||||
const TThis extends Extracted["thisArg"]
|
|
||||||
>(
|
|
||||||
args: [fn: T, thisArg: TThis, ...boundArgs: TBoundArgs]
|
|
||||||
): BindFunction<T, TThis, TBoundArgs, true>;
|
|
||||||
|
|
||||||
declare function callBind<
|
|
||||||
const T extends (this: any, ...args: any[]) => any,
|
|
||||||
Extracted extends ExtractFunctionParams<T>,
|
|
||||||
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[]
|
|
||||||
>(
|
|
||||||
args: [fn: T, ...boundArgs: TBoundArgs]
|
|
||||||
): BindFunction<T, Extracted["thisArg"], TBoundArgs, false>;
|
|
||||||
|
|
||||||
declare function callBind<const TArgs extends readonly unknown[]>(
|
|
||||||
args: [fn: Exclude<TArgs[0], Function>, ...rest: TArgs]
|
|
||||||
): never;
|
|
||||||
|
|
||||||
// export as namespace callBind;
|
|
||||||
export = callBind;
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var bind = require('function-bind');
|
|
||||||
var $TypeError = require('es-errors/type');
|
|
||||||
|
|
||||||
var $call = require('./functionCall');
|
|
||||||
var $actualApply = require('./actualApply');
|
|
||||||
|
|
||||||
/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */
|
|
||||||
module.exports = function callBindBasic(args) {
|
|
||||||
if (args.length < 1 || typeof args[0] !== 'function') {
|
|
||||||
throw new $TypeError('a function is required');
|
|
||||||
}
|
|
||||||
return $actualApply(bind, $call, args);
|
|
||||||
};
|
|
||||||
Loaded 100 of 2451 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user