# Module `lib.tools.filesystem`

Miscellaneous path functions

Expand source code

    # Distributed under Pycameresp License
    # Copyright (c) 2023 Remi BERTHOLET
    """ Miscellaneous path functions """
    import os
    import sys
    import time
    import tools.fnmatch

    try:
            import uasyncio
            import uos
    except:
            pass

    def exists(filename):
            """ Test if the filename existing """
            try:
                    _ = os.lstat(filename)
                    return True
            except:
                    try:
                            _ = os.stat(filename)
                            return True
                    except:
                            pass
            return False

    previous_file_info = []
    previous_get = 0
    def fileinfo(path):
            """ Get the file informations """
            global previous_file_info, previous_get
            current_time = time.time()
            if previous_get + 3 < current_time:
                    previous_get = current_time
                    previous_file_info = []
            if len(previous_file_info) == 0:
                    previous_file_info = [path, os.stat(path)]
            elif previous_file_info[0] != path:
                    previous_file_info = [path, os.stat(path)]
            return previous_file_info[1]

    def isdir(path):
            """ Indicates if the path is a directory """
            try:
                    if fileinfo(path)[0] & 0x4000 == 0x4000:
                            return True
            except:
                    pass
            return False

    def isfile(path):
            """ Indicates if the path is a file """
            try:
                    if fileinfo(path)[0] & 0x4000 == 0:
                            return True
            except:
                    pass
            return False

    def filesize(path):
            """ Get the file size """
            info = fileinfo(path)
            return info[6]

    def filetime(path):
            """ Get the file modified time """
            return fileinfo(path)[8]

    def splitext(p):
            """ Split file extension """
            sep='\\'
            altsep = '/'
            extsep = '.'
            sep_index = p.rfind(sep)
            if altsep:
                    altsepIndex = p.rfind(altsep)
                    sep_index = max(sep_index, altsepIndex)

            dot_index = p.rfind(extsep)
            if dot_index > sep_index:
                    filename_index = sep_index + 1
                    while filename_index < dot_index:
                            if p[filename_index:filename_index+1] != extsep:
                                    return p[:dot_index], p[dot_index:]
                            filename_index += 1
            return p, p[:0]

    def split(p):
            """ Split file """
            sep = "/"
            i = p.rfind(sep) + 1
            head, tail = p[:i], p[i:]
            if head and head != sep*len(head):
                    head = head.rstrip(sep)
            return head, tail

    def makedir(directory, recursive=False):
            """ Make directory recursively """
            if recursive is False:
                    os.mkdir(directory)
            else:
                    directories = [directory]
                    while 1:
                            parts = split(directory)
                            if parts[1] == "" or parts[0] == "":
                                    break
                            directories.append(parts[0])
                            directory = parts[0]

                    directories.reverse()
                    for d in directories:
                            if not(exists(d)):
                                    os.mkdir(d)

    def abspath(cwd, payload):
            """ Get the absolute path """
            # Just a few special cases "..", "." and ""
            # If payload start's with /, set cwd to /
            # and consider the remainder a relative path
            if payload.startswith('/'):
                    cwd = "/"
            for token in payload.split("/"):
                    if token == '..':
                            if cwd != '/':
                                    cwd = '/'.join(cwd.split('/')[:-1])
                                    if cwd == '':
                                            cwd = '/'
                    elif token != '.' and token != '':
                            if cwd == '/':
                                    cwd += token
                            else:
                                    cwd = cwd + '/' + token
            return cwd

    def abspathbytes(cwd, payload):
            """ Get the absolute path into a bytes """
            # Just a few special cases "..", "." and ""
            # If payload start's with /, set cwd to /
            # and consider the remainder a relative path
            if payload.startswith(b'/'):
                    cwd = b"/"
            for token in payload.split(b"/"):
                    if token == b'..':
                            if cwd != b'/':
                                    cwd = b'/'.join(cwd.split(b'/')[:-1])
                                    if cwd == b'':
                                            cwd = b'/'
                    elif token != b'.' and token != b'':
                            if cwd == b'/':
                                    cwd += token
                            else:
                                    cwd = cwd + b'/' + token
            return cwd

    def remove(filename):
            """ Remove file existing """
            try:
                    uos.remove(filename)
            except:
                    pass

    def rename(old, new):
            """ Rename file """
            if exists(new):
                    remove(new)
            try:
                    uos.rename(old, new)
            except:
                    pass

    def ismicropython():
            """ Indicates if the python is micropython """
            if sys.implementation.name == "micropython":
                    return True
            return False

    try:
            import uos
            list_directory = uos.ilistdir
    except:
            def list_directory(path_):
                    """ List directory """
                    result = []
                    for filename in os.listdir(path_):
                            fileinfo_ = os.stat(path_ + "/" + filename)
                            typ = fileinfo_[0]
                            size = fileinfo_[6]

                            result.append((filename, typ, 0, size))
                    return result

    def scandir(path, pattern, recursive, displayer=None):
            """ Scan recursively a directory """
            filenames   = []
            directories = []
            if path == "":
                    path = "."
            if path is not None and pattern is not None and exists(path) and isdir(path):
                    for file_info in list_directory(path):
                            name = file_info[0]
                            typ  = file_info[1]
                            if path != "":
                                    filename = path + "/" + name
                            else:
                                    filename = name
                            if sys.platform != "win32":
                                    filename = filename.replace("//","/")
                                    filename = filename.replace("//","/")

                            # if directory
                            if typ & 0xF000 == 0x4000:
                                    if displayer:
                                            displayer.show(filename)
                                    else:
                                            directories.append(filename)
                                    if recursive:
                                            dirs,fils = scandir(filename, pattern, recursive, displayer)
                                            filenames += fils
                                            directories += dirs
                            else:
                                    if tools.fnmatch.fnmatch(name, pattern):
                                            if displayer:
                                                    displayer.show(filename)
                                                    filenames = [""]
                                            else:
                                                    filenames.append(filename)
            return directories, filenames

    async def ascandir(path, pattern, recursive, displayer=None):
            """ Asynchronous scan recursively a directory """
            filenames   = []
            directories = []
            if path == "":
                    path = "."
            if path is not None and pattern is not None and exists(path) and isdir(path):
                    for file_info in list_directory(path):
                            name = file_info[0]
                            typ  = file_info[1]
                            if path != "":
                                    filename = path + "/" + name
                            else:
                                    filename = name
                            if sys.platform != "win32":
                                    filename = filename.replace("//","/")
                                    filename = filename.replace("//","/")

                            # if directory
                            if typ & 0xF000 == 0x4000:
                                    if displayer:
                                            displayer.show(filename)
                                    else:
                                            directories.append(filename)
                                    if recursive:
                                            dirs,fils = await ascandir(filename, pattern, recursive, displayer)
                                            filenames += fils
                                            directories += dirs
                            else:
                                    if tools.fnmatch.fnmatch(name, pattern):
                                            if displayer:
                                                    displayer.show(filename)
                                                    filenames = [""]
                                            else:
                                                    filenames.append(filename)
                    if ismicropython():
                            await uasyncio.sleep_ms(3)
            return directories, filenames

    def prefix(files):
            """ Get the common prefix of all files """
            # Initializes counters
            counters = []

            # For all files
            for file in files:
                    if type(file) == type(""):
                            file = file.encode("utf8")
                    # file = file.encode("utf8")
                    # Split the file name into a piece
                    paths = file.split(b"/")

                    # For each piece
                    length = len(paths)
                    for i in range(0,length):
                            try:
                                    try:
                                            # Test if counters exist
                                            counters[i][paths[i]] += 1
                                    except:
                                            # Creates a path counters
                                            counters[i][paths[i]] = 1
                            except:
                                    # Adds a new level of depth
                                    counters.append({paths[i] : 1})

            # Constructs the prefix of the list of files
            try:
                    result = b""
                    amount = list(counters[0].values())[0]
                    for counter in counters:
                            if len(tuple(counter.keys())) == 1 and list(counter.values())[0] == amount:
                                    result += list(counter.keys())[0] + b"/"
                            else:
                                    return result [:-1]
                    return result
            except IndexError:
                    return b""

    def normpath(path):
            # Extract from https://github.com/python/cpython/blob/main/Lib/posixpath.py
            """Normalize path, eliminating double slashes, etc."""
            # path = os.fspath(path)
            if isinstance(path, bytes):
                    sep = b'/'
                    empty = b''
                    dot = b'.'
                    dotdot = b'..'
            else:
                    sep = '/'
                    empty = ''
                    dot = '.'
                    dotdot = '..'
            if path == empty:
                    return dot
            initial_slashes = path.startswith(sep)
            # POSIX allows one or two initial slashes, but treats three or more
            # as single slash.
            # (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
            if (initial_slashes and
                    path.startswith(sep*2) and not path.startswith(sep*3)):
                    initial_slashes = 2
            comps = path.split(sep)
            new_comps = []
            for comp in comps:
                    if comp in (empty, dot):
                            continue
                    if (comp != dotdot or (not initial_slashes and not new_comps) or
                                    (new_comps and new_comps[-1] == dotdot)):
                            new_comps.append(comp)
                    elif new_comps:
                            new_comps.pop()
            comps = new_comps
            path = sep.join(comps)
            if initial_slashes:
                    path = sep*initial_slashes + path
            return path or dot

## Functions

`def abspath(cwd, payload)`

  Get the absolute path

  Expand source code

      def abspath(cwd, payload):
              """ Get the absolute path """
              # Just a few special cases "..", "." and ""
              # If payload start's with /, set cwd to /
              # and consider the remainder a relative path
              if payload.startswith('/'):
                      cwd = "/"
              for token in payload.split("/"):
                      if token == '..':
                              if cwd != '/':
                                      cwd = '/'.join(cwd.split('/')[:-1])
                                      if cwd == '':
                                              cwd = '/'
                      elif token != '.' and token != '':
                              if cwd == '/':
                                      cwd += token
                              else:
                                      cwd = cwd + '/' + token
              return cwd

`def abspathbytes(cwd, payload)`

  Get the absolute path into a bytes

  Expand source code

      def abspathbytes(cwd, payload):
              """ Get the absolute path into a bytes """
              # Just a few special cases "..", "." and ""
              # If payload start's with /, set cwd to /
              # and consider the remainder a relative path
              if payload.startswith(b'/'):
                      cwd = b"/"
              for token in payload.split(b"/"):
                      if token == b'..':
                              if cwd != b'/':
                                      cwd = b'/'.join(cwd.split(b'/')[:-1])
                                      if cwd == b'':
                                              cwd = b'/'
                      elif token != b'.' and token != b'':
                              if cwd == b'/':
                                      cwd += token
                              else:
                                      cwd = cwd + b'/' + token
              return cwd

`async def ascandir(path, pattern, recursive, displayer=None)`

  Asynchronous scan recursively a directory

  Expand source code

      async def ascandir(path, pattern, recursive, displayer=None):
              """ Asynchronous scan recursively a directory """
              filenames   = []
              directories = []
              if path == "":
                      path = "."
              if path is not None and pattern is not None and exists(path) and isdir(path):
                      for file_info in list_directory(path):
                              name = file_info[0]
                              typ  = file_info[1]
                              if path != "":
                                      filename = path + "/" + name
                              else:
                                      filename = name
                              if sys.platform != "win32":
                                      filename = filename.replace("//","/")
                                      filename = filename.replace("//","/")

                              # if directory
                              if typ & 0xF000 == 0x4000:
                                      if displayer:
                                              displayer.show(filename)
                                      else:
                                              directories.append(filename)
                                      if recursive:
                                              dirs,fils = await ascandir(filename, pattern, recursive, displayer)
                                              filenames += fils
                                              directories += dirs
                              else:
                                      if tools.fnmatch.fnmatch(name, pattern):
                                              if displayer:
                                                      displayer.show(filename)
                                                      filenames = [""]
                                              else:
                                                      filenames.append(filename)
                      if ismicropython():
                              await uasyncio.sleep_ms(3)
              return directories, filenames

`def exists(filename)`

  Test if the filename existing

  Expand source code

      def exists(filename):
              """ Test if the filename existing """
              try:
                      _ = os.lstat(filename)
                      return True
              except:
                      try:
                              _ = os.stat(filename)
                              return True
                      except:
                              pass
              return False

`def fileinfo(path)`

  Get the file informations

  Expand source code

      def fileinfo(path):
              """ Get the file informations """
              global previous_file_info, previous_get
              current_time = time.time()
              if previous_get + 3 < current_time:
                      previous_get = current_time
                      previous_file_info = []
              if len(previous_file_info) == 0:
                      previous_file_info = [path, os.stat(path)]
              elif previous_file_info[0] != path:
                      previous_file_info = [path, os.stat(path)]
              return previous_file_info[1]

`def filesize(path)`

  Get the file size

  Expand source code

      def filesize(path):
              """ Get the file size """
              info = fileinfo(path)
              return info[6]

`def filetime(path)`

  Get the file modified time

  Expand source code

      def filetime(path):
              """ Get the file modified time """
              return fileinfo(path)[8]

`def isdir(path)`

  Indicates if the path is a directory

  Expand source code

      def isdir(path):
              """ Indicates if the path is a directory """
              try:
                      if fileinfo(path)[0] & 0x4000 == 0x4000:
                              return True
              except:
                      pass
              return False

`def isfile(path)`

  Indicates if the path is a file

  Expand source code

      def isfile(path):
              """ Indicates if the path is a file """
              try:
                      if fileinfo(path)[0] & 0x4000 == 0:
                              return True
              except:
                      pass
              return False

`def ismicropython()`

  Indicates if the python is micropython

  Expand source code

      def ismicropython():
              """ Indicates if the python is micropython """
              if sys.implementation.name == "micropython":
                      return True
              return False

`def makedir(directory, recursive=False)`

  Make directory recursively

  Expand source code

      def makedir(directory, recursive=False):
              """ Make directory recursively """
              if recursive is False:
                      os.mkdir(directory)
              else:
                      directories = [directory]
                      while 1:
                              parts = split(directory)
                              if parts[1] == "" or parts[0] == "":
                                      break
                              directories.append(parts[0])
                              directory = parts[0]

                      directories.reverse()
                      for d in directories:
                              if not(exists(d)):
                                      os.mkdir(d)

`def normpath(path)`

  Normalize path, eliminating double slashes, etc.

  Expand source code

      def normpath(path):
              # Extract from https://github.com/python/cpython/blob/main/Lib/posixpath.py
              """Normalize path, eliminating double slashes, etc."""
              # path = os.fspath(path)
              if isinstance(path, bytes):
                      sep = b'/'
                      empty = b''
                      dot = b'.'
                      dotdot = b'..'
              else:
                      sep = '/'
                      empty = ''
                      dot = '.'
                      dotdot = '..'
              if path == empty:
                      return dot
              initial_slashes = path.startswith(sep)
              # POSIX allows one or two initial slashes, but treats three or more
              # as single slash.
              # (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
              if (initial_slashes and
                      path.startswith(sep*2) and not path.startswith(sep*3)):
                      initial_slashes = 2
              comps = path.split(sep)
              new_comps = []
              for comp in comps:
                      if comp in (empty, dot):
                              continue
                      if (comp != dotdot or (not initial_slashes and not new_comps) or
                                      (new_comps and new_comps[-1] == dotdot)):
                              new_comps.append(comp)
                      elif new_comps:
                              new_comps.pop()
              comps = new_comps
              path = sep.join(comps)
              if initial_slashes:
                      path = sep*initial_slashes + path
              return path or dot

`def prefix(files)`

  Get the common prefix of all files

  Expand source code

      def prefix(files):
              """ Get the common prefix of all files """
              # Initializes counters
              counters = []

              # For all files
              for file in files:
                      if type(file) == type(""):
                              file = file.encode("utf8")
                      # file = file.encode("utf8")
                      # Split the file name into a piece
                      paths = file.split(b"/")

                      # For each piece
                      length = len(paths)
                      for i in range(0,length):
                              try:
                                      try:
                                              # Test if counters exist
                                              counters[i][paths[i]] += 1
                                      except:
                                              # Creates a path counters
                                              counters[i][paths[i]] = 1
                              except:
                                      # Adds a new level of depth
                                      counters.append({paths[i] : 1})

              # Constructs the prefix of the list of files
              try:
                      result = b""
                      amount = list(counters[0].values())[0]
                      for counter in counters:
                              if len(tuple(counter.keys())) == 1 and list(counter.values())[0] == amount:
                                      result += list(counter.keys())[0] + b"/"
                              else:
                                      return result [:-1]
                      return result
              except IndexError:
                      return b""

`def remove(filename)`

  Remove file existing

  Expand source code

      def remove(filename):
              """ Remove file existing """
              try:
                      uos.remove(filename)
              except:
                      pass

`def rename(old, new)`

  Rename file

  Expand source code

      def rename(old, new):
              """ Rename file """
              if exists(new):
                      remove(new)
              try:
                      uos.rename(old, new)
              except:
                      pass

`def scandir(path, pattern, recursive, displayer=None)`

  Scan recursively a directory

  Expand source code

      def scandir(path, pattern, recursive, displayer=None):
              """ Scan recursively a directory """
              filenames   = []
              directories = []
              if path == "":
                      path = "."
              if path is not None and pattern is not None and exists(path) and isdir(path):
                      for file_info in list_directory(path):
                              name = file_info[0]
                              typ  = file_info[1]
                              if path != "":
                                      filename = path + "/" + name
                              else:
                                      filename = name
                              if sys.platform != "win32":
                                      filename = filename.replace("//","/")
                                      filename = filename.replace("//","/")

                              # if directory
                              if typ & 0xF000 == 0x4000:
                                      if displayer:
                                              displayer.show(filename)
                                      else:
                                              directories.append(filename)
                                      if recursive:
                                              dirs,fils = scandir(filename, pattern, recursive, displayer)
                                              filenames += fils
                                              directories += dirs
                              else:
                                      if tools.fnmatch.fnmatch(name, pattern):
                                              if displayer:
                                                      displayer.show(filename)
                                                      filenames = [""]
                                              else:
                                                      filenames.append(filename)
              return directories, filenames

`def split(p)`

  Split file

  Expand source code

      def split(p):
              """ Split file """
              sep = "/"
              i = p.rfind(sep) + 1
              head, tail = p[:i], p[i:]
              if head and head != sep*len(head):
                      head = head.rstrip(sep)
              return head, tail

`def splitext(p)`

  Split file extension

  Expand source code

      def splitext(p):
              """ Split file extension """
              sep='\\'
              altsep = '/'
              extsep = '.'
              sep_index = p.rfind(sep)
              if altsep:
                      altsepIndex = p.rfind(altsep)
                      sep_index = max(sep_index, altsepIndex)

              dot_index = p.rfind(extsep)
              if dot_index > sep_index:
                      filename_index = sep_index + 1
                      while filename_index < dot_index:
                              if p[filename_index:filename_index+1] != extsep:
                                      return p[:dot_index], p[dot_index:]
                              filename_index += 1
              return p, p[:0]