1
0
mirror of https://github.com/wting/autojump synced 2024-10-27 20:34:07 +00:00
wting_autojump/bin/autojump

283 lines
8.5 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 -*-
2012-05-07 06:19:19 +00:00
"""
Copyright © 2008-2012 Joel Schaerer
2013-07-07 02:17:54 +00:00
Copyright © 2012-2013 William Ting
2012-05-07 06:19:19 +00:00
* This program 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, or (at your option)
any later version.
* This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
from __future__ import division, print_function
2013-12-17 02:28:54 +00:00
from collections import namedtuple
from functools import partial
from itertools import ifilter
from itertools import imap
from math import sqrt
from operator import attrgetter
from operator import itemgetter
import os
2013-12-17 02:28:54 +00:00
import platform
import re
import sys
2013-12-17 02:28:54 +00:00
from argparse import ArgumentParser
2013-12-17 02:28:54 +00:00
from data import load
from data import save
from utils import decode
from utils import encode_local
from utils import first
2013-12-17 16:35:43 +00:00
from utils import has_uppercase
2013-12-17 02:28:54 +00:00
from utils import is_osx
from utils import print_entry
from utils import second
2013-12-17 02:28:54 +00:00
VERSION = 'release-v21.8.0'
Entry = namedtuple('Entry', ['path', 'weight'])
2013-07-07 01:23:34 +00:00
def set_defaults():
config = {}
2013-12-17 02:28:54 +00:00
config['tab_menu_separator'] = '__'
2013-12-17 02:28:54 +00:00
if is_osx():
data_home = os.path.join(
os.path.expanduser('~'),
'Library',
'autojump')
else:
data_home = os.getenv(
'XDG_DATA_HOME',
os.path.join(
os.path.expanduser('~'),
'.local',
'share',
'autojump'))
config['data_path'] = os.path.join(data_home, 'autojump.txt')
config['backup_path'] = os.path.join(data_home, 'autojump.txt.bak')
config['tmp_path'] = os.path.join(data_home, 'data.tmp')
return config
2013-12-17 02:28:54 +00:00
def parse_env(config):
# TODO(ting|2013-12-16): add autojump_data_dir support
# TODO(ting|2013-12-15): add ignore case / smartcase support
# TODO(ting|2013-12-15): add symlink support
return config
2013-12-17 02:28:54 +00:00
def parse_args(config):
parser = ArgumentParser(
2013-05-15 01:58:24 +00:00
description='Automatically jump to directory passed as an argument.',
epilog="Please see autojump(1) man pages for full documentation.")
2013-02-25 05:49:45 +00:00
parser.add_argument(
'directory', metavar='DIRECTORY', nargs='*', default='',
help='directory to jump to')
2013-02-25 05:49:45 +00:00
parser.add_argument(
'-a', '--add', metavar='DIRECTORY',
2013-12-17 02:28:54 +00:00
help='add path')
parser.add_argument(
'-i', '--increase', metavar='WEIGHT', nargs='?', type=int,
const=20, default=False,
2013-12-17 02:28:54 +00:00
help='increase current directory weight')
2013-02-25 05:49:45 +00:00
parser.add_argument(
'-d', '--decrease', metavar='WEIGHT', nargs='?', type=int,
const=15, default=False,
2013-12-17 02:28:54 +00:00
help='decrease current directory weight')
# parser.add_argument(
# '-b', '--bash', action="store_true", default=False,
# help='enclose directory quotes to prevent errors')
# parser.add_argument(
# '--complete', action="store_true", default=False,
# help='used for tab completion')
2013-02-25 05:49:45 +00:00
parser.add_argument(
'--purge', action="store_true", default=False,
2013-12-17 02:28:54 +00:00
help='remove non-existent paths from database')
2013-02-25 05:49:45 +00:00
parser.add_argument(
'-s', '--stat', action="store_true", default=False,
help='show database entries and their key weights')
2013-02-25 05:49:45 +00:00
parser.add_argument(
'-v', '--version', action="version", version="%(prog)s " +
2013-12-17 02:28:54 +00:00
VERSION, help='show version information')
args = parser.parse_args()
if args.add:
2013-12-17 02:28:54 +00:00
add_path(config, args.add)
sys.exit(0)
if args.increase:
2013-12-17 02:28:54 +00:00
try:
print_entry(add_path(config, os.getcwdu(), args.increase))
sys.exit(0)
except OSError:
print("Current directory no longer exists.", file=sys.stderr)
sys.exit(1)
if args.decrease:
2013-12-17 02:28:54 +00:00
try:
print_entry(decrease_path(config, os.getcwdu(), args.decrease))
sys.exit(0)
except OSError:
print("Current directory no longer exists.", file=sys.stderr)
sys.exit(1)
if args.purge:
2013-12-17 02:28:54 +00:00
print("Purged %d entries." % purge_missing_paths(config))
sys.exit(0)
if args.stat:
2013-12-17 02:28:54 +00:00
print_stats(config)
sys.exit(0)
2013-12-17 02:28:54 +00:00
print(encode_local(find_matches(config, args.directory)))
sys.exit(0)
2013-12-17 02:28:54 +00:00
# if args.complete:
# config['match_cnt'] = 9
# config['ignore_case'] = True
# config['args'] = args
return config
2012-05-07 01:09:37 +00:00
2013-12-17 02:28:54 +00:00
def add_path(config, path, increment=10):
2013-12-17 15:52:41 +00:00
"""
Add a new path or increment an existing one.
os.path.realpath() is not used because users prefer to have short, symlinked
paths with duplicate entries in the database than a single canonical path.
"""
2013-12-17 02:28:54 +00:00
path = decode(path).rstrip(os.sep)
if path == os.path.expanduser('~'):
return path, 0
2012-05-07 01:09:37 +00:00
2013-12-17 02:28:54 +00:00
data = load(config)
if path in data:
data[path] = sqrt((data[path]**2) + (increment**2))
else:
2013-12-17 02:28:54 +00:00
data[path] = increment
2013-12-17 02:28:54 +00:00
save(config, data)
return path, data[path]
2012-05-07 00:34:03 +00:00
2012-04-07 14:14:19 +00:00
2013-12-17 02:28:54 +00:00
def decrease_path(config, path, increment=15):
"""Decrease weight of existing path."""
path = decode(path).rstrip(os.sep)
data = load(config)
2012-04-07 14:14:19 +00:00
2013-12-17 02:28:54 +00:00
data[path] = max(0, data[path]-increment)
2013-05-15 02:58:43 +00:00
2013-12-17 02:28:54 +00:00
save(config, data)
return path, data[path]
def find_matches(config, needles, count=1):
"""Return [count] paths matching needles."""
entriefy = lambda tup: Entry(*tup)
data = sorted(
imap(entriefy, load(config).iteritems()),
2013-12-17 02:28:54 +00:00
key=attrgetter('weight'),
reverse=True)
if not needles:
return first(data).path
sanitize = lambda x: decode(x).rstrip(os.sep)
needles = map(sanitize, needles)
ignore_case = detect_smartcase(needles)
2013-12-17 02:28:54 +00:00
if len(needles) == 1:
# if one needle, match only against the last directory of each path
exact_matches = match_end(first(needles), data, ignore_case)
else:
exact_matches = match_regex(needles, data, ignore_case)
exists = lambda entry: os.path.exists(entry.path)
return first(ifilter(exists, exact_matches)).path
2013-12-17 02:28:54 +00:00
def match_end(needle, haystack, ignore_case=False):
"""Matches needle against the last directory of path."""
get_last_dir = lambda entry: second(os.path.split(entry.path))
has_needle = lambda entry: needle in get_last_dir(entry)
return ifilter(has_needle, haystack)
def match_regex(needles, haystack, ignore_case=False):
2013-12-17 15:52:30 +00:00
"""
Performs an exact match by combining all arguments into a single regex
expression and finding matches.
For example:
needles = ['qui', 'fox']
regex needle = r'.*qui.*fox.*'
haystack = [
(path="foobar", 10.0),
(path="The quick brown fox jumped over the lazy dog", 12.3)]
result = [(path="The quick brown fox jumped over the lazy dog", 12.3)]
"""
regex_needle = '.*' + '.*'.join(needles) + '.*'
regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE
has_needle = lambda haystack: re.search(regex_needle, haystack.path, flags=regex_flags)
return ifilter(has_needle, haystack)
2013-12-17 02:28:54 +00:00
def purge_missing_paths(config):
"""Remove non-existent paths."""
exists = lambda x: os.path.exists(x[0])
old_data = load(config)
new_data = dict(ifilter(exists, old_data.iteritems()))
save(config, new_data)
return len(old_data) - len(new_data)
def print_stats(config):
data = load(config)
for path, weight in sorted(data.iteritems(), key=itemgetter(1)):
print_entry(path, weight)
print("________________________________________\n")
print("%d:\t total weight" % sum(data.itervalues()))
print("%d:\t number of entries" % len(data))
try:
print("%.2f:\t current directory weight" % data.get(os.getcwdu(), 0))
except OSError:
pass
print("\ndata:\t %s" % config['data_path'])
2013-12-17 16:35:43 +00:00
def detect_smartcase(strings):
"""Detect if any uppercase letters are present in any of the strings."""
return not any(imap(has_uppercase, strings))
2013-12-17 02:28:54 +00:00
def main():
parse_args(parse_env(set_defaults()))
return 0
2012-04-07 14:14:19 +00:00
if __name__ == "__main__":
sys.exit(main())