POSIX compliant

Also, it's smoother than the ZSH variant
This commit is contained in:
2025-05-22 11:04:45 +02:00
parent 3b6008d2b7
commit d840828642

210
fzf-vjour
View File

@@ -1,26 +1,30 @@
#!/usr/bin/env zsh #!/bin/sh
set -eu set -eu
# Read configuration # Read configuration
CONFIGFILE="$HOME/.config/fzf-vjour/config.yaml" CONFIGFILE="$HOME/.config/fzf-vjour/config.yaml"
if [[ ! -e "$CONFIGFILE" ]] if [ ! -e "$CONFIGFILE" ]; then
then
echo "Config file '$CONFIGFILE' not found" echo "Config file '$CONFIGFILE' not found"
exit 1 exit 1
fi fi
ROOT=$(yq '.datadir' <"$CONFIGFILE") ROOT=$(yq '.datadir' <"$CONFIGFILE")
if [[ ! -d "$ROOT" ]] if [ ! -d "$ROOT" ]; then
then
echo "Root directory not set or wrongly set" echo "Root directory not set or wrongly set"
exit 1 exit 1
fi fi
COLLECTION_NAMES=($(yq '.collections [].name' "$CONFIGFILE")) SED_COLLECTIONNAMES_TO_LABELS=$(
COLLECTION_LABELS=($(yq '.collections [].label' "$CONFIGFILE")) printf "sed "
COLLECTION_LABEL_MAX_LEN=0 yq '.collections[] | "s|/*" + .name + "/*|:" + .label + "\ |"' <"$CONFIGFILE" |
for label in "${COLLECTION_LABELS[@]}"; do xargs printf "-e \"%s\" "
[[ ${#label} -gt $COLLECTION_LABEL_MAX_LEN ]] && COLLECTION_LABEL_MAX_LEN=${#label} )
done SED_COLLECTIONLABELS_TO_NAMES=$(
printf "sed "
yq '.collections[] | "s|\ *" + .label + "\ *|/" + .name + "/|"' <"$CONFIGFILE" |
xargs printf "-e \"%s\" "
)
COLLECTION_LABEL_MAX_LEN=$(yq '[.collections[].label | length] | max' <"$CONFIGFILE")
LABLES=$(yq '.collections[].label' <"$CONFIGFILE")
SYNC_CMD=$(yq '.sync_cmd' <"$CONFIGFILE") SYNC_CMD=$(yq '.sync_cmd' <"$CONFIGFILE")
__vjournalnew() { __vjournalnew() {
@@ -176,20 +180,22 @@ print(json.dumps(data))'
# @param string: Path to ics file # @param string: Path to ics file
# @param string: Maximum length of filenames (for padding purposes) # @param string: Maximum length of filenames (for padding purposes)
__filepath_to_searchline() { __filepath_to_searchline() {
local filepath="$1" filepath="$1"
local maxlen="$2" maxlen="$2"
local totallen=$((maxlen + ${#ROOT})) totallen=$((maxlen + ${#ROOT}))
local filepathpad=$(printf "%-${totallen}s" "$filepath") filepathpad=$(printf "%-${totallen}s" "$filepath")
# Color support # Color support
local GREEN=$'\033[1;32m' GREEN=$(printf '\033[1;32m')
local WHITE=$'\033[1;97m' WHITE=$(printf '\033[1;97m')
local FAINT=$'\033[2m' FAINT=$(printf '\033[2m')
local OFF=$'\033[m'; OFF=$(printf '\033[m')
# Parse file # Parse file
local summary categories dtstamp dtstart summary=""
categories=""
dtstamp=""
dtstart=""
while IFS= read -r line; do while IFS= read -r line; do
case "$line" in case "$line" in
SUMMARY:*) SUMMARY:*)
@@ -205,34 +211,29 @@ __filepath_to_searchline() {
dtstart=$(echo "$line" | grep -oE '[0-9]{8}') dtstart=$(echo "$line" | grep -oE '[0-9]{8}')
;; ;;
esac esac
if [[ -n $summary && -n $categories && -n $dtstamp && -n $dtstart ]]; then if [ -n "$summary" ] && [ -n "$categories" ] && [ -n "$dtstamp" ] && [ -n "$dtstart" ]; then
break break
fi fi
done <"$filepath" done <"$filepath"
# Parse date # Parse date
if [[ -n $dtstart ]]; then if [ -n "$dtstart" ]; then
local date_target=$(date -d "$dtstart" +%s) date_target=$(date -d "$dtstart" +%s)
local date_today=$(date +%s) date_today=$(date +%s)
local date_delta=$(( (date_target - date_today) / 86400 )) date_delta=$(((date_target - date_today) / 86400))
local date_expr=$date_delta date_expr=$date_delta
if [[ $date_delta -eq 0 ]] if [ "$date_delta" -eq 0 ]; then
then
date_expr="today" date_expr="today"
elif [[ $date_delta -eq -1 ]] elif [ "$date_delta" -eq -1 ]; then
then
date_expr="yesterday" date_expr="yesterday"
elif [[ $date_delta -eq 1 ]] elif [ "$date_delta" -eq 1 ]; then
then
date_expr="tomorrow" date_expr="tomorrow"
elif [[ $date_delta -lt -1 && $date_delta -ge -7 ]] elif [ "$date_delta" -lt -1 ] && [ "$date_delta" -ge -7 ]; then
then date_expr="last $(date -d "$dtstart" +%A)"
date_expr="last $(date -d $dtstart +%A)" elif [ "$date_delta" -gt 1 ] && [ "$date_delta" -le 7 ]; then
elif [[ $date_delta -gt 1 && $date_delta -le 7 ]] date_expr="next $(date -d "$dtstart" +%A)"
then
date_expr="next $(date -d $dtstart +%A)"
else else
date_expr=$(date -d $dtstart +%x) date_expr=$(date -d "$dtstart" +%x)
fi fi
date_emoji="📘" date_emoji="📘"
else else
@@ -242,141 +243,136 @@ __filepath_to_searchline() {
date_expr=$(printf "%12s" "$date_expr") date_expr=$(printf "%12s" "$date_expr")
# Print line # Print line
echo -e "$dtstamp $filepath $WHITE$date_expr$OFF $date_emoji $GREEN$summary$OFF $FAINT$categories$OFF" echo "$dtstamp $filepath $WHITE$date_expr$OFF $date_emoji $GREEN$summary$OFF $FAINT$categories$OFF"
} }
__lines() { __lines() {
# Collect all files # Collect all files
local files=() FILES_TMP=$(mktemp)
while IFS= read -r -d '' file; do trap 'rm -f "$FILES_TMP"' EXIT
files+=("$file") find "$ROOT" -type f -name '*.ics' >"$FILES_TMP"
done < <(find "$ROOT" -type f -name '*.ics' -print0)
# Compute max length of basenames # Compute max length of basenames
local maxlen=0 maxlen=0
for file in "${files[@]}"; do while IFS= read -r file; do
local name=$(basename "$file") name=$(basename "$file")
[[ ${#name} -gt $maxlen ]] && maxlen=${#name} [ ${#name} -gt "$maxlen" ] && maxlen=${#name}
done done <"$FILES_TMP"
local lines=$(for file in "${files[@]}"; do lines=$(while IFS= read -r file; do
__filepath_to_searchline "$file" "$maxlen" __filepath_to_searchline "$file" "$maxlen"
done) done <"$FILES_TMP")
# Decorate # Decorate
for ((i = 1; i <= ${#COLLECTION_NAMES[@]}; i++)); do lines=$(echo "$lines" | eval "$SED_COLLECTIONNAMES_TO_LABELS")
#local label=$(printf "%${COLLECTION_LABEL_MAX_LEN}s" "${COLLECTION_LABELS[$i]}")
local label="${COLLECTION_LABELS[$i]}"
lines=$(echo "$lines" | sed "s|/*${COLLECTION_NAMES[$i]}/*|:$label |")
done
# Sort and cut off irreleant part # Sort and cut off irreleant part
lines=$(echo "$lines" | sort -g -r | cut -d ':' -f 2-) lines=$(echo "$lines" | sort -g -r | cut -d ':' -f 2-)
echo -e "$lines" echo "$lines"
} }
__filepath_from_selection() { __filepath_from_selection() {
local selection=$(echo "$1" | cut -d " " -f 1,2) selection=$(echo "$1" | cut -d " " -f 1,2 | eval "$SED_COLLECTIONLABELS_TO_NAMES" | sed "s|^|$ROOT/|")
for ((i = 1; i <= ${#COLLECTION_NAMES[@]}; i++)); do echo "$selection"
selection=$(echo "$selection" | sed "s|^.*${COLLECTION_LABELS[$i]} *|$ROOT/${COLLECTION_NAMES[$i]}/|")
done
echo -e "$selection"
} }
# Program starts here # Program starts here
# Command line arguments to be self-contained # Command line arguments to be self-contained
# Generate preview of file from selection # Generate preview of file from selection
if [[ "${1:-}" == "--preview" ]]; then if [ "${1:-}" = "--preview" ]; then
local vjfile=$(__filepath_from_selection "$2") vjfile=$(__filepath_from_selection "$2")
cat "$vjfile" | __vjournal2json | jq -r ".description" | batcat --color=always --style=numbers --language=md __vjournal2json <"$vjfile" | jq -r ".description" | batcat --color=always --style=numbers --language=md
exit exit
fi fi
# Delete file from selection # Delete file from selection
if [[ "${1:-}" == "--delete" ]]; then if [ "${1:-}" = "--delete" ]; then
local vjfile=$(__filepath_from_selection "$2") vjfile=$(__filepath_from_selection "$2")
rm -v "$vjfile" rm -v "$vjfile"
exit
fi fi
# Generate new entry # Generate new entry
if [[ "${1:-}" == "--new" ]]; then if [ "${1:-}" = "--new" ]; then
local label_new=$(for label in "${COLLECTION_LABELS[@]}"; do label_new=$(echo "$LABLES" | fzf --prompt="Select collection> ")
echo "$label" uuid_new=$(uuidgen)
done | fzf --prompt="Select collection> ") vjfile_new=$(__filepath_from_selection "$label_new $uuid_new.ics")
local uuid_new=$(uuidgen) if [ -f "$vjfile_new" ]; then
local vjfile_new=$(__filepath_from_selection "$label_new $uuid_new.ics")
if [[ -f "$vjfile_new" ]]
then
echo "Bad luck..." echo "Bad luck..."
return 1 return 1
fi fi
# #
local TMPFILE="$(mktemp).md" TMPFILE="$(mktemp).md"
local SHAFILE="$TMPFILE.sha" SHAFILE="$TMPFILE.sha"
trap "rm -f $TMPFILE $SHAFILE" EXIT trap 'rm -f "$TMPFILE" "$SHAFILE"' EXIT
echo "::: Keep this line if you want to add a **JOURNAL** entry (associated to today), else we will add a **NOTE**" > "$TMPFILE" {
echo "# <write summary here>" >> "$TMPFILE" echo "::: Keep this line if you want to add a **JOURNAL** entry (associated to today), else we will add a **NOTE**"
echo "> <comma-separated list of categories>" >> "$TMPFILE" echo "# <write summary here>"
echo >> "$TMPFILE" echo "> <comma-separated list of categories>"
echo ""
} >"$TMPFILE"
sha1sum "$TMPFILE" >"$SHAFILE" sha1sum "$TMPFILE" >"$SHAFILE"
# Open in editor # Open in editor
$EDITOR "$TMPFILE" $EDITOR "$TMPFILE" >/dev/tty
# Update if changes are detected # Update if changes are detected
if ! sha1sum -c "$SHAFILE" > /dev/null 2>&1 if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then
then vjfile_tmp="$TMPFILE.ics"
local vjfile_tmp="$TMPFILE.ics" trap 'rm -f "$vjfile_tmp"' EXIT
trap "rm -f $vjfile_tmp" EXIT
__vjournalnew "$uuid_new" <"$TMPFILE" >"$vjfile_tmp" && mv "$vjfile_tmp" "$vjfile_new" __vjournalnew "$uuid_new" <"$TMPFILE" >"$vjfile_tmp" && mv "$vjfile_tmp" "$vjfile_new"
fi fi
fi
if [ "${1:-}" = "--reload" ]; then
__lines
exit exit
fi fi
selection=$(__lines | fzf --ansi \ selection=$(
__lines | fzf --ansi \
--preview="$0 --preview {}" \ --preview="$0 --preview {}" \
--bind="ctrl-d:execute($0 --delete {})" \ --bind="ctrl-d:become($0 --delete {})" \
--bind="ctrl-n:execute($0 --new)" \ --bind="ctrl-n:become($0 --new)" \
--bind="ctrl-s:execute($SYNC_CMD)" \ --bind="ctrl-s:execute($SYNC_CMD)" \
--bind="ctrl-r:reload-sync($0 --reload)"
) )
if [[ -z "$selection" ]]; then if [ -z "$selection" ]; then
return 0; return 0
fi fi
VJ_FILE=$(__filepath_from_selection "$selection") VJ_FILE=$(__filepath_from_selection "$selection")
if [[ ! -f "$VJ_FILE" ]]; then if [ ! -f "$VJ_FILE" ]; then
echo "ERROR: File '$VJ_FILE' does not exist!" echo "ERROR: File '$VJ_FILE' does not exist!"
return 1; return 1
fi fi
# Parse vjournal file and save as json # Parse vjournal file and save as json
TMPJSON="$(mktemp).json" TMPJSON="$(mktemp).json"
trap "rm -f $TMPJSON" EXIT trap 'rm -f "$TMPJSON"' EXIT
cat "$VJ_FILE" | __vjournal2json > "$TMPJSON" __vjournal2json <"$VJ_FILE" >"$TMPJSON"
# Prepare file to be edited # Prepare file to be edited
TMPFILE="$(mktemp).md" TMPFILE="$(mktemp).md"
SHAFILE="$TMPFILE.sha" SHAFILE="$TMPFILE.sha"
trap "rm -f $TMPFILE $SHAFILE" EXIT trap 'rm -f "$TMPFILE" "$SHAFILE"' EXIT
SUMMARY=$(jq -r '.summary' "$TMPJSON") SUMMARY=$(jq -r '.summary' "$TMPJSON")
CATEGORIES=$(jq -r '.categories | join(",")' "$TMPJSON") CATEGORIES=$(jq -r '.categories | join(",")' "$TMPJSON")
echo "# $SUMMARY" > "$TMPFILE" {
echo "> $CATEGORIES" >> "$TMPFILE" echo "# $SUMMARY"
echo >> "$TMPFILE" echo "> $CATEGORIES"
jq -r '.description' "$TMPJSON" >> "$TMPFILE" echo ""
jq -r '.description' "$TMPJSON"
} >"$TMPFILE"
sha1sum "$TMPFILE" >"$SHAFILE" sha1sum "$TMPFILE" >"$SHAFILE"
# Open in editor # Open in editor
$EDITOR "$TMPFILE" $EDITOR "$TMPFILE"
# Update only if changes are detected # Update only if changes are detected
if ! sha1sum -c "$SHAFILE" > /dev/null 2>&1 if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then
then
echo "Uh... chages detected!" echo "Uh... chages detected!"
local vj_file_new="$TMPFILE.ics" vj_file_new="$TMPFILE.ics"
trap "rm -f $vj_file_new" EXIT trap 'rm -f "$vj_file_new"' EXIT
__vjournalupdate "$VJ_FILE" <"$TMPFILE" >"$vj_file_new" && mv "$vj_file_new" "$VJ_FILE" __vjournalupdate "$VJ_FILE" <"$TMPFILE" >"$vj_file_new" && mv "$vj_file_new" "$VJ_FILE"
fi fi