aboutsummaryrefslogtreecommitdiff
path: root/ctct.in
diff options
context:
space:
mode:
Diffstat (limited to 'ctct.in')
-rw-r--r--ctct.in404
1 files changed, 404 insertions, 0 deletions
diff --git a/ctct.in b/ctct.in
new file mode 100644
index 0000000..cf68a76
--- /dev/null
+++ b/ctct.in
@@ -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