Developers

Open files from your terminal

d3codex is a small, open shell script that opens any local D3 / PickBASIC file in the Annotator, full-screen, straight from your terminal. It encodes the file into the page URL and opens your browser. Nothing is uploaded: the source rides in the URL fragment, which the page reads back in the browser.

Install

One line. The installer downloads the script to ~/.local/bin and adds that dir to your PATH if it is not already there (no sudo, nothing outside your home). View the installer.

curl -fsSL https://d3codex.com/install.sh | bash

Prefer to do it by hand? Save the script into a directory already on your PATH (e.g. ~/.local/bin) and make it executable:

curl -fsSL https://d3codex.com/d3codex.sh -o ~/.local/bin/d3codex && chmod +x ~/.local/bin/d3codex

Use it

d3codex myprogram.bp
Open a file in the Annotator, full-screen Focus mode.
cat prog.bp | d3codex -
Read the program from stdin.
d3codex --split prog.bp
Open the split edit + preview view instead of Focus.
d3codex -p prog.bp
Print the URL instead of opening it (useful headless / over SSH).
D3CODEX_ORIGIN=http://localhost:7101 d3codex prog.bp
Point at a local D3Codex build.

d3codex --help lists every option.

How it works

The script builds the Annotator URL and opens it. The source is carried in the URL hash (after #), so it never leaves your machine for a server:

This is the same hash scheme the Annotator's own Share menu produces, so the script and the site stay in lock-step.

Requirements

Only standard tools: bash, base64, tr, mktemp. Compression (#z=) uses python3 or node if present; without either it simply uses the uncompressed form. Supported on macOS and Linux (and WSL on Windows). On native Windows the opener step differs: the script falls back to printing the URL for you to open.

The script

Read it before you run it. This is the exact file served at /d3codex.sh.

#!/usr/bin/env bash
# d3codex - open a local file in the D3Codex web Annotator, full-screen.
#
#   d3codex myprogram.bp
#
# Source / latest version: https://d3codex.com/d3codex.sh
# Annotator:               https://d3codex.com/tools/annotate/
#
# How it works (and a reference example for the D3Codex hash scheme): the source
# is carried in the URL fragment, so nothing is uploaded - the page reads it back
# in the browser. The Annotator (https://d3codex.com/tools/annotate/) accepts:
#   #s=<X>   X = base64url (no padding) of the file's UTF-8 bytes.
#   #z=<X>   X = base64url of a zlib DEFLATE stream of the bytes (used when shorter).
#   &focus=1 opens full-screen Focus mode.
# This script builds both forms, picks the shorter, and opens it in the browser.
#
# Supported on macOS and Linux (and WSL on Windows). On native Windows the opener
# step differs - the script falls back to printing the URL for you to open, or adapt
# opener() to your browser; everything else is portable.
set -euo pipefail

ORIGIN="${D3CODEX_ORIGIN:-https://d3codex.com}"   # override for local dev, e.g. http://localhost:7101
FOCUS="&focus=1"
PRINT_ONLY=0

usage() {
  cat <<'EOF'
d3codex - open a file in the D3Codex Annotator (full-screen Focus mode).
  d3codex FILE          open FILE in the annotator
  d3codex -             read the program from stdin
  d3codex -p FILE       print the URL instead of opening it
  d3codex --split FILE  open in split (edit + preview) view, not Focus
The source is carried in the URL hash; nothing is uploaded.
Override the site with D3CODEX_ORIGIN (e.g. http://localhost:7101).
EOF
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    -p|--print) PRINT_ONLY=1; shift ;;
    --split)    FOCUS=""; shift ;;
    -h|--help)  usage; exit 0 ;;
    --)         shift; break ;;
    -)          break ;;                                   # explicit stdin sentinel
    -*)         echo "d3codex: unknown option: $1" >&2; exit 2 ;;
    *)          break ;;
  esac
done

# base64url, no padding. The tr also unwraps base64's 76-column line breaks, so the
# output is identical on macOS (BSD base64) and Linux (GNU coreutils).
b64url() { base64 | tr '+/' '-_' | tr -d '=\n'; }

# zlib-format DEFLATE (RFC 1950), matching the site's CompressionStream('deflate'),
# which DecompressionStream('deflate') inflates regardless of compressor or level.
# Returns non-zero if neither python3 nor node is available.
deflate() {
  if command -v python3 >/dev/null 2>&1; then
    python3 -c 'import sys,zlib; sys.stdout.buffer.write(zlib.compress(sys.stdin.buffer.read(),9))'
  elif command -v node >/dev/null 2>&1; then
    node -e 'const z=require("zlib"),c=[];process.stdin.on("data",d=>c.push(d)).on("end",()=>process.stdout.write(z.deflateSync(Buffer.concat(c),{level:9})))'
  else
    return 1
  fi
}

# Open a URL in the default browser, or print it if no opener exists.
opener() {
  if   command -v open     >/dev/null 2>&1; then open "$1"
  elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$1"
  elif command -v wslview  >/dev/null 2>&1; then wslview "$1"
  else echo "d3codex: no opener found; URL is:" >&2; printf '%s\n' "$1"
  fi
}

# Resolve the source to a file. Buffer stdin to a temp file so it can be read
# twice (once raw, once compressed); clean it up on exit.
FILE="${1:-}"
TMP=""
trap '[ -n "$TMP" ] && rm -f "$TMP"' EXIT
if [ -z "$FILE" ] || [ "$FILE" = "-" ]; then
  TMP="$(mktemp "${TMPDIR:-/tmp}/d3codex.XXXXXX")"
  cat > "$TMP"
  SRCFILE="$TMP"; LABEL="stdin"
else
  [ -f "$FILE" ] || { echo "d3codex: no such file: $FILE" >&2; exit 1; }
  SRCFILE="$FILE"; LABEL="$FILE"
fi
[ -s "$SRCFILE" ] || { echo "d3codex: nothing to open (empty input)" >&2; exit 1; }

# Build the raw #s= link; switch to the compressed #z= link only if a deflater is
# present, exited cleanly, and produced a genuinely shorter fragment.
RAW="$(b64url < "$SRCFILE")"
KEY=s; FRAG="$RAW"
if Z="$(deflate < "$SRCFILE" 2>/dev/null | b64url)"; then
  [ -n "$Z" ] && [ "${#Z}" -lt "${#RAW}" ] && { KEY=z; FRAG="$Z"; }
fi
URL="${ORIGIN}/tools/annotate/#${KEY}=${FRAG}${FOCUS}"

# Very long programs can exceed the command-line length (ARG_MAX, passing the URL to
# the opener) or the browser's address-bar limit; warn but still try.
[ "${#URL}" -gt 100000 ] &&
  echo "d3codex: warning: large input; the URL may exceed the browser or command-line limit" >&2

if [ "$PRINT_ONLY" -eq 1 ]; then
  printf '%s\n' "$URL"
else
  printf 'Opening %s in the D3Codex Annotator...\n' "$LABEL"
  opener "$URL"
fi

View raw · Back to the API page