420 lines
14 KiB
Bash
Executable file
420 lines
14 KiB
Bash
Executable file
#!/bin/bash
|
|
# Version: 0.2.0
|
|
# Copyright (c) 2017-2026:
|
|
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
|
# Licensed under the terms of the GNU General Public License version 3.
|
|
#
|
|
# Automatically or interractively merge tagfiles from two directories into a new set.
|
|
#
|
|
|
|
# The (space separated) list of package sets.
|
|
PACKAGE_SETS="${PACKAGE_SETS:-a ap d e f k kde kdei l n t tcl x xap xfce y}"
|
|
|
|
# nullglob is required.
|
|
shopt -s nullglob
|
|
|
|
# Functions.
|
|
display_help() {
|
|
# |--------1---------2---------3---------4---------5---------6---------7---------8
|
|
echo "Usage: ${0##*/} [options] <tagfiles dir1> <tagfiles dir2> <dest dir>"
|
|
echo "Automatically or interactively merge tagfiles from <tagfiles dir1> and"
|
|
echo "<tagfiles dir2>, writing the resultant tagfile set to <dest dir>."
|
|
echo
|
|
echo "Caveats:"
|
|
echo " * Priorities from <tagfiles dir2> sets take presidence over <tagfiles dir1>."
|
|
echo " * Only the packages listed in the <tagfiles dir1> sets are merged with the"
|
|
echo " priorities from the <tagfiles dir2> sets - packages in the <tagfiles dir2>"
|
|
echo " sets not in the <tagfiles dir1> set are removed in the <dest dir> sets."
|
|
echo " * Package descriptions are read from .txt files in either <tagfiles dir1> or"
|
|
echo " <tagfiles dir2>, whichever directory contains the file first."
|
|
# echo " * Only comments from <tagfiles dir2> sets are merged into <dest dir> sets."
|
|
echo
|
|
echo "Options:"
|
|
echo " -a, --default-add Do not interactively prompt whether to ADD, REC, SKP or OPT"
|
|
echo " any new packages - automatically use this default."
|
|
echo " -r, --default-rec See above."
|
|
echo " -s, --default-skp See above."
|
|
echo " -o, --default-opt See above."
|
|
echo " -n, --no-recopt Only allow the ADD or SKP priorities. In interactive mode"
|
|
echo " the replacement priority is prompted for, otherwise the"
|
|
echo " options --default-add or --default-skp determine priority."
|
|
echo " -N, --no-desc Do not show the package description in interactive mode."
|
|
echo " -V, --version Display version and copyright information."
|
|
echo " -h, --help Display this help, obviously."
|
|
echo " -- Force the end of option processing. Any options following"
|
|
echo " this are treated as arguments."
|
|
echo "Option processing ceases with the first non-option or --."
|
|
echo
|
|
echo "Arguments (all of which are mandatory):"
|
|
echo " <tagfiles dir1> The top-level directory of the first set of tagfiles."
|
|
echo " <tagfiles dir2> The top-level directory of the second set of tagfiles."
|
|
echo " <dest dir> Path to the directory in which to write the new tagfiles."
|
|
return 0
|
|
}
|
|
|
|
display_version() {
|
|
# |--------1---------2---------3---------4---------5---------6---------7---------8
|
|
echo "${0##*/} v0.2.0"
|
|
echo "Copyright (c) 2017-2026 Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>"
|
|
echo "Licensed under the terms of the GNU GPL v3 <http://gnu.org/licenses/gpl.html>."
|
|
echo "This program is free software: you can modify or redistribute it in accordence"
|
|
echo "with the GNU GPL. However, it comes with ABSOLUTELY NO WARRANTY; not even the"
|
|
echo "implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
|
|
return 0
|
|
}
|
|
|
|
check_directory() {
|
|
# Arguments: $1 = Directory to check.
|
|
# Returns: 0 = OK.
|
|
# 1 = Doesn't exist.
|
|
# 2 = Not a directory.
|
|
# 3 = No read permission.
|
|
# 4 = No write permission.
|
|
if [[ ! -e "$1" ]]; then
|
|
echo "${0##*/}: no such directory: $1" >&2
|
|
return 1
|
|
elif [[ ! -d "$1" ]]; then
|
|
echo "${0##*/}: not a directory: $1" >&2
|
|
return 2
|
|
elif [[ ! -r "$1" ]]; then
|
|
echo "${0##*/}: read permission denied: $1" >&2
|
|
return 3
|
|
elif [[ ! -w "$1" ]]; then
|
|
echo "${0##*/}: write permission denied: $1" >&2
|
|
return 4
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
write_line() {
|
|
# Arguments: $1 = The package name.
|
|
# $2 = The packages priority.
|
|
# $3 = The packages comment.
|
|
# $4 = File to write to.
|
|
|
|
if [[ ! -z "$3" ]]; then
|
|
printf "%s:%s\\t\\t%s\\n" "$1" "$2" "$3" >>"$4" || exit 1
|
|
else
|
|
printf "%s:%s\\n" "$1" "$2" >>"$4" || exit 1
|
|
fi
|
|
}
|
|
|
|
prompt_priority() {
|
|
# Arguments: $1 = Package name being processed.
|
|
# $2 = Previous priority of package being processed.
|
|
# $3 = Any comment from the tagfile for the package.
|
|
# Globsl variables: SET = The tagfile set being processed.
|
|
# TAGFILES_DIR1 = The first tagfiles directory given on the command line.
|
|
# TAGFILES_DIR2 = The second tagfiles directory given on the command line.
|
|
# Returns: The new priority for the package.
|
|
|
|
local ANSWER DESC
|
|
local -a DESCFILE
|
|
|
|
FLAG_MANUAL_INPUT=1
|
|
|
|
echo
|
|
echo
|
|
echo "Package: $SET/$1:"
|
|
[[ ! -v FLAG_NO_DESC ]] && {
|
|
DESCFILE=( "$TAGFILES_DIR1/$SET/$1"-*-*-*.txt "$TAGFILES_DIR2/$SET/$1"-*-*-*.txt )
|
|
DESC="$(grep "^$1:" "${DESCFILE[0]}" 2>/dev/null | sed -re 's/^.*:[[:blank:]]*/ /g; s/^[[:blank:]]*$/ ./g')"
|
|
if [[ -n "$DESC" ]]; then
|
|
echo " Description from ${DESCFILE[0]}:"
|
|
echo "$DESC"
|
|
else
|
|
echo " Description not found."
|
|
fi
|
|
}
|
|
echo " Current priority: ${2:-(none)}"
|
|
echo " Comment: ${3:-(none)}"
|
|
|
|
while :; do
|
|
echo
|
|
if [[ -v FLAG_NO_RECOPT ]]; then
|
|
read -r -n 1 -p " Select new priority for package: (A)DD, or (S)KP: " ANSWER
|
|
else
|
|
read -r -n 1 -p " Select new priority for package: (A)DD, (R)EC, (S)KP, or (O)PT: " ANSWER
|
|
fi
|
|
|
|
if [[ "${ANSWER^^}" = "A" ]]; then
|
|
NEWPRI="ADD"
|
|
break
|
|
elif [[ ! -v FLAG_NO_RECOPT ]] && [[ "${ANSWER^^}" = "R" ]]; then
|
|
NEWPRI="REC"
|
|
break
|
|
elif [[ "${ANSWER^^}" = "S" ]]; then
|
|
NEWPRI="SKP"
|
|
break
|
|
elif [[ ! -v FLAG_NO_RECOPT ]] && [[ "${ANSWER^^}" = "O" ]]; then
|
|
NEWPRI="OPT"
|
|
break
|
|
else
|
|
continue
|
|
fi
|
|
done
|
|
}
|
|
|
|
|
|
process_tagfile() {
|
|
# Arguments: $1 = The tagfiles path to process.
|
|
#
|
|
# Globsl variables: DEST_DIR = The destination directory to write the new tagfiles.
|
|
# SET = The tagfile set being processed.
|
|
|
|
local COMMENT NEWPRI PACKAGE PRIORITY
|
|
|
|
# Clear the destination tagfile.
|
|
: >"$DEST_DIR/$SET/tagfile"
|
|
|
|
# Open the input tagfile for reading.
|
|
# Note: We do this with an fd so we can use another 'read' of stdin for prompting.
|
|
exec {FD}<"$1/$SET/tagfile"
|
|
|
|
# Process the tagfile line by line.
|
|
while IFS=$'\t :' read -r -u "$FD" PACKAGE PRIORITY COMMENT; do
|
|
if [[ -v FLAG_NO_RECOPT ]] && [[ ! "${PRIORITY^^}" =~ ^(ADD|SKP)$ ]]; then
|
|
# Can't use REC or OPT - Get for new priority.
|
|
if [[ -v DEFAULT_PRIORITY ]]; then
|
|
NEWPRI="$DEFAULT_PRIORITY"
|
|
else
|
|
prompt_priority "$PACKAGE" "${PRIORITY^^}" "$COMMENT" # Sets the NEWPRI variable
|
|
fi
|
|
[[ -z "$NEWPRI" ]] && {
|
|
echo "Abort: failed to get new priority" >&2
|
|
exit 1
|
|
}
|
|
write_line "$PACKAGE" "$NEWPRI" "$COMMENT" "$DEST_DIR/$SET/tagfile"
|
|
elif [[ "${PRIORITY^^}" =~ ^(ADD|SKP)$ ]]; then
|
|
# ADD or SKP in tagfile - write it out.
|
|
write_line "$PACKAGE" "${PRIORITY^^}" "$COMMENT" "$DEST_DIR/$SET/tagfile"
|
|
else
|
|
echo "** Invalid priority ($PRIORITY) for $PACKAGE in tagfile:"
|
|
echo "** $1/$2/tagfile"
|
|
# Invalid priority in tagfile - try to fix.
|
|
if [[ -v DEFAULT_PRIORITY ]]; then
|
|
echo "** Correcting using default priority: $DEFAULT_PRIORITY"
|
|
NEWPRI="$DEFAULT_PRIORITY"
|
|
else
|
|
echo "** Prompting for new valid priority..."
|
|
prompt_priority "$PACKAGE" "${PRIORITY^^}" "$COMMENT" # Sets the NEWPRI variabl
|
|
fi
|
|
[[ -z "$NEWPRI" ]] && {
|
|
echo "Abort: failed to get new priority" >&2
|
|
exit 1
|
|
}
|
|
write_line "$PACKAGE" "$NEWPRI" "$COMMENT" "$DEST_DIR/$SET/tagfile"
|
|
fi
|
|
done
|
|
|
|
# Close tagfile file descriptor.
|
|
exec {FD}<&-
|
|
}
|
|
|
|
merge_tagfiles() {
|
|
# Globsl variables: DEST_DIR = The destination directory to write the new tagfiles.
|
|
# SET = The tagfile set being processed.
|
|
# TAGFILES_DIR1 = The first tagfiles directory given on the command line.
|
|
# TAGFILES_DIR2 = The second tagfiles directory given on the command line.
|
|
|
|
local COMMENT1 COMMENT2 PACKAGE NEWPRI PRIORITY1 PRIORITY2
|
|
|
|
# Open the first tagfile for reading.
|
|
# Note: We do this with an fd so we can use another 'read' of stdin for prompting.
|
|
exec {FD}<"$TAGFILES_DIR1/$SET/tagfile"
|
|
|
|
while IFS=$'\t :' read -r -u "$FD" PACKAGE PRIORITY1 COMMENT1; do
|
|
PRIORITY2="$(grep -E "^${PACKAGE//+/\\+}:" "$TAGFILES_DIR2/$SET/tagfile" | sed -re "/^${PACKAGE//+/\\\+}:/ s/[^[:blank:]:]+:[[:blank:]]*([^[:blank:]]+)(\$|[[:blank:]].*\$)/\\1/")"
|
|
COMMENT2="$(grep -E "^${PACKAGE//+/\\+}:" "$TAGFILES_DIR2/$SET/tagfile" | sed -re "/^${PACKAGE//+/\\\+}:/ s/[^[:blank:]:]+:[[:blank:]]*([^[:blank:]]+)(\$|[[:blank:]](.*)\$)/\\3/")"
|
|
if [[ -z "$PRIORITY2" ]]; then
|
|
# Package wasn't in tagfile2.
|
|
if [[ ! -z "$DEFAULT_PRIORITY" ]]; then
|
|
# Use default priority.
|
|
write_line "$PACKAGE" "$DEFAULT_PRIORITY" "$COMMENT1" "$DEST_DIR/$SET/tagfile"
|
|
else
|
|
# Prompt for new priority.
|
|
prompt_priority "$PACKAGE" "$PRIORITY1" "$COMMENT1" # Sets the NEWPRI variabl
|
|
[[ -z "$NEWPRI" ]] && {
|
|
echo "Abort: failed to get new priority" >&2
|
|
exit 1
|
|
}
|
|
write_line "$PACKAGE" "$NEWPRI" "$COMMENT1" "$DEST_DIR/$SET/tagfile"
|
|
fi
|
|
elif [[ ! "${PRIORITY2^^}" =~ ^(ADD|REC|SKP|OPT) ]]; then
|
|
# Package was listed in tagfile2, but priority is invalid.
|
|
if [[ ! -z "$DEFAULT_PRIORITY" ]]; then
|
|
# Use default priority.
|
|
write_line "$PACKAGE" "$DEFAULT_PRIORITY" "${COMMENT2:-$COMMENT1}" "$DEST_DIR/$SET/tagfile"
|
|
else
|
|
# Prompt for new priority.
|
|
prompt_priority "$PACKAGE" "$PRIORITY2" "${COMMENT2:-$COMMENT1}" # Sets the NEWPRI variabl
|
|
[[ -z "$NEWPRI" ]] && {
|
|
echo "Abort: failed to get new priority" >&2
|
|
exit 1
|
|
}
|
|
write_line "$PACKAGE" "$NEWPRI" "${COMMENT2:-$COMMENT1}" "$DEST_DIR/$SET/tagfile"
|
|
fi
|
|
else
|
|
# Package was listed in tagfile2 and priority was valid.
|
|
write_line "$PACKAGE" "${PRIORITY2^^}" "${COMMENT2:-$COMMENT1}" "$DEST_DIR/$SET/tagfile"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Process command line options.
|
|
while :; do
|
|
case "$1" in
|
|
'-a'|'-default-add'|'--default-add')
|
|
if [[ "$DEFAULT_PRIORITY" =~ ^(REC|SKP|OPT)$ ]]; then
|
|
echo "${0##*/}: multiple --default-* options specified" >&2
|
|
exit 1
|
|
else
|
|
DEFAULT_PRIORITY="ADD"
|
|
fi
|
|
shift
|
|
;;
|
|
'-r'|'-default-rec'|'--default-rec')
|
|
[[ -v FLAG_NO_RECOPT ]] && {
|
|
echo "${0##*/}: cannot use --no-recopt with --default-rec" >&2
|
|
exit 1
|
|
}
|
|
if [[ "$DEFAULT_PRIORITY" =~ ^(ADD|SKP|OPT)$ ]]; then
|
|
echo "${0##*/}: multiple --default-* options specified" >&2
|
|
exit 1
|
|
else
|
|
DEFAULT_PRIORITY="REC"
|
|
fi
|
|
shift
|
|
;;
|
|
'-s'|'-default-skp'|'--default-skp')
|
|
if [[ "$DEFAULT_PRIORITY" =~ ^(ADD|REC|OPT)$ ]]; then
|
|
echo "${0##*/}: multiple --default-* options specified" >&2
|
|
exit 1
|
|
else
|
|
DEFAULT_PRIORITY="SKP"
|
|
fi
|
|
shift
|
|
;;
|
|
'-o'|'-default-opt'|'--default-opt')
|
|
[[ -v FLAG_NO_RECOPT ]] && {
|
|
echo "${0##*/}: cannot use --no-recopt with --default-opt" >&2
|
|
exit 1
|
|
}
|
|
if [[ "$DEFAULT_PRIORITY" =~ ^(ADD|REC|SKP)$ ]]; then
|
|
echo "${0##*/}: multiple --default-* options specified" >&2
|
|
exit 1
|
|
else
|
|
DEFAULT_PRIORITY="OPT"
|
|
fi
|
|
shift
|
|
;;
|
|
'-n'|'-no-recopt'|'--no-recopt')
|
|
if [[ "$DEFAULT_PRIORITY" = "REC" ]]; then
|
|
echo "${0##*/}: cannot use --no-recopt with --default-rec" >&2
|
|
exit 1
|
|
elif [[ "$DEFAULT_PRIORITY" = "OPT" ]]; then
|
|
echo "${0##*/}: cannot use --no-recopt with --default-opt" >&2
|
|
exit 1
|
|
else
|
|
FLAG_NO_RECOPT=1
|
|
fi
|
|
shift
|
|
;;
|
|
'-N'|'-no-desc'|'--no-desc')
|
|
FLAG_NO_DESC=1
|
|
shift
|
|
;;
|
|
'-V'|'-version'|'--version')
|
|
display_version
|
|
exit 0
|
|
;;
|
|
'-h'|'-help'|'--help')
|
|
display_help
|
|
exit 0
|
|
;;
|
|
'--')
|
|
shift
|
|
break
|
|
;;
|
|
-*)
|
|
# This prevents the first non-option argument being an entry in the
|
|
# current directory which begins with a '-'. Use the '--' option
|
|
# or ./-foo where this becomes a problem.
|
|
echo "${0##*/}: invalid option: $1" >&2
|
|
echo "Try: ${0##*/} --help" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Process command line arguments.
|
|
(( $# != 3 )) && {
|
|
echo "${0##*/}: incorrect number of non-option arguments" >&2
|
|
echo "Try: ${0##*/} --help" >&2
|
|
exit 1
|
|
}
|
|
for ARG in "$@"; do
|
|
ERROR_TEXT="$(check_directory "$ARG" 2>&1)"
|
|
ERROR_CODE=$?
|
|
if [[ -z "$TAGFILES_DIR1" || -z "$TAGFILES_DIR2" ]]; then
|
|
# <tagfiles dir1> and <tagfiles dir2>
|
|
if (( ERROR_CODE >= 1 && ERROR_CODE <= 3 )); then # Ignore write permission failure.
|
|
echo "$ERROR_TEXT" >&2
|
|
exit 1
|
|
else
|
|
TAGFILES_DIR2="${TAGFILES_DIR1:+$ARG}"
|
|
TAGFILES_DIR1="${TAGFILES_DIR1:-$ARG}" # Yes, these are correct and in the right order!
|
|
fi
|
|
else
|
|
# <dest dir>
|
|
if (( ERROR_CODE >= 2 )); then # Non-existant (ERROR_CODE=1) is OK here.
|
|
echo "$ERROR_TEXT" >&2
|
|
exit 1
|
|
else
|
|
DEST_DIR="$ARG"
|
|
fi
|
|
fi
|
|
done
|
|
[[ -z "$TAGFILES_DIR1" || -z "$TAGFILES_DIR2" || -z "$DEST_DIR" ]] && {
|
|
echo "Abort: argument processing failure" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Create destination directory.
|
|
if [[ -e "$DEST_DIR" ]]; then
|
|
echo "${0##*/}: directory exists: $DEST_DIR" >&2
|
|
exit 1
|
|
else
|
|
# shellcheck disable=SC2174
|
|
mkdir -p -m 755 "$DEST_DIR" || exit 1
|
|
fi
|
|
|
|
# Main loop.
|
|
FLAG_MANUAL_INPUT=0
|
|
for SET in $PACKAGE_SETS; do
|
|
mkdir "$DEST_DIR/$SET" || exit 1
|
|
if [[ -e "$TAGFILES_DIR1/$SET/tagfile" ]]; then
|
|
# Found tagfile in dir1.
|
|
if [[ -e "$TAGFILES_DIR2/$SET/tagfile" ]]; then
|
|
# Found tagfiles in both dirs - merge the tagfiles into dest dir.
|
|
merge_tagfiles
|
|
else
|
|
# Process tagfile in dir1 and write to dest dir.
|
|
process_tagfile "$TAGFILES_DIR1" || exit 1
|
|
fi
|
|
else
|
|
# No tagfile in dir1 - check dir2.
|
|
if [[ -e "$TAGFILES_DIR2/$SET/tagfile" ]]; then
|
|
# Found tagfile in dir2 - process it and write to dest dir.
|
|
process_tagfile "$TAGFILES_DIR2" || exit 1
|
|
else
|
|
# No tagfile in either dir - create a stub file.
|
|
touch "$DEST_DIR/$SET/tagfile" || exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
(( FLAG_MANUAL_INPUT == 1 )) && echo
|