From f66d2ff96dd9f9874fbfa6b50733a9f9c8fbd8d9 Mon Sep 17 00:00:00 2001 From: Darren 'Tadgy' Austin Date: Tue, 12 May 2026 23:27:31 +0100 Subject: [PATCH] Major update to merge-tagfiles. --- merge-tagfiles | 296 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 206 insertions(+), 90 deletions(-) diff --git a/merge-tagfiles b/merge-tagfiles index bedb1bd..9eec3ec 100755 --- a/merge-tagfiles +++ b/merge-tagfiles @@ -1,6 +1,6 @@ #!/bin/bash -# Version: 0.0.1 -# Copyright (c) 2017: +# Version: 0.2.0 +# Copyright (c) 2017-2026: # Darren 'Tadgy' Austin # Licensed under the terms of the GNU General Public License version 3. # @@ -10,21 +10,36 @@ # 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 -#........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1.........2.........3.....:...4 - +# Functions. display_help() { # |--------1---------2---------3---------4---------5---------6---------7---------8 echo "Usage: ${0##*/} [options] " echo "Automatically or interactively merge tagfiles from and" - echo ", writing the resultant tagfile set to . Priorities " - echo "from take presidence over those in ." + echo ", writing the resultant tagfile set to ." + echo + echo "Caveats:" + echo " * Priorities from sets take presidence over ." + echo " * Only the packages listed in the sets are merged with the" + echo " priorities from the sets - packages in the " + echo " sets not in the set are removed in the sets." + echo " * Package descriptions are read from .txt files in either or" + echo " , whichever directory contains the file first." +# echo " * Only comments from sets are merged into sets." echo echo "Options:" - echo " -a, --default-add Do not interactively prompt whether to ADD or SKP any new" - echo " packages in ; automatically use this default." + 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 " -v, --version Display version and copyright information." + 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." @@ -33,14 +48,14 @@ display_help() { echo "Arguments (all of which are mandatory):" echo " The top-level directory of the first set of tagfiles." echo " The top-level directory of the second set of tagfiles." - echo " Path to the directory in which to write the new tagfiles." + echo " 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.0.1" - echo "Copyright (c) 2017 Darren 'Tadgy' Austin " + echo "${0##*/} v0.2.0" + echo "Copyright (c) 2017-2026 Darren 'Tadgy' Austin " echo "Licensed under the terms of the GNU GPL v3 ." 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" @@ -49,12 +64,12 @@ display_version() { } check_directory() { - # Params: $1 = Directory to check. - # Returns: 0 = OK. - # 1 = Doesn't exist. - # 2 = Not a directory. - # 3 = No read permission. - # 4 = No write permission. + # 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 @@ -72,40 +87,68 @@ check_directory() { } write_line() { - # Params: $1 = Package. - # $2 = Priority. - # $3 = Comment. - # $4 = File to write to. + # 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 + printf "%s:%s\\t\\t%s\\n" "$1" "$2" "$3" >>"$4" || exit 1 else - printf "%s:%s\n" "$1" "$2" >>$4 || exit 1 + printf "%s:%s\\n" "$1" "$2" >>"$4" || exit 1 fi } prompt_priority() { - # Params: $1 = Package. - # $2 = Priority. - # $3 = Comment. - # Variables: Modifies NEWPRI variable. + # 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: $1" >&1 - echo "Current priority: ${2:-(none)}" - echo "Comment: ${3:-(none)}" - 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)}" - local ANSWER while :; do - read -r -n 1 -p "Select new tag for package: (A)DD or (S)KP: " ANSWER + 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 @@ -114,32 +157,53 @@ prompt_priority() { process_tagfile() { - # Params: $1 = Input tagfile - # $2 = Output 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. - : >$2 + : >"$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" + exec {FD}<"$1/$SET/tagfile" # Process the tagfile line by line. - local PACKAGE PRIORITY COMMENT NEWPRI while IFS=$'\t :' read -r -u "$FD" PACKAGE PRIORITY COMMENT; do - if [[ "${PRIORITY^^}" = "ADD" || "${PRIORITY^^}" = "SKP" ]]; then - # ADD or SKP. - write_line "$PACKAGE" "${PRIORITY^^}" "$COMMENT" "$2" - else - # REC or OPT - if [[ ! -z "$DEFAULT_PRIORITY" ]]; then - # Use default priority. - write_line "$PACKAGE" "$DEFAULT_PRIORITY" "$COMMENT" "$2" + 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 for new priority. - prompt_priority "$PACKAGE" "${PRIORITY^^}" "$COMMENT" # Has modified the NEWPRI variable - write_line "$PACKAGE" "$NEWPRI" "$COMMENT" "$2" + 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 @@ -148,69 +212,119 @@ process_tagfile() { } merge_tagfiles() { - # Params: $1 = Tagfile 1 - # $2 = Tagfile 2 - # $3 = Output tagfile + # 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}<"$1" - - local PACKAGE1 PRIORITY1 COMMENT1 PACKAGE2_RHS NEWPRI - while IFS=$'\t :' read -r -u "$FD" PACKAGE1 PRIORITY1 COMMENT1; do - PRIORITY2="$(egrep "^${PACKAGE1//+/\\+}:" "$2" | cut -d: -f2- | sed -re 's/[[:blank:]]+.*$//')" - COMMENT2="$(egrep "^${PACKAGE1//+/\\+}:" "$2" | cut -d: -f2- | sed -re 's/(ADD|SKP|REC|OPT)//')" + 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 "$PACKAGE1" "$DEFAULT_PRIORITY" "" "$3" + write_line "$PACKAGE" "$DEFAULT_PRIORITY" "$COMMENT1" "$DEST_DIR/$SET/tagfile" else # Prompt for new priority. - prompt_priority "$PACKAGE1" "${PRIORITY1^^}" "$COMMENT1" # Has modified the NEWPRI variable - write_line "$PACKAGE1" "$NEWPRI" "$COMMENT1" "$3" + 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|SKP|REC|OPT) ]]; then + 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 "$PACKAGE1" "$DEFAULT_PRIORITY" "" "$3" + write_line "$PACKAGE" "$DEFAULT_PRIORITY" "${COMMENT2:-$COMMENT1}" "$DEST_DIR/$SET/tagfile" else # Prompt for new priority. - prompt_priority "$PACKAGE1" "${PRIORITY2^^} (invalid)" "$COMMENT2" # Has modified the NEWPRI variable - write_line "$PACKAGE1" "$NEWPRI" "$COMMENT2" "$3" + 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 "$PACKAGE1" "${PRIORITY2^^}" "$COMMENT2" "$3" + 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" = "SKP" ]]; then - echo "${0##*/}: options '$1' and '-s|--default-skp' are incompatible" >&2 + 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" ]]; then - echo "${0##*/}: options '$1' and '-a|--default-add' are incompatible" >&2 + if [[ "$DEFAULT_PRIORITY" =~ ^(ADD|REC|OPT)$ ]]; then + echo "${0##*/}: multiple --default-* options specified" >&2 exit 1 else DEFAULT_PRIORITY="SKP" fi shift ;; - '-v'|'-version'|'--version') + '-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 ;; @@ -242,12 +356,12 @@ done echo "Try: ${0##*/} --help" >&2 exit 1 } -for ARG in $*; do +for ARG in "$@"; do ERROR_TEXT="$(check_directory "$ARG" 2>&1)" ERROR_CODE=$? if [[ -z "$TAGFILES_DIR1" || -z "$TAGFILES_DIR2" ]]; then # and - if (( $ERROR_CODE >= 1 && $ERROR_CODE <= 3 )); then # Ignore write permission failure. + if (( ERROR_CODE >= 1 && ERROR_CODE <= 3 )); then # Ignore write permission failure. echo "$ERROR_TEXT" >&2 exit 1 else @@ -256,7 +370,7 @@ for ARG in $*; do fi else # - if (( $ERROR_CODE >= 2 )); then # Non-existant (ERROR_CODE=1) is OK here. + if (( ERROR_CODE >= 2 )); then # Non-existant (ERROR_CODE=1) is OK here. echo "$ERROR_TEXT" >&2 exit 1 else @@ -274,31 +388,33 @@ 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" ]]; then - # No tagfile in dir1. - if [[ -e "$TAGFILES_DIR2/$SET" ]]; then + 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/$SET/tagfile" "$DEST_DIR/$SET/tagfile" || exit 1 + process_tagfile "$TAGFILES_DIR2" || exit 1 else # No tagfile in either dir - create a stub file. touch "$DEST_DIR/$SET/tagfile" || exit 1 fi - else - # Found tagfile in dir1. - if [[ ! -e "$TAGFILES_DIR2/$SET" ]]; then - # Process tagfile in dir1 and write to dest dir. - process_tagfile "$TAGFILES_DIR1/$SET/tagfile" "$DEST_DIR/$SET/tagfile" || exit 1 - else - # Found tagfiles in both dirs - merge the tagfiles into dest dir. - merge_tagfiles "$TAGFILES_DIR1/$SET/tagfile" "$TAGFILES_DIR2/$SET/tagfile" "$DEST_DIR/$SET/tagfile" - fi fi done -echo +(( FLAG_MANUAL_INPUT == 1 )) && echo