404 lines
14 KiB
Bash
Executable File
404 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Interactive demo launcher for pikl-menu.
|
|
# Uses pikl to pick a scenario, then runs that scenario in pikl.
|
|
#
|
|
# Usage: ./examples/demo.sh
|
|
|
|
set -euo pipefail
|
|
|
|
# Resolve the pikl binary once up front.
|
|
if [[ -n "${PIKL:-}" ]]; then
|
|
PIKL_BIN="$PIKL"
|
|
elif command -v pikl >/dev/null 2>&1; then
|
|
PIKL_BIN="pikl"
|
|
else
|
|
# Build quietly, use the debug binary directly.
|
|
cargo build --quiet 2>&1
|
|
PIKL_BIN="cargo run --quiet --"
|
|
fi
|
|
|
|
# Wrapper so scenarios can just call `pikl` with args.
|
|
pikl() {
|
|
$PIKL_BIN "$@"
|
|
}
|
|
|
|
# ── Scenario runners ──────────────────────────────────────
|
|
|
|
plain_list() {
|
|
printf "apple\nbanana\ncherry\ndate\nelderberry\nfig\ngrape\nhoneydew\n" \
|
|
| pikl
|
|
}
|
|
|
|
big_list() {
|
|
# seq output is wrapped as JSON strings so they get
|
|
# proper labels (bare numbers parse as JSON numbers
|
|
# with empty display text).
|
|
seq 1 500 | sed 's/.*/"&"/' | pikl
|
|
}
|
|
|
|
json_objects() {
|
|
cat <<'ITEMS' | pikl
|
|
{"label": "Arch Linux", "category": "rolling", "init": "systemd"}
|
|
{"label": "NixOS", "category": "rolling", "init": "systemd"}
|
|
{"label": "Void Linux", "category": "rolling", "init": "runit"}
|
|
{"label": "Debian", "category": "stable", "init": "systemd"}
|
|
{"label": "Alpine", "category": "stable", "init": "openrc"}
|
|
{"label": "Fedora", "category": "semi-rolling", "init": "systemd"}
|
|
{"label": "Gentoo", "category": "rolling", "init": "openrc"}
|
|
ITEMS
|
|
}
|
|
|
|
custom_label_key() {
|
|
cat <<'ITEMS' | pikl --label-key name
|
|
{"name": "Neovim", "type": "editor", "lang": "C/Lua"}
|
|
{"name": "Helix", "type": "editor", "lang": "Rust"}
|
|
{"name": "Kakoune", "type": "editor", "lang": "C++"}
|
|
{"name": "Emacs", "type": "editor", "lang": "Lisp"}
|
|
{"name": "Vim", "type": "editor", "lang": "C"}
|
|
ITEMS
|
|
}
|
|
|
|
git_branches() {
|
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
echo "not in a git repo" >&2
|
|
return 1
|
|
fi
|
|
git branch --format='%(refname:short)' | pikl
|
|
}
|
|
|
|
git_log_picker() {
|
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
echo "not in a git repo" >&2
|
|
return 1
|
|
fi
|
|
git log --oneline -30 | pikl
|
|
}
|
|
|
|
file_picker() {
|
|
find . -maxdepth 3 -type f \
|
|
-not -path './.git/*' \
|
|
-not -path './target/*' \
|
|
-not -name '*.lock' \
|
|
| sort \
|
|
| pikl
|
|
}
|
|
|
|
on_select_hook() {
|
|
printf "one\ntwo\nthree\nfour\nfive\n" \
|
|
| pikl --on-select-exec 'echo "you picked: $(cat)" >&2'
|
|
}
|
|
|
|
mixed_input() {
|
|
cat <<'ITEMS' | pikl
|
|
just a plain string
|
|
{"label": "a json object", "extra": 42}
|
|
another plain string
|
|
{"label": "second object", "extra": 99}
|
|
ITEMS
|
|
}
|
|
|
|
# ── Phase 3 demos ────────────────────────────────────────
|
|
|
|
structured_items() {
|
|
echo "Items have sublabel, icon, and metadata fields." >&2
|
|
echo "The output JSON includes action and index context." >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl
|
|
{"label": "Firefox", "sublabel": "Web Browser", "icon": "firefox", "meta": {"version": "125", "type": "browser"}}
|
|
{"label": "Neovim", "sublabel": "Text Editor", "icon": "nvim", "meta": {"version": "0.10", "type": "editor"}}
|
|
{"label": "Alacritty", "sublabel": "Terminal Emulator", "icon": "alacritty", "meta": {"version": "0.13", "type": "terminal"}}
|
|
{"label": "Thunar", "sublabel": "File Manager", "icon": "thunar", "meta": {"version": "4.18", "type": "filemanager"}}
|
|
{"label": "mpv", "sublabel": "Media Player", "icon": "mpv", "meta": {"version": "0.37", "type": "media"}}
|
|
ITEMS
|
|
}
|
|
|
|
format_template() {
|
|
echo "Using --format to control display." >&2
|
|
echo "Template: '{label} ({sublabel}) v{meta.version}'" >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl --format '{label} ({sublabel}) v{meta.version}'
|
|
{"label": "Firefox", "sublabel": "Web Browser", "meta": {"version": "125"}}
|
|
{"label": "Neovim", "sublabel": "Text Editor", "meta": {"version": "0.10"}}
|
|
{"label": "Alacritty", "sublabel": "Terminal Emulator", "meta": {"version": "0.13"}}
|
|
{"label": "Thunar", "sublabel": "File Manager", "meta": {"version": "4.18"}}
|
|
{"label": "mpv", "sublabel": "Media Player", "meta": {"version": "0.37"}}
|
|
ITEMS
|
|
}
|
|
|
|
filter_fields_demo() {
|
|
echo "Using --filter-fields to search sublabel and meta fields." >&2
|
|
echo "Try typing 'browser' or 'editor' to filter by sublabel." >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl --filter-fields label,sublabel --format '{label} - {sublabel}'
|
|
{"label": "Firefox", "sublabel": "Web Browser"}
|
|
{"label": "Neovim", "sublabel": "Text Editor"}
|
|
{"label": "Alacritty", "sublabel": "Terminal Emulator"}
|
|
{"label": "Thunar", "sublabel": "File Manager"}
|
|
{"label": "mpv", "sublabel": "Media Player"}
|
|
{"label": "GIMP", "sublabel": "Image Editor"}
|
|
{"label": "Inkscape", "sublabel": "Vector Graphics Editor"}
|
|
ITEMS
|
|
}
|
|
|
|
field_filter_demo() {
|
|
echo "Field filters in the query: type meta.type:browser to match a field." >&2
|
|
echo "Try: meta.type:browser or meta.type:editor or !meta.type:browser" >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl --format '{label} [{meta.type}] {meta.res}'
|
|
{"label": "Firefox", "meta": {"type": "browser", "res": "n/a"}}
|
|
{"label": "Chrome", "meta": {"type": "browser", "res": "n/a"}}
|
|
{"label": "Neovim", "meta": {"type": "editor", "res": "n/a"}}
|
|
{"label": "Helix", "meta": {"type": "editor", "res": "n/a"}}
|
|
{"label": "Alacritty", "meta": {"type": "terminal", "res": "n/a"}}
|
|
{"label": "kitty", "meta": {"type": "terminal", "res": "n/a"}}
|
|
{"label": "mpv", "meta": {"type": "media", "res": "3840x2160"}}
|
|
{"label": "vlc", "meta": {"type": "media", "res": "1920x1080"}}
|
|
ITEMS
|
|
}
|
|
|
|
exec_hooks_demo() {
|
|
echo "Exec hooks: --on-hover-exec fires a command on each cursor move." >&2
|
|
echo "Watch stderr for hover notifications as you navigate." >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl \
|
|
--on-hover-exec 'jq -r ".item.label // empty" | xargs -I{} echo " hovering: {}" >&2' \
|
|
--on-select-exec 'jq -r ".item.label // .value // empty" | xargs -I{} echo " selected: {}" >&2'
|
|
{"label": "Arch Linux", "sublabel": "rolling release"}
|
|
{"label": "NixOS", "sublabel": "declarative"}
|
|
{"label": "Void Linux", "sublabel": "runit-based"}
|
|
{"label": "Debian", "sublabel": "rock solid"}
|
|
{"label": "Alpine", "sublabel": "musl + busybox"}
|
|
ITEMS
|
|
}
|
|
|
|
handler_hook_demo() {
|
|
echo "Handler hooks: a persistent process receives events on stdin" >&2
|
|
echo "and can emit commands on stdout to modify the menu." >&2
|
|
echo "" >&2
|
|
echo "This demo logs hover events to stderr via a handler script." >&2
|
|
echo "The handler stays alive for the menu's lifetime." >&2
|
|
echo "" >&2
|
|
|
|
# Create a temporary handler script
|
|
local handler
|
|
handler=$(mktemp /tmp/pikl-handler-XXXXXX.sh)
|
|
cat > "$handler" <<'HANDLER'
|
|
#!/bin/bash
|
|
# Simple handler: logs events to stderr, demonstrates the protocol
|
|
while IFS= read -r event; do
|
|
event_type=$(echo "$event" | jq -r '.event // "unknown"')
|
|
case "$event_type" in
|
|
hover)
|
|
label=$(echo "$event" | jq -r '.item.label // "?"')
|
|
index=$(echo "$event" | jq -r '.index // "?"')
|
|
echo " handler got hover: $label (index $index)" >&2
|
|
;;
|
|
filter)
|
|
text=$(echo "$event" | jq -r '.text // ""')
|
|
echo " handler got filter: '$text'" >&2
|
|
;;
|
|
open)
|
|
echo " handler got open event" >&2
|
|
;;
|
|
close)
|
|
echo " handler got close event" >&2
|
|
;;
|
|
*)
|
|
echo " handler got: $event_type" >&2
|
|
;;
|
|
esac
|
|
done
|
|
HANDLER
|
|
chmod +x "$handler"
|
|
|
|
cat <<'ITEMS' | pikl --on-hover "$handler" --on-hover-debounce 100
|
|
{"label": "Maple", "sublabel": "Studio founder"}
|
|
{"label": "Cedar", "sublabel": "Backend dev"}
|
|
{"label": "Birch", "sublabel": "Frontend dev"}
|
|
{"label": "Pine", "sublabel": "DevOps"}
|
|
{"label": "Spruce", "sublabel": "QA"}
|
|
ITEMS
|
|
|
|
rm -f "$handler"
|
|
}
|
|
|
|
handler_add_items_demo() {
|
|
echo "Handler hooks can modify the menu by emitting commands." >&2
|
|
echo "This demo adds items when you hover over specific entries." >&2
|
|
echo "" >&2
|
|
|
|
local handler
|
|
handler=$(mktemp /tmp/pikl-handler-XXXXXX.sh)
|
|
cat > "$handler" <<'HANDLER'
|
|
#!/bin/bash
|
|
# Handler that adds related items on hover
|
|
while IFS= read -r event; do
|
|
event_type=$(echo "$event" | jq -r '.event // "unknown"')
|
|
if [ "$event_type" = "hover" ]; then
|
|
label=$(echo "$event" | jq -r '.item.label // ""')
|
|
case "$label" in
|
|
"Languages")
|
|
echo '{"action": "add_items", "items": [{"label": " Rust"}, {"label": " Go"}, {"label": " Python"}]}'
|
|
;;
|
|
"Editors")
|
|
echo '{"action": "add_items", "items": [{"label": " Neovim"}, {"label": " Helix"}, {"label": " Emacs"}]}'
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
HANDLER
|
|
chmod +x "$handler"
|
|
|
|
cat <<'ITEMS' | pikl --on-hover "$handler" --on-hover-debounce 300
|
|
{"label": "Languages"}
|
|
{"label": "Editors"}
|
|
{"label": "Shells"}
|
|
ITEMS
|
|
|
|
rm -f "$handler"
|
|
}
|
|
|
|
pipeline_filter_demo() {
|
|
echo "Filter pipeline demo: chain filters with |" >&2
|
|
echo "Try: 'rolling | !void (exact 'rolling', then exclude void)" >&2
|
|
echo "Try: /sys/ (regex: items containing 'sys')" >&2
|
|
echo "Try: meta.init:systemd (field filter on init system)" >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl --format '{label} ({meta.category}, {meta.init})'
|
|
{"label": "Arch Linux", "meta": {"category": "rolling", "init": "systemd"}}
|
|
{"label": "NixOS", "meta": {"category": "rolling", "init": "systemd"}}
|
|
{"label": "Void Linux", "meta": {"category": "rolling", "init": "runit"}}
|
|
{"label": "Debian", "meta": {"category": "stable", "init": "systemd"}}
|
|
{"label": "Alpine", "meta": {"category": "stable", "init": "openrc"}}
|
|
{"label": "Fedora", "meta": {"category": "semi-rolling", "init": "systemd"}}
|
|
{"label": "Gentoo", "meta": {"category": "rolling", "init": "openrc"}}
|
|
ITEMS
|
|
}
|
|
|
|
# ── Phase 4 demos ────────────────────────────────────────
|
|
|
|
multi_select_basic() {
|
|
echo "Multi-select: toggle items with Space or Tab, confirm with Enter." >&2
|
|
echo "Undo with u, redo with Ctrl+R, clear all with U." >&2
|
|
echo "" >&2
|
|
printf "apple\nbanana\ncherry\ndate\nelderberry\nfig\ngrape\nhoneydew\n" \
|
|
| pikl --multi
|
|
}
|
|
|
|
multi_select_packages() {
|
|
echo "Pick packages to install. Space to toggle, Enter to confirm." >&2
|
|
echo "Try V to enter visual mode, then j/k to extend, Space to apply." >&2
|
|
echo "" >&2
|
|
cat <<'ITEMS' | pikl --multi --format '{label} - {sublabel}'
|
|
{"label": "ripgrep", "sublabel": "fast recursive grep"}
|
|
{"label": "fd", "sublabel": "find alternative"}
|
|
{"label": "bat", "sublabel": "cat with syntax highlighting"}
|
|
{"label": "eza", "sublabel": "modern ls replacement"}
|
|
{"label": "delta", "sublabel": "better git diff"}
|
|
{"label": "zoxide", "sublabel": "smarter cd"}
|
|
{"label": "starship", "sublabel": "cross-shell prompt"}
|
|
{"label": "tokei", "sublabel": "code line counter"}
|
|
{"label": "hyperfine", "sublabel": "benchmarking tool"}
|
|
{"label": "procs", "sublabel": "modern ps replacement"}
|
|
ITEMS
|
|
}
|
|
|
|
multi_select_structured() {
|
|
echo "Multi-select with --structured output. Each selected item" >&2
|
|
echo "is a JSON line with action and index fields." >&2
|
|
echo "" >&2
|
|
printf "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" \
|
|
| pikl --multi --structured
|
|
}
|
|
|
|
# ── Scenario menu ─────────────────────────────────────────
|
|
|
|
scenarios=(
|
|
"Plain text list"
|
|
"Big list (500 items)"
|
|
"JSON objects (distros)"
|
|
"Custom --label-key (editors)"
|
|
"Git branches"
|
|
"Git log (last 30)"
|
|
"File picker"
|
|
"Mixed input (plain + JSON)"
|
|
"---"
|
|
"Structured items (sublabel, meta)"
|
|
"Format template (--format)"
|
|
"Filter fields (--filter-fields)"
|
|
"Field filters (meta.type:browser)"
|
|
"Pipeline + field filters"
|
|
"---"
|
|
"Exec hooks (on-hover/select)"
|
|
"Handler hook (event logging)"
|
|
"Handler hook (add items on hover)"
|
|
"---"
|
|
"Multi-select (basic)"
|
|
"Multi-select (packages)"
|
|
"Multi-select (structured output)"
|
|
"---"
|
|
"on-select-exec hook (legacy)"
|
|
)
|
|
|
|
# Map display names to functions
|
|
run_scenario() {
|
|
case "$1" in
|
|
*"Plain text"*) plain_list ;;
|
|
*"Big list"*) big_list ;;
|
|
*"JSON objects"*) json_objects ;;
|
|
*"label-key"*) custom_label_key ;;
|
|
*"Git branches"*) git_branches ;;
|
|
*"Git log"*) git_log_picker ;;
|
|
*"File picker"*) file_picker ;;
|
|
*"Mixed input"*) mixed_input ;;
|
|
*"Structured items"*) structured_items ;;
|
|
*"Format template"*) format_template ;;
|
|
*"Filter fields"*) filter_fields_demo ;;
|
|
*"Field filters"*) field_filter_demo ;;
|
|
*"Pipeline + field"*) pipeline_filter_demo ;;
|
|
*"Exec hooks"*) exec_hooks_demo ;;
|
|
*"Handler hook (event"*) handler_hook_demo ;;
|
|
*"Handler hook (add"*) handler_add_items_demo ;;
|
|
*"Multi-select (basic)"*) multi_select_basic ;;
|
|
*"Multi-select (pack"*) multi_select_packages ;;
|
|
*"Multi-select (struct"*) multi_select_structured ;;
|
|
*"on-select-exec"*) on_select_hook ;;
|
|
"---")
|
|
echo "that's a separator, not a scenario" >&2
|
|
return 1
|
|
;;
|
|
*)
|
|
echo "unknown scenario" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ── Main ──────────────────────────────────────────────────
|
|
|
|
main() {
|
|
echo "pikl demo launcher" >&2
|
|
echo "pick a scenario, then interact with it" >&2
|
|
echo "" >&2
|
|
|
|
choice=$(printf '%s\n' "${scenarios[@]}" | pikl) || {
|
|
echo "cancelled" >&2
|
|
exit 1
|
|
}
|
|
|
|
# pikl outputs JSON. Strip quotes and extract value for matching.
|
|
choice=$(echo "$choice" | jq -r '.value // .' 2>/dev/null || echo "$choice" | tr -d '"')
|
|
|
|
echo "" >&2
|
|
echo "── running: $choice ──" >&2
|
|
echo "" >&2
|
|
|
|
result=$(run_scenario "$choice") || exit $?
|
|
|
|
echo "" >&2
|
|
echo "── result ──" >&2
|
|
echo "$result"
|
|
}
|
|
|
|
main "$@"
|