1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-21 20:38:54 +00:00

#353 Cache aliases in a temporary file

This commit is contained in:
nvbn 2015-09-02 11:10:03 +03:00
parent ea6600be8b
commit 4129ff2717
4 changed files with 103 additions and 4 deletions

View File

@ -10,3 +10,8 @@ def no_memoize(monkeypatch):
@pytest.fixture @pytest.fixture
def settings(): def settings():
return Mock(debug=False, no_colors=True) return Mock(debug=False, no_colors=True)
@pytest.fixture(autouse=True)
def no_cache(monkeypatch):
monkeypatch.setattr('thefuck.utils.cache.disabled', True)

View File

@ -1,8 +1,9 @@
from contextlib import contextmanager
import pytest import pytest
from mock import Mock from mock import Mock
from thefuck.utils import wrap_settings,\ from thefuck.utils import wrap_settings, \
memoize, get_closest, get_all_executables, replace_argument, \ memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands, is_app, for_app get_all_matched_commands, is_app, for_app, cache
from thefuck.types import Settings from thefuck.types import Settings
from tests.utils import Command from tests.utils import Command
@ -34,7 +35,6 @@ def test_no_memoize():
class TestGetClosest(object): class TestGetClosest(object):
def test_when_can_match(self): def test_when_can_match(self):
assert 'branch' == get_closest('brnch', ['branch', 'status']) assert 'branch' == get_closest('brnch', ['branch', 'status'])
@ -116,3 +116,56 @@ def test_for_app(script, names, result):
return True return True
assert match(Command(script), None) == result assert match(Command(script), None) == result
class TestCache(object):
@pytest.fixture(autouse=True)
def enable_cache(self, monkeypatch):
monkeypatch.setattr('thefuck.utils.cache.disabled', False)
@pytest.fixture
def shelve(self, mocker):
value = {}
@contextmanager
def _shelve(path):
yield value
mocker.patch('thefuck.utils.shelve.open', new_callable=lambda: _shelve)
return value
@pytest.fixture(autouse=True)
def mtime(self, mocker):
mocker.patch('thefuck.utils.os.path.getmtime', return_value=0)
@pytest.fixture
def fn(self):
@cache('~/.bashrc')
def fn():
return 'test'
return fn
def test_with_blank_cache(self, shelve, fn):
assert shelve == {}
assert fn() == 'test'
assert shelve == {
'tests.test_utils.<function TestCache.fn.<locals>.fn ': {
'etag': '0', 'value': 'test'}}
def test_with_filled_cache(self, shelve, fn):
cache_value = {
'tests.test_utils.<function TestCache.fn.<locals>.fn ': {
'etag': '0', 'value': 'new-value'}}
shelve.update(cache_value)
assert fn() == 'new-value'
assert shelve == cache_value
def test_when_etag_changed(self, shelve, fn):
shelve.update({
'tests.test_utils.<function TestCache.fn.<locals>.fn ': {
'etag': '-1', 'value': 'old-value'}})
assert fn() == 'test'
assert shelve == {
'tests.test_utils.<function TestCache.fn.<locals>.fn ': {
'etag': '0', 'value': 'test'}}

View File

@ -9,7 +9,7 @@ from subprocess import Popen, PIPE
from time import time from time import time
import io import io
import os import os
from .utils import DEVNULL, memoize from .utils import DEVNULL, memoize, cache
class Generic(object): class Generic(object):
@ -85,6 +85,7 @@ class Bash(Generic):
return name, value return name, value
@memoize @memoize
@cache('.bashrc', '.bash_profile')
def get_aliases(self): def get_aliases(self):
proc = Popen(['bash', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) proc = Popen(['bash', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
return dict( return dict(
@ -169,6 +170,7 @@ class Zsh(Generic):
return name, value return name, value
@memoize @memoize
@cache('.zshrc')
def get_aliases(self): def get_aliases(self):
proc = Popen(['zsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) proc = Popen(['zsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
return dict( return dict(

View File

@ -1,6 +1,8 @@
from difflib import get_close_matches from difflib import get_close_matches
from functools import wraps from functools import wraps
import shelve
from decorator import decorator from decorator import decorator
import tempfile
import os import os
import pickle import pickle
@ -150,3 +152,40 @@ def for_app(*app_names):
return False return False
return decorator(_for_app) return decorator(_for_app)
def cache(*depends_on):
"""Caches function result in temporary file.
Cache will be expired when modification date of files from `depends_on`
will be changed.
Function wrapped in `cache` should be arguments agnostic.
"""
def _get_mtime(name):
path = os.path.join(os.path.expanduser('~'), name)
try:
return str(os.path.getmtime(path))
except OSError:
return '0'
@decorator
def _cache(fn, *args, **kwargs):
if cache.disabled:
return fn(*args, **kwargs)
cache_path = os.path.join(tempfile.gettempdir(), '.thefuck-cache')
key = '{}.{}'.format(fn.__module__, repr(fn).split('at')[0])
etag = '.'.join(_get_mtime(name) for name in depends_on)
with shelve.open(cache_path) as db:
if db.get(key, {}).get('etag') == etag:
return db[key]['value']
else:
value = fn(*args, **kwargs)
db[key] = {'etag': etag, 'value': value}
return value
return _cache
cache.disabled = False