Skip to content

Instantly share code, notes, and snippets.

@danecando
Last active November 27, 2025 13:38
Show Gist options
  • Select an option

  • Save danecando/f5fa5349cae9e7859450039c35fd137a to your computer and use it in GitHub Desktop.

Select an option

Save danecando/f5fa5349cae9e7859450039c35fd137a to your computer and use it in GitHub Desktop.
npm install, but for swift package manager
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: spm-install <github-user>/<repo>[@<selector>] [<target>...]
Examples:
spm-install alex-pinkus/tree-sitter-swift CodeHighlight
spm-install alex-pinkus/[email protected] CodeHighlight CodeHighlightTests
Selectors:
branch:<name> Track a specific branch (default branch is 'main').
rev:<sha> Pin a specific revision.
from:<ver> Range starting at version (up to next major).
<semver> Pin an exact version (e.g. 0.23.1 or v0.23.1).
The script adds the dependency via SwiftPM and wires the inferred TreeSitter*
product into each target.
EOF
}
err() {
echo "spm-install: $*" >&2
exit 1
}
[[ $# -ge 1 ]] || { usage >&2; exit 1; }
SPEC=$1
shift
TARGETS=("$@")
if [[ "$SPEC" == *@* ]]; then
REPO_PATH=${SPEC%@*}
SELECTOR=${SPEC#*@}
else
REPO_PATH=$SPEC
SELECTOR=""
fi
[[ -n "$REPO_PATH" ]] || err "missing repository in '$SPEC'"
[[ "$REPO_PATH" == */* ]] || err "repository must include owner, e.g. alex-pinkus/tree-sitter-swift"
REPO_URL="https://github.com/${REPO_PATH}"
PACKAGE_NAME=${REPO_PATH##*/}
pascal_case() {
local input=$1
if [[ -z "$input" ]]; then
echo ""
return
fi
echo "$input" | tr '-' ' ' | awk '{for(i=1;i<=NF;i++){printf toupper(substr($i,1,1)) tolower(substr($i,2));}}'
}
LANG_SEGMENT=${PACKAGE_NAME#tree-sitter-}
[[ -n "$LANG_SEGMENT" ]] || LANG_SEGMENT=$PACKAGE_NAME
PRODUCT_NAME="TreeSitter$(pascal_case "$LANG_SEGMENT")"
declare -a ADD_OPTS
case "$SELECTOR" in
"" )
BRANCH=${SPM_INSTALL_BRANCH:-${SWIFT_INSTALL_BRANCH:-main}}
ADD_OPTS=(--branch "$BRANCH")
DESC="branch $BRANCH"
;;
branch:*)
BRANCH=${SELECTOR#branch:}
[[ -n "$BRANCH" ]] || err "branch selector requires a name"
ADD_OPTS=(--branch "$BRANCH")
DESC="branch $BRANCH"
;;
rev:*)
REV=${SELECTOR#rev:}
[[ -n "$REV" ]] || err "revision selector requires a sha"
ADD_OPTS=(--revision "$REV")
DESC="revision $REV"
;;
from:*)
FROM=${SELECTOR#from:}
[[ -n "$FROM" ]] || err "from selector requires a version"
ADD_OPTS=(--from "$FROM")
DESC="from $FROM"
;;
*)
VERSION=${SELECTOR#v}
[[ -n "$VERSION" ]] || err "version selector requires a number"
ADD_OPTS=(--exact "$VERSION")
DESC="version $VERSION"
;;
esac
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
cd "$SCRIPT_DIR"
echo "Adding dependency ${REPO_URL} (${DESC})"
if ! swift package add-dependency "$REPO_URL" "${ADD_OPTS[@]}"; then
echo "spm-install: warning: swift package add-dependency failed (possibly already present)" >&2
fi
if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "No targets provided; dependency added only."
else
for target in "${TARGETS[@]}"; do
echo "Linking $PRODUCT_NAME into $target"
if ! swift package add-target-dependency "$PRODUCT_NAME" "$target" --package "$PACKAGE_NAME"; then
echo "spm-install: warning: failed to add $PRODUCT_NAME to $target" >&2
fi
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment