From d8408286422b56ddde6894bf331d86de67147f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Thu, 22 May 2025 11:04:45 +0200 Subject: [PATCH] POSIX compliant Also, it's smoother than the ZSH variant --- fzf-vjour | 260 +++++++++++++++++++++++++++--------------------------- 1 file changed, 128 insertions(+), 132 deletions(-) diff --git a/fzf-vjour b/fzf-vjour index 5200531..b29f4c9 100755 --- a/fzf-vjour +++ b/fzf-vjour @@ -1,27 +1,31 @@ -#!/usr/bin/env zsh +#!/bin/sh set -eu # Read configuration CONFIGFILE="$HOME/.config/fzf-vjour/config.yaml" -if [[ ! -e "$CONFIGFILE" ]] -then +if [ ! -e "$CONFIGFILE" ]; then echo "Config file '$CONFIGFILE' not found" exit 1 fi -ROOT=$(yq '.datadir' < "$CONFIGFILE") -if [[ ! -d "$ROOT" ]] -then +ROOT=$(yq '.datadir' <"$CONFIGFILE") +if [ ! -d "$ROOT" ]; then echo "Root directory not set or wrongly set" exit 1 fi -COLLECTION_NAMES=($(yq '.collections [].name' "$CONFIGFILE")) -COLLECTION_LABELS=($(yq '.collections [].label' "$CONFIGFILE")) -COLLECTION_LABEL_MAX_LEN=0 -for label in "${COLLECTION_LABELS[@]}"; do - [[ ${#label} -gt $COLLECTION_LABEL_MAX_LEN ]] && COLLECTION_LABEL_MAX_LEN=${#label} -done -SYNC_CMD=$(yq '.sync_cmd' < "$CONFIGFILE") +SED_COLLECTIONNAMES_TO_LABELS=$( + printf "sed " + yq '.collections[] | "s|/*" + .name + "/*|:" + .label + "\ |"' <"$CONFIGFILE" | + xargs printf "-e \"%s\" " +) +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") __vjournalnew() { python3 -c ' @@ -176,63 +180,60 @@ print(json.dumps(data))' # @param string: Path to ics file # @param string: Maximum length of filenames (for padding purposes) __filepath_to_searchline() { - local filepath="$1" - local maxlen="$2" - local totallen=$((maxlen + ${#ROOT})) - local filepathpad=$(printf "%-${totallen}s" "$filepath") - + filepath="$1" + maxlen="$2" + totallen=$((maxlen + ${#ROOT})) + filepathpad=$(printf "%-${totallen}s" "$filepath") + # Color support - local GREEN=$'\033[1;32m' - local WHITE=$'\033[1;97m' - local FAINT=$'\033[2m' - local OFF=$'\033[m'; + GREEN=$(printf '\033[1;32m') + WHITE=$(printf '\033[1;97m') + FAINT=$(printf '\033[2m') + OFF=$(printf '\033[m') # Parse file - local summary categories dtstamp dtstart - + summary="" + categories="" + dtstamp="" + dtstart="" while IFS= read -r line; do case "$line" in - SUMMARY:*) - summary="${line#SUMMARY:}" - ;; - CATEGORIES:*) - categories="${line#CATEGORIES:}" - ;; - DTSTAMP:*) - dtstamp="${line#DTSTAMP:}" - ;; - DTSTART*:[0-9]*) - dtstart=$(echo "$line" | grep -oE '[0-9]{8}') - ;; + SUMMARY:*) + summary="${line#SUMMARY:}" + ;; + CATEGORIES:*) + categories="${line#CATEGORIES:}" + ;; + DTSTAMP:*) + dtstamp="${line#DTSTAMP:}" + ;; + DTSTART*:[0-9]*) + dtstart=$(echo "$line" | grep -oE '[0-9]{8}') + ;; esac - if [[ -n $summary && -n $categories && -n $dtstamp && -n $dtstart ]]; then + if [ -n "$summary" ] && [ -n "$categories" ] && [ -n "$dtstamp" ] && [ -n "$dtstart" ]; then break fi - done < "$filepath" - - # Parse date - if [[ -n $dtstart ]]; then - local date_target=$(date -d "$dtstart" +%s) - local date_today=$(date +%s) - local date_delta=$(( (date_target - date_today) / 86400 )) - local date_expr=$date_delta - if [[ $date_delta -eq 0 ]] - then + done <"$filepath" + + # Parse date + if [ -n "$dtstart" ]; then + date_target=$(date -d "$dtstart" +%s) + date_today=$(date +%s) + date_delta=$(((date_target - date_today) / 86400)) + date_expr=$date_delta + if [ "$date_delta" -eq 0 ]; then date_expr="today" - elif [[ $date_delta -eq -1 ]] - then + elif [ "$date_delta" -eq -1 ]; then date_expr="yesterday" - elif [[ $date_delta -eq 1 ]] - then + elif [ "$date_delta" -eq 1 ]; then date_expr="tomorrow" - elif [[ $date_delta -lt -1 && $date_delta -ge -7 ]] - then - date_expr="last $(date -d $dtstart +%A)" - elif [[ $date_delta -gt 1 && $date_delta -le 7 ]] - then - date_expr="next $(date -d $dtstart +%A)" + elif [ "$date_delta" -lt -1 ] && [ "$date_delta" -ge -7 ]; then + date_expr="last $(date -d "$dtstart" +%A)" + elif [ "$date_delta" -gt 1 ] && [ "$date_delta" -le 7 ]; then + date_expr="next $(date -d "$dtstart" +%A)" else - date_expr=$(date -d $dtstart +%x) + date_expr=$(date -d "$dtstart" +%x) fi date_emoji="📘" else @@ -242,142 +243,137 @@ __filepath_to_searchline() { date_expr=$(printf "%12s" "$date_expr") # 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() { # Collect all files - local files=() - while IFS= read -r -d '' file; do - files+=("$file") - done < <(find "$ROOT" -type f -name '*.ics' -print0) + FILES_TMP=$(mktemp) + trap 'rm -f "$FILES_TMP"' EXIT + find "$ROOT" -type f -name '*.ics' >"$FILES_TMP" # Compute max length of basenames - local maxlen=0 - for file in "${files[@]}"; do - local name=$(basename "$file") - [[ ${#name} -gt $maxlen ]] && maxlen=${#name} - done + maxlen=0 + while IFS= read -r file; do + name=$(basename "$file") + [ ${#name} -gt "$maxlen" ] && maxlen=${#name} + done <"$FILES_TMP" - local lines=$(for file in "${files[@]}"; do - __filepath_to_searchline "$file" "$maxlen" - done) + lines=$(while IFS= read -r file; do + __filepath_to_searchline "$file" "$maxlen" + done <"$FILES_TMP") # Decorate - for ((i = 1; i <= ${#COLLECTION_NAMES[@]}; i++)); do - #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 + lines=$(echo "$lines" | eval "$SED_COLLECTIONNAMES_TO_LABELS") # Sort and cut off irreleant part lines=$(echo "$lines" | sort -g -r | cut -d ':' -f 2-) - echo -e "$lines" + echo "$lines" } __filepath_from_selection() { - local selection=$(echo "$1" | cut -d " " -f 1,2) - for ((i = 1; i <= ${#COLLECTION_NAMES[@]}; i++)); do - selection=$(echo "$selection" | sed "s|^.*${COLLECTION_LABELS[$i]} *|$ROOT/${COLLECTION_NAMES[$i]}/|") - done - echo -e "$selection" + selection=$(echo "$1" | cut -d " " -f 1,2 | eval "$SED_COLLECTIONLABELS_TO_NAMES" | sed "s|^|$ROOT/|") + echo "$selection" } # Program starts here # Command line arguments to be self-contained # Generate preview of file from selection -if [[ "${1:-}" == "--preview" ]]; then - local vjfile=$(__filepath_from_selection "$2") - cat "$vjfile" | __vjournal2json | jq -r ".description" | batcat --color=always --style=numbers --language=md +if [ "${1:-}" = "--preview" ]; then + vjfile=$(__filepath_from_selection "$2") + __vjournal2json <"$vjfile" | jq -r ".description" | batcat --color=always --style=numbers --language=md exit fi # Delete file from selection -if [[ "${1:-}" == "--delete" ]]; then - local vjfile=$(__filepath_from_selection "$2") +if [ "${1:-}" = "--delete" ]; then + vjfile=$(__filepath_from_selection "$2") rm -v "$vjfile" - exit fi # Generate new entry -if [[ "${1:-}" == "--new" ]]; then - local label_new=$(for label in "${COLLECTION_LABELS[@]}"; do - echo "$label" - done | fzf --prompt="Select collection> ") - local uuid_new=$(uuidgen) - local vjfile_new=$(__filepath_from_selection "$label_new $uuid_new.ics") - if [[ -f "$vjfile_new" ]] - then +if [ "${1:-}" = "--new" ]; then + label_new=$(echo "$LABLES" | fzf --prompt="Select collection> ") + uuid_new=$(uuidgen) + vjfile_new=$(__filepath_from_selection "$label_new $uuid_new.ics") + if [ -f "$vjfile_new" ]; then echo "Bad luck..." return 1 fi # - local TMPFILE="$(mktemp).md" - local SHAFILE="$TMPFILE.sha" - 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 "# " >> "$TMPFILE" - echo "> " >> "$TMPFILE" - echo >> "$TMPFILE" - sha1sum "$TMPFILE" > "$SHAFILE" + TMPFILE="$(mktemp).md" + SHAFILE="$TMPFILE.sha" + 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**" + echo "# " + echo "> " + echo "" + } >"$TMPFILE" + sha1sum "$TMPFILE" >"$SHAFILE" # Open in editor - $EDITOR "$TMPFILE" + $EDITOR "$TMPFILE" >/dev/tty # Update if changes are detected - if ! sha1sum -c "$SHAFILE" > /dev/null 2>&1 - then - local vjfile_tmp="$TMPFILE.ics" - trap "rm -f $vjfile_tmp" EXIT - __vjournalnew "$uuid_new" < "$TMPFILE" > "$vjfile_tmp" && mv "$vjfile_tmp" "$vjfile_new" + if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then + vjfile_tmp="$TMPFILE.ics" + trap 'rm -f "$vjfile_tmp"' EXIT + __vjournalnew "$uuid_new" <"$TMPFILE" >"$vjfile_tmp" && mv "$vjfile_tmp" "$vjfile_new" fi +fi +if [ "${1:-}" = "--reload" ]; then + __lines exit fi -selection=$(__lines | fzf --ansi \ - --preview="$0 --preview {}" \ - --bind="ctrl-d:execute($0 --delete {})" \ - --bind="ctrl-n:execute($0 --new)" \ - --bind="ctrl-s:execute($SYNC_CMD)" \ +selection=$( + __lines | fzf --ansi \ + --preview="$0 --preview {}" \ + --bind="ctrl-d:become($0 --delete {})" \ + --bind="ctrl-n:become($0 --new)" \ + --bind="ctrl-s:execute($SYNC_CMD)" \ + --bind="ctrl-r:reload-sync($0 --reload)" ) -if [[ -z "$selection" ]]; then - return 0; +if [ -z "$selection" ]; then + return 0 fi VJ_FILE=$(__filepath_from_selection "$selection") -if [[ ! -f "$VJ_FILE" ]]; then +if [ ! -f "$VJ_FILE" ]; then echo "ERROR: File '$VJ_FILE' does not exist!" - return 1; + return 1 fi # Parse vjournal file and save as json TMPJSON="$(mktemp).json" -trap "rm -f $TMPJSON" EXIT -cat "$VJ_FILE" | __vjournal2json > "$TMPJSON" +trap 'rm -f "$TMPJSON"' EXIT +__vjournal2json <"$VJ_FILE" >"$TMPJSON" # Prepare file to be edited TMPFILE="$(mktemp).md" SHAFILE="$TMPFILE.sha" -trap "rm -f $TMPFILE $SHAFILE" EXIT +trap 'rm -f "$TMPFILE" "$SHAFILE"' EXIT SUMMARY=$(jq -r '.summary' "$TMPJSON") CATEGORIES=$(jq -r '.categories | join(",")' "$TMPJSON") -echo "# $SUMMARY" > "$TMPFILE" -echo "> $CATEGORIES" >> "$TMPFILE" -echo >> "$TMPFILE" -jq -r '.description' "$TMPJSON" >> "$TMPFILE" -sha1sum "$TMPFILE" > "$SHAFILE" +{ + echo "# $SUMMARY" + echo "> $CATEGORIES" + echo "" + jq -r '.description' "$TMPJSON" +} >"$TMPFILE" +sha1sum "$TMPFILE" >"$SHAFILE" # Open in editor $EDITOR "$TMPFILE" # Update only if changes are detected -if ! sha1sum -c "$SHAFILE" > /dev/null 2>&1 -then +if ! sha1sum -c "$SHAFILE" >/dev/null 2>&1; then echo "Uh... chages detected!" - local vj_file_new="$TMPFILE.ics" - trap "rm -f $vj_file_new" EXIT - __vjournalupdate "$VJ_FILE" < "$TMPFILE" > "$vj_file_new" && mv "$vj_file_new" "$VJ_FILE" + vj_file_new="$TMPFILE.ics" + trap 'rm -f "$vj_file_new"' EXIT + __vjournalupdate "$VJ_FILE" <"$TMPFILE" >"$vj_file_new" && mv "$vj_file_new" "$VJ_FILE" fi exec "$0"