182 lines
5.4 KiB
Nix
182 lines
5.4 KiB
Nix
{
|
|
writeShellScriptBin,
|
|
rofi,
|
|
libnotify,
|
|
coreutils,
|
|
gnugrep,
|
|
gnused,
|
|
jq,
|
|
}:
|
|
writeShellScriptBin "rofi-project-opener" ''
|
|
#!/usr/bin/env bash
|
|
# rofi-project-opener - Rofi-based project directory launcher
|
|
|
|
set -euo pipefail
|
|
|
|
# Core utilities
|
|
NOTIFY="${libnotify}/bin/notify-send"
|
|
ROFI="${rofi}/bin/rofi"
|
|
MKDIR="${coreutils}/bin/mkdir"
|
|
RM="${coreutils}/bin/rm"
|
|
SORT="${coreutils}/bin/sort"
|
|
CUT="${coreutils}/bin/cut"
|
|
DIRNAME="${coreutils}/bin/dirname"
|
|
BASENAME="${coreutils}/bin/basename"
|
|
GREP="${gnugrep}/bin/grep"
|
|
SED="${gnused}/bin/sed"
|
|
JQ="${jq}/bin/jq"
|
|
|
|
# Configuration files
|
|
CONFIG_DIR="''${XDG_CONFIG_HOME:-$HOME/.config}/rofi-project-opener"
|
|
CONFIG_FILE="$CONFIG_DIR/config"
|
|
PROJECTS_JSON="$CONFIG_DIR/projects.json"
|
|
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
|
|
# Fallback to environment variables or defaults
|
|
TERMINAL="''${TERMINAL:-kitty}"
|
|
TERMINAL_CMD="''${TERMINAL_CMD:-}"
|
|
ROFI_PROMPT="''${ROFI_PROMPT:-Select project}"
|
|
ROFI_ARGS="''${ROFI_ARGS:--dmenu -i}"
|
|
|
|
# Check for projects.json
|
|
if [[ ! -f "$PROJECTS_JSON" ]]; then
|
|
echo "[rofi-project-opener] Error: No projects.json found at $PROJECTS_JSON" >&2
|
|
"$NOTIFY" -u critical -a "rofi-project-opener" "No projects.json configuration found"
|
|
exit 1
|
|
fi
|
|
PROJECTS_JSON_DATA="$(cat "$PROJECTS_JSON")"
|
|
|
|
# Temporary file for project list
|
|
PROJECTS_LIST="''${XDG_RUNTIME_DIR:-/tmp}/rofi-project-opener.$$"
|
|
|
|
# Cleanup on exit
|
|
cleanup() {
|
|
[[ -f "$PROJECTS_LIST" ]] && "$RM" -f "$PROJECTS_LIST"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Ensure runtime directory exists
|
|
"$MKDIR" -p "$("$DIRNAME" "$PROJECTS_LIST")"
|
|
|
|
# Show error notification
|
|
show_error() {
|
|
local message="$1"
|
|
echo "[rofi-project-opener] Error: $message" >&2
|
|
"$NOTIFY" -u critical -a "rofi-project-opener" "$message"
|
|
}
|
|
|
|
# Build list of projects from JSON config
|
|
# Format: display_name|full_path|args
|
|
build_project_list() {
|
|
> "$PROJECTS_LIST"
|
|
|
|
# Parse JSON and iterate over each base directory entry
|
|
local keys
|
|
keys=$(echo "$PROJECTS_JSON_DATA" | "$JQ" -r 'keys[]')
|
|
|
|
for key in $keys; do
|
|
local base_path base_args
|
|
base_path=$(echo "$PROJECTS_JSON_DATA" | "$JQ" -r --arg k "$key" '.[$k].path')
|
|
base_args=$(echo "$PROJECTS_JSON_DATA" | "$JQ" -r --arg k "$key" '.[$k].args // ""')
|
|
|
|
# Expand ~ to $HOME
|
|
base_path="''${base_path/#\~/$HOME}"
|
|
# Expand $HOME variable (allows both ~ and $HOME in config)
|
|
base_path="$(eval echo "$base_path")"
|
|
|
|
# Skip if directory doesn't exist
|
|
if [[ ! -d "$base_path" ]]; then
|
|
echo "[rofi-project-opener] Warning: Directory does not exist: $base_path" >&2
|
|
continue
|
|
fi
|
|
|
|
# Find 1st level subdirectories (non-hidden)
|
|
for project in "$base_path"/*/; do
|
|
if [[ -d "$project" ]]; then
|
|
# Get directory name without trailing slash
|
|
project_path="''${project%/}"
|
|
project_name="$("$BASENAME" "$project_path")"
|
|
|
|
# Skip hidden directories
|
|
if [[ "$project_name" == .* ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Use key as display prefix (e.g., "dev/myproject")
|
|
echo "$key/$project_name|$project_path|$base_args" >> "$PROJECTS_LIST"
|
|
fi
|
|
done
|
|
done
|
|
|
|
# Check if we found any projects
|
|
[[ -s "$PROJECTS_LIST" ]]
|
|
}
|
|
|
|
# Build project list
|
|
if ! build_project_list; then
|
|
show_error "No projects found in configured directories"
|
|
exit 1
|
|
fi
|
|
|
|
# Deduplicate and sort
|
|
"$SORT" -t '|' -k1 -u "$PROJECTS_LIST" -o "$PROJECTS_LIST"
|
|
|
|
# Display in rofi and get selection
|
|
# shellcheck disable=SC2086
|
|
selection=$("$CUT" -d '|' -f1 "$PROJECTS_LIST" | "$ROFI" $ROFI_ARGS -p "$ROFI_PROMPT") || true
|
|
|
|
# Exit if cancelled
|
|
if [[ -z "$selection" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Get the full path and args from selection
|
|
selected_line=$("$GREP" "^$selection|" "$PROJECTS_LIST")
|
|
project_path=$(echo "$selected_line" | "$CUT" -d '|' -f2)
|
|
project_args=$(echo "$selected_line" | "$CUT" -d '|' -f3)
|
|
|
|
# Exit if path not found (shouldn't happen)
|
|
if [[ -z "$project_path" ]]; then
|
|
show_error "Could not find project path for: $selection"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify directory still exists
|
|
if [[ ! -d "$project_path" ]]; then
|
|
show_error "Project directory no longer exists: $project_path"
|
|
exit 1
|
|
fi
|
|
|
|
# Build terminal command
|
|
# Placeholders: %s = project path, %a = project args
|
|
if [[ -n "$TERMINAL_CMD" ]]; then
|
|
# Substitute placeholders
|
|
final_cmd="$TERMINAL_CMD"
|
|
final_cmd="''${final_cmd//%s/$project_path}"
|
|
final_cmd="''${final_cmd//%a/$project_args}"
|
|
|
|
# Source /etc/profile (base PATH) and HM session vars (sessionPath) for NixOS
|
|
exec "$TERMINAL" --hold -e bash --login -c "
|
|
unset __HM_SESS_VARS_SOURCED
|
|
for f in ~/.nix-profile/etc/profile.d/hm-session-vars.sh /etc/profiles/per-user/\$USER/etc/profile.d/hm-session-vars.sh; do
|
|
[ -f \"\$f\" ] && . \"\$f\"
|
|
done
|
|
cd '$project_path' && exec $final_cmd
|
|
"
|
|
else
|
|
# Default: open terminal, cd to project, run opencode with args
|
|
# Source /etc/profile (base PATH) and HM session vars (sessionPath) for NixOS
|
|
exec "$TERMINAL" --hold -e bash --login -c "
|
|
unset __HM_SESS_VARS_SOURCED
|
|
for f in ~/.nix-profile/etc/profile.d/hm-session-vars.sh /etc/profiles/per-user/\$USER/etc/profile.d/hm-session-vars.sh; do
|
|
[ -f \"\$f\" ] && . \"\$f\"
|
|
done
|
|
cd '$project_path' && opencode $project_args
|
|
"
|
|
fi
|
|
''
|