#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright Grégory Soutadé 2012 # This file is part of autojump2 # autojump2 is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # autojump2 is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with autojump2. If not, see . # Original code has been written by Joel Schaerer (https://github.com/joelthelion/autojump) from sys import argv, exit, stderr, stdout import getopt import os from optparse import OptionParser import shutil import re COMPLETION_SEPARATOR = '__' CONFIG_DIR = os.environ.get("AUTOJUMP2_DATA_DIR", os.path.expanduser("~")) DATABASE_NAME = '.autojump2.dict' def _walk_path(path): res = [] try: for p in os.listdir(path): p = path + p if not os.path.islink(p) and not os.path.isdir(p): continue res.append(p) except OSError: print >> stderr, "Error listing %s" % path return res def walk_path(path): res = [] try: pos = path.index('*') sub_path = path[:pos] for p in _walk_path(sub_path): if '*' in path[pos+1:]: for p2 in walk_path(p + path[pos+1:]): res.append(p2) else: res.append(p + path[pos+1:]) except ValueError: pass return res def add_path(l, h, path): path = path.replace('\\*', '*') if path in l: return False tmp = [] l.append(path) l.sort() # Generic paths must be after normal path for p in l: if not '*' in p: tmp.append(p) for p in l: if '*' in p: tmp.append(p) del l[:] for p in tmp: l.append(p) if '*' in path: paths = walk_path(p) if len(paths) > 0: h[path] = paths return True def remove_path(l, h, path): path = path.replace('\\*', '*') if not path in l: return False else: l.remove(path) if path in h: del h[path] return True def modify_path(l, h, src, dest): src = src.replace('\\*', '*') dest = dest.replace('\\*', '*') if not src in l: return False if not remove_path(l, h, src) or not add_path(l, h, dest): return False return True def save_database(l, h): path = CONFIG_DIR + os.sep + DATABASE_NAME bak = path + '.bak' if os.path.exists(bak): try: os.remove(bak) except OSError as e: pass try: if os.path.exists(path): shutil.copy(path, bak) except OSError as e: print >> stderr, "Error while creating backup of autojump2 dic @ \'%s\'. (%s)" % (path.bak, e) raise try: if os.path.exists(path): os.remove(path) except OSError as e: print >> stderr, "Error can't remove autojump2 database @ \'%s\'. (%s)" % (path, e) raise f = open(path, 'w') try: for p in l: f.write(p + '\n') if p in h: for p2 in h[p]: f.write('>>> ' + p2 + '\n') f.flush() f.close() except OSError as e: print >> stderr, "Error write autojump2 database @ \'%s\'. (%s)" % (path, e) shutil.copy(bak, path) return True def open_database(): l = [] h = {} path = CONFIG_DIR + os.sep + DATABASE_NAME prev_line = '' if not os.path.exists(path): return l, h f = open(path) for line in f: if line.startswith('>>> ') and prev_line != '': if not prev_line in h: h[prev_line] = [] h[prev_line].append(line[4:][:-1]) # Remove '>>> ' and '\n' else: l.append(line[:-1]) # Remove last '\n' prev_line = line[:-1] f.close() return l, h def list_database(l, h): if not len(l): print >> stderr, 'Any path saved' return print >> stderr, 'Saved paths :' for p in l: print >> stderr, '\t' + p if p in h: sublist = h[p] sublist.sort() for p2 in sublist: print >> stderr, '\t>>> ' + p2 return True def path_matching(l, h, args, pos = -1): res = [] exprs = [] if len(args) == 0: return res # If args are : 'proj' and 'v2' # exp1 is /a/b/c/projXXXv2 # exp2 is /a/b/proj/v2 exp1 = '^.*' + os.sep for a in args: exp1 += '[^' + os.sep + ']*' + a + '[^' + os.sep + ']*' # exp1 += '$' e1 = re.compile(exp1) exprs.append(e1) exp2 = '' if len(args) > 1: exp2 = '^.*' + os.sep for a in args[:-1]: exp2 += '[^' + os.sep + ']*' + a + '.*' + os.sep exp2 += '[^' + os.sep + ']*' + args[-1] + '[^' + os.sep + ']*' # exp2 += '[^' + os.sep + ']*' + args[-1] + '[^' + os.sep + ']*$' e2 = re.compile(exp2) exprs.append(e2) for path in l: if '*' in path: try: sublist = h[path] sublist.sort() for p in sublist: for e in exprs: if re.match(e, p): if pos != -1: pos -= 1 if pos == 0 and not p in res: res.append(p) elif not p in res: res.append(p) break except KeyError as e: print >> stderr, "Error database may be corrupted, invalid key (%s)" % e else: for e in exprs: if re.match(e, path): if pos != -1: pos -= 1 if pos == 0 and not path in res: res.append(path) elif not path in res: res.append(path) break # print >> stderr, 'Exprs : \n%s\n%s' % (exp1, exp2) # print >> stderr, 'Matching paths :' # for path in res: # print >> stderr, path return res #################################### Main code #################################### if __name__ == '__main__': usage = '%prog [options]\n'\ 'Navigate throw your filesystems with recorded links' optparser = OptionParser(usage=usage) optparser.add_option('-a', '--add', dest='add', help='Add a new path to the database', metavar="path") optparser.add_option('-r', '--remove', dest='remove', help='Remove a path from the database', metavar="path") optparser.add_option('-m', '--modify', dest='modify', nargs=2, help='Modify key weight', metavar="path_src path_dest") optparser.add_option('-u', '--update', dest='update', help='Update path with *', metavar="path") optparser.add_option('-l', '--list', dest='list', action="store_true", help='List database') optparser.add_option('-c', '--completion', dest='completion', action="store_true", help='Use autojump\'s completion') optparser.add_option('-b', '--bash', dest='bash', action="store_true", help='Current shell is bash') (optlist, args) = optparser.parse_args(argv[1:]) l, h = open_database() if optlist.add: if optlist.completion: exit(1) path = os.path.abspath(optlist.add) if os.path.isfile(path): print >> stderr, "Error, cannot add a file (%s) in database, directory needed" % path elif add_path(l, h, path): save_database(l, h) print >> stderr, '>>> \'%s\' correctly added to database' % (path) else: print >> stderr, 'Error \'%s\' already exists in database' % (path) elif optlist.remove: if optlist.completion: exit(1) if remove_path(l, h, os.path.abspath(optlist.remove)): save_database(l, h) print >> stderr, '>>> \'%s\' correctly removed from database' % (optlist.remove) else: print >> stderr, 'Error \'%s\' doesn\'t exists in database' % (optlist.remove) elif optlist.modify: if optlist.completion: exit(1) if os.path.isfile(os.path.abspath(optlist.modify[1])): print >> stderr, "Error, cannot add a file in database, directory needed" elif modify_path(l, h, optlist.modify[0], os.path.abspath(optlist.modify[1])): save_database(l, h) print >> stderr, '>>> \'%s\' is now \'%s\'' % (optlist.modify[0], os.path.abspath(optlist.modify[1])) else: print >> stderr, 'Error \'%s\' doesn\'t exists in database' % (optlist.modify[0]) elif optlist.update: if optlist.completion: exit(1) if not modify_path(l, h, optlist.update, optlist.update): print >> stderr, 'Error updating ' + optlist.update else: save_database(l, h) print >> stderr, '>>> Database updated' elif optlist.list: if optlist.completion: exit(1) list_database(l, h) else: # Do the hard work if optlist.bash: quotes = '"' else: quotes = "" if optlist.completion: m = re.search('^.*(' + COMPLETION_SEPARATOR + '.*)$', args[-1]) if m: # Remove '__' args[-1] = args[-1][:-len(m.group(1))] matches = path_matching(l, h, args) if len(matches) > 1: print("\n" . join(("%s%s%d%s%s" % (args[-1], COMPLETION_SEPARATOR, n+1, COMPLETION_SEPARATOR, r)\ for n,r in enumerate(matches)))) elif len(matches) == 1: print quotes + matches[0] + quotes else: m = re.search('^.*' + COMPLETION_SEPARATOR + '([0-9]+)$', args[-1]) if m: args[-1] = args[-1][:-len(COMPLETION_SEPARATOR)-len(m.group(1))] matches = path_matching(l, h, args, int(m.group(1))) else: matches = path_matching(l, h, args) if len(matches) > 0: print quotes + matches[0] + quotes exit(0) exit(1)