rewrite of foldermenu
This commit is contained in:
436
foldermenu.py
436
foldermenu.py
@@ -12,11 +12,53 @@ readline.parse_and_bind('tab: complete')
|
|||||||
readline.parse_and_bind('set editing-mode vi')
|
readline.parse_and_bind('set editing-mode vi')
|
||||||
|
|
||||||
MENUFILE='.foldermenu'
|
MENUFILE='.foldermenu'
|
||||||
|
DEFAULTFILE = os.path.expanduser(os.path.join('~','.config','foldermenu','default'))
|
||||||
|
|
||||||
|
def setup_options():
|
||||||
|
''' Setup the command line options '''
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
parser=ArgumentParser(description="Prints folder specific shell commands stored in '"+MENUFILE+
|
||||||
|
"' file, and in addition the executables in the current folder. "+
|
||||||
|
"Menufile format for each line: 'description:command'. "+
|
||||||
|
"If the command ends in '&' it is run in the background.")
|
||||||
|
parser.add_argument("-d","--no-defaults",action='store_false', dest='defaults',default=True,
|
||||||
|
help="Do not show default entries from "+DEFAULTFILE)
|
||||||
|
parser.add_argument("-x","--no-exec",action='store_false', dest='executables',default=True,
|
||||||
|
help="Do not show executables in the listing.")
|
||||||
|
parser.add_argument("--columns",'-f',type=int,action='store', dest='columns',default=0,
|
||||||
|
help="Number of columns. 0 for automatic")
|
||||||
|
parser.add_argument("-l","--list",action='store_true', dest='list',default=False,
|
||||||
|
help="Print the list, don't wait for keypress.")
|
||||||
|
parser.add_argument("--command",'-c',type=str,action='store', dest='command',
|
||||||
|
help="Command to run (1-9a-z..), any argumets after -- are forwarded to the command ")
|
||||||
|
parser.add_argument("--no-colors",action="store_false",dest="colors",default=True,
|
||||||
|
help="Disable colored output")
|
||||||
|
parser.add_argument("--horizontal",action="store_true",dest="horizontal",default=False,
|
||||||
|
help="Horizontal order of items, only valid for -l listing.")
|
||||||
|
parser.add_argument("args",type=str,action="store",default="",nargs="?",
|
||||||
|
help="Arguments for the command, if -c used. The string will be re-parsed with shutils. Use '--' to skip local parsing.")
|
||||||
|
options=parser.parse_args()
|
||||||
|
if not os.path.exists(DEFAULTFILE):
|
||||||
|
options.defaults=False
|
||||||
|
return options
|
||||||
|
|
||||||
def termsize():
|
def termsize():
|
||||||
rows, columns = os.popen('stty size', 'r').read().split()
|
rows, columns = os.popen('stty size', 'r').read().split()
|
||||||
return (int(rows),int(columns))
|
return (int(rows),int(columns))
|
||||||
|
|
||||||
|
def ichr(i):
|
||||||
|
''' convert integer to 1-9, a-z, A-Z, omitting x '''
|
||||||
|
|
||||||
|
if i < 10:
|
||||||
|
return str(i)
|
||||||
|
i=i + 87
|
||||||
|
if i>119:
|
||||||
|
i=i+1
|
||||||
|
if i>122:
|
||||||
|
i=i-122+64
|
||||||
|
return chr(i)
|
||||||
|
|
||||||
class bc:
|
class bc:
|
||||||
MAG = '\033[95m'
|
MAG = '\033[95m'
|
||||||
BLU = '\033[94m'
|
BLU = '\033[94m'
|
||||||
@@ -34,10 +76,9 @@ class bc:
|
|||||||
self.GRE = ''
|
self.GRE = ''
|
||||||
self.YEL = ''
|
self.YEL = ''
|
||||||
self.RED = ''
|
self.RED = ''
|
||||||
self.END = ''
|
|
||||||
self.CLR = ''
|
|
||||||
self.CYA = ''
|
self.CYA = ''
|
||||||
self.WHI = ''
|
self.WHI = ''
|
||||||
|
self.END = ''
|
||||||
|
|
||||||
def pos(self,y,x):
|
def pos(self,y,x):
|
||||||
return "\033["+str(y)+";"+str(x)+"H"
|
return "\033["+str(y)+";"+str(x)+"H"
|
||||||
@@ -57,172 +98,283 @@ class getch:
|
|||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
return ch
|
return ch
|
||||||
|
|
||||||
def read_menu():
|
class launch_item:
|
||||||
''' Read menu file '''
|
''' Class for launchable items '''
|
||||||
|
description=''
|
||||||
|
command=''
|
||||||
|
launcher=''
|
||||||
|
def __init__(self,command,description,launcher):
|
||||||
|
self.command=command
|
||||||
|
self.description=description
|
||||||
|
self.launcher=launcher
|
||||||
|
|
||||||
|
class entry_collection:
|
||||||
|
''' Object containing the list items, and the printing methods '''
|
||||||
|
options=[]
|
||||||
entries=[]
|
entries=[]
|
||||||
if os.path.exists(MENUFILE):
|
|
||||||
f=file(MENUFILE,'r')
|
|
||||||
for row in f:
|
|
||||||
if row.strip()=='':
|
|
||||||
continue
|
|
||||||
if row[0]=='#':
|
|
||||||
continue
|
|
||||||
row=row.strip().split(':',1)
|
|
||||||
if len(row)==1:
|
|
||||||
row=[row[0], row[0]]
|
|
||||||
else:
|
|
||||||
row=[row[0].strip()+' ('+row[1]+')', row[1]]
|
|
||||||
entries.append(row)
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def read_folder():
|
|
||||||
''' Read folder contents, return executable files and dirs '''
|
|
||||||
|
|
||||||
dirs=[]
|
dirs=[]
|
||||||
dirs.append(['..','..'])
|
menu_keys=[ichr(i+1) for i in range(60)]
|
||||||
executables=[]
|
args=''
|
||||||
for f in os.listdir('.'):
|
dir_mode=False
|
||||||
if os.path.isfile(f):
|
|
||||||
if os.access(f,os.X_OK):
|
|
||||||
executables.append([f,f])
|
|
||||||
if os.path.isdir(f) and f[0]!='.':
|
|
||||||
dirs.append([f,f])
|
|
||||||
dirs.sort(key=lambda x: x[0])
|
|
||||||
executables.sort(key=lambda x: x[0])
|
|
||||||
|
|
||||||
return (executables,dirs)
|
|
||||||
|
|
||||||
def print_help():
|
|
||||||
if not os.path.exists(MENUFILE):
|
|
||||||
print('Consider having a '+MENUFILE+' file containing shell commands / line.')
|
|
||||||
print('Command may be of format "My Description: my_command" or simply "my_command -switch"')
|
|
||||||
|
|
||||||
def ichr(i):
|
|
||||||
''' convert integer to 1-9, a-z, A-Z, omitting x '''
|
|
||||||
|
|
||||||
if i < 10:
|
|
||||||
return str(i)
|
|
||||||
i=i + 87
|
|
||||||
if i>119:
|
|
||||||
i=i+1
|
|
||||||
if i>122:
|
|
||||||
i=i-122+64
|
|
||||||
return chr(i)
|
|
||||||
|
|
||||||
def drawmenu(entries,dir_mode,args=""):
|
|
||||||
maxrows,maxcolumns = termsize()
|
|
||||||
maxrows-=5
|
|
||||||
maxcolumns-=10
|
|
||||||
twocol=False
|
|
||||||
co=bc()
|
co=bc()
|
||||||
if dir_mode:
|
max_length=0
|
||||||
helptext=".:execs"
|
|
||||||
else:
|
def __init__(self, options):
|
||||||
helptext='-:args ('+co.END+args+co.YEL+') .:folders'
|
self.options=options
|
||||||
|
self.initialize()
|
||||||
|
if not self.options.colors:
|
||||||
|
self.co.disable()
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.entries=[]
|
||||||
|
self.dirs=[]
|
||||||
|
if self.options.defaults:
|
||||||
|
self.read_menu(DEFAULTFILE)
|
||||||
|
self.read_menu()
|
||||||
|
self.read_folder()
|
||||||
|
if len(self.entries)>0:
|
||||||
|
self.max_length=max([len(e.description) for e in self.entries])+1
|
||||||
|
|
||||||
|
def set_args(self,args):
|
||||||
|
self.args=args;
|
||||||
|
|
||||||
print(co.END+co.CLR+co.pos(1,3)+co.YEL+'FolderMenu x:exit '+helptext+co.END)
|
def read_menu(self,menu_file=MENUFILE):
|
||||||
if len(entries)>10:
|
''' Read the menu file '''
|
||||||
twocol=True
|
if os.path.exists(menu_file):
|
||||||
maxrows=int(math.ceil(min(maxrows/2.0, len(entries)/2.0)))
|
f=file(menu_file,'r')
|
||||||
maxcolumns=int(math.ceil(maxcolumns/2.0))
|
for row in f:
|
||||||
r=1
|
if row.strip()=='':
|
||||||
for e in range(len(entries)):
|
continue
|
||||||
if r>maxrows:
|
if row[0]=='#':
|
||||||
break
|
continue
|
||||||
printline=entries[e][1]
|
row=row.strip().split(':',1)
|
||||||
if len(printline)>maxcolumns:
|
if len(row)==1:
|
||||||
printline=printline[:maxcolumns]+"..."
|
row=[row[0], row[0]]
|
||||||
print(co.WHI+entries[e][0]+co.END+' '+entries[e][3]+printline+co.END)
|
else:
|
||||||
r=1+r
|
row=[row[0], row[1].strip()+' ('+row[0].strip()+')']
|
||||||
if twocol:
|
self.entries.append(launch_item(command=row[0],
|
||||||
|
description=row[1],
|
||||||
|
launcher="menu"))
|
||||||
|
def read_folder(self):
|
||||||
|
''' Read folder contents, return executable files and dirs '''
|
||||||
|
|
||||||
|
self.dirs.append(launch_item(command='..',
|
||||||
|
description='..',
|
||||||
|
launcher="dir"))
|
||||||
|
dirs=[]
|
||||||
|
executables=[]
|
||||||
|
for f in os.listdir('.'):
|
||||||
|
if os.path.isfile(f):
|
||||||
|
if os.access(f,os.X_OK):
|
||||||
|
executables.append(f)
|
||||||
|
if os.path.isdir(f) and f[0]!='.':
|
||||||
|
dirs.append(f)
|
||||||
|
dirs.sort()
|
||||||
|
for d in dirs:
|
||||||
|
self.dirs.append(launch_item(
|
||||||
|
command=d,
|
||||||
|
description=d,
|
||||||
|
launcher="dir"))
|
||||||
|
if self.options.executables:
|
||||||
|
executables.sort()
|
||||||
|
for e in executables:
|
||||||
|
self.entries.append(launch_item(
|
||||||
|
command=e,
|
||||||
|
description=e,
|
||||||
|
launcher="exec"))
|
||||||
|
|
||||||
|
def entry_color(self,launcher):
|
||||||
|
if launcher=="dir":
|
||||||
|
return self.co.WHI
|
||||||
|
if launcher=="exec":
|
||||||
|
return self.co.GRE
|
||||||
|
if launcher=="menu":
|
||||||
|
return self.co.CYA
|
||||||
|
|
||||||
|
def menu(self):
|
||||||
|
""" draws the menu at the top of the screen """
|
||||||
|
if self.dir_mode:
|
||||||
|
helptext=".:executables"
|
||||||
|
my_entries=self.dirs
|
||||||
|
else:
|
||||||
|
helptext='-:args ('+self.co.END+self.args+self.co.YEL+') .:folders'
|
||||||
|
my_entries=self.entries
|
||||||
|
|
||||||
|
maxrows,maxcolumns = termsize()
|
||||||
|
maxrows-=5
|
||||||
|
maxcolumns-=10
|
||||||
|
if self.options.columns==0:
|
||||||
|
pars=float(1)
|
||||||
|
if len(my_entries)>9:
|
||||||
|
pars=float(2)
|
||||||
|
else:
|
||||||
|
pars=float(self.options.columns)
|
||||||
|
print(self.co.END+self.co.CLR+self.co.pos(1,3)+self.co.YEL+'FolderMenu x:exit '+helptext+self.co.END)
|
||||||
|
maxrows=int(math.ceil(min(maxrows/pars, len(my_entries)/pars)))
|
||||||
|
maxcolumns=int(math.ceil(maxcolumns/pars))
|
||||||
r=1
|
r=1
|
||||||
for e in range(e,len(entries)):
|
par=1
|
||||||
|
for e,i in zip(my_entries,self.menu_keys):
|
||||||
if r>maxrows:
|
if r>maxrows:
|
||||||
break
|
par=1+par
|
||||||
printline=entries[e][1]
|
r=1
|
||||||
|
printline=e.description
|
||||||
if len(printline)>maxcolumns:
|
if len(printline)>maxcolumns:
|
||||||
printline=printline[:maxcolumns]+"..."
|
printline=printline[:maxcolumns]+"..."
|
||||||
print(co.pos(r+1,maxcolumns)+'| '+co.WHI+entries[e][0]+co.END+' '+entries[e][3]+printline+co.END)
|
if par==1:
|
||||||
|
print(self.co.WHI+i+self.co.END+' '+self.entry_color(e.launcher)+printline+self.co.END)
|
||||||
|
else:
|
||||||
|
print(self.co.pos(r+1,maxcolumns*(par-1))+'| '+self.co.WHI+i+self.co.END+' '+self.entry_color(e.launcher)+printline+self.co.END)
|
||||||
r=1+r
|
r=1+r
|
||||||
print(co.pos(maxrows+2,0))
|
print(self.co.pos(maxrows+2,0))
|
||||||
|
|
||||||
def append_index(entries,offset=0,color=None,t='menu'):
|
|
||||||
e=1+offset
|
|
||||||
for el in range(len(entries)):
|
|
||||||
entries[el]=[ichr(e), entries[el][0], entries[el][1], color,t]
|
|
||||||
e=e+1
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def launch(key,entries,args=""):
|
def list(self):
|
||||||
''' launch the given program '''
|
""" draws the list at cursor """
|
||||||
bg=False
|
maxrows,maxcolumns = termsize()
|
||||||
idx=[i for i in range(len(entries)) if entries[i][0]==key][0]
|
maxrows-=5
|
||||||
command_str=entries[idx][2]
|
maxcolumns-=10
|
||||||
if command_str[-1]=='&':
|
# heuristics for guessing column count
|
||||||
command_str=command_str[:-1]
|
if self.options.columns==0:
|
||||||
bg=True
|
pars=float(1)
|
||||||
if len(args)>0:
|
if len(self.entries)>9:
|
||||||
command_str=command_str+" "+args
|
pars=math.floor(maxcolumns/float(self.max_length))
|
||||||
if entries[idx][4]=='exec':
|
pars=max(1,pars)
|
||||||
command_str='./'+command_str
|
if len(self.entries)/pars < pars:
|
||||||
if entries[idx][4]=='dir':
|
while len(self.entries)/pars < pars:
|
||||||
os.chdir(command_str)
|
pars-=1
|
||||||
return
|
|
||||||
try:
|
|
||||||
print('#$ '+command_str)
|
|
||||||
if bg:
|
|
||||||
subprocess.Popen(command_str, stderr=subprocess.PIPE, shell=True,executable="/bin/bash")
|
|
||||||
else:
|
else:
|
||||||
subprocess.call(command_str, stderr=subprocess.STDOUT, shell=True,executable="/bin/bash")
|
pars=float(self.options.columns)
|
||||||
except:
|
maxrows=int(math.ceil(min(maxrows/pars, len(self.entries)/pars)))
|
||||||
print('Unable to run: "'+command_str+'"')
|
maxcolumns=int(math.ceil(maxcolumns/pars))-2
|
||||||
|
# If names won't fit the columns, make sure at least 3 characters are visible
|
||||||
|
if maxcolumns<6:
|
||||||
|
origmaxrows,origmaxcolumns = termsize()
|
||||||
|
origmaxrows-=5
|
||||||
|
origmaxcolumns-=10
|
||||||
|
while maxcolumns<6:
|
||||||
|
pars=pars-1
|
||||||
|
maxrows=int(math.ceil(min(origmaxrows/pars, len(self.entries)/pars)))
|
||||||
|
maxcolumns=int(math.ceil(origmaxcolumns/pars))-2
|
||||||
|
|
||||||
print('Press any key...')
|
self.max_length=min(maxcolumns,self.max_length)
|
||||||
ch=getch()
|
if self.options.horizontal:
|
||||||
inkey=ord(ch.get())
|
foo=pars
|
||||||
|
pars=maxrows
|
||||||
def initialize():
|
maxrows=foo
|
||||||
entries=read_menu()
|
formatted=[]
|
||||||
entries=append_index(entries, color=bc.CYA,t='menu')
|
for r in range(int(maxrows)):
|
||||||
[execs,dirs]=read_folder()
|
formatted.append([])
|
||||||
execs=append_index(execs, offset=len(entries), color=bc.GRE,t='exec')
|
for p in range(int(pars)):
|
||||||
entries.extend(execs)
|
formatted[r].append(' '*(self.max_length))
|
||||||
dirs=append_index(dirs,color=bc.BLU+bc.WHI,t='dir')
|
if self.options.horizontal:
|
||||||
return (entries,dirs)
|
formatted[r][p]+=' '
|
||||||
|
r=0
|
||||||
|
par=0
|
||||||
|
for e,i in zip(self.entries,self.menu_keys):
|
||||||
|
if r>=maxrows:
|
||||||
|
par=1+par
|
||||||
|
r=0
|
||||||
|
printline=e.description[:(maxcolumns-3)]
|
||||||
|
printline=printline+' '*(self.max_length-len(printline)-1)
|
||||||
|
formatted[r][par]=self.co.WHI+i+self.co.END+' '+self.entry_color(e.launcher)+printline+self.co.END
|
||||||
|
r=1+r
|
||||||
|
if self.options.horizontal:
|
||||||
|
# let's shuffle the deck, and print values in horizontal order:
|
||||||
|
formatted=zip(*formatted)
|
||||||
|
|
||||||
|
for row in formatted:
|
||||||
|
print '|'.join(row)
|
||||||
|
|
||||||
def main():
|
def launch(self,key):
|
||||||
[entries,dirs]=initialize()
|
''' launch the given entry '''
|
||||||
show_entries=entries
|
|
||||||
dir_mode=False
|
bg=False
|
||||||
|
#Run the program in background
|
||||||
|
idx=self.menu_keys.index(key)
|
||||||
|
# note, no error checking here
|
||||||
|
if self.dir_mode:
|
||||||
|
command_str=self.dirs[idx].command
|
||||||
|
os.chdir(command_str)
|
||||||
|
return
|
||||||
|
# continue here if not changing folders
|
||||||
|
command_str=self.entries[idx].command
|
||||||
|
|
||||||
|
if command_str[-1]=='&':
|
||||||
|
command_str=command_str[:-1]
|
||||||
|
bg=True
|
||||||
|
if len(self.args)>0:
|
||||||
|
command_str=command_str+" "+self.args
|
||||||
|
if self.entries[idx].launcher=='exec':
|
||||||
|
command_str='./'+command_str
|
||||||
|
|
||||||
|
if not self.options.command:
|
||||||
|
print('#$ '+command_str)
|
||||||
|
try:
|
||||||
|
if bg:
|
||||||
|
subprocess.Popen(command_str, stderr=subprocess.PIPE, shell=True,executable="/bin/bash")
|
||||||
|
else:
|
||||||
|
subprocess.call(command_str, stderr=subprocess.STDOUT, shell=True,executable="/bin/bash")
|
||||||
|
except:
|
||||||
|
print('Unable to run: "'+command_str+'"')
|
||||||
|
|
||||||
|
if not (self.options.command or bg):
|
||||||
|
print('Press any key...')
|
||||||
|
ch=getch()
|
||||||
|
inkey=ord(ch.get())
|
||||||
|
|
||||||
|
def flip_mode(self):
|
||||||
|
self.dir_mode=not self.dir_mode
|
||||||
|
|
||||||
|
def is_key(self,key):
|
||||||
|
if self.dir_mode:
|
||||||
|
my_len=len(self.dirs)
|
||||||
|
else:
|
||||||
|
my_len=len(self.entries)
|
||||||
|
try:
|
||||||
|
idx=self.menu_keys.index(key)
|
||||||
|
except ValueError:
|
||||||
|
return (False,'Not a possible key')
|
||||||
|
if idx+1>my_len:
|
||||||
|
return (False,'No such entry')
|
||||||
|
return (True,'')
|
||||||
|
|
||||||
|
|
||||||
|
def start_engines():
|
||||||
|
options=setup_options()
|
||||||
|
entries=entry_collection(options)
|
||||||
|
if options.list:
|
||||||
|
entries.list()
|
||||||
|
if not options.command:
|
||||||
|
sys.exit(0)
|
||||||
|
if options.command:
|
||||||
|
found,message=entries.is_key(options.command)
|
||||||
|
if not found:
|
||||||
|
print(message)
|
||||||
|
sys.exit(1)
|
||||||
|
entries.set_args(options.args)
|
||||||
|
entries.launch(options.command)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
ch=getch()
|
ch=getch()
|
||||||
args=""
|
|
||||||
drawmenu(show_entries,dir_mode,args)
|
|
||||||
while True:
|
while True:
|
||||||
|
entries.menu()
|
||||||
inkey=ord(ch.get())
|
inkey=ord(ch.get())
|
||||||
#print('-'+str((inkey))+'-')
|
#print('-'+str((inkey))+'-')
|
||||||
if inkey in [120,27,3,24,4]:
|
if inkey in [120,27,3,24,4]:
|
||||||
print_help()
|
|
||||||
print('Exited in: '+os.getcwd())
|
print('Exited in: '+os.getcwd())
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if inkey==45: # -
|
if inkey==45: # -
|
||||||
readline.set_startup_hook(lambda: readline.insert_text(args))
|
readline.set_startup_hook(lambda: readline.insert_text(entries.args))
|
||||||
args=raw_input('args: ')
|
args=raw_input('args: ')
|
||||||
|
entries.set_args(args)
|
||||||
readline.set_startup_hook(None)
|
readline.set_startup_hook(None)
|
||||||
if inkey==46: # .
|
if inkey==46: # .
|
||||||
dir_mode = not dir_mode
|
entries.flip_mode()
|
||||||
|
found,message=entries.is_key(chr(inkey))
|
||||||
if chr(inkey) in [x[0] for x in show_entries]:
|
if found:
|
||||||
launch(chr(inkey),show_entries,args)
|
entries.launch(chr(inkey))
|
||||||
[entries,dirs]=initialize()
|
entries.initialize()
|
||||||
if dir_mode:
|
|
||||||
show_entries=dirs
|
|
||||||
else:
|
|
||||||
show_entries=entries
|
|
||||||
|
|
||||||
drawmenu(show_entries,dir_mode,args)
|
start_engines()
|
||||||
|
|
||||||
main()
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user