func ByHeader(input io.ReadSeeker) (Unarchiver, error) {
	var matcher Matcher
	for _, m := range matchers {
		ok, err := m.Match(input)
		if err != nil {
			return nil, fmt.Errorf("matching on format %s: %v", m, err)
		}
		if ok {
			matcher = m
			break
		}
	}
	switch matcher.(type) {
	case *Zip:
		return NewZip(), nil
	case *Tar:
		return NewTar(), nil
	case *Rar:
		return NewRar(), nil
	}
	return nil, ErrFormatNotRecognized
}
ByHeader returns the unarchiver value that matches the input's
file header. It does not affect the current read position.
If the file's header is not a recognized archive format, then
ErrFormatNotRecognized will be returned.
package archiver
import (
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
)
type Archiver interface {
	ExtensionChecker
	Archive(sources []string, destination string) error
}
type ExtensionChecker interface {
	CheckExt(name string) error
}
type Unarchiver interface {
	Unarchive(source, destination string) error
}
type Writer interface {
	Create(out io.Writer) error
	Write(f File) error
	Close() error
}
type Reader interface {
	Open(in io.Reader, size int64) error
	Read() (File, error)
	Close() error
}
type Extractor interface {
	Extract(source, target, destination string) error
}
type File struct {
	os.FileInfo
	Header interface{}
	io.ReadCloser
}
type FileInfo struct {
	os.FileInfo
	CustomName string
}
func (fi FileInfo) Name() string {
	if fi.CustomName != "" {
		return fi.CustomName
	}
	return fi.FileInfo.Name()
}
type ReadFakeCloser struct {
	io.Reader
}
func (rfc ReadFakeCloser) Close() error { return nil }
type Walker interface {
	Walk(archive string, walkFn WalkFunc) error
}
type WalkFunc func(f File) error
var ErrStopWalk = fmt.Errorf("walk stopped")
var ErrFormatNotRecognized = fmt.Errorf("format not recognized")
type Compressor interface {
	ExtensionChecker
	Compress(in io.Reader, out io.Writer) error
}
type Decompressor interface {
	Decompress(in io.Reader, out io.Writer) error
}
type Matcher interface {
	Match(io.ReadSeeker) (bool, error)
}
func Archive(sources []string, destination string) error {
	aIface, err := ByExtension(destination)
	if err != nil {
		return err
	}
	a, ok := aIface.(Archiver)
	if !ok {
		return fmt.Errorf("format specified by destination filename is not an archive format: %s (%T)", destination, aIface)
	}
	return a.Archive(sources, destination)
}
func Unarchive(source, destination string) error {
	uaIface, err := ByExtension(source)
	if err != nil {
		return err
	}
	u, ok := uaIface.(Unarchiver)
	if !ok {
		return fmt.Errorf("format specified by source filename is not an archive format: %s (%T)", source, uaIface)
	}
	return u.Unarchive(source, destination)
}
func Walk(archive string, walkFn WalkFunc) error {
	wIface, err := ByExtension(archive)
	if err != nil {
		return err
	}
	w, ok := wIface.(Walker)
	if !ok {
		return fmt.Errorf("format specified by archive filename is not a walker format: %s (%T)", archive, wIface)
	}
	return w.Walk(archive, walkFn)
}
func Extract(source, target, destination string) error {
	eIface, err := ByExtension(source)
	if err != nil {
		return err
	}
	e, ok := eIface.(Extractor)
	if !ok {
		return fmt.Errorf("format specified by source filename is not an extractor format: %s (%T)", source, eIface)
	}
	return e.Extract(source, target, destination)
}
func CompressFile(source, destination string) error {
	cIface, err := ByExtension(destination)
	if err != nil {
		return err
	}
	c, ok := cIface.(Compressor)
	if !ok {
		return fmt.Errorf("format specified by destination filename is not a recognized compression algorithm: %s", destination)
	}
	return FileCompressor{Compressor: c}.CompressFile(source, destination)
}
func DecompressFile(source, destination string) error {
	cIface, err := ByExtension(source)
	if err != nil {
		return err
	}
	c, ok := cIface.(Decompressor)
	if !ok {
		return fmt.Errorf("format specified by source filename is not a recognized compression algorithm: %s", source)
	}
	return FileCompressor{Decompressor: c}.DecompressFile(source, destination)
}
func fileExists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}
func mkdir(dirPath string, dirMode os.FileMode) error {
	err := os.MkdirAll(dirPath, dirMode)
	if err != nil {
		return fmt.Errorf("%s: making directory: %v", dirPath, err)
	}
	return nil
}
func writeNewFile(fpath string, in io.Reader, fm os.FileMode) error {
	err := os.MkdirAll(filepath.Dir(fpath), 0755)
	if err != nil {
		return fmt.Errorf("%s: making directory for file: %v", fpath, err)
	}
	out, err := os.Create(fpath)
	if err != nil {
		return fmt.Errorf("%s: creating new file: %v", fpath, err)
	}
	defer out.Close()
	err = out.Chmod(fm)
	if err != nil && runtime.GOOS != "windows" {
		return fmt.Errorf("%s: changing file mode: %v", fpath, err)
	}
	_, err = io.Copy(out, in)
	if err != nil {
		return fmt.Errorf("%s: writing file: %v", fpath, err)
	}
	return nil
}
func writeNewSymbolicLink(fpath string, target string) error {
	err := os.MkdirAll(filepath.Dir(fpath), 0755)
	if err != nil {
		return fmt.Errorf("%s: making directory for file: %v", fpath, err)
	}
	_, err = os.Lstat(fpath)
	if err == nil {
		err = os.Remove(fpath)
		if err != nil {
			return fmt.Errorf("%s: failed to unlink: %+v", fpath, err)
		}
	}
	err = os.Symlink(target, fpath)
	if err != nil {
		return fmt.Errorf("%s: making symbolic link for: %v", fpath, err)
	}
	return nil
}
func writeNewHardLink(fpath string, target string) error {
	err := os.MkdirAll(filepath.Dir(fpath), 0755)
	if err != nil {
		return fmt.Errorf("%s: making directory for file: %v", fpath, err)
	}
	_, err = os.Lstat(fpath)
	if err == nil {
		err = os.Remove(fpath)
		if err != nil {
			return fmt.Errorf("%s: failed to unlink: %+v", fpath, err)
		}
	}
	err = os.Link(target, fpath)
	if err != nil {
		return fmt.Errorf("%s: making hard link for: %v", fpath, err)
	}
	return nil
}
func isSymlink(fi os.FileInfo) bool {
	return fi.Mode()&os.ModeSymlink != 0
}
func within(parent, sub string) bool {
	rel, err := filepath.Rel(parent, sub)
	if err != nil {
		return false
	}
	return !strings.Contains(rel, "..")
}
func multipleTopLevels(paths []string) bool {
	if len(paths) < 2 {
		return false
	}
	var lastTop string
	for _, p := range paths {
		p = strings.TrimPrefix(strings.Replace(p, `\`, "/", -1), "/")
		for {
			next := path.Dir(p)
			if next == "." {
				break
			}
			p = next
		}
		if lastTop == "" {
			lastTop = p
		}
		if p != lastTop {
			return true
		}
	}
	return false
}
func folderNameFromFileName(filename string) string {
	base := filepath.Base(filename)
	firstDot := strings.Index(base, ".")
	if firstDot > -1 {
		return base[:firstDot]
	}
	return base
}
func makeNameInArchive(sourceInfo os.FileInfo, source, baseDir, fpath string) (string, error) {
	name := filepath.Base(fpath)  
	if sourceInfo.IsDir() {
		dir, err := filepath.Rel(filepath.Dir(source), filepath.Dir(fpath))
		if err != nil {
			return "", err
		}
		name = path.Join(filepath.ToSlash(dir), name)
	}
	return path.Join(baseDir, name), nil  
}
func NameInArchive(sourceInfo os.FileInfo, source, fpath string) (string, error) {
	return makeNameInArchive(sourceInfo, source, "", fpath)
}
func ByExtension(filename string) (interface{}, error) {
	var ec interface{}
	for _, c := range extCheckers {
		if err := c.CheckExt(filename); err == nil {
			ec = c
			break
		}
	}
	switch ec.(type) {
	case *Rar:
		return NewRar(), nil
	case *Tar:
		return NewTar(), nil
	case *TarBrotli:
		return NewTarBrotli(), nil
	case *TarBz2:
		return NewTarBz2(), nil
	case *TarGz:
		return NewTarGz(), nil
	case *TarLz4:
		return NewTarLz4(), nil
	case *TarSz:
		return NewTarSz(), nil
	case *TarXz:
		return NewTarXz(), nil
	case *TarZstd:
		return NewTarZstd(), nil
	case *Zip:
		return NewZip(), nil
	case *Gz:
		return NewGz(), nil
	case *Bz2:
		return NewBz2(), nil
	case *Lz4:
		return NewLz4(), nil
	case *Snappy:
		return NewSnappy(), nil
	case *Xz:
		return NewXz(), nil
	case *Zstd:
		return NewZstd(), nil
	}
	return nil, fmt.Errorf("format unrecognized by filename: %s", filename)
}