#!/bin/sh

# An enhanced and more portable ls -l

# Licence: LGPLv2
# Author:
#    http://www.pixelbeat.org/
# Notes:
#    A symlink target may lose info as there are no mode bits to highlight.
# Changes:
#    V0.1, 18 Sep 2009, Initial release
#    V0.2, 30 Sep 2009, Correctly strip "total" line for all locales
#                       Modify colors even when dircolors doesn't support $TERM
#                       Don't error for ls without --group-dir or --no-group
#    V0.3, 21 Oct 2009, Handle older ls' which pepper output with reset codes
#
#    V1.5, 20 Mar 2025
#      http://github.com/pixelb/scripts/commits/master/scripts/l

# apply thousands separator to file sizes
BLOCK_SIZE=\'1; export BLOCK_SIZE

# Get access to grep that supports POSIX -E option on Solaris.
# Note we don't just add /usr/xpg4/bin to PATH as that has
# an incompatible sed.
if echo . | /usr/xpg4/bin/grep -E -q . 2>/dev/null; then
  egrep='/usr/xpg4/bin/grep -E'
else
  egrep='grep -E'
fi

# Traditional unix time format with abbreviated month translated from locale.
# en_* locales default to ISO format without this for reasons discussed here:
# http://lists.gnu.org/archive/html/bug-coreutils/2009-09/msg00433.html
# Note as of coreutils 8.6, `ls` will again use traditional rather than ISO
# format, unless otherwise specified by translators.
TIME_STYLE='+%b %e  %Y
%b %e %H:%M'
export TIME_STYLE

# Add a fancy symlink arrow
if echo "$LANG" | grep -i "utf-*8$" >/dev/null; then
  SYM_ARROW="▪▶"
else
  SYM_ARROW="->"
fi

ESC=`printf '\033'`

# Disable color if the current $TERM doesn't support it
if ! tput setaf 1 >/dev/null 2>&1 && ! tput AF 1 >/dev/null 2>&1; then
  color_when=
# enable highlighting if outputting to terminal or forcing
elif test -t 1 \
  || echo "$*" | $egrep -- "--color( |=always|$)" >/dev/null; then
  # Note if the user specifies --color=auto then that will override this
  # and hence the output will not be colored :(
  color_when=always
  if tput bold >/dev/null 2>&1; then #terminfo
    BLD=`tput bold`
    RST=`tput sgr0`
    HLI=`tput smso`
  elif tput md >/dev/null 2>&1; then #termcap
    BLD=`tput md`
    RST=`tput me`
    HLI=`tput so`
  fi
  LS_HLI=`echo "$HLI" | sed "s/$ESC\[\(.*\)m/\1/"`
else
  color_when=auto
fi

ls=ls #default to standard name
{ ls --version >/dev/null 2>&1; } ||
{ gls --color -d . >/dev/null 2>&1 && ls=gls; } || NONGNU=1
dircolors=dircolors
[ "$ls" = 'gls' ] && dircolors=gdircolors

if [ "$color_when" ]; then
  if [ "$NONGNU" ]; then
    { colorls -d .; } >/dev/null 2>&1 && ls=colorls # for OpenBSD
    $ls -G -d . >/dev/null 2>&1 && color_opt=-G #not supported on Solaris
    [ "$color_when" = "always" ] && { CLICOLOR_FORCE=1; export CLICOLOR_FORCE; }
    color_when=

    # Turn off the confusing different background colors
    # as we'll highlight the mode bits instead. Also change the
    # main colors to match the default one on linux (coreutils).
    LSCOLORS="ExGxcxdxCxegedCxCxExEx"; export LSCOLORS
  else
    color_opt='--color='
    color_never="--color=never"
    # modify the coreutils default colors if distro or user colors not set
    [ "$LS_COLORS" ] || eval `$dircolors`
    # xterm-256color not supported by older dircolors for example
    [ "$LS_COLORS" ] || eval `TERM=xterm $dircolors`

    # Turn off the confusing entries with different backgrounds
    # as we'll highlight the mode bits and hardlink count instead.
    #
    # Also turn off capability colouring even though we don't
    # highlight anything in lieu of it, as this will short circuit
    # the slow capability checks within ls
    #
    # Also we reset the dangling symlink color to "highlight"
    # as it defaults to blinking.  We could do all this in ~/.dir_colors
    # but so this script is more general, we do it here.

    # Ensure LS_COLORS is delimited with ':' to simplify the following regexes.
    # Otherwise we'd have to consider using word boundaries (\b, \<, [[:<:]]),
    # but those are non portable.
    LS_COLORS=":$LS_COLORS:"; export LS_COLORS

    # Newer ls use "mh" to represent the color for multi hardlinked
    # files so handle the backwards incompatibility with "hl"
    if   $dircolors | grep ":mh=" >/dev/null; then
      hl_no_color="s/:hl=[^:]*:/:mh=:/; s/:mh=[^:]*:/:mh=:/"
    elif dircolors | grep ":hl=" >/dev/null; then
      hl_no_color="s/:mh=[^:]*:/:hl=:/; s/:hl=[^:]*:/:hl=:/"
    fi

    # The extra quoting (wrapping in "") is required for
    # speed in bash where it inefficiently tries to glob
    # for each *.ext component of LS_COLORS, which is
    # noticeably slow in directories with many files.
    eval "`
      echo LS_COLORS=\'\"$LS_COLORS\"\'\; |

      sed \"
        $hl_no_color
        s/:su=[^:]*:/:su=:/
        s/:sg=[^:]*:/:sg=:/
        s/:ow=[^:]*:/:ow=:/
        s/:st=[^:]*:/:st=:/
        s/:tw=[^:]*:/:tw=:/

        s/:ca=[^:]*:/:ca=:/

        s/:mi=[^:]\{1,\}:/:mi=$LS_HLI:/
      \"

      echo 'export LS_COLORS'
    `"
  fi
fi
# Get locale total string to match on
total=`$ls -sl $color_never / | sed 's/\([^ ]*\).*/\1/;q'`

# Use these options if available
$ls --group-directories-first -d . >/dev/null 2>&1 && gdf=--group-directories
$ls --no-group -d . >/dev/null 2>&1 && ng=--no-group
$ls --quoting=shell-escape -d . >/dev/null 2>&1 && quote=--quoting=shell-escape

# Bypass long format manipulation if any of following formats are specified
# FIXME: Matching options like this is a bit brittle
lfmt=lfmt
echo "$*" | $egrep -- "-(m|i|x|s|C|-version|-help)" >/dev/null && lfmt=""

# set -o pipefail is bash/ksh/zsh specific
# So manually propagate the exit status from ls.
# See http://stackoverflow.com/a/30658405/4421

exec 4>&1
ls_status=`{ {

# Start with the standard long format listing
# with colours, and latest files at bottom
$ls -lrtq $color_opt$color_when $ng $gdf $quote "$@";
printf $? 1>&3; } |

# process with sed to...
sed "
  # Remove total line(s) I never use
  /$total [0-9,. ]\{1,\}$/d

  # prettify symlink arrows
  s/ -> / $BLD$SYM_ARROW$RST /

  # Conditionally process long format output
  b $lfmt
  :lfmt

  # Ignore directory name headings
  /^[^ ]\{1,\}:$/b

  # temporarily shrink any prepended reset codes
  # from older ls' to a single char
  s/^$ESC\[0*m/0/

  # highlight ug+s indicators
  s/^\(0\{0,1\}.\{3\}\)\([sS]\)/\1$HLI\2$RST/
  s/^\(0\{0,1\}.\{6\}\)\([sS]\)/\1$HLI\2$RST/

  # highlight +t o+w indicators for directories
  /^0\{0,1\}d/!b not_dir
  s/^\(0\{0,1\}.\{9\}\)\([tT]\)/\1$HLI\2$RST/
  s/^\(0\{0,1\}.\{8\}\)w/\1${HLI}w$RST/
  s/^\(0\{0,1\}.\{3\}\)\([sS]\)/\1$HLI\2$RST/
  s/^\(0\{0,1\}.\{6\}\)\([sS]\)/\1$HLI\2$RST/
  :not_dir

  # highlight multiply linked files
  /^0\{0,1\}[^d]\{10,\}/!b not_hl
  /^[^ ]* *1 /b not_hl
  s/^\(0\{0,1\}[^ ]*\)\( *\)\([0-9]\{1,\}\)/\1\2$HLI\3$RST/
  :not_hl

  # restore any reset codes replaced above
  s/^0/${ESC}[0m/
" 1>&4; } 3>&1`

sed_status=$?

# Note if sed fails, then if ls outputs it will usually get
# a SIGPIPE and exit with status 141.  Though that's dependent
# on ls(1) propagating that "error", and also will not be the case
# if ls does not output after sed terminates.  So factor both
# exit values into the final exit status.
test $ls_status != 0 && exit $ls_status || exit $sed_status
