| 
									
										
										
										
											2010-12-02 16:46:23 +00:00
										 |  |  | #!/usr/bin/env python | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  | """Copyright Joel Schaerer 2008-2010 | 
					
						
							|  |  |  | 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/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Autojump is a small tool that maintains a database of your most | 
					
						
							|  |  |  | used directories, and finds the best match to help you jump to | 
					
						
							|  |  |  | frequently used places.""" | 
					
						
							| 
									
										
										
										
											2009-05-13 09:32:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-24 09:30:01 +00:00
										 |  |  | from __future__ import division, print_function | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | try: # fix to get optimised pickle in python < 3 | 
					
						
							|  |  |  |     import cPickle as pickle | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     import pickle | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-21 09:43:07 +00:00
										 |  |  | import getopt | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | from sys import argv, stderr, version_info | 
					
						
							| 
									
										
										
										
											2010-09-29 13:22:13 +00:00
										 |  |  | from tempfile import NamedTemporaryFile | 
					
						
							| 
									
										
										
										
											2010-11-18 15:36:38 +00:00
										 |  |  | from operator import itemgetter | 
					
						
							| 
									
										
										
										
											2008-04-21 09:43:07 +00:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2011-01-03 22:23:26 +00:00
										 |  |  | MAX_KEYWEIGHT = 1000 | 
					
						
							|  |  |  | MAX_STORED_PATHS = 600 | 
					
						
							|  |  |  | COMPLETION_SEPARATOR = '__' | 
					
						
							|  |  |  | CONFIG_DIR = os.environ.get("AUTOJUMP_DATA_DIR", os.path.expanduser("~")) | 
					
						
							| 
									
										
										
										
											2008-06-02 15:43:38 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-03 22:23:26 +00:00
										 |  |  | def uniqadd(collection, key): | 
					
						
							|  |  |  |     """Adds a key to a list only if it is not already present""" | 
					
						
							|  |  |  |     if key not in collection: | 
					
						
							|  |  |  |         collection.append(key) | 
					
						
							| 
									
										
										
										
											2009-02-13 22:22:32 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def dicadd(dic, key, increment=1): | 
					
						
							| 
									
										
										
										
											2011-01-03 22:23:26 +00:00
										 |  |  |     """Increment a value in a dic, set it to 0 | 
					
						
							|  |  |  |     if is is not already present""" | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     dic[key] = dic.get(key, 0.)+increment | 
					
						
							| 
									
										
										
										
											2008-04-21 09:43:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def save(path_dict, dic_file): | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     """Save the database in an atomic way, and preserve | 
					
						
							|  |  |  |        a backup file.""" | 
					
						
							|  |  |  |     temp = NamedTemporaryFile(dir=CONFIG_DIR, delete=False) | 
					
						
							|  |  |  |     pickle.dump(path_dict, temp, -1) | 
					
						
							|  |  |  |     temp.flush() | 
					
						
							|  |  |  |     os.fsync(temp) | 
					
						
							|  |  |  |     temp.close() | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     #cf. http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/ | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     os.rename(temp.name, dic_file)  | 
					
						
							| 
									
										
										
										
											2010-09-29 13:22:13 +00:00
										 |  |  |     try: #backup file | 
					
						
							|  |  |  |         import time  | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |         if (not os.path.exists(dic_file+".bak") or | 
					
						
							|  |  |  |                 time.time()-os.path.getmtime(dic_file+".bak")>86400): | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |             import shutil | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |             shutil.copy(dic_file, dic_file+".bak") | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     except OSError as ex: | 
					
						
							|  |  |  |         print("Error while creating backup autojump file. (%s)" % | 
					
						
							|  |  |  |                 ex, file=stderr) | 
					
						
							| 
									
										
										
										
											2009-02-18 14:13:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | 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())  | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     if keyweight > MAX_KEYWEIGHT:  | 
					
						
							| 
									
										
										
										
											2009-02-18 13:12:13 +00:00
										 |  |  |         for k in path_dict.keys(): | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |             path_dict[k] *= 0.9 * MAX_KEYWEIGHT / keyweight | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |         save(path_dict, dic_file) | 
					
						
							| 
									
										
										
										
											2009-02-18 13:12:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def clean_dict(sorted_dirs, path_dict): | 
					
						
							| 
									
										
										
										
											2011-01-03 22:23:26 +00:00
										 |  |  |     """Limits the sized of the path_dict to MAX_STORED_PATHS.  | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     Returns True if keys were deleted""" | 
					
						
							| 
									
										
										
										
											2011-01-03 22:23:26 +00:00
										 |  |  |     if len(sorted_dirs) > MAX_STORED_PATHS: | 
					
						
							| 
									
										
										
										
											2010-07-21 14:48:35 +00:00
										 |  |  |         #remove 25 more than needed, to avoid doing it every time | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         for path, dummy in sorted_dirs[MAX_STORED_PATHS-25:]: | 
					
						
							|  |  |  |             del path_dict[path] | 
					
						
							| 
									
										
										
										
											2010-07-21 14:44:43 +00:00
										 |  |  |         return True | 
					
						
							|  |  |  |     else: return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def match(path, pattern, ignore_case=False, only_end=False): | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     """Check whether a path matches a particular pattern""" | 
					
						
							| 
									
										
										
										
											2010-07-21 14:44:43 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         if os.path.realpath(os.curdir) == path : | 
					
						
							|  |  |  |             return False | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     #Sometimes the current path doesn't exist anymore. | 
					
						
							|  |  |  |     #In that case, jump if possible. | 
					
						
							|  |  |  |     except OSError: | 
					
						
							| 
									
										
										
										
											2010-07-21 14:44:43 +00:00
										 |  |  |         pass | 
					
						
							|  |  |  |     if only_end: | 
					
						
							|  |  |  |         match_string = "/".join(path.split('/')[-1-pattern.count('/'):]) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         match_string = path | 
					
						
							|  |  |  |     if ignore_case: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         does_match = (match_string.lower().find(pattern.lower()) != -1) | 
					
						
							| 
									
										
										
										
											2010-07-21 14:44:43 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         does_match = (match_string.find(pattern) != -1) | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     #return True if there is a match and the path exists  | 
					
						
							|  |  |  |     #(useful in the case of external drives, for example) | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     return does_match and os.path.exists(path)  | 
					
						
							| 
									
										
										
										
											2010-07-21 14:44:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def find_matches(dirs, patterns, result_list, ignore_case, max_matches): | 
					
						
							|  |  |  |     """Find max_matches paths that match the pattern,  | 
					
						
							|  |  |  |     and add them to the result_list""" | 
					
						
							|  |  |  |     for path, count in dirs: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         if len(result_list) >= max_matches : | 
					
						
							|  |  |  |             break | 
					
						
							| 
									
										
										
										
											2010-04-25 23:00:57 +00:00
										 |  |  |         #For the last pattern, only match the end of the pattern | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |         if all(match(path, p, ignore_case, | 
					
						
							|  |  |  |             only_end=(n == len(patterns)-1)) for n, p in enumerate(patterns)): | 
					
						
							|  |  |  |             uniqadd(result_list, path) | 
					
						
							| 
									
										
										
										
											2009-02-18 13:12:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  | def open_dic(dic_file, error_recovery=False): | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |     """Try hard to open the database file, recovering | 
					
						
							|  |  |  |        from backup if needed. """ | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |         aj_file = open(dic_file, 'rb') | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         if version_info[0] > 2: | 
					
						
							| 
									
										
										
										
											2010-10-24 09:42:45 +00:00
										 |  |  |             #encoding is only specified for python2.x compatibility | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |             path_dict = pickle.load(aj_file, encoding="utf-8") | 
					
						
							| 
									
										
										
										
											2010-10-24 09:42:45 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |             path_dict = pickle.load(aj_file) | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |         aj_file.close() | 
					
						
							|  |  |  |         return path_dict | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |     except (IOError, EOFError, pickle.UnpicklingError): | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |         if not error_recovery and os.path.exists(dic_file+".bak"): | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |             print('Problem with autojump database,\ | 
					
						
							|  |  |  |                     trying to recover from backup...', file=stderr) | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |             import shutil | 
					
						
							| 
									
										
										
										
											2010-12-23 10:05:20 +00:00
										 |  |  |             shutil.copy(dic_file+".bak", dic_file) | 
					
						
							|  |  |  |             return open_dic(dic_file, True) | 
					
						
							| 
									
										
										
										
											2009-09-16 14:06:20 +00:00
										 |  |  |         else: return {} #if everything fails, return an empty file | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  | def shell_utility(): | 
					
						
							|  |  |  |     """Run this when autojump is called as a shell utility""" | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         optlist, args = getopt.getopt(argv[1:], 'a', | 
					
						
							|  |  |  |                 ['stat', 'import', 'completion', 'bash'])  | 
					
						
							|  |  |  |     except getopt.GetoptError as ex: | 
					
						
							|  |  |  |         print("Unknown command line argument: %s" % ex) | 
					
						
							|  |  |  |         exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if CONFIG_DIR == os.path.expanduser("~"): | 
					
						
							|  |  |  |         dic_file = CONFIG_DIR+"/.autojump_py" | 
					
						
							| 
									
										
										
										
											2009-02-18 13:12:13 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         dic_file = CONFIG_DIR+"/autojump_py" | 
					
						
							|  |  |  |     path_dict = open_dic(dic_file) | 
					
						
							|  |  |  |     if ('-a', '') in optlist: | 
					
						
							|  |  |  |         # The home dir can be reached quickly by "cd" | 
					
						
							|  |  |  |         # and may interfere with other directories | 
					
						
							|  |  |  |         if(args[-1] != os.path.expanduser("~")):  | 
					
						
							|  |  |  |             dicadd(path_dict, args[-1]) | 
					
						
							|  |  |  |             save(path_dict, dic_file) | 
					
						
							|  |  |  |     elif ('--stat', '') in optlist: | 
					
						
							|  |  |  |         paths = list(path_dict.items()) | 
					
						
							|  |  |  |         paths.sort(key=itemgetter(1)) | 
					
						
							|  |  |  |         for path, count in paths[-100:]: | 
					
						
							|  |  |  |             print("%.1f:\t%s" % (count, path)) | 
					
						
							|  |  |  |         print("Total key weight: %d. Number of stored paths: %d" % | 
					
						
							|  |  |  |                 (sum(path_dict.values()), len(paths))) | 
					
						
							|  |  |  |     elif ('--import', '') in optlist: | 
					
						
							|  |  |  |         for i in open(args[-1]).readlines(): | 
					
						
							|  |  |  |             dicadd(path_dict, i[:-1]) | 
					
						
							|  |  |  |         pickle.dump(path_dict, open(dic_file, 'w'), -1) | 
					
						
							| 
									
										
										
										
											2009-02-18 13:12:13 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |         import re | 
					
						
							|  |  |  |         completion = False | 
					
						
							|  |  |  |         #userchoice is i if the pattern is __pattern__i, otherwise -1 | 
					
						
							|  |  |  |         userchoice = -1  | 
					
						
							|  |  |  |         results = [] | 
					
						
							|  |  |  |         if ('--completion', '') in optlist: | 
					
						
							|  |  |  |             completion = True | 
					
						
							| 
									
										
										
										
											2010-07-20 12:44:45 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2011-01-04 20:00:59 +00:00
										 |  |  |             forget(path_dict, dic_file) #gradually forget about old directories | 
					
						
							|  |  |  |         if not args: patterns = [""] | 
					
						
							|  |  |  |         else: patterns = args | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If the last pattern contains a full path, jump there | 
					
						
							|  |  |  |         # The regexp is because we need to support stuff like | 
					
						
							|  |  |  |         # "j wo jo__3__/home/joel/workspace/joel" for zsh | 
					
						
							|  |  |  |         last_pattern_path = re.sub("(.*)"+COMPLETION_SEPARATOR, "", patterns[-1]) | 
					
						
							|  |  |  |         if (len(last_pattern_path)>0 and | 
					
						
							|  |  |  |               last_pattern_path[0] == "/" and | 
					
						
							|  |  |  |               os.path.exists(last_pattern_path)): | 
					
						
							|  |  |  |             if not completion: print(last_pattern_path) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             #check for ongoing completion, and act accordingly | 
					
						
							|  |  |  |             endmatch = re.search(COMPLETION_SEPARATOR+"([0-9]+)", patterns[-1]) | 
					
						
							|  |  |  |             if endmatch:  #user has selected a completion | 
					
						
							|  |  |  |                 userchoice = int(endmatch.group(1)) | 
					
						
							|  |  |  |                 patterns[-1] = re.sub(COMPLETION_SEPARATOR+"[0-9]+.*", | 
					
						
							|  |  |  |                         "", patterns[-1]) | 
					
						
							|  |  |  |             else: #user hasn't selected a completion, display the same choices again | 
					
						
							|  |  |  |                 endmatch = re.match("(.*)"+COMPLETION_SEPARATOR, patterns[-1]) | 
					
						
							|  |  |  |                 if endmatch: patterns[-1] = endmatch.group(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             dirs = list(path_dict.items()) | 
					
						
							|  |  |  |             dirs.sort(key=itemgetter(1), reverse=True) | 
					
						
							|  |  |  |             if completion or userchoice != -1: | 
					
						
							|  |  |  |                 max_matches = 9 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 max_matches = 1 | 
					
						
							|  |  |  |             find_matches(dirs, patterns, results, False, max_matches) | 
					
						
							|  |  |  |             # If not found, try ignoring case. | 
					
						
							|  |  |  |             # On completion always show all results | 
					
						
							|  |  |  |             if completion or not results:  | 
					
						
							|  |  |  |                 find_matches(dirs, patterns, results, | 
					
						
							|  |  |  |                         ignore_case=True, max_matches=max_matches)  | 
					
						
							|  |  |  |             # Keep the database to a reasonable size | 
					
						
							|  |  |  |             if not completion and clean_dict(dirs, path_dict): | 
					
						
							|  |  |  |                 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%s%d%s%s" % (patterns[-1], | 
					
						
							|  |  |  |                     COMPLETION_SEPARATOR, n+1, COMPLETION_SEPARATOR, r) | 
					
						
							|  |  |  |                     for n, r in enumerate(results[:8])))) | 
					
						
							|  |  |  |             elif results: print(quotes+results[0]+quotes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     shell_utility() |