do_what.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import sys
  2. import os
  3. import subprocess
  4. import configparser
  5. import magic
  6. import json
  7. import datetime
  8. mime = magic.Magic(mime=True)
  9. # supported values: fish, bash, zsh
  10. if ( 'DO_WHAT_SHELL' in os.environ ):
  11. shell=os.environ['DO_WHAT_SHELL']
  12. else:
  13. print("echo Shell type not set. To set up the environment, add the following line to your shell\\\'s init file:")
  14. print("echo dowhat {shell} \| .")
  15. exit()
  16. # Defaults
  17. print_file="cat"
  18. pretty_print_file="less -R"
  19. edit_file="vim"
  20. list_directory="ls --color=auto"
  21. list_git_directory="ls --color=auto"
  22. change_dir="cd"
  23. print_dir="pwd"
  24. help_command="man"
  25. path_locator="which"
  26. open_file="xdg-open"
  27. # general function for setting a shell's environment variable
  28. def set_runtime_var(name, value, options=""):
  29. if ( shell == "fish" ):
  30. print("set "+name+" "+options+" \""+value+"\"")
  31. elif ( shell == "bash" or shell == "zsh" ):
  32. print("export "+name+"="+"\""+value+"\"")
  33. # checks if a file is a binary file or a plaintext file
  34. def is_binary_file(filepathname):
  35. textchars = bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x7f)) + bytearray(range(0x80, 0x100))
  36. is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))
  37. if is_binary_string(open(filepathname, 'rb').read(1024)):
  38. return True
  39. else:
  40. return False
  41. # check if a given program is in the path environment using which
  42. def env_has(program):
  43. def is_exe(fpath):
  44. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  45. fpath, fname = os.path.split(program)
  46. if fpath:
  47. if is_exe(program):
  48. return True
  49. else:
  50. for path in os.environ["PATH"].split(os.pathsep):
  51. exe_file = os.path.join(path, program)
  52. if is_exe(exe_file):
  53. return True
  54. return None
  55. # get the number of lines in a file
  56. def file_len(fname):
  57. with open(fname) as f:
  58. for i, l in enumerate(f):
  59. pass
  60. try:
  61. i
  62. except NameError:
  63. return 0
  64. else:
  65. return i + 1
  66. # bootstrap the config
  67. ENV_HOME=os.environ['HOME']
  68. subprocess.call(['mkdir', '-p', ENV_HOME+'/.config/do_what'])
  69. if ( os.path.isfile(ENV_HOME+'/.config/do_what/what.config') ):
  70. config = configparser.ConfigParser()
  71. config.read(ENV_HOME+'/.config/do_what/what.config')
  72. # TODO add else default configs
  73. if ( 'DEFAULT' in config ):
  74. if ( 'print_file' in config['DEFAULT'] ):
  75. print_file = config['DEFAULT']['print_file']
  76. if ( 'pretty_print_file' in config['DEFAULT'] ):
  77. pretty_print_file = config['DEFAULT']['pretty_print_file']
  78. if ( 'edit_file' in config['DEFAULT'] ):
  79. edit_file = config['DEFAULT']['edit_file']
  80. if ( 'list_directory' in config['DEFAULT'] ):
  81. list_directory = config['DEFAULT']['list_directory']
  82. if ( 'help_command' in config['DEFAULT'] ):
  83. help_command = config['DEFAULT']['help_command']
  84. if ( 'path_locator' in config['DEFAULT'] ):
  85. path_locator = config['DEFAULT']['path_locator']
  86. if ( 'list_git_directory' in config['DEFAULT'] ):
  87. list_git_directory = config['DEFAULT']['list_git_directory']
  88. if ( 'open_file' in config['DEFAULT'] ):
  89. open_file = config['DEFAULT']['open_file']
  90. else:
  91. config = configparser.ConfigParser()
  92. config['DEFAULT'] = {
  93. 'list_directory': list_directory,
  94. 'edit_file': edit_file,
  95. 'pretty_print_file': pretty_print_file,
  96. 'print_file': print_file,
  97. 'help_command': help_command,
  98. 'path_locator': path_locator,
  99. 'list_git_directory': list_git_directory,
  100. 'open_file': open_file
  101. }
  102. print("echo Default config loaded.")
  103. with open(ENV_HOME+'/.config/do_what/what.config', 'w') as configfile:
  104. config.write(configfile)
  105. configfile.close()
  106. # check if active mode is enabled
  107. argiter = 0
  108. active = False
  109. for arg in sys.argv:
  110. if ( str(arg) == '-' ):
  111. active = True
  112. break
  113. argiter += 1
  114. # absolutize the path for python
  115. if ( (active and len(sys.argv) >= 3) or (not active and len(sys.argv) >= 2) ):
  116. filearg = sys.argv[1]
  117. path = sys.argv[1]
  118. if ( path[0] != "/" and path[0] != "~" and path[0] != "." ):
  119. path = os.getcwd()+"/"+path
  120. elif ( path[0] == "." and len(path) > 1 and ( path[:2] == "./" ) ):
  121. path = os.getcwd()+path[1:]
  122. elif ( path[0] == "." and len(path) > 1 and ( path[:2] == ".." ) ):
  123. path = os.getcwd()+"/"+path
  124. elif ( path[0] == "." and len(path) == 1 ):
  125. path = os.getcwd()
  126. # CLIpboard (ha.)
  127. if ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'c' ):
  128. subprocess.call(['rm', '-rf', ENV_HOME+'/.config/do_what/clipboard'])
  129. subprocess.call(['touch', ENV_HOME+'/.config/do_what/clipboard'])
  130. if ( filearg == '-' ):
  131. path = os.getcwd()
  132. if ( os.path.isdir(path) or os.path.isfile(path) ):
  133. with open(ENV_HOME+"/.config/do_what/clipboard", "w") as cb:
  134. cbdata = { 'action': 'copy', 'path': path }
  135. json.dump(cbdata, cb)
  136. print("echo Copied: "+path)
  137. exit()
  138. else:
  139. print("echo Unknown file/directory.")
  140. exit()
  141. elif ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'x' ):
  142. subprocess.call(['rm', '-rf', ENV_HOME+'/.config/do_what/clipboard'])
  143. subprocess.call(['touch', ENV_HOME+'/.config/do_what/clipboard'])
  144. if ( filearg == '-' ):
  145. path = os.getcwd()
  146. if ( os.path.isdir(path) or os.path.isfile(path) ):
  147. with open(ENV_HOME+"/.config/do_what/clipboard", "w") as cb:
  148. cbdata = { 'action': 'cut', 'path': path }
  149. json.dump(cbdata, cb)
  150. print("echo Cut: "+path)
  151. exit()
  152. else:
  153. print("echo Unknown file/directory.")
  154. exit()
  155. elif ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'p' ):
  156. if ( os.path.isfile(ENV_HOME+"/.config/do_what/clipboard") ):
  157. with open(ENV_HOME+"/.config/do_what/clipboard", "r") as cb:
  158. try:
  159. cbdata = json.load(cb)
  160. except json.decoder.JSONDecodeError:
  161. print("echo Invalid clipboard file.")
  162. exit()
  163. print("echo \""+str(cbdata)+"\"")
  164. if ( cbdata['action'] == 'copy' ):
  165. file_op = 'cp'
  166. elif ( cbdata['action'] == 'cut' ):
  167. file_op = 'mv'
  168. if ( os.path.isdir( cbdata['path'] ) and filearg == '-' ):
  169. print("echo Pasted directory: "+cbdata['path'])
  170. print(file_op+" -r "+cbdata['path']+" .")
  171. elif ( os.path.isdir( cbdata['path'] ) and os.path.isdir( path ) ):
  172. print("echo Pasted directory "+cbdata['path']+" in "+path)
  173. print(file_op+" -r "+cbdata['path']+" "+path)
  174. elif ( os.path.isdir( cbdata['path'] ) and os.path.isfile( path ) ):
  175. print("echo Cannot paste directory into regular file.")
  176. print("echo \"("+cbdata['path']+" -> "+path+")\"")
  177. elif ( os.path.isfile( cbdata['path'] ) and filearg == '-' ):
  178. print("echo Pasted file: "+cbdata['path'])
  179. print(file_op+" "+cbdata['path']+" .")
  180. elif ( os.path.isfile( cbdata['path'] ) and os.path.isdir( path ) ):
  181. print("echo Pasted file "+cbdata['path']+" in directory "+path)
  182. print(file_op+" "+cbdata['path']+" "+path)
  183. elif ( os.path.isfile( cbdata['path'] ) and os.path.isfile( path ) ):
  184. print("echo Pasted file "+cbdata['path']+" over existing file "+path)
  185. print(file_op+" "+cbdata['path']+" "+path)
  186. elif ( os.path.isfile( cbdata['path'] ) and filearg != '-' ):
  187. print("echo Pasted file "+cbdata['path']+" as new file "+path)
  188. print(file_op+" "+cbdata['path']+" "+path)
  189. elif ( os.path.isdir( cbdata['path'] ) and filearg != '-' ):
  190. print("echo Pasted directory "+cbdata['path']+" as new directory "+path)
  191. print(file_op+" "+cbdata['path']+" "+path)
  192. else:
  193. print("echo Invalid clipboard. Use \\\'c\\\' directive to copy a file or directory.")
  194. else:
  195. print("echo Empty clipboard. Use \\\'c\\\' directive to copy a file or directory.")
  196. exit()
  197. # File/Directory backup creator
  198. # Capital B = Time versioned backup
  199. elif ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'B' ):
  200. if ( filearg == '-' ):
  201. print("No file/directory specified to backup.")
  202. elif ( os.path.isfile( path ) ):
  203. bakname = ".bak-"+datetime.datetime.now().strftime("%Y-%B-%d_%I:%M%p")
  204. print("cp "+path+" "+path+bakname)
  205. print("echo Created file backup: "+path+bakname)
  206. elif( os.path.isdir( path ) ):
  207. bakname = ".bak-"+datetime.datetime.now().strftime("%Y-%B-%d_%I:%M%p")
  208. print("cp -r "+path+" "+path+bakname)
  209. print("echo Created directory backup: "+path+bakname)
  210. exit()
  211. # Lowercase b = un-versioned backup
  212. elif ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'b' ):
  213. if ( filearg == '-' ):
  214. print("No file/directory specified to backup.")
  215. elif ( os.path.isfile( path ) ):
  216. bakname = ".bak"
  217. print("cp "+path+" "+path+bakname)
  218. print("echo Created file backup: "+path+bakname)
  219. elif( os.path.isdir( path ) ):
  220. bakname = ".bak"
  221. print("cp -r "+path+" "+path+bakname)
  222. print("echo Created directory backup: "+path+bakname)
  223. exit()
  224. # Lowercase r = restore the specified backup
  225. elif ( active and len(sys.argv) > argiter+1 and sys.argv[argiter+1] == 'r' ):
  226. if ( filearg == '-' ):
  227. print("No file/directory specified to restore.")
  228. elif ( os.path.isfile( path ) ):
  229. if ( ".bak" not in path ):
  230. print("echo "+path+" is not a valid backup file.")
  231. else:
  232. restorepath = path.split(".bak", 1)[0]
  233. print("mv "+path+" "+restorepath)
  234. print("echo Restored file: "+restorepath)
  235. elif ( os.path.isdir( path ) ):
  236. if ( ".bak" not in path ):
  237. print("echo "+path+" is not a valid backup directory.")
  238. else:
  239. restorepath = path.split(".bak", 1)[0]
  240. print("mv "+path+" "+restorepath)
  241. print("echo Restored directory: "+restorepath)
  242. exit()
  243. elif ( active and len(sys.argv) > argiter+1 ):
  244. print("echo Invalid directive: "+sys.argv[argiter+1])
  245. exit()
  246. # File Operations
  247. if ( (not active and len(sys.argv) == 1) or (active and len(sys.argv) == 2) ):
  248. # list the contents of the current directory
  249. if ( not active ):
  250. if ( os.path.isdir('.git') ):
  251. print(list_git_directory)
  252. else:
  253. print(list_directory)
  254. # print the working directory (active)
  255. elif ( active ):
  256. print(print_dir)
  257. elif ( (not active and len(sys.argv) == 2) or (active and len(sys.argv) == 3) ):
  258. # if directory, list its contents
  259. if ( os.path.isdir(path) and not active ):
  260. if ( os.path.isdir(path+"/.git") ):
  261. print(list_git_directory+" "+path)
  262. else:
  263. print(list_directory+" "+path)
  264. # if directory, change into it (active)
  265. elif ( os.path.isdir(path) and active ):
  266. print(change_dir+" "+path)
  267. # if file, display its contents
  268. elif ( os.path.isfile(path) and not active ):
  269. # check if file is binary
  270. if ( is_binary_file(path) ):
  271. filemime = mime.from_file(path)
  272. print("echo Binary file: "+filemime)
  273. if ( filemime == "inode/symlink" ):
  274. print("echo Symlink location: "+os.path.realpath(path))
  275. else:
  276. trows, tcolumns = os.popen('stty size', 'r').read().split()
  277. # if file is taller than the terminal, pipe it to a pretty-print display
  278. if ( int(trows) - 5 < int(file_len(path)) ):
  279. set_runtime_var('LESSOPEN', '| /usr/share/source-highlight/src-hilite-lesspipe.sh %s')
  280. set_runtime_var('LESS', ' -R ')
  281. print(pretty_print_file+" "+path)
  282. # if file is shorter than the terminal, print its contents
  283. else:
  284. print(print_file+" "+path)
  285. # if file, open in editor (active)
  286. elif ( os.path.isfile(path) and active ):
  287. if ( is_binary_file(path) ):
  288. print(open_file+" "+path)
  289. else:
  290. if ( os.access(path, os.W_OK) ):
  291. print(edit_file+" "+path)
  292. else:
  293. print("sudo "+edit_file+" "+path)
  294. else:
  295. # check if file is a path program, then list using which/man on active
  296. path_has = env_has(filearg)
  297. # print the location of the program
  298. if ( path_has and not active ):
  299. print(path_locator+" "+filearg)
  300. # display the help page for the program (active)
  301. elif ( path_has and active ):
  302. print(help_command+" "+filearg)
  303. else:
  304. print("echo Unknown file/directory.")