From a880f5b6779ccd55064a6844bd3cb25d76eea3c4 Mon Sep 17 00:00:00 2001 From: Jonathan Grizou Date: Mon, 24 Jun 2013 22:39:37 +0200 Subject: [PATCH 1/8] bulshit --- papers/commands/remove_cmd.py | 9 +++++++++ papers/plugins/texnote/texnote_cmd.py | 23 ++++++++++++++++------- setup.py | 4 ++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/papers/commands/remove_cmd.py b/papers/commands/remove_cmd.py index a40e40a..b312c1d 100644 --- a/papers/commands/remove_cmd.py +++ b/papers/commands/remove_cmd.py @@ -1,5 +1,6 @@ from .. import repo from .. import color +from .. import configs from .helpers import add_references_argument, parse_references @@ -18,4 +19,12 @@ def command(config, ui, references): sure = ui.input_yn(question=are_you_sure, default='n') if sure: for c in citekeys: + # Extend with plugin commands, think about how to create a smart registering system for plugins + plugs = configs.get_plugins(config) + for plugname in plugs: + module_name = 'papers.plugins.' + plugname + '.' + plugname + '_cmd' + plug = __import__(module_name, globals(), locals(), ['callback'], -1) + plug.callback(config, ui, 'remove', c) + rp.remove(c) + diff --git a/papers/plugins/texnote/texnote_cmd.py b/papers/plugins/texnote/texnote_cmd.py index 83b1ce3..d838153 100644 --- a/papers/plugins/texnote/texnote_cmd.py +++ b/papers/plugins/texnote/texnote_cmd.py @@ -27,19 +27,28 @@ TEXNOTE_DIR = 'texnote' def parser(subparsers, config): parser = subparsers.add_parser('texnote', help="edit advance note in latex") - parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) add_references_argument(parser, single=True) + parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) return parser -def command(config, ui, ref, view): - ui.print_('texnote test') +def command(config, ui, reference, view): if view is not None: - subprocess.Popen(['papers', 'open', ref]) - + subprocess.Popen(['papers', 'open', reference]) # check if citekey exist - open_texnote(config, ui, ref) + open_texnote(config, ui, reference) + + +def callback(config, ui, name, ref): + if name == 'remove': + rp = repo.Repository.from_directory(config) + paper = rp.get_paper(parse_reference(ui, rp, ref)) + if paper.metadata.has_key('texnote'): + os.remove(paper.metadata['texnote']) + paper.metadata.pop('texnote') + metapath = rp.path_to_paper_file(paper.citekey, 'meta') + files.save_meta(paper.metadata, metapath) def open_texnote(config, ui, ref): @@ -48,7 +57,7 @@ def open_texnote(config, ui, ref): if not paper.metadata.has_key('texnote'): texnote_dir = os.path.join(rp.papersdir, TEXNOTE_DIR) - # if folder does not exist create it + # if folder does not exist create it, this should be relative if not os.path.exists(texnote_dir): os.mkdir(texnote_dir) texnote_path = os.path.join(texnote_dir, paper.citekey + '.tex') diff --git a/setup.py b/setup.py index c9d0a64..461cdc8 100644 --- a/setup.py +++ b/setup.py @@ -10,9 +10,9 @@ setup(name='papers', description='research papers manager', requires=['pybtex'], packages=find_packages(), + #package_data={'': ['*.tex']}, scripts=['papers/papers'] ) -# TODO include package data from plugins -# Jonathan could not make it works (08/06/2013) +# TODO include proper package data from plugins (08/06/2013) # should we use MANIFEST.in or package_data = ..., or both From ff195c0859a475f5a873a1714c77f6c169d88564 Mon Sep 17 00:00:00 2001 From: jgrizou Date: Tue, 25 Jun 2013 19:45:00 +0200 Subject: [PATCH 2/8] Plugins class created and handle for the parse. Next step is to code the event messages. Next one is to code the setup extension. --- papers/papers | 9 ++- papers/plugins.py | 72 +++++++++++++++++++ papers/{plugins => plugs}/__init__.py | 0 papers/{plugins => plugs}/texnote/__init__.py | 0 .../texnote/note_sample.tex | 0 .../texnote/texnote.py} | 60 +++++++--------- setup.py | 2 +- 7 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 papers/plugins.py rename papers/{plugins => plugs}/__init__.py (100%) rename papers/{plugins => plugs}/texnote/__init__.py (100%) rename papers/{plugins => plugs}/texnote/note_sample.tex (100%) rename papers/{plugins/texnote/texnote_cmd.py => plugs/texnote/texnote.py} (72%) diff --git a/papers/papers b/papers/papers index 55281b8..659bbfb 100755 --- a/papers/papers +++ b/papers/papers @@ -8,6 +8,7 @@ import collections from papers.ui import UI from papers import configs from papers import commands +from papers import plugins cmds = collections.OrderedDict([ ('init', commands.init_cmd), @@ -30,11 +31,17 @@ ui = UI(config) # Extend with plugin commands plugs = configs.get_plugins(config) for plugname in plugs: - module_name = 'papers.plugins.' + plugname + '.' + plugname + '_cmd' + module_name = 'papers.plugs.' + plugname + '.' + plugname + '_cmd' plug = __import__(module_name, globals(), locals(), ['parser', 'command'], -1) cmds.update(collections.OrderedDict([(plugname, plug)])) +plugins.load_plugins(config, ui, plugs) +for p in plugins.get_plugins().values(): + cmds.update(collections.OrderedDict([(p.name, p)])) + +##### + parser = argparse.ArgumentParser(description="research papers repository") subparsers = parser.add_subparsers(title="valid commands", dest="command") diff --git a/papers/plugins.py b/papers/plugins.py new file mode 100644 index 0000000..7626ddb --- /dev/null +++ b/papers/plugins.py @@ -0,0 +1,72 @@ +import importlib + +PLUGIN_NAMESPACE = 'plugs' + +_classes = [] +_instances = {} + + +class PapersPlugin(object): + """The base class for all plugins. Plugins provide + functionality by defining a subclass of PapersPlugin and overriding + the abstract methods defined here. + """ + def __init__(self, config, ui): + """Perform one-time plugin setup. + """ + self.name = self.__module__.split('.')[-1] + self.config = config + self.ui = ui + + #config and ui and given again to stay consistent with the core papers cmd. + #two options: + #- create specific cases in script papers/papers + #- do not store self.config and self.ui and use them if needed when command is called + #this may end up a lot of function with config/ui in argument + #or just keep it this way... + def parser(self, subparsers, config): + """ Should retrun the parser with plugins specific command. + This is a basic example + """ + parser = subparsers.add_parser(self.name, help="echo string in argument") + parser.add_argument('strings', nargs='*', help='the strings') + return parser + + def command(self, config, ui, strings): + """This function will be called with argument defined in the parser above + This is a basic example + """ + for s in strings: + print s + + +def load_plugins(config, ui, names): + """Imports the modules for a sequence of plugin names. Each name + must be the name of a Python module under the "PLUGIN_NAMESPACE" namespace + package in sys.path; the module indicated should contain the + PapersPlugin subclasses desired. + """ + for name in names: + modname = '%s.%s.%s.%s' % ('papers', PLUGIN_NAMESPACE, name, name) + try: + try: + namespace = importlib.import_module(modname) + except ImportError as exc: + # Again, this is hacky: + if exc.args[0].endswith(' ' + name): + ui.warning('** plugin %s not found' % name) + else: + raise + else: + for obj in namespace.__dict__.values(): + if isinstance(obj, type) and issubclass(obj, PapersPlugin) \ + and obj != PapersPlugin: + _classes.append(obj) + _instances[obj] = obj(config, ui) + + except: + ui.warning('** error loading plugin %s' % name) + + +def get_plugins(): + return _instances diff --git a/papers/plugins/__init__.py b/papers/plugs/__init__.py similarity index 100% rename from papers/plugins/__init__.py rename to papers/plugs/__init__.py diff --git a/papers/plugins/texnote/__init__.py b/papers/plugs/texnote/__init__.py similarity index 100% rename from papers/plugins/texnote/__init__.py rename to papers/plugs/texnote/__init__.py diff --git a/papers/plugins/texnote/note_sample.tex b/papers/plugs/texnote/note_sample.tex similarity index 100% rename from papers/plugins/texnote/note_sample.tex rename to papers/plugs/texnote/note_sample.tex diff --git a/papers/plugins/texnote/texnote_cmd.py b/papers/plugs/texnote/texnote.py similarity index 72% rename from papers/plugins/texnote/texnote_cmd.py rename to papers/plugs/texnote/texnote.py index d838153..7ef9b0c 100644 --- a/papers/plugins/texnote/texnote_cmd.py +++ b/papers/plugs/texnote/texnote.py @@ -1,42 +1,33 @@ -#import ConfigParser - -#from ... import configs -#cfg = configs.read_config() - -#TEXNOTE_SECTION = 'texnote' -#DEFAULT_EDIT_CMD = cfg.get(configs.MAIN_SECTION, 'edit-cmd') - -#TODO file should not be created before the end of the process to ensure everything went ok -#TODO add subparser to have more feature -#TODO add clean command to wipe out any compilation file -#TODO add function to merge several texnote in one based on a research result - import os import shutil import subprocess from ... import repo -from ...paper import NoDocumentFile from ... import configs from ... import files +from ...plugins import PapersPlugin from ...commands.helpers import add_references_argument, parse_reference TEXNOTE_SECTION = 'texnote' TEXNOTE_SAMPLE_FILE = os.path.join(os.path.dirname(__file__), 'note_sample.tex') TEXNOTE_DIR = 'texnote' -def parser(subparsers, config): - parser = subparsers.add_parser('texnote', help="edit advance note in latex") - add_references_argument(parser, single=True) - parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) - return parser +class TexnotePlugin(PapersPlugin): + + def parser(self, subparsers, config): + parser = subparsers.add_parser(self.name, help="edit advance note in latex") + add_references_argument(parser, single=True) + parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) + return parser -def command(config, ui, reference, view): - if view is not None: - subprocess.Popen(['papers', 'open', reference]) - # check if citekey exist - open_texnote(config, ui, reference) + def command(self, config, ui, reference, view): + if view is not None: + subprocess.Popen(['papers', 'open', reference]) + open_texnote(config, ui, reference) + + +# temporary def callback(config, ui, name, ref): @@ -44,7 +35,7 @@ def callback(config, ui, name, ref): rp = repo.Repository.from_directory(config) paper = rp.get_paper(parse_reference(ui, rp, ref)) - if paper.metadata.has_key('texnote'): + if 'texnote' in paper.metadata: os.remove(paper.metadata['texnote']) paper.metadata.pop('texnote') metapath = rp.path_to_paper_file(paper.citekey, 'meta') @@ -55,13 +46,14 @@ def open_texnote(config, ui, ref): rp = repo.Repository.from_directory(config) paper = rp.get_paper(parse_reference(ui, rp, ref)) - if not paper.metadata.has_key('texnote'): + #ugly to recode like for the doc field + if not 'texnote' in paper.metadata: texnote_dir = os.path.join(rp.papersdir, TEXNOTE_DIR) # if folder does not exist create it, this should be relative if not os.path.exists(texnote_dir): os.mkdir(texnote_dir) texnote_path = os.path.join(texnote_dir, paper.citekey + '.tex') - paper.metadata['texnote'] = files.clean_path(texnote_path) + paper.metadata['texnote'] = files.clean_path(texnote_path) # save path in metadata metapath = rp.path_to_paper_file(paper.citekey, 'meta') files.save_meta(paper.metadata, metapath) @@ -82,7 +74,6 @@ def open_texnote(config, ui, ref): subprocess.Popen([config.get(configs.MAIN_SECTION, 'edit-cmd'), texnote_path]) - ##### ugly replace by proper ##### def format_author(author): first = author.first() @@ -97,13 +88,14 @@ def format_author(author): formatted += ' ' + last[0] return formatted + def concatenate_authors(authors): concatenated = '' for a in range(len(authors)): if len(authors) > 1 and a > 0: if a == len(authors) - 1: concatenated += 'and ' - else : + else: concatenated += ', ' concatenated += authors[a] return concatenated @@ -120,23 +112,23 @@ def autofill_texnote(texnote_path, bibentry): fields = bibentry.fields persons = bibentry.persons - if fields.has_key('title'): + if 'title' in fields: title_str = fields['title'] text = text.replace("TITLE", title_str) - if fields.has_key('year'): + if 'year' in fields: year_str = fields['year'] text = text.replace("YEAR", year_str) - if fields.has_key('abstract'): + if 'abstract' in fields: abstract_str = fields['abstract'] text = text.replace("ABSTRACT", abstract_str) - if persons.has_key('author'): + if 'author' in persons: authors = [] for author in persons['author']: authors.append(format_author(author)) - author_str = concatenate_authors(authors) + author_str = concatenate_authors(authors) text = text.replace("AUTHOR", author_str) # write file diff --git a/setup.py b/setup.py index 461cdc8..e0c3119 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup(name='papers', description='research papers manager', requires=['pybtex'], packages=find_packages(), - #package_data={'': ['*.tex']}, + package_data={'': ['*.tex']}, scripts=['papers/papers'] ) From ed44a2c26278aef6805bba35df96883a737de0f6 Mon Sep 17 00:00:00 2001 From: Jonathan Grizou Date: Wed, 26 Jun 2013 12:19:24 +0200 Subject: [PATCH 3/8] Event mechanism working perfectly and very simple and clean --- papers/commands/remove_cmd.py | 15 ++++--- papers/events.py | 77 +++++++++++++++++++++++++++++++++ papers/papers | 12 +---- papers/plugins.py | 8 ++-- papers/plugs/texnote/texnote.py | 26 +++++------ 5 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 papers/events.py diff --git a/papers/commands/remove_cmd.py b/papers/commands/remove_cmd.py index b312c1d..9d3ac7d 100644 --- a/papers/commands/remove_cmd.py +++ b/papers/commands/remove_cmd.py @@ -3,6 +3,13 @@ from .. import color from .. import configs from .helpers import add_references_argument, parse_references +from ..events import Event + +class RemoveEvent(Event): + def __init__(self, config, ui, citekey): + self.config = config + self.ui = ui + self.citekey = citekey def parser(subparsers, config): parser = subparsers.add_parser('remove', help='removes a paper') @@ -19,12 +26,8 @@ def command(config, ui, references): sure = ui.input_yn(question=are_you_sure, default='n') if sure: for c in citekeys: - # Extend with plugin commands, think about how to create a smart registering system for plugins - plugs = configs.get_plugins(config) - for plugname in plugs: - module_name = 'papers.plugins.' + plugname + '.' + plugname + '_cmd' - plug = __import__(module_name, globals(), locals(), ['callback'], -1) - plug.callback(config, ui, 'remove', c) + rmevent = RemoveEvent(config, ui, c) + rmevent.send() rp.remove(c) diff --git a/papers/events.py b/papers/events.py new file mode 100644 index 0000000..da61858 --- /dev/null +++ b/papers/events.py @@ -0,0 +1,77 @@ +msg_list = {} + +def listen(EventClass): + def wrap(f): + if isinstance(EventClass, type) \ + and issubclass(EventClass, Event) \ + and EventClass != Event: + + if not EventClass.__name__ in msg_list: + msg_list[EventClass.__name__] = [] + msg_list[EventClass.__name__].append(f) + # next step allow us to call the function itself without Event raised + def wrapped_f(*args): + f(*args) + return wrapped_f + else: + raise IOError('{} is not an Event subclass'.format(EventClass)) + return wrap + + +class Event(object): + def __init__(self, string): + """This is an example of simple event that can be raised + Inherit from this class and redefine whatever you need, + except the send funtion + """ + self.string = string + + def send(self): + """ This function send the instance of the class, i.e. the event to be sent, + to all function that listen to it + """ + if self.__class__.__name__ in msg_list: + for f in msg_list[self.__class__.__name__]: + f(self) + +if __name__ == "__main__": + + class TestEvent(Event): + def print_one(self): + print 'one' + + @listen(TestEvent) + def Display(TestEventInstance): + print TestEventInstance.string + + @listen(TestEvent) + def Helloword(TestEventInstance): + print 'Helloword' + + @listen(TestEvent) + def PrintIt(TestEventInstance): + TestEventInstance.print_one() + + class AddEvent(Event): + def __init__(self): + pass + + def add(self, a, b): + return a + b + + @listen(AddEvent) + def DoIt(AddEventInstance): + print AddEventInstance.add(17, 25) + + # using the callback system + myevent = TestEvent('abcdefghijklmnopqrstuvwxyz') + myevent.send() # this one call three function + + addevent = AddEvent() + addevent.send() + + # but also work without the event raising system! + Display(myevent) + Helloword(myevent) + PrintIt(myevent) + DoIt(addevent) diff --git a/papers/papers b/papers/papers index 659bbfb..5058445 100755 --- a/papers/papers +++ b/papers/papers @@ -29,18 +29,10 @@ config = configs.read_config() ui = UI(config) # Extend with plugin commands -plugs = configs.get_plugins(config) -for plugname in plugs: - module_name = 'papers.plugs.' + plugname + '.' + plugname + '_cmd' - plug = __import__(module_name, globals(), locals(), - ['parser', 'command'], -1) - cmds.update(collections.OrderedDict([(plugname, plug)])) - -plugins.load_plugins(config, ui, plugs) +plugins.load_plugins(config, ui, configs.get_plugins(config)) for p in plugins.get_plugins().values(): cmds.update(collections.OrderedDict([(p.name, p)])) - -##### +# parser = argparse.ArgumentParser(description="research papers repository") subparsers = parser.add_subparsers(title="valid commands", dest="command") diff --git a/papers/plugins.py b/papers/plugins.py index 7626ddb..87883a3 100644 --- a/papers/plugins.py +++ b/papers/plugins.py @@ -22,8 +22,8 @@ class PapersPlugin(object): #two options: #- create specific cases in script papers/papers #- do not store self.config and self.ui and use them if needed when command is called - #this may end up a lot of function with config/ui in argument - #or just keep it this way... + #this may end up with a lot of function with config/ui in argument + #or just keep it that way... def parser(self, subparsers, config): """ Should retrun the parser with plugins specific command. This is a basic example @@ -54,7 +54,7 @@ def load_plugins(config, ui, names): except ImportError as exc: # Again, this is hacky: if exc.args[0].endswith(' ' + name): - ui.warning('** plugin %s not found' % name) + ui.warning('plugin {} not found'.format(name)) else: raise else: @@ -65,7 +65,7 @@ def load_plugins(config, ui, names): _instances[obj] = obj(config, ui) except: - ui.warning('** error loading plugin %s' % name) + ui.warning('error loading plugin {}'.format(name)) def get_plugins(): diff --git a/papers/plugs/texnote/texnote.py b/papers/plugs/texnote/texnote.py index 7ef9b0c..34b9f39 100644 --- a/papers/plugs/texnote/texnote.py +++ b/papers/plugs/texnote/texnote.py @@ -8,6 +8,9 @@ from ... import files from ...plugins import PapersPlugin from ...commands.helpers import add_references_argument, parse_reference +from ...events import listen +from ...commands.remove_cmd import RemoveEvent + TEXNOTE_SECTION = 'texnote' TEXNOTE_SAMPLE_FILE = os.path.join(os.path.dirname(__file__), 'note_sample.tex') TEXNOTE_DIR = 'texnote' @@ -27,19 +30,16 @@ class TexnotePlugin(PapersPlugin): open_texnote(config, ui, reference) -# temporary - - -def callback(config, ui, name, ref): - if name == 'remove': - rp = repo.Repository.from_directory(config) - paper = rp.get_paper(parse_reference(ui, rp, ref)) +@listen(RemoveEvent) +def remove(rmevent): + rp = repo.Repository.from_directory(rmevent.config) + paper = rp.get_paper(parse_reference(rmevent.ui, rp, rmevent.citekey)) - if 'texnote' in paper.metadata: - os.remove(paper.metadata['texnote']) - paper.metadata.pop('texnote') - metapath = rp.path_to_paper_file(paper.citekey, 'meta') - files.save_meta(paper.metadata, metapath) + if 'texnote' in paper.metadata: + os.remove(paper.metadata['texnote']) + paper.metadata.pop('texnote') + metapath = rp.path_to_paper_file(paper.citekey, 'meta') + files.save_meta(paper.metadata, metapath) def open_texnote(config, ui, ref): @@ -108,7 +108,7 @@ def autofill_texnote(texnote_path, bibentry): text = f.read() f.close() # modify with bib info - print bibentry + #print bibentry fields = bibentry.fields persons = bibentry.persons From 415b1b37c53843da64a780e290671e7b676ce08d Mon Sep 17 00:00:00 2001 From: jgrizou Date: Wed, 26 Jun 2013 20:00:44 +0200 Subject: [PATCH 4/8] args in event mechanism --- papers/events.py | 26 ++++++++++++++------------ papers/papers | 2 +- papers/plugins.py | 7 +++++++ papers/plugs/texnote/texnote.py | 18 ++++++++++++++---- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/papers/events.py b/papers/events.py index da61858..43f3640 100644 --- a/papers/events.py +++ b/papers/events.py @@ -1,14 +1,16 @@ -msg_list = {} +listener = {} -def listen(EventClass): + +def listen(EventClass, *args): def wrap(f): if isinstance(EventClass, type) \ and issubclass(EventClass, Event) \ and EventClass != Event: - if not EventClass.__name__ in msg_list: - msg_list[EventClass.__name__] = [] - msg_list[EventClass.__name__].append(f) + if not EventClass.__name__ in listener: + listener[EventClass.__name__] = [] + listener[EventClass.__name__].append((f, args)) + # next step allow us to call the function itself without Event raised def wrapped_f(*args): f(*args) @@ -30,9 +32,9 @@ class Event(object): """ This function send the instance of the class, i.e. the event to be sent, to all function that listen to it """ - if self.__class__.__name__ in msg_list: - for f in msg_list[self.__class__.__name__]: - f(self) + if self.__class__.__name__ in listener: + for f, args in listener[self.__class__.__name__]: + f(self, *args) if __name__ == "__main__": @@ -40,9 +42,9 @@ if __name__ == "__main__": def print_one(self): print 'one' - @listen(TestEvent) - def Display(TestEventInstance): - print TestEventInstance.string + @listen(TestEvent, 12, 15) + def Display(TestEventInstance, nb1, nb2): + print TestEventInstance.string, nb1, nb2 @listen(TestEvent) def Helloword(TestEventInstance): @@ -71,7 +73,7 @@ if __name__ == "__main__": addevent.send() # but also work without the event raising system! - Display(myevent) + Display(myevent, 12, 15) Helloword(myevent) PrintIt(myevent) DoIt(addevent) diff --git a/papers/papers b/papers/papers index 5058445..865eec0 100755 --- a/papers/papers +++ b/papers/papers @@ -38,7 +38,7 @@ parser = argparse.ArgumentParser(description="research papers repository") subparsers = parser.add_subparsers(title="valid commands", dest="command") for cmd_mod in cmds.values(): - subparser = cmd_mod.parser(subparsers, config) + subparser = cmd_mod.parser(subparsers, config) # why do we return the subparser ? args = parser.parse_args() args.config = config diff --git a/papers/plugins.py b/papers/plugins.py index 87883a3..7794e3f 100644 --- a/papers/plugins.py +++ b/papers/plugins.py @@ -39,6 +39,13 @@ class PapersPlugin(object): for s in strings: print s + @classmethod + def get_instance(cls): + if cls in _instances: + return _instances[cls] + else: + raise RuntimeError("{} instance not created".format(cls.__name__)) + def load_plugins(config, ui, names): """Imports the modules for a sequence of plugin names. Each name diff --git a/papers/plugs/texnote/texnote.py b/papers/plugs/texnote/texnote.py index 34b9f39..7528279 100644 --- a/papers/plugs/texnote/texnote.py +++ b/papers/plugs/texnote/texnote.py @@ -20,21 +20,31 @@ class TexnotePlugin(PapersPlugin): def parser(self, subparsers, config): parser = subparsers.add_parser(self.name, help="edit advance note in latex") - add_references_argument(parser, single=True) + sub = parser.add_subparsers(title="valid texnote commands", dest="texcmd") + p = sub.add_parser("remove", help="remove a reference") + add_references_argument(p, single=True) + p = sub.add_parser("edit", help="edit the reference texnote") + add_references_argument(p, single=True) + #add_references_argument(parser, single=True) parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) return parser - def command(self, config, ui, reference, view): + def command(self, config, ui, texcmd, reference, view): if view is not None: subprocess.Popen(['papers', 'open', reference]) - open_texnote(config, ui, reference) + if texcmd == 'edit': + open_texnote(config, ui, reference) + + def toto(self): + print "toto" @listen(RemoveEvent) def remove(rmevent): + texplug = TexnotePlugin.get_instance() + texplug.toto() rp = repo.Repository.from_directory(rmevent.config) paper = rp.get_paper(parse_reference(rmevent.ui, rp, rmevent.citekey)) - if 'texnote' in paper.metadata: os.remove(paper.metadata['texnote']) paper.metadata.pop('texnote') From 6d303b2c4c00c68c730c6ed28f75598ca652e154 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Thu, 27 Jun 2013 18:35:57 +0200 Subject: [PATCH 5/8] Simplifies event mechanism. - moves RemoveEvent to events - makes listen a classmethod of the event --- papers/commands/remove_cmd.py | 8 +---- papers/events.py | 58 +++++++++++++++++---------------- papers/plugs/texnote/texnote.py | 11 ++++--- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/papers/commands/remove_cmd.py b/papers/commands/remove_cmd.py index 9d3ac7d..e1dfea4 100644 --- a/papers/commands/remove_cmd.py +++ b/papers/commands/remove_cmd.py @@ -3,13 +3,8 @@ from .. import color from .. import configs from .helpers import add_references_argument, parse_references -from ..events import Event +from ..events import RemoveEvent -class RemoveEvent(Event): - def __init__(self, config, ui, citekey): - self.config = config - self.ui = ui - self.citekey = citekey def parser(subparsers, config): parser = subparsers.add_parser('remove', help='removes a paper') @@ -30,4 +25,3 @@ def command(config, ui, references): rmevent.send() rp.remove(c) - diff --git a/papers/events.py b/papers/events.py index 43f3640..05b27a2 100644 --- a/papers/events.py +++ b/papers/events.py @@ -1,23 +1,4 @@ -listener = {} - - -def listen(EventClass, *args): - def wrap(f): - if isinstance(EventClass, type) \ - and issubclass(EventClass, Event) \ - and EventClass != Event: - - if not EventClass.__name__ in listener: - listener[EventClass.__name__] = [] - listener[EventClass.__name__].append((f, args)) - - # next step allow us to call the function itself without Event raised - def wrapped_f(*args): - f(*args) - return wrapped_f - else: - raise IOError('{} is not an Event subclass'.format(EventClass)) - return wrap +_listener = {} class Event(object): @@ -29,28 +10,49 @@ class Event(object): self.string = string def send(self): - """ This function send the instance of the class, i.e. the event to be sent, - to all function that listen to it + """ This function sends the instance of the class, i.e. the event + to be sent, to all function that listen to it. """ - if self.__class__.__name__ in listener: - for f, args in listener[self.__class__.__name__]: + if self.__class__.__name__ in _listener: + for f, args in _listener[self.__class__.__name__]: f(self, *args) + @classmethod + def listen(cls, *args): + def wrap(f): + if cls.__name__ not in _listener: + _listener[cls.__name__] = [] + _listener[cls.__name__].append((f, args)) + + # next step allow us to call the function itself without Event raised + def wrapped_f(*args): + f(*args) + return wrapped_f + return wrap + + +class RemoveEvent(Event): + def __init__(self, config, ui, citekey): + self.config = config + self.ui = ui + self.citekey = citekey + + if __name__ == "__main__": class TestEvent(Event): def print_one(self): print 'one' - @listen(TestEvent, 12, 15) + @TestEvent.listen(12, 15) def Display(TestEventInstance, nb1, nb2): print TestEventInstance.string, nb1, nb2 - @listen(TestEvent) + @TestEvent.listen() def Helloword(TestEventInstance): print 'Helloword' - @listen(TestEvent) + @TestEvent.listen() def PrintIt(TestEventInstance): TestEventInstance.print_one() @@ -61,7 +63,7 @@ if __name__ == "__main__": def add(self, a, b): return a + b - @listen(AddEvent) + @AddEvent.listen() def DoIt(AddEventInstance): print AddEventInstance.add(17, 25) diff --git a/papers/plugs/texnote/texnote.py b/papers/plugs/texnote/texnote.py index 7528279..36b8776 100644 --- a/papers/plugs/texnote/texnote.py +++ b/papers/plugs/texnote/texnote.py @@ -8,8 +8,8 @@ from ... import files from ...plugins import PapersPlugin from ...commands.helpers import add_references_argument, parse_reference -from ...events import listen -from ...commands.remove_cmd import RemoveEvent +from ...events import RemoveEvent + TEXNOTE_SECTION = 'texnote' TEXNOTE_SAMPLE_FILE = os.path.join(os.path.dirname(__file__), 'note_sample.tex') @@ -39,14 +39,17 @@ class TexnotePlugin(PapersPlugin): print "toto" -@listen(RemoveEvent) +@RemoveEvent.listen() def remove(rmevent): texplug = TexnotePlugin.get_instance() texplug.toto() rp = repo.Repository.from_directory(rmevent.config) paper = rp.get_paper(parse_reference(rmevent.ui, rp, rmevent.citekey)) if 'texnote' in paper.metadata: - os.remove(paper.metadata['texnote']) + try: + os.remove(paper.metadata['texnote']) + except OSError: + pass # For some reason, the texnote file didn't exist paper.metadata.pop('texnote') metapath = rp.path_to_paper_file(paper.citekey, 'meta') files.save_meta(paper.metadata, metapath) From 05ab7ec32a3dbba037ec9054f880fd9323b4726e Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Thu, 27 Jun 2013 19:41:45 +0200 Subject: [PATCH 6/8] Cleanup events. - moves tests to separate test_events file - cleanup base Event class --- papers/events.py | 51 ++-------------------------------- tests/test_events.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 tests/test_events.py diff --git a/papers/events.py b/papers/events.py index 05b27a2..efd94d6 100644 --- a/papers/events.py +++ b/papers/events.py @@ -2,12 +2,8 @@ _listener = {} class Event(object): - def __init__(self, string): - """This is an example of simple event that can be raised - Inherit from this class and redefine whatever you need, - except the send funtion - """ - self.string = string + """Base event that can be sent to listeners. + """ def send(self): """ This function sends the instance of the class, i.e. the event @@ -36,46 +32,3 @@ class RemoveEvent(Event): self.config = config self.ui = ui self.citekey = citekey - - -if __name__ == "__main__": - - class TestEvent(Event): - def print_one(self): - print 'one' - - @TestEvent.listen(12, 15) - def Display(TestEventInstance, nb1, nb2): - print TestEventInstance.string, nb1, nb2 - - @TestEvent.listen() - def Helloword(TestEventInstance): - print 'Helloword' - - @TestEvent.listen() - def PrintIt(TestEventInstance): - TestEventInstance.print_one() - - class AddEvent(Event): - def __init__(self): - pass - - def add(self, a, b): - return a + b - - @AddEvent.listen() - def DoIt(AddEventInstance): - print AddEventInstance.add(17, 25) - - # using the callback system - myevent = TestEvent('abcdefghijklmnopqrstuvwxyz') - myevent.send() # this one call three function - - addevent = AddEvent() - addevent.send() - - # but also work without the event raising system! - Display(myevent, 12, 15) - Helloword(myevent) - PrintIt(myevent) - DoIt(addevent) diff --git a/tests/test_events.py b/tests/test_events.py new file mode 100644 index 0000000..0b509a3 --- /dev/null +++ b/tests/test_events.py @@ -0,0 +1,65 @@ +from unittest import TestCase + +from papers.events import Event + + +_output = None + + +class TestEvent(Event): + def __init__(self, string): + self.string = string + + def print_one(self): + _output.append('one') + + +class AddEvent(Event): + def __init__(self): + pass + + def add(self, a, b): + return a + b + + +@TestEvent.listen(12, 15) +def display(TestEventInstance, nb1, nb2): + _output.append("%s %s %s" + % (TestEventInstance.string, nb1, nb2)) + + +@TestEvent.listen() +def hello_word(TestEventInstance): + _output.append('Helloword') + + +@TestEvent.listen() +def print_it(TestEventInstance): + TestEventInstance.print_one() + + +@AddEvent.listen() +def do_it(AddEventInstance): + _output.append(AddEventInstance.add(17, 25)) + + +class TestEvents(TestCase): + + def setUp(self): + global _output + _output = [] + + def test_listen_TestEvent(self): + # using the callback system + myevent = TestEvent('abcdefghijklmnopqrstuvwxyz') + myevent.send() # this one call three function + correct = ['abcdefghijklmnopqrstuvwxyz 12 15', + 'Helloword', + 'one'] + self.assertEquals(_output, correct) + + def test_listen_AddEvent(self): + addevent = AddEvent() + addevent.send() + correct = [42] + self.assertEquals(_output, correct) From 014234b159f64ee9ea0274c93c40727a15fcf52d Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Thu, 27 Jun 2013 20:07:05 +0200 Subject: [PATCH 7/8] FIX issue with plugins being called plugins. For some reason the installer creates a plugin package inside papers. Therefore the 'import plugins' refers to the __init__.py module in that directory. Fixed by renaming 'plugins.py' into 'plugin.py'. --- papers/papers | 6 +++--- papers/{plugins.py => plugin.py} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename papers/{plugins.py => plugin.py} (100%) diff --git a/papers/papers b/papers/papers index 45f0d0c..89c78de 100755 --- a/papers/papers +++ b/papers/papers @@ -8,7 +8,7 @@ import collections from papers.ui import UI from papers import configs from papers import commands -from papers import plugins +from papers import plugin cmds = collections.OrderedDict([ ('init', commands.init_cmd), @@ -30,8 +30,8 @@ config = configs.read_config() ui = UI(config) # Extend with plugin commands -plugins.load_plugins(config, ui, configs.get_plugins(config)) -for p in plugins.get_plugins().values(): +plugin.load_plugins(config, ui, configs.get_plugins(config)) +for p in plugin.get_plugins().values(): cmds.update(collections.OrderedDict([(p.name, p)])) # diff --git a/papers/plugins.py b/papers/plugin.py similarity index 100% rename from papers/plugins.py rename to papers/plugin.py From 9134ac62da25ee8f17ed953252f8e009b0a46ca0 Mon Sep 17 00:00:00 2001 From: Jonathan Grizou Date: Fri, 28 Jun 2013 00:29:55 +0200 Subject: [PATCH 8/8] Error when renaming plugins.py in plugin.py, in texnote the import name was not changed. --- papers/plugs/texnote/texnote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/plugs/texnote/texnote.py b/papers/plugs/texnote/texnote.py index 36b8776..da26383 100644 --- a/papers/plugs/texnote/texnote.py +++ b/papers/plugs/texnote/texnote.py @@ -5,7 +5,7 @@ import subprocess from ... import repo from ... import configs from ... import files -from ...plugins import PapersPlugin +from ...plugin import PapersPlugin from ...commands.helpers import add_references_argument, parse_reference from ...events import RemoveEvent