125 lines
3.0 KiB
Bash
Executable File
125 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO="${PROJREPO:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
LOCK="$REPO/.projects.lock"
|
|
TS="$(date +%F_%H%M%S)"
|
|
cd "$REPO"
|
|
|
|
need() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: missing $1"; exit 1; }; }
|
|
need jq
|
|
need git
|
|
need python3
|
|
need flock
|
|
|
|
exec 9>"$LOCK"
|
|
flock -n 9 || { echo "ERROR: projectsctl is already running"; exit 2; }
|
|
|
|
backup() {
|
|
mkdir -p "$REPO/backups"
|
|
cp -a "$REPO/projects.json" "$REPO/backups/projects.json.$TS"
|
|
}
|
|
|
|
render() {
|
|
./tools/render_projects_index.py >/dev/null
|
|
}
|
|
|
|
git_sync() {
|
|
git rev-parse --is-inside-work-tree >/dev/null
|
|
git checkout main >/dev/null 2>&1 || true
|
|
git pull --rebase --autostash >/dev/null 2>&1 || true
|
|
}
|
|
|
|
git_commit_push() {
|
|
if git diff --quiet && git diff --cached --quiet; then
|
|
echo "No changes to commit."
|
|
exit 0
|
|
fi
|
|
git add projects.json PROJECTS_INDEX.md
|
|
git commit -m "projects: update ($TS)" >/dev/null
|
|
git push origin main >/dev/null
|
|
echo "OK: committed and pushed to main."
|
|
}
|
|
|
|
cmd="${1:-}"; shift || true
|
|
|
|
case "$cmd" in
|
|
add)
|
|
name="${1:-}"; status="${2:-active}"; shift 2 || true
|
|
repo="${1:-}"; local_path="${2:-}"; shift 2 || true
|
|
notes="${*:-}"
|
|
[[ -n "$name" ]] || { echo "usage: projectsctl add <name> [status] [repo] [local_path] [notes...]"; exit 1; }
|
|
|
|
git_sync
|
|
backup
|
|
|
|
tmp="$(mktemp)"
|
|
jq --arg name "$name" --arg status "$status" --arg repo "$repo" --arg lp "$local_path" --arg notes "$notes" '
|
|
.projects |= ( . + [ {name:$name,status:$status,type:"",repo:$repo,local_path:$lp,notes:$notes} ] )
|
|
' projects.json > "$tmp"
|
|
mv "$tmp" projects.json
|
|
|
|
render
|
|
git_commit_push
|
|
;;
|
|
|
|
set-status)
|
|
name="${1:-}"; status="${2:-}"
|
|
[[ -n "$name" && -n "$status" ]] || { echo "usage: projectsctl set-status <name> <active|paused|archived>"; exit 1; }
|
|
|
|
git_sync
|
|
backup
|
|
|
|
tmp="$(mktemp)"
|
|
jq --arg name "$name" --arg status "$status" '
|
|
.projects |= (map(if .name==$name then .status=$status else . end))
|
|
' projects.json > "$tmp"
|
|
mv "$tmp" projects.json
|
|
|
|
render
|
|
git_commit_push
|
|
;;
|
|
|
|
update-notes)
|
|
name="${1:-}"; shift || true
|
|
notes="${*:-}"
|
|
[[ -n "$name" && -n "$notes" ]] || { echo "usage: projectsctl update-notes <name> <notes...>"; exit 1; }
|
|
|
|
git_sync
|
|
backup
|
|
|
|
tmp="$(mktemp)"
|
|
jq --arg name "$name" --arg notes "$notes" '
|
|
.projects |= (map(if .name==$name then .notes=$notes else . end))
|
|
' projects.json > "$tmp"
|
|
mv "$tmp" projects.json
|
|
|
|
render
|
|
git_commit_push
|
|
;;
|
|
|
|
list)
|
|
python3 - <<'PY'
|
|
import json
|
|
p=json.load(open("projects.json"))
|
|
for pr in p.get("projects",[]):
|
|
print(f"{pr.get('status',''):8} {pr.get('name','')}")
|
|
PY
|
|
;;
|
|
|
|
render)
|
|
render
|
|
echo "OK: rendered PROJECTS_INDEX.md"
|
|
;;
|
|
|
|
*)
|
|
echo "usage:"
|
|
echo " projectsctl add <name> [status] [repo] [local_path] [notes...]"
|
|
echo " projectsctl set-status <name> <active|paused|archived>"
|
|
echo " projectsctl update-notes <name> <notes...>"
|
|
echo " projectsctl list"
|
|
echo " projectsctl render"
|
|
exit 1
|
|
;;
|
|
esac
|