func parseNonBatch(prefix string, key string) string {
	if key == prefix || !strings.HasPrefix(key, prefix) {
		return key
	}
	parsedKey := strings.TrimSuffix(key, s3Separator)
	if loc := strings.LastIndex(parsedKey, s3Separator); loc < len(prefix) {
		if loc < 0 {
			return key
		}
		parsedKey = key[loc:]
		return strings.TrimPrefix(parsedKey, s3Separator)
	}
	parsedKey = strings.TrimPrefix(key, prefix)
	parsedKey = strings.TrimPrefix(parsedKey, s3Separator)
	index := strings.Index(parsedKey, s3Separator) + 1
	if index <= 0 || index >= len(parsedKey) {
		return parsedKey
	}
	trimmedKey := parsedKey[:index]
	return trimmedKey
}
parseNonBatch parses keys for non-wildcard operations.
It subtracts prefix part from the key and gets first
path.

Example:
key: a/b/c/d
prefix: a/b
output: c/
package url
import (
	"encoding/json"
	"fmt"
	"net/url"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
)
const (
	globCharacters string = "?*"
	s3Scheme string = "s3://"
	s3Separator string = "/"
	matchAllRe string = ".*"
)
type urlType int
const (
	remoteObject urlType = iota
	localObject
)
type URL struct {
	Type      urlType
	Scheme    string
	Bucket    string
	Path      string
	Delimiter string
	Prefix    string
	relativePath string
	filter       string
	filterRegex  *regexp.Regexp
	raw          bool
}
type Option func(u *URL)
func WithRaw(mode bool) Option {
	return func(u *URL) {
		u.raw = mode
	}
}
func New(s string, opts ...Option) (*URL, error) {
	split := strings.Split(s, "://")
	if len(split) == 1 {
		url := &URL{
			Type:   localObject,
			Scheme: "",
			Path:   s,
		}
		for _, opt := range opts {
			opt(url)
		}
		if err := url.setPrefixAndFilter(); err != nil {
			return nil, err
		}
		if runtime.GOOS == "windows" {
			url.Path = filepath.ToSlash(url.Path)
		}
		return url, nil
	}
	if len(split) != 2 {
		return nil, fmt.Errorf("storage: unknown url format %q", s)
	}
	scheme, rest := split[0], split[1]
	if scheme != "s3" {
		return nil, fmt.Errorf("s3 url should start with %q", s3Scheme)
	}
	parts := strings.SplitN(rest, s3Separator, 2)
	key := ""
	bucket := parts[0]
	if len(parts) == 2 {
		key = strings.TrimLeft(parts[1], s3Separator)
	}
	if bucket == "" {
		return nil, fmt.Errorf("s3 url should have a bucket")
	}
	if hasGlobCharacter(bucket) {
		return nil, fmt.Errorf("bucket name cannot contain wildcards")
	}
	url := &URL{
		Type:   remoteObject,
		Scheme: "s3",
		Bucket: bucket,
		Path:   key,
	}
	for _, opt := range opts {
		opt(url)
	}
	if err := url.setPrefixAndFilter(); err != nil {
		return nil, err
	}
	return url, nil
}
func (u *URL) IsRemote() bool {
	return u.Type == remoteObject
}
func (u *URL) IsPrefix() bool {
	return u.IsRemote() && strings.HasSuffix(u.Path, "/")
}
func (u *URL) IsBucket() bool {
	return u.IsRemote() && u.Path == ""
}
func (u *URL) Absolute() string {
	if !u.IsRemote() {
		return u.Path
	}
	return u.remoteURL()
}
func (u *URL) Relative() string {
	if u.relativePath != "" {
		return u.relativePath
	}
	return u.Absolute()
}
func (u *URL) Base() string {
	basefn := filepath.Base
	if u.IsRemote() {
		basefn = path.Base
	}
	return basefn(u.Path)
}
func (u *URL) Dir() string {
	basefn := filepath.Dir
	if u.IsRemote() {
		basefn = path.Dir
	}
	return basefn(u.Path)
}
func (u *URL) Join(s string) *URL {
	if runtime.GOOS == "windows" {
		s = filepath.ToSlash(s)
	}
	clone := u.Clone()
	clone.Path = path.Join(clone.Path, s)
	return clone
}
func (u *URL) remoteURL() string {
	s := u.Scheme + "://"
	if u.Bucket != "" {
		s += u.Bucket
	}
	if u.Path != "" {
		s += "/" + u.Path
	}
	return s
}
func (u *URL) setPrefixAndFilter() error {
	if u.raw {
		return nil
	}
	loc := strings.IndexAny(u.Path, globCharacters)
	wildOperation := loc > -1
	if !wildOperation {
		u.Delimiter = s3Separator
		u.Prefix = u.Path
	} else {
		u.Prefix = u.Path[:loc]
		u.filter = u.Path[loc:]
	}
	filterRegex := matchAllRe
	if u.filter != "" {
		filterRegex = regexp.QuoteMeta(u.filter)
		filterRegex = strings.Replace(filterRegex, "\\?", ".", -1)
		filterRegex = strings.Replace(filterRegex, "\\*", ".*?", -1)
	}
	filterRegex = regexp.QuoteMeta(u.Prefix) + filterRegex
	r, err := regexp.Compile("^" + filterRegex + "$")
	if err != nil {
		return err
	}
	u.filterRegex = r
	return nil
}
func (u *URL) Clone() *URL {
	return &URL{
		Type:      u.Type,
		Scheme:    u.Scheme,
		Bucket:    u.Bucket,
		Delimiter: u.Delimiter,
		Path:      u.Path,
		Prefix:    u.Prefix,
		relativePath: u.relativePath,
		filter:       u.filter,
		filterRegex:  u.filterRegex,
	}
}
func (u *URL) SetRelative(base string) {
	dir := filepath.Dir(base)
	u.relativePath, _ = filepath.Rel(dir, u.Absolute())
}
func (u *URL) Match(key string) bool {
	if !u.filterRegex.MatchString(key) {
		return false
	}
	isBatch := u.filter != ""
	if isBatch {
		v := parseBatch(u.Prefix, key)
		u.relativePath = v
		return true
	}
	v := parseNonBatch(u.Prefix, key)
	u.relativePath = v
	return true
}
func (u *URL) String() string {
	return u.Absolute()
}
func (u *URL) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.String())
}
func (u *URL) IsWildcard() bool {
	return !u.raw && hasGlobCharacter(u.Path)
}
func parseBatch(prefix string, key string) string {
	index := strings.LastIndex(prefix, s3Separator)
	if index < 0 || !strings.HasPrefix(key, prefix) {
		return key
	}
	trimmedKey := key[index:]
	trimmedKey = strings.TrimPrefix(trimmedKey, s3Separator)
	return trimmedKey
}