From 757a8b300eb77cde1cc070b5586ae2635864409d Mon Sep 17 00:00:00 2001 From: Fabien Benureau Date: Sat, 5 Dec 2015 20:30:18 +0100 Subject: [PATCH] Add an update mechanism for old repositories The update is done transparently, and displays a warning message explaining the change. All the update machinery has been moved to the new update module. --- pubs/__init__.py | 2 +- pubs/commands/update_cmd.py | 39 ----------- pubs/config/__init__.py | 2 +- pubs/config/conf.py | 27 +++++--- pubs/pubs_cmd.py | 32 ++------- pubs/update.py | 63 ++++++++++++++++++ tests/test_config.py | 125 ++++++++++++++++++------------------ tests/test_databroker.py | 5 +- 8 files changed, 152 insertions(+), 143 deletions(-) delete mode 100644 pubs/commands/update_cmd.py create mode 100644 pubs/update.py diff --git a/pubs/__init__.py b/pubs/__init__.py index 2b8877c..ef7eb44 100644 --- a/pubs/__init__.py +++ b/pubs/__init__.py @@ -1 +1 @@ -__version__ = '0.5.0' +__version__ = '0.6.0' diff --git a/pubs/commands/update_cmd.py b/pubs/commands/update_cmd.py deleted file mode 100644 index 0457f6a..0000000 --- a/pubs/commands/update_cmd.py +++ /dev/null @@ -1,39 +0,0 @@ -import sys - -from .. import repo -from .. import color - -from ..uis import get_ui -from ..__init__ import __version__ - -def parser(subparsers): - parser = subparsers.add_parser('update', help='update the repository to the lastest format') - return parser - - -def command(conf, args): - - ui = get_ui() - - code_version = __version__.split('.') - if len(conf['internal']['version']) == 1: # support for deprecated version scheme. - conf['internal']['version'] = '0.{}.0'.format(conf['internal']['version']) - repo_version = conf['internal']['version'].split('.') - - if repo_version == code_version: - ui.message('Your pubs repository is up-to-date.') - sys.exit(0) - elif repo_version > code_version: - ui.message('Your repository was generated with an newer version of pubs.\n' - 'You should not use pubs until you install the newest version.') - sys.exit(0) - else: - msg = ("You should backup the pubs directory {} before continuing." - "Continue ?").format(color.dye_out(conf['main']['pubsdir'], color.filepath)) - sure = ui.input_yn(question=msg, default='n') - if not sure: - sys.exit(0) - - #TODO: update!! -# conf['internal']['version'] = repo_version -# conf['internal']['version'] diff --git a/pubs/config/__init__.py b/pubs/config/__init__.py index d7ea4b7..e9d8caa 100644 --- a/pubs/config/__init__.py +++ b/pubs/config/__init__.py @@ -1 +1 @@ -from .conf import load_default_conf, load_conf, save_conf, get_pubspath +from .conf import get_confpath, load_default_conf, load_conf, save_conf, check_conf diff --git a/pubs/config/conf.py b/pubs/config/conf.py index b891fd3..7867955 100644 --- a/pubs/config/conf.py +++ b/pubs/config/conf.py @@ -15,7 +15,7 @@ def load_default_conf(): default_conf.validate(validator, copy=True) return default_conf -def get_pubspath(verify=True): +def get_confpath(verify=True): """Returns the pubs path. If verify is True, verify that pubs.conf exist in the directory, and exit with an error if not. @@ -31,19 +31,26 @@ def get_pubspath(verify=True): ui.exit(error_code=1) return confpath -def load_conf(check_conf=True): +def check_conf(conf): + """Type checks a configuration""" + validator = validate.Validator() + results = conf.validate(validator, copy=True) + assert results == True, '{}'.format(results) # TODO: precise error dialog when parsing error + +def load_conf(check=True, path=None): """Load the user config""" - pubspath = get_pubspath(verify=True) - with open(pubspath, 'r') as f: + if path is None: + path = get_confpath(verify=True) + with open(path, 'rb') as f: conf = configobj.ConfigObj(f.readlines(), configspec=configspec) - if check_conf: - validator = validate.Validator() - results = conf.validate(validator, copy=True) - assert results == True, '{}'.format(results) # TODO: precise error dialog when parsing error + if check: + check_conf(conf) return conf -def save_conf(conf): - with open(get_pubspath(verify=False), 'w') as f: +def save_conf(conf, path=None): + if path is None: + path = get_confpath(verify=False) + with open(path, 'wb') as f: conf.write(outfile=f) diff --git a/pubs/pubs_cmd.py b/pubs/pubs_cmd.py index 16d7a80..512dc79 100644 --- a/pubs/pubs_cmd.py +++ b/pubs/pubs_cmd.py @@ -6,8 +6,8 @@ import collections from . import uis from . import config from . import commands +from . import update from . import plugins -from .__init__ import __version__ CORE_CMDS = collections.OrderedDict([ @@ -27,37 +27,17 @@ CORE_CMDS = collections.OrderedDict([ ('websearch', commands.websearch_cmd), ('edit', commands.edit_cmd), - # ('update', commands.update_cmd), ]) -def _update_check(conf, ui): - code_version = __version__.split('.') - if len(conf['internal']['version']) == 1: # support for deprecated version scheme. - conf['internal']['version'] = '0.{}.0'.format(conf['internal']['version']) - repo_version = conf['internal']['version'].split('.') - - if repo_version > code_version: - ui.warning( - 'your repository was generated with an newer version' - ' of pubs (v{}) than the one you are using (v{}).' - '\n'.format(repo_version, code_version) + - 'You should not use pubs until you install the ' - 'newest version.') - sys.exit() - elif repo_version < code_version: - ui.message( - 'warning: your repository version (v{})'.format(repo_version) - + 'must be updated to version {}.\n'.format(code_version) - + "run 'pubs update'.") - sys.exit() - - def execute(raw_args=sys.argv): # loading config if len(raw_args) > 1 and raw_args[1] != 'init': try: - conf = config.load_conf(check_conf=True) + conf = config.load_conf(check=False) + if update.update_check(conf): # an update happened, reload conf. + conf = config.load_conf(check=False) + config.check_conf(conf) except IOError as e: print('error: {}'.format(str(e))) sys.exit() @@ -67,8 +47,6 @@ def execute(raw_args=sys.argv): uis.init_ui(conf) ui = uis.get_ui() - _update_check(conf, ui) - parser = argparse.ArgumentParser(description="research papers repository") subparsers = parser.add_subparsers(title="valid commands", dest="command") diff --git a/pubs/update.py b/pubs/update.py new file mode 100644 index 0000000..7f90832 --- /dev/null +++ b/pubs/update.py @@ -0,0 +1,63 @@ +from . import config +from . import uis +from .__init__ import __version__ + + +def update_check(conf): + """Runs an update if necessary, and return True in that case.""" + + code_version = __version__.split('.') + try: + repo_version = conf['internal']['version'].split('.') + except KeyError: + repo_version = ['0', '5', '0'] + + if repo_version > code_version: + uis.init_ui(config.load_default_conf()) + ui = uis.get_ui() + + ui.warning( + 'Your repository was generated with an newer version' + ' of pubs (v{}) than the one you are using (v{}).' + '\n'.format(repo_version, code_version) + + 'You should not use pubs until you install the ' + 'newest version.') + sys.exit() + + elif repo_version < code_version: + return update(conf, code_version, repo_version) + + return False + +def update(conf, code_version, repo_version): + """Runs an update if necessary, and return True in that case.""" + + if repo_version == ['0', '5', '0']: # we need to update + default_conf = config.load_default_conf() + uis.init_ui(config.load_default_conf()) + ui = uis.get_ui() + + for key in ['pubsdir', 'docsdir', 'edit_cmd', 'open_cmd']: + default_conf['main'][key] = conf['pubs'][key] + if conf['pubs']['import_move']: + default_conf['main']['add_doc'] = 'move' + elif conf['pubs']['import_copy']: + default_conf['main']['add_doc'] = 'copy' + else: + default_conf['main']['add_doc'] = 'link' + + backup_path = config.get_confpath() + '.old' + config.save_conf(conf, path=backup_path) + config.save_conf(default_conf) + + ui.warning( + 'Your configuration file has been updated. ' + 'The old file has been moved to `{}`. '.format(backup_path) + + 'Some, but not all, of your settings has been transferred ' + 'to the new file.\n' + 'You can inspect and modify your configuration ' + ' using the `pubs config` command.' + ) + + return True + return False diff --git a/tests/test_config.py b/tests/test_config.py index 6dbe176..4c49d3e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,71 +2,70 @@ import unittest import dotdot -from pubs import configs -from pubs.configs import config +from pubs.config import conf from pubs.p3 import configparser -class TestConfig(unittest.TestCase): - - def test_create_config(self): - a = configs.Config() - a.as_global() - self.assertEqual(a, config()) - - def test_config_content(self): - a = configs.Config() - a.as_global() - - self.assertEqual(config().pubsdir, configs.DFT_CONFIG['pubsdir']) - self.assertEqual(config().color, configs.str2bool(configs.DFT_CONFIG['color'])) - - def test_set(self): - a = configs.Config() - a.as_global() - config().color = 'no' - self.assertEqual(config().color, False) - self.assertEqual(config('pubs').color, False) - # booleans type for new variables are memorized, but not saved. - config().bla = True - self.assertEqual(config().bla, True) - self.assertEqual(config('pubs').bla, True) - - with self.assertRaises(configparser.NoOptionError): - config()._cfg.get(configs.MAIN_SECTION, '_section') - - def test_reload(self): - - default_color = configs.DFT_CONFIG['color'] - - a = configs.Config() - a.as_global() - a.color = False - a.bla = 'foo' - config.color = not configs.str2bool(default_color) - self.assertEqual(config().color, not configs.str2bool(default_color)) - - b = configs.Config() - b.as_global() - self.assertEqual(b, config()) - self.assertEqual(config().color, configs.str2bool(default_color)) - - def test_exception(self): - - a = configs.Config() - a.as_global() - - with self.assertRaises(configparser.NoOptionError): - config().color2 - self.assertEqual(config().get('color2', default = 'blue'), 'blue') - - with self.assertRaises(configparser.NoSectionError): - config(section = 'bla3').color - self.assertEqual(config(section = 'bla3').get('color', default = 'green'), 'green') - self.assertEqual(config(section = 'bla3').get('color', default = config().color), True) - - def test_keywords(self): - a = configs.Config(pubs_dir = '/blabla') - self.assertEqual(a.pubs_dir, '/blabla') +# class TestConfig(unittest.TestCase): +# +# def test_create_config(self): +# a = configs.Config() +# a.as_global() +# self.assertEqual(a, config()) +# +# def test_config_content(self): +# a = configs.Config() +# a.as_global() +# +# self.assertEqual(config().pubsdir, configs.DFT_CONFIG['pubsdir']) +# self.assertEqual(config().color, configs.str2bool(configs.DFT_CONFIG['color'])) +# +# def test_set(self): +# a = configs.Config() +# a.as_global() +# config().color = 'no' +# self.assertEqual(config().color, False) +# self.assertEqual(config('pubs').color, False) +# # booleans type for new variables are memorized, but not saved. +# config().bla = True +# self.assertEqual(config().bla, True) +# self.assertEqual(config('pubs').bla, True) +# +# with self.assertRaises(configparser.NoOptionError): +# config()._cfg.get(configs.MAIN_SECTION, '_section') +# +# def test_reload(self): +# +# default_color = configs.DFT_CONFIG['color'] +# +# a = configs.Config() +# a.as_global() +# a.color = False +# a.bla = 'foo' +# config.color = not configs.str2bool(default_color) +# self.assertEqual(config().color, not configs.str2bool(default_color)) +# +# b = configs.Config() +# b.as_global() +# self.assertEqual(b, config()) +# self.assertEqual(config().color, configs.str2bool(default_color)) +# +# def test_exception(self): +# +# a = configs.Config() +# a.as_global() +# +# with self.assertRaises(configparser.NoOptionError): +# config().color2 +# self.assertEqual(config().get('color2', default = 'blue'), 'blue') +# +# with self.assertRaises(configparser.NoSectionError): +# config(section = 'bla3').color +# self.assertEqual(config(section = 'bla3').get('color', default = 'green'), 'green') +# self.assertEqual(config(section = 'bla3').get('color', default = config().color), True) +# +# def test_keywords(self): +# a = configs.Config(pubs_dir = '/blabla') +# self.assertEqual(a.pubs_dir, '/blabla') if __name__ == '__main__': diff --git a/tests/test_databroker.py b/tests/test_databroker.py index e8bfa6e..09f2a60 100644 --- a/tests/test_databroker.py +++ b/tests/test_databroker.py @@ -5,7 +5,8 @@ import os import dotdot import fake_env -from pubs import content, filebroker, databroker, datacache, configs +from pubs import content, filebroker, databroker, datacache +from pubs.config import conf import str_fixtures from pubs import endecoder @@ -20,7 +21,7 @@ class TestDataBroker(unittest.TestCase): page99_bibentry = ende.decode_bibdata(str_fixtures.bibtex_raw0) for db_class in [databroker.DataBroker, datacache.DataCache]: - self.fs = fake_env.create_fake_fs([content, filebroker, configs]) + self.fs = fake_env.create_fake_fs([content, filebroker, conf]) db = db_class('tmp', create=True)