#!/bin/bash # # ctct - a simple console contact manager # # Copyright 2015,2016 Einhard Leichtfuß # # ctct is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ctct is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ctct. If not, see . # ## DEFAULT SETTINGS: system_config_dir="@default_confdir@" user_config_dir="@default_user_config_dir@" datadir="@default_datadir@" fallback_editor="@default_fallback_editor@" default_editor= # none - use $EDITOR confirm_deletion=@default_confirm_deletion@ ## USER SETTINGS: test -f "$system_config_dir/config.sh" \ && source "$system_config_dir/config.sh" test -f "$user_config_dir/config.sh" \ && source "$user_config_dir/config.sh" ## CONSTANTS: exec_name="${0##*/}" TRUE=0 FALSE=1 function print_help() { cat << EOF usage: $exec_name [.] | [.] - show contact $exec_name [-s] - search by name $exec_name -S - search by data $exec_name -e . | . - edit entry $exec_name -e . - create new entry $exec_name -d . | . - delete entry $exec_name -l - list all entries $exec_name -h - print this help EOF } function main() { if ! test -d "$datadir" then ! mkdir "$datadir" \ && print_error "$datadir could not be created, quitting..." \ && exit 1 fi test $# -eq 0 && print_help && exit 1 if [ "$1" = "-h" ] || ( [[ "--help" =~ ^"$1" ]] && [ ${#1} -ge 3 ] ) then print_help; exit 0 elif [ "$1" = "-l" ] || \ ( [[ "--list-all" =~ ^"$1" ]] && [ ${#1} -ge 3 ] ) then list_all; exit $? elif [ "$1" = "-s" ] || \ ( [[ "--search-by-name" =~ ^"$1" ]] && [ ${#1} -ge 13 ] ) then shift; find_similar "Found:" "${@,,}"; exit $? elif [ "$1" = "-S" ] || \ ( [[ "--search-by-data" =~ ^"$1" ]] && [ ${#1} -ge 13 ] ) then shift; search_file "Found:" "${@,,}"; exit $? elif [ "$1" = "-e" ] || ( [[ "--edit" =~ ^"$1" ]] && [ ${#1} -ge 3 ] ); then edit_file "$2"; exit $? elif [ "$1" = "-d" ] || \ ( [[ "--delete" =~ ^"$1" ]] && [ ${#1} -ge 3 ] ) then test $# -lt 2 && print_error "$exec_name: no contact to be deleted" \ && exit 1 shift; delete_file "$@"; exit $? elif [[ "$1" =~ ^- ]]; then print_error "$exec_name: unknown option $1"; exit 1 fi if ! find_exact "${1,,}" then print_msg "No match found." find_similar "Did you mean:" "${@,,}" fi } function print_msg() { printf "%s\n" "$*" } function print_error() { printf "%s\n" "$*" >&2 } function find_exact() { local ret=$FALSE for file in "$datadir"/*.* do local name=${file##*/} if test "$name" = "$1" || \ test "$name" = "$(sed 's/^\(.*\)\.\(.*\)/\2.\1/' <<< "$1")" || \ [[ "$name" =~ \.$1$ ]] || [[ "$name" =~ ^$1\. ]] then print_msg "Found $name:" cat "$file" echo ret=$TRUE fi done return $ret } # $1: initial success message; $2-${$#}: search-pattern function find_similar() { local found=false msg="$1" name bool shift 1 # skip $1 # disallow '.' in any pattern # - any pattern should be either part of the first or the last name # else the reverse order (last.first) would have to be checked as well if [[ "$*" =~ \. ]] then return $FALSE fi for file in "$datadir"/*.* do name=${file##*/} bool=true for pattern in "$@" do if ! [[ "$name" =~ "$pattern" ]] then bool=false break fi done if $bool then ! $found && print_msg "$msg" && found=true print_msg " $name" fi done $found && return $TRUE || return $FALSE } function search_file() { local found=false msg="$1" pattern_string shift 1 # skip $1 IFS=$'\n' pattern_string="$*" IFS=$' \t\n' for file in "$datadir"/*.* do if grep -qF "$pattern_string" "$file" then ! $found && print_msg "$msg" && found=true print_msg " ${file##*/}" fi done $found && return $TRUE || return $FALSE } function list_all() { ls -1 "$datadir" } function edit_file() { local file if ! [[ "$1" =~ ^[a-z]+([-_][a-z]+)*\.[a-z]+([-_][a-z]+)* ]] then print_error "usage: $exec_name --edit ." return 1 fi if test -f "$datadir/$1" then file="$datadir/$1" else file="$datadir/$(sed 's/^\(.*\)\.\(.*\)/\2.\1/' <<< "$1")" if ! test -f "$file" then file="$datadir/$1" fi fi if test -n "$default_editor" then "$default_editor" "$file" elif test -n "$EDITOR" && which "$EDITOR" >/dev/null 2>&1 then "$EDITOR" "$file" else "$fallback_editor" "$file" fi } function delete_file() { local i=0 files rev str typeset -i i typeset -a files # check for existance of all to be deleted files for file in "$@" do if test -f "$datadir/$file"; then files[i]="$file" else rev="$(sed 's/^\(.*\)\.\(.*\)/\2.\1/' <<< "$file")" if test -f "$datadir/$rev"; then files[i]="$rev" else print_error "$exec_name: Contact \"$file\" does not exist, aborting..." return 1 fi fi i+=1 done if "$confirm_deletion"; then # prepare confirmation if test $# -eq 1; then str="Do you really want to delete ${files[0]}" else print_msg "Contacts to be deleted:" for file in "${files[@]}"; do print_msg " $file" done str="Are you sure to delete all these contacts" fi # confirm and delete if confirm "$str" n; then for file in "${files[@]}"; do rm "$datadir/$file" done fi else # simply delete for file in "${files[@]}"; do rm "$datadir/$file" done fi } # $1: confirmation string; $2: default action (y/n) function confirm() { local str="$1 " default_yes var if [ "${2,,}" = "y" ]; then str+="[Y/n]? " default_yes=true else str+="[y/N]? " default_yes=false fi printf "%s" "$str" read var if test -z "$var"; then $default_yes && return $TRUE || return $FALSE fi var="${var,,}" if test "$var" = "y" -o "$var" = "yes"; then return $TRUE else return $FALSE fi } main "$@" # vim: ts=2 sw=2