diff options
Diffstat (limited to 'ctct.in')
-rw-r--r-- | ctct.in | 404 |
1 files changed, 404 insertions, 0 deletions
@@ -0,0 +1,404 @@ +#!/usr/bin/env 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 <http://www.gnu.org/licenses/>. +# + +## 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 +input_program="@default_input_program@" +output_program="@default_output_program@" +visual_program="@default_visual_program@" +confirm_deletion=@default_confirm_deletion@ +confirm_default_yes=@default_confirm_default_yes@ + +## 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 <name> - show contact + $exec_name -l - list all entries + $exec_name [-s] <part> [...] - search by name + $exec_name -S <part> [...] - search by data + $exec_name -e <name> - edit / create entry + $exec_name -d <name> [...] - delete entries + $exec_name --rename <old> <new> - rename entry + $exec_name --version - show version information + $exec_name -h - print this help +EOF +} + +function main() +{ + if ! test -d "$datadir" && ! mkdir "$datadir" + then + print_error "$exec_name: $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 [[ "--version" =~ "$1" ]] && [ ${#1} -ge 3 ] + then + print_version; 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 + test $# -lt 2 && print_error "$exec_name: no entry specified." \ + && exit 1 + edit_file "$2"; exit $? + elif [ "$1" = "-d" ] || \ + ( [[ "--delete" =~ ^"$1" ]] && [ ${#1} -ge 3 ] ) + then + test $# -lt 2 && print_error "$exec_name: no entry to be deleted." \ + && exit 1 + shift; delete_file "$@"; exit $? + elif [[ "--rename" =~ ^"$1" ]] && [ ${#1} -ge 3 ] + then + ( test $# -lt 2 && print_error "$exec_name: no entry specified." ) || \ + ( test $# -lt 3 && print_error "$exec_name: no new name specified." ) \ + && exit 1 + rename_file "$2" "$3"; exit $? + elif [[ "$1" =~ ^- ]]; then + print_error "$exec_name: unknown option '$1'"; exit 1 + fi + + if ! find_exact "$1" + then + find_similar "Did you mean:" "$@" || print_msg "No match found." + fi +} + +function print_msg() +{ + printf "%s\n" "$@" +} + +function print_error() +{ + printf "%s\n" "$@" >&2 +} + +function find_exact() +{ + local file + file="$datadir/$(get_filename "$1")" || return $FALSE + + if test "$visual_program" = "cat" + then + "$output_program" < "$file" + else + eval "$output_program" < "$file" | "$visual_program" + fi + return $TRUE +} + +# $1: initial success message; $2-${$#}: search-patterns +function find_similar() +{ + local found=false msg="$1" name bool file pattern + 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 + # NEW + test -f "$file" || continue + + 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" valid file pattern + shift 1 # skip $1 + + for file in "$datadir"/* + do + # NEW + test -f "$file" || continue + + valid=true + if test "$output_program" = "cat" + then + for pattern in "$@" + do + ! grep -qi "$pattern" "$file" && valid=false && break + done + else + for pattern in "$@" + do + ! eval "$output_program" < "$file" | \ + grep -qi "$pattern" "$file" && valid=false && break + done + fi + if $valid + then + ! $found && print_msg "$msg" && found=true + print_msg " ${file##*/}" + fi + done + $found && return $TRUE || return $FALSE +} + +function list_all() +{ + # NEW + find "$datadir" -maxdepth 1 -type f | sed 's|.*/||' +} + +function edit_file() +{ + local file tmp_file editor new=false + + if ! file="$datadir/$(get_filename "$1")" + then + # NEW + if check_syntax "$1" && check_non_existance "$1" + then + file="$datadir/$1" + new=true + else + return $FALSE + fi + fi + + if test -n "$default_editor" + then + editor="$default_editor" + elif test -n "$EDITOR" && which "$EDITOR" >/dev/null 2>&1 + then + editor="$EDITOR" + else + editor="$fallback_editor" + fi + + if test "$input_program" = "cat" -a "$output_program" = "cat" + then + $new && touch "$file" # vim does not save an empty file + "$editor" "$file" + else + tmp_file="$(mktemp)" + # output_program may contain further arguments: + ! $new && eval "$output_program" < "$file" > "$tmp_file" + "$editor" "$tmp_file" + eval "$input_program" < "$tmp_file" > "$file" + fi +} + +function rename_file() +{ + check_syntax "$2" || return $FALSE + + local file + if file="$datadir/$(get_filename "$1")" + then + if get_filename "$2" > /dev/null + then + print_error "$exec_name: Entry \"$2\" does already exist." + return $FALSE + elif ! check_non_existance "$2" + then + return $FALSE + else + mv "$file" "$datadir/$2" + fi + else + print_error "$exec_name: Entry \"$1\" does not exist." + return $FALSE + fi +} + +function delete_file() +{ + local i files name file str + typeset -i i=0 + typeset -a files + + # check for existance of all to be deleted files + for name in "$@" + do + if file="$(get_filename "$name")" + then + files[i++]="$file" + else + print_error "$exec_name: Contact \"$1\" does not exist, aborting..." + return $FALSE + fi + done + + if "$confirm_deletion"; then + # prepare confirmation + if test $# -eq 1; then + str="Do you really want to delete the entry ${files[0]}" + else + print_msg "Entries to be deleted:" + for file in "${files[@]}"; do + print_msg " $file" + done + str="Are you sure to delete all these entries" + fi + + # confirm and delete + if confirm "$str"; then + for file in "${files[@]}"; do + rm "$datadir/$file" + done + fi + else # simply delete + for file in "${files[@]}"; do + rm "$datadir/$file" + done + fi +} + +function get_filename() +{ + local rev + + if test -f "$datadir/$1"; then + printf "%s\n" "$1" + elif [[ "$1" =~ \. ]] + then + rev=( ${1/\./ } ) + rev="${rev[1]}.${rev[0]}" + if test -f "$datadir/$rev"; then + printf "%s\n" "$rev" + else + return $FALSE + fi + else + return $FALSE + fi +} + +# $1: confirmation string +function confirm() +{ + local str="$1 " var + if $confirm_default_yes; then + str+="[Y/n]? " + else + str+="[y/N]? " + fi + printf "%s" "$str" + + read var + if test -z "$var"; then + $confirm_default_yes && return $TRUE || return $FALSE + fi + + var="${var,,}" + if test "$var" = "y" -o "$var" = "yes"; then + return $TRUE + else + return $FALSE + fi +} + +function check_syntax() +{ + if [[ "$1" =~ ^[A-Za-z]+([-_][A-Za-z]+)*(\.[A-Za-z]+([-_][A-Za-z]+)*)?$ ]] + then + return $TRUE + else + print_error "$exec_name: invalid name \"$1\"" \ + " An entry name may only contain letters," \ + " '-' and '_' as separators and exactly one dot ('.')" + return $FALSE + fi +} + +# NEW +# Only use this when $1 is assured not to be a regular file. +function check_non_existance() +{ + if test -e "$datadir/$1" + then + print_error "$exec_name: file \"$1\" exists in $datadir," \ + "however is not a regular file." + return $FALSE + else + return $TRUE + fi +} + +function print_version() { +cat << EOF +ctct - Version @PACKAGE_VERSION@ +Copyright 2015, 2016 Einhard Leichtfuß. +License AGPLv3+: GNU Affero General Public License, version 3 or later + - <http://gnu.org/licenses/agpl.html> +This is free software: You are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF +} + +main "$@" + +# vim: ts=2 sw=2 |