#!/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