public
Description: A cd command that learns - easily navigate directories from the command line
Homepage:
Clone URL: git://github.com/joelthelion/autojump.git
autojump / autojump
100755 159 lines (142 sloc) 6.254 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/python
#Copyright Joel Schaerer 2008, 2009
#This file is part of autojump
 
#autojump 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.
#
#autojump 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 autojump. If not, see <http://www.gnu.org/licenses/>.
 
from __future__ import division
import cPickle
import getopt
from sys import argv,exit,stderr
import os
import signal
max_keyweight=1000
dead_dirs=False #global variable (evil ;-) to know if we should save the dict at the end
 
def signal_handler(arg1,arg2):
    print "Received SIGINT, trying to continue"
signal.signal(signal.SIGINT,signal_handler) #Don't break on sigint
 
def uniqadd(list,key):
    if key not in list:
        list.append(key)
 
def dicadd(dic,key,increment=1):
    dic[key]=dic.get(key,0.)+increment
 
def match(path,pattern,path_dict,re_flags=0):
    import re
    if os.path.realpath(os.curdir)==path : return False
    if re.search(pattern,"/".join(path.split('/')[-1-pattern.count('/'):]),re_flags) is None:
        return False
    else:
        if os.path.exists(path) : return True
        else: #clean up dead directories
            del path_dict[path]
            global dead_dirs
            dead_dirs=True
            return False
 
def save(path_dict,dic_file):
    f=open(dic_file+".tmp",'w')
    cPickle.dump(path_dict,f,-1)
    f.flush()
    os.fsync(f)
    f.close()
    try:
        os.rename(dic_file+".tmp",dic_file) #cf. http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/
        import time #backup file
        if not os.path.exists(dic_file+".bak") or time.time()-os.path.getmtime(dic_file+".bak")>86400:
            import shutil
            shutil.copy(dic_file,dic_file+".bak")
    except OSError:
        pass #Fail quietly, this usually means a concurrent autojump process already did the job
 
def forget(path_dict,dic_file):
    """Gradually forget about directories. Only call from the actual jump since it can take time"""
    keyweight=sum(path_dict.values()) #Gradually forget about old directories
    if keyweight>max_keyweight:
        for k in path_dict.keys():
            path_dict[k]*=0.9*max_keyweight/keyweight
        save(path_dict,dic_file)
 
def find_matches(dirs,pattern,path_dict,result_list,re_flags,max_matches):
    """Find max_matches paths that match the pattern, and add them to the result_list"""
    for path,count in dirs:
        if len(result_list) >= max_matches : break
        if match(path,pattern,path_dict,re_flags):
            uniqadd(result_list,path)
 
def open_dic(dic_file,error_recovery=False):
    try:
        aj_file=open(dic_file)
        path_dict=cPickle.load(aj_file)
        aj_file.close()
        return path_dict
    except (IOError,EOFError,cPickle.UnpicklingError):
        if not error_recovery and os.path.exists(dic_file+".bak"):
            print >> stderr, 'Problem with autojump database, trying to recover from backup...'
            import shutil
            shutil.copy(dic_file+".bak",dic_file)
            return open_dic(dic_file,True)
        else: return {} #if everything fails, return an empty file
 
#Main code
try:
    optlist, args = getopt.getopt(argv[1:], 'a',['stat','import','completion', 'bash'])
except getopt.GetoptError, e:
    print "Unknown command line argument: %s" % e
    exit(1)
 
dic_file=os.path.expanduser("~/.autojump_py")
path_dict=open_dic(dic_file)
if ('-a','') in optlist:
    if(args[-1] != os.path.expanduser("~")): # home dir can be reached quickly by "cd" and may interfere with other directory
        dicadd(path_dict,args[-1])
        save(path_dict,dic_file)
elif ('--stat','') in optlist:
    a=path_dict.items()
    a.sort(key=lambda e:e[1])
    for path,count in a[-100:]:
        print "%.1f:\t%s" % (count,path)
    print "Total key weight: %d" % sum(path_dict.values())
elif ('--import','') in optlist:
    for i in open(args[-1]).readlines():
        dicadd(path_dict,i[:-1])
    cPickle.dump(path_dict,open(dic_file,'w'),-1)
else:
    import re
    completion=False
    userchoice=-1 #3 if the pattern is of the form __pattern__3, otherwise -1
    results=[]
    if ('--completion','') in optlist:
        completion=True
    else:
        forget(path_dict,dic_file) #gradually forget about old directories
    if not args: pattern=""
    else: pattern=args[-1]
 
    if len(pattern)>0 and pattern[0]=="/" and os.path.exists(pattern): #if pattern is a full path, jump there
        if not completion : print pattern
    else:
        endmatch=re.search("__([0-9]+)",pattern)
        if endmatch:
            userchoice=int(endmatch.group(1))
            pattern=re.sub("__[0-9]+.*","",pattern)
        else:
            endmatch=re.match("(.*)__",pattern)
            if endmatch: pattern=endmatch.group(1)
 
        dirs=path_dict.items()
        dirs.sort(key=lambda e:e[1],reverse=True)
        find_matches(dirs,pattern,path_dict,results,re_flags=0,max_matches=9)
        dirs=path_dict.items() #we need to recreate the list since the first iteration potentially deletes paths
        dirs.sort(key=lambda e:e[1],reverse=True)
        if completion or not results: #if not found, try ignoring case. On completion always show all results
            find_matches(dirs,pattern,path_dict,results,re_flags=re.IGNORECASE,max_matches=9)
        if dead_dirs and not completion: #save the dict if there were some non-existent directories in the database
            save(path_dict,dic_file)
 
        if completion and ('--bash', '') in optlist: quotes='"'
        else: quotes=""
 
        if userchoice!=-1:
            if len(results) > userchoice-1 : print quotes+results[userchoice-1]+quotes
        elif len(results) > 1 and completion:
            print "\n".join(("%s__%d__%s" % (pattern,n+1,r) for n,r in enumerate(results[:8])))
        elif results: print quotes+results[0]+quotes