#!/bin/sh

# Copyright 2015-2021 Yury Gribov
# 
# Use of this source code is governed by MIT license that can be
# found in the LICENSE.txt file.

# Unpacks files from archives of various types.
# For details run without arguments.

set -eu

error() {
  echo >&2 "$(basename $0): error: $@"
  exit 1
}

warn() {
  echo >&2 "$(basename $0): warning: $@"
}

check_prog_silent() {
  which "$1" > /dev/null 2>&1
}

check_prog() {
  for p in "$@"; do
    if ! check_prog_silent "$p"; then
      details=
      # TODO: rpm
      if which apt-file 2>&1; then
        details=":
$(apt-file $p)"
      elif which yum 2>&1; then
        details=":
$(yum whatprovides $p)"
      fi
      error "your system is missing program '$p'" \
            "which is required to unpack this type of file$details"
    fi
  done
}

isemptydir() {
  test -d "$1" && test "$(find "$1" -maxdepth 0 -empty)"
}

strip_ext() {
  # Handle things like glibc-2.20.tar.bz2
  noext="${1%.*}"
  noext="${noext%.tar}"
  echo "$noext"
}

# Detect situations where it makes sense to unnest subfolder
# e.g. glibc-2.20/glibc-2.20
maybe_unnest() {
  d=$(ls -1 "$1")
  if [ $(echo "$d" | wc -l) -eq 1 -a "$d" = "$(basename "$1")" ]; then
    d=$(ls -1 "$1")
    mv "$d" "$d.$$"   # Workaround if some $f matches $d
    d="$d.$$"
    OIFS="$IFS"
    IFS='
'
    for f in $(ls -A "$1/$d"); do   # Respect hidden files
      mv "$1/$d/$f" "$1/$(basename $f)"
    done
    OIFS="$IFS"
    rmdir "$1/$d"
  fi
}

print_help_and_exit() {
  cat <<EOF
Syntax: $me FILE...
Unpacks archives of various types (based on extension of FILE).

Supported archives:
  .a
  .apk
  .cab
  .cpio
  .deb
  .jar
  .rar
  .rpm
  .tar*
  winmail.dat
  .zip
  .7z

Example:
  $ pk my.tar.gz 1.txt 2.txt
  $ upk my.tar.gz
  $ ls my/
  1.txt 2.txt
EOF
  exit 1
}

me=$(basename $0)

curdir="$PWD"

case "${1:-h}" in
-h | --help)
  print_help_and_exit
  ;;
esac

for src; do
  src=$(readlink -f "$src")
  src_no_ext=$(strip_ext "$(basename "$src")")
  src_basename=$(basename "$src")

  test -f $src || error "file $src does not exist"

  # Nest
  mkdir -p "$src_no_ext"
  isemptydir "$src_no_ext" || error "cowardly refusing to clear original contents of $src_no_ext"
  trap "cd '$curdir' && rm -rf '$src_no_ext'" EXIT INT TERM
  cd "$src_no_ext"

  # Unpack
  case "$src_basename" in
    *.a | *.ar)
      check_prog ar
      ar x "$src"
      ;;
    *.cab)
      check_prog cabextract
      cabextract "$src"
      ;;
    *.cpio)
      check_prog cpio
      cat "$src" | cpio -id
      ;;
    *.deb | *.ddeb)
      if check_prog_silent dpkg; then
        dpkg -x "$src" .
      elif check_prog_silent ar; then
        ar x "$src"
      else
        error "your system is missing programs 'dpkg' or 'ar'" \
          "which is required to unpack this type of file"
      fi
      ;;
#    *.gz)
#      gunzip "$src"
#      ;;
    *.rar)
      check_prog unrar
      unrar e "$src"
      ;;
    *.rpm)
      check_prog rpm2cpio cpio
      rpm2cpio "$src" | cpio -id
      ;;
    *.tar)
      check_prog tar
      tar xf "$src"
      ;;
    *.tar.gz | *.tgz)
      check_prog tar gunzip
      tar xzf "$src"
      ;;
    *.tar.bz2 | *.tbz2)
      check_prog tar bunzip2
      tar xjf "$src"
      ;;
    *.tar.lz | *.tar.lzip | *.tlz)
      check_prog tar lzip
      tar --lzip xf "$src"
      ;;
    *.tar.xz | *.txz)
      check_prog tar unxz
      tar xJf "$src"
      ;;
    winmail*.dat*)
      check_prog tnef
      tnef --file=$src
      ;;
    *.lzma | *.zip | *.jar | *.apk)
      if check_prog_silent 7z; then
        # 7z supports newer ZIP formats
        7z x "$src"
      else
        check_prog unzip
        unzip "$src"
      fi
      ;;
    *.lz | *.lzip | *.lzma)
      check_prog lzip
      lzip -d "$src"
      ;;
    *.7z)
      check_prog 7zr
      7zr x "$src"
      ;;
    *)
      error "unsupported archive: $src"
      ;;
  esac

  maybe_unnest "$PWD"

  cd "$curdir"
  trap '' EXIT INT TERM  # Unset
done

