first commit
This commit is contained in:
commit
2d682a6115
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2020 eli2and40
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,26 @@
|
||||||
|
# sl4ng
|
||||||
|
|
||||||
|
This package serves as a workflow booster for emailing, iterating, and a veritable smorgasboard of cases scattered in between.
|
||||||
|
|
||||||
|
You want persistant data but Pickle and Dill ask too many damned questions?
|
||||||
|
sl4ng.persistance.save
|
||||||
|
sl4ng.persistance.load
|
||||||
|
|
||||||
|
You be writing generators so nice that you want to use them twice, thrice, or indefinitely? We gotchu!
|
||||||
|
sl4ng.types.regenerator
|
||||||
|
|
||||||
|
You're on the brink of the creative breakthrough of the ages but you'll end it all in a fit of rage if you accidentally overwite your projects ever again? We gotchu!
|
||||||
|
sl4ng.files.paths.namespacer
|
||||||
|
|
||||||
|
You want to look at an object's source code but inspect.getsource makes a mess of rendering things in your REPL, or perhaps you want to jump straight into its package-folder or source-file?
|
||||||
|
sl4ng.debug.getsource
|
||||||
|
sl4ng.debug.pop
|
||||||
|
|
||||||
|
You want to want to see if your iterable is the codomain of a constant function? We gotchu
|
||||||
|
sl4ng.functional.eq
|
||||||
|
|
||||||
|
You really like your dictionary, but it's got some duplicate values? We.. Got.. Chu!
|
||||||
|
sl4ng.iteration.deduplicate
|
||||||
|
|
||||||
|
You've read this far and think "Damn, son, this package looks diggity fresh, but some of those functions are deeply nested"? We gotchu
|
||||||
|
Everything is imported to init
|
Binary file not shown.
|
@ -0,0 +1,605 @@
|
||||||
|
"""
|
||||||
|
File management wrappers on os.path, shutil, and useful non-standard modules
|
||||||
|
|
||||||
|
"Apparent drive" refers to the term before the first os.sep in an object's given path.
|
||||||
|
if the given path is relative
|
||||||
|
then the ADrive may be ".." or the name of the File/Directory
|
||||||
|
else
|
||||||
|
the drive is the name/letter of the disk partition on which the content at the address is stored.
|
||||||
|
"Apparent Directory" similarly refers first term before the last os.sep in an object's given path
|
||||||
|
if the given path is relative
|
||||||
|
then the ADir may be ".." or the name of the File/Directory
|
||||||
|
else
|
||||||
|
the drive is the name/letter of the disk partition on which the content at the address is stored.
|
||||||
|
|
||||||
|
|
||||||
|
TODO
|
||||||
|
Add relative path support
|
||||||
|
Classes for specific mimes
|
||||||
|
A version of @cached_property which checks the date modified before determining whether or not to recompute
|
||||||
|
Caches for new directories which include objects to add to them upon creation
|
||||||
|
Strict searching
|
||||||
|
Caches for pickling (backup simplification)
|
||||||
|
Remove Size from repr for Directories. Large ones take too long to initialize
|
||||||
|
"""
|
||||||
|
__all__ = 'Address Directory File'.split()
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
from typing import Iterable, List, Dict
|
||||||
|
import os, re, sys, shutil
|
||||||
|
|
||||||
|
import filetype as ft, audio_metadata as am
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
|
generator = type(i for i in range(0))
|
||||||
|
function = type(ft.match)
|
||||||
|
|
||||||
|
formats = {
|
||||||
|
'pics': "bmp png jpg jpeg tiff".split(),
|
||||||
|
'music': "mp3 m4a wav ogg wma flac aiff alac".split(),
|
||||||
|
'videos': "mp4 wmv".split(),
|
||||||
|
'docs': "doc docx pdf xlsx pptx ppt xls csv".split(),
|
||||||
|
}
|
||||||
|
formats['all'] = [*chain.from_iterable(formats.values())]
|
||||||
|
|
||||||
|
class MemorySize(int):
|
||||||
|
"""
|
||||||
|
Why should you have to sacrifice utility for readability?
|
||||||
|
"""
|
||||||
|
def __repr__(self):
|
||||||
|
return nice_size(self)
|
||||||
|
|
||||||
|
class Address:
|
||||||
|
"""
|
||||||
|
Base class for a non-descript path-string.
|
||||||
|
Relative paths are not currently supported
|
||||||
|
Methods return self unless otherwise stated
|
||||||
|
"""
|
||||||
|
def __init__(self, path:str):
|
||||||
|
self.path = path = normalize(path)
|
||||||
|
# if not os.path.exists(path):
|
||||||
|
# print(Warning(f'Warning: Path "{path}" does not exist'))
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.path)
|
||||||
|
def __repr__(self):
|
||||||
|
# return self.path
|
||||||
|
# tipo = "Directory File".split()[self.isfile]
|
||||||
|
return f"{tipo(self)}(name={self.name}, up={self.up.name})"
|
||||||
|
|
||||||
|
def create(self, content:[str, bytes, bytearray]=None, raw:bool=False):
|
||||||
|
"""
|
||||||
|
Create an entry in the file-system. If the address is not vacant no action will be taken.
|
||||||
|
The raw handle only works for Files and enables writing bytes/bytearrays
|
||||||
|
"""
|
||||||
|
if self.exists:
|
||||||
|
return self
|
||||||
|
elif content:
|
||||||
|
os.makedirs(self.up.path, exist_ok=True)
|
||||||
|
if raw:
|
||||||
|
if isinstance(content, (bytes, bytearray)):
|
||||||
|
fobj = open(self.path, 'wb')
|
||||||
|
else:
|
||||||
|
fobj = open(self.path, 'w')
|
||||||
|
fobj.write(content)
|
||||||
|
fobj.close()
|
||||||
|
else:
|
||||||
|
if isinstance(content, str):
|
||||||
|
content = bytes(content, encoding='utf-8')
|
||||||
|
else:
|
||||||
|
content = bytes(content)
|
||||||
|
with open(self.path, 'wb') as fobj:
|
||||||
|
fobj.write(content)
|
||||||
|
else:
|
||||||
|
os.makedirs(self.up.path, exist_ok=True)
|
||||||
|
if likefile(self.path):
|
||||||
|
with open(self.path, 'x') as fobj:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.makedirs(self.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
@property
|
||||||
|
def isdir(self):
|
||||||
|
if self.exists:
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
elif type(self) == type(Directory('.')):
|
||||||
|
return True
|
||||||
|
elif type(self) == type(File('a')):
|
||||||
|
return False
|
||||||
|
return not likefile(self.path)
|
||||||
|
@property
|
||||||
|
def isfile(self):
|
||||||
|
if self.exists:
|
||||||
|
return os.path.isfile(self.path)
|
||||||
|
elif type(self) == type(File('a')):
|
||||||
|
return True
|
||||||
|
elif type(self) == type(Directory('.')):
|
||||||
|
return False
|
||||||
|
return likefile(self.path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def obj(self):
|
||||||
|
"""
|
||||||
|
Determine if self.path points to a file or folder and create the corresponding object
|
||||||
|
"""
|
||||||
|
if self.exists:
|
||||||
|
return File(self.path) if os.path.isfile(self.path) else Directory(self.path)
|
||||||
|
else:
|
||||||
|
return File(self.path) if likefile(self.path) else Directory(self.path)
|
||||||
|
@property
|
||||||
|
def up(self):
|
||||||
|
"""
|
||||||
|
Return the ADir
|
||||||
|
"""
|
||||||
|
return Address(delevel(self.path)).obj
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
Return the name of the referent
|
||||||
|
"""
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
"""
|
||||||
|
Return consecutive ADirs until the ADrive is reached
|
||||||
|
"""
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(Address(i).obj for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def colleagues(self):
|
||||||
|
"""
|
||||||
|
Every member of the same Directory whose type is the same as the referent
|
||||||
|
"""
|
||||||
|
return (i for i in self.up if isinstance(i, type(self)))
|
||||||
|
@property
|
||||||
|
def neighbours(self):
|
||||||
|
"""
|
||||||
|
Everything in the same Directory
|
||||||
|
"""
|
||||||
|
return self.up.content
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
"""
|
||||||
|
Number of ancestors
|
||||||
|
"""
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def top(self):
|
||||||
|
"""
|
||||||
|
The apparent drive. Will not be helpful if self.path is relative
|
||||||
|
"""
|
||||||
|
return self.ancestors[0]
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
|
||||||
|
def delevel(self, steps:int=1, path:bool=False) -> str:
|
||||||
|
"""
|
||||||
|
Go up some number of levels in the File system
|
||||||
|
"""
|
||||||
|
return delevel(self.path, steps) if path else Directory(delevel(self.path, steps))
|
||||||
|
@property
|
||||||
|
def ancestry(self):
|
||||||
|
"""
|
||||||
|
A fancy representation of the tree from the apparent drive up to the given path
|
||||||
|
"""
|
||||||
|
print(f'ancestry({self.name})')
|
||||||
|
ancs = list(self.ancestors[1:])
|
||||||
|
# ancs = self.ancestors[1:]
|
||||||
|
ancs.append(self.path)
|
||||||
|
# print(ancs)
|
||||||
|
for i, anc in enumerate(ancs):
|
||||||
|
print('\t' + ('', '.'*i)[i>0] + i*' ' + [i for i in str(anc).split(os.sep) if i][-1] + '/')
|
||||||
|
return self
|
||||||
|
def touch(self):
|
||||||
|
"""
|
||||||
|
Implements the unix command 'touch', which updates the 'date modified' of the content at the path
|
||||||
|
"""
|
||||||
|
p = self.path
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = Address(p).obj
|
||||||
|
return self
|
||||||
|
def erase(self, recycle:bool=True):
|
||||||
|
"""
|
||||||
|
Send a File to the trash, or remove it without recycling.
|
||||||
|
"""
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
return self
|
||||||
|
def clone(self, folder:str=None, name:str=None, cwd:bool=False, sep:str='_', touch=False):
|
||||||
|
"""
|
||||||
|
Returns a clone of the referent at a given Directory-path
|
||||||
|
The given path will be created if it doesn't exist
|
||||||
|
Will copy in the File's original folder if no path is given
|
||||||
|
The cwd switch will always copy to the current working Directory
|
||||||
|
"""
|
||||||
|
copier = (shutil.copy2, shutil.copytree)[self.isdir]
|
||||||
|
if cwd:
|
||||||
|
new = os.path.join(os.getcwd(), name if name else self.name)
|
||||||
|
elif folder:
|
||||||
|
new = os.path.join(folder, name if name else self.name)
|
||||||
|
else:
|
||||||
|
new = self.path
|
||||||
|
new = nameSpacer(new, sep=sep)
|
||||||
|
os.makedirs(delevel(new), exist_ok=True)
|
||||||
|
copier(self.path, new)
|
||||||
|
out = Address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
def move(self, folder:str=None, name:str=None, dodge:bool=False, sep:str='_', recycle:bool=True):
|
||||||
|
"""
|
||||||
|
addy.move(folder) -> move to the given Directory ()
|
||||||
|
addy.move(name) -> move to the given path (relative paths will follow from the objects existing path)
|
||||||
|
addy.move(folder, name) -> move to the given Directory
|
||||||
|
|
||||||
|
:param dodge:
|
||||||
|
enables automatic evasion of file-system collisions
|
||||||
|
:param sep:
|
||||||
|
chooses the separator you use between the object's name and numerical affix in your directory's namespace
|
||||||
|
**inert if dodge is not True
|
||||||
|
:param recycle:
|
||||||
|
enables you to avoid the PermissionError raised by os.remove (if you have send2trash installed)
|
||||||
|
** the PermissionError is due to the object being in use at the time of attempted deletion
|
||||||
|
"""
|
||||||
|
if folder and name:
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
new = os.path.join(folder, name)
|
||||||
|
elif folder:
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
new = os.path.join(folder, self.name)
|
||||||
|
elif name:
|
||||||
|
new = os.path.join(self.up.path, name)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'The file couldn\'t be moved because {name=} and {folder=}. Set either one or both')
|
||||||
|
new = nameSpacer(new, sep=sep) if dodge else new
|
||||||
|
folder, name = os.path.split(new)
|
||||||
|
self.clone(folder=folder, name=name)
|
||||||
|
# os.remove(self.path)
|
||||||
|
self.erase()
|
||||||
|
self.path = new
|
||||||
|
return self
|
||||||
|
def rename(self, name:str):
|
||||||
|
"""
|
||||||
|
Change the name of the referent
|
||||||
|
"""
|
||||||
|
return self.move(name=name)
|
||||||
|
def expose(self):
|
||||||
|
"""
|
||||||
|
Reveal the referent in the system's file explorer (will open the containing Directory if the referent is a File)
|
||||||
|
"""
|
||||||
|
if self.isdir:
|
||||||
|
os.startfile(self.path)
|
||||||
|
else:
|
||||||
|
os.startfile(self.up.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class File(Address):
|
||||||
|
"""
|
||||||
|
Create a new File object for context management and ordinary operations
|
||||||
|
"""
|
||||||
|
def __init__(self, path:str='NewFile'):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
super(File, self).__init__(path)
|
||||||
|
self._stream = None
|
||||||
|
def __enter__(self):
|
||||||
|
return self.open()
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.close()
|
||||||
|
def __repr__(self):
|
||||||
|
# return self.path
|
||||||
|
# tipo = "Directory File".split()[self.isfile]
|
||||||
|
return f"{tipo(self)}(name={self.name}, up={self.up.name}, size={self.size})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return MemorySize(os.stat(self.path).st_size)
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
return match.MIME if (match:=ft.guess(self.path)) else None
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return self.mime.split('/')[0] if (match:=ft.guess(self.path)) else None
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return os.path.splitext(self.name)[1]
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
"""
|
||||||
|
return the File's name without the extension
|
||||||
|
"""
|
||||||
|
return os.path.splitext(self.name)[0]
|
||||||
|
def open(self, mode:str='r', scrape:bool=False):
|
||||||
|
"""
|
||||||
|
Return the File's byte or text stream.
|
||||||
|
Scrape splits the text at all whitespace and returns the content as a string
|
||||||
|
"""
|
||||||
|
if scrape:
|
||||||
|
with open(self.path, mode) as fobj:
|
||||||
|
return ' '.join(fobj.read().split())
|
||||||
|
with open(self.path, mode) as fobj:
|
||||||
|
self._stream = fobj
|
||||||
|
return self._stream
|
||||||
|
def close(self):
|
||||||
|
if self._stream:
|
||||||
|
self._stream.close()
|
||||||
|
return self
|
||||||
|
@property
|
||||||
|
def run(self):
|
||||||
|
os.startfile(self.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class Items:
|
||||||
|
"""
|
||||||
|
A wrapper on a directory's content which makes it easier to access by turning elements into attributes
|
||||||
|
"""
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = normalize(path)
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return Directory(self.path)[attr]
|
||||||
|
|
||||||
|
|
||||||
|
class Directory(Address):
|
||||||
|
"""
|
||||||
|
Directory('.') == Directory(os.getcwd())
|
||||||
|
"""
|
||||||
|
def __init__(self, path:str='NewDirectory'):
|
||||||
|
if path=='.':
|
||||||
|
path = os.getcwd()
|
||||||
|
elif path == 'NewDirectory':
|
||||||
|
path = nameSpacer(path)
|
||||||
|
elif path == '~':
|
||||||
|
path = os.path.expanduser(path)
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
self.path = normalize(path)
|
||||||
|
super(Directory, self).__init__(path)
|
||||||
|
self.index = -1
|
||||||
|
def __repr__(self):
|
||||||
|
# return self.path
|
||||||
|
# tipo = "Directory File".split()[self.isfile]
|
||||||
|
return f"{tipo(self)}(name={self.name}, up={self.up.name})"
|
||||||
|
def __len__(self):
|
||||||
|
return len(os.listdir(self.path))
|
||||||
|
def __bool__(self):
|
||||||
|
"""
|
||||||
|
Check if the Directory is empty or not
|
||||||
|
"""
|
||||||
|
return len(os.listdir(self.path)) > 0
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
def __next__(self):
|
||||||
|
if self.index<len(self)-1:
|
||||||
|
self.index += 1
|
||||||
|
return self.content[self.index]
|
||||||
|
self.index = -1
|
||||||
|
raise StopIteration
|
||||||
|
def __getitem__(self, item:str):
|
||||||
|
"""
|
||||||
|
Return an object whose name is an exact match for the given item
|
||||||
|
"""
|
||||||
|
if any(re.search(f'^{item}$', i.name, re.I) for i in self.content):
|
||||||
|
return Address(os.path.join(self.path, item)).obj
|
||||||
|
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||||
|
def __truediv__(self, other:str):
|
||||||
|
if isinstance(other, str):
|
||||||
|
return Address(os.path.join(self.path, other)).obj
|
||||||
|
raise TypeError(f"Other must be a string")
|
||||||
|
def __call__(self, keyword:str, sort:bool=False, case:bool=False, **kwargs) -> Iterable:
|
||||||
|
"""
|
||||||
|
See help(self.search)
|
||||||
|
"""
|
||||||
|
return self.search(keyword, sort, case, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def items(self):
|
||||||
|
"""
|
||||||
|
This extension allows you to call folder contents as if they were attributes.
|
||||||
|
Will not work if your file system does not use a python-viable naming convention
|
||||||
|
example:
|
||||||
|
>>> folder.items.subfolder
|
||||||
|
"""
|
||||||
|
return Items(self.path)
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for directories
|
||||||
|
"""
|
||||||
|
return (addy.obj for i in os.listdir(self.path) if (addy:=Address(os.path.join(self.path, i))).isdir)
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for Files
|
||||||
|
"""
|
||||||
|
return (addy.obj for i in os.listdir(self.path) if (addy:=Address(os.path.join(self.path, i))).isfile)
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
"""
|
||||||
|
Return address-like objects from "os.listdir"
|
||||||
|
"""
|
||||||
|
return tuple(Address(os.path.join(self.path, i)).obj for i in os.listdir(self.path))
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
"""
|
||||||
|
Return All Files from all branches
|
||||||
|
"""
|
||||||
|
# return tuple(self.gather())
|
||||||
|
return map(lambda x: Address(x).obj, self.gather())
|
||||||
|
@property
|
||||||
|
def branches(self):
|
||||||
|
"""
|
||||||
|
Return Every Directory whose path contains "self.path"
|
||||||
|
"""
|
||||||
|
return tuple(set(File(i).delevel() for i in self.gather()))
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
"""
|
||||||
|
Return the sum of sizes of all files in self and branches
|
||||||
|
"""
|
||||||
|
return MemorySize(sum(file.size for file in self.leaves))
|
||||||
|
@property
|
||||||
|
def mimes(self):
|
||||||
|
"""
|
||||||
|
Return File mimes for all Files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(file.mime for file in self.gather()))
|
||||||
|
@property
|
||||||
|
def kinds(self):
|
||||||
|
"""
|
||||||
|
Return File types for all Files from branches
|
||||||
|
"""
|
||||||
|
return tuple(set(m.split('/')[0] for m in self.mime))
|
||||||
|
@property
|
||||||
|
def exts(self):
|
||||||
|
"""
|
||||||
|
Return extensions for all Files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(f.ext for f in self.gather()))
|
||||||
|
@property
|
||||||
|
def isroot(self):
|
||||||
|
"""
|
||||||
|
Return check if the Directory is at the highest level of the File system
|
||||||
|
"""
|
||||||
|
return not self.depth
|
||||||
|
|
||||||
|
def add(self, other:Address, copy:bool=False):
|
||||||
|
"""
|
||||||
|
Introduce new elements. Send an address-like object to self.
|
||||||
|
"""
|
||||||
|
if not self.exists:
|
||||||
|
raise OSError(f"Cannot add to Directory({self.name}) because it doesn't exist")
|
||||||
|
elif not other.exists:
|
||||||
|
if issubclass(type(other), Address):
|
||||||
|
tipo = 'Directory File'.split()[other.isfile]
|
||||||
|
raise OSError(f"{other.name.title()} could not be added to {self.name} because it doesn't exist")
|
||||||
|
new = os.path.join(self.path, os.path.split(other.path)[-1])
|
||||||
|
other.clone(folder=self.up.path) if copy else other.rename(new)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def enter(self):
|
||||||
|
"""
|
||||||
|
Set referent as current working Directory
|
||||||
|
"""
|
||||||
|
os.chdir(self.path)
|
||||||
|
|
||||||
|
def gather(self, titles:bool=False, walk:bool=True, ext:str='') -> generator:
|
||||||
|
"""
|
||||||
|
Generate an iterable of the files rooted in a given folder. The results will be strings, not File objects
|
||||||
|
It is possible to search for multiple File extensions if you separate each one with a space, comma, asterisk, or tilde.
|
||||||
|
Only use one symbol per gathering though.
|
||||||
|
|
||||||
|
:param titles: if you only want to know the names of the files gathered, not their full paths
|
||||||
|
:param walk: if you want to recursively scan subdiretories
|
||||||
|
:param ext: if you want to filter for particular extensions
|
||||||
|
"""
|
||||||
|
folder = self.path
|
||||||
|
if walk:
|
||||||
|
if ext:
|
||||||
|
ext = ext.replace('.', '')
|
||||||
|
sep = [i for i in ',`* ' if i in ext]
|
||||||
|
pattern = '|'.join(f'\.{i}$' for i in ext.split(sep[0] if sep else None))
|
||||||
|
pat = re.compile(pattern, re.I)
|
||||||
|
for root, folders, names in os.walk(folder):
|
||||||
|
for name in names:
|
||||||
|
if os.path.isfile(p:=os.path.join(root, name)) and pat.search(name) and name!='NTUSER.DAT':
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
for root, folders, names in os.walk(folder):
|
||||||
|
for name in names:
|
||||||
|
if os.path.exists(p:=os.path.join(root, name)):
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
if ext:
|
||||||
|
ext = ext.replace('.', '')
|
||||||
|
sep = [i for i in ',`* ' if i in ext]
|
||||||
|
pattern = '|'.join(f'\.{i}$' for i in ext.split(sep[0] if sep else None))
|
||||||
|
pat = re.compile(pattern, re.I)
|
||||||
|
for name in os.listdir(folder):
|
||||||
|
if os.path.isfile(p:=os.path.join(folder, name)) and pat.search(name) and name!='NTUSER.DAT':
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
for name in os.listdir(folder):
|
||||||
|
if os.path.isfile(p:=os.path.join(folder, name)):
|
||||||
|
yield name if titles else p
|
||||||
|
|
||||||
|
def search(self, keyword:str, sort:bool=False, case:bool=False, prescape:bool=False, strict:bool=True, **kwargs) -> Iterable:
|
||||||
|
"""
|
||||||
|
Return an iterator of Files whose path match the given keyword within a Directory.
|
||||||
|
The search is linear and the sorting is based on the number of matches. If sorted, a list will be returned.
|
||||||
|
Case pertains to case-sensitivity
|
||||||
|
Prescape informs the method that kewords do not need to be escaped
|
||||||
|
For kwargs see help(self.gather)
|
||||||
|
|
||||||
|
:param keyword: terms you wish to match, separated by spaces
|
||||||
|
:param sort: if you would like to sort the results by number of matches
|
||||||
|
:param case: if you would like to make the search case sensitive
|
||||||
|
:param prescape: if you have already re.escape-d the terms you would like to search
|
||||||
|
:param strict: if you only want to see results which match every term in the keyword string
|
||||||
|
"""
|
||||||
|
casesensitivity = (re.I, 0)[case]
|
||||||
|
escaper = (re.escape, lambda x: x)[prescape]
|
||||||
|
if isinstance(keyword, str):
|
||||||
|
keyword = keyword.split()
|
||||||
|
if not isinstance(keyword, str):
|
||||||
|
keyword = '|'.join(map(escaper, keyword))
|
||||||
|
if strict:
|
||||||
|
return filter(
|
||||||
|
lambda x: len([*re.finditer(keyword, x, casesensitivity)]) >= len(keyword.split('|')),
|
||||||
|
self.gather(**kwargs),
|
||||||
|
)
|
||||||
|
elif sort:
|
||||||
|
return sorted(
|
||||||
|
filter(
|
||||||
|
lambda x: len([*re.finditer(keyword, x, casesensitivity)]) == len(keyword.split('|')),
|
||||||
|
self.gather(**kwargs),
|
||||||
|
),
|
||||||
|
key=lambda x: len([*re.finditer(keyword, x, casesensitivity)]),
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return filter(
|
||||||
|
lambda x: re.search(keyword, x, casesensitivity),
|
||||||
|
self.gather(**kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# class Archive(Address):
|
||||||
|
# def __init__(self, path:str='NewFile'):
|
||||||
|
# path = os.path.abspath(trim(path))
|
||||||
|
# super(File, self).__init__(path)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
show(locals().keys())
|
||||||
|
|
||||||
|
|
||||||
|
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||||
|
dp = r'c:\users\kenneth\videos'
|
||||||
|
|
||||||
|
d = Directory(dp)
|
||||||
|
f = File(fp)
|
||||||
|
tf = File('testfile.ext')
|
||||||
|
td = Directory('testdir')
|
||||||
|
|
||||||
|
system = (d, f)
|
||||||
|
# show(d('slowthai', sort=True))
|
||||||
|
# show(d('alix melanie', sort=True))
|
||||||
|
# show(d('melanie alix', sort=True))
|
||||||
|
|
||||||
|
# print(formats['all'])
|
||||||
|
# for v in formats.values():
|
||||||
|
# print(' '.join(v))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
cfg = configparser.ConfigParser()
|
||||||
|
# cfg[]
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
pass
|
|
@ -0,0 +1,380 @@
|
||||||
|
"""
|
||||||
|
Implementation of a rudimentary file system
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
grab the
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys, shutil, pathlib
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import filetype as ft
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
|
from sl4ng import show, delevel, gather, nameSpacer
|
||||||
|
|
||||||
|
def trim(path,edge=os.sep):
|
||||||
|
out = path[:]
|
||||||
|
while out.startswith(edge):
|
||||||
|
out = out[1:]
|
||||||
|
while out.endswith(edge):
|
||||||
|
out = out[:-1]
|
||||||
|
print(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
class size(int):
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{round(self*10**-3):,} kb'
|
||||||
|
|
||||||
|
class _path(str):
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class address:
|
||||||
|
"""
|
||||||
|
A systemic pointer to the location of the data associated with a file system object
|
||||||
|
"""
|
||||||
|
# def init(self,path:str):
|
||||||
|
# assert os.path.exists(self.path), f'"{path}" is not a valid address on this system'
|
||||||
|
# self.path = path
|
||||||
|
path:str
|
||||||
|
# if path:
|
||||||
|
# assert os.path.exists(self.path), f'"{path}" is not a valid address on this system'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
@property
|
||||||
|
def isdir(self):
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
@property
|
||||||
|
def isfile(self):
|
||||||
|
return os.path.isfile(self.path)
|
||||||
|
@property
|
||||||
|
def obj(self):
|
||||||
|
if self.isdir:
|
||||||
|
return directory(self.path)
|
||||||
|
elif self.isfile:
|
||||||
|
return file(self.path)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'"{self.path}" is not a valid address on this system')
|
||||||
|
if exists:
|
||||||
|
# del isfile,isdir
|
||||||
|
( , )
|
||||||
|
|
||||||
|
|
||||||
|
class directory:
|
||||||
|
def __init__(self,path:str):
|
||||||
|
assert address(path).isdir, f'"{path}" is not a directory'
|
||||||
|
self.path = trim(path)
|
||||||
|
self._ind = -1
|
||||||
|
cntnt = (address(os.path.join(path,i)) for i in os.listdir(path))
|
||||||
|
for i in cntnt:
|
||||||
|
name = os.path.split(i.path)[1]
|
||||||
|
if i.isdir:
|
||||||
|
setattr(self,name,i.path)
|
||||||
|
elif i.isfile:
|
||||||
|
setattr(self,os.path.splitext(name)[0],i.path)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(str(self),i))).isdir)
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(self.path,i))).isfile)
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
return tuple(address(os.path.join(self.path,i).obj for i in os.listdir(self.path)))
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
return tuple(self.gather())
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
return tuple(set(i.container() for i in self.gather()))
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(directory(i) for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
return self.ancestors[0]#.split(':')[0]
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
# return size(sum(os.stat(file).st_size for file in self.gather()))
|
||||||
|
return size(sum(file.size for file in self.leaves))
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
# return tuple(set(match.MIME if (match:=ft.guess(file)) else 'UNKNOWN' for file in self.gather()))
|
||||||
|
return tuple(set(file.mime for file in self.gather()))
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return tuple(set(m.split('/')[0] for m in self.mime))
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return tuple(set(f.ext for f in self.gather()))
|
||||||
|
@property
|
||||||
|
def siblings(self):
|
||||||
|
return tuple(i for i in self.container() if isinstance(i,type(self)))
|
||||||
|
@property
|
||||||
|
def peers(self):
|
||||||
|
return self.container().content
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
|
||||||
|
def touch(self):
|
||||||
|
p = str(self)
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = address(p).obj
|
||||||
|
return self
|
||||||
|
def delete(self,recycle=True):
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
del self
|
||||||
|
def rename(self,new,relative=False):
|
||||||
|
new = new if not relative else os.path.join(delevel(self.path),new)
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
os.rename(self.path,new)
|
||||||
|
self = address(new).obj
|
||||||
|
return self
|
||||||
|
def clone(self,new=None,relative=False,touch=False):
|
||||||
|
if new:
|
||||||
|
if relative:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),new))
|
||||||
|
else:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),self.name))
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
shutil.copy2(self.path,new) #if touch else shutil.copy2(self.path,new)
|
||||||
|
out = address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
def container(self,steps=1,path=False):
|
||||||
|
return delevel(self.path,steps) if path else directory(delevel(self.path,steps))
|
||||||
|
def heritage(self):
|
||||||
|
print(f'\nheritage({self.name.title()})')
|
||||||
|
ancs = list(self.ancestors)
|
||||||
|
ancs.append(self.path)
|
||||||
|
for i,anc in enumerate(ancs):
|
||||||
|
print('\t'+('','.'*i)[i>0]+i*' '+[i for i in str(anc).split(os.sep) if i][-1])
|
||||||
|
|
||||||
|
def show(self, indentation:int=1, enum:bool=False, start:int=1, indentor:str='\t'):
|
||||||
|
assert indentation>0, f'"{indentation}" is not a viable indentation level'
|
||||||
|
print((indentation-1)*'\t'+self.name)
|
||||||
|
show(self.content,indentation,enum,start,indentor)
|
||||||
|
def gather(self,names:bool=False,walk:bool=True,ext:str=None,paths=False):
|
||||||
|
if paths:
|
||||||
|
yield from gather(str(self),names,walk,ext)
|
||||||
|
else:
|
||||||
|
for path in gather(str(self),names,walk,ext):
|
||||||
|
yield file(path)
|
||||||
|
def add(self,new):
|
||||||
|
if (obj:=address(new).obj):
|
||||||
|
return obj.move(self)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'"{new}" does is neither a file or directory')
|
||||||
|
|
||||||
|
def moveContent(self,other):
|
||||||
|
# assert address(trim(str(other))).isdir, f'"{other}" is not a viable directory here'
|
||||||
|
assert address((str(other))).isdir, f'"{other}" is not a viable directory here'
|
||||||
|
for f in self.gather():
|
||||||
|
# rest = f.path[len(self.path)+1:]
|
||||||
|
ending = trim(f.path[len(self.path)+1:])
|
||||||
|
new = os.path.join(trim(str(other)),ending)
|
||||||
|
# os.makedirs(delevel(new))
|
||||||
|
print(self)
|
||||||
|
print(f)
|
||||||
|
print(ending)
|
||||||
|
print(new)
|
||||||
|
print()
|
||||||
|
|
||||||
|
def isroot(self):
|
||||||
|
return not self.depth
|
||||||
|
def __bool__(self):
|
||||||
|
return len(os.listdir(self.path))>0
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.content)
|
||||||
|
def __next__(self):
|
||||||
|
if self._ind<len(self)-1:
|
||||||
|
self._ind += 1
|
||||||
|
return self.content[self._ind]
|
||||||
|
self._ind = -1
|
||||||
|
raise StopIteration
|
||||||
|
def __getitem__(self,item):
|
||||||
|
if item in self.content:
|
||||||
|
return address(os.path.join(self.path,item)).obj
|
||||||
|
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||||
|
|
||||||
|
move = rename
|
||||||
|
extension = ext
|
||||||
|
copy = clone
|
||||||
|
|
||||||
|
class file:
|
||||||
|
def __init__(self,path:str):
|
||||||
|
assert address(path).isfile, f'"{path}" is not a file'
|
||||||
|
self.path = trim(path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
return os.path.splitext(self.name)[0]
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(directory(i) for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
return self.ancestors[0]#.split(':')[0]
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return size(os.stat(str(self)).st_size)
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
return match.MIME if (match:=ft.guess(str(self))) else 'UNKNOWN'
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return self.mime.split('/')[0] if (match:=ft.guess(str(self))) else 'UNKNOWN'
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return os.path.splitext(self.name)[1]
|
||||||
|
@property
|
||||||
|
def siblings(self):
|
||||||
|
return tuple(i for i in self.container() if isinstance(i,type(self)))
|
||||||
|
@property
|
||||||
|
def peers(self):
|
||||||
|
return self.container().content
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
|
||||||
|
def touch(self):
|
||||||
|
p = str(self)
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = address(p).obj
|
||||||
|
return self
|
||||||
|
def delete(self,recycle=True):
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
del self
|
||||||
|
def rename(self,new,relative=False):
|
||||||
|
new = new if not relative else os.path.join(delevel(self.path),new)
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
os.rename(self.path,new)
|
||||||
|
self = address(new).obj
|
||||||
|
return self
|
||||||
|
def clone(self,new=None,relative=False,touch=False):
|
||||||
|
if new:
|
||||||
|
if relative:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),new))
|
||||||
|
else:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),self.name))
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
shutil.copy2(self.path,new) #if touch else shutil.copy2(self.path,new)
|
||||||
|
out = address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
def container(self,steps=1,path=False):
|
||||||
|
return delevel(self.path,steps) if path else directory(delevel(self.path,steps))
|
||||||
|
def heritage(self):
|
||||||
|
print(f'\nheritage({self.name.title()})')
|
||||||
|
ancs = list(self.ancestors)
|
||||||
|
ancs.append(self.path)
|
||||||
|
for i,anc in enumerate(ancs):
|
||||||
|
print('\t'+('','.'*i)[i>0]+i*' '+[i for i in str(anc).split(os.sep) if i][-1])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
move = rename
|
||||||
|
extension = ext
|
||||||
|
copy = clone
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
dp = r'c:\users\kenneth\videos'
|
||||||
|
# print(directory(dp))
|
||||||
|
# show(directory(dp).fils,)
|
||||||
|
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||||
|
# print(os.path.isfile(fp))
|
||||||
|
# print(file(fp))
|
||||||
|
fp = r'C:\Users\Kenneth\Music\Collection\slowthai\The Bottom _ North Nights\02 North Nights.mp3'
|
||||||
|
dp = delevel(fp,1)
|
||||||
|
|
||||||
|
d = address(dp).obj
|
||||||
|
print(d)
|
||||||
|
# d.show(1)
|
||||||
|
f = address(fp).obj
|
||||||
|
print(f)
|
||||||
|
|
||||||
|
system = (d,f)
|
||||||
|
print(system)
|
||||||
|
print([i.name for i in system])
|
||||||
|
print([i.depth for i in system])
|
||||||
|
print([i.ancestors for i in system])
|
||||||
|
print([i.heritage() for i in system])
|
||||||
|
print([i.root for i in system])
|
||||||
|
print([i.size for i in system])
|
||||||
|
[print(i.size) for i in system]
|
||||||
|
[print(i.kind) for i in system]
|
||||||
|
[print(i.ext) for i in system]
|
||||||
|
[print(i.stat) for i in system]
|
||||||
|
print(d.leaves)
|
||||||
|
|
||||||
|
# d.moveContent(r'c:\users\kenneth\downloads\\')
|
||||||
|
# print(f.clone(touch=True))
|
||||||
|
# print(f.clone())
|
||||||
|
# print(f.move(nameSpacer(str(f))))
|
||||||
|
# os.startfile(f.container().path)
|
||||||
|
# help(f)
|
||||||
|
# for i in d:
|
||||||
|
# ob = d[i]
|
||||||
|
# print(ob,ob.kind,ob.ext)
|
||||||
|
# if isinstance(ob,directory):
|
||||||
|
# for o in ob.children:
|
||||||
|
# o2 = ob[o]
|
||||||
|
# print('\t',o2,o2.kind,o2.ext)
|
||||||
|
|
||||||
|
# for p in d.gather():
|
||||||
|
# print(p.size,p.mime,p.ext,sys.getrefcount(p))
|
||||||
|
# f = file(p)
|
||||||
|
# if 'matroska' in f.mime:
|
||||||
|
# print(f.container())
|
||||||
|
# print(f.name)
|
||||||
|
# print(f.size)
|
||||||
|
|
||||||
|
|
||||||
|
# for i in d:print(i)
|
||||||
|
# print(os.stat(dp).st_size)
|
||||||
|
# print(os.stat(fp).st_size)
|
||||||
|
# print(dict(d))
|
||||||
|
# d.heritage()
|
||||||
|
# os.rename('Few Nolder','randombumbashit')
|
||||||
|
# print(os.listdir('randombumbashit'))
|
|
@ -0,0 +1,366 @@
|
||||||
|
"""
|
||||||
|
Implementation of a rudimentary file system
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
grab the
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys, shutil, pathlib, re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import filetype as ft
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
|
from sl4ng import show, delevel, gather, nameSpacer, ffplay, commons, nopes
|
||||||
|
# from magnitudes import rep
|
||||||
|
from .magnitudes import represent# as rp
|
||||||
|
# from magnitudes import *
|
||||||
|
|
||||||
|
forbiddenChars = r'\/:?*<>|"'
|
||||||
|
|
||||||
|
formats = {
|
||||||
|
'pics':['bmp','png','jpg','jpeg','tiff',],
|
||||||
|
'music':['mp3','m4a','wav','ogg','wma','flac','aiff','alac',],
|
||||||
|
'videos':['mp4','wmv',],
|
||||||
|
'docs':['doc','docx','pdf','xlsx','pptx','ppt','xls','csv',],
|
||||||
|
}
|
||||||
|
|
||||||
|
orders = {
|
||||||
|
1:'deca',
|
||||||
|
2:'hecto',
|
||||||
|
3:'kilo',
|
||||||
|
6:'mega',
|
||||||
|
9:'giga',
|
||||||
|
12:'tera',
|
||||||
|
15:'peta',
|
||||||
|
18:'exa',
|
||||||
|
21:'zetta',
|
||||||
|
24:'yotta'
|
||||||
|
}
|
||||||
|
|
||||||
|
def trim(path,edge=os.sep):
|
||||||
|
out = path[:]
|
||||||
|
while out.startswith(edge):
|
||||||
|
out = out[1:]
|
||||||
|
while out.endswith(edge):
|
||||||
|
out = out[:-1]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# @dataclass
|
||||||
|
# class size:
|
||||||
|
# val:int
|
||||||
|
class size(int):
|
||||||
|
# def __repr__(self):
|
||||||
|
# rep = round(self*10**-3)
|
||||||
|
# if len(st)
|
||||||
|
# return f'{round(self*10**-3):,} kb'
|
||||||
|
def __repr__(self):
|
||||||
|
return rp(self)
|
||||||
|
# def __str__(self):
|
||||||
|
# return str(self.val)
|
||||||
|
# def __add__(self,other):
|
||||||
|
# return self.val + size(other)
|
||||||
|
# def __truediv__(self,other):
|
||||||
|
# return self.val + size(other)
|
||||||
|
# def __sub__(self,other):
|
||||||
|
# return self.val + size(other)
|
||||||
|
# def __mul__(self,other):
|
||||||
|
# return self.val + size(other)
|
||||||
|
# pass
|
||||||
|
# def __str__(self):
|
||||||
|
# return str(int(self))
|
||||||
|
# def __repr__(self,dim='bytes',long=False,lower=False,precision=2,sep='-'):
|
||||||
|
# orders = {
|
||||||
|
# 3:'kilo',
|
||||||
|
# 6:'mega',
|
||||||
|
# 9:'giga',
|
||||||
|
# 12:'tera',
|
||||||
|
# 15:'peta',
|
||||||
|
# 18:'exa',
|
||||||
|
# 21:'zetta',
|
||||||
|
# 24:'yotta',
|
||||||
|
# }
|
||||||
|
|
||||||
|
# sredro = {v:k for k,v in orders.items()}
|
||||||
|
|
||||||
|
# pretty = lambda number,unit='': f'{number:,} {unit}'.strip()
|
||||||
|
# setcase = lambda unit,lower=False: [unit.upper().strip(),unit.lower().strip()][lower]
|
||||||
|
# setlength = lambda mag,dim,long=False,sep='-': ('',sep)[long].join(((mag[0],dim[0]),(mag,dim))[long])
|
||||||
|
|
||||||
|
|
||||||
|
# mags = tuple(sorted(orders.keys()))
|
||||||
|
# booly = lambda i: len(str(int(self))) < len(str(10**mags[i+1]))
|
||||||
|
# fits = tuple(nopes((booly(i) for i in range(len(mags)-1)),True))
|
||||||
|
# fits = tuple(filter(booly,range(len(mags)-1)))
|
||||||
|
# mag = orders[mags[min(fits) if fits else len(mags)-1]]
|
||||||
|
# unit = setcase(setlength(mag,dim,long,['',sep][long]),lower)
|
||||||
|
# number = round(self*10**-sredro[mag],precision)
|
||||||
|
# return pretty(number,unit).lower() if lower else pretty(number,unit).upper()
|
||||||
|
@dataclass
|
||||||
|
class _pathLike:
|
||||||
|
path:str
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(str(self))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
@property
|
||||||
|
def up(self):
|
||||||
|
return address(delevel(str(self))).obj
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
return os.path.splitext(self.name)[0]
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(address(i).obj for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def siblings(self):
|
||||||
|
# return tuple(i for i in self.delevel() if isinstance(i,type(self)))
|
||||||
|
return tuple(i for i in self.up if isinstance(i,type(self)))
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
return self.ancestors[0]#.split(':')[0]
|
||||||
|
@property
|
||||||
|
def peers(self):
|
||||||
|
return self.delevel().content
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
"""
|
||||||
|
return os.stat(str(self))
|
||||||
|
|
||||||
|
def delevel(self,steps=1,path=False):
|
||||||
|
return delevel(self.path,steps) if path else directory(delevel(self.path,steps))
|
||||||
|
def heritage(self):
|
||||||
|
print(f'\nheritage({self.name.title()})')
|
||||||
|
ancs = list(self.ancestors)
|
||||||
|
ancs.append(self.path)
|
||||||
|
for i,anc in enumerate(ancs):
|
||||||
|
print('\t'+('','.'*i)[i>0]+i*' '+[i for i in str(anc).split(os.sep) if i][-1])
|
||||||
|
|
||||||
|
def touch(self):
|
||||||
|
p = str(self)
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = address(p).obj
|
||||||
|
return self
|
||||||
|
def delete(self,recycle=True):
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
del self
|
||||||
|
def rename(self,new,relative=False):
|
||||||
|
new = new if not relative else os.path.join(delevel(self.path),new)
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
os.rename(self.path,new)
|
||||||
|
self = address(new).obj
|
||||||
|
return self
|
||||||
|
def clone(self,new=None,relative=False,touch=False):
|
||||||
|
if new:
|
||||||
|
if relative:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),new))
|
||||||
|
else:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path),self.name))
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
shutil.copy2(self.path,new) #if touch else shutil.copy2(self.path,new)
|
||||||
|
out = address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
|
||||||
|
move = rename
|
||||||
|
copy = clone
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class address(_pathLike):
|
||||||
|
"""
|
||||||
|
A systemic pointer to the location of the data associated with a file system object
|
||||||
|
"""
|
||||||
|
def __init__(self,path:str):
|
||||||
|
super(address,self).__init__(path)
|
||||||
|
# assert os.path.exists(path), f'"{path}" is not a valid address on this system'
|
||||||
|
assert self.exists, f'"{path}" is not a valid address on this system'
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isdir(self):
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
@property
|
||||||
|
def isfile(self):
|
||||||
|
return os.path.isfile(self.path)
|
||||||
|
@property
|
||||||
|
def obj(self):
|
||||||
|
if self.isdir:
|
||||||
|
return directory(self.path)
|
||||||
|
elif self.isfile:
|
||||||
|
return file(self.path)
|
||||||
|
|
||||||
|
def expose(self):
|
||||||
|
os.startfile(self.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
# class directory(_pathLike):
|
||||||
|
class directory(address):
|
||||||
|
def __init__(self,path:str):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
assert address(path)#.isdir, f'"{path}" is not a directory'
|
||||||
|
super(directory,self).__init__(path)
|
||||||
|
self._ind = -1
|
||||||
|
# self.
|
||||||
|
def __bool__(self):
|
||||||
|
return len(os.listdir(self.path))>0
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.content)
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
def __next__(self):
|
||||||
|
if self._ind<len(self)-1:
|
||||||
|
self._ind += 1
|
||||||
|
return self.content[self._ind]
|
||||||
|
self._ind = -1
|
||||||
|
raise StopIteration
|
||||||
|
def __getitem__(self,item):
|
||||||
|
if any(re.search(f'^{item}$',i.name,re.I) for i in self.content):
|
||||||
|
# for i in nopes((re.search(f'^{item}$',i.name,re.I) for i in self.content),True):
|
||||||
|
return address(os.path.join(self.path,item)).obj
|
||||||
|
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(str(self),i))).isdir)
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(self.path,i))).isfile)
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
return tuple(address(os.path.join(self.path,i)).obj for i in os.listdir(self.path))
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
return tuple(self.gather())
|
||||||
|
@property
|
||||||
|
def branches(self):
|
||||||
|
return tuple(set(i.delevel() for i in self.gather()))
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return size(sum(file.size for file in self.leaves))
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
return tuple(set(file.mime for file in self.gather()))
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return tuple(set(m.split('/')[0] for m in self.mime))
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return tuple(set(f.ext for f in self.gather()))
|
||||||
|
@property
|
||||||
|
def isroot(self):
|
||||||
|
return not self.depth
|
||||||
|
|
||||||
|
def enter(self):
|
||||||
|
os.chdir(self.path)
|
||||||
|
def gather(self,names:bool=False,walk:bool=True,ext:str=None,paths=False):
|
||||||
|
if paths:
|
||||||
|
yield from gather(str(self),names,walk,ext)
|
||||||
|
else:
|
||||||
|
yield from set(file(path) for path in gather(str(self),names,walk,ext))
|
||||||
|
# for path in gather(str(self),names,walk,ext):
|
||||||
|
# yield file(path)
|
||||||
|
def add(self,new):
|
||||||
|
if (obj:=address(new).obj):
|
||||||
|
return obj.move(self)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'"{new}" does is neither a file or directory')
|
||||||
|
|
||||||
|
def moveContent(self,other):
|
||||||
|
# assert address(trim(str(other))).isdir, f'"{other}" is not a viable directory here'
|
||||||
|
assert address((str(other))).isdir, f'"{other}" is not a viable directory here'
|
||||||
|
for f in self.gather():
|
||||||
|
# rest = f.path[len(self.path)+1:]
|
||||||
|
ending = trim(f.path[len(self.path)+1:])
|
||||||
|
new = os.path.join(trim(str(other)),ending)
|
||||||
|
# os.makedirs(delevel(new))
|
||||||
|
print(self)
|
||||||
|
print(f)
|
||||||
|
print(ending)
|
||||||
|
print(new)
|
||||||
|
print()
|
||||||
|
def show(self, indentation:int=1, enum:bool=False, start:int=1, indentor:str='\t'):
|
||||||
|
assert indentation>0, f'"{indentation}" is not a viable indentation level'
|
||||||
|
print((indentation-1)*'\t'+self.name)
|
||||||
|
show(self.content,indentation,enum,start,indentor)
|
||||||
|
|
||||||
|
extension = ext
|
||||||
|
|
||||||
|
|
||||||
|
class file(_pathLike):
|
||||||
|
def __init__(self,path:str):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
assert address(path)#, f'"{path}" is not a file'
|
||||||
|
super(file,self).__init__(path)
|
||||||
|
self._stream = None
|
||||||
|
def __enter__(self):
|
||||||
|
self._stream = open(str(self),'b')
|
||||||
|
return self
|
||||||
|
def __exit__(self):
|
||||||
|
self._stream.close()
|
||||||
|
self._stream = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return size(os.stat(str(self)).st_size)
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
return match.MIME if (match:=ft.guess(str(self))) else 'UNKNOWN'
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return self.mime.split('/')[0] if (match:=ft.guess(str(self))) else 'UNKNOWN'
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return os.path.splitext(self.name)[1]
|
||||||
|
|
||||||
|
extension = ext
|
||||||
|
|
||||||
|
|
||||||
|
user = directory(commons['home'])
|
||||||
|
root = user.up.up
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||||
|
dp = r'c:\users\kenneth\videos'
|
||||||
|
|
||||||
|
# fp = r'C:\Users\Kenneth\Music\Collection\slowthai\The Bottom _ North Nights\02 North Nights.mp3'#[:-1]
|
||||||
|
# dp = delevel(fp,1)#[:-1]
|
||||||
|
|
||||||
|
# d = address(dp).obj
|
||||||
|
# f = address(fp).obj
|
||||||
|
|
||||||
|
d = directory(dp)
|
||||||
|
f = file(fp)
|
||||||
|
|
||||||
|
system = (d,f)
|
||||||
|
[print(i) for i in system]
|
||||||
|
[print(i.size) for i in system]
|
||||||
|
print()
|
||||||
|
# [show(i.delevel()) for i in system]
|
||||||
|
# [show(i.delevel()) for i in system]
|
||||||
|
|
||||||
|
print(forbiddenChars)
|
||||||
|
|
||||||
|
# with f as fob:
|
||||||
|
# print(fob)
|
|
@ -0,0 +1,54 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from functools import reduce, cached_property
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
from sl4ng import show
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(path,relative=False):
|
||||||
|
other = ''.join(i for i in '\/' if not i==os.sep)
|
||||||
|
if other in path:
|
||||||
|
terms = []
|
||||||
|
for term in path.split(os.sep):
|
||||||
|
if other in term:
|
||||||
|
for part in term.split(other):
|
||||||
|
terms.append(part)
|
||||||
|
else:
|
||||||
|
terms.append(term)
|
||||||
|
path = os.path.join(*terms)
|
||||||
|
if relative:
|
||||||
|
path = '.'+os.sep+path
|
||||||
|
return path
|
||||||
|
|
||||||
|
def hasdirs(path):
|
||||||
|
return bool(re.search(re.escape(os.sep),normalize(path)))
|
||||||
|
|
||||||
|
def likeFile(path):
|
||||||
|
path = normalize(path)
|
||||||
|
return bool(re.search('readme$|.+\.(\S)+$',path.split(os.sep)[-1],re.I))
|
||||||
|
|
||||||
|
|
||||||
|
class address(Path):
|
||||||
|
def __init__(self,string):
|
||||||
|
string = normalize(self.string)
|
||||||
|
if not os.path.exists(string):
|
||||||
|
print(Warning(f'Warning: Path "{string}" does not exist'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mock = 'drive: users user place subplace file.extension.secondextension'.split()
|
||||||
|
# mock = 'drive: users user place subplace readme'.split()
|
||||||
|
testPaths = [
|
||||||
|
''.join(mock),
|
||||||
|
os.path.join(*mock),
|
||||||
|
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[:-1]),
|
||||||
|
'/'.join(mock[:-1]),
|
||||||
|
mock[-1]
|
||||||
|
]
|
||||||
|
# show(zip(testPaths,map(normalize,testPaths)))
|
||||||
|
# show(zip(testPaths,map(hasdirs,testPaths)))
|
||||||
|
# show(zip(testPaths,map(likeFile,testPaths)))
|
||||||
|
|
||||||
|
demo = address(os.path.join(*mock))
|
|
@ -0,0 +1,344 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from functools import reduce, cached_property
|
||||||
|
import os, re, sys, shutil
|
||||||
|
|
||||||
|
|
||||||
|
import filetype as ft
|
||||||
|
from send2trash import send2trash
|
||||||
|
import audio_metadata as am
|
||||||
|
|
||||||
|
|
||||||
|
from sl4ng import show, delevel, gather, nameSpacer, ffplay, commons, nopes
|
||||||
|
# from magnitudes import represent# as rp
|
||||||
|
|
||||||
|
def normalize(path, relative=False):
|
||||||
|
other = ''.join(i for i in '\/' if not i==os.sep)
|
||||||
|
if other in path:
|
||||||
|
terms = []
|
||||||
|
for term in path.split(os.sep):
|
||||||
|
if other in term:
|
||||||
|
for part in term.split(other):
|
||||||
|
terms.append(part)
|
||||||
|
else:
|
||||||
|
terms.append(term)
|
||||||
|
path = os.path.join(*terms)
|
||||||
|
if relative:
|
||||||
|
path = '.'+os.sep+path
|
||||||
|
return path
|
||||||
|
|
||||||
|
def hasdirs(path):
|
||||||
|
return bool(re.search(re.escape(os.sep),normalize(path)))
|
||||||
|
|
||||||
|
def likeFile(path):
|
||||||
|
path = normalize(path)
|
||||||
|
return bool(re.search('readme$|.+\.(\S)+$',path.split(os.sep)[-1],re.I))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class address:
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path = normalize(path)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
print(Warning(f'Warning: Path "{path}" does not exist'))
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __repr__(self):
|
||||||
|
return self.path
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.path)
|
||||||
|
|
||||||
|
def create(self, content=None, raw=False):
|
||||||
|
if self.exists:
|
||||||
|
return self
|
||||||
|
elif content:
|
||||||
|
if raw:
|
||||||
|
if isinstance(content,(bytes,bytearray)):
|
||||||
|
fobj = open(self.path,'wb')
|
||||||
|
else:
|
||||||
|
fobj = open(self.path,'w')
|
||||||
|
fobj.write(content)
|
||||||
|
fobj.close()
|
||||||
|
else:
|
||||||
|
if isinstance(content,str):
|
||||||
|
content = bytes(content,encoding='utf-8')
|
||||||
|
else:
|
||||||
|
content = bytes(content)
|
||||||
|
with open(self.path,'wb') as fobj:
|
||||||
|
fobj.write(content)
|
||||||
|
else:
|
||||||
|
if likeFile(self.path):
|
||||||
|
with open(self.path,'x') as fobj:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.makedirs(self.path,exist_ok=True)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isdir(self):
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
@property
|
||||||
|
def isfile(self):
|
||||||
|
return os.path.isfile(self.path)
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
@property
|
||||||
|
def obj(self):
|
||||||
|
if self.exists:
|
||||||
|
return file(self.path) if self.isfile else folder(self.path)
|
||||||
|
else:
|
||||||
|
return file(self.path) if likeFile(self.path) else directory(self.path)
|
||||||
|
@property
|
||||||
|
def up(self):
|
||||||
|
return address(delevel(self.path)).obj
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(address(i).obj for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def colleagues(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return tuple(i for i in self.up if isinstance(i, type(self)))
|
||||||
|
@property
|
||||||
|
def neighbours(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.up.content
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def top(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.ancestors[0]#.split(':')[0]
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
|
||||||
|
def delevel(self, steps=1, path=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return delevel(self.path,steps) if path else directory(delevel(self.path,steps))
|
||||||
|
def heritage(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
print(f'\nheritage({self.name.title()})')
|
||||||
|
ancs = list(self.ancestors)
|
||||||
|
ancs.append(self.path)
|
||||||
|
for i,anc in enumerate(ancs):
|
||||||
|
print('\t'+('','.'*i)[i>0]+i*' '+[i for i in str(anc).split(os.sep) if i][-1])
|
||||||
|
|
||||||
|
def touch(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
p = self.path
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = address(p).obj
|
||||||
|
return self
|
||||||
|
def erase(self, recycle=True):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
return self
|
||||||
|
def rename(self, new, relative=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
new = new if not relative else os.path.join(delevel(self.path),new)
|
||||||
|
os.makedirs(delevel(new),exist_ok=True)
|
||||||
|
os.rename(self.path,new)
|
||||||
|
self = address(new).obj
|
||||||
|
return self
|
||||||
|
def clone(self,new=None, relative=False, touch=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if new:
|
||||||
|
if os.path.isdir(new):
|
||||||
|
new = nameSpacer(os.path.join(new, self.name))
|
||||||
|
if relative:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path), self.name))
|
||||||
|
else:
|
||||||
|
new = nameSpacer(os.path.join(delevel(self.path), self.name))
|
||||||
|
os.makedirs(delevel(new), exist_ok=True)
|
||||||
|
shutil.copy2(self.path, new)
|
||||||
|
out = address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
|
||||||
|
def expose(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
os.startfile(self.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
class file(address):
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
return os.path.splitext(self.name)[0]
|
||||||
|
|
||||||
|
def content(mode='rb'):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
with open(self.path, mode) as fobj:
|
||||||
|
return fobj
|
||||||
|
|
||||||
|
class directory(address):
|
||||||
|
def __init__(self,path:str):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
assert address(path)#.isdir, f'"{path}" is not a directory'
|
||||||
|
super(directory,self).__init__(path)
|
||||||
|
self._ind = -1
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.content)
|
||||||
|
def __bool__(self):
|
||||||
|
"""
|
||||||
|
Check if the directory is empty or not
|
||||||
|
"""
|
||||||
|
return len(os.listdir(self.path))>0
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
def __next__(self):
|
||||||
|
if self._ind<len(self)-1:
|
||||||
|
self._ind += 1
|
||||||
|
return self.content[self._ind]
|
||||||
|
self._ind = -1
|
||||||
|
raise StopIteration
|
||||||
|
def __getitem__(self,item):
|
||||||
|
if any(re.search(f'^{item}$',i.name,re.I) for i in self.content):
|
||||||
|
# for i in nopes((re.search(f'^{item}$',i.name,re.I) for i in self.content),True):
|
||||||
|
return address(os.path.join(self.path,item)).obj
|
||||||
|
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for directories
|
||||||
|
"""
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(str(self),i))).isdir)
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for files
|
||||||
|
"""
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=address(os.path.join(self.path,i))).isfile)
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
"""
|
||||||
|
Return address-like objects from "os.listdir"
|
||||||
|
"""
|
||||||
|
return tuple(address(os.path.join(self.path,i)).obj for i in os.listdir(self.path))
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
"""
|
||||||
|
Return All files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(self.gather())
|
||||||
|
@property
|
||||||
|
def branches(self):
|
||||||
|
"""
|
||||||
|
Return Every directory whose path contains "self.path"
|
||||||
|
"""
|
||||||
|
return tuple(set(i.delevel() for i in self.gather()))
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
"""
|
||||||
|
Return Prettified version of _size
|
||||||
|
"""
|
||||||
|
return sum(file.size for file in self.leaves)
|
||||||
|
@property
|
||||||
|
def _size(self):
|
||||||
|
"""
|
||||||
|
Return sum of file sizes for all leaves
|
||||||
|
"""
|
||||||
|
return sum(file.size for file in self.leaves)
|
||||||
|
@property
|
||||||
|
def mimes(self):
|
||||||
|
"""
|
||||||
|
Return file mimes for all files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(file.mime for file in self.gather()))
|
||||||
|
@property
|
||||||
|
def kinds(self):
|
||||||
|
"""
|
||||||
|
Return file types for all files from branches
|
||||||
|
"""
|
||||||
|
return tuple(set(m.split('/')[0] for m in self.mime))
|
||||||
|
@property
|
||||||
|
def exts(self):
|
||||||
|
"""
|
||||||
|
Return extensions for all files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(f.ext for f in self.gather()))
|
||||||
|
@property
|
||||||
|
def isroot(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return check if the directory is at the highest level of the file system
|
||||||
|
"""
|
||||||
|
return not self.depth
|
||||||
|
|
||||||
|
def add(self, other, copy=False):
|
||||||
|
"""
|
||||||
|
Introduce new elements. Send an address-like object to self.
|
||||||
|
"""
|
||||||
|
new = os.path.join(self.path, os.path.split(other.path)[-1])
|
||||||
|
other.rename(new)
|
||||||
|
return self
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mock = 'drive: users user place subplace file.extension.secondextension'.split()
|
||||||
|
# mock = 'drive: users user place subplace readme'.split()
|
||||||
|
testPaths = [
|
||||||
|
''.join(mock),
|
||||||
|
os.path.join(*mock),
|
||||||
|
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[:-1]),
|
||||||
|
'/'.join(mock[:-1]),
|
||||||
|
mock[-1]
|
||||||
|
]
|
||||||
|
# show(zip(testPaths,map(normalize,testPaths)))
|
||||||
|
# show(zip(testPaths,map(hasdirs,testPaths)))
|
||||||
|
# show(zip(testPaths,map(likeFile,testPaths)))
|
||||||
|
|
||||||
|
# ddemo = address(os.path.join(*mock[:-1])).obj
|
||||||
|
# fdemo = address(os.path.join(*mock)).obj
|
||||||
|
# ddemo = directory(os.path.join(*mock[:-1]))
|
||||||
|
# fdemo = file(os.path.join(*mock))
|
||||||
|
|
||||||
|
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||||
|
dp = r'c:\users\kenneth\videos'
|
||||||
|
|
||||||
|
d = directory(dp)
|
||||||
|
f = file(fp)
|
||||||
|
|
||||||
|
system = (d,f)
|
||||||
|
|
|
@ -0,0 +1,562 @@
|
||||||
|
"""
|
||||||
|
File management wrappers on os.path, shutil, and useful non-standard modules
|
||||||
|
|
||||||
|
"Apparent drive" refers to the term before the first os.sep in an object's given path.
|
||||||
|
if the given path is relative
|
||||||
|
then the ADrive may be ".." or the name of the File/Directory
|
||||||
|
else
|
||||||
|
the drive is the name/letter of the disk partition on which the content at the address is stored.
|
||||||
|
"Apparent Directory" similarly refers first term before the last os.sep in an object's given path
|
||||||
|
if the given path is relative
|
||||||
|
then the ADir may be ".." or the name of the File/Directory
|
||||||
|
else
|
||||||
|
the drive is the name/letter of the disk partition on which the content at the address is stored.
|
||||||
|
|
||||||
|
|
||||||
|
TODO
|
||||||
|
Add relative path support
|
||||||
|
Classes for specific mimes
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from functools import reduce, cached_property
|
||||||
|
from itertools import chain
|
||||||
|
from typing import Iterable
|
||||||
|
import os, re, sys, shutil
|
||||||
|
|
||||||
|
|
||||||
|
import filetype as ft, audio_metadata as am
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from sl4ng import show, delevel, gather, nameSpacer, ffplay, commons, nopes
|
||||||
|
# from magnitudes import represent# as rp
|
||||||
|
|
||||||
|
|
||||||
|
gen = type(i for i in range(0))
|
||||||
|
func = type(ft.match)
|
||||||
|
|
||||||
|
forbiddens = r'\/:?*<>|"' # at least on windows
|
||||||
|
|
||||||
|
formats = {
|
||||||
|
'pics': "bmp png jpg jpeg tiff".split(),
|
||||||
|
'music': "mp3 m4a wav ogg wma flac aiff alac".split(),
|
||||||
|
'videos': "mp4 wmv".split(),
|
||||||
|
'docs': "doc docx pdf xlsx pptx ppt xls csv".split(),
|
||||||
|
}
|
||||||
|
formats['all'] = [*chain.from_iterable(formats.values())]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def trim(path:str, edge:str=os.sep) -> str:
|
||||||
|
out = path[:]
|
||||||
|
while out.startswith(edge):
|
||||||
|
out = out[1:]
|
||||||
|
while out.endswith(edge):
|
||||||
|
out = out[:-1]
|
||||||
|
return out
|
||||||
|
|
||||||
|
def normalize(path:str, relative:bool=False) -> str:
|
||||||
|
other = ''.join(i for i in '\/' if not i==os.sep)
|
||||||
|
if other in path:
|
||||||
|
terms = []
|
||||||
|
for term in path.split(os.sep):
|
||||||
|
if other in term:
|
||||||
|
for part in term.split(other):
|
||||||
|
terms.append(part)
|
||||||
|
else:
|
||||||
|
terms.append(term)
|
||||||
|
path = os.path.join(*terms)
|
||||||
|
if relative:
|
||||||
|
path = '.'+os.sep+path
|
||||||
|
return path
|
||||||
|
|
||||||
|
def hasdirs(path:str) -> bool:
|
||||||
|
return bool(re.search(re.escape(os.sep), normalize(path)))
|
||||||
|
|
||||||
|
def likeFile(path:str) -> bool:
|
||||||
|
path = normalize(path)
|
||||||
|
return bool(re.search('readme$|.+\.(\S)+$',path.split(os.sep)[-1],re.I))
|
||||||
|
|
||||||
|
def multisplit(splitters:Iterable[str], target:str) -> gen:
|
||||||
|
"""
|
||||||
|
Split a string by a the elements of a sequence
|
||||||
|
>>> list(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||||
|
['wma', 'wmv', 'mp3', 'vga', 'mp4', 'ogg']
|
||||||
|
"""
|
||||||
|
splitters = iter(splitters)
|
||||||
|
result = target.split(next(splitters))
|
||||||
|
for splitter in splitters:
|
||||||
|
result = [*chain.from_iterable(i.split(splitter) for i in result)]
|
||||||
|
yield from filter(None, result)
|
||||||
|
|
||||||
|
class Address:
|
||||||
|
"""
|
||||||
|
Methods return self unless otherwise stated
|
||||||
|
"""
|
||||||
|
def __init__(self, path:str):
|
||||||
|
self.path = path = normalize(path)
|
||||||
|
# if not os.path.exists(path):
|
||||||
|
# print(Warning(f'Warning: Path "{path}" does not exist'))
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
def __repr__(self):
|
||||||
|
return self.path
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.path)
|
||||||
|
|
||||||
|
def create(self, content:[str, bytes, bytearray]=None, raw:bool=False):
|
||||||
|
"""
|
||||||
|
Create an entry in the file-system. If the address is not vacant no action will be taken.
|
||||||
|
The raw handle only works for Files and enables writing bytes/bytearrays
|
||||||
|
"""
|
||||||
|
if self.exists:
|
||||||
|
return self
|
||||||
|
elif content:
|
||||||
|
if raw:
|
||||||
|
if isinstance(content,(bytes,bytearray)):
|
||||||
|
fobj = open(self.path,'wb')
|
||||||
|
else:
|
||||||
|
fobj = open(self.path,'w')
|
||||||
|
fobj.write(content)
|
||||||
|
fobj.close()
|
||||||
|
else:
|
||||||
|
if isinstance(content,str):
|
||||||
|
content = bytes(content,encoding='utf-8')
|
||||||
|
else:
|
||||||
|
content = bytes(content)
|
||||||
|
with open(self.path,'wb') as fobj:
|
||||||
|
fobj.write(content)
|
||||||
|
else:
|
||||||
|
if likeFile(self.path):
|
||||||
|
with open(self.path,'x') as fobj:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.makedirs(self.path,exist_ok=True)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
@property
|
||||||
|
def isdir(self):
|
||||||
|
if self.exists:
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
return not likeFile(self.path)
|
||||||
|
@property
|
||||||
|
def isfile(self):
|
||||||
|
if self.exists:
|
||||||
|
return os.path.isdir(self.path)
|
||||||
|
return likeFile(self.path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def obj(self):
|
||||||
|
"""
|
||||||
|
Determine if self.path points to a file or folder and create the corresponding object
|
||||||
|
"""
|
||||||
|
if self.exists:
|
||||||
|
return File(self.path) if os.path.isfile(self.path) else Directory(self.path)
|
||||||
|
else:
|
||||||
|
return File(self.path) if likeFile(self.path) else Directory(self.path)
|
||||||
|
@property
|
||||||
|
def up(self):
|
||||||
|
"""
|
||||||
|
Return the ADir
|
||||||
|
"""
|
||||||
|
return Address(delevel(self.path)).obj
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
Return the name of the referent
|
||||||
|
"""
|
||||||
|
return os.path.split(self.path)[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ancestors(self):
|
||||||
|
"""
|
||||||
|
Return consecutive ADirs until the ADrive is reached
|
||||||
|
"""
|
||||||
|
level = []
|
||||||
|
p = self.path[:]
|
||||||
|
while p != delevel(p):
|
||||||
|
p = delevel(p)
|
||||||
|
level.append(p)
|
||||||
|
return tuple(Address(i).obj for i in level)[::-1]
|
||||||
|
@property
|
||||||
|
def colleagues(self):
|
||||||
|
"""
|
||||||
|
Every member of the same Directory whose type is the same as the referent
|
||||||
|
"""
|
||||||
|
return tuple(i for i in self.up if isinstance(i, type(self)))
|
||||||
|
@property
|
||||||
|
def neighbours(self):
|
||||||
|
"""
|
||||||
|
Everything in the same Directory
|
||||||
|
"""
|
||||||
|
return self.up.content
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
"""
|
||||||
|
Number of ancestors
|
||||||
|
"""
|
||||||
|
return len(self.ancestors)
|
||||||
|
@property
|
||||||
|
def top(self):
|
||||||
|
"""
|
||||||
|
The apparent drive. Will not be helpful if self.path is relative
|
||||||
|
"""
|
||||||
|
return self.ancestors[0]
|
||||||
|
@property
|
||||||
|
def stat(self):
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
"""
|
||||||
|
return os.stat(self.path)
|
||||||
|
|
||||||
|
def delevel(self, steps:int=1, path:bool=False) -> str:
|
||||||
|
"""
|
||||||
|
Go up some number of levels in the File system
|
||||||
|
"""
|
||||||
|
return delevel(self.path,steps) if path else Directory(delevel(self.path,steps))
|
||||||
|
def heritage(self):
|
||||||
|
"""
|
||||||
|
A fancy representation of the tree from the apparent drive up to the given path
|
||||||
|
"""
|
||||||
|
print(f'\nheritage({self.name.title()})')
|
||||||
|
ancs = list(self.ancestors)
|
||||||
|
ancs.append(self.path)
|
||||||
|
for i,anc in enumerate(ancs):
|
||||||
|
print('\t' + ('','.'*i)[i>0] + i*' ' + [i for i in str(anc).split(os.sep) if i][-1])
|
||||||
|
return self
|
||||||
|
def touch(self):
|
||||||
|
"""
|
||||||
|
Implements the unix command 'touch', which updates the 'date modified' of the content at the path
|
||||||
|
"""
|
||||||
|
p = self.path
|
||||||
|
pathlib.Path(p).touch()
|
||||||
|
self = Address(p).obj
|
||||||
|
return self
|
||||||
|
def erase(self, recycle:bool=True):
|
||||||
|
"""
|
||||||
|
Send a File to the trash, or remove it without recycling.
|
||||||
|
"""
|
||||||
|
send2trash(self.path) if recycle else os.remove(self.path)
|
||||||
|
return self
|
||||||
|
def copy(self, folder:str=None, cwd:bool=False, sep:str='_'):
|
||||||
|
"""
|
||||||
|
Returns a clone of the referent at a given Directory-path
|
||||||
|
The given path will be created if it doesn't exist
|
||||||
|
Will copy in the File's original folder if no path is given
|
||||||
|
The cwd switch will always copy to the current working Directory
|
||||||
|
"""
|
||||||
|
copier = (shutil.copy2, shutil.copytree)[self.isdir]
|
||||||
|
if folder:
|
||||||
|
new = os.path.join(folder, self.name)
|
||||||
|
elif cwd:
|
||||||
|
new = os.path.join(os.getcwd(), self.name)
|
||||||
|
else:
|
||||||
|
new = self.path
|
||||||
|
new = nameSpacer(new, sep=sep)
|
||||||
|
os.makedirs(delevel(new), exist_ok=True)
|
||||||
|
copier(self.path, new)
|
||||||
|
out = Address(new).obj
|
||||||
|
return out.touch() if touch else out
|
||||||
|
def move(self, folder:str=None, name:str=None, dodge:str=False, sep:str='_'):
|
||||||
|
"""
|
||||||
|
addy.move(folder) -> move to the given Directory ()
|
||||||
|
addy.move(name) -> move to the given path (relative paths will follow from the objects existing path)
|
||||||
|
addy.move(folder, name) -> move to the given Directory
|
||||||
|
|
||||||
|
The dodge handle enables automatic file-system collision evasion
|
||||||
|
"""
|
||||||
|
if folder and name:
|
||||||
|
new = os.path.join(folder, name)
|
||||||
|
elif folder:
|
||||||
|
new = os.path.join(folder, self.name)
|
||||||
|
elif name:
|
||||||
|
new = os.path.join(self.up.path, name)
|
||||||
|
else:
|
||||||
|
raise ValueError('The file couldn\'t be moved because "name" and "folder" are undefined')
|
||||||
|
new = nameSpacer(new, sep=sep) if dodge else new
|
||||||
|
os.rename(self.path, new)
|
||||||
|
self.path = new
|
||||||
|
return self
|
||||||
|
def rename(self, name:str):
|
||||||
|
"""
|
||||||
|
Change the name of the referent
|
||||||
|
"""
|
||||||
|
return self.move(name=name)
|
||||||
|
def expose(self):
|
||||||
|
"""
|
||||||
|
Reveal the referent in the system's file explorer (will open the containing Directory if the referent is a File)
|
||||||
|
"""
|
||||||
|
if self.isdir:
|
||||||
|
os.startFile(self.path)
|
||||||
|
else:
|
||||||
|
os.startFile(self.up.path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
class File(Address):
|
||||||
|
def __init__(self,path:str):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
super(File,self).__init__(path)
|
||||||
|
self._stream = None
|
||||||
|
def __enter__(self):
|
||||||
|
self._stream = open(str(self),'b')
|
||||||
|
return self._stream
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self._stream.close()
|
||||||
|
self._stream = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return size(os.stat(self.path).st_size)
|
||||||
|
@property
|
||||||
|
def mime(self):
|
||||||
|
return match.MIME if (match:=ft.guess(str(self))) else None
|
||||||
|
@property
|
||||||
|
def kind(self):
|
||||||
|
return self.mime.split('/')[0] if (match:=ft.guess(self.path)) else None
|
||||||
|
@property
|
||||||
|
def ext(self):
|
||||||
|
return os.path.splitext(self.name)[1]
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
"""
|
||||||
|
return the File's name without the extension
|
||||||
|
"""
|
||||||
|
return os.path.splitext(self.name)[0]
|
||||||
|
def open(mode:str='r', scrape:bool=False):
|
||||||
|
"""
|
||||||
|
Return the File's byte or text stream.
|
||||||
|
Scrape splits the text at all whitespace and returns the content as a string
|
||||||
|
"""
|
||||||
|
if scrape:
|
||||||
|
with open(self.path, mode) as fobj:
|
||||||
|
return ' '.join(fobj.read().split())
|
||||||
|
with open(self.path, mode) as fobj:
|
||||||
|
return fobj
|
||||||
|
|
||||||
|
class Directory(Address):
|
||||||
|
def __init__(self, path:str):
|
||||||
|
path = os.path.abspath(trim(path))
|
||||||
|
self.path = normalize(path)
|
||||||
|
super(Directory, self).__init__(path)
|
||||||
|
self._ind = -1
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.content)
|
||||||
|
def __bool__(self):
|
||||||
|
"""
|
||||||
|
Check if the Directory is empty or not
|
||||||
|
"""
|
||||||
|
return len(os.listdir(self.path))>0
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
def __next__(self):
|
||||||
|
if self._ind<len(self)-1:
|
||||||
|
self._ind += 1
|
||||||
|
return self.content[self._ind]
|
||||||
|
self._ind = -1
|
||||||
|
raise StopIteration
|
||||||
|
def __getitem__(self, item):
|
||||||
|
"""
|
||||||
|
Return an object whose name is an exact match for the given item
|
||||||
|
"""
|
||||||
|
if any(re.search(f'^{item}$', i.name, re.I) for i in self.content):
|
||||||
|
return Address(os.path.join(self.path, item)).obj
|
||||||
|
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||||
|
def __truediv__(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
return Address(os.path.join(self.path, other)).obj
|
||||||
|
raise TypeError(f"Other must be a string")
|
||||||
|
def __call__(self, keyword:str, sort:bool=False, case:bool=False, **kwargs) -> Iterable:
|
||||||
|
"""
|
||||||
|
See help(self.search)
|
||||||
|
"""
|
||||||
|
return self.search(keyword, sort, case, **kwargs)
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for directories
|
||||||
|
"""
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=Address(os.path.join(str(self),i))).isdir)
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
"""
|
||||||
|
Return "os.listdir" but filtered for Files
|
||||||
|
"""
|
||||||
|
return tuple(addy.obj for i in os.listdir(self.path) if (addy:=Address(os.path.join(self.path,i))).isfile)
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
"""
|
||||||
|
Return address-like objects from "os.listdir"
|
||||||
|
"""
|
||||||
|
return tuple(Address(os.path.join(self.path,i)).obj for i in os.listdir(self.path))
|
||||||
|
@property
|
||||||
|
def leaves(self):
|
||||||
|
"""
|
||||||
|
Return All Files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(self.gather())
|
||||||
|
@property
|
||||||
|
def branches(self):
|
||||||
|
"""
|
||||||
|
Return Every Directory whose path contains "self.path"
|
||||||
|
"""
|
||||||
|
return tuple(set(i.delevel() for i in self.gather()))
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
"""
|
||||||
|
Return Prettified version of _size
|
||||||
|
"""
|
||||||
|
return sum(File.size for File in self.leaves)
|
||||||
|
@property
|
||||||
|
def _size(self):
|
||||||
|
"""
|
||||||
|
Return sum of File sizes for all leaves
|
||||||
|
"""
|
||||||
|
return sum(File.size for File in self.leaves)
|
||||||
|
@property
|
||||||
|
def mimes(self):
|
||||||
|
"""
|
||||||
|
Return File mimes for all Files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(File.mime for File in self.gather()))
|
||||||
|
@property
|
||||||
|
def kinds(self):
|
||||||
|
"""
|
||||||
|
Return File types for all Files from branches
|
||||||
|
"""
|
||||||
|
return tuple(set(m.split('/')[0] for m in self.mime))
|
||||||
|
@property
|
||||||
|
def exts(self):
|
||||||
|
"""
|
||||||
|
Return extensions for all Files from all branches
|
||||||
|
"""
|
||||||
|
return tuple(set(f.ext for f in self.gather()))
|
||||||
|
@property
|
||||||
|
def isroot(self):
|
||||||
|
"""
|
||||||
|
Return check if the Directory is at the highest level of the File system
|
||||||
|
"""
|
||||||
|
return not self.depth
|
||||||
|
|
||||||
|
def add(self, other, copy=False):
|
||||||
|
"""
|
||||||
|
Introduce new elements. Send an address-like object to self.
|
||||||
|
"""
|
||||||
|
if not other.exists:
|
||||||
|
raise OSError(f"{other.name.title()} could not be added to {self.name.title()} because it doesn't exist")
|
||||||
|
new = os.path.join(self.path, os.path.split(other.path)[-1])
|
||||||
|
other.rename(new)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def enter(self):
|
||||||
|
"""
|
||||||
|
Set referent as current working Directory
|
||||||
|
"""
|
||||||
|
os.chdir(self.path)
|
||||||
|
|
||||||
|
def gather(self, titles:bool=False, walk:bool=True, ext:str='') -> gen:
|
||||||
|
"""
|
||||||
|
Generate an iterable of the Files rooted in a given folder
|
||||||
|
It is possible to search for multiple File extensions if you separate each one with a space, comma, asterisk, or tilde.
|
||||||
|
Only use one symbol per gathering though.
|
||||||
|
"""
|
||||||
|
folder = self.path
|
||||||
|
if walk:
|
||||||
|
if ext:
|
||||||
|
ext = ext.replace('.', '')
|
||||||
|
sep = [i for i in ',`* ' if i in ext]
|
||||||
|
pattern = '|'.join(f'\.{i}$' for i in ext.split(sep[0] if sep else None))
|
||||||
|
pat = re.compile(pattern, re.I)
|
||||||
|
for root, folders, names in os.walk(folder):
|
||||||
|
for name in names:
|
||||||
|
if os.path.isfile(p:=os.path.join(root, name)) and pat.search(name) and name!='NTUSER.DAT':
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
for root, folders, names in os.walk(folder):
|
||||||
|
for name in names:
|
||||||
|
if os.path.exists(p:=os.path.join(root, name)):
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
if ext:
|
||||||
|
ext = ext.replace('.', '')
|
||||||
|
sep = [i for i in ',`* ' if i in ext]
|
||||||
|
pattern = '|'.join(f'\.{i}$' for i in ext.split(sep[0] if sep else None))
|
||||||
|
pat = re.compile(pattern, re.I)
|
||||||
|
for name in os.listdir(folder):
|
||||||
|
if os.path.isfile(p:=os.path.join(folder, name)) and pat.search(name) and name!='NTUSER.DAT':
|
||||||
|
yield name if titles else p
|
||||||
|
else:
|
||||||
|
for name in os.listdir(folder):
|
||||||
|
if os.path.isfile(p:=os.path.join(folder, name)):
|
||||||
|
yield name if titles else p
|
||||||
|
|
||||||
|
def search(self, keyword:str, sort:bool=False, case:bool=False, prescape:bool=False, **kwargs) -> Iterable:
|
||||||
|
"""
|
||||||
|
Return an iterator of Files whose path match the given keyword within a Directory.
|
||||||
|
The search is linear and the sorting is based on the number of matches. If sorted, a list will be returned.
|
||||||
|
Case pertains to case-sensitivity
|
||||||
|
Prescape informs the method that kewords do not need to be escaped
|
||||||
|
For kwargs see help(self.gather)
|
||||||
|
"""
|
||||||
|
casesensitivity = (re.I, 0)[case]
|
||||||
|
escaper = (re.escape, id)[prescape]
|
||||||
|
if isinstance(keyword, str):
|
||||||
|
keyword = keyword.split()
|
||||||
|
if not isinstance(keyword, str):
|
||||||
|
keyword = '|'.join(map(escaper, keyword))
|
||||||
|
if sort:
|
||||||
|
return sorted(
|
||||||
|
filter(
|
||||||
|
# lambda x: re.search(keyword, x, casesensitivity),
|
||||||
|
lambda x: len([*re.finditer(keyword, x, casesensitivity)]) == len(keyword.split('|')),
|
||||||
|
self.gather(**kwargs),
|
||||||
|
),
|
||||||
|
key=lambda x: len([*re.finditer(keyword, x, casesensitivity)]),
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return filter(
|
||||||
|
# lambda x: re.finditer(keyword, x, casesensitivity),
|
||||||
|
lambda x: re.search(keyword, x, casesensitivity),
|
||||||
|
self.gather(**kwargs)
|
||||||
|
)
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mock = 'drive: users user place subplace File.extension.secondextension'.split()
|
||||||
|
# mock = 'drive: users user place subplace readme'.split()
|
||||||
|
testPaths = [
|
||||||
|
''.join(mock),
|
||||||
|
os.path.join(*mock),
|
||||||
|
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[:-1]),
|
||||||
|
'/'.join(mock[:-1]),
|
||||||
|
mock[-1]
|
||||||
|
]
|
||||||
|
show(zip(map(type,map(lambda x: Address(x).obj, testPaths)),testPaths))
|
||||||
|
# show(zip(testPaths,map(normalize,testPaths)))
|
||||||
|
# show(zip(testPaths,map(hasdirs,testPaths)))
|
||||||
|
# show(zip(testPaths,map(likeFile,testPaths)))
|
||||||
|
|
||||||
|
# ddemo = Address(os.path.join(*mock[:-1])).obj
|
||||||
|
# fdemo = Address(os.path.join(*mock)).obj
|
||||||
|
# ddemo = Directory(os.path.join(*mock[:-1]))
|
||||||
|
# fdemo = File(os.path.join(*mock))
|
||||||
|
|
||||||
|
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||||
|
dp = r'c:\users\kenneth\videos'
|
||||||
|
|
||||||
|
d = Directory(dp)
|
||||||
|
f = File(fp)
|
||||||
|
|
||||||
|
system = (d, f)
|
||||||
|
show(d('slowthai',sort=True))
|
||||||
|
show(d('alix melanie', sort=True))
|
||||||
|
|
||||||
|
print(formats['all'])
|
||||||
|
for v in formats.values():
|
||||||
|
print(' '.join(v))
|
|
@ -0,0 +1,122 @@
|
||||||
|
"""
|
||||||
|
Compute the correct order of magnitude of an integer in a given dimension
|
||||||
|
"""
|
||||||
|
all = [
|
||||||
|
'represent',
|
||||||
|
'force',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# from m3ta import sample,show,nopes
|
||||||
|
from sl4ng import sample,nopes
|
||||||
|
|
||||||
|
|
||||||
|
orders = {
|
||||||
|
0:'mono',
|
||||||
|
1:'deca',
|
||||||
|
2:'hecto',
|
||||||
|
3:'kilo',
|
||||||
|
6:'mega',
|
||||||
|
9:'giga',
|
||||||
|
12:'tera',
|
||||||
|
15:'peta',
|
||||||
|
18:'exa',
|
||||||
|
21:'zetta',
|
||||||
|
24:'yotta',
|
||||||
|
}
|
||||||
|
|
||||||
|
sredro = {v:k for k,v in orders.items()}
|
||||||
|
|
||||||
|
pretty = lambda number,unit='': f'{number:,} {unit}'.strip()
|
||||||
|
setcase = lambda unit,lower=False: [unit.upper().strip(),unit.lower().strip()][lower]
|
||||||
|
setlength = lambda mag,dim,long=False,sep='-': ('',sep)[long].join(((mag[0],dim[0]),(mag,dim))[long])
|
||||||
|
|
||||||
|
# def getmagnitude(value,never='mono deca hecto'.split()):
|
||||||
|
# mags = tuple(sorted(i for i in orders.keys() if not orders[i] in never))
|
||||||
|
# booly = lambda i: len(str(int(value))) < len(str(10**mags[i+1]))
|
||||||
|
# fits = tuple(nopes((booly(i) for i in range(len(mags)-1)),True))
|
||||||
|
# fit = mags[min(fits) if fits else len(mags)-1]
|
||||||
|
# return orders[fit]
|
||||||
|
|
||||||
|
def magnitude(value,omissions='mono deca hecto'.split()):
|
||||||
|
mags = tuple(sorted(i for i in orders.keys() if not orders[i] in omissions))
|
||||||
|
booly = lambda i: len(str(int(value))) < len(str(10**mags[i+1]))
|
||||||
|
fits = tuple(nopes((booly(i) for i in range(len(mags)-1)),True))
|
||||||
|
fit = mags[min(fits) if fits else len(mags)-1]
|
||||||
|
return orders[fit]
|
||||||
|
|
||||||
|
# def represent(self,dim='bytes',long=False,lower=False,precision=2,sep='-',omissions='mono deca hecto'.split()):
|
||||||
|
# mag = magnitude(self,omissions)
|
||||||
|
# unit = setcase(setlength(mag,dim,long,['',sep][long]),lower)
|
||||||
|
# number = round(self*10**-sredro[mag],precision)
|
||||||
|
# out = pretty(number,unit)
|
||||||
|
# return (out.upper(),out.lower())[lower]
|
||||||
|
|
||||||
|
|
||||||
|
def represent(self,dim='bytes',long=False,lower=False,precision=2,sep='-',omissions='mono deca hecto'.split()):
|
||||||
|
return force(self,mag=magnitude(self,omissions),dim=dim,long=long,lower=lower,precision=precision,sep=sep)
|
||||||
|
|
||||||
|
def force(self,mag='kilo',dim='bytes',long=False,lower=False,precision=2,sep='-'):
|
||||||
|
# precision = sredro[mag]
|
||||||
|
unit = setcase(setlength(mag,dim,long,sep),lower)
|
||||||
|
val = round(self*10**-(sredro[mag]),precision)
|
||||||
|
return pretty(val,unit)
|
||||||
|
|
||||||
|
def f2(self,dim='bytes',long=False,lower=False,precision=2,sep='-',omissions='mono deca hecto'.split()):
|
||||||
|
"""
|
||||||
|
This should behave well on int subclasses
|
||||||
|
"""
|
||||||
|
mag = magnitude(self,omissions)
|
||||||
|
precision = sredro[mag] if self<5 else precision
|
||||||
|
unit = setcase(setlength(mag,dim,long,sep),lower)
|
||||||
|
val = round(self*10**-(sredro[mag]),precision)
|
||||||
|
return pretty(val,unit)
|
||||||
|
|
||||||
|
|
||||||
|
def file_size(self,dim='bytes',mag='kilo',long=False,lower=False,precision=2,sep='-',never='mono deca hecto'.split(),force=False):
|
||||||
|
if force:
|
||||||
|
return force(self,mag=mag,dim=dim,long=long,lower=lower,precision=precision,sep=sep)
|
||||||
|
return rep(self,dim=dim,long=long,lower=lower,precision=precision,sep=sep,never=never)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
band = lambda level,base=10,shift=0,root=1: tuple(root+i+shift for i in ((level+1)*base,level*base))[::-1]
|
||||||
|
format = lambda selection: int(''.join(str(i) for i in selection))
|
||||||
|
|
||||||
|
digits = range(*band(0))
|
||||||
|
|
||||||
|
l = 0
|
||||||
|
s = 0
|
||||||
|
b = 30
|
||||||
|
r = 1
|
||||||
|
x,y = band(l,b,s,r)
|
||||||
|
# print(x,y)
|
||||||
|
val = sample(digits,y)
|
||||||
|
sizes = [format(val[:i][::-1]) for i in range(x,y)]
|
||||||
|
# show(sizes)
|
||||||
|
# sizes = [44259260028,315436]
|
||||||
|
|
||||||
|
for size in sizes[::-1]:
|
||||||
|
# print(
|
||||||
|
string = f"""{(len(pretty(max(sizes)))-len(pretty(size)))*' '+pretty(size)}
|
||||||
|
{size = }
|
||||||
|
{len(str(size)) = }
|
||||||
|
{force(size) = }
|
||||||
|
{force(size,mag=magnitude(size)) = }
|
||||||
|
{f2(size) = }
|
||||||
|
{represent(size) = }
|
||||||
|
{magnitude(size) = }
|
||||||
|
|
||||||
|
|
||||||
|
""".splitlines()
|
||||||
|
print(*((x.strip(),x)[i<1] for i,x in enumerate(string)),sep='\n\t',end='\n\n')
|
||||||
|
# )
|
||||||
|
print(pretty(sizes[-1]))
|
||||||
|
print(band(l,s,b,r))
|
||||||
|
print(x,y)
|
||||||
|
print(f'{format(digits):,}')
|
||||||
|
# print(all(rep(size)==rep2(size) for size in sizes))
|
|
@ -0,0 +1,24 @@
|
||||||
|
from sl4ng import gen, show, chords, flatten as chain
|
||||||
|
from itertools import chain, tee
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
def multisplit(splitters:Sequence[str], target:str) -> gen:
|
||||||
|
"""
|
||||||
|
Split a string by a sequence of arguments
|
||||||
|
>>> list(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||||
|
['wma', 'wmv', 'mp3', 'vga', 'mp4', 'ogg']
|
||||||
|
"""
|
||||||
|
result = target.split(splitters[0])
|
||||||
|
for splitter in splitters[1:]:
|
||||||
|
result = [*chain.from_iterable(i.split(splitter) for i in result)]
|
||||||
|
# yield from [i for i in result if i]
|
||||||
|
yield from filter(None,result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# multisplit('a b c'.split(), 'carrot cabbage macabre'.split(','))
|
||||||
|
x, y = tee(multisplit('a b c'.split(), 'carrot cabbage macabre'))
|
||||||
|
x, y = tee(multisplit(',`* ', 'carrot cabbage macabre'))
|
||||||
|
x, y = tee(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||||
|
show(x)
|
||||||
|
print(list(y))
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .strings import *
|
||||||
|
from .magnitudes import *
|
||||||
|
from .debug import *
|
||||||
|
from .media import *
|
|
@ -0,0 +1,99 @@
|
||||||
|
__all__ = 'show pop sample nopes generator tipo'.split()
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Iterable, Any
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
|
||||||
|
generator = type(i for i in '')
|
||||||
|
|
||||||
|
def show(array:Iterable[Any], indentation:int=0, enum:bool=False, first:int=1, indentor:str='\t', tail=True, head=False, file=sys.stdout, sep:str=None) -> None:
|
||||||
|
"""
|
||||||
|
Print each element of an array. This will consume a generator.
|
||||||
|
"""
|
||||||
|
if (wasstr:=isinstance(file,str)): file = open(file)
|
||||||
|
print('\n', file=file) if head else None
|
||||||
|
for i,j in enumerate(array,first):
|
||||||
|
print(
|
||||||
|
(
|
||||||
|
f"{indentation*indentor}{j}",
|
||||||
|
f"{indentation*indentor}{i}\t{j}"
|
||||||
|
)[enum],
|
||||||
|
sep='',
|
||||||
|
file=file
|
||||||
|
)
|
||||||
|
if sep: print(sep)
|
||||||
|
print('\n', file=file) if tail else None
|
||||||
|
if wasstr: file.close()
|
||||||
|
|
||||||
|
|
||||||
|
def pop(arg:Any=None, file=False, silent:bool=False) -> str:
|
||||||
|
"""
|
||||||
|
Open the folder containing a given module, or object's module
|
||||||
|
Open current working directory if no object is given
|
||||||
|
Open the module's file if file=True
|
||||||
|
Return the path which is opened
|
||||||
|
|
||||||
|
This will raise an attribute error whenever there is no file to which the object/module is imputed
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
module = type(os)
|
||||||
|
if arg:
|
||||||
|
if isinstance(arg, module):
|
||||||
|
path = arg.__file__
|
||||||
|
else:
|
||||||
|
mstr = arg.__module__
|
||||||
|
if (top:=mstr.split('.')[0]) in globals().keys():
|
||||||
|
m = eval(mstr)
|
||||||
|
else:
|
||||||
|
t = exec(f'import {top}')
|
||||||
|
m = eval(mstr)
|
||||||
|
path = m.__file__
|
||||||
|
if not file:
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
else:
|
||||||
|
path = os.getcwd()
|
||||||
|
if not silent:
|
||||||
|
os.startfile(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def sample(iterable:Iterable, n:int) -> tuple:
|
||||||
|
"""
|
||||||
|
Obtains a random sample of any length from any iterable and returns it as a tuple
|
||||||
|
Dependencies: random.randint(a,b)
|
||||||
|
In: iterable, lengthOfDesiredSample
|
||||||
|
Out: iterable of length(n)
|
||||||
|
"""
|
||||||
|
import random as r
|
||||||
|
iterable = tuple(iterable) if type(iterable)==type({0,1}) else iterable
|
||||||
|
choiceIndices = tuple(r.randint(0,len(iterable)-1) for i in range(n))
|
||||||
|
return tuple(iterable[i] for i in choiceIndices)
|
||||||
|
|
||||||
|
|
||||||
|
def nopes(iterable:Iterable[Any], yeps:bool=False) -> generator:
|
||||||
|
"""
|
||||||
|
if yeps==False
|
||||||
|
Return the indices of all false-like boolean values of an iterable
|
||||||
|
Return indices of all true-like boolean values of an iterable
|
||||||
|
dependencies: None
|
||||||
|
example
|
||||||
|
t = (0,1,0,0,1)
|
||||||
|
>>> tuple(nopes(t))
|
||||||
|
(0,2,3)
|
||||||
|
>>> tuple(nopes(t,True))
|
||||||
|
(1,4)
|
||||||
|
"""
|
||||||
|
for i, j in enumerate(iterable):
|
||||||
|
if (not j, j)[yeps]:
|
||||||
|
yield i
|
||||||
|
|
||||||
|
|
||||||
|
def tipo(inpt:Any=type(lambda:0)) -> str:
|
||||||
|
"""
|
||||||
|
Return the name of an object's type
|
||||||
|
Dependencies: None
|
||||||
|
In: object
|
||||||
|
Out: str
|
||||||
|
"""
|
||||||
|
return str(type(inpt)).split("'")[1].split('.')[-1]
|
|
@ -0,0 +1,100 @@
|
||||||
|
"""
|
||||||
|
Making numbers readable and dimensional one order of magnitude at a time
|
||||||
|
"""
|
||||||
|
__all__ = 'nice_size'.split()
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .debug import sample, show, nopes
|
||||||
|
|
||||||
|
|
||||||
|
orders = {
|
||||||
|
0: 'mono',
|
||||||
|
1: 'deca',
|
||||||
|
2: 'hecto',
|
||||||
|
3: 'kilo',
|
||||||
|
6: 'mega',
|
||||||
|
9: 'giga',
|
||||||
|
12: 'tera',
|
||||||
|
15: 'peta',
|
||||||
|
18: 'exa',
|
||||||
|
21: 'zetta',
|
||||||
|
24: 'yotta',
|
||||||
|
}
|
||||||
|
|
||||||
|
sredro = {v:k for k,v in orders.items()}
|
||||||
|
|
||||||
|
def lasso(number:complex, unit:str='') -> str:
|
||||||
|
"""
|
||||||
|
Make a large number more readable by inserting commas before every third power of 10 and adding units
|
||||||
|
"""
|
||||||
|
return f'{number:,} {unit}'.strip()
|
||||||
|
|
||||||
|
def set_case(unit:str, lower:bool=False) -> str:
|
||||||
|
"""
|
||||||
|
Set the case of a number's unit string
|
||||||
|
"""
|
||||||
|
return [unit.upper().strip(), unit.lower().strip()][lower]
|
||||||
|
|
||||||
|
def set_length(mag:str, unit:str, long:bool=False, sep:str='-') -> str:
|
||||||
|
"""
|
||||||
|
Set the length of a number's unit string
|
||||||
|
"""
|
||||||
|
return ('', sep)[long].join(((mag[0], unit[0]), (mag, unit))[long])
|
||||||
|
|
||||||
|
def magnitude(value:complex, omissions:list='mono deca hecto'.split()):
|
||||||
|
"""
|
||||||
|
Determine the best order of magnitude for a given number
|
||||||
|
"""
|
||||||
|
mags = tuple(sorted(i for i in orders.keys() if not orders[i] in omissions))
|
||||||
|
booly = lambda i: len(str(int(value))) < len(str(10**mags[i+1]))
|
||||||
|
fits = tuple(nopes((booly(i) for i in range(len(mags)-1)),True))
|
||||||
|
fit = mags[min(fits) if fits else len(mags)-1]
|
||||||
|
return orders[fit]
|
||||||
|
|
||||||
|
def nice_size(self:complex, unit:str='bytes', long:bool=False, lower:bool=False, precision:int=2, sep:str='-', omissions:list='mono deca hecto'.split()):
|
||||||
|
"""
|
||||||
|
This should behave well on int subclasses
|
||||||
|
"""
|
||||||
|
mag = magnitude(self, omissions)
|
||||||
|
precision = sredro[mag] if self<5 else precision
|
||||||
|
unit = set_case(set_length(mag, unit, long, sep), lower)
|
||||||
|
val = round(self*10**-(sredro[mag]), precision)
|
||||||
|
return lasso(val, unit)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
band = lambda level, base=10, shift=0, root=1: tuple(root+i+shift for i in ((level+1)*base, level*base))[::-1]
|
||||||
|
format = lambda selection: int(''.join(str(i) for i in selection))
|
||||||
|
|
||||||
|
digits = range(*band(0))
|
||||||
|
|
||||||
|
l = 0
|
||||||
|
s = 0
|
||||||
|
b = 30
|
||||||
|
r = 1
|
||||||
|
x,y = band(l, b, s, r)
|
||||||
|
# print(x,y)
|
||||||
|
val = sample(digits,y)
|
||||||
|
sizes = [format(val[:i][::-1]) for i in range(x, y)]
|
||||||
|
# show(sizes)
|
||||||
|
# sizes = [44259260028,315436]
|
||||||
|
|
||||||
|
for size in sizes[::-1]:
|
||||||
|
string = f"""{(len(lasso(max(sizes)))-len(lasso(size)))*' '+lasso(size)}
|
||||||
|
{size = }
|
||||||
|
{len(str(size)) = }
|
||||||
|
{nice_size(size) = }
|
||||||
|
{magnitude(size) = }
|
||||||
|
|
||||||
|
|
||||||
|
""".splitlines()
|
||||||
|
print(*((x.strip(), x)[i<1] for i, x in enumerate(string)), sep='\n\t', end='\n\n')
|
||||||
|
print(lasso(sizes[-1]))
|
||||||
|
print(band(l,s,b,r))
|
||||||
|
print(x,y)
|
||||||
|
print(f'{format(digits):,}')
|
||||||
|
# print(all(rep(size)==rep2(size) for size in sizes))
|
|
@ -0,0 +1,191 @@
|
||||||
|
__all__ = 'commons ffplay delevel convert sortbysize'.split()
|
||||||
|
|
||||||
|
from typing import Sequence, Iterable
|
||||||
|
import os, time, re, subprocess, sys
|
||||||
|
|
||||||
|
import filetype as ft
|
||||||
|
|
||||||
|
from .debug import show
|
||||||
|
|
||||||
|
|
||||||
|
defaultScriptDirectory = r'e:\projects\monties'
|
||||||
|
commons = {
|
||||||
|
"""
|
||||||
|
Hopefully such hacks as these are now obsolete
|
||||||
|
"""
|
||||||
|
'music': os.path.join(os.path.expanduser('~'), 'music', 'collection'),
|
||||||
|
'images': os.path.join(os.path.expanduser('~'), 'pictures'),
|
||||||
|
'pictures': os.path.join(os.path.expanduser('~'), 'pictures'),
|
||||||
|
'pics': os.path.join(os.path.expanduser('~'), 'pictures'),
|
||||||
|
'videos': os.path.join(os.path.expanduser('~'), 'videos'),
|
||||||
|
'ytdls': {
|
||||||
|
'music': {
|
||||||
|
'singles': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'singles'),
|
||||||
|
'mixes': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'mixes'),
|
||||||
|
'albums': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'album'),
|
||||||
|
},
|
||||||
|
'graff': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'graff'),
|
||||||
|
'bullshit': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'bullshitters'),
|
||||||
|
'bull': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'bullshitters'),
|
||||||
|
'code': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'cscode'),
|
||||||
|
'cs': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'cscode'),
|
||||||
|
'maths': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'maths'),
|
||||||
|
'math': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'maths'),
|
||||||
|
'movies': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'movies'),
|
||||||
|
'other': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'other'),
|
||||||
|
'physics': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'physics'),
|
||||||
|
'phys': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'physics'),
|
||||||
|
'politics': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'politics'),
|
||||||
|
'pol': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'politics'),
|
||||||
|
},
|
||||||
|
'documents': os.path.join(os.path.expanduser('~'), 'documents'),
|
||||||
|
'docs': os.path.join(os.path.expanduser('~'), 'documents'),
|
||||||
|
'downloads': os.path.join(os.path.expanduser('~'), 'downloads'),
|
||||||
|
'desktop': os.path.join(os.path.expanduser('~'), 'desktop'),
|
||||||
|
'books': os.path.join(os.path.expanduser('~'), 'documents', 'bookes'),
|
||||||
|
'monties': os.path.join(defaultScriptDirectory, str(time.localtime()[0])),
|
||||||
|
'scripts': defaultScriptDirectory,
|
||||||
|
'demos': os.path.join(defaultScriptDirectory, 'demos'),
|
||||||
|
'site': os.path.join(sys.exec_prefix, 'lib', 'site-packages'),
|
||||||
|
'home': os.path.expanduser('~'),
|
||||||
|
'user': os.path.expanduser('~'),
|
||||||
|
'root': os.path.expanduser('~'),
|
||||||
|
'~': os.path.expanduser('~'),
|
||||||
|
}
|
||||||
|
os.makedirs(commons['monties'], exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def ffplay(files:Sequence[str], hide:bool=True, fullscreen:bool=True, loop:bool=True, quiet:bool=True, randomize:bool=True, silent:bool=False) -> None:
|
||||||
|
"""
|
||||||
|
Play a collection of files using ffmpeg's ffplay cli
|
||||||
|
Dependencies: FFMPEG,subprocess,os
|
||||||
|
In: files,fullscreen=True,quiet=True
|
||||||
|
Out: None
|
||||||
|
|
||||||
|
If entering files as a string, separate each path by an asterisk (*), othewise feel free to use any iterator
|
||||||
|
-loop {f"-loop {loop}" if loop else ""}
|
||||||
|
"""
|
||||||
|
namext = lambda file: os.path.split(file)[1]
|
||||||
|
nome = lambda file: os.path.splitext(namext(file))[0]
|
||||||
|
ext = lambda file: os.path.splitext(file)[1]
|
||||||
|
isvid = lambda file: ft.match(file) in ft.video_matchers
|
||||||
|
vidtitle = lambda vid: '-'.join(i.strip() for i in vid.split('-')[:-1])
|
||||||
|
albumtrack = lambda file: bool(re.search(f'\d+\s.+{ext(file)}', file, re.I))
|
||||||
|
attitle = lambda file: ' '.join(i.strip() for i in nome(file).split(' ')[1:])
|
||||||
|
aov = lambda file: albumtrack(file) or isvid(file)
|
||||||
|
title = lambda file: ''.join(i for i in os.path.splitext(namext(file)[1])[0] if i not in '0123456789').strip()
|
||||||
|
windowtitle = lambda file: [namext(file),[attitle(file),vidtitle(file)][isvid(file)]][aov(file)]
|
||||||
|
play = lambda file: subprocess.run(f'ffplay {("", "-nodisp")[hide]} -window_title "{windowtitle(file)}" -autoexit {"-fs" if fullscreen else ""} {"-v error" if quiet else ""} "{file}"')
|
||||||
|
files = files.split('*') if isinstance(files,str) else files
|
||||||
|
if loop:
|
||||||
|
while (1 if loop==True else loop+1):
|
||||||
|
files = shuffle(files) if randomize else files
|
||||||
|
for i,f in enumerate(files, 1):
|
||||||
|
if os.path.isdir(f):
|
||||||
|
fls = [os.path.join(f, i) for i in gather(f, names=False)]
|
||||||
|
for j,file in enumerate(fls, 1):
|
||||||
|
name = os.path.split(file)[1]
|
||||||
|
print(f'{j} of {len(fls)}:\t{name}') if not silent else None
|
||||||
|
ffplay(file, hide, fullscreen, False, quiet, randomize, True)
|
||||||
|
else:
|
||||||
|
folder,name = os.path.split(f)
|
||||||
|
print(f'{i} of {len(files)}:\t{name}') if not silent else None
|
||||||
|
play(f)
|
||||||
|
loop -= 1
|
||||||
|
else:
|
||||||
|
files = shuffle(files) if randomize else files
|
||||||
|
for i, f in enumerate(files, 1):
|
||||||
|
if os.path.isdir(f):
|
||||||
|
fls = [os.path.join(f, i) for i in gather(f, names=False)]
|
||||||
|
for j, file in enumerate(fls, 1):
|
||||||
|
name = os.path.split(file)[1]
|
||||||
|
print(f'{j} of {len(fls)}:\t{name}') if not silent else None
|
||||||
|
ffplay(file, hide, fullscreen, False, quiet, randomize, True)
|
||||||
|
else:
|
||||||
|
print(f'{i} of {len(files)}:\t{title(f)}') if not silent else None
|
||||||
|
play(f)
|
||||||
|
|
||||||
|
|
||||||
|
def delevel(path:str, steps:int=1) -> str:
|
||||||
|
"""
|
||||||
|
This will climb the given path tree by the given number of steps.
|
||||||
|
No matter how large the number of steps, it will stop as soon as it reaches the root.
|
||||||
|
Probably needs revision for paths on systems which hide the root drive.
|
||||||
|
example
|
||||||
|
>>> for i in range(4):print(delevel(r'c:/users/admin',i))
|
||||||
|
c:/users/admin
|
||||||
|
c:/users
|
||||||
|
c:/
|
||||||
|
c:/
|
||||||
|
dependencies: os.sep
|
||||||
|
"""
|
||||||
|
while steps and (len(path.split(os.sep))-1):
|
||||||
|
path = os.sep.join((path.split(os.sep)[:-1]))
|
||||||
|
steps -= 1
|
||||||
|
return path if not path.endswith(':') else path+os.sep
|
||||||
|
|
||||||
|
|
||||||
|
def convert(file:str, format:str='.wav', bitRate:int=450, delete:bool=False, options:str=''):
|
||||||
|
"""
|
||||||
|
Convert an audio file
|
||||||
|
Dependencies: m3ta.nameUpdater, send2trash, ffmpeg, subprocess, os
|
||||||
|
In: file,format='.wav',bitRate=450,delete=False,options=''
|
||||||
|
Outs: None
|
||||||
|
"""
|
||||||
|
trash = tryimport('send2trash','send2trash','remove','os')
|
||||||
|
os.chdir(os.path.split(file)[0])
|
||||||
|
|
||||||
|
_title = lambda file: file.split(os.sep)[-1].split('.')[0]
|
||||||
|
_new = lambda file,format: nameUpdater(_title(file)+format)
|
||||||
|
_name = lambda file: file.split(os.sep)[-1]
|
||||||
|
format = '.' + format if '.' != format[0] else format
|
||||||
|
|
||||||
|
name = _title(file)
|
||||||
|
new = _new(file,format)
|
||||||
|
cmd = f'ffmpeg -y -i "{file}" -ab {bitRate*1000} "{new}"' if bitRate != 0 else f'ffmpeg {options} -y -i "{file}" "{new}"'
|
||||||
|
announcement = f"Converting:\n\t{file} --> {new}\n\t{cmd=}"
|
||||||
|
print(announcement)
|
||||||
|
subprocess.run(cmd)
|
||||||
|
print('Conversion is complete')
|
||||||
|
if delete:
|
||||||
|
trash(file)
|
||||||
|
print(f'Deletion is complete\n\t{new}\n\n\n')
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
def sortbysize(files:Iterable[str]=None) -> list:
|
||||||
|
"""
|
||||||
|
Sort a collection of file paths by the size of the corresponding files (largest to smallest)
|
||||||
|
Dependencies: os
|
||||||
|
In: iterableCollection
|
||||||
|
Out: list
|
||||||
|
"""
|
||||||
|
files = [os.getcwd(), list(files)][bool(files)]
|
||||||
|
size = lambda file: os.stat(file).st_size
|
||||||
|
out = []
|
||||||
|
while len(files)>0:
|
||||||
|
sizes = set(size(file) for file in files)
|
||||||
|
for file in files:
|
||||||
|
if size(file) == max(sizes):
|
||||||
|
out.append(file)
|
||||||
|
files.remove(file)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mock = 'drive: users user place subplace file.extension.secondextension'.split()
|
||||||
|
# mock = 'drive: users user place subplace readme'.split()
|
||||||
|
testPaths = [
|
||||||
|
''.join(mock),
|
||||||
|
os.path.join(*mock),
|
||||||
|
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[:-1]),
|
||||||
|
'/'.join(mock[:-1]),
|
||||||
|
mock[-1]
|
||||||
|
]
|
||||||
|
show(testPaths)
|
||||||
|
# show(zip(map(type,map(lambda x: Address(x).obj, testPaths)),testPaths))
|
||||||
|
# show(zip(testPaths,map(normalize,testPaths)))
|
||||||
|
# show(zip(testPaths,map(hasdirs,testPaths)))
|
||||||
|
# show(zip(testPaths,map(likeFile,testPaths)))
|
|
@ -0,0 +1,123 @@
|
||||||
|
__all__ = "trim normalize hasdirs likefile multisplit forbiddens nameSpacer".split()
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
from .debug import *
|
||||||
|
|
||||||
|
|
||||||
|
forbiddens = r'\/:?*<>|"'
|
||||||
|
|
||||||
|
def trim(path:str, edge:str=os.sep) -> str:
|
||||||
|
"""
|
||||||
|
Remove trailing/leading separators (or edges) from a path string
|
||||||
|
"""
|
||||||
|
out = path[:]
|
||||||
|
while any(out.startswith(x) for x in (edge,' ')):
|
||||||
|
out = out[1:]
|
||||||
|
while any(out.endswith(x) for x in (edge,' ')):
|
||||||
|
out = out[:-1]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(path:str, relative:bool=False, force:bool=False) -> str:
|
||||||
|
"""
|
||||||
|
Standardize a path for a current operating system.
|
||||||
|
Given an equivalently structured file/project system, this should make code reusable across platforms
|
||||||
|
If force is true, all forbidden characters will be replaced with an empty string
|
||||||
|
"""
|
||||||
|
other = ''.join(i for i in '\/' if not i==os.sep)
|
||||||
|
if force:
|
||||||
|
new = ''.join(i for i in path if not i in forbiddens)
|
||||||
|
else:
|
||||||
|
new = path[:]
|
||||||
|
if other in path:
|
||||||
|
terms = []
|
||||||
|
for term in path.split(os.sep):
|
||||||
|
if other in term:
|
||||||
|
for part in term.split(other):
|
||||||
|
terms.append(part)
|
||||||
|
else:
|
||||||
|
terms.append(term)
|
||||||
|
new = os.path.join(*terms)
|
||||||
|
if relative:
|
||||||
|
new = '.'+os.sep+path
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
def hasdirs(path:str) -> bool:
|
||||||
|
"""
|
||||||
|
check if a path string contains directories or not
|
||||||
|
"""
|
||||||
|
return bool(re.search(re.escape(os.sep), normalize(path)))
|
||||||
|
|
||||||
|
|
||||||
|
def likefile(path:str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a path string looks like a file or not
|
||||||
|
"""
|
||||||
|
path = normalize(path)
|
||||||
|
return bool(re.search('readme$|.+\.(\S)+$',path.split(os.sep)[-1], re.I))
|
||||||
|
|
||||||
|
|
||||||
|
def multisplit(splitters:Iterable[str], target:str='abc') -> generator:
|
||||||
|
"""
|
||||||
|
Split a string by a the elements of a sequence
|
||||||
|
>>> list(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||||
|
['wma', 'wmv', 'mp3', 'vga', 'mp4', 'ogg']
|
||||||
|
"""
|
||||||
|
splitters = iter(splitters)
|
||||||
|
result = target.split(next(splitters))
|
||||||
|
for splitter in splitters:
|
||||||
|
result = [*chain.from_iterable(i.split(splitter) for i in result)]
|
||||||
|
yield from filter(None, result)
|
||||||
|
|
||||||
|
|
||||||
|
def nameSpacer(path:str, sep:str='_') -> str:
|
||||||
|
"""
|
||||||
|
Returns a unique version of a given string by appending an integer
|
||||||
|
Dependencies: os module
|
||||||
|
In: string
|
||||||
|
Out: string
|
||||||
|
"""
|
||||||
|
id = 2
|
||||||
|
oldPath = path[:]
|
||||||
|
while os.path.exists(path): ##for general use
|
||||||
|
newPath = list(os.path.splitext(path))
|
||||||
|
if sep in newPath[0]:
|
||||||
|
if newPath[0].split(sep)[-1].isnumeric():
|
||||||
|
# print('case1a')
|
||||||
|
id = newPath[0].split(sep)[-1]
|
||||||
|
newPath[0] = newPath[0].replace(f'{sep}{id}', f'{sep}{str(int(id)+1)}')
|
||||||
|
path = ''.join(newPath)
|
||||||
|
else:
|
||||||
|
# print('case1b')
|
||||||
|
newPath[0] += f'{sep}{id}'
|
||||||
|
path = ''.join(newPath)
|
||||||
|
id += 1
|
||||||
|
else:
|
||||||
|
# print('case2')
|
||||||
|
newPath[0] += f'{sep}{id}'
|
||||||
|
path = ''.join(newPath)
|
||||||
|
id += 1
|
||||||
|
return path
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mock = 'drive: users user place subplace file.extension.secondextension'.split()
|
||||||
|
# mock = 'drive: users user place subplace readme'.split()
|
||||||
|
testPaths = [
|
||||||
|
''.join(mock),
|
||||||
|
os.path.join(*mock),
|
||||||
|
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||||
|
os.path.join(*mock[:-1]),
|
||||||
|
'/'.join(mock[:-1]),
|
||||||
|
mock[-1]
|
||||||
|
]
|
||||||
|
tests = [(eval(f'{f}(r"{p}")'), f, p) for f in __all__ for p in testPaths]
|
||||||
|
|
||||||
|
show(tests)
|
||||||
|
# show(zip(map(type, map(lambda x: Address(x).obj, testPaths)), testPaths))
|
||||||
|
# show(zip(testPaths, map(normalize, testPaths)))
|
||||||
|
# show(zip(testPaths, map(hasdirs, testPaths)))
|
||||||
|
# show(zip(testPaths, map(likefile, testPaths)))
|
|
@ -0,0 +1,6 @@
|
||||||
|
[build-system]
|
||||||
|
requires = [
|
||||||
|
"setuptools>=42",
|
||||||
|
"wheel"
|
||||||
|
]
|
||||||
|
build-backend = "setuptools.build_meta"
|
|
@ -0,0 +1,32 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open('README.md', 'r') as fob:
|
||||||
|
long_description = fob.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='filey',
|
||||||
|
version='0.0.1',
|
||||||
|
author='Kenneth Elisandro',
|
||||||
|
author_email='eli2and40@tilde.club',
|
||||||
|
url='https://tildegit.org/eli2and40/cabinet',
|
||||||
|
# packages=find_packages(),
|
||||||
|
packages=['filey'],
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
],
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
keywords='utilities productivity',
|
||||||
|
license='MIT',
|
||||||
|
requires=[
|
||||||
|
'filetype',
|
||||||
|
'audio_metadata',
|
||||||
|
'send2trash',
|
||||||
|
'sl4ng'
|
||||||
|
],
|
||||||
|
# py_modules=['filey'],
|
||||||
|
python_requires='>=3.8',
|
||||||
|
|
||||||
|
)
|
Loading…
Reference in New Issue