Merge pull request #37 from pubs/feat/new_config

feat/new_config: better, more robust, more flexible configuration
main
Fabien Benureau 9 years ago
commit 76be98a900

@ -1 +1 @@
__version__ = '0.5.0'
__version__ = '0.6.0'

@ -27,6 +27,7 @@ def generate_colors(stream, color=True, bold=True, italic=True):
colors[u'bold'] = u''
colors[u'italic'] = u''
colors[u'end'] = u''
colors[u''] = u''
if (color or bold or italic) and _color_supported(stream):
bold_flag, italic_flag = '', ''
@ -36,6 +37,8 @@ def generate_colors(stream, color=True, bold=True, italic=True):
if italic:
colors['italic'] = u'\033[3m'
italic_flag = '3;'
if bold and italic:
colors['bolditalic'] = u'\033[1;3m'
for i, name in enumerate(COLOR_LIST):
if color:
@ -69,10 +72,17 @@ def dye_err(s, color='end'):
def _nodye(s, *args, **kwargs):
return s
def setup(color=False, bold=False, italic=False):
def setup(conf):
global COLORS_OUT, COLORS_ERR
COLORS_OUT = generate_colors(sys.stdout, color=color, bold=bold, italic=italic)
COLORS_ERR = generate_colors(sys.stderr, color=color, bold=bold, italic=italic)
COLORS_OUT = generate_colors(sys.stdout, color=conf['formating']['color'],
bold=conf['formating']['bold'],
italic=conf['formating']['italics'])
COLORS_ERR = generate_colors(sys.stderr, color=conf['formating']['color'],
bold=conf['formating']['bold'],
italic=conf['formating']['italics'])
for key, value in conf['theme'].items():
COLORS_OUT[key] = COLORS_OUT.get(value, '')
COLORS_ERR[key] = COLORS_ERR.get(value, '')
# undye
undye_re = re.compile('\x1b\[[;\d]*[A-Za-z]')
@ -80,10 +90,3 @@ undye_re = re.compile('\x1b\[[;\d]*[A-Za-z]')
def undye(s):
"""Purge string s of color"""
return undye_re.sub('', s)
# colors
ok = 'green'
error = 'red'
citekey = 'purple'
filepath = 'bold'
tag = 'cyan'

@ -1,5 +1,7 @@
# core
from . import init_cmd
from . import conf_cmd
from . import add_cmd
from . import rename_cmd
from . import remove_cmd
@ -16,4 +18,3 @@ from . import import_cmd
from . import websearch_cmd
from . import edit_cmd
# from . import update_cmd

@ -1,5 +1,4 @@
from ..uis import get_ui
from ..configs import config
from .. import bibstruct
from .. import content
from .. import repo
@ -28,12 +27,12 @@ def parser(subparsers):
return parser
def bibentry_from_editor(ui, rp):
def bibentry_from_editor(conf, ui, rp):
again = True
bibstr = templates.add_bib
while again:
try:
bibstr = content.editor_input(config().edit_cmd,
bibstr = content.editor_input(conf['main']['edit_cmd'],
bibstr,
suffix='.bib')
if bibstr == templates.add_bib:
@ -57,7 +56,7 @@ def bibentry_from_editor(ui, rp):
return bibentry
def command(args):
def command(conf, args):
"""
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
:param docfile: path (no url yet) to a pdf or ps file
@ -69,12 +68,12 @@ def command(args):
tags = args.tags
citekey = args.citekey
rp = repo.Repository(config())
rp = repo.Repository(conf)
# get bibtex entry
if bibfile is None:
if args.doi is None and args.isbn is None:
bibentry = bibentry_from_editor(ui, rp)
bibentry = bibentry_from_editor(conf, ui, rp)
else:
if args.doi is not None:
bibentry_raw = apis.doi2bibtex(args.doi)
@ -124,18 +123,28 @@ def command(args):
'{}, using {} instead.').format(bib_docfile, docfile))
# create the paper
copy = args.copy
if copy is None:
copy = conf['main']['doc_add'] in ('copy', 'move')
move = args.move
if move is None:
move = conf['main']['doc_add'] == 'move'
try:
rp.push_paper(p)
if docfile is not None:
rp.push_doc(p.citekey, docfile, copy=args.copy or args.move)
if args.copy:
if args.move:
rp.push_doc(p.citekey, docfile, copy=copy or args.move)
if copy:
if move:
content.remove_file(docfile)
# elif ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye_out(docfile, 'bold'))):
# content.remove_file(docfile)
ui.message('added to pubs:\n{}'.format(pretty.paper_oneliner(p)))
if copy:
if move:
ui.message('{} was moved to the pubs repository.'.format(docfile))
else:
ui.message('{} was copied to the pubs repository.'.format(docfile))
except ValueError as v:
ui.error(v.message)
ui.exit(1)

@ -1,6 +1,5 @@
from .. import repo
from .. import color
from ..configs import config
from ..uis import get_ui
from .. import content
@ -20,7 +19,7 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
"""
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
:param docfile: path (no url yet) to a pdf or ps file
@ -28,7 +27,7 @@ def command(args):
ui = get_ui()
rp = repo.Repository(config())
rp = repo.Repository(conf)
paper = rp.pull_paper(args.citekey)
try:
@ -38,10 +37,10 @@ def command(args):
if args.move:
content.remove_file(document)
# else:
# if ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye_out(document, 'bold'))):
# if ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye_out(document, 'filepath'))):
# content.remove_file(document)
ui.message('{} attached to {}'.format(color.dye_out(document, 'bold'), color.dye_out(paper.citekey, color.citekey)))
ui.message('{} attached to {}'.format(color.dye_out(document, 'filepath'), color.dye_out(paper.citekey, 'citekey')))
except ValueError as v:
ui.error(v.message)

@ -0,0 +1,36 @@
from .. import uis
from .. import config
from .. import content
def parser(subparsers):
parser = subparsers.add_parser('conf',
help='open the configuration in an editor')
return parser
def command(conf, args):
uis.init_ui(conf)
ui = uis.get_ui()
while True:
# get modif from user
content.edit_file(conf['main']['edit_cmd'], config.get_confpath())
new_conf = config.load_conf(check=False)
try:
config.check_conf(new_conf)
ui.message('The configuration file was updated.')
break
except AssertionError: # TODO better error message
ui.error('Error reading the modified configuration file.')
options = ['edit_again', 'abort']
choice = options[ui.input_choice(
options, ['e', 'a'],
question=('Edit again or abort? If you abort, the changes will be reverted.')
)]
if choice == 'abort':
config.save_conf(conf)
ui.message('The changes have been reverted.')
break

@ -1,6 +1,6 @@
from ..paper import Paper
from .. import repo
from ..configs import config
from ..uis import get_ui
from ..endecoder import EnDecoder
from ..utils import resolve_citekey
@ -16,12 +16,12 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
ui = get_ui()
meta = args.meta
rp = repo.Repository(config())
rp = repo.Repository(conf)
citekey = resolve_citekey(rp, args.citekey, ui=ui, exit_on_fail=True)
paper = rp.pull_paper(citekey)

@ -1,7 +1,6 @@
from __future__ import print_function
from .. import repo
from ..configs import config
from ..uis import get_ui
from .. import endecoder
@ -14,14 +13,14 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
"""
"""
# :param bib_format (only 'bibtex' now)
ui = get_ui()
rp = repo.Repository(config())
rp = repo.Repository(conf)
try:
papers = [rp.pull_paper(c) for c in args.citekeys]

@ -6,7 +6,7 @@ from .. import endecoder
from .. import bibstruct
from .. import color
from ..paper import Paper
from ..configs import config
from ..uis import get_ui
from ..content import system_path, read_file
@ -58,7 +58,7 @@ def many_from_path(bibpath):
return papers
def command(args):
def command(conf, args):
"""
:param bibpath: path (no url yet) to a bibliography file
"""
@ -66,10 +66,10 @@ def command(args):
ui = get_ui()
bibpath = args.bibpath
copy = args.copy
if copy is None:
copy = config().import_copy
rp = repo.Repository(config())
copy = conf['main']['doc_add'] in ('copy', 'move')
rp = repo.Repository(conf)
# Extract papers from bib
papers = many_from_path(bibpath)
keys = args.keys or papers.keys()
@ -80,12 +80,13 @@ def command(args):
ui.error('could not load entry for citekey {}.'.format(k))
else:
rp.push_paper(p)
ui.message('{} imported'.format(color.dye_out(p.citekey, color.citekey)))
ui.message('{} imported'.format(color.dye_out(p.citekey, 'citekey')))
docfile = bibstruct.extract_docfile(p.bibdata)
if docfile is None:
ui.warning("no file for {}.".format(p.citekey))
else:
rp.push_doc(p.citekey, docfile, copy=args.copy)
rp.push_doc(p.citekey, docfile, copy=copy)
#FIXME should move the file if configured to do so.
except KeyError:
ui.error('no entry found for citekey {}.'.format(k))
except IOError as e:

@ -2,25 +2,25 @@
import os
from ..configs import config
from ..uis import get_ui
from .. import color
from ..repo import Repository
from ..content import system_path, check_directory
from .. import config
def parser(subparsers):
parser = subparsers.add_parser('init',
help="initialize the pubs directory")
parser.add_argument('-p', '--pubsdir', default=None,
help='path to pubs directory (if none, ~/.ubs is used)')
help='directory where to put the pubs repository (if none, ~/.pubs is used)')
parser.add_argument('-d', '--docsdir', default='docsdir://',
help=('path to document directory (if not specified, documents will'
'be stored in /path/to/pubsdir/doc/)'))
return parser
def command(args):
def command(conf, args):
"""Create a .pubs directory"""
ui = get_ui()
@ -34,13 +34,15 @@ def command(args):
if check_directory(pubsdir, fail=False) and len(os.listdir(pubsdir)) > 0:
ui.error('directory {} is not empty.'.format(
color.dye_err(pubsdir, color.filepath)))
color.dye_err(pubsdir, 'filepath')))
ui.exit()
ui.message('Initializing pubs in {}'.format(color.dye_out(pubsdir, color.filepath)))
ui.message('Initializing pubs in {}'.format(color.dye_out(pubsdir, 'filepath')))
config().pubsdir = pubsdir
config().docsdir = docsdir
config().save()
conf['main']['pubsdir'] = pubsdir
conf['main']['docsdir'] = docsdir
conf['main']['open_cmd'] = config.default_open_cmd()
conf['main']['edit_cmd'] = config.default_edit_cmd()
config.save_conf(conf)
Repository(config(), create=True)
Repository(conf, create=True)

@ -3,7 +3,6 @@ from datetime import datetime
from .. import repo
from .. import pretty
from .. import bibstruct
from ..configs import config
from ..uis import get_ui
@ -28,7 +27,7 @@ def parser(subparsers):
help='list only pubs without attached documents.')
parser.add_argument('query', nargs='*',
help='Paper query (e.g. "year: 2000" or "tags: math")')
help='Paper query ("author:Einstein", "title:learning", "year:2000" or "tags:math")')
return parser
@ -36,9 +35,9 @@ def date_added(p):
return p.added or datetime(1, 1, 1)
def command(args):
def command(conf, args):
ui = get_ui()
rp = repo.Repository(config())
rp = repo.Repository(conf)
papers = filter(lambda p: filter_paper(p, args.query,
case_sensitive=args.case_sensitive),
rp.all_papers())

@ -1,8 +1,8 @@
from .. import repo
from .. import content
from ..configs import config
from ..uis import get_ui
def parser(subparsers):
parser = subparsers.add_parser('note',
help='edit the note attached to a paper')
@ -11,17 +11,16 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
"""
"""
ui = get_ui()
rp = repo.Repository(config())
rp = repo.Repository(conf)
if not rp.databroker.exists(args.citekey):
ui.error("citekey {} not found".format(args.citekey))
ui.exit(1)
notepath = rp.databroker.real_notepath(args.citekey)
content.edit_file(config().edit_cmd, notepath, temporary=False)
content.edit_file(conf['main']['edit_cmd'], notepath, temporary=False)

@ -1,7 +1,7 @@
import subprocess
from .. import repo
from ..configs import config
from ..uis import get_ui
from .. import color
from ..content import system_path
@ -17,21 +17,23 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
ui = get_ui()
with_command = args.with_command
rp = repo.Repository(config())
rp = repo.Repository(conf)
citekey = resolve_citekey(rp, args.citekey, ui=ui, exit_on_fail=True)
paper = rp.pull_paper(citekey)
if with_command is None:
with_command = config().open_cmd
with_command = conf['main']['open_cmd']
if with_command is None: # default in conf have not been changed
pass # TODO platform specific
if paper.docpath is None:
ui.error('No document associated with the entry {}.'.format(
color.dye_err(citekey, color.citekey)))
color.dye_err(citekey, 'citekey')))
ui.exit()
try:
@ -39,7 +41,7 @@ def command(args):
cmd = with_command.split()
cmd.append(docpath)
subprocess.Popen(cmd)
ui.message('{} opened.'.format(color.dye_out(docpath, color.filepath)))
ui.message('{} opened.'.format(color.dye_out(docpath, 'filepath')))
except OSError:
ui.error("Command does not exist: %s." % with_command)
ui.exit(127)

@ -1,6 +1,5 @@
from .. import repo
from .. import color
from ..configs import config
from ..uis import get_ui
@ -13,21 +12,21 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
ui = get_ui()
force = args.force
rp = repo.Repository(config())
rp = repo.Repository(conf)
if force is None:
are_you_sure = (("Are you sure you want to delete paper(s) [{}]"
" (this will also delete associated documents)?")
.format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))
.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys])))
sure = ui.input_yn(question=are_you_sure, default='n')
if force or sure:
for c in args.citekeys:
rp.remove_paper(c)
ui.message('The paper(s) [{}] were removed'.format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))
ui.message('The paper(s) [{}] were removed'.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys])))
# FIXME: print should check that removal proceeded well.
else:
ui.message('The paper(s) [{}] were *not* removed'.format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))
ui.message('The paper(s) [{}] were *not* removed'.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys])))

@ -1,5 +1,4 @@
from ..uis import get_ui
from ..configs import config
from .. import bibstruct
from .. import content
from .. import repo
@ -14,14 +13,14 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
"""
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
:param docfile: path (no url yet) to a pdf or ps file
"""
ui = get_ui()
rp = repo.Repository(config())
rp = repo.Repository(conf)
paper = rp.pull_paper(args.citekey)
rp.rename_paper(paper, args.new_citekey)

@ -20,7 +20,6 @@ The different use cases are :
import re
from ..repo import Repository
from ..configs import config
from ..uis import get_ui
from .. import pretty
from .. import color
@ -70,23 +69,22 @@ def _tag_groups(tags):
minus_tags.append(tag[1:])
return set(plus_tags), set(minus_tags)
def command(args):
def command(conf, args):
"""Add, remove and show tags"""
ui = get_ui()
citekeyOrTag = args.citekeyOrTag
tags = args.tags
rp = Repository(config())
rp = Repository(conf)
if citekeyOrTag is None:
ui.message(color.dye_out(' '.join(sorted(rp.get_tags())), color.tag))
ui.message(color.dye_out(' '.join(sorted(rp.get_tags())), 'tag'))
else:
if rp.databroker.exists(citekeyOrTag):
p = rp.pull_paper(citekeyOrTag)
if tags is None:
ui.message(color.dye_out(' '.join(sorted(p.tags)), color.tag))
ui.message(color.dye_out(' '.join(sorted(p.tags)), 'tag'))
else:
add_tags, remove_tags = _tag_groups(_parse_tag_seq(tags))
for tag in add_tags:

@ -1,37 +0,0 @@
import sys
from .. import repo
from .. import color
from ..configs import config
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(args):
ui = get_ui()
code_version = __version__
repo_version = int(config().version)
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(config().papers_dir, color.filepath))
sure = ui.input_yn(question=msg, default='n')
if not sure:
sys.exit(0)
# config().version = repo_version
# config().save()

@ -11,7 +11,7 @@ def parser(subparsers):
return parser
def command(args):
def command(conf, args):
ui = get_ui()
search_string = args.search_string

@ -0,0 +1,2 @@
from .conf import get_confpath, load_default_conf, load_conf, save_conf, check_conf
from .conf import default_open_cmd, default_edit_cmd

@ -0,0 +1,102 @@
import os
import platform
import shutil
import configobj
import validate
from .spec import configspec
DFT_CONFIG_PATH = os.path.expanduser('~/.pubsrc')
def load_default_conf():
"""Load the default configuration"""
default_conf = configobj.ConfigObj(configspec=configspec)
validator = validate.Validator()
default_conf.validate(validator, copy=True)
return default_conf
def get_confpath(verify=True):
"""Return the configuration filepath
If verify is True, verify that the file exists and exit with an error if not.
"""
confpath = DFT_CONFIG_PATH
if 'PUBSCONF' in os.environ:
confpath = os.path.abspath(os.path.expanduser(os.environ['PUBSCONF']))
if verify:
if not os.path.isfile(confpath):
from .. import uis
ui = uis.get_ui()
ui.error('configuration file not found at `{}`'.format(confpath))
ui.exit(error_code=1)
return confpath
def check_conf(conf):
"""Type check 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 configuration"""
if path is None:
path = get_confpath(verify=True)
with open(path, 'rb') as f:
conf = configobj.ConfigObj(f.readlines(), configspec=configspec)
if check:
check_conf(conf)
conf.filename = path
return conf
def save_conf(conf, path=None):
"""Save the configuration."""
if path is not None:
conf.filename = path
elif conf.filename is None:
conf.filename = get_confpath(verify=False)
with open(conf.filename, 'wb') as f:
conf.write(outfile=f)
def default_open_cmd():
"""Chooses the default command to open documents"""
if platform.system() == 'Darwin':
return 'open'
elif platform.system() == 'Linux':
return 'xdg-open'
elif platform.system() == 'Windows':
return 'start'
else:
return None
def which(cmd):
try:
return shutil.which(cmd) # available in python 3.3
except AttributeError:
for path in ['.'] + os.environ["PATH"].split(os.pathsep):
filepath = os.path.join(path.strip('"'), cmd)
if os.path.isfile(path) and os.access(path, os.X_OK):
return filepath
return None
def default_edit_cmd():
"""Find an available editor"""
if 'EDITOR' in os.environ:
return os.environ['EDITOR']
elif platform.system() == 'Darwin' or 'Linux':
for editor in ['vim', 'nano', 'emacs', 'vi']:
if which(editor) is not None:
return editor
elif platform.system() == 'Windows':
return 'Notepad.exe | Out-Null' # wait for notepad to close
else:
return None

@ -0,0 +1,86 @@
from .. import __version__
configspec = """
[main]
# Where the pubs repository files (bibtex, metadata, notes) are located
pubsdir = string(default='~/pubs')
# Where the documents files are located (default: $(pubsdir)/doc/)
docsdir = string(default="docsdir://")
# Specify if a document should be copied or moved in the docdir, or only
# linked when adding a publication.
doc_add = option('copy', 'move', 'link', default='move')
# the command to use when opening document files
open_cmd = string(default=None)
# which editor to use when editing bibtex files.
# if using a graphical editor, use the --wait or --block option, i.e.:
# "atom --wait"
# "kate --block"
edit_cmd = string(default=None)
[formating]
# Enable bold formatting, if the terminal supports it.
bold = boolean(default=True)
# Enable italics, if the terminal supports it.
italics = boolean(default=True)
# Enable colors, if the terminal supports it.
color = boolean(default=True)
[theme]
# Here you can define the color theme used by pubs, if enabled in the
# 'formating' section. Predefined theme are available at:
# https://github.com/pubs/pubs/blob/master/theme.md
# Available colors are: 'black', 'red', 'green', 'yellow', 'blue', 'purple',
# 'cyan', and 'grey'. Bold colors are available by prefixing 'b' in front of
# the color name ('bblack', 'bred', etc.), italic colors by prefixing 'i',
# and bold italic by prefixing 'bi'. Finally, 'bold', 'italic' and
# 'bolditalic' can be used to apply formatting without changing the color.
# For no color, use an empty string ''
# messages
ok = string(default='green')
warning = string(default='yellow')
error = string(default='red')
# ui elements
filepath = string(default='bold')
citekey = string(default='purple')
tag = string(default='cyan')
# bibliographic fields
author = string(default='bold')
title = string(default='')
publisher = string(default='italic')
year = string(default='bold')
volume = string(default='bold')
pages = string(default='')
[plugins]
# comma-separated list of the plugins to load
active = list(default=list())
[[alias]]
# new subcommands can be defined, e.g.:
# print = open --with lp
# evince = open --with evince
# shell commands can also be defined, by prefixing them with a bang `!`, e.g:
# count = !pubs list -k | wc -l
[internal]
# The version of this configuration file. Do not edit.
version = string(min=5, default='{}')
""".format(__version__).split('\n')

@ -1,112 +0,0 @@
import os
import sys
import collections
from .p3 import configparser, ConfigParser, _read_config
from .content import check_file, _open
from . import __version__
# constant stuff (DFT = DEFAULT)
MAIN_SECTION = 'pubs'
DFT_CONFIG_PATH = os.path.expanduser('~/.pubsrc')
try:
DFT_EDIT_CMD = os.environ['EDITOR']
except KeyError:
DFT_EDIT_CMD = 'vi'
DFT_PLUGINS = ''
DFT_CONFIG = collections.OrderedDict([
('pubsdir', os.path.expanduser('~/.pubs')),
('docsdir', ''),
('import_copy', True),
('import_move', False),
('color', True),
('version', __version__),
('version_warning', True),
('open_cmd', 'open'),
('edit_cmd', DFT_EDIT_CMD),
('plugins', DFT_PLUGINS)
])
BOOLEANS = {'import_copy', 'import_move', 'color', 'version_warning'}
# package-shared config that can be accessed using :
# from configs import config
_config = None
def config(section=MAIN_SECTION):
if _config is None:
raise ValueError('not config instanciated yet')
_config._section = section
return _config
class Config(object):
def __init__(self, **kwargs):
object.__setattr__(self, '_section', MAIN_SECTION) # active section
object.__setattr__(self, '_cfg', ConfigParser())
self._cfg.add_section(self._section)
for name, value in DFT_CONFIG.items():
self._cfg.set(self._section, name, str(value))
for name, value in kwargs.items():
self.__setattr__(name, value)
def as_global(self):
global _config
_config = self
def load(self, path=DFT_CONFIG_PATH):
if not check_file(path, fail=False):
raise IOError(("The configuration file {} does not exist."
" Did you run 'pubs init' ?").format(path))
b_flag = ''
if sys.version_info[0] == 2: # HACK, FIXME please
b_flag = 'b'
with _open(path, 'r{}+'.format(b_flag)) as f:
_read_config(self._cfg, f)
return self
def save(self, path=DFT_CONFIG_PATH):
b_flag = ''
if sys.version_info[0] == 2: # HACK, FIXME please
b_flag = 'b'
with _open(path, 'w{}+'.format(b_flag)) as f:
self._cfg.write(f)
def __setattr__(self, name, value):
if name in ('_cfg', '_section'):
object.__setattr__(self, name, value)
else:
if type(value) is bool:
BOOLEANS.add(name)
self._cfg.set(self._section, name, str(value))
def __getattr__(self, name):
value = self._cfg.get(self._section, name)
if name in BOOLEANS:
value = str2bool(value)
return value
def get(self, name, default=None):
try:
return self.__getattr__(name)
except (configparser.NoOptionError, configparser.NoSectionError):
return default
def items(self):
for name, value in self._cfg.items(self._section):
if name in BOOLEANS:
value = str2bool(value)
yield name, value
def str2bool(s):
return str(s).lower() in ('yes', 'true', 't', 'y', '1')

@ -28,13 +28,13 @@ class PapersPlugin(object):
raise RuntimeError("{} instance not created".format(cls.__name__))
def load_plugins(ui, names):
def load_plugins(conf, ui):
"""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:
for name in conf['plugins']['active']:
modname = '%s.%s.%s.%s' % ('pubs', PLUGIN_NAMESPACE, name, name)
try:
namespace = importlib.import_module(modname)
@ -49,7 +49,7 @@ def load_plugins(ui, names):
if isinstance(obj, type) and issubclass(obj, PapersPlugin) \
and obj != PapersPlugin:
_classes.append(obj)
_instances[obj] = obj()
_instances[obj] = obj(conf)
def get_plugins():

@ -2,7 +2,6 @@ import subprocess
import shlex
from ...plugins import PapersPlugin
from ...configs import config
from ...pubs_cmd import execute
@ -16,9 +15,10 @@ class Alias(object):
self.parser = parser
p = parser.add_parser(self.name, help='user defined command')
p.add_argument('arguments', nargs='*',
help="arguments to be passed to the user defined command")
help="arguments to be passed to the user defined command")
return p
def run(self, args):
def command(self, conf, args):
raise NotImplementedError
@classmethod
@ -35,7 +35,7 @@ class CommandAlias(Alias):
- other arguments are passed to the command
"""
def run(self, args):
def command(self, conf, args):
raw_args = ([args.prog]
+ shlex.split(self.definition
+ ' '
@ -45,7 +45,7 @@ class CommandAlias(Alias):
class ShellAlias(Alias):
def run(self, args):
def command(self, conf, args):
"""Uses a shell function so that arguments can be used in the command
as shell arguments.
"""
@ -59,12 +59,14 @@ class AliasPlugin(PapersPlugin):
name = 'alias'
def __init__(self):
def __init__(self, conf):
self.aliases = []
for name, definition in config('alias').items():
self.aliases.append(Alias.create_alias(name, definition))
def get_commands(self, parser):
for a in self.aliases:
a.parser(parser)
return [(a.name, a.run) for a in self.aliases]
if 'alias' in conf['plugins']:
for name, definition in conf['plugins']['alias'].items():
self.aliases.append(Alias.create_alias(name, definition))
def update_parser(self, subparsers):
"""Add subcommand to the provided subparser"""
for alias in self.aliases:
alias_parser = alias.parser(subparsers)
alias_parser.set_defaults(func=alias.command)

@ -33,10 +33,11 @@ def bib_oneliner(bibdata):
journal = ' ' + bibdata.get('booktitle', '')
return u'{authors} \"{title}\"{journal}{year}'.format(
authors=color.dye_out(authors, 'bold'),
title=bibdata.get('title', ''),
journal=color.dye_out(journal, 'italic'),
year=' ({})'.format(bibdata['year']) if 'year' in bibdata else '',
authors=color.dye_out(authors, 'author'),
title=color.dye_out(bibdata.get('title', ''), 'title'),
journal=color.dye_out(journal, 'publisher'),
year=' ({})'.format(color.dye_out(bibdata['year'], 'year'))
if 'year' in bibdata else ''
)
@ -55,7 +56,7 @@ def paper_oneliner(p, citekey_only=False):
else:
bibdesc = bib_oneliner(p.bibdata)
tags = '' if len(p.tags) == 0 else '| {}'.format(
','.join(color.dye_out(t, color.tag) for t in sorted(p.tags)))
','.join(color.dye_out(t, 'tag') for t in sorted(p.tags)))
return u'[{citekey}] {descr} {tags}'.format(
citekey=color.dye_out(p.citekey, 'purple'),
citekey=color.dye_out(p.citekey, 'citekey'),
descr=bibdesc, tags=tags)

@ -1,92 +1,85 @@
import sys
import argparse
import collections
from . import uis
from . import configs
from . import config
from . import commands
from . import update
from . import plugins
from .__init__ import __version__
CORE_CMDS = collections.OrderedDict([
('init', commands.init_cmd),
('add', commands.add_cmd),
('rename', commands.rename_cmd),
('remove', commands.remove_cmd),
('list', commands.list_cmd),
('attach', commands.attach_cmd),
('open', commands.open_cmd),
('tag', commands.tag_cmd),
('note', commands.note_cmd),
('export', commands.export_cmd),
('import', commands.import_cmd),
('websearch', commands.websearch_cmd),
('edit', commands.edit_cmd),
# ('update', commands.update_cmd),
])
def _update_check(config, ui):
if config.version_warning:
code_version = __version__.split('.')
if len(config.version) == 1: # support for deprecated version scheme.
config.version = '0.{}.0'.format(config.version)
repo_version = config.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. (use version_warning in you pubsrc '
'to bypass this error)')
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()
('init', commands.init_cmd),
('conf', commands.conf_cmd),
('add', commands.add_cmd),
('rename', commands.rename_cmd),
('remove', commands.remove_cmd),
('list', commands.list_cmd),
('attach', commands.attach_cmd),
('open', commands.open_cmd),
('tag', commands.tag_cmd),
('note', commands.note_cmd),
('export', commands.export_cmd),
('import', commands.import_cmd),
('websearch', commands.websearch_cmd),
('edit', commands.edit_cmd),
])
def execute(raw_args=sys.argv):
# loading config
config = configs.Config()
if len(raw_args) > 1 and raw_args[1] != 'init':
conf_parser = argparse.ArgumentParser(prog="pubs", add_help=False)
conf_parser.add_argument("-c", "--config", help="path to config file",
type=str, metavar="FILE")
#conf_parser.add_argument("-u", "--update", help="update config if needed",
# default=False, action='store_true')
top_args, remaining_args = conf_parser.parse_known_args(raw_args[1:])
if top_args.config:
conf_path = top_args.config
else:
conf_path = config.get_confpath(verify=False) # will be checked on load
# Loading config
if len(remaining_args) > 0 and remaining_args[0] != 'init':
try:
config.load()
conf = config.load_conf(path=conf_path, check=False)
if update.update_check(conf, path=conf.filename):
# an update happened, reload conf.
conf = config.load_conf(path=conf_path, check=False)
config.check_conf(conf)
except IOError as e:
print('error: {}'.format(str(e)))
sys.exit()
config.as_global()
else:
conf = config.load_default_conf()
conf.filename = conf_path
uis.init_ui(config)
uis.init_ui(conf)
ui = uis.get_ui()
_update_check(config, ui)
parser = argparse.ArgumentParser(description="research papers repository")
parser = argparse.ArgumentParser(description="research papers repository",
prog="pubs", add_help=True)
parser.add_argument('--version', action='version', version=__version__)
subparsers = parser.add_subparsers(title="valid commands", dest="command")
subparsers.required = True
cmd_funcs = collections.OrderedDict()
# Populate the parser with core commands
for cmd_name, cmd_mod in CORE_CMDS.items():
cmd_mod.parser(subparsers)
cmd_funcs[cmd_name] = cmd_mod.command
cmd_parser = cmd_mod.parser(subparsers)
cmd_parser.set_defaults(func=cmd_mod.command)
# Extend with plugin commands
plugins.load_plugins(ui, config.plugins.split())
plugins.load_plugins(conf, ui)
for p in plugins.get_plugins().values():
cmd_funcs.update(p.get_commands(subparsers))
args = parser.parse_args(raw_args[1:])
args.prog = parser.prog # Hack: there might be a better way...
cmd = args.command
del args.command
p.update_parser(subparsers)
cmd_funcs[cmd](args)
# Parse and run appropriate command
args = parser.parse_args(remaining_args)
args.prog = "pubs" # FIXME?
args.func(conf, args)

@ -22,10 +22,10 @@ class InvalidReference(Exception):
class Repository(object):
def __init__(self, config, create=False):
self.config = config
def __init__(self, conf, create=False):
self.conf = conf
self._citekeys = None
self.databroker = DataCache(self.config.pubsdir, create=create)
self.databroker = DataCache(self.conf['main']['pubsdir'], create=create)
@property
def citekeys(self):
@ -133,7 +133,7 @@ class Repository(object):
def push_doc(self, citekey, docfile, copy=None):
p = self.pull_paper(citekey)
if copy is None:
copy = self.config.import_copy
copy = self.conf['main']['doc_add'] in ('copy', 'move')
if copy:
docfile = self.databroker.add_doc(citekey, docfile)
else:

@ -6,6 +6,7 @@ import codecs
from .content import editor_input
from . import color
from . import config
from .p3 import _get_raw_stdout, _get_raw_stderr, input, ustr
@ -30,7 +31,7 @@ def _get_encoding(conf):
def get_ui():
if _ui is None:
return PrintUI() # no editor support. (#FIXME?)
return PrintUI(config.load_default_conf()) # no editor support. (#FIXME?)
return _ui
@ -41,16 +42,12 @@ def init_ui(conf):
class PrintUI(object):
def __init__(self, conf=None):
def __init__(self, conf):
"""
:param conf: if None, conservative default values are used.
Useful to instanciate the UI before parsing the config file.
"""
if conf is None:
color.setup()
else:
color.setup(color=True, bold=True, italic=True)
# color.setup(color=conf.color, bold=conf.bold, italic=conf.italic)
color.setup(conf)
self.encoding = _get_encoding(conf)
self._stdout = codecs.getwriter(self.encoding)(_get_raw_stdout(),
errors='replace')
@ -63,11 +60,11 @@ class PrintUI(object):
def warning(self, message, **kwargs):
kwargs['file'] = self._stderr
print('{}: {}'.format(color.dye_err('warning', 'yellow'), message), **kwargs)
print('{}: {}'.format(color.dye_err('warning', 'warning'), message), **kwargs)
def error(self, message, **kwargs):
kwargs['file'] = self._stderr
print('{}: {}'.format(color.dye_err('error', 'red'), message), **kwargs)
print('{}: {}'.format(color.dye_err('error', 'error'), message), **kwargs)
def exit(self, error_code=1):
sys.exit(error_code)
@ -79,7 +76,7 @@ class InputUI(PrintUI):
def __init__(self, conf):
super(InputUI, self).__init__(conf)
self.editor = conf.edit_cmd
self.editor = conf['main']['edit_cmd']
def input(self):
try:
@ -101,14 +98,12 @@ class InputUI(PrintUI):
:returns: int
the index of the chosen option
"""
char_color = 'bold'
option_chars = [s[0] for s in options]
displayed_chars = [c.upper() if i == default else c
for i, c in enumerate(option_chars)]
if len(set(option_chars)) != len(option_chars): # duplicate chars, char choices are deactivated. #FIXME: should only deactivate ambiguous chars
option_chars = []
char_color = color.end
option_str = '/'.join(["{}{}".format(color.dye_out(c, 'bold'), s[1:])
for c, s in zip(displayed_chars, options)])

@ -0,0 +1,106 @@
import shutil
import io
from . import config
from . import uis
from . import color
from .__init__ import __version__
def update_check(conf, path=None):
"""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, path=path)
return False
def update(conf, code_version, repo_version, path=None):
"""Runs an update if necessary, and return True in that case."""
if path is None:
path = config.get_confpath()
if repo_version == ['0', '5', '0']: # we need to update
default_conf = config.load_default_conf()
for key in ['pubsdir', 'docsdir', 'edit_cmd', 'open_cmd']:
if key in conf['pubs']:
default_conf['main'][key] = conf['pubs'][key]
if conf.get('import_move'):
default_conf['main']['add_doc'] = 'move'
elif conf.get('import_copy'):
default_conf['main']['add_doc'] = 'copy'
else:
default_conf['main']['add_doc'] = 'link'
backup_path = path + '.old'
shutil.move(path, backup_path)
config.save_conf(default_conf)
uis.init_ui(default_conf)
ui = uis.get_ui()
ui.warning(
'Your configuration file has been updated. '
'Your old configuration has been moved to `{}`. '.format(color.dye_out(backup_path, 'filepath')) +
'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
# continuous update while configuration is stabilizing
if repo_version == ['0', '6', '0'] and repo_version < code_version:
default_conf = config.load_default_conf()
for section_name, section in conf.items():
for key, value in section.items():
try:
default_conf[section_name][key]
default_conf[section_name][key] = value
except KeyError:
pass
# we don't update plugins
for section_name, section in conf['plugins'].items():
default_conf[section_name]['plugins'][section_name] = section
# comparing potential changes
with open(path, 'r') as f:
old_conf_text = f.read()
new_conf_text = io.BytesIO()
default_conf.write(outfile=new_conf_text)
if new_conf_text.getvalue() != old_conf_text:
backup_path = path + '.old'
shutil.move(path, backup_path)
default_conf.filename = path
config.save_conf(default_conf)
uis.init_ui(default_conf)
ui = uis.get_ui()
ui.warning('Your configuration file has been updated.\n'
'Your old configuration has been moved to `{}`.'.format(color.dye_out(backup_path, 'filepath')))
return True
return False

@ -9,13 +9,13 @@ def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True):
citekeys = repo.citekeys_from_prefix(citekey)
if len(citekeys) == 0:
if ui is not None:
ui.error("no citekey named or beginning with '{}'".format(color.dye_out(citekey, color.citekey)))
ui.error("no citekey named or beginning with '{}'".format(color.dye_out(citekey, 'citekey')))
if exit_on_fail:
ui.exit()
elif len(citekeys) == 1:
if citekeys[0] != citekey:
if ui is not None:
ui.warning("provided citekey '{}' has been autocompleted into '{}'".format(color.dye_out(citekey, color.citekey), color.dye_out(citekeys[0], color.citekey)))
ui.warning("provided citekey '{}' has been autocompleted into '{}'".format(color.dye_out(citekey, 'citekey'), color.dye_out(citekeys[0], 'citekey')))
citekey = citekeys[0]
elif citekey not in citekeys:
if ui is not None:

@ -13,8 +13,8 @@ Pubs is built with the following principles in mind:
**Notice:** pubs is still in early development and cannot be considered as stable.
Getting started
---------------
## Getting started
Create your library (by default, goes to '~/.pubs/').
pubs init
@ -36,8 +36,8 @@ or an ISBN (dashes are ignored):
pubs add -I 978-0822324669 -d article.pdf
References always up-to-date
----------------------------
## References always up-to-date
If you use latex, you can automatize references, by creating a bash script with:
#!/bin/bash
@ -52,28 +52,28 @@ This ensure that your reference file is always up-to-date; you can cite a paper
and then add `\cite{Loeb_2012}` in your manuscript. After running the bash script, the citation will correctly appear in your compiled pdf.
Customization
-------------
Pubs is designed to interact well with your command line tool chain. You can add custom commands to pubs by defining aliases in your config file. Here are a few examples.
## Customization
Pubs is designed to interact well with your command line tool chain. You can add custom commands to pubs by defining aliases in your config file.
[alias]
print = open -w lp
[[alias]]
evince = open --with evince
count = !pubs list -k | wc -l
For more advanced functionalities, pubs also support plugins. Actually *alias* is itself a plugin!
The first command defines a new subcommand: `pubs open -w lp` will be executed when `pubs print` is typed.
The second starts with a bang: `!`, and is treated as a shell command.
## Requirements
Requirements
------------
- python >= 2.7 or >= 3.3
- [dateutil](http://labix.org/python-dateutil)
- [pyYaml](http://pyyaml.org) (will be deprecated soon)
- [bibtexparser](https://github.com/sciunto/python-bibtexparser) >= 0.6.1
Authors
-------
## Authors
- [Fabien Benureau](http://fabien.benureau.com)
- Olivier Mangin
- Jonathan Grizou
- [Fabien Benureau](http://fabien.benureau.com)
- Olivier Mangin
- Jonathan Grizou
- Arnold Sykosch

@ -2,7 +2,7 @@
from setuptools import setup, find_packages
VERSION = '0.5.0'
VERSION = '0.6.0'
setup(
name = 'pubs',
@ -13,10 +13,13 @@ setup(
url = 'https://github.com/pubs/pubs',
description = 'command-line scientific bibliography manager',
packages = ['pubs', 'pubs.commands', 'pubs.templates', 'pubs.plugs'],
packages = ['pubs', 'pubs.config',
'pubs.commands',
'pubs.templates',
'pubs.plugs', 'pubs.plugs.alias'],
scripts = ['pubs/pubs'],
install_requires = ['pyyaml', 'bibtexparser', 'python-dateutil', 'requests',
install_requires = ['pyyaml', 'bibtexparser', 'python-dateutil', 'requests', 'configobj',
'beautifulsoup4'], # to be made optional?
classifiers=[

@ -103,6 +103,8 @@ class FakeIO(object):
fakefs_stringio = self.fake_open.Call(*args, **kwargs)
return UnicodeStringIOWrapper(fakefs_stringio)
BytesIO = real_io.BytesIO
StringIO = real_io.StringIO
def create_fake_fs(module_list):

@ -5,7 +5,7 @@ from pubs import color
def perf_color():
s = str(list(range(1000)))
for _ in range(5000000):
color.dye(s, color.red)
color.dye_out(s, 'red')
if __name__ == '__main__':

@ -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__':

@ -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)

@ -0,0 +1,84 @@
import unittest
import dotdot
import pubs
from pubs import config
from pubs.plugs.alias.alias import (Alias, AliasPlugin, CommandAlias,
ShellAlias)
def to_args(arg_str):
o = lambda: None # Dirty hack
o.prog = 'pubs'
o.arguments = arg_str.split(' ')
return o
class FakeExecuter(object):
called = None
executed = None
def call(self, obj, shell=None):
self.called = obj
def execute(self, obj):
self.executed = obj
class AliasTestCase(unittest.TestCase):
def setUp(self):
self.subprocess = FakeExecuter()
pubs.plugs.alias.alias.subprocess = self.subprocess
self.cmd_execute = FakeExecuter()
pubs.plugs.alias.alias.execute = self.cmd_execute.execute
def testAlias(self):
alias = Alias.create_alias('print', 'open -w lpppp')
alias.command(None, to_args('CiteKey'))
self.assertIsNone(self.subprocess.called)
self.assertEqual(self.cmd_execute.executed,
['pubs', 'open', '-w', 'lpppp', 'CiteKey'])
def testShellAlias(self):
"""This actually just test that subprocess.call is called.
"""
alias = Alias.create_alias('count', '!pubs list -k | wc -l')
alias.command(None, to_args(''))
self.assertIsNone(self.cmd_execute.executed)
self.assertIsNotNone(self.subprocess.called)
class AliasPluginTestCase(unittest.TestCase):
def setUp(self):
self.conf = config.load_default_conf()
self.conf['plugins']['active'] = ['alias']
def testAliasPluginCreated(self):
self.plugin = AliasPlugin(self.conf)
def testAliasPluginOneCommnand(self):
self.conf['plugins']['alias'] = {'print': 'open -w lpppp'}
self.plugin = AliasPlugin(self.conf)
self.assertEqual(len(self.plugin.aliases), 1)
self.assertEqual(type(self.plugin.aliases[0]), CommandAlias)
self.assertEqual(self.plugin.aliases[0].name, 'print')
self.assertEqual(self.plugin.aliases[0].definition, 'open -w lpppp')
def testAliasPluginOneShell(self):
self.conf['plugins']['alias'] = {'count': '!pubs list -k | wc -l'}
self.plugin = AliasPlugin(self.conf)
self.assertEqual(len(self.plugin.aliases), 1)
self.assertEqual(type(self.plugin.aliases[0]), ShellAlias)
self.assertEqual(self.plugin.aliases[0].name, 'count')
self.assertEqual(self.plugin.aliases[0].definition,
'pubs list -k | wc -l')
def testAliasPluginTwoCommnands(self):
self.conf['plugins']['alias'] = {'print': 'open -w lpppp',
'count': '!pubs list -k | wc -l'}
self.plugin = AliasPlugin(self.conf)
self.assertEqual(len(self.plugin.aliases), 2)

@ -5,7 +5,7 @@ import os
import dotdot
import fake_env
from pubs import endecoder, pretty, color
from pubs import endecoder, pretty, color, config
from str_fixtures import bibtex_raw0
@ -13,7 +13,8 @@ from str_fixtures import bibtex_raw0
class TestPretty(unittest.TestCase):
def setUp(self):
color.setup()
conf = config.load_default_conf()
color.setup(conf)
def test_oneliner(self):
decoder = endecoder.EnDecoder()

@ -7,14 +7,15 @@ import fixtures
from pubs.repo import Repository, _base27, CiteKeyCollision, InvalidReference
from pubs.paper import Paper
from pubs import configs
from pubs import config
class TestRepo(fake_env.TestFakeFs):
def setUp(self):
super(TestRepo, self).setUp()
self.repo = Repository(configs.Config(), create=True)
default_conf = config.load_default_conf()
self.repo = Repository(default_conf, create=True)
self.repo.push_paper(Paper.from_bibentry(fixtures.turing_bibentry))

@ -7,8 +7,9 @@ import os
import dotdot
import fake_env
from pubs import pubs_cmd
from pubs import color, content, filebroker, uis, p3, endecoder, configs
from pubs import pubs_cmd, update, color, content, filebroker, uis, p3, endecoder
from pubs.config import conf
import configobj
import str_fixtures
import fixtures
@ -17,7 +18,7 @@ from pubs.commands import init_cmd, import_cmd
# makes the tests very noisy
messagePUT=False
PRINT_OUTPUT=False
CAPTURE_OUTPUT=True
@ -56,8 +57,9 @@ class CommandTestCase(unittest.TestCase):
maxDiff = 1000000
def setUp(self):
self.fs = fake_env.create_fake_fs([content, filebroker, configs, init_cmd, import_cmd])
self.fs = fake_env.create_fake_fs([content, filebroker, conf, init_cmd, import_cmd, configobj, update])
self.default_pubs_dir = self.fs['os'].path.expanduser('~/.pubs')
self.default_conf_path = self.fs['os'].path.expanduser('~/.pubsrc')
def execute_cmds(self, cmds, capture_output=CAPTURE_OUTPUT):
""" Execute a list of commands, and capture their output
@ -102,12 +104,12 @@ class CommandTestCase(unittest.TestCase):
except fake_env.FakeInput.UnexpectedInput:
self.fail('Unexpected input asked by command: {}.'.format(
actual_cmd))
if messagePUT:
if PRINT_OUTPUT:
print(outs)
return outs
def tearDown(self):
fake_env.unset_fake_fs([content, filebroker, configs, init_cmd, import_cmd])
fake_env.unset_fake_fs([content, filebroker, conf, init_cmd, import_cmd, configobj])
class DataCommandTestCase(CommandTestCase):
@ -323,7 +325,8 @@ class TestUsecase(DataCommandTestCase):
def test_first(self):
correct = ['Initializing pubs in /paper_first\n',
'added to pubs:\n[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n',
'added to pubs:\n[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n'
'data/pagerank.pdf was copied to the pubs repository.\n',
'[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n',
'\n',
'',
@ -507,8 +510,21 @@ class TestUsecase(DataCommandTestCase):
'pubs attach --move Page99 data/pagerank.pdf'
]
self.execute_cmds(cmds)
self.assertTrue(self.fs['os'].path.isfile(self.default_conf_path))
self.assertFalse(self.fs['os'].path.exists('/data/pagerank.pdf'))
def test_alternate_config(self):
alt_conf = self.fs['os'].path.expanduser('~/.alt_conf')
cmds = ['pubs -c ' + alt_conf + ' init',
'pubs --config ' + alt_conf + ' import data/ Page99',
'pubs list -c ' + alt_conf
]
outs = self.execute_cmds(cmds)
# check if pubs works as expected
self.assertEqual(1 + 1, len(outs[-1].split('\n')))
# check whether we actually changed the config file
self.assertFalse(self.fs['os'].path.isfile(self.default_conf_path))
self.assertTrue(self.fs['os'].path.isfile(alt_conf))
if __name__ == '__main__':
unittest.main()

Loading…
Cancel
Save