sorry
This commit is contained in:
parent
9a67abbe20
commit
0d77b9a67d
136
filey/handles.py
136
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<len(self)-1:
|
||||
self.index += 1
|
||||
return self.content[self.index]
|
||||
|
@ -626,7 +633,7 @@ class Place(Thing):
|
|||
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.
|
||||
"""
|
||||
|
@ -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())
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'))
|
||||
show(search(folder, '_', 'png'))
|
||||
|
|
22
setup.py
22
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',
|
||||
)
|
Loading…
Reference in New Issue