#!/usr/bin/python
"""
as bash "ls"
Params:
-a : hidden files
-m : display type-mime (if python-magic installed)
-s : sort by size (less -> big)
-t : sort by date (old -> recent)
-o : display owner
-d : display only date
"""
import os
import sys
import stat
from pathlib import Path
import datetime
import operator
try:
import magic # pacman -S python-magic --asdeps
except ModuleNotFoundError:
pass
def rgb_fg(r: int, g: int, b: int) -> str:
return '\x1b[38;2;' + str(r) + ';' + str(g) + ';' + str(b) + 'm'
def rgb_bg(r: int, g: int, b: int) -> str:
return '\x1b[48;2;' + str(r) + ';' + str(g) + ';' + str(b) + 'm'
def html_color(html, txt=True):
if txt:
return rgb_fg(int('0x'+html[0:2], 0), int('0x'+html[2:4], 0), int('0x'+html[4:6], 0))
return rgb_bg(int('0x'+html[0:2], 0), int('0x'+html[2:4], 0), int('0x'+html[4:6], 0))
COLOR_NONE = '\33[0m'
COLOR_LINK = html_color('666666')
COLOR_DIR = html_color('33ccee')
COLOR_EXE = html_color('33eecc')
COLOR_MIME = html_color('888888')
COLOR_GIT = html_color('999999')
class Node:
def __init__(self, node, file_stats: None, use_mime=False, use_owner=False, short_date=True):
self.node = node # Path
self.stat = file_stats or node.stat() # FileNotFoundError = bad link
self.use_mime = use_mime and 'magic' in sys.modules
self.use_owner = use_owner
self.short_date = short_date
def get_size(self):
if self.node.is_dir():
try:
return len(set(self.node.iterdir()))
except PermissionError:
return ''
else:
return self.convert_bytes(self.stat.st_size)
def get_owner(self):
ret = ''
try:
one = self.node.owner()
except KeyError:
one = '?' # user on other linux
two = ''
try:
two = f"/{self.node.group()}"
except KeyError:
two = f"/{self.stat.st_gid}" # group on other linux
if two[1:] == one:
two = ''
if self.stat.st_uid > 999:
#ret = f"{one}({self.stat.st_uid}){two}"
ret = f"{one}{two}"
else:
ret = f"{one}"
return f"{ret:16}"
def get_time(self):
long = 10 if self.short_date else 16
return str(datetime.datetime.fromtimestamp(self.stat.st_mtime))[:long]
def get_name(self):
name = f"{COLOR_DIR}{self.node.name}/{COLOR_NONE}" if self.node.is_dir() else self.node.name
name = f"{name} {COLOR_LINK}-> {self.node.resolve()}{COLOR_NONE}" if self.node.is_symlink() else name
if self.is_exe:
name = f"{COLOR_EXE}{name}{COLOR_NONE}"
if self.node.is_dir():
gits = self.get_git()
name = f"{name} {COLOR_GIT}{gits}{COLOR_NONE}"
return f"{name}"
@property
def is_exe(self):
return self.stat.st_mode & (stat.S_IXGRP | stat.S_IXUSR)
def is_git(self):
git_dir = Path(self.node / ".git/refs/heads")
try:
branches = [x.name for x in git_dir.iterdir()]
if len(branches) > 1:
with open(self.node / ".git/HEAD", 'rt') as f_read:
current = f_read.readline().split('/').pop().rstrip()
branches[branches.index(current)] = f"{current}*"
return tuple(branches)
except (PermissionError, FileNotFoundError):
pass
return tuple()
def get_git(self):
gits = self.is_git()
if gits:
return str(gits).replace("'", '')
return ''
def is_dir(self):
""" for sort """
return self.node.is_dir()
def name_lower(self):
""" for sort """
return self.node.name.lower()
def size(self):
""" for sort """
return self.stat.st_size
def time(self):
""" for sort """
return self.stat.st_mtime
def __str__(self):
""" display file with infos """
mime = ""
try:
if self.use_mime and not self.node.is_dir():
mime = magic.detect_from_filename(self.node).mime_type
except ValueError:
pass
owner = ''
if self.use_owner:
owner = self.get_owner()
datas = (
f"{oct(stat.S_IMODE(self.stat.st_mode))[2:]:4}",
owner,
f"{self.get_size():>10}",
f"{self.get_time()}",
f"{self.get_icon(mime)} {self.get_name():66}",
f"{COLOR_MIME}{mime}{COLOR_NONE}",
)
return ' '.join(datas)
def get_icon(self, mime=None):
"""
🚫 🌀
🔴 🔵 🔶 🔷 🔸 🔹
"""
filenames = {
'.directory': '🐬',
'lost+found/' : '🧻', # 🚾
'.ssh' : '🔐', #'🔑', 🔒
'.config' : '🔧',
}
extensions = {
'.php': '🐘',
'.py': '🐍',
'.png': '🌌',
'.jpg': '🌌',
'.txt': '📝',
'.log': '📄',
'.conf': '🔧',
'.css': '📑',
'.html': '🌐',
'.service': '⚙',
'.pacsave': '🚸',
'.fr': '🇫🇷',
'.en': '🇬🇧',
}
mimes = {
'inode/directory': '📁',
'image/png': '🌌',
'image/gif': '🌌',
'image/vnd.microsoft.icon': '🌌',
'inode/symlink': '🔗',
'text/plain': '📄',
'text/x-php': '🐘',
'text/x-python': '🐍',
'application/json': '📑',
'text/html': '🌐',
'text/x-shellscript': '🌀',
'text/x-objective-c': '🌀',
'application/octet-stream': ' ',
}
ico = filenames.get(self.node.name, None)
#print(self.node.name, ico)
if ico:
return ico
if self.node.is_dir():
return '📁' # 🗂
if self.node.is_symlink():
return '🔗'
if self.is_exe:
return '✨'
ico = extensions.get(self.node.suffix, None)
if ico:
return ico
if 'magic' in sys.modules:
try:
if not mime:
mime = magic.detect_from_filename(self.node).mime_type
ico = mimes.get(mime, None)
if ico:
return ico
except ValueError:
return ' '
return ' '
@staticmethod
def convert_bytes(num):
""" this function will convert bytes to MB.... GB... etc """
if num == 0:
return ''
step_unit = 1000.0 #1024 bad size
for x in 'b', 'K', 'M', 'G', 'T':
if num < step_unit:
if x == 'b':
return f"{num} {x}"
return "%3.1f %s" % (num, x)
num /= step_unit
def parse_user_parameters():
""" parse sys.argv and set var options """
options = {
'hidden': False,
'mime': False,
'owner': False,
'short_date': False,
'help': False,
'sort': 'name_lower',
}
for param in sys.argv:
if not param.startswith('-'):
continue
param = param.rstrip('-').strip()
for char in param:
if char == 'h':
options['help'] = True
if char == 'a':
options['hidden'] = True
if char == 'm':
options['mime'] = True
if char == 'o':
options['owner'] = True
if char == 'd':
options['short_date'] = True
if char == 't':
options['sort'] = 'time'
if char == 's':
options['sort'] = 'size'
'''
# first version
options['hidden'] = '-a' in sys.argv
options['mime'] = '-m' in sys.argv
options['owner'] = '-o' in sys.argv
options['short_date'] = '-d' in sys.argv
if '-t' in sys.argv:
options['sort'] = 'time'
if '-s' in sys.argv:
options['sort'] = 'size'
'''
return options
options = parse_user_parameters()
if options['help']:
print("""Params:
-a : hidden files
-m : display type-mime (if python-magic installed)
-s : sort by size (less -> big)
-t : sort by date (old -> recent)
-o : display owner
-d : display only date""")
exit(0)
def iterdir(path, options):
"""Iterate over the files in this directory. Does not yield any result for the special paths '.' and '..'. """
for entry in os.scandir(path):
if entry.name in {'.', '..'}:
continue
try:
yield Node(
Path(entry.path),
use_mime=options['mime'],
use_owner=options['owner'],
short_date=options['short_date'],
file_stats=entry.stat()
)
except FileNotFoundError:
print(f"🤕🔗 ERROR: \"{entry.name}\" a break link !")
cur_dir = Path.cwd()
if len(sys.argv) > 1:
# find first attr not a parameter, is path to list
for p in sys.argv[1:]:
if p.startswith('-'):
continue
if p.startswith('/'):
cur_dir = Path(p)
elif not p.startswith('-'):
cur_dir = cur_dir / p
if cur_dir.is_dir():
node = Node(cur_dir, use_mime=False, file_stats=False)
print(":: list:", cur_dir, f"{COLOR_MIME}{node.get_git()}{COLOR_NONE}")
count = 0
for node in sorted(sorted(iterdir(cur_dir, options), key=operator.methodcaller(options['sort'])), key=operator.methodcaller('is_dir'), reverse=True):
count += 1
if not options['hidden']:
if not node.node.name.startswith('.'):
print(node)
else:
print(node)
print(f":: ({count}) entrées")
else:
try:
fnode = Node(cur_dir, use_mime=options['mime'], file_stats=False)
print(fnode)
except FileNotFoundError:
pass