#!/bin/bash help() { printf '\ncontact is a command line contact manager\n\n' help-short printf '\n\nA typical contact looks like this: name=John Doe email=john.doe@doe-trading.com mobile=1234567890 nick=Johnny You can add any number of properties more: address, country, city, email2, url, xmpp or any custom property. `contact mutt-aliases` will print all contacts with alias file syntax. It will read the "name", "nick", and "email" properties. You can run `contact mutt-aliases > ~/.mutt/aliases` to update your aliases file. `contact vcf-export` will format the output in0 vCard syntax, which can then be imported into various other applications. It will read the "name", "email", "mobile", "work", "home", and "phone" properties. Create a .vcf file for importing with `contact vcf-export > ~/contacts.vcf` ' } help-short() { printf 'Syntax: contact [COMMAND] [NAME] [PROPERTY] Commands: No command: Interactive prompt add: Add new contact edit: Edit existing contact delete: Delete contact get: Get property of contact getfiltered: start interactive prompt, but only list properties matching $2 git: Interact with the git repository directly Example: `contact git status` To enable the git repository features of contact you need to run: `contact git init` If you already have contacts you need to manually commit them: `contact git add --all;contact git commit -m "Commit all existing contacts"` help: Show this message mutt-aliases: Export as mutt aliases. vcf-export: Export as .vcf file. rename-all: Rename all files to the correct name rebuild: Rebuild the cache\n' } error() { case $1 in 64) msg="No matching contact" ;; 65) msg="Please specify name (name=Max Muster)" ;; 66) msg="Contact already exists" ;; 67) msg="No contacts in storage. Add a contact first. Run \`contact help\` for more information" ;; esac echo >&2 "Error: $msg" exit $1 } rebuild() { check-files files=$(for f in *; do printf "%s: %s\n" "$f" "$(sed ':a;N;$!ba;s/\n/; /g' "$f")"; done) mkdir -p "$HOME"/.cache echo "$files" > "$HOME"/.cache/contact-cache } getcache() { if [ ! -f "$HOME/.cache/contact-cache" ]; then rebuild fi echo "$(< "$HOME"/.cache/contact-cache)" } getprop() { if [ ! -f "$1" ]; then error 64 fi properties="$(< "$1")" if [ -n "$3" ]; then properties="$(echo "$properties" | grep "$3")" fi if [ -n "$2" ]; then val=$(echo "$properties" | fzf -e -f "$2=") else val=$(echo "$properties" | fzf) fi val="$(echo "${val}" | sed 's/.*=//g')" echo "$val" } getall() { val="$(fzf -e -f "$2=" < "$1")" list="" while read -r line; do list+="${line#*=}\n" done <<< "$val" echo -e "$list" } getfile() { set -e files="$(getcache)" if [ -n "$1" ]; then ret=$(echo "$files" | fzf -f "$1") file=$(echo "$ret" | head -n1) else file=$(echo "$files" | fzf) fi file=$(echo "$file" | cut -d":" -f1) if [ ! -f "$file" ]; then error 64 fi echo "$file" } edit() { file="$1" if [ -n "$EDITOR" ]; then $EDITOR "$file" else vi "$file" fi filter "$file" rename "$file" } filter() { file="$1" sed '/^$/d' "$file" > "$file.new" mv "$file.new" "$file" } rename() { file="$1" filter "$file" name=$(getprop "$file" "name") if [ -z "$name" ]; then error 65 fi if [ "$cdir/$name" != "$file" ]; then if [ -f "$name" ]; then error 66 fi mv "$file" "$name" if [ -d ".git" ]; then git rm "$file" > /dev/null 2>&1 || : fi fi if [ -d ".git" ]; then git add "$name" || : git commit -m "Updated $name" > /dev/null 2>&1 || : fi rebuild } delete() { file="$1" rm "$file" if [ -d ".git" ]; then git rm "$file" > /dev/null || : git commit -m "Deleted $file" > /dev/null 2>&1 || : fi rebuild } check-files() { if [ -z "$(ls -A)" ]; then error 67 fi } mutt-aliases() { check-files for f in *; do name=$(getprop "$f" "name") mails=$(getprop "$f" "email") if [ -n "$mails" ]; then count=0 echo "$mails" | while read mail; do nick=$(getprop "$f" "nick") if [ -z "$nick" ]; then nick="$name" fi if [ "$count" -gt 0 ]; then nick="${nick} $count" fi # Replace space with underscore nick="${nick// /_}" # Lowercase the string nick="${nick,,}" count=$((count + 1)) printf "alias %s %s <%s>\n" "$nick" "$name" "$mail" done fi done } vcf-export() { check-files for f in *; do name=$(getprop "$f" "name") lastname=$(echo "$name" | rev | cut -d' ' -f1 | rev) firstname=$(echo "$name" | cut -d' ' -f1) if [ "$lastname" = "$firstname" ]; then lastname="" fi nick=$(getall "$f" "nick") mail=$(getall "$f" "email") cell=$(getall "$f" "mobile") work=$(getall "$f" "work") home=$(getall "$f" "home") phone=$(getall "$f" "phone") address=$(getprop "$f" "address" | head -n1) zip=$(getprop "$f" "zip" | head -n1) city=$(getprop "$f" "city" | head -n1) country=$(getprop "$f" "country" | head -n1) birthday=$(getprop "$f" "birthday" | sed -e 's/\.//g' -e 's/-//g') eol="\r\n" out="BEGIN:VCARD${eol}" out+="VERSION:3.0${eol}" out+="N:$lastname;$firstname;;;${eol}" out+="FN:$name${eol}" if [ -n "$birthday" ]; then out+="BDAY:$birthday${eol}" fi while read -r e; do if [ -n "$e" ]; then out+="TEL:$e${eol}" fi done <<< "$phone" while read -r e; do if [ -n "$e" ]; then out+="TEL;TYPE=CELL:$e${eol}" fi done <<< "$cell" while read -r e; do if [ -n "$e" ]; then out+="TEL;TYPE=WORK:$work${eol}" fi done <<< "$work" while read -r e; do if [ -n "$e" ]; then out+="TEL;TYPE=HOME:$e${eol}" fi done <<< "$home" if [ -n "$address" ] || [ -n "$city" ] || [ -n "$zip" ] || [ -n "$country" ]; then out+="ADR;HOME:;;${address};${city};;${zip};$country${eol}" fi while read -r e; do if [ -n "$e" ]; then out+="EMAIL;TYPE=INTERNET;TYPE=PREF;TYPE=HOME:$e${eol}" fi done <<< "$mail" while read -r e; do if [ -n "$e" ]; then out+="X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;$e;1;;;;;;;;;;;;;${eol}" fi done <<< "$nick" out+="END:VCARD${eol}" out+="${eol}" echo -ne $out done } template="name= email= mobile=" set -e cdir="$HOME"/.contacts mkdir -p "$cdir" cd "$cdir" command="$1" if [ "$command" = "add" ]; then tmp=$(mktemp) echo "$template" > "$tmp" edit "$tmp" elif [ "$command" = "edit" ]; then file=$(getfile "$2") edit "$cdir/$file" elif [ "$command" = "delete" ]; then file=$(getfile "$2") delete "$file" elif [ "$command" = "getfile" ]; then file=$(getfile "$2") echo "$file" elif [ "$command" = "rename-all" ]; then for f in *; do rename "$cdir/$f" done rebuild elif [ "$command" = "rebuild" ]; then rebuild elif [ "$command" = "vcf-export" ]; then vcf-export elif [ "$command" = "mutt-aliases" ]; then mutt-aliases elif [ "$command" = "git" ]; then "$@" elif [ "$command" = "help" ]; then help elif [ "$command" = "get" ]; then file=$(getfile "$2") property=$(getprop "$file" "$3") echo "$property" elif [ "$command" = "getfiltered" ]; then file=$(getfile) property=$(getprop "$file" "" "$2") echo "$property" elif [ "$command" = "" ]; then file=$(getfile) property=$(getprop "$file") echo "$property" else help-short >&2 printf >&2 '\nRun `contact help` for more information\n' exit 100 fi