first commit

This commit is contained in:
kendfss 2021-02-20 02:28:03 +01:00
commit 2d682a6115
22 changed files with 3064 additions and 0 deletions

19
LICENSE Normal file
View File

@ -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.

26
README.md Normal file
View File

@ -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

BIN
filey/README.md Normal file

Binary file not shown.

605
filey/__init__.py Normal file
View File

@ -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))

View File

@ -0,0 +1,7 @@
import configparser
cfg = configparser.ConfigParser()
# cfg[]
if __name__=='__main__':
pass

View File

@ -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'))

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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))

View File

@ -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))

24
filey/preamble/utils.py Normal file
View File

@ -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))

View File

View File

View File

4
filey/utils/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from .strings import *
from .magnitudes import *
from .debug import *
from .media import *

99
filey/utils/debug.py Normal file
View File

@ -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]

100
filey/utils/magnitudes.py Normal file
View File

@ -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))

191
filey/utils/media.py Normal file
View File

@ -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)))

123
filey/utils/strings.py Normal file
View File

@ -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)))

6
pyproject.toml Normal file
View File

@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

32
setup.py Normal file
View File

@ -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',
)