#!/bin/sh
# lshar.sh - Luxferre's shell archiver
# Outputs to stdout only (for GNU and BSD shar compatibility)
# No external deps for .shars (sh commands required: mkdir, printf)
# Archive creation also depends on sort (Busybox, Toybox or GNU Coreutils)
# Works with any text or binary files and directory structures
# Created by Luxferre in 2023, released into public domain

# a portable, compact, no-deps serializer of any file into printf sequence
f2p() {
  c=0 # init counter
  l="$2" # get chunk length
  [ -z "$l" ] && l=4096 # 4096-byte chunks by default (shouldn't be a problem)
  cprefix="printf '%b' '" # pattern to prepend to every chunk
  csuffix="' >> '$1'\n" # pattern to append to every chunk
  printf '%b' "$cprefix" # always start with the prefix
  IFS='' # separator
  while read -r -s -d '' -n1 b; do # convert every byte
    if [ "$c" == "$l" ]; then # roll over and output suffix+prefix
      c=0 # reinit counter
      printf '%b' "$csuffix" # end the chunk
      printf '%b' "$cprefix" # begin a new chunk
    else
      c="$(( c + 1 ))" # increment c
    fi
    bcode="$(printf '%d' "'$b")" # get the ASCII code of the byte
    if [ "$bcode" -gt 32 ] && [ "$bcode" -lt 127 ] && 
       [ "$bcode" -ne 39 ] && [ "$bcode" -ne 92 ]
    then # printable and not a single quote or a backslash
      printf '%b' "$b" # codes from 34 to 126 are safe to print
    else # subject to escaping, print the hex representation
      printf '\\x%02x' "$bcode"
    fi
  done <"$1"
  printf '%b' "$csuffix" # always end with the suffix
}

# main code
LF=$'\n' # cache the linefeed character
flist='' # pre-build the file list from args
dlist='' # pre-build the directory list from args
for i; do # iterate over args
  bname="${i##*/}" # get the basename
  if [ -d "$i" ]; then # it's a directory, we skip . and ..
    [ "$bname" == '.' ] || [ "$bname" == '..' ] && continue 
    dlist="${dlist}${i%/}${LF}"          # append the directory
  else # it's a file
    dname="${i%%$bname}"               # get the basedir
    dlist="${dlist}${dname%/}${LF}"    # append the basedir 
    flist="${flist}${i}${LF}"          # append the file
  fi
done
flist="$(printf '%s' "$flist" | sort -u)" # remove file duplicates
dlist="$(printf '%s' "$dlist" | sort -u)" # remove directory duplicates
printf '%s\n' '#!/bin/sh' # output the shebang, just in case
IFS="$LF" # update the separator
for indir in $dlist; do # create the directory structure first
  [ -z "$indir" ] && continue                        # skip empty names
  printf '# dir: %s\n' "$indir"
  printf "printf 'c - %%s\\\n' '%s'\n" "$indir"      # announce creation
  printf "mkdir -p '%s' > /dev/null 2>&1\n" "$indir" # create the dir
done
for infile in $flist; do # iterate over the file list afterwards
  [ -z "$infile" ] && continue                         # skip empty names
  printf '# file: %s\n' "$infile"
  printf "printf 'x - %%s\\\n' '%s'\n" "$infile"       # announce unrolling
  printf "printf '' > '%s'\n" "$infile"                # clear the file
  f2p "$infile"                                        # run the serializer
  IFS="$LF"                                            # update the separator
done
