Source code for filesysobjects.plugins.smb

# -*- 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,
}


[docs]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
[docs]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
[docs]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
[docs]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)
[docs]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
[docs]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 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(tpf, 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
[docs]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
[docs]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
[docs]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