From 962265909921cfe6de72f160c466f38e635dc3e5 Mon Sep 17 00:00:00 2001 From: Marek Marecki Date: Tue, 3 May 2016 20:42:30 +0200 Subject: [PATCH 01/20] Fix: handling regex characters in path patterns Before this commit if a regex-special character was present on the commandline as a part of path pattern autojump would fail with `sre_constants.error`. --- bin/autojump | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/autojump b/bin/autojump index 2be7f7f..81f1e38 100755 --- a/bin/autojump +++ b/bin/autojump @@ -279,7 +279,7 @@ def match_consecutive(needles, haystack, ignore_case=False): regex_no_sep_end = regex_no_sep + '$' regex_one_sep = regex_no_sep + sep + regex_no_sep # can't use compiled regex because of flags - regex_needle = regex_one_sep.join(needles).replace('\\', '\\\\') + regex_no_sep_end # noqa + regex_needle = regex_one_sep.join(map(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE found = lambda entry: re.search( regex_needle, From a32f237043025df2ec434055d5adbef1c2897101 Mon Sep 17 00:00:00 2001 From: Marek Marecki Date: Tue, 3 May 2016 20:45:09 +0200 Subject: [PATCH 02/20] Add Python 3.5 to tests --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1dfb82f..b06031b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,8 @@ envlist = py27, py32, py33, - py34 + py34, + py35 # ignore missing setup.py skipsdist = True From 9241b4b20f9b4bde883bcd46c2479409d20145e3 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:10:57 -0700 Subject: [PATCH 03/20] Add make clean. --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5466f00..a9603be 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ VERSION = $(shell grep -oE "[0-9]+\.[0-9]+\.[0-9]+" bin/autojump) TAGNAME = release-v$(VERSION) -.PHONY: docs install uninstall lint tar test +.PHONY: clean docs install uninstall pre-commit lint tar test install: ./install.py @@ -45,3 +45,8 @@ test: pre-commit test-fast: pre-commit @find . -type f -iname '*.py[co]' -delete tox -e py27 + +clean: + @find . -type f -iname '*.py[co]' -delete + @find . -type d -iname '__pycache__' -delete + @rm -fr .tox From a24d199161506c0a4defe20c4a197fb5f0c71aec Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:11:40 -0700 Subject: [PATCH 04/20] Fix tox.ini / test runner. --- requirements-dev.txt | 1 - tox.ini | 16 ++++------------ 2 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 requirements-dev.txt diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 053148f..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ -tox diff --git a/tox.ini b/tox.ini index b06031b..83b3465 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = # ignore missing setup.py skipsdist = True -[testenv:py] +[testenv] setenv = PYTHONDONTWRITEBYTECODE = 1 deps = @@ -19,22 +19,14 @@ deps = ipython pytest commands = - coverage run --source=bin/ -m pytest -vv -rxs --tb native -s --durations 10 --strict {posargs:tests} + coverage run --source=bin/ -m py.test -rxs --tb native -s --strict {posargs:tests} coverage report -m -[testenv:flake8] -deps = - flake8 - pyflakes - pep8 - mccabe -commands = - flake8 . [testenv:pre-commit] deps = - pre-commit -command = + pre-commit>=0.7.0 +commands = pre-commit {posargs} [pytest] From d1822bc11a4e8fb714b5703d50a33df55e9cad88 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:14:36 -0700 Subject: [PATCH 05/20] Update pre-commit. --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e26ac79..a69e3b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: git@github.com:pre-commit/pre-commit-hooks.git - sha: 6f2b0a27e5b9047c6c067fb3d575ba323d572793 + sha: 35548254adb636ce52b5574eb1904b8c795b673e hooks: - id: autopep8-wrapper args: @@ -16,10 +16,10 @@ - id: fix-encoding-pragma - id: flake8 args: - - --max-complexity=10 - - --max-line-length=130 - - --ignore=E126,E128,E731 - - --exclude=bin/autojump_argparse.py + - --max-complexity=10 + - --max-line-length=130 + - --ignore=E126,E128,E731 + - --exclude=bin/autojump_argparse.py - id: requirements-txt-fixer - id: trailing-whitespace - repo: git@github.com:asottile/reorder_python_imports.git From 94dae01b4993869b70a9af2c67b99ebe01b3a2c6 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:16:08 -0700 Subject: [PATCH 06/20] Move unit tests to own folder. --- tests/autojump_test.py | 0 tests/{autojump_data_test.py => unit/__init__.py} | 0 tests/{ => unit}/autojump_utils_test.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/autojump_test.py rename tests/{autojump_data_test.py => unit/__init__.py} (100%) rename tests/{ => unit}/autojump_utils_test.py (100%) diff --git a/tests/autojump_test.py b/tests/autojump_test.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/autojump_data_test.py b/tests/unit/__init__.py similarity index 100% rename from tests/autojump_data_test.py rename to tests/unit/__init__.py diff --git a/tests/autojump_utils_test.py b/tests/unit/autojump_utils_test.py similarity index 100% rename from tests/autojump_utils_test.py rename to tests/unit/autojump_utils_test.py From be5b703996a7e3f1c9b59ba2fe46fd6c51a3483d Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:16:33 -0700 Subject: [PATCH 07/20] Add integration test dir. --- tests/integration/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/__init__.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 From e75d4d4d110f345ab8d0572a2dbc046f145d65ae Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:19:31 -0700 Subject: [PATCH 08/20] Drop official support for Python 3.2. It looks like pip 8.x has dropped support for Python 3.2 and is causing test build problems. Since Python 3.3, 3.4, and 3.5 are supported I don't see any issues with dropping 3.2 test coverage although it will probably still work. --- README.md | 2 +- tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5af4374..62b9549 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ INSTALLATION ### REQUIREMENTS -- Python v2.6+ +- Python v2.6+ (except Python3.2) - Supported shells: - bash v4.0+ - zsh diff --git a/tox.ini b/tox.ini index 83b3465..e2542fb 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ envlist = py26, py27, - py32, py33, py34, py35 From 180c5d840272bef70b5f7b6c94851c6e330539af Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:24:29 -0700 Subject: [PATCH 09/20] Exclude vendorized argparse lib from coverage report. --- Makefile | 4 ++-- tox.ini | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a9603be..e828f0a 100644 --- a/Makefile +++ b/Makefile @@ -40,11 +40,11 @@ tar: test: pre-commit @find . -type f -iname '*.py[co]' -delete - tox + @tox test-fast: pre-commit @find . -type f -iname '*.py[co]' -delete - tox -e py27 + @tox -e py27 clean: @find . -type f -iname '*.py[co]' -delete diff --git a/tox.ini b/tox.ini index e2542fb..26e8b83 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,8 @@ deps = ipython pytest commands = - coverage run --source=bin/ -m py.test -rxs --tb native -s --strict {posargs:tests} + coverage run --source=bin/ --omit=bin/autojump_argparse.py -m \ + py.test -rxs --tb native -s --strict {posargs:tests} coverage report -m From 2e60fa2892ad0a4b3bc3965d36e9312b1cd75e07 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:32:38 -0700 Subject: [PATCH 10/20] Add vendorized argparse comment. --- bin/autojump | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/autojump b/bin/autojump index 81f1e38..6eeec10 100755 --- a/bin/autojump +++ b/bin/autojump @@ -38,6 +38,7 @@ else: from itertools import ifilter from itertools import imap +# Vendorized argparse for Python 2.6 support from autojump_argparse import ArgumentParser from autojump_data import dictify From 797d97c9bffd133f97ce05ce3b9f8ef72807bd86 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:38:31 -0700 Subject: [PATCH 11/20] Add module naming comment. --- bin/autojump | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/autojump b/bin/autojump index 6eeec10..247c24e 100755 --- a/bin/autojump +++ b/bin/autojump @@ -18,7 +18,6 @@ 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 print_function from difflib import SequenceMatcher @@ -41,6 +40,10 @@ else: # Vendorized argparse for Python 2.6 support from autojump_argparse import ArgumentParser +# autojump is not a standard python package but rather installed as a mixed +# shell + Python app with no outside dependencies (except Python). As a +# consequence we use relative imports and depend on file prefixes to prevent +# module conflicts. from autojump_data import dictify from autojump_data import entriefy from autojump_data import Entry From 7922a9013d57a4466c5efba7350a282880a95c4d Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:44:43 -0700 Subject: [PATCH 12/20] Update copyright dates. --- README.md | 4 ++-- bin/autojump | 2 +- docs/autojump.1 | 2 +- docs/body.md | 2 +- docs/install.md | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 62b9549..182193a 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ INSTALLATION ### REQUIREMENTS -- Python v2.6+ (except Python3.2) +- Python v2.6+ except v3.2 - Supported shells: - bash v4.0+ - zsh @@ -148,7 +148,7 @@ maintained by William Ting. More contributors can be found in `AUTHORS`. COPYRIGHT --------- -Copyright © 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL +Copyright © 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. diff --git a/bin/autojump b/bin/autojump index 247c24e..9205085 100755 --- a/bin/autojump +++ b/bin/autojump @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ Copyright © 2008-2012 Joel Schaerer - Copyright © 2012-2014 William Ting + Copyright © 2012-2016 William Ting * 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 diff --git a/docs/autojump.1 b/docs/autojump.1 index 188194f..2e185ce 100644 --- a/docs/autojump.1 +++ b/docs/autojump.1 @@ -127,7 +127,7 @@ maintained by William Ting. More contributors can be found in \f[C]AUTHORS\f[]. .SS COPYRIGHT .PP -Copyright © 2012 Free Software Foundation, Inc. +Copyright © 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. diff --git a/docs/body.md b/docs/body.md index 9d635b4..91a169e 100644 --- a/docs/body.md +++ b/docs/body.md @@ -28,7 +28,7 @@ William Ting. More contributors can be found in `AUTHORS`. COPYRIGHT --------- -Copyright © 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version +Copyright © 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. diff --git a/docs/install.md b/docs/install.md index 1ad7d66..895ff29 100644 --- a/docs/install.md +++ b/docs/install.md @@ -2,7 +2,7 @@ ### REQUIREMENTS -- Python v2.6+ +- Python v2.6+ except v3.2 - Supported shells: - bash v4.0+ - zsh @@ -41,7 +41,7 @@ MacPorts also available: ## Windows -Windows support is enabled by [clink](https://code.google.com/p/clink/) which +Windows support is enabled by [clink](https://mridgers.github.io/clink/) which should be installed prior to installing autojump. ### MANUAL From 218d779b4d63f511d3cb237f4b23f29c2a2da7f9 Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:55:01 -0700 Subject: [PATCH 13/20] Simplify flake8 noqa. --- bin/autojump | 110 +-------------------------- bin/autojump_match.py | 120 ++++++++++++++++++++++++++++++ tests/unit/autojump_utils_test.py | 28 +++---- 3 files changed, 137 insertions(+), 121 deletions(-) create mode 100644 bin/autojump_match.py diff --git a/bin/autojump b/bin/autojump index 9205085..3431eec 100755 --- a/bin/autojump +++ b/bin/autojump @@ -20,13 +20,11 @@ """ from __future__ import print_function -from difflib import SequenceMatcher from itertools import chain from math import sqrt from operator import attrgetter from operator import itemgetter import os -import re import sys if sys.version_info[0] == 3: @@ -49,6 +47,9 @@ from autojump_data import entriefy from autojump_data import Entry from autojump_data import load from autojump_data import save +from autojump_match import match_anywhere +from autojump_match import match_consecutive +from autojump_match import match_fuzzy from autojump_utils import first from autojump_utils import get_pwd from autojump_utils import get_tab_entry_info @@ -225,111 +226,6 @@ def handle_tab_completion(needle, entries): TAB_SEPARATOR) -def match_anywhere(needles, haystack, ignore_case=False): - """ - Matches needles anywhere in the path as long as they're in the same (but - not necessary consecutive) order. - - For example: - needles = ['foo', 'baz'] - regex needle = r'.*foo.*baz.*' - haystack = [ - (path="/foo/bar/baz", weight=10), - (path="/baz/foo/bar", weight=10), - (path="/foo/baz", weight=10)] - - result = [ - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] - """ - regex_needle = '.*' + '.*'.join(needles).replace('\\', '\\\\') + '.*' - regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE - found = lambda haystack: re.search( - regex_needle, - haystack.path, - flags=regex_flags) - return ifilter(found, haystack) - - -def match_consecutive(needles, haystack, ignore_case=False): - """ - Matches consecutive needles at the end of a path. - - For example: - needles = ['foo', 'baz'] - haystack = [ - (path="/foo/bar/baz", weight=10), - (path="/foo/baz/moo", weight=10), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] - - regex_needle = re.compile(r''' - foo # needle #1 - [^/]* # all characters except os.sep zero or more times - / # os.sep - [^/]* # all characters except os.sep zero or more times - baz # needle #2 - [^/]* # all characters except os.sep zero or more times - $ # end of string - ''') - - result = [ - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] - """ - # The normal \\ separator needs to be escaped again for use in regex. - sep = '\\\\' if is_windows() else os.sep - regex_no_sep = '[^' + sep + ']*' - regex_no_sep_end = regex_no_sep + '$' - regex_one_sep = regex_no_sep + sep + regex_no_sep - # can't use compiled regex because of flags - regex_needle = regex_one_sep.join(map(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa - regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE - found = lambda entry: re.search( - regex_needle, - entry.path, - flags=regex_flags) - return ifilter(found, haystack) - - -def match_fuzzy(needles, haystack, ignore_case=False): - """ - Performs an approximate match with the last needle against the end of - every path past an acceptable threshold (FUZZY_MATCH_THRESHOLD). - - For example: - needles = ['foo', 'bar'] - haystack = [ - (path="/foo/bar/baz", weight=11), - (path="/foo/baz/moo", weight=10), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10), - (path="/foo/bar", weight=10)] - - result = [ - (path="/foo/bar/baz", weight=11), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10), - (path="/foo/bar", weight=10)] - - This is a weak heuristic and used as a last resort to find matches. - """ - end_dir = lambda path: last(os.path.split(path)) - if ignore_case: - needle = last(needles).lower() - match_percent = lambda entry: SequenceMatcher( - a=needle, - b=end_dir(entry.path.lower())).ratio() - else: - needle = last(needles) - match_percent = lambda entry: SequenceMatcher( - a=needle, - b=end_dir(entry.path)).ratio() - meets_threshold = lambda entry: match_percent(entry) >= \ - FUZZY_MATCH_THRESHOLD - return ifilter(meets_threshold, haystack) - - def purge_missing_paths(entries): """Remove non-existent paths from a list of entries.""" exists = lambda entry: os.path.exists(entry.path) diff --git a/bin/autojump_match.py b/bin/autojump_match.py new file mode 100644 index 0000000..088e8bf --- /dev/null +++ b/bin/autojump_match.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import re +import sys +from difflib import SequenceMatcher + + +if sys.version_info[0] == 3: + ifilter = filter + imap = map + os.getcwdu = os.getcwd +else: + from itertools import ifilter + from itertools import imap + + +def match_anywhere(needles, haystack, ignore_case=False): + """ + Matches needles anywhere in the path as long as they're in the same (but + not necessary consecutive) order. + + For example: + needles = ['foo', 'baz'] + regex needle = r'.*foo.*baz.*' + haystack = [ + (path="/foo/bar/baz", weight=10), + (path="/baz/foo/bar", weight=10), + (path="/foo/baz", weight=10)] + + result = [ + (path="/moo/foo/baz", weight=10), + (path="/foo/baz", weight=10)] + """ + regex_needle = '.*' + '.*'.join(needles).replace('\\', '\\\\') + '.*' + regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE + found = lambda haystack: re.search( + regex_needle, + haystack.path, + flags=regex_flags) + return ifilter(found, haystack) + + +def match_consecutive(needles, haystack, ignore_case=False): + """ + Matches consecutive needles at the end of a path. + + For example: + needles = ['foo', 'baz'] + haystack = [ + (path="/foo/bar/baz", weight=10), + (path="/foo/baz/moo", weight=10), + (path="/moo/foo/baz", weight=10), + (path="/foo/baz", weight=10)] + + regex_needle = re.compile(r''' + foo # needle #1 + [^/]* # all characters except os.sep zero or more times + / # os.sep + [^/]* # all characters except os.sep zero or more times + baz # needle #2 + [^/]* # all characters except os.sep zero or more times + $ # end of string + ''') + + result = [ + (path="/moo/foo/baz", weight=10), + (path="/foo/baz", weight=10)] + """ + # The normal \\ separator needs to be escaped again for use in regex. + sep = '\\\\' if is_windows() else os.sep + regex_no_sep = '[^' + sep + ']*' + regex_no_sep_end = regex_no_sep + '$' + regex_one_sep = regex_no_sep + sep + regex_no_sep + # can't use compiled regex because of flags + regex_needle = regex_one_sep.join(map(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa + regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE + found = lambda entry: re.search( + regex_needle, + entry.path, + flags=regex_flags) + return ifilter(found, haystack) + + +def match_fuzzy(needles, haystack, ignore_case=False): + """ + Performs an approximate match with the last needle against the end of + every path past an acceptable threshold (FUZZY_MATCH_THRESHOLD). + + For example: + needles = ['foo', 'bar'] + haystack = [ + (path="/foo/bar/baz", weight=11), + (path="/foo/baz/moo", weight=10), + (path="/moo/foo/baz", weight=10), + (path="/foo/baz", weight=10), + (path="/foo/bar", weight=10)] + + result = [ + (path="/foo/bar/baz", weight=11), + (path="/moo/foo/baz", weight=10), + (path="/foo/baz", weight=10), + (path="/foo/bar", weight=10)] + + This is a weak heuristic and used as a last resort to find matches. + """ + end_dir = lambda path: last(os.path.split(path)) + if ignore_case: + needle = last(needles).lower() + match_percent = lambda entry: SequenceMatcher( + a=needle, + b=end_dir(entry.path.lower())).ratio() + else: + needle = last(needles) + match_percent = lambda entry: SequenceMatcher( + a=needle, + b=end_dir(entry.path)).ratio() + meets_threshold = lambda entry: match_percent(entry) >= \ + FUZZY_MATCH_THRESHOLD + return ifilter(meets_threshold, haystack) diff --git a/tests/unit/autojump_utils_test.py b/tests/unit/autojump_utils_test.py index fd71a20..07f67b3 100644 --- a/tests/unit/autojump_utils_test.py +++ b/tests/unit/autojump_utils_test.py @@ -6,20 +6,20 @@ import sys import mock import pytest -sys.path.append(os.path.join(os.getcwd(), 'bin')) -import autojump_utils # noqa -from autojump_utils import encode_local # noqa -from autojump_utils import first # noqa -from autojump_utils import get_tab_entry_info # noqa -from autojump_utils import has_uppercase # noqa -from autojump_utils import in_bash # noqa -from autojump_utils import is_python3 # noqa -from autojump_utils import last # noqa -from autojump_utils import sanitize # noqa -from autojump_utils import second # noqa -from autojump_utils import surround_quotes # noqa -from autojump_utils import take # noqa -from autojump_utils import unico # noqa +sys.path.append(os.path.join(os.getcwd(), 'bin')) # noqa +import autojump_utils +from autojump_utils import encode_local +from autojump_utils import first +from autojump_utils import get_tab_entry_info +from autojump_utils import has_uppercase +from autojump_utils import in_bash +from autojump_utils import is_python3 +from autojump_utils import last +from autojump_utils import sanitize +from autojump_utils import second +from autojump_utils import surround_quotes +from autojump_utils import take +from autojump_utils import unico if is_python3(): From 737563570e68b9670d5819a5e180acb4f4d78d5c Mon Sep 17 00:00:00 2001 From: William Ting Date: Tue, 3 May 2016 23:57:00 -0700 Subject: [PATCH 14/20] Fix match imports and simplify fuzzy threshold. --- bin/autojump | 2 +- bin/autojump_match.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bin/autojump b/bin/autojump index 3431eec..bf1443d 100755 --- a/bin/autojump +++ b/bin/autojump @@ -65,7 +65,7 @@ from autojump_utils import sanitize from autojump_utils import take from autojump_utils import unico -VERSION = '22.3.2' +VERSION = '22.3.3' FUZZY_MATCH_THRESHOLD = 0.6 TAB_ENTRIES_COUNT = 9 TAB_SEPARATOR = '__' diff --git a/bin/autojump_match.py b/bin/autojump_match.py index 088e8bf..5549e1b 100644 --- a/bin/autojump_match.py +++ b/bin/autojump_match.py @@ -2,11 +2,14 @@ # -*- coding: utf-8 -*- import os import re -import sys from difflib import SequenceMatcher +from autojump_utils import is_python3 +from autojump_utils import is_windows +from autojump_utils import last -if sys.version_info[0] == 3: + +if is_python3(): ifilter = filter imap = map os.getcwdu = os.getcwd @@ -73,7 +76,7 @@ def match_consecutive(needles, haystack, ignore_case=False): regex_no_sep_end = regex_no_sep + '$' regex_one_sep = regex_no_sep + sep + regex_no_sep # can't use compiled regex because of flags - regex_needle = regex_one_sep.join(map(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa + regex_needle = regex_one_sep.join(imap(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE found = lambda entry: re.search( regex_needle, @@ -82,10 +85,10 @@ def match_consecutive(needles, haystack, ignore_case=False): return ifilter(found, haystack) -def match_fuzzy(needles, haystack, ignore_case=False): +def match_fuzzy(needles, haystack, ignore_case=False, threshold=0.6): """ Performs an approximate match with the last needle against the end of - every path past an acceptable threshold (FUZZY_MATCH_THRESHOLD). + every path past an acceptable threshold. For example: needles = ['foo', 'bar'] @@ -115,6 +118,5 @@ def match_fuzzy(needles, haystack, ignore_case=False): match_percent = lambda entry: SequenceMatcher( a=needle, b=end_dir(entry.path)).ratio() - meets_threshold = lambda entry: match_percent(entry) >= \ - FUZZY_MATCH_THRESHOLD + meets_threshold = lambda entry: match_percent(entry) >= threshold return ifilter(meets_threshold, haystack) From eafd6ac451601cb6bf5fd7e52d63d8192b9ffcef Mon Sep 17 00:00:00 2001 From: William Ting Date: Wed, 4 May 2016 00:50:06 -0700 Subject: [PATCH 15/20] Verbose pytest output to help understand test failures. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 26e8b83..d2d98a0 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ deps = pytest commands = coverage run --source=bin/ --omit=bin/autojump_argparse.py -m \ - py.test -rxs --tb native -s --strict {posargs:tests} + py.test -vv -rxs --tb native -s --strict {posargs:tests} coverage report -m From cf013c6875cf9ae74f459984457466dc809b92e6 Mon Sep 17 00:00:00 2001 From: William Ting Date: Wed, 4 May 2016 00:50:34 -0700 Subject: [PATCH 16/20] Misc cleanup. --- bin/autojump_match.py | 52 +++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/bin/autojump_match.py b/bin/autojump_match.py index 5549e1b..663fdf2 100644 --- a/bin/autojump_match.py +++ b/bin/autojump_match.py @@ -27,20 +27,23 @@ def match_anywhere(needles, haystack, ignore_case=False): needles = ['foo', 'baz'] regex needle = r'.*foo.*baz.*' haystack = [ - (path="/foo/bar/baz", weight=10), - (path="/baz/foo/bar", weight=10), - (path="/foo/baz", weight=10)] + (path='/foo/bar/baz', weight=10), + (path='/baz/foo/bar', weight=10), + (path='/foo/baz', weight=10), + ] result = [ - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] + (path='/moo/foo/baz', weight=10), + (path='/foo/baz', weight=10), + ] """ regex_needle = '.*' + '.*'.join(needles).replace('\\', '\\\\') + '.*' regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE found = lambda haystack: re.search( regex_needle, haystack.path, - flags=regex_flags) + flags=regex_flags, + ) return ifilter(found, haystack) @@ -51,10 +54,11 @@ def match_consecutive(needles, haystack, ignore_case=False): For example: needles = ['foo', 'baz'] haystack = [ - (path="/foo/bar/baz", weight=10), - (path="/foo/baz/moo", weight=10), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] + (path='/foo/bar/baz', weight=10), + (path='/foo/baz/moo', weight=10), + (path='/moo/foo/baz', weight=10), + (path='/foo/baz', weight=10), + ] regex_needle = re.compile(r''' foo # needle #1 @@ -67,8 +71,9 @@ def match_consecutive(needles, haystack, ignore_case=False): ''') result = [ - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10)] + (path='/moo/foo/baz', weight=10), + (path='/foo/baz', weight=10), + ] """ # The normal \\ separator needs to be escaped again for use in regex. sep = '\\\\' if is_windows() else os.sep @@ -81,7 +86,8 @@ def match_consecutive(needles, haystack, ignore_case=False): found = lambda entry: re.search( regex_needle, entry.path, - flags=regex_flags) + flags=regex_flags, + ) return ifilter(found, haystack) @@ -93,17 +99,19 @@ def match_fuzzy(needles, haystack, ignore_case=False, threshold=0.6): For example: needles = ['foo', 'bar'] haystack = [ - (path="/foo/bar/baz", weight=11), - (path="/foo/baz/moo", weight=10), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10), - (path="/foo/bar", weight=10)] + (path='/foo/bar/baz', weight=11), + (path='/foo/baz/moo', weight=10), + (path='/moo/foo/baz', weight=10), + (path='/foo/baz', weight=10), + (path='/foo/bar', weight=10), + ] result = [ - (path="/foo/bar/baz", weight=11), - (path="/moo/foo/baz", weight=10), - (path="/foo/baz", weight=10), - (path="/foo/bar", weight=10)] + (path='/foo/bar/baz', weight=11), + (path='/moo/foo/baz', weight=10), + (path='/foo/baz', weight=10), + (path='/foo/bar', weight=10), + ] This is a weak heuristic and used as a last resort to find matches. """ From 3cb4e8a28cacaff4381938ca78e1ebeff998a8f7 Mon Sep 17 00:00:00 2001 From: William Ting Date: Wed, 4 May 2016 00:50:47 -0700 Subject: [PATCH 17/20] Add match_anywhere tests. --- bin/autojump_match.py | 2 +- tests/unit/autojump_match_test.py | 71 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/unit/autojump_match_test.py diff --git a/bin/autojump_match.py b/bin/autojump_match.py index 663fdf2..c390a75 100644 --- a/bin/autojump_match.py +++ b/bin/autojump_match.py @@ -37,7 +37,7 @@ def match_anywhere(needles, haystack, ignore_case=False): (path='/foo/baz', weight=10), ] """ - regex_needle = '.*' + '.*'.join(needles).replace('\\', '\\\\') + '.*' + regex_needle = '.*' + '.*'.join(imap(re.escape, needles)) + '.*' regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE found = lambda haystack: re.search( regex_needle, diff --git a/tests/unit/autojump_match_test.py b/tests/unit/autojump_match_test.py new file mode 100644 index 0000000..2f249cb --- /dev/null +++ b/tests/unit/autojump_match_test.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import sys + +import pytest + +sys.path.append(os.path.join(os.getcwd(), 'bin')) # noqa +from autojump_data import Entry +from autojump_match import match_anywhere + + +class TestMatchAnywhere(object): + + entry1 = Entry('/foo/bar/baz', 10) + entry2 = Entry('/baz/foo/bar', 10) + entry3 = Entry('/foo/baz', 10) + entry4 = Entry('/中/zhong/国/guo', 10) + entry5 = Entry('/is\'t/this/a/b*tchin/edge/case?', 10) + win_entry1 = Entry('C:\\foo\\bar\\baz', 10) + win_entry2 = Entry('D:\Program Files (x86)\GIMP', 10) + win_entry3 = Entry('C:\Windows\System32', 10) + + @pytest.fixture + def haystack(self): + return [ + self.entry1, + self.entry2, + self.entry3, + self.entry4, + self.entry5, + ] + + @pytest.fixture + def windows_haystack(self): + return [self.win_entry1, self.win_entry2, self.win_entry3] + + def test_single_needle(self, haystack): + assert list( + match_anywhere( + ['bar'], + haystack)) == [ + self.entry1, + self.entry2] + + def test_consecutive(self, haystack): + assert list(match_anywhere(['foo', 'bar'], haystack)) \ + == [self.entry1, self.entry2] + assert list(match_anywhere(['bar', 'foo'], haystack)) == [] + + def test_skip(self, haystack): + assert list(match_anywhere(['baz', 'bar'], haystack)) == [self.entry2] + assert list(match_anywhere(['中', '国'], haystack)) == [self.entry4] + + def test_ignore_case(self, haystack): + assert list(match_anywhere(['bAz', 'bAR'], haystack, ignore_case=True)) \ + == [self.entry2] + + def test_backslashes_for_windows_paths(self, windows_haystack): + # https://github.com/wting/autojump/issues/281 + assert list(match_anywhere(['foo', 'baz'], windows_haystack)) \ + == [self.win_entry1] + assert list(match_anywhere(['program', 'gimp'], windows_haystack, True)) \ + == [self.win_entry2] + assert list(match_anywhere(['win', '32'], windows_haystack, True)) \ + == [self.win_entry3] + + def test_wildcard_in_needle(self, haystack): + # https://github.com/wting/autojump/issues/402 + assert list(match_anywhere(['*', 'this'], haystack)) == [] + assert list(match_anywhere(['this', '*'], haystack)) == [self.entry5] From dc9e11e7d57d5f59e510f4ea4d5e9e26fcba802e Mon Sep 17 00:00:00 2001 From: William Ting Date: Mon, 9 May 2016 22:17:06 -0700 Subject: [PATCH 18/20] Use the same line length settings for autopep8 and flake8. --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a69e3b1..324d870 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,7 @@ - --in-place - --aggressive - --aggressive + - --max-line-length=131 - id: check-added-large-files - id: check-ast - id: check-case-conflict @@ -17,7 +18,7 @@ - id: flake8 args: - --max-complexity=10 - - --max-line-length=130 + - --max-line-length=131 - --ignore=E126,E128,E731 - --exclude=bin/autojump_argparse.py - id: requirements-txt-fixer From 5e550267c73a4aa377d320fb60d80a46fe3f0b56 Mon Sep 17 00:00:00 2001 From: William Ting Date: Wed, 22 Jun 2016 18:08:51 -0700 Subject: [PATCH 19/20] Add test-xfail to run broken tests. --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e828f0a..3675eed 100644 --- a/Makefile +++ b/Makefile @@ -39,11 +39,12 @@ tar: sha1sum autojump_v$(VERSION).tar.gz test: pre-commit - @find . -type f -iname '*.py[co]' -delete @tox +test-xfail: pre-commit + @tox -- --runxfail + test-fast: pre-commit - @find . -type f -iname '*.py[co]' -delete @tox -e py27 clean: From 7c7865ea7ecfd937284f774fb0818c5cc10c340a Mon Sep 17 00:00:00 2001 From: William Ting Date: Wed, 22 Jun 2016 18:09:36 -0700 Subject: [PATCH 20/20] Add match consecutive tests. --- bin/autojump_match.py | 13 +++--- tests/unit/autojump_match_test.py | 72 ++++++++++++++++++++++++++++--- tox.ini | 2 +- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/bin/autojump_match.py b/bin/autojump_match.py index c390a75..75f9aca 100644 --- a/bin/autojump_match.py +++ b/bin/autojump_match.py @@ -5,11 +5,10 @@ import re from difflib import SequenceMatcher from autojump_utils import is_python3 -from autojump_utils import is_windows from autojump_utils import last -if is_python3(): +if is_python3(): # pragma: no cover ifilter = filter imap = map os.getcwdu = os.getcwd @@ -60,6 +59,7 @@ def match_consecutive(needles, haystack, ignore_case=False): (path='/foo/baz', weight=10), ] + # We can't actually use re.compile because of re.UNICODE regex_needle = re.compile(r''' foo # needle #1 [^/]* # all characters except os.sep zero or more times @@ -75,13 +75,10 @@ def match_consecutive(needles, haystack, ignore_case=False): (path='/foo/baz', weight=10), ] """ - # The normal \\ separator needs to be escaped again for use in regex. - sep = '\\\\' if is_windows() else os.sep - regex_no_sep = '[^' + sep + ']*' + regex_no_sep = '[^' + os.sep + ']*' regex_no_sep_end = regex_no_sep + '$' - regex_one_sep = regex_no_sep + sep + regex_no_sep - # can't use compiled regex because of flags - regex_needle = regex_one_sep.join(imap(re.escape, needles)).replace('\\', '\\\\') + regex_no_sep_end # noqa + regex_one_sep = regex_no_sep + os.sep + regex_no_sep + regex_needle = regex_one_sep.join(imap(re.escape, needles)) + regex_no_sep_end regex_flags = re.IGNORECASE | re.UNICODE if ignore_case else re.UNICODE found = lambda entry: re.search( regex_needle, diff --git a/tests/unit/autojump_match_test.py b/tests/unit/autojump_match_test.py index 2f249cb..bd02ff5 100644 --- a/tests/unit/autojump_match_test.py +++ b/tests/unit/autojump_match_test.py @@ -8,6 +8,7 @@ import pytest sys.path.append(os.path.join(os.getcwd(), 'bin')) # noqa from autojump_data import Entry from autojump_match import match_anywhere +from autojump_match import match_consecutive class TestMatchAnywhere(object): @@ -36,12 +37,7 @@ class TestMatchAnywhere(object): return [self.win_entry1, self.win_entry2, self.win_entry3] def test_single_needle(self, haystack): - assert list( - match_anywhere( - ['bar'], - haystack)) == [ - self.entry1, - self.entry2] + assert list(match_anywhere(['bar'], haystack)) == [self.entry1, self.entry2] def test_consecutive(self, haystack): assert list(match_anywhere(['foo', 'bar'], haystack)) \ @@ -69,3 +65,67 @@ class TestMatchAnywhere(object): # https://github.com/wting/autojump/issues/402 assert list(match_anywhere(['*', 'this'], haystack)) == [] assert list(match_anywhere(['this', '*'], haystack)) == [self.entry5] + + +class TestMatchConsecutive(object): + + entry1 = Entry('/foo/bar/baz', 10) + entry2 = Entry('/baz/foo/bar', 10) + entry3 = Entry('/foo/baz', 10) + entry4 = Entry('/中/zhong/国/guo', 10) + entry5 = Entry('/日/本', 10) + entry6 = Entry('/is\'t/this/a/b*tchin/edge/case?', 10) + win_entry1 = Entry('C:\Foo\Bar\Baz', 10) + win_entry2 = Entry('D:\Program Files (x86)\GIMP', 10) + win_entry3 = Entry('C:\Windows\System32', 10) + + @pytest.fixture + def haystack(self): + return [ + self.entry1, + self.entry2, + self.entry3, + self.entry4, + self.entry5, + ] + + @pytest.fixture + def windows_haystack(self): + return [self.win_entry1, self.win_entry2, self.win_entry3] + + def test_single_needle(self, haystack): + assert list(match_consecutive(['baz'], haystack)) == [self.entry1, self.entry3] + assert list(match_consecutive(['本'], haystack)) == [self.entry5] + + def test_consecutive(self, haystack): + assert list(match_consecutive(['bar', 'baz'], haystack)) == [self.entry1] + assert list(match_consecutive(['foo', 'bar'], haystack)) == [self.entry2] + assert list(match_consecutive(['国', 'guo'], haystack)) == [self.entry4] + assert list(match_consecutive(['bar', 'foo'], haystack)) == [] + + def test_ignore_case(self, haystack): + assert list(match_consecutive(['FoO', 'bAR'], haystack, ignore_case=True)) \ + == [self.entry2] + + def test_windows_ignore_case(self, windows_haystack): + assert list(match_consecutive(['gimp'], windows_haystack, True)) == [self.win_entry2] + + @pytest.mark.xfail(reason='https://github.com/wting/autojump/issues/418') + def test_backslashes_for_windows_paths(self, windows_haystack): + assert list(match_consecutive(['program', 'gimp'], windows_haystack, True)) \ + == [self.win_entry2] + + @pytest.mark.xfail(reason='https://github.com/wting/autojump/issues/418') + def test_foo_bar_baz(self, windows_haystack): + assert list(match_consecutive(['bar', 'baz'], windows_haystack, ignore_case=True)) \ + == [self.win_entry1] + + @pytest.mark.xfail(reason='https://github.com/wting/autojump/issues/402') + def test_thing(self, windows_haystack): + assert list(match_consecutive(['win', '32'], windows_haystack, True)) \ + == [self.win_entry3] + + @pytest.mark.xfail(reason='https://github.com/wting/autojump/issues/402') + def test_wildcard_in_needle(self, haystack): + assert list(match_consecutive(['*', 'this'], haystack)) == [] + assert list(match_consecutive(['*', 'edge', 'case'], haystack)) == [self.entry6] diff --git a/tox.ini b/tox.ini index d2d98a0..8a21b97 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ deps = coverage ipdb ipython - pytest + pytest >= 2.9 commands = coverage run --source=bin/ --omit=bin/autojump_argparse.py -m \ py.test -vv -rxs --tb native -s --strict {posargs:tests}