tidy: Tidies filenames and fixes issues such as permission issues with files in the specified directory

#!/usr/bin/env bash
help_text="
NAME
    tidy - Tidies filenames and fixes issues such as permission issues with files in the specified directory.

USAGE
    tidy [<folder>]

OPTIONS
    -n|--name
        Only tidy filenames - do not check permissions etc.

    -fn|-nf
        Force rename of filenames without prompting.  Implies -n.

    -h|--help
        Show help text.

DESCRIPTION
    Tidies filenames and fixes issues such as permission issues with files in the specified directory.

AUTHORS
    mjnurse.github.io - 2020
"
help_line="Tidies filenames and fixes issues such as permission issues with files in the specified directory"
web_desc_line="Tidies filenames and fixes issues such as permission issues with files in the specified directory"

force=n
names_only=n
while [[ "$1" != "" ]]; do
    case $1 in
        -h|--help)
            echo "$help_text"
            exit
            ;;
        -n|--name)
            names_only=y
            ;;
        -fn|-nf)
            force=y
            names_only=y
            ;;
        -*)
            echo "Unknown option: $1"
            echo "Try ${0##*/} -h for more information"
            exit 1
            ;;
        ?*)
            cd $1
            ;;
    esac 
    shift
done

cdef="\e[39m" # default colour

cbla="\e[30m"; cgra="\e[90m"; clgra="\e[37m"; cwhi="\e[97m"

cred="\e[31m"; cgre="\e[32m"; cyel="\e[33m" 
cblu="\e[34m"; cmag="\e[35m"; ccya="\e[36m";

clred="\e[91m"; clgre="\e[92m"; clyel="\e[93m"
clblu="\e[94m"; clmag="\e[95m"; clcya="\e[96m"


function cecho {
    color=c$1
    shift
    message="$*"
    echo -e "${!color}${message}${cdef}"
}


cecho gre ================================================================================
cecho gre "File Type Shouldn't be executable"
cecho gre ================================================================================

remove_all=n
for f in *; do
    if [[ -x "$f" ]]; then
        ft="$(file -b "$f")"
        case "${ft^^}" in
            DIRECTORY*) ft="Directory";;
            HTML*) ft="HTML";;
            JPEG*) ft="JPEG";;
            PNG*) ft="PNG";;
            GIF*) ft="GIF";;
            JSON*) ft="JSON";;
            M*EXCEL*) ft="Excel";;
            M*WORD*) ft="Word";;
            M*POWERPOINT*) ft="PowerPoint";;
            PEM*) ft="PEM";;
            PDF*) ft="PDF";;
            SVG*) ft="SVG";;
            ZIP*) ft="Zip";;
            # The following can be executable so skip them
            BOURNE*) ft="skip";;
            PYTHON*) ft="skip";;
            # Print other file types
            *) echo $f - $ft;ft="Other";;
        esac
        if [[ "$ft" != "Other" && "$ft" != "skip" && "$ft" != "Directory" ]]; then
                echo --------------------------------------------------------------------------------
                echo -e "FILE:      $f"
                echo -e "TYPE:      $ft"
                echo --------------------------------------------------------------------------------
            opt=""
            if [[ "$remove_all" == "y" ]]; then
                chmod -x "$f"
                continue
            fi
            while [[ ! "${opt^}" =~ ^[AIQRS]$ ]]; do
                [[ -n "$opt" ]] && echo "Invalid option: $opt"
                printf "${clblu}[i]gnore rule, [r]emove exec, remove exec from [a]ll, [s]kip, q[uit]. [aiqrS]?: ${cdef}"
                read opt
                [[ -z "$opt" ]] && opt="S"
            done
            case $opt in
                a|A)
                    remove_all=y
                    chmod -x "$f"
                    ;;
                r|R)
                    chmod -x "$f"
                    ;;
                i|I)
                    break;;
                q|Q)
                    exit;;
            esac
        fi
    fi
done


cecho gre ================================================================================
cecho gre "Filename contains character other than [a-z][0-9]_-."
cecho gre ================================================================================

rename_all=n
for f in *; do
    if [[ -f "$f" && ! -L "$f" ]]; then
        if [[ -x "$f" ]]; then
            link_char="-"
            exec_message="${cgre}(Executable)${cdef}"
        else
            link_char="_"
            exec_message=""
        fi
        newname=$(echo "$f" | sed "
            s/[^a-zA-Z0-9._-]/${link_char}/g;
            s/\([a-z]\)\([A-Z]\)/\1-\L\2/g;
            s/\(.*[a-z].*[A-Z].*\)\(\.[^\.]\+\)/\L\1\2/g;
            s/\(.*[A-Z].*[a-z].*\)\(\.[^\.]\+\)/\L\1\2/g;
            s/\([^\.]*[A-Z][^\.]*[a-z][^\.]*\)/\L\1/g;
            s/\([^\.]*[a-z][^\.]*[A-Z][^\.]*\)/\L\1/g;
            s/[-_][-_]*/${link_char}/g; 
        ")
        if [[ "$f" != "$newname" && "$f" != "README.md" ]]; then
            echo --------------------------------------------------------------------------------
            echo -e "FILE:      $f $exec_message"
            echo "Rename as: $newname"
            echo --------------------------------------------------------------------------------
            if [[ $force == y || $rename_all == y ]]; then
                opt="r"
            else
                opt=""
                while [[ ! "${opt^}" =~ ^[AIQRS]$ ]]; do
                    [[ -n "$opt" ]] && echo "Invalid option: $opt"
                    printf "${clblu}[i]gnore rule, [q]uit, [r]ename, rename [a]ll, [s]kip [aiqrS]?: ${cdef}"
                    read opt
                    [[ -z "$opt" ]] && opt="S"
                    if [[ "${opt^}" == "A" ]]; then
                        rename_all=y
                        opt="r"
                    fi
                done
            fi
            case $opt in
                r|R)
                    # rename via a temp tile name to avoid the Windows file system saying 
                    # a.txt is same as A.txt etc.
                    val=$RANDOM
                    mv "$f" "${newname}.tmp.${val}"
                    mv "${newname}.tmp.${val}" "$newname"
                    cecho gre "Renamed to: $newname"
                    ;;
                i|I)
                    break;;
                q|Q)
                    exit;;
            esac
        fi
    fi
done

if [[ $names_only == y ]]; then
    exit
fi

cecho gre ================================================================================
cecho gre Executable but not '#!/usr/bin/env ...'
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -executable); do

    if [[ -f $f && ! -L $f ]]; then
        line1="$(head -1 "$f")"
        if [[ "${line1:0:14}" != "#!/usr/bin/env" ]]; then
            echo --------------------------------------------------------------------------------
            echo FILE: $f
            echo --------------------------------------------------------------------------------
            echo First 5 lines:
            echo --------------
            head -5 "$f" | trunc
            echo
            printf "${clblu}[i]gnore rule, [r]emove exec, add [b]ash, add [p]ython3, [s]kip, q[uit]. [irbpSq]?: ${cdef}"
            read opt
            case $opt in
                r|R)
                    chmod a-x "$f"
                    ;;
                b|B)
                    sed -i '/^#!\/bin\/bash/d' "$f"
                    sed -i '1i#!/usr/bin/env bash' "$f"
                    ;;
                p|P)
                    sed -i '1i#!/usr/bin/env python3' "$f"
                    ;;
                i|I)
                    break
                    ;;
                q|Q)
                    exit
                    ;;
            esac
        fi 
    fi
done

cecho gre ================================================================================
cecho gre '#!/bin/bash not #!/usr/bin/env bash'
cecho gre ================================================================================

for f in $(find . -maxdepth 1 ! -executable); do
   if [[ -f $f && ! -L $f ]]; then
      line1="$(head -1 "$f" | tr -d '\000')"
      line1=${line1//[$' \t\r\n']}
      if [[ "$line1" == "#!/bin/bash" ]]; then
         echo --------------------------------------------------------------------------------
         echo '#!/bin/bash not #!/usr/bin/env bash'
         echo
         echo FILE: $f
         echo --------------------------------------------------------------------------------
         read -p "[i]gnore rule, [u]pdate to #!/usr/bin/env bash, [s]kip, q[uit]. [iuSq]?: " opt
         case $opt in
            u|U)
               sed -i '1s|#!/bin/bash|#!/usr/bin/env bash|' "$f";;
            i|I)
               break;;
            q|Q)
               exit;;
         esac
      fi 
   fi
done

cecho gre ================================================================================
cecho gre '#!/usr/bin/env bash' but not Executable
cecho gre ================================================================================

for f in $(find . -maxdepth 1 ! -executable); do
   if [[ -f $f && ! -L $f ]]; then
      line1="$(head -1 "$f" | tr -d '\000')"
      if [[ "$line1" == "#!/usr/bin/env bash" ]]; then
         echo --------------------------------------------------------------------------------
         echo FILE: $f
         echo --------------------------------------------------------------------------------
         echo First 5 lines:
            echo --------------
         head -5 "$f"
         echo
         read -p "[i]gnore rule, [d]elete '#!/usr/bin/env bash', [m]ake exec, [s]kip, q[uit]. [idmSq]?: " opt
         case $opt in
            d|D)
               sed -i '1d' "$f";;
            m|M)
               chmod u+x "$f";;
            i|I)
               break;;
            q|Q)
               exit;;
         esac
      fi 
   fi
done

cecho gre ================================================================================
cecho gre Writable by Other
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -perm -o=w); do
   if [[ -f $f && ! -L $f ]]; then
      echo --------------------------------------------------------------------------------
      ls -al $f
      echo
      read -p "[i]gnore rule, [r]remove writeable other, [s]kip, q[uit]. [irSq]?: " opt
      case $opt in
         r|R)
            chmod o-w $f;;
         i|I)
            break;;
         q|Q)
            exit;;
      esac
   fi
done

cecho gre ================================================================================
cecho gre Writable by Group
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -perm -g=w); do
   if [[ -f $f && ! -L $f ]]; then
      echo --------------------------------------------------------------------------------
      ls -al $f
      echo
      read -p "[i]gnore rule, [r]remove writeable group, [s]kip, q[uit]. [irSq]?: " opt
      case $opt in
         r|R)
            chmod g-w $f;;
         i|I)
            break;;
         q|Q)
            exit;;
      esac
   fi
done

cecho gre ================================================================================
cecho gre Executable by Other
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -perm -o=x); do
   if [[ -f $f && ! -L $f ]]; then
      echo --------------------------------------------------------------------------------
      ls -al $f
      echo
      read -p "[i]gnore rule, [r]remove Exec other, [s]kip, q[uit]. [irSq]?: " opt
      case $opt in
         r|R)
            chmod o-x $f;;
         i|I)
            break;;
         q|Q)
            exit;;
      esac
   fi
done

cecho gre ================================================================================
cecho gre Executable by Group
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -perm -g=x); do
   if [[ -f $f && ! -L $f ]]; then
      echo --------------------------------------------------------------------------------
      ls -al $f
      echo
      read -p "[i]gnore rule, [r]remove Exec group, [s]kip, q[uit]. [irSq]?: " opt
      case $opt in
         r|R)
            chmod g-x $f;;
         i|I)
            break;;
         q|Q)
            exit;;
      esac
   fi
done

cecho gre ================================================================================
cecho gre No header
cecho gre ================================================================================

for f in $(find . -maxdepth 1 -executable); do
   if [[ -f $f && ! -L $f ]]; then
      l1="$(cat "$f" | sed -n '1p')"
      l2="$(cat "$f" | sed -n '2p')"
      if [[ "$l1" == "#!/usr/bin/env bash" && "${l2:0:10}" != "help_text=" ]]; then
         cecho lcya --------------------------------------------------------------------------------
         cecho lcya No header:
         ls -al "$f"
         cecho lcya --------------------------------------------------------------------------------
         head -15 "$f"
         echo
         read -p "[i]gnore rule, [a]dd header, add [b]asic header, edit [v]im, edit [c]ode, [s]kip, q[uit]. [iabvcSq]?: " opt
         case ${opt^} in
            A)
               tidytmp="/tmp/tidy.tmp"
echo '#!/usr/bin/env bash
help_text="
NAME
    '${f:2}' - One line description.

USAGE
    '${f:2}' [options] <parameters>

OPTIONS
    -x
        Description...

    -h|--help
        Show help text.

DESCRIPTION
    Description description description description.

AUTHOR
    mjnurse.github.io - 2026
"
help_line="'tbc'"
web_desc_line="'tbc'"

try="Try ${0##*/} -h for more information"
tmp="${help_text##*USAGE}"
usage=$(echo "Usage: ${tmp%%OPTIONS*}" | tr -d "\n" | sed "s/  */ /g")

if [[ "$1" == "" ]]; then
    echo "${usage}"
    echo "${try}"
    exit 1
fi

case $1 in
    -h|--help)
        echo "$help_text"
        exit
        ;;
esac
' > $tidytmp
               tail +2 "$f" >> $tidytmp
               cp -f $tidytmp "$f"
               ;;
            B)
               tidytmp="/tmp/tidy.tmp"
echo '#!/usr/bin/env bash
help_text="
usage: <<FILENAME>> [options] <filename>
-h : This help text.
"

help_line="tbc"
web_desc_line="tbc"
' > $tidytmp
               tail +2 "$f" >> $tidytmp
               cp -f $tidytmp "$f"
               ;;            
            V)
               vgim $f
               ;;
            C)
               code $f
               ;;
            I)
               break
               ;;
            Q)
               exit
               ;;
         esac
      fi
   fi
done