This commit is contained in:
kendfss 2021-08-12 19:28:02 +01:00
parent 9a67abbe20
commit 0d77b9a67d
4 changed files with 174 additions and 82 deletions

View File

@ -27,10 +27,10 @@ TODO
Caches for pickling (backup simplification) Caches for pickling (backup simplification)
Remove Size from repr for Directories. Large ones take too long to initialize Remove Size from repr for Directories. Large ones take too long to initialize
""" """
__all__ = 'forbiddens Thing Place Thing Path Address Directory Folder File Library'.split() __all__ = 'forbiddens Thing Place Thing Path Address Directory Folder File Library Scanner'.split()
from itertools import chain from itertools import chain
from typing import Dict, Iterable, Iterator, List, Sequence, TypeAlias from typing import Iterable, Iterator, TypeAlias, Callable
from warnings import warn from warnings import warn
import io, os, pathlib, re, sys, shutil import io, os, pathlib, re, sys, shutil
@ -155,7 +155,8 @@ class Thing:
name = type(self).__name__.split('.')[-1] name = type(self).__name__.split('.')[-1]
size = f", size={self.size}" if self.isfile or isinstance(self, File) else '' size = f", size={self.size}" if self.isfile or isinstance(self, File) else ''
real = (f", real={self.exists}", '')[self.exists] real = (f", real={self.exists}", '')[self.exists]
return f"{name}(name={self.name}, dir={self.up.name}{size}{real})" up = f", dir={self.dir.name}" if self.dir.path != self.path else ''
return f"{name}(name={self.name}{up}{size}{real})"
def create(self, content:str|bytes|bytearray=None, raw:bool=False, exist_ok:bool=True, mode:int=511) -> _Path: def create(self, content:str|bytes|bytearray=None, raw:bool=False, exist_ok:bool=True, mode:int=511) -> _Path:
""" """
@ -206,7 +207,7 @@ class Thing:
Determine if self.path points to a file or folder and create the corresponding object Determine if self.path points to a file or folder and create the corresponding object
""" """
if self.isfile: if self.isfile:
return Thing(self.path) return File(self.path)
elif self.isdir: elif self.isdir:
return Place(self.path) return Place(self.path)
else: else:
@ -223,17 +224,18 @@ class Thing:
""" """
Return the name of the referent Return the name of the referent
""" """
return os.path.split(self.path)[1] path, nome = os.path.split(self.path)
return nome if nome else path
@property @property
def ancestors(self) -> tuple: def ancestors(self) -> tuple[_Place]:
""" """
Return consecutive ADirs until the ADrive is reached Return consecutive ADirs until the ADrive is reached
""" """
level = [] level = []
p = self.path p = self.path
while p != delevel(p): while p != shell.delevel(p):
p = delevel(p) p = shell.delevel(p)
level.append(p) level.append(p)
return tuple(Thing(i).obj for i in level)[::-1] return tuple(Thing(i).obj for i in level)[::-1]
@property @property
@ -243,7 +245,7 @@ class Thing:
""" """
return (i for i in self.up if isinstance(i, type(self))) return (i for i in self.up if isinstance(i, type(self)))
@property @property
def neighbours(self) -> tuple[_File, _Place]: def neighbours(self) -> tuple[_Placefile]:
""" """
Everything in the same Place Everything in the same Place
""" """
@ -269,13 +271,15 @@ class Thing:
@property @property
def ancestry(self) -> str: def ancestry(self) -> str:
""" """
A fancy representation of the tree from the apparent drive up to the given path A nice representation of the tree from the apparent drive up to the given path
""" """
print(f'ancestry({self.name})') tree = f'ancestry({self.name})'
print(tree)
ancs = list(self.ancestors[1:]) ancs = list(self.ancestors[1:])
ancs.append(self.path) ancs.append(self.path)
for i, anc in enumerate(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] + '/') line = '\n\t' + ('', '.' * i)[i > 0] + i * ' ' + [i for i in str(anc).split(os.sep) if i][-1] + '/'
print(line)
return self return self
@property @property
def isempty(self): def isempty(self):
@ -308,7 +312,7 @@ class Thing:
path path
return (True -> string, False -> Thing-like object) return (True -> string, False -> Thing-like object)
""" """
return delevel(self.path, steps) if path else Place(delevel(self.path, steps)) return shell.delevel(self.path, steps) if path else Place(shell.delevel(self.path, steps))
def touch(self) -> _Path: def touch(self) -> _Path:
""" """
Implements the unix command 'touch', which updates the 'date modified' of the content at the path Implements the unix command 'touch', which updates the 'date modified' of the content at the path
@ -346,7 +350,7 @@ class Thing:
else: else:
new = self.path new = self.path
new = shell.namespacer(new, sep=sep) new = shell.namespacer(new, sep=sep)
os.makedirs(delevel(new), exist_ok=True) os.makedirs(shell.delevel(new), exist_ok=True)
copier(self.path, new) copier(self.path, new)
out = Thing(new).obj out = Thing(new).obj
return out.touch() if touch else out return out.touch() if touch else out
@ -459,16 +463,19 @@ class Place(Thing):
Place('.') == Place(os.getcwd()) Place('.') == Place(os.getcwd())
""" """
def __init__(self, path:str='NewPlace'): def __init__(self, path:str='NewPlace'):
if path=='.':
path = os.getcwd()
elif path == 'NewPlace':
path = shell.namespacer(path)
elif path == '~':
path = os.path.expanduser(path)
path = os.path.abspath(shell.trim(path))
if os.path.isfile(path): if os.path.isfile(path):
raise ValueError("Given path corresponds to a file") raise ValueError("Given path corresponds to a file")
self.path = os.path.realpath(path) if path.startswith(r"\\wsl$"):
self.path = path
else:
if path=='.':
path = os.getcwd()
elif path == 'NewPlace':
path = shell.namespacer(path)
elif path == '~':
path = os.path.expanduser(path)
path = os.path.abspath(shell.trim(path))
self.path = os.path.realpath(path)
super(type(self), self).__init__(path) super(type(self), self).__init__(path)
self.index = -1 self.index = -1
def __len__(self): def __len__(self):
@ -480,7 +487,7 @@ class Place(Thing):
return len(os.listdir(self.path)) > 0 return len(os.listdir(self.path)) > 0
def __iter__(self) -> Iterator[_Placefile]: def __iter__(self) -> Iterator[_Placefile]:
return self return self
def __next__(self) -> Sequence[_Placefile]: def __next__(self) -> _Placefile:
if self.index<len(self)-1: if self.index<len(self)-1:
self.index += 1 self.index += 1
return self.content[self.index] return self.content[self.index]
@ -626,7 +633,7 @@ class Place(Thing):
return not self.depth return not self.depth
def add(self, other:Address, copy:bool=False) -> _Place: def add(self, other:Thing, copy:bool=False) -> _Place:
""" """
Introduce new elements. Send an address-like object to self. Introduce new elements. Send an address-like object to self.
""" """
@ -684,6 +691,87 @@ class Audio(File):
@property @property
def title(self): def title(self):
return self.tags['title'] return self.tags['title']
class Scanner:
"""
Object which scans text files, and their names, for given keywords
example
>>> s = Scanner("f = ma")
>>> s("./principia.txt", strict=True)
True
>>> s("./bible.txt", strict=True)
False
>>> s("./principia.txt", strict=False)
True
>>> s("./bible.txt", strict=False)
True
"""
def __init__(self, keywords:str, mode:str='r', strict:bool=True, prescaped:bool=False, case:bool=False, opener:Callable=open, lines:bool=True):
"""
params:
keywords
terms to search for
mode
'r' or 'rb'
strict
True -> search for words
False -> clauses
prescaped
whether or not terms have already been regex escaped
case
true -> case sensitive
opener
must return an object with a "readlines" or "read" method (depends on lines)
lines
wheter or not to scan by lines
"""
self.__case = case
self.__keywords = keywords
self.opener = opener
self.lines = lines
self.mode = mode
self.strict = strict
self.prescaped = prescaped
@property
def keywords(self):
"""
handles any necessary escaping
"""
return re.escape(self.__keywords) if not self.prescaped else self.__keywords
@property
def case(self):
"""
standardize the case-fold setting
"""
return re.I if not self.__case else 0
@property
def pattern(self):
"""
compile the search pattern
"""
return re.compile(
(
'|'.join(self.keywords.split()),
self.keywords
)[self.strict],
self.case
)
def __call__(self, path:str, lines:bool=None) -> bool:
"""
Scan a file at a given path for a predefined word/clause, you can also override the default lines argument
"""
if isinstance(lines, type(None)):
lines = self.lines
try:
with self.opener(path, self.mode) as fob:
method = (fob.read, fob.readlines)[lines]
return self.pattern.search(path) or any(map(self.pattern.search, method()))
except UnicodeDecodeError:
return False
if __name__ == '__main__': if __name__ == '__main__':
# show(locals().keys()) # show(locals().keys())

View File

@ -1,4 +1,4 @@
# __all__ = "discard unarchive create_enter audio_or_video namespacer isempty move send2trash ffplay trim move_file delevel convert cat".split() # __all__ = "discard unarchive create_enter audio_or_video NameSpacer namespacer isempty move send2trash ffplay trim move_file delevel convert cat".split()
__all__ = "mcd mv move_file rm ffplay convert cat".split() __all__ = "mcd mv move_file rm ffplay convert cat".split()
from itertools import filterfalse from itertools import filterfalse
@ -30,44 +30,39 @@ def delevel(path:str, steps:int=1) -> str:
steps -= 1 steps -= 1
return path if not path.endswith(':') else path+os.sep return path if not path.endswith(':') else path+os.sep
def namespacer(path:str, sep:str='_', start:int=2) -> str:
class NameSpacer:
""" """
Returns a unique version of a given string by appending an integer Create an incrementation of a path if it already exists on the local system
example: params
tree: format
/folder a formattable string amenable to both "name" and "index" key words
/file.ext
/file_2.ext examples
>>> my_path = "c:/users"
>>> namespacer('file', sep='-', start=2) >>> NameSpacer()(my_path)
file-2.ext c:/users_2
>>> namespacer('file', sep='_', start=2) >>> NameSpacer("{name} ({index})")(my_path)
file_3.ext c:/users (2)
>>> namespacer('file', sep='_', start=0)
file_0.ext
""" """
id = start def __init__(self, format:str="_{index}"):
oldPath = path[:] self.format = format
while os.path.exists(path): def __call__(self, path:str, index:int=2) -> str:
newPath = list(os.path.splitext(path)) if not os.path.exists(path):
if sep in newPath[0]: return path
if newPath[0].split(sep)[-1].isnumeric(): if os.path.exists(new:=self.new(path, index)):
# print('case1a') return self(path, index + 1)
id = newPath[0].split(sep)[-1] return new
newPath[0] = newPath[0].replace(f'{sep}{id}', f'{sep}{str(int(id)+1)}') def new(self, path:str, index:int) -> str:
path = ''.join(newPath) weirdos = ".tar".split()
else: name, ext = os.path.splitext(path)
# print('case1b') while any(name.endswith(weirdo:=w) for w in weirdos):
newPath[0] += f'{sep}{id}' if name != (name:=name.removesuffix(weirdo)):
path = ''.join(newPath) ext = weirdo + ext
id += 1 return name + self.format.format(index=index) + ext
else: namespacer = NameSpacer()
# print('case2')
newPath[0] += f'{sep}{id}'
path = ''.join(newPath)
id += 1
return path
def trim(path:str, edge:str=os.sep) -> str: def trim(path:str, edge:str=os.sep) -> str:
""" """
@ -123,7 +118,7 @@ def create_enter(*args:[str, Iterable[str]], go_back:bool=False, recursive:bool=
""" """
home = os.getcwd() home = os.getcwd()
for arg in flat(args): for arg in flat(args):
arg = nameSpacer(arg) if arg!='..' and not overwrite else arg arg = namespacer(arg) if arg!='..' and not overwrite else arg
os.makedirs(arg, exist_ok=exist_ok) os.makedirs(arg, exist_ok=exist_ok)
os.chdir(arg if recursive else home) os.chdir(arg if recursive else home)
last_stop = home if go_back else os.getcwd() last_stop = home if go_back else os.getcwd()
@ -150,9 +145,9 @@ def move(source:str, dest:str, make_dest:bool=False) -> str:
if not make_dest: if not make_dest:
raise ValueError(f"Destination's path doesn't point to a directory") raise ValueError(f"Destination's path doesn't point to a directory")
os.makedirs(dest, exist_ok=True) os.makedirs(dest, exist_ok=True)
root, name = os.path.split(path) root, name = os.path.split(source)
new = os.path.join(dest, name) new = os.path.join(dest, name)
os.rename(path, new) os.rename(source, new)
return new return new
mv = move mv = move
@ -337,7 +332,7 @@ def isempty(path:str, make:bool=False) -> bool:
with open(path, 'rb') as f: with open(path, 'rb') as f:
return not bool(len(tuple(i for i in f))) return not bool(len(tuple(i for i in f)))
elif os.path.isdir(path): elif os.path.isdir(path):
return not bool(len(os.listdir(file))) return not bool(len(os.listdir(path)))
elif make: elif make:
if os.path.splitext(path)[-1]: if os.path.splitext(path)[-1]:
x = open(path, 'x') x = open(path, 'x')

View File

@ -7,6 +7,9 @@ from itertools import permutations, chain
import os, re import os, re
from sl4ng import pop, show, multisplit, join, mainame, eq from sl4ng import pop, show, multisplit, join, mainame, eq
LOCKOUTS = "Config.Msi*System Volume Information*$Recycle.Bin*C:\\Users\\Administrator".lower().split('*')
def walk(root:str='.', dirs:bool=False, absolute:bool=True) -> Iterator[str]: def walk(root:str='.', dirs:bool=False, absolute:bool=True) -> Iterator[str]:
""" """
Walk a directory's tree yielding paths to any files and/or folders along the way Walk a directory's tree yielding paths to any files and/or folders along the way
@ -29,7 +32,7 @@ def walk(root:str='.', dirs:bool=False, absolute:bool=True) -> Iterator[str]:
root = (str, os.path.realpath)[absolute](str(root)) root = (str, os.path.realpath)[absolute](str(root))
for name in os.listdir(root): for name in os.listdir(root):
path = os.path.join(root, name) path = os.path.join(root, name)
if os.path.isdir(path): if os.path.isdir(path) and not name.lower() in LOCKOUTS:
if dirs: if dirs:
yield (name, path)[absolute] yield (name, path)[absolute]
yield from walk(path, dirs=dirs, absolute=absolute) yield from walk(path, dirs=dirs, absolute=absolute)
@ -67,12 +70,14 @@ def files(root:str='.', exts:str='', negative:bool=False, absolute:bool=True) ->
root = (str, os.path.realpath)[absolute](str(root)) root = (str, os.path.realpath)[absolute](str(root))
pat = parse_extensions(exts) pat = parse_extensions(exts)
predicate = lambda x: not bool(pat.search(x)) if negative else bool(pat.search(x)) predicate = lambda x: not bool(pat.search(x)) if negative else bool(pat.search(x))
for name in os.listdir(root): name = os.path.split(root)[1]
path = os.path.join(root, name) if not (name.lower() in LOCKOUTS or root.lower() in LOCKOUTS):
if os.path.isdir(path): for name in os.listdir(root):
yield from files(path, exts=exts, negative=negative, absolute=absolute) path = os.path.join(root, name)
elif predicate(path): if os.path.isdir(path) and not name.lower() in LOCKOUTS:
yield (name, path)[absolute] yield from files(path, exts=exts, negative=negative, absolute=absolute)
elif predicate(path):
yield (name, path)[absolute]
def folders(root:str='.', absolute:bool=True) -> Iterator[str]: def folders(root:str='.', absolute:bool=True) -> Iterator[str]:
""" """
@ -94,7 +99,7 @@ def folders(root:str='.', absolute:bool=True) -> Iterator[str]:
root = (str, os.path.realpath)[absolute](str(root)) root = (str, os.path.realpath)[absolute](str(root))
for name in os.listdir(root): for name in os.listdir(root):
path = os.path.join(root, name) path = os.path.join(root, name)
if os.path.isdir(path): if os.path.isdir(path) and not name.lower() in LOCKOUTS:
yield (name, path)[absolute] yield (name, path)[absolute]
yield from folders(path, absolute=absolute) yield from folders(path, absolute=absolute)
@ -248,4 +253,4 @@ if __name__ == "__main__":
# box4 = [*folders(folder, True)] # box4 = [*folders(folder, True)]
# show(box4, 0, 1) # show(box4, 0, 1)
# show(search(folder, '__init__')) # show(search(folder, '__init__'))
show(search(folder, '_', 'png')) show(search(folder, '_', 'png'))

View File

@ -3,12 +3,15 @@ from setuptools import setup, find_packages
with open('README.md', 'r') as fob: with open('README.md', 'r') as fob:
long_description = fob.read() long_description = fob.read()
with open('requirements.txt', 'r') as fob:
requirements = fob.readlines()
setup( setup(
name='filey', name='filey',
version='0.0.2', version='0.0.2',
author='Kenneth Sabalo', author='Kenneth Sabalo',
author_email='kennethsantanasablo@gmail.com', author_email='kennethsantanasablo@gmail.com',
url='https://tildegit.org/eli2and40/filey', url='https://github.com/kendfss/filey',
packages=['filey'], packages=['filey'],
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
@ -19,13 +22,14 @@ setup(
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
keywords='utilities operating path file system', keywords='utilities operating path file system',
license='MIT', license='MIT',
requires=[ requires=requirements,
"audio_metadata", # requires=[
"dill", # "audio_metadata",
"filetype", # "dill",
"send2trash", # "filetype",
"sl4ng", # "send2trash",
"pypeclip", # "sl4ng",
], # "pypeclip",
# ],
python_requires='>3.9', python_requires='>3.9',
) )