# -*- coding: utf-8 -*-
"""The 'filesysobjects.apppaths' module provides operations on static application resource paths.
"""
from __future__ import absolute_import
from __future__ import print_function

import os
import sys

import re
import glob

from pysourceinfo.fileinfo import getcaller_pathname, getcaller_filepathname
from pysourceinfo.helper import getpythonpath_rel

from filesysobjects import FileSysObjectsError, AppPathError, PathError, ISSTR
from filesysobjects import RTE, RTE_POSIX, RTE_WIN32, RTE_GENERIC, \
    RTE_URI, RTE_HTTP, RTE_HTTPS, RTE_FILEURI, RTE_FILEURI0, RTE_FILEURI4, RTE_FILEURI5
from filesysobjects.paths import normpathx, escapepathx, unescapepathx, gettpf, getspf

__author__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2010-2016 Arno-Can Uestuensoez" \
                "@Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.1.20'
__uuid__ = "4135ab0f-fbb8-45a2-a6b1-80d96c164b72"

__docformat__ = "restructuredtext en"

#
# for test and development
_mydebug = False

#*
# *** static compiled strings ***
#*

# pathname seperator
if RTE & RTE_WIN32:
    OSSEP = os.path.sep  #: os separator
    OSSEPCLS = '[\\\\]'  #: character class os separator
    OSSEPCLSN = '[^\\\\]'  #: character class without separator
else:
    OSSEP = os.path.sep  #: os separator
    OSSEPCLS = '[/]'  #: character class os separator
    OSSEPCLSN = '[^/]'  #: character class without separator

#
# prohibited characters for optional validation - see strict options
#

#: windows: ::
#:
#:    r'[:<>*?]'
INVALIDCHARSWIN = re.compile(r'[:<>*?]')

#: posix: ::
#:
#:    r'\0'
INVALIDCHARSPOSIX = re.compile(r'\0')

#: posix + windows: ::
#:
#:    r'[:<>*?\0]'
INVALIDCHARS = re.compile(r'[:<>*?\0]')  #: super position of both: r'[:<>*?\0]'

#: maps unambiguous escape characters to escape sequences
ESC_CHAR_MAP = {
    '\a': "\\a",
    '\b': "\\b",
    '\f': "\\f",
    '\n': "\\n",
    '\r': "\\r",
    '\t': "\\t",
    '\v': "\\v",
}

#: maps escape characters for escape sequences to unescape
UNESC_CHAR_MAP = {
    'a': "\a",
    'b': "\b",
    'f': "\f",
    'n': "\n",
    'r': "\r",
    't': "\t",
    'v': "\v",
}

# pylint: disable-msg=W0105

#: Dummy for same group count for processing.
_DUMMY = """(()|())"""

#: The tail of a path atom including possible escaped os.pathsep as ordinary character
_NOSEP_COL = """(
    (["]{3}.*?["]{3}|[']{3}.*?[']{3}|[^:"']+|['"])+
   |(["]{3}.*?["]{3}|[']{3}.*?[']{3}|[^:"']+|['"])+
   )"""

#: The tail of a path atom including possible escaped os.pathsep as ordinary character
_NOSEP_SEM = """(
    (["]{3}.*?["]{3}|[']{3}.*?[']{3}|[^;"']+|['"])+
   |(["]{3}.*?["]{3}|[']{3}.*?[']{3}|[^;"']+|['"])+
   )"""


# [MS-DTYP] - 2.2.57 - UNC definitions
# pchar = %x20-21 / %x23-29 / %x2D-2E / %x30-39 / %x40-5A / %x5E-7B / %x7D-FF
# pchar = r'[\x20-\x21\x23-\x29\x2D-\x2E\x30-\x39\x40-\x5A\x5E-\x7B\x7D-\xFF]'
# pchar="""[^\x00-\x1f\x22\x2a-\x2c\x2f\x3a-\x3f\x5b-\x5d\x7c]"""


#: Scanner/Parser for colon based search path separator: ::
#:
#:    os.pathsep == ':'
#:
#: The applied rules are: ::
#:
#:     4: ('file://[/]' +2SEP | unc://)  +(host)  +1SEP  +(share)  +(object) # [MS-DTYP] 2.2.57
#:    12: ('file://')                    +()      +()    +(drive)  +(path)
#:    20: ('file://')                    +(auth)  +()    +()       +(path)
#:    28: ('file://')                    +()      +()    +()       +(path)
#:    36: ('smb://'|'cifs://')  +(host)  +1SEP  +(share)  +(path)
#:    44: (2SEP)                +(host)  +1SEP  +(share)  +(path) # in general a share present - on POSIX too???
#:    52: (scheme '://')           +(auth)  +1SEP  +(path)   +"?" +(query-fragment)
#:    60: ()                    +()      +()    +(drive)  +(path)
#:    68: ()                    +()      +()    +(drive)  +(path)
#:    76: ()                    +()      +()    +(drive)  +()
#:    84: ()                    +()      +()    +()       +(path)
#:    92: ()                    +()       +()   +()       +()
#:    94 os.pathsep
APPPATHSCANNER_COL = re.compile(
r"""((
     ((file://[/]{0,1}[/\\\\]{2}|unc://)(.{0,1}[^/\\\\]+)[/\\\\]([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_COL + """)) #  4
    |((file:///{0,1}|file:)()([a-zA-Z]:)[/\\\\]*?([/\\\\]{0,1}""" + _NOSEP_COL + """))                               # 12
    |((file://)([^/\\\\]+)()(""" + _NOSEP_COL + """))                                                                # 20
    |((file://|file:)()()(""" + _NOSEP_COL + """))                                                                   # 28
    |((smb://|cifs://)([^/]{0,1}[^/\\\\]*)[/\\\\]+([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_COL + """))                # 36
    |(([\\\\]{2}|[/]{2})(?![:])([^/\\\\]+)[/\\\\]([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_COL + """))                 # 44
    |(([^:/?#]{2,}://)([^/]{0,1}[^/\\\\]*)[/\\\\]*?([/]{0,1}[^?:;]+)([?]""" + _NOSEP_COL + """){0,1})                # 52
    |(()()([a-zA-Z]:)([/\\\\]+?""" + _NOSEP_COL + """))                                                              # 60
    |(()()([a-zA-Z]:)(""" + _NOSEP_COL + """))                                                                       # 68
    |(()()([a-zA-Z]:)(""" + _DUMMY + """))                                                                           # 76
    |(()()()""" + _NOSEP_COL + """())                                                                                # 84
    |(()()()()""" + _DUMMY + """(?=[:]))                                                                             # 92
)[:]?)                                                                                                               # 94
""", re.X)  # @UndefinedVariable

#: Scanner/Parser for semicolon based search path separator. ::
#:
#:    os.pathsep == ';'
#:
#: The applied rules are corresponding to *APPPATHSCANNER_COL*.
APPPATHSCANNER_SEM = re.compile(
r"""((
     ((file://[/]{0,1}[/\\\\]{2}|unc://)(.{0,1}[^/\\\\]+)[/\\\\]([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_SEM + """)) #  4
    |((file:///{0,1}|file:)()([a-zA-Z]:)[/\\\\]*?([/\\\\]{0,1}""" + _NOSEP_SEM + """))                               # 12
    |((file://)([^/\\\\]+)()(""" + _NOSEP_SEM + """))                                                                # 20
    |((file://|file:)()()(""" + _NOSEP_SEM + """))                                                                   # 28
    |((smb://|cifs://)([^/]{0,1}[^/\\\\]*)[/\\\\]+([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_SEM + """))                # 36
    |(([\\\\]{2}|[/]{2})(?![:])([^/\\\\]+)[/\\\\]([^/\\\\]+)[/\\\\]*([/\\\\]""" + _NOSEP_SEM + """))                 # 44
    |(([^;/?#]{2,}://)([^/]{0,1}[^/\\\\]*)[/\\\\]*?([/]{0,1}[^?:;]+)([?]""" + _NOSEP_SEM + """){0,1})                # 52
    |(()()([a-zA-Z]:)([/\\\\]+?""" + _NOSEP_SEM + """))                                                              # 60
    |(()()([a-zA-Z]:)(""" + _NOSEP_SEM + """))                                                                       # 68
    |(()()([a-zA-Z]:)(""" + _DUMMY + """))                                                                           # 76
    |(()()()""" + _NOSEP_SEM + """())                                                                                # 84
    |(()()()()""" + _DUMMY + """(?=[;]))                                                                             # 92
)[;]?)                                                                                                    # 94
""", re.X)  # @UndefinedVariable


#: Helper with group indexes pointing onto the supported syntax terms of *APPPATHSCANNER*.
APPPATHINDEX = (
    4,
    12,
    20,
    28,
    36,
    44,
    52,
    60,
    68,
    76,
    84,
    92, )

#: Helper with human readable enums for types of path variable elements of *APPPATHINDEX*.
APPTYPES = (
    'share',
    'ldsys',
    'rfsys',
    'lfsys',
    'smb',
    'share',
    'uri',
    'ldsys',
    'ldsys',
    'ldsys',
    'lfsys',
    'lfsys', )

#: Helper with human readable enums for secondary level-2 types of *APPTYPES*.
APPTYPES_L2 = {
    'http://': ('http',),
    'https://': ('https',),
    'smb://': ('smb',),
    'cifs://': ('smb',),  # non-standard
    'unc://': ('share',),  # non-standard
    '\\\\': ('share',),
    '//': ('share',),
}

#
# *** splits environment variables ***
#
if RTE & RTE_WIN32:
    _ENV_SPLIT = re.compile(  #: Split-out environment variables for substitution.
       r"""
       (
           (([^%]*?)([%][a-zA-Z0-9_]+[%]))           # 2: defined without brace
         | (([^%]*?)([%][a-zA-Z0-9_]+[^%]?))         # 5: ERROR:
         | ((.*)())                                  # 8: any
       )
       """, re.X)  # @UndefinedVariable

    _ENV_SPLITg = [  #: Entry points into sub strings environment variables and literals.
        2,
        5,
        8,
    ]
else:
    _ENV_SPLIT = re.compile(  #: Split-out environment variables for substitution.
       r"""
       (
           (([^$]*?)([$][{][a-zA-Z0-9_]+[}]))        # 2: defined with brace
         | (([^$]*?)([$][a-zA-Z0-9_]+[;]?))          # 5: defined without brace
         | (([^$]*?)([$][{][a-zA-Z0-9_]+[^}]?))      # 8: ERROR:
         | ((.*)())                                  # 11: any
       )
       """, re.X)  # @UndefinedVariable

    _ENV_SPLITg = [  #: Entry points into sub strings environment variables and literals.
        2,
        5,
        8,
        11,
    ]

# pylint: enable-msg=W0105
_tpf_num = {
    'default': RTE,
    'uri': RTE_URI,  # TODO: replace by RTE_URI virtual platform bit
    'http': RTE_HTTP,  # TODO: replace by RTE_URI virtual platform bit
    'https': RTE_HTTPS,  # TODO: replace by RTE_URI virtual platform bit
    'posix': RTE_POSIX,
    'win': RTE_WIN32,
    'win32': RTE_WIN32,
    'local': RTE,
}


def addpath_to_searchpath(spath, plist=None, **kargs):
    """Adds a path to 'plist'.

    In case of relative path searches in provided
    'plist', or 'kargs[searchplist]'a hook, when found
    verifies the existence within file system, in case
    of success adds the completed path to 'plist' the list.

    In case of 'glob' adds all entries.

    Args:
        **spath**:

            A path to be added to 'plist'.
            See common options for details.
            Valid scope types:

                * literal : X
                * re      : -
                * blob    : -

            default := caller-file-position.

        **plist**:

            List to for the storage, and by default
            search list too.
            See common options for details.

            default := sys.path

        kargs:
            **append**:
                Append, this is equal to
                pos=len(plist).

            **checkreal**:
                Checks redundancy by resolving real path,
                else literally.

            **exist**:
                Checks whether exists, else nothing is done.

            **pos**:
                A specific position for insertion
                within range(0,len(plist)). ::

                  pos := #pos

            **prepend**:
                Prepend, this is equal to
                pos=0.

            **redundant**:
                Add relative, allow redundant when
                same is already present.

            **relative**:
                Add relative sub path to
                provided base. ::

                   relative := <base>:

            **searchplist**:
                Alternative list to search for checks.

            **spf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to API in the manual at :ref:`spf <OPTS_SPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.getspf() <paths.html#getspf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.

            **tpf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to the API in the manual at :ref:`tpf <OPTS_TPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.gettpf() <paths.html#gettpf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.

    Returns:
        When successful returns insertion position, else a 'value<0'.
        The insertion position in case of multiple items is the position
        of the last.

    Raises:
        AppPathError
        passed through exceptions
    """
    if plist == None:
        plist = sys.path

    _spf = kargs.get('spf', False)
    _tpf = kargs.get('tpf', False)

    _splist = kargs.get('searchplist', plist)

    pos = 0
    relative = None
    _exist = False
    _red = False
    _chkr = False
    for k, v in kargs.items():
        if k == 'prepend':
            pos = 0
        elif k == 'append':
            pos = -1
        elif k == 'pos':
            if not type(v) is int:
                raise AppPathError("Digits required for 'pos'*" + str(pos)
                                       + ")")
            pos = v
        elif k == 'relative':
            relative = v.split(os.pathsep)
        elif k == 'exists':
            _exist = v
        elif k == 'redundant':
            _red = v
        elif k == 'checkreal':
            _chkr = v

    def _add(s):
        if relative:
            s = getpythonpath_rel(s, relative)
        if not _red:
            if _chkr:
                for sx in map(lambda x: os.path.realpath(x), plist):
                    if os.path.realpath(s) == sx:
                        return
            else:
                if s in plist:
                    return

        if pos == -1:
            plist.append(s)
            return len(plist) - 1
        else:
            plist.insert(pos, s)
            return pos

    # normalize
    _start_elems = splitapppathx(spath, appsplit=True, **kargs)[0]
    spath = splitapppathx_getlocalpath(_start_elems, tpf=_tpf)

    if _exist:
        if os.path.isabs(spath) and os.path.exists(spath):
            return _add(spath)
        elif os.path.exists(os.path.curdir + os.sep + spath):
            return _add(os.path.normpath(os.path.curdir + os.sep + spath))
        else:
            for s in _splist[:]:
                if os.path.exists(s + os.sep + spath):
                    pos = _add(s + os.sep + spath)
    else:
        if os.path.isabs(spath):
            return _add(spath)
        elif os.path.exists(os.path.curdir + os.sep + spath):
            return _add(os.path.normpath(os.path.curdir + os.sep + spath))
        else:
            for s in _splist[:]:
                if os.path.exists(s + os.sep + spath):
                    pos = _add(s + os.sep + spath)
    return pos


def delpath_from_searchpath(dellist, plist=None, **kargs):
    """Deletes a list of paths from 'plist'.

    Args:
        **dellist**:
            A list of paths to be deleted
            from 'plist'. Valid scope types:

                * literal : X
                * re      : X
                * glob    : X

            see kargs[regexpr|glob].

            default := None

        **plist**:
            List of search paths.

            default := sys.path

        kargs:
            The following keys are additional before
            comparison, on 'dellist' only when no
            match pattern is provided:

                **case**:
                    Calls on both: os.path.normcase

                **esc**:
                    Calls on both: escapepathx/unescapepathx

                **exist**:
                    Calls on both: os.path.exists

                **noexist**:
                    Calls on both: not os.path.exists

                **norm**:
                    Calls on both: normpathx

                **norm**:
                    Calls on both: os.path.normpath

                **real**:
                    Calls on both: os.path.realpath

            **regexpr|glob**:
                Input is a list of

                **regexpr**:
                    regular expressions,
                    just processed by
                        're.match(dl,pl)'

                **glob**:
                    process glob, and check
                    containment in set

    Returns:
        When successful returns True, else False.

    Raises:
        passed through exceptions

    """
    if plist == None:
        plist = sys.path
    if not dellist:
        return True
    _exists = False
    _rg = False
    _raw = kargs.get('raw', False)
    _real = kargs.get('real', False)
    _norm = kargs.get('norm', False)
    _case = kargs.get('case', False)

    for k, v in kargs.items():  # @UnusedVariable

        if k == 'exist':
            _exist = True
            _exists = True

        elif k == 'noexist':
            _exist = False
            _exists = True

        elif k == 'regexpr':
            _reg = True
            _glob = False
            __rg = True

        elif k == 'glob':
            _reg = False
            _glob = True
            __rg = True

    # seems to be sure
    if type(dellist) == str:
        dellist = [dellist]

    for dl in dellist:

        for pl in reversed(plist):

            if not _raw:
                if dl and len(dl) > 6 and dl[0:7] == 'file://':
                    dl = os.sep + dl[7:].lstrip(os.sep)
                if pl and len(pl) > 6 and pl[0:7] == 'file://':
                    pl = os.sep + pl[7:].lstrip(os.sep)

            if _real:
                if not _rg:
                    dl = os.path.realpath(dl)
                pl = os.path.realpath(pl)

            if _norm:
                if not _rg:
                    dl = os.path.normpath(dl)
                pl = os.path.normpath(pl)

            if _case:
                if not _rg:
                    dl = os.path.normcase(dl)
                pl = os.path.normcase(pl)

            if _exists:
                if _exist:
                    if not _rg:
                        if not os.path.exists(pl) or not os.path.exists(dl):
                            continue
                    elif os.path.exists(pl):
                        continue

            if _rg:
                if _reg:
                    if re.match(dl, pl):
                        plist.pop(plist.index(pl))
                elif _glob:
                    if pl in glob.glob(dl):
                        plist.pop(plist.index(pl))
            else:
                if dl == pl:
                    plist.pop(plist.index(pl))

    return True


def join_apppathx_entry(entry, **kw):
    """Assembles the components of an application path entry.

    Known standard applications are: ::

      cifs , file, ftp, http, https, smb, unc, uri
      <unc-file-path>, <posix-app-file-path>

    The path entry is not normalized again, thus
    has to be in the appropriate platform syntax.

    Args:

        **entry**:
            Application entry as provided by *splitapppathx()*.


        kw:
            **apppre**:
                Add application prefix.

            **tpf**:
                Target platform.

    Returns:

        Entry as a single stings.

    Raises:
        pass-through

    """
    res = ''
    apppre = kw.get('apppre', True)
    tsep, pathsep, tpf, rte, _apre = gettpf(kw.get('tpf', RTE))

    if apppre:  # URI scheme allways uses a slash
        tsep = '/'

    if entry[0]:  # URI/URL
        if entry[0].startswith('smb'):
            res = r'smb://'
            tsep = '/'
            if entry[1]:
                res += entry[1]
            if entry[2]:
                res += tsep + entry[2]
            if entry[3]:
                res += entry[3]

        elif entry[0].startswith('cifs'):
            res = r'cifs://'
            tsep = '/'
            if entry[1]:
                res += entry[1]
            if entry[2]:
                res += tsep + entry[2]
            if entry[3]:
                res += entry[3]

        elif entry[0].startswith('share'):
            if apppre:
                if rte & 255 | RTE_URI == RTE_FILEURI0:
                    res = 'file://'  # minimal
                elif rte & 255 | RTE_URI == RTE_FILEURI4:
                    res = 'file:////'  # traditional
                #elif rte & 255 == RTE_FILEURI5 or rte & 255 == RTE_FILEURI:
                else:
                    res = 'file://///'  # extra slash

                tsep = '/'
            else:
                res = 2 * tsep
            if entry[1]:
                res += entry[1]
            if entry[2]:
                res += tsep + entry[2]
            if entry[3]:
                res += entry[3]

        elif entry[0].startswith('rfsys'):
            if apppre:
                res = r'file://'
            else:
                if tpf == 'win':
                    res = '\\\\'
                else:
                    res = '//'

            if entry[1]:
                res += entry[1]
            if entry[3]:
                res += entry[3]

        elif entry[0].startswith('ldsys'):
            if apppre:
                # RFC8089.E.2: URIs of the form "file:///c:/path/to/file" are already
                # supported by the "path-absolute" rule.
                res = 'file:///'
            res += entry[2] + entry[3]

        elif entry[0].startswith('lfsys'):
            if apppre:
                res = 'file://'
                tsep = '/'
            res += entry[3]

        elif entry[0].startswith('https'):
            res = 'https://'
            tsep = '/'
            if entry[1]:
                res += entry[1]
            # entry 2 is share padding for path position comaptibility
            if entry[3]:
                res += entry[3]
            if entry[4]:
                if entry[4][0] == '?':
                    res += entry[4]
                else:
                    res += '?' + entry[4]

        elif entry[0].startswith('http'):
            res = 'http://'
            tsep = '/'
            if entry[1]:
                res += entry[1]
            # entry 2 is share padding for path position comaptibility
            if entry[3]:
                res += entry[3]
            if entry[4]:
                if entry[4][0] == '?':
                    res += entry[4]
                else:
                    res += '?' + entry[4]

        elif entry[0].startswith('ftp'):
            res = 'ftp://'
            tsep = '/'
            if entry[1]:
                res += entry[1]
            if entry[2]:
                if entry[2][0] is not '/':
                    res += '/' + entry[2]
                else:
                    res += entry[2]
            if entry[3]:
                res += '?' + entry[3]

        elif entry[0].startswith('unc'):
            if apppre:
                res = 'file://///'
                tsep = '/'
            else:
                res += 2 * tsep
            if entry[1]:
                res += entry[1]
            if entry[2]:
                res += tsep + entry[2]
            if entry[3]:
                res += entry[3]

        else:  # generic URI/URL
            tsep = gettpf(tpf)[0]
            ret = ''
            ret += entry[0] + "://"
            if entry[1]:
                ret += entry[1]
            if entry[2]:
                if entry[2][0] == '/':
                    ret += entry[2]
                else:
                    ret += "/" + entry[2]
            if entry[3]:
                if entry[3][0] == '?':
                    ret += entry[3]
                else:
                    ret += "?" + entry[3]
            return ret

    else:  # s.th. else - generic local path
        if entry[2]:
            res += 2 * tsep + entry[2]
        if entry[3]:
            res += entry[3]

    return res


def normapppathx(spath, **kargs):
    """Generic extention of *normpathx()* by application schemes and search path syntax.

    Args:
        **spath**:
            Accepts path, or search-path.

        kargs:
            Supports the parameters of :ref:`splitapppathx() <def_splitapppathx>`

            **apppre**:
                Application scheme.

            **appsplit**:
                Split into scheme and components of an application path.

            **spf**:
                Source platform.

            **tpf**:
                Target platform.

    Returns:
        Normalized path or search-path.

    Raises:
        pass-through: see :ref:`splitapppathx() <def_splitapppathx>`

    """
    kargs['raw'] = False
    apppre = kargs.get('apppre')

    if kargs.get('appsplit'):
        return splitapppathx(spath, **kargs)

    _tsep, _tpsep, _tpf, _t, _apre = gettpf(kargs.get('tpf'), apppre=apppre)
    if not _tsep:
        _spf = kargs.get('spf', os.pathsep)
        _tsep, _tpsep, _tpf, _t, _apre = gettpf(_spf, apppre=apppre)

    res = []

    kargs['tpf'] = _tpf
    for apx in splitapppathx(spath, **kargs):
        if type(apx) in ISSTR:
            res.append(apx)
        else:
            res.append(join_apppathx_entry(apx, **kargs))
    return _tpsep.join(res)


def set_uppertree_searchpath(start=None, top=None, plist=None, **kargs):
    """Prepends each directory path from from 'start' on upward to 'top'
    in-place into *plist*. For example: ::

       start :=  /my/top/a/b/c
       top   :=  /my/top

    results in: ::

       plist := [
          '/my/top/a/b/c',
          '/my/top/a/b',
          '/my/top/a',
          '/my/top',
       ]

    Args:
        **start**:
            Start components of a path string.
            See common options for details.
            Valid scope types:

                * literal : X
                * re      : -
                * blob    : -

            default := caller-file-position.

        **top**:
            End component of a path string.
            The node 'top' is included.
            Valid scope types:

                * literal : X
                * re      : -
                * blob    : -

            default := <same-as-start>

        **plist**:
            List to for the storage.
            See common options for details.

            default := sys.path

        kargs:
            **append**:
                Appends the set of search paths.

            **matchidx**:
                Ignore matches '< #idx',
                adds match '== #idx' and returns.

                   matchidx := #idx

                default := 0 # all

            **matchcnt**:
                The maximal number of matches
                returned when multiple occur. ::

                   matchcnt=#num:

            **matchlvl**:
                Increment of match for top node when
                multiple are in the path. ::

                   matchlvl := #num:

                See common options for details.

            **matchlvlbackward**:
                Increment of match for top node when
                multiple are in the path. ::

                   matchlvlbackward := #num:

                See common options for details.

            **noTypeCheck**:
                Suppress required identical types of 'top' and
                'start'. As a rule of thumb for current
                version, the search component has to be less
                restrictive typed than the searched.
                The default applicable type matches are::

                     top    ¦ start
                    --------+---------------------
                     lfsys  ¦ lfsys, ldsys, share
                            | smb, cifs,
                     ldsys  ¦ ldsys
                     share  ¦ share
                     smb    ¦ smb
                     cifs   ¦ cifs
                     http   ¦ http, https
                     https  ¦ https, http

                See common options for details.

            **prepend**:
                Prepends the set of search paths.
                This is default.

            **raw**:
                Suppress normalization by call of
                'os.path.normpath'.

            **relonly**:
                The paths are inserted relative to the
                top node only. This is mainly for test
                purposes. The intermix of relative and
                absolute path entries is not verified.

            **reverse**:
                This reverses the resulting search order
                 from bottom-up to top-down.

            **unique**:
                Insert non-present only, else present
                entries are not checked, thus the search order
                is changed in general for 'prepend', while
                for 'append' the present still covers the new
                entry.

    Returns:
        When successful returns 'True', else returns either 'False',
        or raises an exception.

    Raises:
        AppPathError

        pass-through
    """
    if plist == None:
        plist = sys.path

    _relo = False
    _matchcnt = 0
    _matchidx = 0

    set_uppertree_searchpath._matchcnt = 0
    set_uppertree_searchpath._matchidx = 0

    matchlvl = 0
    matchlvlbackward = -1
    reverse = False
    unique = False
    prepend = True
    _tchk = True
    _raw = False
    _split = False
    _sitem = False

    for k, v in kargs.items():
        if k == 'relonly':
            _relo = True
        elif k == 'matchcnt':
            if not type(v) is int:
                raise AppPathError("Digits only matchcnt:" + str(v))
            _matchcnt = v
        elif k == 'matchidx':
            if not type(v) is int:
                raise AppPathError("Digits only matchidx:" + str(v))
            _matchidx = v
        elif k == 'matchlvl':
            if not type(v) is int:
                raise AppPathError("Digits only matchlvl:" + str(v))
            matchlvl = v
        elif k == 'matchlvlbackward':
            if not type(v) is int:
                raise AppPathError("Digits only matchlvlbackward:" + str(v))
            matchlvlbackward = v
        elif k == 'reverse':
            reverse = True
        elif k == 'unique':
            unique = True
        elif k == 'append':
            prepend = False
        elif k == 'prepend':
            prepend = True
        elif k == 'raw':
            _raw = True
        elif k == 'splitItems':
            _split = True
        elif k == 'singleitem':
            _sitem = True
        elif k == 'noTypeCheck':
            _tchk = False

    if matchlvl > 0:
        matchlvlbackward = -1

    # Prepare search path list
    # if decided to normalize, and whether to ignore leading '//'
    if _raw:  # match basically literally
        _plst = []
        for i in plist:
            # normalize
            _elems = splitapppathx(i, appsplit=True, **kargs)[0]
            _plst.append(splitapppathx_getlocalpath(_elems))

    else:  # normalize for safer match conditions
        _plst = []
        for i in plist:
            # normalize
            _elems = splitapppathx(i, appsplit=True, **kargs)[0]
            _plst.append(splitapppathx_getlocalpath(_elems))

    # 0. prep start dir
    if start == '':
        raise AppPathError("Empty start:''")
    elif start == None:
        start = getcaller_filepathname(2)  # caller file
    # normalize
    _start_elems = splitapppathx(start, appsplit=True, **kargs)[0]
    start = splitapppathx_getlocalpath(_start_elems)
    # try a literal
    if not os.path.isabs(start):
        start = getcaller_pathname(2) + os.sep + start
    if os.path.isfile(start):
        start = os.path.dirname(start)  # we need dir
    if not os.path.exists(start):
        raise AppPathError("Missing start:" + str(start))

    # 1. prep top dir

    # normalize
    if top == '':
        raise AppPathError("Empty top:''")
    elif top == None:
        top = getcaller_pathname(2)  # caller file

    # normalize
    _top_elems = list(splitapppathx(top, appsplit=True, **kargs)[0])

    # ptype
    if _tchk:
        if _top_elems and _start_elems:
            if _top_elems[0] != _start_elems[0]:

                # TODO: still to enhance..
                if _top_elems[0] in ('lfsys', ):
                    if os.path.realpath(_top_elems[3]):
                        pass
                    elif _start_elems[0] in (
                            'ldsys',
                            'lfsys', ):
                        pass
                    else:
                        raise AppPathError(
                            "'lfsys' combined with " + str(_start_elems[0]) +
                            " requires relative pathname for 'lfsys', given: "
                            + str(_top_elems[3]))
                    pass
                else:
                    raise AppPathError(
                        "This version requires compatible types: start(" +
                        str(_start_elems[0]) + ") =! top(" +
                        str(_top_elems[0]) + ")")

    top = splitapppathx_getlocalpath(_top_elems)

    # if absolute
    if os.path.isabs(top):
        if not os.path.exists(top):
            raise AppPathError("Top does not exist:" + str(top))

    def _addsub(x, pl=plist):
        """...same for all."""
        # >3: nonlocal _matchcnt
        if _matchcnt != 0 and _matchcnt <= set_uppertree_searchpath._matchcnt:
            return
        if _matchidx != 0 and _matchidx != set_uppertree_searchpath._matchidx:
            return

        if unique and x in pl or x in plist:
            return False
        if reverse:
            pl.append(x)
        else:
            pl.insert(0, x)
        set_uppertree_searchpath._matchcnt += 1
        pass

    # find top
    if top:
        # FIXME:
        if top == '.':
            top = os.path.abspath(top)
        top = escapepathx(top)
        start = escapepathx(start)

        # for now works literally
        a = start.split(top)
        if len(a) == 1 and top != start:
            raise AppPathError(
                "Top-node is not in search path:\n  top   = %s\n  start = %s" %
                (str(top), str(start)))

        if matchlvl >= len(a):  # check valid range
            raise AppPathError("Match count out of range:" + str(matchlvl)
                                   + ">" + str(len(a)))
        # check valid range
        elif matchlvlbackward > 0 and matchlvlbackward >= len(a):
            raise AppPathError("Match count out of range:" +
                                   str(matchlvlbackward) + ">" + str(len(a)))

    else:
        if matchlvl > 0:
            raise AppPathError("Match count out of range:" + str(matchlvl)
                                   + "> 0")
        if matchlvlbackward > 0:
            raise AppPathError("Match count out of range:" +
                                   str(matchlvlbackward) + "> 0")
        _addsub(start)
        return True

    #
    # so we have actually at least one top within valid range and a remaining sub-path - let us start
    #

    if a == ['', '']:  # top == start
        if matchlvl > 0:
            raise AppPathError("Match count out of range:" + str(matchlvl)
                                   + "> 0")
        if matchlvlbackward > 0:
            raise AppPathError("Match count out of range:" + str(matchlvl)
                                   + "> 0")
        _addsub(start)
        return True

    elif a[0] == '':  # top is prefix
        _tpath = top

        if matchlvlbackward >= 0:
            mcnt = len(a) - 1 - matchlvlbackward
        else:
            mcnt = matchlvl

        _spath = top.join(a[mcnt + 1:])  # sub-path for search recursion

    else:

        # get index for requested number of ignored/contained matches
        if matchlvlbackward >= 0:
            mcnt = len(a) - 1 - matchlvlbackward
        else:
            mcnt = matchlvl + 1

        # set matched prefix and postfix
        if os.path.isabs(top):
            _tpath = top
            # sub-path for search recursion
            _spath = (os.sep + top + os.sep).join(a[mcnt:])
        elif not a[mcnt - 1]:  # tail
            _tpath = (os.sep + top + os.sep).join(a[:mcnt])
            _spath = ''
        else:
            # top path as search hook
            _tpath = (os.sep + top + os.sep).join(a[:mcnt]) + os.sep + top
            # sub-path for search recursion
            _spath = (os.sep + top + os.sep).join(a[mcnt:])

    _tpath = os.path.normpath(_tpath)
    _spath = os.path.normpath(_spath)
    if not os.path.isabs(top) and os.path.isabs(_spath):
        _spath = _spath[1:]

    if _relo:  # relative paths, mainly for test
        curp = ''
    else:
#        curp = unescapepathx(_tpath)
#        curp = os.path.normpath(curp)
        curp = os.path.normpath(_tpath)
        if curp not in plist:  # insert top itself
            _addsub(curp)

    a = _spath.split(os.sep)
    if prepend:
        for p in a:
            if not p:
                continue
            curp = os.path.join(curp, p)
            _addsub(curp)
    else:
        _buf = []
        for p in a:
            if not p:
                continue
            curp = os.path.join(curp, p)
            _addsub(curp, _buf)
        plist.extend(_buf)

    return True


def splitapppathx(spath, **kargs):
    """Splits PATH variables which may include URI type prefixes into
    a list of single path entries.

    The default behavior is to split a search path into a list of
    contained path entries. E.g::

       p = 'file:///a/b/c:/d/e::x/y:smb://host/share/q/w:http://host/a/b/:https://host/a?xy#123'

    Is split into: ::

            px = [
                'file:///a/b/c',
                'file://host/a/b/c',
                'file://///host/share/a/b/c',
                '/d/e',
                '',
                'x/y',
                'smb://host/share/q/w',
                '//host/share/q/w',
                'http://a/b/',
                'https://host/a?xy#123',
            ]

    With parameter '*appsplit*' ::

            px = [
                ('lfsys',   '',      '',       '/a/b/c'),
                ('rfsys',   'host',  '',       '/a/b/c'),
                ('unc',     'host',  'share',  '/a/b/c'),
                ('lfsys',   '',      '',       '/d/e'),
                ('lfsys',   '',      '',       ''),
                ('lfsys',   '',      '',       'x/y'),
                ('smb',     'host',  'share',  'q/w'),
                ('share',   'host',  'share',  'q/w'),
                ('share',   'host',  'share',  'q/w'),

                ('http',    'host',   '',      '/a/b/',   ''),
                ('https',   'host',   '',      '/a',      'xy#123'),
            ]

    For reserved prefix keywords as parts of the name, these
    should be escaped.

    Args:
        **spath**:
            The search path to be split.

        kargs:
            The provided key-options are also transparently passed
            through to 'normpathx()' `[see] <paths.html#normpathx>`_ - if not 'raw'.

            **apppre**:
                Adds application prefix. ::

                    appsplit = (True|False)

                default := False

            **appsplit**:
                Splits into tuples of application entries. ::

                    appsplit = (True|False)

                default := False

                For example: ::

                   file:///my/path/a

                results for *True* in: ::

                   (lfsys, '', '', '/my/path/a')

            **delnulpsep**:
                Delete empty search paths.

                default := True

            **escape**:
                Escapes backslash.

                default := False

            **keepsep**:
                Modifies the behavior of 'strip' parameter.
                If 'False', the trailing separator is dropped. ::

                   splitapppathx('/a/b', keepsep=False)   => ('', 'a', 'b')
                   splitapppathx('/a/b/', keepsep=False)  => ('', 'a', 'b')

                for 'True' trailing separators are kept as directory
                marker::

                   splitapppathx('/a/b', keepsep=True)    => ('', 'a', 'b')
                   splitapppathx('/a/b/', keepsep=True)   => ('', 'a', 'b', '')

                default := False # for URIs except file://, smb://
                default := True  # for file path names

            **pathsep**:
                Replaces path separator set by the *spf*, e.g. for a URI
                pathlist from an alternate platform. The resulting path
                separator selects the type of the scanner *APPPATHSCANNER*. ::

                    pathsep := (';' | ':')

            **raw**:
                Displays a list of unaltered split path items, superposes 'rpath'
                and 'rtype'. ::

                    raw = (True|False)

                default := False

            **rpath**:
                Displays the path as provided raw sub string.

                For further details refer to 'splitapppathx'
                `[see] <#split-appprefix>`_. ::

                    rpath = (True|False)

            **rtype**:
                Displays the type prefix as provided raw sub string.

                For further details refer to 'splitapppathx'
                `[see] <#split-appprefix>`_. ::

                    rtype = (True|False)

            **spf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to API in the manual at :ref:`spf <OPTS_SPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.getspf() <paths.html#getspf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.

            **strict**:
                Validates for the target OS/FS, throws exception when invalid
                characters are contained.

            **strip**:
                Strips null-entries.

                default := True

            **tpf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to the API in the manual at :ref:`tpf <OPTS_TPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.gettpf() <paths.html#gettpf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.

            **unescape**:
                Unescapes backslash.

                default := False

    Returns:
        When split successful returns a list of tuples: ::

            appsplit == False

                [
                    <pathvar-item>,
                    ...
                ]

            appsplit == True

                [
                    (TYPE, host-name, share-name, pathname),
                    ...
                ]

        The tuple contains: ::

            (TYPE, host-name, share-name, pathname)

              TYPE := (raw|cifs|smb|share|http|https|lfsys|rfsys|ldsys)
                 raw := (<raw-pathvar-item>)
                 cifs := ('cifs://')
                 smb := ('smb://')
                 share := ('file:///'+2SEP|'file://'+2SEP|2SEP)
                 rfsys := ('file://')
                 lfsys := ('file://'|'')
                 ldsys := [a-z]':'

                 http := ('http://')
                 https := ('http://')

              host-name := (host-name|'')
              share-name := (valid-share-name|'')
              valid-share-name := (
                 smb-share-name
                 | cifs-share-name
                 | win-drive-share-name
                 | win-drive-os
                 | win-special-share-name
              )
              pathname := "pathname on target"

            specials:

               raw := ('raw', '', '', raw-pathvar-item)
               rpath := (TYPE, host-name, share-name, raw-pathname)
               rtype := (raw-type, host-name, share-name, pathname)
               rtype + rpath := (raw-type, host-name, share-name, raw-pathname)

        For compatibility the URI for http/https adds one item
        'query+fragment', while the share remains empty. ::

           ('http' | 'https') := (TYPE, host-name, '', pathname, query+fragment)

        else: ::

           ('lfsys', '', '', apstr)

        **REMARK**:
            The hostname may contain in current release
            any suboption, but is not tested with options at all.

    Raises:
        PathError

        passed-through

    """
    _delnulpsep = kargs.get('delnulpsep', True)  #: remove resulting empty entries
    _normpathx = kargs.get('normpathx', True)  # : calls normapthx() on path-part
    apppre = kargs.get('apppre', False)
    appsplit = kargs.get('appsplit', False)  # : control application tuples, else ordinary path
    raw = kargs.get('raw', False)  # : control raw
    rpath = kargs.get('rpath', False)  # : control RAW-PATH
    rtype = kargs.get('rtype', False)  # : control RAW-TYPE
    strict = kargs.get('strict', False)  # : validate and raise if not valid

    # platform path types

    # the source platform - controls the type of scanner - APPPATHSCANNER
#    pathsep = kargs.get('spf', '')  # : control input domain - has precedence for apppathx parser selection
#    ssep, pathsep, _spf, _input = getspf(pathsep)  # @UnusedVariable
    spf = kargs.get('spf', '')  # : control input domain - has precedence for apppathx parser selection
    ssep, pathsep, _spf, _input = getspf(spf)  # @UnusedVariable
    pathsep = kargs.get('pathsep', pathsep)  # alter when provided

    # the traget platform
    tpf = kargs.get('tpf', False)
    tsep, tpsep, _tpf, _output, _apre = gettpf(pathsep, apppre=apppre)  # @UnusedVariable


    #
    # set parser for apppathx - the pathsep is actually the only difference
    #
    if pathsep[0] is ':':
        _appparser = APPPATHSCANNER_COL

    elif pathsep[0] is ';':
        _appparser = APPPATHSCANNER_SEM

    else:
        # should never occur - for now going ahead...
        if RTE & RTE_WIN32:
            _appparser = APPPATHSCANNER_SEM
        else:
            _appparser = APPPATHSCANNER_COL

    ret = []  # : collect result
    g = 0  # : preserve for final analysis outside the loop

    def getraw(i, g):
        """Get raw string.

        Args:
            i: iterator
            g: current group

        Returns:
            (s,e): for sub string
        """
        # get start string position without APP-PREFIX
        s = i.start(g + 1)
        if s == -1:
            s = i.start(g + 2)
            if s == -1:
                s = i.start(g + 3)

        # get end string position
        e = i.end(g + 3)
        if e == -1:
            e = i.end(g + 2)
            if e == -1:
                e = i.end(g + 1)

        return (
            s,
            e, )

    def _validate(plst):
        for x in plst:
            if _output & RTE_POSIX and _output & RTE_WIN32 and INVALIDCHARS.search(x) \
                    or _output & RTE_GENERIC and INVALIDCHARS.search(x):
                raise PathError(
                    """invalid tpf char(:<>*?\\0) = '""" + str(x) + "'")
            elif _output & RTE_POSIX and INVALIDCHARSPOSIX.search(x):
                raise PathError(
                    """invalid tpf char(\\0) = '""" + str(x) + "'")
            elif _output & RTE_WIN32 and INVALIDCHARSWIN.search(x):
                raise PathError(
                    """invalid tpf char(:<>*?) = '""" + str(x) + "'")

            # else: let the OS decide...

    if not spath:  # shortcut for empty _input - None and 0/length-string
        if not appsplit:
            return []
        if raw:
            return [('raw', '', '', '')]
        if rtype or (rpath and rtype):
            return [('', '', '', '')]
        else:
            return [('lfsys', '', '', '')]

    # APPPATHSCANNER_COL APPPATHSCANNER_SEM
    for i in _appparser.finditer(spath):
        for g in APPPATHINDEX:  # do this for getting the syntax term
            _cur = None

            if i.start(g) == -1:  # if there is a match at all
                continue

            if g == 92:  # EMPTY: shortcut for empty group == empty path entry
                if raw:  # raw prio higher
                    _cur = ('raw', '', '', '')
                elif rtype or (rpath and rtype):
                    _cur = ('', '', '', '')
                # elif rpath:
                else:
                    _cur = ('lfsys', '', '', '')

                ret.append(_cur)
                break

            elif i.group(g + 3) or i.group(g):  # 4:local file system / 0:uri or IEEE/UNC/SMB
                if raw:
                    # _cur = ('raw', '', '', pathvar[i.start(g):i.end(g + 3)])
                    ret.append(('raw', '', '',
                                spath[i.start(g):i.end(g + 3)]))
                    break
                elif rtype and rpath or rtype:

                    if g is 52:
                        # **uri**

                        _cur = [i.group(g), i.group(g + 1), '', i.group(g + 2),
                            i.group(g + 3)]

                        #check registered
                        try:
                            _cur[0] = APPTYPES_L2[i.group(g)][0]
                        except KeyError:
                            # non-registered
                            raise FileSysObjectsError("URI not supported: " + str(i.group(g)))

                        if not _cur[-1]:
                            _cur[-1] = ''

                    else:
                        _cur = (i.group(g), i.group(g + 1), i.group(g + 2),
                            i.group(g + 3))

                # elif rpath:
                else:
                    _cur = ['', i.group(g + 1), i.group(g + 2), i.group(g + 3)]

                    if g is 36:
                        # **smb**

                        _cur[0] = APPTYPES_L2.get(
                            i.group(g), APPTYPES[int((g - 4) / 8)])[0]

                    elif g is 52:
                        # **uri**

                        #check registered
                        try:
                            _cur[0] = APPTYPES_L2[i.group(g)][0]
                        except KeyError:
                            # non-registered
                            raise FileSysObjectsError("URI not supported: " + str(i.group(g)))

                        #_cur[0] = re.sub(r':/*', '', i.group(g))

                        _cur.insert(2, '')  # dummy-share for compatibility of path position, extends 1 field

                        if not _cur[-1]:
                            _cur[-1] = ''
                    else:
                        _cur[0] = APPTYPES[int((g - 4) / 8)]

                    _cur = tuple(_cur)

            elif i.group(g + 2):  # 2:DOS-SC_DRIVE - only
                if raw:
                    # _cur = ('raw', '', '', pathvar[i.start(g):i.start(g + 3)])
                    ret.append(('raw', '', '',
                                spath[i.start(g):i.start(g + 3)]))
                    break
                elif rtype and rpath or rtype:
                    _cur = (i.group(g), i.group(g + 1), i.group(g + 2), '')
                else:
                    if g is 36:
                        # **smb**
                        _a = APPTYPES_L2.get(i.group(g), APPTYPES[int((g - 4) / 8)])
                    else:
                        _a = APPTYPES[int((g - 4) / 8)]
                    _cur = (_a, i.group(g + 1), i.group(g + 2), '')

            elif not i.group(g + 1):  # here: 0,1,2,3 => empty group
                _cur = ('lfsys', '', '', '')

            else:
                continue  # should not occur, anyhow...

            #
            # post processing on prepared match - includes the processing of the path
            #
            if _cur:
                gsepback = ''
                if i.start(g) > 0:  # look back
                    gsepback = i.string[i.start(g) - 1]

                if _input & RTE_POSIX and not _input & RTE_WIN32 \
                        and g in (60, 68, 76):  # DOSDRIVE
                    # Posix: ignore drives and uses 'colon' as separator

                    if raw:
                        ret.append(('raw', '', '', _cur[2] + _cur[3]))
                    elif pathsep == ";":
                        # selected by option 'pathsep'
                        # anyhow, posix does not know anything about drives
                        ret.append(('lfsys', '', '', _cur[2] + _cur[3]))
                    else:
                        # treat drive as path
                        ret.append(('lfsys', '', '', _cur[-2][0]))
                        # treat path on drive still as path
                        ret.append(('lfsys', '', '', _cur[-1]))

                elif _input & (RTE_POSIX | RTE_WIN32) == (RTE_POSIX
                                                          | RTE_WIN32):
                    # both: Posix and Win, reserved sep and pathsep for both,
                    # app-tags are still valid
                    ret.append(_cur)

                elif _input & RTE_GENERIC:
                    # simple character split, but keeps app-URIs

                    if g in (60, 68, 76):  # DOSDRIVE - ignores DOS drives
                        if not gsepback or gsepback in pathsep:  # first or prefix-sep
                            ret.append(('lfsys', '', '', _cur[-2][0]))

                        elif gsepback not in pathsep:  # have s.th. to concat
                            ret[-1] = list(ret[-1])
                            ret[-1][-1] += gsepback + _cur[-2][0]
                            ret[-1] = tuple(list(ret[-1]))

                        if ':' not in pathsep:
                            # append path too

                            # FIXME: speedup
                            ret[-1] = list(ret[-1])
                            ret[-1][-1] += ':' + _cur[-1]
                            ret[-1] = tuple(list(ret[-1]))

                        else:  # treat path on drive still as path
                            ret.append(('lfsys', '', '', _cur[-1]))
                    else:
                        ret.append(_cur)

                elif gsepback:
                    if gsepback in pathsep:
                        ret.append(_cur)
                    else:
                        if _cur[3]:
                            ret[-1] = list(ret[-1])
                            kargs['apppre'] = apppre
                            ret[-1][-1] += gsepback + \
                                join_apppathx_entry(
                                    _cur, **kargs)
                            ret[-1] = tuple(list(ret[-1]))
                else:
                    ret.append(_cur)

                if ret:
                    if g == 52:
                        #
                        # *** URI
                        #
                        if not os.path.isabs(ret[-1][3]):
                            raise FileSysObjectsError("requires absolute path, got: " + str(ret[-1][3]))

                        kargs['apppre'] = False
                        #kargs['spf'] = 'posix'
                        kargs['tpf'] = 'uri'
                        kargs['keepsep'] = kargs.get('keepsep', True)
                        if kargs.get('strip', True):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2], normpathx(
                                ret[-1][3], **kargs), ret[-1][4])

                        if kargs.get('escape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2], escapepathx(
                                ret[-1][3], **kargs), ret[-1][4])
                        elif kargs.get('unescape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2], unescapepathx(
                                ret[-1][3], **kargs), ret[-1][4])

                        if strict:  # validate
                            # app should be inherently by re
                            _validate(_cur[-2])

                    elif g in (4, 12, 20, 28, ):
                        #
                        #*** file-URI, SMB/CIFS
                        #
                        if kargs.get('apppre'):
                            # create applicationscheme, thus uri with shlashes only
                            _tpf_old = kargs.get('tpf')
                            kargs['tpf'] = 'uri'
                            kargs['apppre'] = False

                        elif kargs.get('tpf'):
                            # a tpf provided
                            _tpf_old = None
                            pass

                        else:
                            # no tpf provided
                            _tpf_old = None
                            kargs['tpf'] = RTE

                        if _normpathx:
                            if ret[-1][3][:2] in (
                                    '//',
                                    '\\\\', ) and (ret[-1][1] or ret[-1][2]):
                                _np = normpathx(ret[-1][3][1:], **kargs)

                            else:
                                _np = normpathx(ret[-1][3], **kargs)
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2], _np)

                        if kargs.get('escape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2],
                                       escapepathx(ret[-1][3], **kargs))
                        elif kargs.get('unescape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2],
                                       unescapepathx(ret[-1][3], **kargs))

                        if strict:  # validate
                            # app should be inherently by re
                            _validate(_cur[-1])

                        if _tpf_old:
                            kargs['tpf'] = _tpf_old

                    else:
                        if _normpathx:
                            if ret[-1][3][:2] in (
                                    '//',
                                    '\\\\', ) and (ret[-1][1] or ret[-1][2]):
                                # network share UNC/POSIX-app
                                _np = normpathx(ret[-1][3][1:], **kargs)
                            else:
                                # source is ordinary local file
                                _np = normpathx(ret[-1][3], **kargs)
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2], _np)

                        if kargs.get('escape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2],
                                       escapepathx(ret[-1][3], **kargs))
                        elif kargs.get('unescape'):
                            ret[-1] = (ret[-1][0], ret[-1][1], ret[-1][2],
                                       unescapepathx(ret[-1][3], **kargs))

                        if strict:  # validate
                            # app should be inherently by re
                            _validate(_cur[-1])

                break

        if apppre and ret[-1][3][0] != tsep[0]:
            raise PathError("requires absolute path, got: " + str(ret[-1][3]))

        if _delnulpsep and ret:
            for l in reversed(ret):
                if l[-1] or l[-2]:
                    break
                ret.pop()

    #* the case empty group regexpr at the end of the search path else complicates the regexpr at all
    # empty group at the end of string
    if not _delnulpsep and i.end(g - 1) < len(
            i.string) and i.string[-1] in pathsep:
        if raw:
            ret.append(('raw', '', '', ''))
        elif rtype:
            ret.append(('', '', '', ''))
        else:
            ret.append(('lfsys', '', '', ''))

    if not appsplit:  # ordinary pathsplit
        _ret = []
        if raw:
            _ret = map(lambda x: x[3], ret)
        else:  # expects 'rtype', and valid entries
            for r in ret:
                if not os.path.abspath(r[3]):
                    raise FileSysObjectsError("requires absolute path, got: " + str(r[3]))
                _ret.append(join_apppathx_entry(r, tpf=tpf, apppre=apppre))
        return _ret

    return ret


def splitapppathx_getlocalpath(elems, **kargs):
    """Joins application elements to a path for local access.

    Args:
        **elems**:
            Elements as provided by 'splitapppathx'

        kargs:
            **tpf**:
                Target platform for the file pathname, for details refer to
                :ref:`normpathx() <def_normpathx>`.

    Returns:
        When successful the local access path, else None.

    Raises:
        AppPathError
        passed through exceptions
    """
    _tpf = kargs.get('tpf', False)
    if _tpf in (
            'Windows',
            'win', ):
        s = '\\'
    elif _tpf == 'posix':
        s = '/'
    else:
        s = os.sep

    ret = ''

    if not elems or type(elems) not in (
            tuple,
            list, ) or len(elems) < 4:
        raise FileSysObjectsError('Incompatible input:' + str(elems))

    if elems[1]:
        if not elems[2]:
            if RTE & RTE_WIN32 and elems[0].upper() not in ('smb', 'share'):
                raise AppPathError("Missing share name for start=" +
                                       str(elems))
        ret += 2 * s + elems[1]

    if elems and len(elems) > 1:
        if elems[1]:
            ret += s + elems[2]
        else:
            ret += elems[2]

    if elems and ret:
        if (elems[1] or elems[2]) and elems[3][0] not in ('/', '\\', os.sep):
            ret += s + elems[3]
        else:
            ret += elems[3]
    elif elems:
        ret += elems[3]

    return ret


def gettop_from_pathstring(spath, plist=None, **kargs):
    """Searches for a partial path *spath* in a list of
    search paths *plist*. The current version supports for
    pure in-memory evaluation of literals and regexpr.

    The following example of input with default parameters ::

       spath := 'a/b/c'
       plist := [
          '/my/path/a/c/x/y/a/b/x/d/e/r',
          '/my/path/a/c/x/y/a/b/c/d/e/r',
       ]

    results in the first match: ::

       result := '/my/path/a/c/x/y/a/b/c'
       #
       # shortes top-match: '/my/path/a/c/x/y/' + 'a/b/c'
       #

    Same for a regular expression: ::

       spath := 'a/.*/[cxy]/[def]{1}'

    results again in: ::

       result := '/my/path/a/c/x/y/a/b/c'

    The match is performed by the re module based on *re.split*
    spanning multiple directories in case of reguar expressions.
    Thus the expressions require some caution when constraints
    are required. The function itself serves as a framework
    providing parameterization of the match criteria.

    Args:
        **spath**:
            A path to be appended to an item from 'plist'.

        **plist**:
            List of search strings to be extended
            by the subpath *spath*.

            default := sys.path

        kargs:
            **hook**:
                Returns the insertion point of *spath* without
                the *spath* itself.

            **keepsep**:
                Keeps significant separators, e.g. a trailing 'os.sep'.

                default := True

            **matchidx**:
                Use the n-th match only. ::

                   matchidx := #idx:

                   0 <= idx   ; first match

                * Ignore matches for '*< #idx*'
                * return match for '*== #idx*', and stop search

                default := 0 # first match

                Depends on *reverse*.

            **pathsep**:
                Replaces the path separator set by the *spf*. ::

                    pathsep := (';' | ':')

            **pattern**:
                Sets and activates scope and type of match pattern.
                The pattern is matched node-by-node for each
                corresponding directory level: ::

                   pattern := (literal | regexpr)

                * *literal*:

                  Match literally. Pattern are treated as characters.

                * *regexpr*:

                  Match regular expression for individual nodes,
                  implies no contained reserved characters of the
                  current file system, so 'os.sep' and no 'os.pathsep'.

                default := literal

            **raw**:
                Suppress normalization by call of
                'os.path.normpath'.

            **reverse**:
                This reverses the resulting search order
                from bottom-up to top-down. Takes effect on
                'redundant' only.

            **split**:
                Returns the path prefix matched on the search list,
                and the relative sub path outside the search list
                as a tuple. ::

                   split := (True | False)

                For example the input: ::

                   spath := 'a/b/c'
                   plist := [
                      '/my/path/a/c/x/y/a/b/x/d/e/r',
                      '/my/path/a/c/x/y/a/b/c/d/e/r',
                   ]

                results in: ::

                   result := ('/my/path/a/c/x/y', 'a/b/c')

            **spf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to API in the manual at :ref:`spf <OPTS_SPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.getspf() <paths.html#getspf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.

            **tpf**:
                Source platform, defines the input syntax domain.
                For the syntax refer to the API in the manual at :ref:`tpf <OPTS_TPF>`.

                For additi0onal details refer to
                :ref:`tpf and spf <TPF_AND_SPF>`,
                `paths.gettpf() <paths.html#gettpf>`_,
                :ref:`normapppathx() <def_normapppathx>`,
                `normpathx() <paths.html#normpathx>`_.


    Returns:
        When successful returns by default the expanded pathname, else None.
        The return value in case of success depends on the parameters:

        * if *split == True*: returns a *tuple* ::

             result = (result[0], result[1],)

          result[0]: The matched path including the resolved searched sub-path *spath*.

          result[1]: The remainder.

        * if *split == True* and *hook == True*: returns a *tuple* ::

             result = (result[0], result[1], result[2],)

          result[0]: The matched path excluding the sub-path *spath*.

          result[1]: The resolved searched sub-path *spath*.

          result[2]: The remainder.

        * else: returns an *str* ::

             result = <str>

          * if *hook == True*:

            The matched path excluding the sub-path *spath* and an
            evtl. present remainder.

          * else:

            A string of the matched path including the resolved
            searched sub-path *spath*, excluding an evtl. present
            remainder.

    Raises:
        AppPathError

        passed-through

    """
    if plist == None:
        plist = sys.path
    elif type(plist) != list:
        raise AppPathError("Requires list argument:" + str(plist))

    try:
        _hook = kargs.pop('hook')
    except KeyError:
        _hook = False
    try:
        _rev = kargs.pop('reverse')
    except KeyError:
        _rev = False
    try:
        _split = kargs.pop('split')
    except KeyError:
        _split = False

    try:
        matchidx = kargs.pop('matchidx')
        if not type(matchidx) is int or matchidx < 0:
            raise AppPathError("Requires int>0 matchidx=" + str(matchidx))
    except KeyError:
        matchidx = 1

    try:
        _pat = kargs.pop('pattern')
        if _pat == 'regexpr':
            _pat = 1
        #
        # reminder: glob not supported for current release, requires an offline interpreter
        # elif v == 'glob':
        #     _pat = 2
        elif _pat == 'literal':
            _pat = 0
        else:
            raise FileSysObjectsError("gettop_from_pathstring:parameter not supported: pattern = " + str(_pat))
    except KeyError:
        _pat = 0

    raw = kargs.get('raw', False)
    _keepsep = kargs.get('keepsep', True)

    #
    # get spf
    #
    _spf = kargs.get('spf', False)
    ssep, spsep, _spf, _input = getspf(_spf)  # @UnusedVariable
    pathsep = kargs.get('pathsep', spsep)

    #
    # get tpf
    #
    _tpf = kargs.get('tpf', False)
    tsep, tpsep, _tpf, _output, _apre = gettpf(_tpf)  # @UnusedVariable


    def _comp(p, b):
        """match regexpr
        p: regexpr
        b: item plist[i][j]
        """
        if _pat == 1: # regexpr
            if p == '*':  # assume a glob expression, thus terminate 're' processing now
                return
            pc = re.compile(r'^' + p + r'$')
            px = pc.match(b)
            if px:
                return px.string[px.start():px.end()]

        elif _pat == 0:  # literal
            if p == b:
                return b

    # define processed portion, save prefix for later prepend on result
    if not raw:
        # For *spath*, is relative thus cannot be http/https
        #
        #   _rtype   type
        #   _host:   host
        #   _share:  share
        #   sp:      local path
        #
        _rtype, _host, _share, sp = splitapppathx(
            spath, appsplit=True, spf=_spf, tpf=_tpf, rtype=True, keepsep=_keepsep)[0]
        _app_prefix = _rtype + _host
        if _share:
            if _app_prefix:
                _app_prefix += ssep + _share
            else:
                _app_prefix = _share

    else:
        _app_prefix, sp = '', spath

    # normalize search path
    #
    # For *spath*
    #
    #   _sp_elems[0]   type
    #   _sp_elems[1]:  host
    #   _sp_elems[2]:  share
    #   _sp_elems[3]:  local path
    #
    kargs['keepsep'] =  _keepsep
    _sp_elems = splitapppathx(sp, appsplit=True, **kargs)[0]
    sp = splitapppathx_getlocalpath(_sp_elems, tpf=_tpf)
    sp_tmp = sp

    sp = sp.split(ssep)
    if sp and sp[-1] == '':
        sp = sp[:-1]

    # TODO:
    if sp and sp[0] == '':  # is @root
        if len(sp) > 1:
            _contained = False
            for cx in plist:  # initially check anchor
                # TODO:
                # _cxe = splitapppathx(cx, **kargs)[0]
                _cxe = splitapppathx_getlocalpath(_sp_elems, tpf=_tpf)
                if _cxe.startswith(ssep + sp[1]):
                    _contained = True
            if not _contained:
                return None
            sp = sp[1:]

    _maxmatch = 0  # the maximum length of match that actually occured
    _currentmatch = []  # stack of current last list

    si0 = -1
    if _rev:
        _pl = reversed(plist[:])
    else:
        _pl = plist

    for sl in _pl:
        # scan each path of pathlist(plist) for sp(spath) item-by-item
        si0 += 1
        _matchidx = matchidx  # count the present matches for each search path from plist

        if not sl:
            continue

        if not raw:  # canonical
            # manage app paths - current network only
            _elements = splitapppathx(
                sl, appsplit=True, spf=_spf, tpf=_tpf, rtype=True)[0]
            try:
                _prtype, _phost, _pshare, sl, _qufrag = _elements  # for http/https
            except Exception:
                _prtype, _phost, _pshare, sl = _elements  # for file systems

            _prefix = _prtype + _phost

            if _pshare:
                if _prefix:
                    _prefix += tsep + _pshare
                else:
                    _prefix = _pshare

            if _app_prefix:
                if _app_prefix != _prefix:
                    continue

        else:
            _prefix = ''


        # split the pattern
        s = re.split(escapepathx(sp_tmp, _spf), sl)
        if len(s) < 2 or len(s) -1 < matchidx:
            # no match
            continue
            # return None

        lx = int((len(sl) - len(''.join(s))) / (len(s) -1))
        sp_resolved = sl[len(s[0]):len(s[0])+lx]
        if sp_resolved[-1] not in ('/', '\\'):
            for x in range(1,len(s)):
                if s[x] and s[x][0] not in ('/', '\\'):
                    s[x] = tsep + s[x]
        _r = _prefix

        # join as much as requested by matchidx
        if _rev:
            lx = len(s) - matchidx #- (s[-1] == '')
            if lx <= 0 or lx > len(s):
                return None
            _j = sp_resolved.join(s[:lx])

        else:
            if matchidx < 1 or matchidx > len(s):
                return None
            if matchidx == len(s):
                lx = matchidx - (s[-1] == '')
            else:
                lx = matchidx
            if lx == 0:
                _j = s[0]
            else:
                _j = sp_resolved.join(s[:lx])

        if not _hook:
            _j += sp_resolved

        remainder = ''
        if _split:
            if _rev:
                remainder = sp_resolved.join(s[len(s) - matchidx:])
            else:
                remainder = sp_resolved.join(s[matchidx:])
            if remainder[0] in ('/', '\\'):
                remainder = remainder[1:]
            if remainder[-1] in ('/', '\\'):
                remainder = remainder[:-1]

        if _j:
            if _r.startswith('file:'):
                if os.path.isabs(sl):
                    # RFC 8089
                    # add prefix and absolute path
                    _r += _j
                else:
                    raise FileSysObjectsError("RFC8089 Requires absolute path, got: " + str(sl))

            elif _r.startswith('http:'):
                if os.path.isabs(sl):
                    # RFC 3986
                    # add prefix and absolute path
                    _r += _j
                else:
                    raise FileSysObjectsError("RFC3986 Requires absolute path, got: " + str(sl))

            elif _sp_elems[0] is not 'lfsys':
                join_apppathx_entry((_prtype, _phost, _pshare, sl))

            else:
                _r += _j

        if _r[-1] in ('/', '\\'):
            _r = _r[:-1]

        if not _split:
            return _r

        else:
            if _hook:
                return (_r, sp_resolved, remainder)
            else:
                return (_r, remainder)

    return None


def gettop_from_pathstring_iter(spath, plist=None, **kargs):
    """Iterates all matches in plist,see gettop_from_pathstring.
    """
    if plist == None:
        plist = sys.path

    for pl in plist:
        r = gettop_from_pathstring(spath, [pl], **kargs)
        if r:
            yield r

