#!/usr/bin/env bash # Shared pieces for install.sh and settings.sh: the bash half of common.ps1. # # Sourced, not run. JSON is read through whichever of jq, node or python3 is present, # in that order -- a fresh Linux box has python3, a dev box has node, and jq is the # nicest when it is there. Nothing here needs more than bash, curl and one of those. set -euo pipefail setup_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" setup_config="$setup_root/config.json" setup_dist="$setup_root/dist" die() { echo "error: $*" >&2; exit 1; } note() { printf '\033[90m%s\033[0m\n' "$*"; } ok() { printf '\033[32m%s\033[0m\n' "$*"; } warn() { printf '\033[33m%s\033[0m\n' "$*"; } # --- JSON ------------------------------------------------------------------------ json_tool() { if command -v jq >/dev/null; then echo jq elif command -v node >/dev/null; then echo node elif command -v python3 >/dev/null; then echo python3 else die "need jq, node or python3 to read JSON" fi } # json_get -> the scalar at that path, or nothing. json_get() { local file="$1" path="$2" case "$(json_tool)" in jq) jq -r "try (.$path) // empty" "$file" 2>/dev/null ;; node) node -e ' const fs = require("fs"); const src = process.argv[1] === "-" ? 0 : process.argv[1]; let j; try { j = JSON.parse(fs.readFileSync(src, "utf8")); } catch { process.exit(0); } const v = process.argv[2].split(".").reduce((o, k) => (o == null ? undefined : o[k]), j); if (v !== undefined && v !== null) console.log(v); ' "$file" "$path" ;; python3) python3 - "$file" "$path" <<'EOF' import json, sys src = sys.stdin if sys.argv[1] == "-" else open(sys.argv[1], encoding="utf-8") try: j = json.load(src) except Exception: sys.exit(0) for k in sys.argv[2].split("."): j = j.get(k) if isinstance(j, dict) else None if j is None: sys.exit(0) print(j) EOF ;; esac } # json_rows ... -> one TSV line per element. json_rows() { local file="$1" path="$2"; shift 2 local fields="$*" case "$(json_tool)" in jq) jq -r --arg f "$fields" "try (.$path[] | [ (\$f | split(\" \"))[] as \$k | (.[\$k] // \"\" | tostring) ] | @tsv) // empty" "$file" 2>/dev/null ;; node) node -e ' const fs = require("fs"); const src = process.argv[1] === "-" ? 0 : process.argv[1]; let j; try { j = JSON.parse(fs.readFileSync(src, "utf8")); } catch { process.exit(0); } const arr = process.argv[2].split(".").reduce((o, k) => (o == null ? undefined : o[k]), j); const fields = process.argv[3].split(" "); for (const item of Array.isArray(arr) ? arr : []) console.log(fields.map(f => item[f] ?? "").join("\t")); ' "$file" "$path" "$fields" ;; python3) python3 - "$file" "$path" "$fields" <<'EOF' import json, sys src = sys.stdin if sys.argv[1] == "-" else open(sys.argv[1], encoding="utf-8") try: j = json.load(src) except Exception: sys.exit(0) for k in sys.argv[2].split("."): j = j.get(k) if isinstance(j, dict) else None for item in (j if isinstance(j, list) else []): print("\t".join(str(item.get(f, "")) for f in sys.argv[3].split(" "))) EOF ;; esac } # --- config --------------------------------------------------------------------- # GITEA_URL / GITEA_OWNER / GITEA_REPO override config.json, as in common.ps1. load_config() { [ -f "$setup_config" ] || die "config.json not found at $setup_config" gitea_url="${GITEA_URL:-$(json_get "$setup_config" gitea.url)}" gitea_owner="${GITEA_OWNER:-$(json_get "$setup_config" gitea.owner)}" gitea_repo="${GITEA_REPO:-$(json_get "$setup_config" gitea.repo)}" gitea_url="${gitea_url%/}" } require_gitea() { case "$gitea_url" in *example.com*|"") die "config.json still has the placeholder Gitea URL. Set it there, or set GITEA_URL, GITEA_OWNER and GITEA_REPO." ;; esac } # Prints "idpathrepo" for every extension, optionally only the given ids. configured_extensions() { local only="${1:-}" local rows rows="$(json_rows "$setup_config" extensions id path repo)" if [ -z "$only" ]; then echo "$rows" return fi local id for id in $only; do grep -P "^$id\t" <<<"$rows" || die "not in config.json: $id" done } # --- Gitea ---------------------------------------------------------------------- # The token, from the argument, GITEA_TOKEN, or ~/.gitea-token. Never from the repo. gitea_token() { local token="${1:-}" if [ -n "$token" ]; then echo "$token"; return; fi if [ -n "${GITEA_TOKEN:-}" ]; then echo "$GITEA_TOKEN"; return; fi if [ -s "$HOME/.gitea-token" ]; then tr -d '[:space:]' < "$HOME/.gitea-token"; return; fi die "No Gitea token. Set GITEA_TOKEN, write one to ~/.gitea-token, or pass --token." } # gitea_release -> the release JSON, or nothing for 404. gitea_release() { local tag="$1" token="$2" local encoded encoded="$(printf '%s' "$tag" | sed 's/ /%20/g; s/\//%2F/g')" local status body body="$(curl -sS -w '\n%{http_code}' -H "Authorization: token $token" \ "$gitea_url/api/v1/repos/$gitea_owner/$gitea_repo/releases/tags/$encoded")" status="${body##*$'\n'}" body="${body%$'\n'*}" case "$status" in 200) echo "$body" ;; 404) ;; *) die "Gitea answered HTTP $status for release '$tag': $body" ;; esac } # gitea_download # # curl re-sends the Authorization header across redirects only to the same host, which # is exactly the rule wanted: Gitea answers browser_download_url with a redirect to the # attachment on the same instance, and a redirect elsewhere must not carry the token. gitea_download() { local url="$1" destination="$2" token="$3" curl -fsSL --max-redirs 5 -H "Authorization: token $token" -o "$destination" "$url" } # --- VS Code -------------------------------------------------------------------- code_command() { if command -v code >/dev/null; then command -v code; return; fi local candidate for candidate in /usr/bin/code /usr/share/code/bin/code /snap/bin/code \ /var/lib/flatpak/exports/bin/com.visualstudio.code "$HOME/.local/bin/code" \ /usr/local/bin/code "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \ /usr/bin/code-insiders; do [ -x "$candidate" ] && { echo "$candidate"; return; } done die "The 'code' CLI was not found. In VS Code run 'Shell Command: Install code command in PATH', or add its bin/ to PATH." } # VS Code's user folder; VSCODE_USER_DIR overrides, as in the PowerShell scripts. code_user_dir() { if [ -n "${VSCODE_USER_DIR:-}" ]; then echo "$VSCODE_USER_DIR"; return; fi case "$(uname -s)" in Darwin) echo "$HOME/Library/Application Support/Code/User" ;; *) echo "${XDG_CONFIG_HOME:-$HOME/.config}/Code/User" ;; esac }