diff --git a/Ils b/Ils new file mode 100644 index 0000000..2fd6e35 --- /dev/null +++ b/Ils @@ -0,0 +1,241 @@ +#!/bin/python3 +import argparse +import datetime +import glob +import json +import os +import time + +installed=False +while not installed: + try: + from terminaltables import DoubleTable + from terminaltables import GithubFlavoredMarkdownTable + installed=True + except: + print("Installing terminaltables") + os.system("sudo pip3 install terminaltables") + + +def uTd(ts): + return datetime.datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') + +def get_size(start_path = '.'): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + # skip if it is symbolic link + if not os.path.islink(fp): + total_size += os.path.getsize(fp) + + return total_size + +def oneTrueNoMore(list): + flag = False + for element in list: + if element: + if flag: + return False + flag = True + return True + + +def getTab(data, args): + if not oneTrueNoMore([ + args.by_size_descending, + args.by_size_ascending, + args.by_adate_descending, + args.by_adate_ascending, + args.by_cdate_descending, + args.by_cdate_ascending, + args.by_mdate_descending, + args.by_mdate_ascending, + args.by_name_descending, + args.by_name_ascending, + ]): + print("You must specify only ONE sorting mode") + exit(1) + if args.by_size_descending: + data.sort(key=lambda el: el["size"]["size"]) + data.reverse() + elif args.by_size_ascending: + data.sort(key=lambda el: el["size"]["size"]) + elif args.by_adate_descending: + data.sort(key=lambda el: el["dates"]["atime"]) + data.reverse() + elif args.by_adate_ascending: + data.sort(key=lambda el: el["dates"]["atime"]) + elif args.by_cdate_descending: + data.sort(key=lambda el: el["dates"]["ctime"]) + data.reverse() + elif args.by_cdate_ascending: + data.sort(key=lambda el: el["dates"]["ctime"]) + elif args.by_mdate_descending: + data.sort(key=lambda el: el["dates"]["mtime"]) + data.reverse() + elif args.by_mdate_ascending: + data.sort(key=lambda el: el["dates"]["mtime"]) + elif args.by_name_descending: + data.sort(key=lambda el: el["name"]) + data.reverse() + elif args.by_name_ascending: + data.sort(key=lambda el: el["name"]) + if args.group: + data.sort(key=lambda el: el["group"]) + tab = [["Type", "Name", "Size", "Permissions", "Dates"]] + for r in data: + tab.append([r["desc"], r["name"], r["size"]["human"], r["permissions"], r["dates"]["humanAll"]]) + return tab + + +def sizeof_fmt(num, suffix='B'): + for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + +data=[] +parser = argparse.ArgumentParser(description='Improved ls') +parser.add_argument('DIRECTORY', metavar='dir', type=str, help='DIRECTORY') +parser.add_argument('--by-size-descending', "-sd", action='store_true', help='Sort by descending size') +parser.add_argument('--by-size-ascending', "-sa", action='store_true', help='Sort by ascending size') +parser.add_argument('--by-adate-descending', "-ldd", action='store_true', help='Sort by descending access date') +parser.add_argument('--by-adate-ascending', "-lda", action='store_true', help='Sort by ascending access date') +parser.add_argument('--by-cdate-descending', "-cdd", action='store_true', help='Sort by descending creation date') +parser.add_argument('--by-cdate-ascending', "-cda", action='store_true', help='Sort by ascending creation date') +parser.add_argument('--by-mdate-descending', "-mdd", action='store_true', help='Sort by descending edit date') +parser.add_argument('--by-mdate-ascending', "-mda", action='store_true', help='Sort by ascending edit date') +parser.add_argument('--by-name-descending', "-nd", action='store_true', help='Sort by descending name') +parser.add_argument('--by-name-ascending', "-na", action='store_true', help='Sort by ascending name (Default)') +parser.add_argument('--group', "-g", action='store_true', help='Group by type (directories, files, symlinks)') +parser.add_argument('--markdown', "-md", action='store_true', help='Print markdown table') +parser.add_argument('--show-author-info', "-a", action='store_true', + help='Show author information about the directory (folders created with Imkdir)') +args = parser.parse_args() +if not os.path.isdir(args.DIRECTORY): + print("Directory \""+args.DIRECTORY+"\" does not exist") + exit(1) +if not os.access(args.DIRECTORY, os.R_OK): + print("Cannot access \"" + args.DIRECTORY + "\"") + exit(1) +k = glob.glob(args.DIRECTORY + "/*") +for element in k: + if os.path.islink(element): + typ = "Symlink" + name = os.path.basename(element) + group = typ + elif os.path.isdir(element): + group = "Directory" + descriptorArray = [] + dirs = len([name for name in os.listdir(element) if os.path.isdir(os.path.join(element, name))]) + if dirs > 0: + if dirs == 1: + descMeasure = "directory" + else: + descMeasure = "directories" + descriptorArray.append(str(dirs) + " " + descMeasure) + files = len([name for name in os.listdir(element) if os.path.isfile(os.path.join(element, name))]) + if files > 0: + if files == 1: + descMeasure = "file" + else: + descMeasure = "files" + descriptorArray.append(str(files) + " " + str(descMeasure)) + symlinks = len([name for name in os.listdir(element) if os.path.islink(os.path.join(element, name))]) + if symlinks > 0: + if symlinks == 1: + descMeasure = "symlink" + else: + symlinks = "symlinks" + descriptorArray.append(str(symlinks) + " " + str(descMeasure)) + typ = "Directory" + if len(descriptorArray): + typ += "\n (" + ", ".join(descriptorArray) + ")" + name = os.path.basename(element) + else: + typ = "File" + name = os.path.basename(element) + rr = name.split(".") + ext = "" + if len(rr) > 1: + ext = rr[len(rr) - 1].upper() + typ += " " + ext + group = typ + stats = os.stat(element) + size = sizeof_fmt(stats.st_size) + dates = "Opened: " + uTd(stats.st_atime) + "\nEdited: " + uTd(stats.st_mtime) + "\nCreated: " + uTd(stats.st_ctime) + mask = oct(stats.st_mode)[-3:] + dT={ + "desc": typ, + "name": name, + "size": { + "human": size, + "size": stats.st_size + } + , + "permissions": mask, + "dates": { + "atime": stats.st_atime, + "mtime": stats.st_mtime, + "ctime": stats.st_ctime, + "humanAll": dates} + , + "group": group + } + if dT["group"]== "Directory": + dT["size"]["size"]=get_size(element) + dT["size"]["human"]=sizeof_fmt(dT["size"]["size"]) + data.append(dT) +tableA = getTab(data, args) +if args.markdown: + table = GithubFlavoredMarkdownTable(tableA) +else: + table = DoubleTable(tableA) +table.inner_row_border = True +print("\n"+args.DIRECTORY + "\n\n+---------+\n") +tabAuthor = [] +columnsAuthor = { + "name": "Name", + "surname": "Suraname", + "email": "Email", + "gpg": "GPG Key", + "gh": "Github", + "te": "Telegram", + "tw": "Twitter", + "notes": "Notes", + "created": "Created", + "utcoffset": "Timezone", + "platform": "Platform" +} +if args.show_author_info and os.path.isfile(args.DIRECTORY + "/.ilft"): + data = json.loads(open(args.DIRECTORY + "/.ilft", "r").read()) + for key in data.keys(): + k=key + if not data[key]: + continue + if k in columnsAuthor.keys(): + k=columnsAuthor[k] + if key=="created": + data[key]=uTd(data[key]) + elif key=="utcoffset": + ts=int(data[key]) + taa=abs(ts) + data[key] = time.strftime('%Hh %Mm', time.gmtime(taa)) + if ts<0: + data[key]="-"+data[key] + else: + data[key]="+"+data[key] + + tabAuthor.append([k,data[key]]) + if args.markdown: + tabAuthor.insert(0,["Characteristic","Value"]) + TA = GithubFlavoredMarkdownTable(tabAuthor) + else: + TA = DoubleTable(tabAuthor) + + TA.inner_heading_row_border=False + print(TA.table+"\n\n") + +print(table.table) diff --git a/Imkdir b/Imkdir new file mode 100644 index 0000000..68322d3 --- /dev/null +++ b/Imkdir @@ -0,0 +1,66 @@ +#! /bin/python3 +import argparse,shlex,os,subprocess,json,time,platform,datetime +def mkdir(cmd,dirs,data): + try: + subprocess.check_output(cmd, shell=True, text=True) + for d in dirs: + open(d+"/.ilft","w").write(json.dumps(data)) + except Exception as e: + return False +config_folder=os.path.expanduser("~")+"/.config/ilftools/" +parser = argparse.ArgumentParser(description='Improved mkdir') +parser.add_argument('DIRECTORIES', metavar='dir', type=str, nargs='+',help='DIRECTORY(ies)') +parser.add_argument('--version', action='version', version='%(prog)s 0.1') +parser.add_argument('--mode', "-m", metavar='mode', type=str, help=' set file mode (as in chmod), not a=rwx - umask') +parser.add_argument('--parents',"-p",action='store_true',help='no error if existing, make parent directories as needed') +parser.add_argument('--verbose',"-v",action='store_true',help='print a message for each created directory') +parser.add_argument('-Z',action='store_true',help='set SELinux security context of each created directory to the default type') +parser.add_argument('--context', metavar='context', type=str,help='like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX') +parser.add_argument('--emulate',"-e", action='store_true',help='Emulate mkdir directly, no additional features') +parser.add_argument('--anonymous',"-a", action='store_true',help='Don\'t add personal info to the directories') +args = parser.parse_args() +cmd="mkdir " +if args.Z: + cmd+="-Z " +if args.mode != None : + cmd+="-m "+shlex.quote(args.mode)+" " +if args.context != None: + cmd+="--context "+shlex.quote(args.context)+" " +if args.parents: + cmd+="-p " +if args.verbose: + cmd+="-v " +for d in args.DIRECTORIES: + cmd+=shlex.quote(d)+" " +if not os.path.exists(config_folder+"author"): + r=str(input("No author info has been specified for this user.\nIt will be stored in /.config/ilftools/\nDo you want to set the data now? [y/N]? ")) + rn=r.strip().lower() + if rn=="yes" or rn=="y": + print("--------\nCAUTION: this information will be added to any repository created by Imkdir\nIf you want to create a directory without personal data, try Imkdir -a\n--------") + name=str(input("Name: ")) + surname=str(input("Surname: ")) + nickname=str(input("Nickname: ")) + email=str(input("Email: ")) + gpg=str(input("GPG key: ")) + gh=str(input("GitHub usename: ")) + te=str(input("Telegram usename: ")) + tw=str(input("Twitter usename: ")) + notes=str(input("Notes:")) + data={"name":name,"surname":surname,"nickname":nickname,"email":email,"gpg":gpg,"gh":gh,"te":te,"tw":tw,"notes":notes} + dsj=json.dumps(data) + os.system("mkdir -p "+config_folder) + open(config_folder+"author","w").write(dsj) + dsj=data + print("--------\nConfiguration saved, proceeding to make the directory\n--------") + else: + dsj={} +else: + if not args.anonymous: + dsj=json.loads(open(config_folder+"author","r").read()) + else: + dsj={} + +dsj["created"]=datetime.datetime.utcnow().timestamp() +dsj["utcoffset"]=-time.timezone +dsj["platform"]=platform.system() +mkdir(cmd,args.DIRECTORIES,dsj) diff --git a/README.md b/README.md new file mode 100644 index 0000000..857869a --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# ILFT - Interactive Linux File Tools + +Collection of tools for interactive and metadata-friendly directory and file management + + + +* **Ils** + + *Interactive ls* + + Ls, with pretty tables, dir overviews, permissions and much more + + The author data is fetched from ``{DIR}/.ilft`` as saved by **Imkdir** + + Example + + + + ![img](https://i.imgur.com/CbWOHxA.png) + + ![img](https://i.imgur.com/K7YO5YP.png) + + ``` + usage: Ils [-h] [--by-size-descending] [--by-size-ascending] [--by-adate-descending] + [--by-adate-ascending] [--by-cdate-descending] [--by-cdate-ascending] + [--by-mdate-descending] [--by-mdate-ascending] [--by-name-descending] + [--by-name-ascending] [--group] [--markdown] [--show-author-info] + dir + + Improved ls + + positional arguments: + dir DIRECTORY + + optional arguments: + -h, --help show this help message and exit + --by-size-descending, -sd + Sort by descending size + --by-size-ascending, -sa + Sort by ascending size + --by-adate-descending, -ldd + Sort by descending access date + --by-adate-ascending, -lda + Sort by ascending access date + --by-cdate-descending, -cdd + Sort by descending creation date + --by-cdate-ascending, -cda + Sort by ascending creation date + --by-mdate-descending, -mdd + Sort by descending edit date + --by-mdate-ascending, -mda + Sort by ascending edit date + --by-name-descending, -nd + Sort by descending name + --by-name-ascending, -na + Sort by ascending name (Default) + --group, -g Group by type (directories, files, symlinks) + --markdown, -md Print markdown table + --show-author-info, -a + Show author information about the directory (folders created with + Imkdir) + + ``` + +* **Ilmkdir** + + *Interactive mkdir* + + Mkdir with author data + + On the first run, it saved personal information in ``~/.config/ilftools/author`` through a guided procedure + + When a directory is created through this tool, data is saved in ``{DIR}/.ilft`` + + ``` + usage: Imkdir [-h] [--version] [--mode mode] [--parents] [--verbose] [-Z] + [--context context] [--emulate] [--anonymous] + dir [dir ...] + + Improved mkdir + + positional arguments: + dir DIRECTORY(ies) + + optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + --mode mode, -m mode set file mode (as in chmod), not a=rwx - umask + --parents, -p no error if existing, make parent directories as needed + --verbose, -v print a message for each created directory + -Z set SELinux security context of each created directory to the + default type + --context context like -Z, or if CTX is specified then set the SELinux or SMACK + security context to CTX + --emulate, -e Emulate mkdir directly, no additional features + --anonymous, -a Don't add personal info to the directories + ``` + + \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..47d5007 --- /dev/null +++ b/install.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Installing..." +sudo chmod +x Ils +sudo cp Ils /bin/Ils +sudo chmod +x Imkdir +sudo cp Imkdir /bin/Imkdir +echo "Installed!"