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