From 0d77b9a67d28d9f3948ce9d320753fde8e8ac199 Mon Sep 17 00:00:00 2001 From: kendfss Date: Thu, 12 Aug 2021 19:28:02 +0100 Subject: [PATCH] sorry --- filey/handles.py | 136 ++++++++++++++++++++++++++++++++++++++--------- filey/shell.py | 75 ++++++++++++-------------- filey/walking.py | 23 ++++---- setup.py | 22 ++++---- 4 files changed, 174 insertions(+), 82 deletions(-) diff --git a/filey/handles.py b/filey/handles.py index 2aa3747..96d9a96 100644 --- a/filey/handles.py +++ b/filey/handles.py @@ -27,10 +27,10 @@ TODO Caches for pickling (backup simplification) 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 typing import Dict, Iterable, Iterator, List, Sequence, TypeAlias +from typing import Iterable, Iterator, TypeAlias, Callable from warnings import warn import io, os, pathlib, re, sys, shutil @@ -155,7 +155,8 @@ class Thing: name = type(self).__name__.split('.')[-1] size = f", size={self.size}" if self.isfile or isinstance(self, File) else '' 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: """ @@ -206,7 +207,7 @@ class Thing: Determine if self.path points to a file or folder and create the corresponding object """ if self.isfile: - return Thing(self.path) + return File(self.path) elif self.isdir: return Place(self.path) else: @@ -223,17 +224,18 @@ class Thing: """ 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 - def ancestors(self) -> tuple: + def ancestors(self) -> tuple[_Place]: """ Return consecutive ADirs until the ADrive is reached """ level = [] p = self.path - while p != delevel(p): - p = delevel(p) + while p != shell.delevel(p): + p = shell.delevel(p) level.append(p) return tuple(Thing(i).obj for i in level)[::-1] @property @@ -243,7 +245,7 @@ class Thing: """ return (i for i in self.up if isinstance(i, type(self))) @property - def neighbours(self) -> tuple[_File, _Place]: + def neighbours(self) -> tuple[_Placefile]: """ Everything in the same Place """ @@ -269,13 +271,15 @@ class Thing: @property 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.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] + '/') + line = '\n\t' + ('', '.' * i)[i > 0] + i * ' ' + [i for i in str(anc).split(os.sep) if i][-1] + '/' + print(line) return self @property def isempty(self): @@ -308,7 +312,7 @@ class Thing: path 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: """ Implements the unix command 'touch', which updates the 'date modified' of the content at the path @@ -346,7 +350,7 @@ class Thing: else: new = self.path 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) out = Thing(new).obj return out.touch() if touch else out @@ -459,16 +463,19 @@ class Place(Thing): Place('.') == Place(os.getcwd()) """ 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): 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) self.index = -1 def __len__(self): @@ -480,7 +487,7 @@ class Place(Thing): return len(os.listdir(self.path)) > 0 def __iter__(self) -> Iterator[_Placefile]: return self - def __next__(self) -> Sequence[_Placefile]: + def __next__(self) -> _Placefile: if self.index _Place: + def add(self, other:Thing, copy:bool=False) -> _Place: """ Introduce new elements. Send an address-like object to self. """ @@ -684,6 +691,87 @@ class Audio(File): @property def title(self): 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__': # show(locals().keys()) diff --git a/filey/shell.py b/filey/shell.py index b5cdd30..66f2234 100644 --- a/filey/shell.py +++ b/filey/shell.py @@ -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() from itertools import filterfalse @@ -30,44 +30,39 @@ def delevel(path:str, steps:int=1) -> str: steps -= 1 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: - tree: - /folder - /file.ext - /file_2.ext - - >>> namespacer('file', sep='-', start=2) - file-2.ext - >>> namespacer('file', sep='_', start=2) - file_3.ext - >>> namespacer('file', sep='_', start=0) - file_0.ext + params + format + a formattable string amenable to both "name" and "index" key words + + examples + >>> my_path = "c:/users" + >>> NameSpacer()(my_path) + c:/users_2 + >>> NameSpacer("{name} ({index})")(my_path) + c:/users (2) """ - id = start - oldPath = path[:] - while os.path.exists(path): - 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 + def __init__(self, format:str="_{index}"): + self.format = format + def __call__(self, path:str, index:int=2) -> str: + if not os.path.exists(path): + return path + if os.path.exists(new:=self.new(path, index)): + return self(path, index + 1) + return new + def new(self, path:str, index:int) -> str: + weirdos = ".tar".split() + name, ext = os.path.splitext(path) + while any(name.endswith(weirdo:=w) for w in weirdos): + if name != (name:=name.removesuffix(weirdo)): + ext = weirdo + ext + return name + self.format.format(index=index) + ext +namespacer = NameSpacer() 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() 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.chdir(arg if recursive else home) 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: raise ValueError(f"Destination's path doesn't point to a directory") os.makedirs(dest, exist_ok=True) - root, name = os.path.split(path) + root, name = os.path.split(source) new = os.path.join(dest, name) - os.rename(path, new) + os.rename(source, new) return new mv = move @@ -337,7 +332,7 @@ def isempty(path:str, make:bool=False) -> bool: with open(path, 'rb') as f: return not bool(len(tuple(i for i in f))) elif os.path.isdir(path): - return not bool(len(os.listdir(file))) + return not bool(len(os.listdir(path))) elif make: if os.path.splitext(path)[-1]: x = open(path, 'x') diff --git a/filey/walking.py b/filey/walking.py index 1b9bb17..f539673 100644 --- a/filey/walking.py +++ b/filey/walking.py @@ -7,6 +7,9 @@ from itertools import permutations, chain import os, re 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]: """ 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)) for name in os.listdir(root): path = os.path.join(root, name) - if os.path.isdir(path): + if os.path.isdir(path) and not name.lower() in LOCKOUTS: if dirs: yield (name, path)[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)) pat = parse_extensions(exts) predicate = lambda x: not bool(pat.search(x)) if negative else bool(pat.search(x)) - for name in os.listdir(root): - path = os.path.join(root, name) - if os.path.isdir(path): - yield from files(path, exts=exts, negative=negative, absolute=absolute) - elif predicate(path): - yield (name, path)[absolute] + name = os.path.split(root)[1] + if not (name.lower() in LOCKOUTS or root.lower() in LOCKOUTS): + for name in os.listdir(root): + path = os.path.join(root, name) + if os.path.isdir(path) and not name.lower() in LOCKOUTS: + 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]: """ @@ -94,7 +99,7 @@ def folders(root:str='.', absolute:bool=True) -> Iterator[str]: root = (str, os.path.realpath)[absolute](str(root)) for name in os.listdir(root): 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 from folders(path, absolute=absolute) @@ -248,4 +253,4 @@ if __name__ == "__main__": # box4 = [*folders(folder, True)] # show(box4, 0, 1) # show(search(folder, '__init__')) - show(search(folder, '_', 'png')) \ No newline at end of file + show(search(folder, '_', 'png')) diff --git a/setup.py b/setup.py index 38bccd6..a859274 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,15 @@ from setuptools import setup, find_packages with open('README.md', 'r') as fob: long_description = fob.read() +with open('requirements.txt', 'r') as fob: + requirements = fob.readlines() + setup( name='filey', version='0.0.2', author='Kenneth Sabalo', author_email='kennethsantanasablo@gmail.com', - url='https://tildegit.org/eli2and40/filey', + url='https://github.com/kendfss/filey', packages=['filey'], classifiers=[ "Programming Language :: Python :: 3", @@ -19,13 +22,14 @@ setup( long_description_content_type='text/markdown', keywords='utilities operating path file system', license='MIT', - requires=[ - "audio_metadata", - "dill", - "filetype", - "send2trash", - "sl4ng", - "pypeclip", - ], + requires=requirements, + # requires=[ + # "audio_metadata", + # "dill", + # "filetype", + # "send2trash", + # "sl4ng", + # "pypeclip", + # ], python_requires='>3.9', ) \ No newline at end of file