transfered relevant sections from sl4ng. added shell utilities. simplified walker interface. added libraries and iterable searches for result refinement. removed legacy code (preambles). added basic audio support via ffmpeg
This commit is contained in:
parent
c76c05e134
commit
2075020f25
|
@ -127,3 +127,6 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
filey/testdir
|
||||
readme_test.py
|
||||
|
|
190
README.md
190
README.md
|
@ -0,0 +1,190 @@
|
|||
from typing import Dict, List, Callable
|
||||
from subprocess import run, Popen
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
import re, os, platform, sys
|
||||
|
||||
from pyperclip import copy, paste
|
||||
from sl4ng import show, getsource, pop, unzip, main, join, ffplay, regenerator, kill, kbd, convert
|
||||
from filey import Address, Directory, File
|
||||
import sl4ng, filey
|
||||
|
||||
def clear():
|
||||
Popen('clear')
|
||||
sleep(.2)
|
||||
def cls():
|
||||
clear()
|
||||
show(map(repr, cd), head=False, tail=False)
|
||||
|
||||
architecture = platform.architecture()
|
||||
|
||||
escaped = tuple(i for i in kbd() if re.escape(i)!=i)
|
||||
|
||||
this = __file__
|
||||
here = os.path.dirname(__file__)
|
||||
|
||||
c = Directory('c:/')
|
||||
e = Directory('e:/')
|
||||
f = Directory('f:/')
|
||||
|
||||
cd = Directory('.')
|
||||
user = Directory('~')
|
||||
documents = user/'documents'
|
||||
root = c if c.exists else None
|
||||
appdata = user/'appdata'
|
||||
|
||||
downs = user / 'Downloads'
|
||||
web = downs / 'tools/languages' # need library class
|
||||
|
||||
dev = Directory(r"e:/")
|
||||
sp = dev / 'shellpower'
|
||||
|
||||
git = dev / 'gitting'
|
||||
clones = git/'gitclone/clones'
|
||||
ignore = clones / 'github/gitignore'
|
||||
mdn = git / r'gitclone\clones\mdn\content\files\en-us'
|
||||
brython = git / r'gitclone\clones\brython-dev\brython\www\doc\en'
|
||||
|
||||
projects = dev / 'projects'
|
||||
monties = projects / 'monties'
|
||||
|
||||
site = dev / 'Languages\Python38-64\lib'
|
||||
docs = monties / 'docs'
|
||||
|
||||
prod = Directory(r'f:/')
|
||||
flps = prod / 'programs/project files/fl'
|
||||
|
||||
def killfl():
|
||||
kill('fl64')
|
||||
kill('ilbridge')
|
||||
|
||||
def chdir(path):
|
||||
global cd
|
||||
# import os
|
||||
os.chdir(path)
|
||||
cd = Directory(path)
|
||||
# os.chdir = chdir
|
||||
|
||||
# p = videos / "please refrain from touching the pelican\\told you so"
|
||||
# play = lambda x, d=p: ffplay((d/x).path, hide=False, loop=False, fullscreen=False)
|
||||
# rename = lambda x, y, d=p: (d/x).rename(y)
|
||||
# find = lambda words, ext='': show
|
||||
|
||||
class Scanner:
|
||||
"""
|
||||
Create an object which scans a text file for a given keyword
|
||||
"""
|
||||
def __init__(self, keywords:str, mode:str='r', strict:bool=True, prescaped:bool=False, casefold:bool=True, 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
|
||||
casefold
|
||||
true -> case insensitive
|
||||
opener
|
||||
must return an object with a "readlines" or "read" method (depends on lines)
|
||||
lines
|
||||
|
||||
"""
|
||||
self.casefold = casefold
|
||||
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 __casefold(self):
|
||||
"""
|
||||
standardize the case-fold setting
|
||||
"""
|
||||
return re.I if self.casefold else 0
|
||||
@property
|
||||
def pattern(self):
|
||||
"""
|
||||
compile the search pattern
|
||||
"""
|
||||
return re.compile(
|
||||
(
|
||||
join(self.__keywords.split(), '|'),
|
||||
self.__keywords
|
||||
)[self.strict],
|
||||
self.__casefold
|
||||
)
|
||||
def __call__(self, path:str, lines:bool=None):
|
||||
"""
|
||||
Scan a file at a given path for a predefined word/clause
|
||||
"""
|
||||
if isinstance(lines, type(None)):
|
||||
lines = self.lines
|
||||
with self.opener(path, self.mode) as fob:
|
||||
if lines:
|
||||
return self.pattern.search(path) or any(map(self.pattern.search, fob.readlines()))
|
||||
return self.pattern.search(path) or any(map(self.pattern.search, fob.read()))
|
||||
def scan(keywords:str, mode='r', strict=True, prescaped=False, casefold=True):
|
||||
casefold = (0, re.I)[casefold]
|
||||
keywords = re.escape(keywords) if not prescaped else keywords
|
||||
pattern = re.compile(keywords if strict and not prescaped else join(keywords.split(), '|'), casefold)
|
||||
def wrap(path):
|
||||
with open(path, mode) as fob:
|
||||
return any(map(pattern.search, fob.readlines())) or pattern.search(path)
|
||||
return wrap
|
||||
# show(user('', ext='txt'))
|
||||
|
||||
def philter(func, itr, start=1, index=False):
|
||||
for i, e in enumerate(itr, start):
|
||||
if any(j in e for j in 'venv'.split()):
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
if func(e):
|
||||
# yield i, e if index else e
|
||||
yield (e, (i, e))[index]
|
||||
except UnicodeDecodeError:
|
||||
# print(f"UnicodeDecodeError @ {e}")
|
||||
# raise
|
||||
# break
|
||||
continue
|
||||
def docsearch(keywords:str, location:Directory=monties, ext:str='py'):
|
||||
show(filter(scan(keywords), location('', ext=ext)))
|
||||
exotics = {
|
||||
"cdot": "·",
|
||||
}
|
||||
exords = {key: ord(val) for key, val in exotics.items()}
|
||||
tfs = [
|
||||
r'C:\Users\Kenneth\childlist.txt',
|
||||
r'C:\Users\Kenneth\file.txt',
|
||||
# r'C:\Users\Kenneth\frozenpips.txt',
|
||||
r'C:\Users\Kenneth\parentlist.txt',
|
||||
# r'C:\Users\Kenneth\pipfreeze.txt',
|
||||
# r'C:\Users\Kenneth\pipsweets.txt',
|
||||
]
|
||||
|
||||
|
||||
# tword = 'def clone'
|
||||
# show(filter(scan(tword), monties('', ext='py')))
|
||||
# show(map(pop, filter(scan(tword), tfs)))
|
||||
# show(filter(scan(tword), tfs))
|
||||
|
||||
|
||||
# show(downs('tensor', ext='pdf'))
|
||||
# with open('')
|
||||
# with (Directory('~') / 'pyo_rec.wav').open('r') as fob:
|
||||
# help(fob)
|
||||
# print(fob.read())
|
||||
# fob = fob.open(
|
||||
# f = (Directory('~') / 'pyo_rec.wav')
|
||||
# help(f.open)
|
BIN
filey/README.md
BIN
filey/README.md
Binary file not shown.
|
@ -1,605 +1,14 @@
|
|||
"""
|
||||
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.
|
||||
from .handles import *
|
||||
from .shell import *
|
||||
from .walking import *
|
||||
from .persistence import *
|
||||
from .shortcuts import *
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
|
@ -0,0 +1,709 @@
|
|||
"""
|
||||
A collection of route objects to help you manage your filey Things and Places elegantly
|
||||
Exports the following aliases:
|
||||
File <-> Thing
|
||||
Directory <-> Place
|
||||
Path/Address <-> Thing
|
||||
Thing 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 Thing/Place
|
||||
else
|
||||
the drive is the name/letter of the disk partition on which the content at the address is stored.
|
||||
"Apparent Place" 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 Thing/Place
|
||||
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__ = 'forbiddens Thing Place Thing Path Address Directory Folder File Library'.split()
|
||||
|
||||
from itertools import chain
|
||||
from typing import Dict, Iterable, Iterator, List, Sequence, TypeAlias
|
||||
from warnings import warn
|
||||
import io, os, pathlib, re, sys, shutil
|
||||
|
||||
from send2trash import send2trash
|
||||
from sl4ng import unique, nice_size
|
||||
from sl4ng import pop, show
|
||||
import audio_metadata as am, filetype as ft
|
||||
|
||||
from . import shell, walking
|
||||
|
||||
|
||||
formats = { # incase mimes fail
|
||||
'pics': "bmp png jpg jpeg tiff svg psd".split(),
|
||||
'music': "mp3 m4a wav ogg wma flac aiff alac flp live".split(),
|
||||
'videos': "mp4 wmv".split(),
|
||||
'docs': "doc docx pdf xlsx pptx ppt xls csv".split(),
|
||||
}
|
||||
formats['all'] = [*chain.from_iterable(formats.values())]
|
||||
|
||||
|
||||
forbiddens = r'\/:?*<>|"'
|
||||
|
||||
|
||||
_Path:TypeAlias = "Thing"
|
||||
_Pathstr:TypeAlias = "Thing|str"
|
||||
_Place:TypeAlias = "Place"
|
||||
_Placestr:TypeAlias = "Place|str"
|
||||
_Placefile:TypeAlias = "Place|File"
|
||||
_File:TypeAlias = "File"
|
||||
_Filestr:TypeAlias = "File|str"
|
||||
SYSTEM_PATH:TypeAlias = type(pathlib.Path(__file__))
|
||||
|
||||
|
||||
class MemorySize(int):
|
||||
"""
|
||||
Why should you have to sacrifice utility for readability?
|
||||
"""
|
||||
def __repr__(self):
|
||||
return nice_size(self)
|
||||
|
||||
class Library:
|
||||
"""
|
||||
Allows categorization for multiple searching and can also be used as a makeshift playlist
|
||||
"""
|
||||
def __init__(self, *paths):
|
||||
self.paths = []
|
||||
for p in paths:
|
||||
if os.path.exists(str(p)):
|
||||
self.paths.append(str(p))
|
||||
self.index = -1
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return self
|
||||
def __next__(self) -> str:
|
||||
if self.index < len(self.paths) - 1:
|
||||
self.index += 1
|
||||
return self.paths[self.index]
|
||||
self.index = -1
|
||||
raise StopIteration
|
||||
def __call__(self, terms, **kwargs) -> Iterator[str]:
|
||||
"""
|
||||
Find files under directories in self.paths matching the given terms/criteria
|
||||
Any files in self.path will also be yielded if they match
|
||||
|
||||
Params
|
||||
Args
|
||||
terms:str
|
||||
the terms sought after.
|
||||
separate by spaces
|
||||
an empty string simply walks the full tree
|
||||
Kwargs
|
||||
exts:str
|
||||
any file extensions you wish to check for, separate by spaces
|
||||
case:bool
|
||||
toggle case sensitivity, assumes True if any terms are upper cased
|
||||
negative:bool - kwarg
|
||||
Any files/folders with names or extensions matching the terms and exts will be omitted.
|
||||
dirs:int
|
||||
0 -> ignore all directories
|
||||
1 -> directories and files
|
||||
2 -> directories only
|
||||
strict:int
|
||||
0 -> match any terms in any order
|
||||
1 -> match all terms in any order (interruptions allowed)
|
||||
2 -> match all terms in any order (no interruptions allowed)
|
||||
3 -> match all terms in given order (interruptions)
|
||||
4 -> match all terms in given order (no interruptions)
|
||||
combinations of the following are not counted as interruptions:
|
||||
[' ', '_', '-']
|
||||
5 -> match termstring as though it was pre-formatted regex
|
||||
names:bool
|
||||
True -> only yield results whose names match
|
||||
False -> yield results who match at any level
|
||||
"""
|
||||
for i in self:
|
||||
if os.path.isfile(i):
|
||||
yield i
|
||||
elif os.path.isdir(i):
|
||||
yield from walking.search(i, terms, **kwargs)
|
||||
|
||||
class Thing:
|
||||
"""
|
||||
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 = os.path.realpath(str(path))
|
||||
if not os.path.exists(path):
|
||||
warn(f'Warning: Path "{path}" does not exist', Warning)
|
||||
def __eq__(self, other:_Pathstr) -> bool:
|
||||
if isinstance(other, (str, type(self))):
|
||||
return self.sameas(str(other))
|
||||
raise NotImplementedError(f"Cannot compare {type(self).__name__} with {type(other).__name__}")
|
||||
def __str__(self):
|
||||
return self.path
|
||||
def __hash__(self):
|
||||
"""
|
||||
Compute the hash of this Thing's path
|
||||
"""
|
||||
return hash(self.path)
|
||||
def __repr__(self):
|
||||
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})"
|
||||
|
||||
def create(self, content:str|bytes|bytearray=None, raw:bool=False, exist_ok:bool=True, mode:int=511) -> _Path:
|
||||
"""
|
||||
Create an entry in the file-system. If the address is not vacant no action will be taken.
|
||||
The raw handle only works for Things and enables writing bytes/bytearrays
|
||||
"""
|
||||
if self.exists:
|
||||
return self
|
||||
elif content or isinstance(self, File):
|
||||
os.makedirs(self.up.path, exist_ok=exist_ok)
|
||||
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)
|
||||
self = File(self.path)
|
||||
else:
|
||||
os.makedirs(self.path, mode=mode, exist_ok=exist_ok)
|
||||
self = Place(self.path)
|
||||
return self
|
||||
@property
|
||||
def __short_repr(self) -> str:
|
||||
"""
|
||||
Return check if the Place is at the highest level of the Thing system
|
||||
"""
|
||||
return f"{type(self).__name__}({self.name})"
|
||||
@property
|
||||
def exists(self) -> bool:
|
||||
return os.path.exists(self.path)
|
||||
@property
|
||||
def isdir(self) -> bool:
|
||||
return os.path.isdir(self.path)
|
||||
@property
|
||||
def isfile(self) -> bool:
|
||||
return os.path.isfile(self.path)
|
||||
@property
|
||||
def obj(self) -> _Path:
|
||||
"""
|
||||
Determine if self.path points to a file or folder and create the corresponding object
|
||||
"""
|
||||
if self.isfile:
|
||||
return Thing(self.path)
|
||||
elif self.isdir:
|
||||
return Place(self.path)
|
||||
else:
|
||||
return Thing(self.path)
|
||||
@property
|
||||
def dir(self) -> _Place:
|
||||
"""
|
||||
Return the containing directory
|
||||
"""
|
||||
return Thing(os.path.dirname(self.path)).obj
|
||||
up = dir
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Return the name of the referent
|
||||
"""
|
||||
return os.path.split(self.path)[1]
|
||||
|
||||
@property
|
||||
def ancestors(self) -> tuple:
|
||||
"""
|
||||
Return consecutive ADirs until the ADrive is reached
|
||||
"""
|
||||
level = []
|
||||
p = self.path
|
||||
while p != delevel(p):
|
||||
p = delevel(p)
|
||||
level.append(p)
|
||||
return tuple(Thing(i).obj for i in level)[::-1]
|
||||
@property
|
||||
def colleagues(self) -> Iterator:
|
||||
"""
|
||||
Every member of the same Place whose type is the same as the referent
|
||||
"""
|
||||
return (i for i in self.up if isinstance(i, type(self)))
|
||||
@property
|
||||
def neighbours(self) -> tuple[_File, _Place]:
|
||||
"""
|
||||
Everything in the same Place
|
||||
"""
|
||||
return self.up.content
|
||||
@property
|
||||
def depth(self) -> int:
|
||||
"""
|
||||
Number of ancestors
|
||||
"""
|
||||
return len(self.ancestors)
|
||||
@property
|
||||
def top(self) -> str:
|
||||
"""
|
||||
The apparent drive. Will not be helpful if self.path is relative
|
||||
"""
|
||||
return self.ancestors[0]
|
||||
@property
|
||||
def stat(self) -> os.stat_result:
|
||||
"""
|
||||
return os.stat(self.path)
|
||||
"""
|
||||
return os.stat(self.path)
|
||||
@property
|
||||
def ancestry(self) -> str:
|
||||
"""
|
||||
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.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
|
||||
@property
|
||||
def isempty(self):
|
||||
return shell.isempty(self.path, make=False)
|
||||
|
||||
def start(self, command:str=None) -> _Path:
|
||||
"""
|
||||
Open the path using the system default or a command of your choice
|
||||
"""
|
||||
pred = isinstance(command, type(None))
|
||||
|
||||
arg = (f'{command} "{self.path}"', self.path)[pred]
|
||||
fun = (os.system, os.startfile)[pred]
|
||||
fun(arg)
|
||||
# os.startfile(self.path) if isinstance(command, type(None)) else os.system(f'{self.path} ""')
|
||||
return self
|
||||
def expose(self):
|
||||
"""
|
||||
Reveal the referent in the system's file explorer (will open the containing Place if the referent is a Thing)
|
||||
"""
|
||||
os.startfile(self.up.path)
|
||||
return self
|
||||
|
||||
def delevel(self, steps:int=1, path:bool=False) -> str:
|
||||
"""
|
||||
Go up some number of levels in the Thing system
|
||||
Params
|
||||
steps
|
||||
the number of steps to delevel
|
||||
path
|
||||
return (True -> string, False -> Thing-like object)
|
||||
"""
|
||||
return delevel(self.path, steps) if path else Place(delevel(self.path, steps))
|
||||
def touch(self) -> _Path:
|
||||
"""
|
||||
Implements the unix command 'touch', which updates the 'date modified' of the content at the path
|
||||
"""
|
||||
p = self.path
|
||||
pathlib.Path(p).touch()
|
||||
self = Thing(p).obj
|
||||
return self
|
||||
def erase(self, recycle:bool=True) -> _Path:
|
||||
"""
|
||||
Send a Thing to the trash, or remove it without recycling.
|
||||
"""
|
||||
shell.discard(self.path, recycle=recycle)
|
||||
return self
|
||||
def sameas(self, other:_Pathstr) -> bool:
|
||||
"""
|
||||
Check if this Thing's associated data content is equivalent to some other
|
||||
"""
|
||||
if self.exists and os.path.exists(str(other)):
|
||||
return os.path.samefile(self.path, str(other))
|
||||
else:
|
||||
raise OSError("One or both paths point to a non-existent entity")
|
||||
def clone(self, folder:str=None, name:str=None, sep:str='_', touch=False) -> _Path:
|
||||
"""
|
||||
Returns a clone of the referent at a given Place-path
|
||||
The given path will be created if it doesn't exist
|
||||
Will copy in the Thing's original folder if no path is given
|
||||
The cwd switch will always copy to the current working Place
|
||||
"""
|
||||
copier = (shutil.copy2, shutil.copytree)[self.isdir]
|
||||
if not self.exists:
|
||||
raise NotImplementedError(f"Copying is not implemented for inexistant files/directories")
|
||||
elif folder:
|
||||
new = os.path.join(folder, name if name else self.name)
|
||||
else:
|
||||
new = self.path
|
||||
new = shell.namespacer(new, sep=sep)
|
||||
os.makedirs(delevel(new), exist_ok=True)
|
||||
copier(self.path, new)
|
||||
out = Thing(new).obj
|
||||
return out.touch() if touch else out
|
||||
def move(self, folder:_Placestr, dodge:bool=False) -> _Path:
|
||||
"""
|
||||
This will clone and delete the underlying file/directory
|
||||
addy.move(folder) -> move to the given Place ()
|
||||
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 Place
|
||||
|
||||
: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 not self.exists:
|
||||
raise OSError(f"{ self.__short_repr} does not exist")
|
||||
folder = str(folder)
|
||||
if not os.path.exists(folder):
|
||||
raise OSError(f"folder={other.__short_repr} does not exist")
|
||||
self.path = shell.move(self.path, folder)
|
||||
return self
|
||||
def rename(self, name:str) -> _Path:
|
||||
"""
|
||||
Change the name of the referent
|
||||
This will clone and delete the underlying file/directory
|
||||
"""
|
||||
path = os.path.join(self.dir.path, name)
|
||||
os.rename(self.path, path)
|
||||
self.path = path
|
||||
return self
|
||||
Address = Path = Thing
|
||||
|
||||
|
||||
class File(Thing):
|
||||
"""
|
||||
Create a new File object for context management and ordinary operations
|
||||
"""
|
||||
def __init__(self, path:str='NewThing'):
|
||||
path = os.path.abspath(shell.trim(path))
|
||||
if os.path.isdir(path):
|
||||
raise ValueError("Given path corresponds to a directory")
|
||||
super(type(self), self).__init__(path)
|
||||
self.__stream = None
|
||||
def __enter__(self):
|
||||
return self.open()
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
if self.exists:
|
||||
return MemorySize(os.stat(self.path).st_size)
|
||||
@property
|
||||
def mime(self) -> str|type(None):
|
||||
return match.MIME if (match := ft.guess(self.path)) else None
|
||||
@property
|
||||
def kind(self) -> str|type(None):
|
||||
return self.mime.split('/')[0] if (match:=ft.guess(self.path)) else None
|
||||
@property
|
||||
def ext(self) -> str:
|
||||
return os.path.splitext(self.name)[1]
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""
|
||||
return the File's name without the extension
|
||||
"""
|
||||
return os.path.splitext(self.name)[0]
|
||||
def open(self, mode='r', lines=False, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -> io.TextIOWrapper|io.BufferedReader:
|
||||
"""
|
||||
Return the File's byte or text stream.
|
||||
Scrape splits the text at all whitespace and returns the content as a string
|
||||
"""
|
||||
fobj = open(self.path, mode, buffering, encoding, errors, newline, closefd, opener)
|
||||
self.__stream = fobj
|
||||
return self.__stream
|
||||
def close(self) -> _File:
|
||||
if self.__stream:
|
||||
self.__stream.close()
|
||||
self.stream = None
|
||||
return self
|
||||
def cat(self, text:bool=True, lines:bool=False, copy:bool=False) -> str|bytes|list[str|bytes]:
|
||||
"""
|
||||
Mimmicks the unix command.
|
||||
|
||||
Params
|
||||
lines
|
||||
whether or not you would like to read the lines (instead of the uninterrupted stream)
|
||||
For other args, please refer to help (io.open)
|
||||
"""
|
||||
return shell.cat(self.path, lines=lines, copy=copy)
|
||||
|
||||
|
||||
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 = os.path.realpath(path)
|
||||
def __getattr__(self, attr):
|
||||
return Place(self.path)[attr]
|
||||
|
||||
|
||||
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)
|
||||
super(type(self), self).__init__(path)
|
||||
self.index = -1
|
||||
def __len__(self):
|
||||
return len(os.listdir(self.path))
|
||||
def __bool__(self):
|
||||
"""
|
||||
Check if the Place is empty or not
|
||||
"""
|
||||
return len(os.listdir(self.path)) > 0
|
||||
def __iter__(self) -> Iterator[_Placefile]:
|
||||
return self
|
||||
def __next__(self) -> Sequence[_Placefile]:
|
||||
if self.index<len(self)-1:
|
||||
self.index += 1
|
||||
return self.content[self.index]
|
||||
self.index = -1
|
||||
raise StopIteration
|
||||
def __getitem__(self, item:str|int) -> _Path:
|
||||
"""
|
||||
Return an object whose name is an exact match for the given item
|
||||
Params
|
||||
items
|
||||
int -> calls os.listdir and returns the item'th element
|
||||
str -> checks if the item is reachable through self.path and returns if possible
|
||||
"""
|
||||
if isinstance(item, int):
|
||||
return os.listdir(self.path)[item]
|
||||
elif isinstance(item, str):
|
||||
if os.path.isabs(item):
|
||||
if os.path.dirname(item) == self.path:
|
||||
return Thing(item).obj
|
||||
if os.path.exists(path := os.path.join(self.path, item)):
|
||||
return Thing(path).obj
|
||||
raise ValueError(f'The folder "{self.name}" does not contain anything called "{item}"')
|
||||
def __truediv__(self, other:str):
|
||||
if isinstance(other, str):
|
||||
return Thing(os.path.join(self.path, other)).obj
|
||||
raise TypeError(f"Other must be a string")
|
||||
def __call__(self, terms, **kwargs) -> Iterator[str]:
|
||||
"""
|
||||
Find files under self.path matching the given terms/criteria
|
||||
|
||||
Params
|
||||
Args
|
||||
terms:str
|
||||
the terms sought after. an empty string simply walks
|
||||
Kwargs
|
||||
exts:str
|
||||
any file extensions you wish to check for, separate by spaces
|
||||
case:bool
|
||||
toggle case sensitivity, assumes True if any terms are upper cased
|
||||
negative:bool - kwarg
|
||||
Any files/folders with names or extensions matching the terms and exts will be omitted.
|
||||
dirs:int
|
||||
0 -> ignore all directories
|
||||
1 -> directories and files
|
||||
2 -> directories only
|
||||
strict:int
|
||||
0 -> match any terms in any order
|
||||
1 -> match all terms in any order (interruptions allowed)
|
||||
2 -> match all terms in any order (no interruptions allowed)
|
||||
3 -> match all terms in given order (interruptions)
|
||||
4 -> match all terms in given order (no interruptions)
|
||||
combinations of the following are not counted as interruptions:
|
||||
[' ', '_', '-']
|
||||
regex:bool
|
||||
if true, the term-string will be compiled immediately with no further processing.
|
||||
don't forget to set case=True if need be!
|
||||
names:bool
|
||||
True -> only yield results whose names match
|
||||
False -> yield results who match at any level
|
||||
"""
|
||||
# yield from Searcher(terms, ext='', folders=False, absolute=True, case=False, strict=True)(self.path)
|
||||
# yield from walking.search(self.path, terms, exts=exts, folders=folders, absolute=absolute, case=case, strict=strict, regex=regex, names=names)
|
||||
yield from walking.search(self.path, terms, **kwargs)
|
||||
def gather(self, dirs:bool=False, absolute:bool=True) -> Iterator[str]:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
yield from walking.walk(self.path, dirs=dirs, absolute=True)
|
||||
@property
|
||||
def items(self) -> Items:
|
||||
"""
|
||||
This extension allows you to call folder contents as if they were attributes.
|
||||
Will not work on any files/folders whose names wouldn't fly as python variables/attributes
|
||||
example:
|
||||
>>> folder.items.subfolder
|
||||
"""
|
||||
return Items(self.path)
|
||||
@property
|
||||
def children(self) -> _Place:
|
||||
"""
|
||||
Return "os.listdir" but filtered for directories
|
||||
"""
|
||||
return (addy.obj for i in os.listdir(self.path) if (addy:=Thing(os.path.join(self.path, i))).isdir)
|
||||
@property
|
||||
def files(self) -> Iterator[File]:
|
||||
"""
|
||||
Return "os.listdir" but filtered for Files
|
||||
"""
|
||||
return (addy.obj for i in os.listdir(self.path) if (addy:=Thing(os.path.join(self.path, i))).isfile)
|
||||
@property
|
||||
def content(self) -> Iterator[Thing]:
|
||||
"""
|
||||
Return address-like objects from "os.listdir"
|
||||
"""
|
||||
return tuple(Thing(os.path.join(self.path, i)).obj for i in os.listdir(self.path))
|
||||
@property
|
||||
def leaves(self) -> Iterator[File]:
|
||||
"""
|
||||
Return All Things from all branches
|
||||
"""
|
||||
yield from map(lambda x: Thing(x).obj, walking.files(self.path, absolute=True))
|
||||
@property
|
||||
def branches(self) -> Iterator[_Place]:
|
||||
"""
|
||||
Return Every Place whose path contains "self.path"
|
||||
"""
|
||||
yield from map(lambda x: Thing(x).obj, walking.folders(self.path, absolute=True))
|
||||
@property
|
||||
def size(self) -> MemorySize:
|
||||
"""
|
||||
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) -> tuple[str]:
|
||||
"""
|
||||
Return Thing mimes for all Things from all branches
|
||||
"""
|
||||
return tuple(unique(filter(None, (File(path).mime for path in self.gather(dirs=False, absolute=True)))))
|
||||
@property
|
||||
def kinds(self) -> tuple[str]:
|
||||
"""
|
||||
Return Thing types for all Things from branches
|
||||
"""
|
||||
return tuple(m.split('/')[1] for m in self.mimes)
|
||||
@property
|
||||
def exts(self) -> tuple[str]:
|
||||
"""
|
||||
Return extensions for all Things from all branches
|
||||
"""
|
||||
return tuple(unique(filter(None, (File(path).ext for path in self.gather(dirs=False, absolute=True)))))
|
||||
@property
|
||||
def isroot(self) -> bool:
|
||||
"""
|
||||
Return check if the Place is at the highest level of the Thing system
|
||||
"""
|
||||
return not self.depth
|
||||
|
||||
|
||||
def add(self, other:Address, copy:bool=False) -> _Place:
|
||||
"""
|
||||
Introduce new elements. Send an address-like object to self.
|
||||
"""
|
||||
if isinstance(other, (str, SYSTEM_PATH)):
|
||||
other = Thing(str(other)).obj
|
||||
if not self.exists:
|
||||
raise OSError(f"{self.__short_repr} does not exist")
|
||||
if not other.exists:
|
||||
raise OSError(f"{other.__short_repr} does not 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) -> _Place:
|
||||
"""
|
||||
Set referent as current working Directory
|
||||
"""
|
||||
if self.exists:
|
||||
os.chdir(self.path)
|
||||
return self
|
||||
raise OSError(f"{self.__short_repr} does not exist")
|
||||
Directory = Folder = Place
|
||||
|
||||
# class Archive(Thing):
|
||||
# def __init__(self, path:str='NewThing'):
|
||||
# path = os.path.abspath(shell.trim(path))
|
||||
# super(Thing, self).__init__(path)
|
||||
# class Searcherable:
|
||||
|
||||
class Audio(File):
|
||||
def __init__(self, path:str):
|
||||
path = os.path.abspath(shell.trim(path))
|
||||
if os.path.isdir(path) or not ft.audio_match(path):
|
||||
raise ValueError("Given path corresponds to a directory")
|
||||
super(type(self), self).__init__(path)
|
||||
@property
|
||||
def metadata(self):
|
||||
return am.load(self.path)
|
||||
@property
|
||||
def tags(self):
|
||||
self.metadata['tags']
|
||||
@property
|
||||
def pictures(self):
|
||||
self.metadata['pictures']
|
||||
@property
|
||||
def streaminfo(self):
|
||||
self.metadata['streaminfo']
|
||||
|
||||
@property
|
||||
def artist(self):
|
||||
return self.tags['artist']
|
||||
@property
|
||||
def album(self):
|
||||
return self.tags['album']
|
||||
@property
|
||||
def title(self):
|
||||
return self.tags['title']
|
||||
|
||||
if __name__ == '__main__':
|
||||
# show(locals().keys())
|
||||
|
||||
|
||||
fp = r'c:\users\kenneth\pyo_rec.wav'
|
||||
dp = r'c:\users\kenneth\videos'
|
||||
|
||||
d = Place(dp)
|
||||
f = Thing(fp)
|
||||
tf = Thing('testfile.ext')
|
||||
td = Place('testdir')
|
||||
|
||||
system = (d, f)
|
||||
# show(d('slowthai', sort=True))
|
||||
# show(d('alix melanie', sort=True))
|
||||
# show(d('melanie alix', sort=True))
|
||||
|
||||
# print(formats['all'])
|
||||
# for v in formats.values():
|
||||
# print(' '.join(v))
|
||||
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
from typing import Any, Tuple
|
||||
import os, pickle, re
|
||||
|
||||
import dill
|
||||
|
||||
from sl4ng.iteration import deduplicate, dictate, flat
|
||||
from sl4ng.debug import tryimport, tipo
|
||||
|
||||
from .shell import namespacer
|
||||
|
||||
def dillsave(obj:Any, filename:str, overwrite:bool=True) -> Any:
|
||||
"""
|
||||
Pickles the given object to a file with the given path/name.
|
||||
If the object cannot be serialized with pickle, we shall use dill instead
|
||||
Returns the object
|
||||
"""
|
||||
if overwrite:
|
||||
with open(filename, 'wb') as fob:
|
||||
dill.dump(obj, fob, protocol=dill.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
with open(namespacer(filename), 'wb') as fob:
|
||||
dill.dump(obj, fob, protocol=dill.HIGHEST_PROTOCOL)
|
||||
|
||||
def save(obj:Any, filename:str, overwrite:bool=True) -> Any:
|
||||
"""
|
||||
Pickles the given object to a file with the given path/name.
|
||||
If the object cannot be serialized with pickle, we shall use dill instead
|
||||
Returns the object
|
||||
"""
|
||||
try:
|
||||
if overwrite:
|
||||
with open(filename, 'wb') as fob:
|
||||
pickle.dump(obj, fob, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
with open(namespacer(filename), 'wb') as fob:
|
||||
pickle.dump(obj, fob, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
except pickle.PicklingError:
|
||||
dillsave(obj, filename, overwrite)
|
||||
except TypeError:
|
||||
dillsave(obj, filename, overwrite)
|
||||
return obj
|
||||
|
||||
def load(filename:str) -> Any:
|
||||
"""
|
||||
Return unpickled data from a chosen file
|
||||
If the file doesn't exist it will be created
|
||||
"""
|
||||
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'rb') as fob:
|
||||
var = pickle.load(fob)
|
||||
return var
|
||||
else:
|
||||
x = open(filename)
|
||||
x.close()
|
||||
return
|
||||
|
||||
def jar(file, duplicates:bool=False, flags:int=re.I):
|
||||
"""
|
||||
Consolidate your pickles and eat them, discard the clones by default though
|
||||
"""
|
||||
trash = tryimport('send2trash', 'send2trash', 'remove', 'os')
|
||||
name, ext = os.path.splitext(file)
|
||||
pattern = f'^{name}|{name}_\d+\.{ext}$'
|
||||
p = re.compile(pattern)
|
||||
folder = None if not os.sep in file else os.path.split(file)[0]
|
||||
os.chdir(folder) if folder else None
|
||||
matches = deduplicate({f: load(f) for f in os.listdir(folder) if p.search(f, flags)})
|
||||
results = list(matches.values())[:]
|
||||
for i in matches: print(i)
|
||||
[trash(file) for file in matches]
|
||||
save(results, file)
|
||||
|
||||
|
||||
|
||||
class LoggerLengthError(Exception):
|
||||
"""
|
||||
A logger has reached it's maximum length
|
||||
"""
|
||||
class LoggerMaxLengthError(Exception):
|
||||
"""
|
||||
Cannot positively update due to inevitable length error
|
||||
"""
|
||||
class LoggerKindError(Exception):
|
||||
"""
|
||||
Tried to add an element of the wrong type
|
||||
"""
|
||||
class LoggerArgumentError(Exception):
|
||||
"""
|
||||
Couldn't unite loggers because they have distinct arguments
|
||||
"""
|
||||
class LoggerAgreementError(Exception):
|
||||
"""
|
||||
Shouldn't compare loggers because they have distinct arguments
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, path:str='log.pkl', tight:bool=True, kind:[type, Tuple[type]]=None, max_len:int=None, dodge:bool=True):
|
||||
"""
|
||||
Keep track of things so that you can pick up where you left off at the last runtime.
|
||||
All saves are made at the end of an update
|
||||
|
||||
|
||||
params
|
||||
path
|
||||
path to the logger's pickle file
|
||||
tight
|
||||
True -> no element can be added twice
|
||||
kind
|
||||
guarantees that any elements added will be of the desired type. Leave as None if you don't care
|
||||
max_len
|
||||
if the Logger's length >= max_len, the first element will be removed
|
||||
doge
|
||||
if false and IF there already exists a logger with equal arguments but different content, it's content will be synchronized and it will be overwritten upon updating.
|
||||
if true, a new name will be generated for the serial-file.
|
||||
"""
|
||||
kind = kind if isinstance(kind, (tuple, list)) else tuple(flat([kind])) if kind else None
|
||||
if os.path.exists(path):
|
||||
if isinstance((slf := load(path)), type(self)):
|
||||
omittables = '_Logger__index _Logger__itr path content'.split()
|
||||
# kws = dictate(slf.__dict__, omittables)
|
||||
kwargs = dict(zip(('tight', 'kind', 'max_len'), (tight, kind, max_len)))
|
||||
# if all(slf.__dict__.get(i)==kwargs.get(i) for i in kws):
|
||||
kws = {key: slf.__dict__[key] for key in kwargs}
|
||||
# print([j==kwargs.get(i) for i, j in kws.values()])
|
||||
# if all(j==kwargs.get(i) for i, j in kws.values()):
|
||||
if all(j==kwargs.get(i) for i, j in kws.items()):
|
||||
self.max_len = slf.max_len
|
||||
self.kind = slf.kind
|
||||
self.tight = slf.tight
|
||||
self.path = slf.path
|
||||
self.content = slf.content
|
||||
else:
|
||||
# diff = {key: False for key in slf.__dict__ if (val:=slf.__dict__[key])!=kwargs.get(key)}
|
||||
# diff = {key: kws[key]==val for key, val in kwargs.items()}
|
||||
diff = {key: (kws[key], val) for key, val in kwargs.items() if kws[key]!=val}
|
||||
# raise LoggerArgumentError(f"There is already a {tipo(self)} at the given path whose attributes do not agree:\n\t{load(path).__dict__}\n\t{kwargs}")
|
||||
raise LoggerArgumentError(f"There is already a {tipo(self)} at the given path whose attributes do not agree:\n\t{diff}")
|
||||
else:
|
||||
raise TypeError(f'There is already persistent data in a file at the given path but it is not an instance of the {tipo(self)!r} class')
|
||||
else:
|
||||
self.max_len = max_len
|
||||
self.kind = kind
|
||||
self.tight = tight
|
||||
self.path = path
|
||||
self.content = []
|
||||
if dodge:
|
||||
self.path = namespacer(path)
|
||||
self.__index = -1
|
||||
|
||||
def __repr__(self):
|
||||
r = f"Logger(length={len(self)}, tight={self.tight}, max_len={self.max_len})"
|
||||
r += f"[{', '.join(map(tipo, self.kind))}]" if self.kind else ''
|
||||
return r
|
||||
|
||||
def compare(self, other):
|
||||
"""
|
||||
Return essentialized versions of self's and other's __dict__ attributes
|
||||
"""
|
||||
if isinstance(other, type(self)):
|
||||
omittables = '_Logger__index _Logger__itr path content'.split()
|
||||
sd = dictate(self.__dict__, omittables)
|
||||
od = dictate(other.__dict__, omittables)
|
||||
return sd, od
|
||||
raise TypeError(f"Other is not an instance of the {tipo(self)} class")
|
||||
def agree(self, other):
|
||||
"""
|
||||
Check if they are suitable for concatenation
|
||||
"""
|
||||
sd, od = self.compare(other)
|
||||
return sd == od
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Not order sensitive
|
||||
"""
|
||||
if self.agree(other):
|
||||
return sorted(self.content) == sorted(other.content)
|
||||
raise LoggerAgreementError(f"self does not agree with other")
|
||||
def __gt__(self, other):
|
||||
if self.agree(other):
|
||||
return all(i in self for i in other) and len(self) > len(other)
|
||||
raise LoggerAgreementError(f"self does not agree with other")
|
||||
def __lt__(self, other):
|
||||
if self.agree(other):
|
||||
return all(i in other for i in self) and len(self) < len(other)
|
||||
raise LoggerAgreementError(f"self does not agree with other")
|
||||
def __ge__(self, other):
|
||||
return self.__gt__(other) or self.__eq__(other)
|
||||
def __le__(self, other):
|
||||
return self.__lt__(other) or self.__eq__(other)
|
||||
|
||||
|
||||
def __add__(self, other):
|
||||
if self.agree(other):
|
||||
if isinstance(self.max_len, None):
|
||||
[*map(self.update, other.content)]
|
||||
elif len(other) <= self.max_len - len(self):
|
||||
[*map(self.update, other.content)]
|
||||
else:
|
||||
raise LoggerMaxLengthError(f'Cannot update "self" with content from "other" because the outcome would have more than {self.max_lengh} elements')
|
||||
def __len__(self):
|
||||
return len(self.content)
|
||||
def __iter__(self):
|
||||
self.__itr = iter(self.content)
|
||||
return self.__itr
|
||||
def __next__(self):
|
||||
if self.__index < len(self) - 1:
|
||||
self.__index += 1
|
||||
return self.content[self.__index]
|
||||
self.__index = -1
|
||||
raise StopIteration
|
||||
def __in__(self, item):
|
||||
return item in self.content
|
||||
@property
|
||||
def exists(self):
|
||||
return os.path.exists(self.path)
|
||||
def update(self, element, remove:bool=False):
|
||||
"""
|
||||
Safely add/remove an element to/from, serialize, and return, self.
|
||||
"""
|
||||
if remove:
|
||||
self.content.remove(element)
|
||||
else:
|
||||
if not isinstance(self.kind, type(None)):
|
||||
if not type(element) in self.kind:
|
||||
raise LoggerKindError(f'Argument is not {self.kind}')
|
||||
if self.tight:
|
||||
if element in self:
|
||||
return self
|
||||
if not isinstance(self.max_len, type(None)):
|
||||
if self.max_len == len(self):
|
||||
raise LoggerLengthError("Logger has reached maximum capacity")
|
||||
self.content.append(element)
|
||||
save(self, self.path)
|
||||
return self
|
|
@ -1,7 +0,0 @@
|
|||
import configparser
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
# cfg[]
|
||||
|
||||
if __name__=='__main__':
|
||||
pass
|
|
@ -1,380 +0,0 @@
|
|||
"""
|
||||
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'))
|
|
@ -1,366 +0,0 @@
|
|||
"""
|
||||
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)
|
|
@ -1,54 +0,0 @@
|
|||
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))
|
|
@ -1,344 +0,0 @@
|
|||
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)
|
||||
|
|
@ -1,562 +0,0 @@
|
|||
"""
|
||||
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))
|
|
@ -1,122 +0,0 @@
|
|||
"""
|
||||
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))
|
|
@ -1,24 +0,0 @@
|
|||
from sl4ng import gen, show, chords, flatten as chain
|
||||
from itertools import chain, tee
|
||||
from typing import Sequence
|
||||
|
||||
def multisplit(splitters:Sequence[str], target:str) -> gen:
|
||||
"""
|
||||
Split a string by a sequence of arguments
|
||||
>>> list(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||
['wma', 'wmv', 'mp3', 'vga', 'mp4', 'ogg']
|
||||
"""
|
||||
result = target.split(splitters[0])
|
||||
for splitter in splitters[1:]:
|
||||
result = [*chain.from_iterable(i.split(splitter) for i in result)]
|
||||
# yield from [i for i in result if i]
|
||||
yield from filter(None,result)
|
||||
|
||||
|
||||
|
||||
# multisplit('a b c'.split(), 'carrot cabbage macabre'.split(','))
|
||||
x, y = tee(multisplit('a b c'.split(), 'carrot cabbage macabre'))
|
||||
x, y = tee(multisplit(',`* ', 'carrot cabbage macabre'))
|
||||
x, y = tee(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||
show(x)
|
||||
print(list(y))
|
|
@ -0,0 +1,347 @@
|
|||
# __all__ = "discard unarchive create_enter audio_or_video 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
|
||||
from typing import Iterable, Iterator
|
||||
from warnings import warn
|
||||
import os, subprocess, sys, time
|
||||
|
||||
from send2trash import send2trash
|
||||
from sl4ng import shuffle, flat
|
||||
import filetype as ft, audio_metadata as am, pyperclip as pc
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
path = os.path.normpath(path)
|
||||
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 namespacer(path:str, sep:str='_', start:int=2) -> str:
|
||||
"""
|
||||
Returns a unique version of a given string by appending an integer
|
||||
|
||||
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
|
||||
"""
|
||||
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 trim(path:str, edge:str=os.sep) -> str:
|
||||
"""
|
||||
Remove trailing/leading separators (or edges) from a path string
|
||||
"""
|
||||
out = path[:]
|
||||
if sys.platform == 'win32':
|
||||
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 cat(path:str, text:bool=True, lines:bool=False, copy:bool=False) -> str|bytes|list[str|bytes]:
|
||||
"""
|
||||
Extract the text or bytes, if the keyword is set to false, from a file
|
||||
::text::
|
||||
text or bytes?
|
||||
::lines::
|
||||
split text by line or return raw?
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
mode = "rb r".split()[text]
|
||||
with open(path, mode) as f:
|
||||
content = f.readlines() if lines else f.read()
|
||||
pc.copy(content) if copy else None
|
||||
return content
|
||||
|
||||
def create_enter(*args:[str, Iterable[str]], go_back:bool=False, recursive:bool=True, overwrite:bool=True, exist_ok:bool=True) -> str:
|
||||
"""
|
||||
recursively create and enter directories.
|
||||
params:
|
||||
go_back
|
||||
if set to True, the process will return to the starting directory
|
||||
recursive
|
||||
if set to False, all directories will be created in the starting directory
|
||||
overwrite
|
||||
if a directory "dir_n" exists, "dir_n+1" will be created, unless set to False
|
||||
exist_ok
|
||||
passed to the os.makedirs call. If set to False, and overwrite is True, and collision occurs, an exception will be raised
|
||||
eg
|
||||
each of the following calls create the following tree:
|
||||
dir-1
|
||||
dir0: starting directory
|
||||
dir1
|
||||
dir2
|
||||
dir3
|
||||
dir4
|
||||
dir5
|
||||
|
||||
>>> mcd('dir1 dir2 .. dir3 .. .. dir4 .. .. dir5'.split())
|
||||
>>> mcd('dir1/dir2 ../dir3 ../../dir4 ../../dir5'.split())
|
||||
"""
|
||||
home = os.getcwd()
|
||||
for arg in flat(args):
|
||||
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()
|
||||
os.chdir(last_stop)
|
||||
return last_stop
|
||||
mcd = create_enter
|
||||
|
||||
def move(source:str, dest:str, make_dest:bool=False) -> str:
|
||||
"""
|
||||
Move source to dest
|
||||
Return path to new version
|
||||
|
||||
Params
|
||||
source
|
||||
path to original file/folder
|
||||
dest
|
||||
path to new containing directory.
|
||||
This will assume that the directory is on the same disk as os.getcwd()
|
||||
make_dest
|
||||
create destination if it doesn't already exist
|
||||
"""
|
||||
dest = (os.path.realpath, str)[os.path.isabs(dest)](dest)
|
||||
if not os.path.isdir(dest):
|
||||
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)
|
||||
new = os.path.join(dest, name)
|
||||
os.rename(path, new)
|
||||
return new
|
||||
mv = move
|
||||
|
||||
def move_file(file:str, dest:str, make_dest:bool=False, clone:bool=False) -> str:
|
||||
"""
|
||||
Move a file to a given directory
|
||||
This uses iteration to copy the file byte by byte.
|
||||
Use filey.operations.move unless you're having some permission issues
|
||||
Params
|
||||
source
|
||||
path to original file/folder
|
||||
dest
|
||||
path to new containing directory.
|
||||
This will assume that the directory is on the same disk as os.getcwd()
|
||||
make_dest
|
||||
create destination if it doesn't already exist
|
||||
"""
|
||||
if os.path.isdir(source):
|
||||
raise ValueError(f"Source path points to a directory")
|
||||
|
||||
dest = (os.path.realpath, str)[os.path.isabs(dest)](dest)
|
||||
if not os.path.isdir(dest):
|
||||
if not make_dest:
|
||||
raise ValueError(f"Destination path doesn't point to a directory")
|
||||
else:
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
|
||||
root, name = os.path.split(path)
|
||||
new = os.path.join(dest, name)
|
||||
|
||||
with open(source, 'rb') as src:
|
||||
with open(new, 'wb') as dst:
|
||||
dst.write(src.read())
|
||||
try:
|
||||
None if clone else os.remove(file)
|
||||
except PermissionError:
|
||||
warn("Could not remove file after copying due to PermissionError", Warning)
|
||||
return new
|
||||
|
||||
def discard(path:str, recycle:bool=True) -> None:
|
||||
"""
|
||||
Remove an address from the file-system.
|
||||
Will fall back to recycle if recycle is False, but a permission error is raised, and vis-versa
|
||||
Params
|
||||
Path
|
||||
address of the file/folder you wish to remove
|
||||
recycle
|
||||
send to (True -> recycle bin, False -> anihilate)
|
||||
"""
|
||||
fb = (os.remove, send2trash)
|
||||
first, backup = fb if not recycle else fb[::-1]
|
||||
try:
|
||||
first(path)
|
||||
except PermissionError:
|
||||
backup(path)
|
||||
rm = discard
|
||||
|
||||
def audio_or_video(path:str) -> bool:
|
||||
"""
|
||||
Check if a file is audio or video
|
||||
True if audio, False if video, else ValueError
|
||||
"""
|
||||
if ft.video_match(path):
|
||||
return False
|
||||
elif ft.audio_match(path):
|
||||
return True
|
||||
raise ValueError("Mime does not compute")
|
||||
|
||||
def ffplay(files:Iterable[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
|
||||
Files can be passed as a single string of paths separated by asterisks
|
||||
|
||||
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: audio_or_video(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 convert(file:str, format:str='wav', bitRate:int=450, delete:bool=False, options:str='') -> str:
|
||||
"""
|
||||
Convert an audio file using FFMPEG.
|
||||
Verbosity is minimized by default.
|
||||
Params
|
||||
file
|
||||
path to target file
|
||||
format
|
||||
desired output format
|
||||
bitRate
|
||||
only applies to lossy formats like ogg and mp3, will be autocorrected by FFMPEG
|
||||
delete
|
||||
whether or not to keep the file upon completion
|
||||
options
|
||||
additional options to pass to ffmpeg
|
||||
"""
|
||||
os.chdir(os.path.split(file)[0])
|
||||
|
||||
_title = lambda file: file.split(os.sep)[-1].split('.')[0]
|
||||
_new = lambda file, format: namespacer(_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:
|
||||
send2trash(file)
|
||||
print(f'Deletion is complete\n\t{new}\n\n\n')
|
||||
return new
|
||||
|
||||
def unarchive(path:str, app:str='rar') -> str:
|
||||
"""
|
||||
Extract an archive to a chosen destination, or one generated based on the name of the archive
|
||||
App refers to the comandlet you wish to invoke via subprocess.run
|
||||
|
||||
"""
|
||||
path = os.path.realpath(path)
|
||||
route, namext = os.path.split(path)
|
||||
name, ext = os.path.splitext(namext)
|
||||
dest = namespacer(os.path.join(route, name))
|
||||
|
||||
options = {
|
||||
'tar':'-x -f',
|
||||
'rar':'e -or -r',
|
||||
'winrar':'',
|
||||
}
|
||||
cmd = f'{app} {options[app]} "{src}" '
|
||||
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
os.chdir(dest)
|
||||
|
||||
subprocess.run(cmd)
|
||||
return dest
|
||||
|
||||
def isempty(path:str, make:bool=False) -> bool:
|
||||
"""
|
||||
Check if a given file or folder is empty or not with the option to create it if it doesn't exit
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
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)))
|
||||
elif make:
|
||||
if os.path.splitext(path)[-1]:
|
||||
x = open(path, 'x')
|
||||
x.close()
|
||||
else:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return True
|
|
@ -0,0 +1,44 @@
|
|||
import os, sys, time
|
||||
|
||||
user_scripts = r'e:\projects\monties'
|
||||
shortcuts = {
|
||||
'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(user_scripts, str(time.localtime()[0])),
|
||||
'scripts': user_scripts,
|
||||
'demos': os.path.join(user_scripts, '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(shortcuts['monties'], exist_ok=True)
|
|
@ -1,4 +0,0 @@
|
|||
from .strings import *
|
||||
from .magnitudes import *
|
||||
from .debug import *
|
||||
from .media import *
|
|
@ -1,99 +0,0 @@
|
|||
__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]
|
|
@ -1,100 +0,0 @@
|
|||
"""
|
||||
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))
|
|
@ -1,191 +0,0 @@
|
|||
__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)))
|
|
@ -1,123 +0,0 @@
|
|||
__all__ = "trim normalize hasdirs likefile multisplit forbiddens nameSpacer".split()
|
||||
|
||||
from typing import Iterable
|
||||
import os, re
|
||||
|
||||
from .debug import *
|
||||
|
||||
|
||||
forbiddens = r'\/:?*<>|"'
|
||||
|
||||
def trim(path:str, edge:str=os.sep) -> str:
|
||||
"""
|
||||
Remove trailing/leading separators (or edges) from a path string
|
||||
"""
|
||||
out = path[:]
|
||||
while any(out.startswith(x) for x in (edge,' ')):
|
||||
out = out[1:]
|
||||
while any(out.endswith(x) for x in (edge,' ')):
|
||||
out = out[:-1]
|
||||
return out
|
||||
|
||||
|
||||
def normalize(path:str, relative:bool=False, force:bool=False) -> str:
|
||||
"""
|
||||
Standardize a path for a current operating system.
|
||||
Given an equivalently structured file/project system, this should make code reusable across platforms
|
||||
If force is true, all forbidden characters will be replaced with an empty string
|
||||
"""
|
||||
other = ''.join(i for i in '\/' if not i==os.sep)
|
||||
if force:
|
||||
new = ''.join(i for i in path if not i in forbiddens)
|
||||
else:
|
||||
new = path[:]
|
||||
if other in path:
|
||||
terms = []
|
||||
for term in path.split(os.sep):
|
||||
if other in term:
|
||||
for part in term.split(other):
|
||||
terms.append(part)
|
||||
else:
|
||||
terms.append(term)
|
||||
new = os.path.join(*terms)
|
||||
if relative:
|
||||
new = '.'+os.sep+path
|
||||
return new
|
||||
|
||||
|
||||
def hasdirs(path:str) -> bool:
|
||||
"""
|
||||
check if a path string contains directories or not
|
||||
"""
|
||||
return bool(re.search(re.escape(os.sep), normalize(path)))
|
||||
|
||||
|
||||
def likefile(path:str) -> bool:
|
||||
"""
|
||||
Check if a path string looks like a file or not
|
||||
"""
|
||||
path = normalize(path)
|
||||
return bool(re.search('readme$|.+\.(\S)+$',path.split(os.sep)[-1], re.I))
|
||||
|
||||
|
||||
def multisplit(splitters:Iterable[str], target:str='abc') -> generator:
|
||||
"""
|
||||
Split a string by a the elements of a sequence
|
||||
>>> list(multisplit(',`* ', 'wma,wmv mp3`vga*mp4 ,`* ogg'))
|
||||
['wma', 'wmv', 'mp3', 'vga', 'mp4', 'ogg']
|
||||
"""
|
||||
splitters = iter(splitters)
|
||||
result = target.split(next(splitters))
|
||||
for splitter in splitters:
|
||||
result = [*chain.from_iterable(i.split(splitter) for i in result)]
|
||||
yield from filter(None, result)
|
||||
|
||||
|
||||
def nameSpacer(path:str, sep:str='_') -> str:
|
||||
"""
|
||||
Returns a unique version of a given string by appending an integer
|
||||
Dependencies: os module
|
||||
In: string
|
||||
Out: string
|
||||
"""
|
||||
id = 2
|
||||
oldPath = path[:]
|
||||
while os.path.exists(path): ##for general use
|
||||
newPath = list(os.path.splitext(path))
|
||||
if sep in newPath[0]:
|
||||
if newPath[0].split(sep)[-1].isnumeric():
|
||||
# print('case1a')
|
||||
id = newPath[0].split(sep)[-1]
|
||||
newPath[0] = newPath[0].replace(f'{sep}{id}', f'{sep}{str(int(id)+1)}')
|
||||
path = ''.join(newPath)
|
||||
else:
|
||||
# print('case1b')
|
||||
newPath[0] += f'{sep}{id}'
|
||||
path = ''.join(newPath)
|
||||
id += 1
|
||||
else:
|
||||
# print('case2')
|
||||
newPath[0] += f'{sep}{id}'
|
||||
path = ''.join(newPath)
|
||||
id += 1
|
||||
return path
|
||||
|
||||
if __name__ == '__main__':
|
||||
mock = 'drive: users user place subplace file.extension.secondextension'.split()
|
||||
# mock = 'drive: users user place subplace readme'.split()
|
||||
testPaths = [
|
||||
''.join(mock),
|
||||
os.path.join(*mock),
|
||||
os.path.join(*mock[:4])+'/'+'/'.join(mock[4:]),
|
||||
os.path.join(*mock[3:4])+'/'+'/'.join(mock[4:]),
|
||||
os.path.join(*mock[:-1]),
|
||||
'/'.join(mock[:-1]),
|
||||
mock[-1]
|
||||
]
|
||||
tests = [(eval(f'{f}(r"{p}")'), f, p) for f in __all__ for p in testPaths]
|
||||
|
||||
show(tests)
|
||||
# show(zip(map(type, map(lambda x: Address(x).obj, testPaths)), testPaths))
|
||||
# show(zip(testPaths, map(normalize, testPaths)))
|
||||
# show(zip(testPaths, map(hasdirs, testPaths)))
|
||||
# show(zip(testPaths, map(likefile, testPaths)))
|
|
@ -0,0 +1,251 @@
|
|||
# __all__ = "walk files folders".split()
|
||||
__all__ = "search_iter files show search folders walk".split()
|
||||
|
||||
|
||||
from typing import Iterator, Iterable, Any
|
||||
from itertools import permutations, chain
|
||||
import os, re
|
||||
from sl4ng import pop, show, multisplit, join, mainame, eq
|
||||
|
||||
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
|
||||
This will always yield files.
|
||||
If you only want directories, look for the "folders" function in filey.utils.walkers
|
||||
|
||||
Caution: if you pass a relative pathname for top, don't change the
|
||||
current working directory between resumptions of walk. walk never
|
||||
changes the current directory, and assumes that the client doesn't
|
||||
either. - taken from os.walk documentation
|
||||
|
||||
Params
|
||||
root: str|pathlike|Place
|
||||
path to starting directory
|
||||
folders
|
||||
(True -> omit, False -> include) paths to directories
|
||||
absolute
|
||||
yield (True -> absolute paths, False -> names only)
|
||||
"""
|
||||
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 dirs:
|
||||
yield (name, path)[absolute]
|
||||
yield from walk(path, dirs=dirs, absolute=absolute)
|
||||
else:
|
||||
yield (name, path)[absolute]
|
||||
|
||||
def parse_extensions(extensions:str) -> re.Pattern:
|
||||
"""
|
||||
Create a regex parser to check for file extensions.
|
||||
Note: Separate extensions by one of
|
||||
[',', '`', '*', ' ']
|
||||
"""
|
||||
sep = [i for i in ',`* ' if i in extensions]
|
||||
pattern = '|'.join(f'\.{i}$' for i in extensions.split(sep[0] if sep else None))
|
||||
pat = re.compile(pattern, re.I)
|
||||
return pat
|
||||
|
||||
def files(root:str='.', exts:str='', negative:bool=False, absolute:bool=True) -> Iterator[str]:
|
||||
"""
|
||||
Search for files along a directory's tree.
|
||||
Also (in/ex)-clude any whose extension satisfies the requirement
|
||||
|
||||
Params
|
||||
root: str|pathlike|Place
|
||||
path to starting directory
|
||||
exts
|
||||
extensions of interest.
|
||||
you can pass more than one extension at a time by separating them with one of the following:
|
||||
[',', '`', '*', ' ']
|
||||
negative
|
||||
(True -> omit, False -> include) matching extensions
|
||||
absolute
|
||||
yield (True -> abspaths, False -> names only)
|
||||
"""
|
||||
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]
|
||||
|
||||
def folders(root:str='.', absolute:bool=True) -> Iterator[str]:
|
||||
"""
|
||||
Search for files along a directory's tree.
|
||||
Also (in/ex)-clude any whose extension satisfies the requirement
|
||||
|
||||
Params
|
||||
root: str|pathlike|Place
|
||||
path to starting directory
|
||||
exts
|
||||
extensions of interest.
|
||||
you can pass more than one extension at a time by separating them with any of the following:
|
||||
[',', '`', '*', ' ']
|
||||
negative
|
||||
(True -> omit, False -> include) matching extensions
|
||||
absolute
|
||||
yield (True -> abspaths, False -> names only)
|
||||
"""
|
||||
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):
|
||||
yield (name, path)[absolute]
|
||||
yield from folders(path, absolute=absolute)
|
||||
|
||||
def __term_perms(terms:str, case:int, tight:bool) -> re.Pattern:
|
||||
"""
|
||||
compute the regex pattern for posible permutations of search terms
|
||||
"""
|
||||
sep = "[\\ _\\-]*" if tight else "(.)*"
|
||||
if isinstance(terms, str):
|
||||
terms = terms.split()
|
||||
terms = map(re.escape, terms)
|
||||
rack = (sep.join(perm) for perm in permutations(terms))
|
||||
return re.compile("|".join(rack), case)
|
||||
|
||||
|
||||
def search_iter(iterable:str, terms:Iterable[str], exts:str='', case:bool=False, negative:bool=False, dirs:int=0, strict:int=1, regex:bool=False, names:bool=True) -> Iterator[str]:
|
||||
"""
|
||||
Find files matching the given terms within a directory's tree
|
||||
Params
|
||||
root
|
||||
the directory in which the walking search commences
|
||||
separate by spaces
|
||||
terms
|
||||
the terms sought after
|
||||
separate by spaces
|
||||
exts
|
||||
any file extensions you wish to check for
|
||||
case
|
||||
toggle case sensitivity
|
||||
negative
|
||||
Ignored unless dirs==0. Any files matching the terms will be omitted.
|
||||
dirs
|
||||
0 -> ignore all directories
|
||||
1 -> directories and files
|
||||
2 -> directories only
|
||||
strict
|
||||
0 -> match any terms in any order
|
||||
1 -> match all terms in any order (interruptions allowed)
|
||||
2 -> match all terms in any order (no interruptions allowed)
|
||||
3 -> match all terms in given order (interruptions)
|
||||
4 -> match all terms in given order (no interruptions)
|
||||
combinations of the following are not counted as interruptions:
|
||||
[' ', '_', '-']
|
||||
5 -> match string will be compiled as though it was preformatted regex
|
||||
names
|
||||
True -> only yield results whose names match
|
||||
False -> yield results who match at any level
|
||||
"""
|
||||
tight = strict in (2, 4)
|
||||
sep = "[\\ _\\-]*" if tight else "(.)*"
|
||||
scope = (str, lambda x: os.path.split(x)[1])[names]
|
||||
case = 0 if case else re.I
|
||||
|
||||
expat = parse_extensions(exts)
|
||||
tepat = {
|
||||
0: re.compile("|".join(map(re.escape, terms.split())), case),
|
||||
1: __term_perms(terms, case, 0),
|
||||
2: __term_perms(terms, case, 1),
|
||||
3: re.compile(sep.join(map(re.escape, terms)), case),
|
||||
4: re.compile(sep.join(map(re.escape, terms)), case),
|
||||
5: re.compile(terms, case)
|
||||
}[strict] if not regex else re.compile(terms, case)
|
||||
|
||||
predicate = (
|
||||
lambda i: tepat.search(i) and expat.search(i),
|
||||
lambda i: not (tepat.search(i) or expat.search(i)),
|
||||
)[negative]
|
||||
|
||||
for i in iterable:
|
||||
if predicate(scope(i)):
|
||||
yield i
|
||||
|
||||
def search(root:str, terms:Iterable[str], exts:str='', case:bool=False, negative:bool=False, dirs:int=0, strict:int=1, regex:bool=False, names:bool=True) -> Iterator[str]:
|
||||
"""
|
||||
Find files matching the given terms within a directory's tree
|
||||
Uses linear search
|
||||
Params
|
||||
root
|
||||
the directory in which the walking search commences
|
||||
separate by spaces
|
||||
terms
|
||||
the terms sought after
|
||||
exts
|
||||
any file extensions you wish to check for
|
||||
separate by spaces
|
||||
case
|
||||
toggle case sensitivity
|
||||
negative
|
||||
Any files/folders with names or extensions matching the terms and exts will be omitted.
|
||||
dirs
|
||||
0 -> ignore all directories
|
||||
1 -> directories and files
|
||||
2 -> directories only
|
||||
strict
|
||||
0 -> match any terms in any order
|
||||
1 -> match all terms in any order (interruptions allowed)
|
||||
2 -> match all terms in any order (no interruptions allowed)
|
||||
3 -> match all terms in given order (interruptions)
|
||||
4 -> match all terms in given order (no interruptions)
|
||||
combinations of the following are not counted as interruptions:
|
||||
[' ', '_', '-']
|
||||
5 -> match string will be compiled as though it was preformatted regex
|
||||
names
|
||||
True -> only yield results whose names match
|
||||
False -> yield results who match at any level
|
||||
"""
|
||||
func = {
|
||||
0: files,
|
||||
1: walk,
|
||||
2: folders,
|
||||
}[dirs]
|
||||
kwargs = {
|
||||
0: { "exts": exts, "negative": negative, "absolute": True, },
|
||||
1: { "dirs": True, "absolute": True, },
|
||||
2: { "absolute": True, },
|
||||
}[dirs]
|
||||
|
||||
yield from search_iter(
|
||||
(i for i in func(root, **kwargs)),
|
||||
terms=terms, exts=exts, case=case,
|
||||
negative=negative, dirs=dirs,
|
||||
strict=strict, names=names
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
folder = r'E:\Projects\Monties\2021\file management'
|
||||
folder = 'C:\\Users\\Kenneth\\Downloads\\byextension'
|
||||
folder = r"E:\Projects\Monties\2021\media\file_management\filey"
|
||||
folder = "../../.."
|
||||
folder = "c:/users/kenneth/pictures"
|
||||
|
||||
# box = [*walk(folder, absolute=True)]
|
||||
# print(all(map(os.path.exists, box)))
|
||||
# print(__file__ in box)
|
||||
# show(box, 0, 1)
|
||||
|
||||
# box = [*walk(folder, dirs=False, absolute=True)]
|
||||
# box2 = [*files(folder, exts='', absolute=True)]
|
||||
# print(all(i in box2 for i in box) and all(i in box for i in box2))
|
||||
|
||||
# exts = 'jpg .jpeg pdf'
|
||||
# show([*files(folder, exts=exts, negative=False, absolute=True)])
|
||||
|
||||
# box = [*walk(folder, dirs=False, absolute=True)]
|
||||
# box2 = [*files(folder, exts=exts, negative=False, absolute=True)]
|
||||
# box3 = [*files(folder, exts=exts, negative=True, absolute=True)]
|
||||
# print(eq(map(sorted, (box2+box3, box))))
|
||||
|
||||
|
||||
# box4 = [*folders(folder, True)]
|
||||
# show(box4, 0, 1)
|
||||
# show(search(folder, '__init__'))
|
||||
show(search(folder, '_', 'png'))
|
|
@ -0,0 +1,33 @@
|
|||
Flexible functions and handy handles for your favourite filey things.
|
||||
|
||||
ΓÇ£ΓÇ£ΓÇ£python # central_nervous_system.py import os
|
||||
|
||||
from filey import Directory, File, ffplay, convert
|
||||
|
||||
this = **file** here = os.path.dirname(**file**) pwd = Directory(here)
|
||||
|
||||
here == str(pwd) # True
|
||||
|
||||
def cd(path): global pwd os.chdir(path) pwd.path = path
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- c = Directory('c:/') -->
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- e = Directory('e:/') -->
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- f = Directory('f:/') -->
|
||||
|
||||
user = Directory(ΓÇÿ~ΓÇÖ) music = user / ΓÇ£musicΓÇ¥ album1 =
|
||||
music[ΓÇ£collection/Alix Perez/1984ΓÇ¥] # raises an OSError if it cannot be
|
||||
found album2 = music[ΓÇ£collection/dBridge/the gemini principleΓÇ¥] artist1
|
||||
= music.items.collection[ΓÇ£venuq/escrowsΓÇ¥] playlist = Library(album1,
|
||||
album2, artist1) for track in playlist(ΓÇÖΓÇÖ): # search library for any
|
||||
matches of the empty string. Directories are also searchable via
|
||||
**call** ffplay(track.path)
|
||||
|
||||
ΓÇ£ΓÇ£ΓÇ¥
|
|
@ -0,0 +1,5 @@
|
|||
dill
|
||||
filetype
|
||||
pyperclip
|
||||
send2trash
|
||||
sl4ng
|
25
setup.py
25
setup.py
|
@ -5,11 +5,10 @@ with open('README.md', 'r') as fob:
|
|||
|
||||
setup(
|
||||
name='filey',
|
||||
version='0.0.1',
|
||||
author='Kenneth Elisandro',
|
||||
author_email='eli2and40@tilde.club',
|
||||
url='https://tildegit.org/eli2and40/cabinet',
|
||||
# packages=find_packages(),
|
||||
version='0.0.2',
|
||||
author='Kenneth Sabalo',
|
||||
author_email='kennethsantanasablo@gmail.com',
|
||||
url='https://tildegit.org/eli2and40/filey',
|
||||
packages=['filey'],
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
|
@ -18,15 +17,15 @@ setup(
|
|||
],
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
keywords='utilities productivity',
|
||||
keywords='utilities operating path file system',
|
||||
license='MIT',
|
||||
requires=[
|
||||
'filetype',
|
||||
'audio_metadata',
|
||||
'send2trash',
|
||||
'sl4ng'
|
||||
"audio_metadata",
|
||||
"dill",
|
||||
"filetype",
|
||||
"send2trash",
|
||||
"sl4ng",
|
||||
"pypeclip",
|
||||
],
|
||||
# py_modules=['filey'],
|
||||
python_requires='>=3.8',
|
||||
|
||||
python_requires='>3.9',
|
||||
)
|
Loading…
Reference in New Issue