new print ui, more robust colors

main
Fabien Benureau 9 years ago
parent d5a4fcf73c
commit cce9406670

@ -54,7 +54,6 @@ def generate_citekey(bibdata):
:raise ValueError: if no author nor editor is defined. :raise ValueError: if no author nor editor is defined.
""" """
citekey, entry = get_entry(bibdata) citekey, entry = get_entry(bibdata)
author_key = 'author' if 'author' in entry else 'editor' author_key = 'author' if 'author' in entry else 'editor'
try: try:
first_author = entry[author_key][0] first_author = entry[author_key][0]

@ -1,47 +1,90 @@
""" """
Small code to handle colored text Small code to handle colored text
""" """
import sys
import re import re
bold = '\033[1m' def _color_supported(stream):
end = '\033[0m' """Returns True is the stream supports colors"""
if sys.platform == 'win32' and 'ANSICON' not in os.environ:
black = '\033[0;30m' return False
red = '\033[0;31m' if hasattr(stream, 'isatty') and stream.isatty(): # we have a tty
green = '\033[0;32m' try:
yellow = '\033[0;33m' import curses
blue = '\033[0;34m' curses.setupterm()
purple = '\033[0;35m' return curses.tigetnum('colors') >= 8
cyan = '\033[0;36m' except Exception: # not picky.
grey = '\033[0;37m' return False
return False
ok = green
error = red COLOR_LIST = [u'black', u'red', u'green', u'yellow', u'blue', u'purple', u'cyan', u'grey']
normal = grey
citekey = purple def generate_colors(stream, color=True, bold=True, italic=True):
filepath = bold colors = {name: u'' for name in COLOR_LIST}
tag = cyan colors.update({u'b' +name: u'' for name in COLOR_LIST})
colors.update({u'i' +name: u'' for name in COLOR_LIST})
def dye(s, color=end, bold=False): colors.update({u'bi'+name: u'' for name in COLOR_LIST})
assert color[0] == '\033' colors[u'bold'] = u''
colors[u'italic'] = u''
colors[u'end'] = u''
if (color or bold or italic) and _color_supported(stream):
bold_flag, italic_flag = '', ''
if bold:
colors['bold'] = u'\x1b[1m'
bold_flag = '1;'
if italic:
colors['italic'] = u'\x1b[3m'
italic_flag = '3;'
for i, name in enumerate(COLOR_LIST):
if color:
color_flag = '3{}'.format(name)
colors[name] = u'\x1b[{}m'.format(color_flag)
colors.update({u'b'+name: u'\x1b[{}3{}m'.format(bold_flag, i) for i, name in enumerate(COLOR_LIST)})
colors.update({u'i'+name: u'\x1b[{}3{}m'.format(italic_flag, i) for i, name in enumerate(COLOR_LIST)})
colors.update({u'bi'+name: u'\x1b[{}3{}m'.format(bold_flag, italic_flag, i) for i, name in enumerate(COLOR_LIST)})
else:
if bold: if bold:
color = '\033[1' + color[3:] colors.update({u'b'+name: u'\x1b[{}m'.format(bold_flag, i) for i, name in enumerate(COLOR_LIST)})
return color + s + end if italic:
colors.update({u'i'+name: u'\x1b[{}m'.format(italic_flag, i) for i, name in enumerate(COLOR_LIST)})
if bold or italic:
colors.update({u'bi'+name: u'\x1b[{}m'.format(bold_flag, italic_flag, i) for i, name in enumerate(COLOR_LIST)})
if color or bold or italic:
colors[u'end'] = u'\x1b[0m'
return colors
COLORS_OUT = generate_colors(sys.stdout, color=True, bold=True, italic=True)
COLORS_ERR = generate_colors(sys.stderr, color=True, bold=True, italic=True)
def dye_out(s, color='end'):
return '{}{}{}'.format(COLORS_OUT[color], s, COLORS_OUT['end'])
def dye_err(s, color='end'):
return '{}{}{}'.format(COLORS_ERR[color], s, COLORS_OUT['end'])
_dye = dye
def _nodye(s, *args, **kwargs): def _nodye(s, *args, **kwargs):
return s return s
def setup(enable = True): def setup(color=True, bold=True, italic=True):
global dye global COLORS_OUT, COLORS_ERR
if enable: COLORS_OUT = generate_colors(sys.stdout, color=color, bold=color, italic=color)
dye = _dye COLORS_ERR = generate_colors(sys.stderr, color=color, bold=color, italic=color)
else:
dye = _nodye
# undye
undye_re = re.compile('\x1b\[[;\d]*[A-Za-z]') undye_re = re.compile('\x1b\[[;\d]*[A-Za-z]')
def undye(s): def undye(s):
"""Purge string s of color""" """Purge string s of color"""
return undye_re.sub('', s) return undye_re.sub('', s)
# colors
ok = 'green'
error = 'red'
citekey = 'purple'
filepath = 'bold'
tag = 'cyan'

@ -128,13 +128,14 @@ def command(args):
try: try:
rp.push_paper(p) rp.push_paper(p)
if docfile is not None: if docfile is not None:
rp.push_doc(p.citekey, docfile, copy=args.copy) rp.push_doc(p.citekey, docfile, copy=args.copy or args.move)
if args.copy: if args.copy:
if args.move: if args.move:
content.remove_file(docfile) content.remove_file(docfile)
elif ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye(docfile, color.bold))): # elif ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye_out(docfile, 'bold'))):
content.remove_file(docfile) # content.remove_file(docfile)
ui.print_('added to pubs:\n{}'.format(pretty.paper_oneliner(p)))
ui.print_out('added to pubs:\n{}'.format(pretty.paper_oneliner(p)))
except ValueError as v: except ValueError as v:
ui.error(v.message) ui.error(v.message)
ui.exit(1) ui.exit(1)

@ -34,10 +34,14 @@ def command(args):
try: try:
document = args.document document = args.document
rp.push_doc(paper.citekey, document, copy=args.copy) rp.push_doc(paper.citekey, document, copy=args.copy)
if args.copy and args.move: if args.copy:
if args.move:
content.remove_file(document) 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'))):
# content.remove_file(document)
ui.print_('{} attached to {}'.format(color.dye(document, color.bold), color.dye(paper.citekey, color.citekey))) ui.print_out('{} attached to {}'.format(color.dye_out(document, 'bold'), color.dye_out(paper.citekey, color.citekey)))
except ValueError as v: except ValueError as v:
ui.error(v.message) ui.error(v.message)

@ -36,4 +36,4 @@ def command(args):
bib[p.citekey] = p.bibdata bib[p.citekey] = p.bibdata
exporter = endecoder.EnDecoder() exporter = endecoder.EnDecoder()
bibdata_raw = exporter.encode_bibdata(bib) bibdata_raw = exporter.encode_bibdata(bib)
ui.print_(bibdata_raw) ui.print_out(bibdata_raw)

@ -80,7 +80,7 @@ def command(args):
ui.error('could not load entry for citekey {}.'.format(k)) ui.error('could not load entry for citekey {}.'.format(k))
else: else:
rp.push_paper(p) rp.push_paper(p)
ui.print_('{} imported'.format(color.dye(p.citekey, color.cyan))) ui.print_out('{} imported'.format(color.dye_out(p.citekey, color.citekey)))
docfile = bibstruct.extract_docfile(p.bibdata) docfile = bibstruct.extract_docfile(p.bibdata)
if docfile is None: if docfile is None:
ui.warning("no file for {}.".format(p.citekey)) ui.warning("no file for {}.".format(p.citekey))

@ -34,11 +34,10 @@ def command(args):
if check_directory(pubsdir, fail=False) and len(os.listdir(pubsdir)) > 0: if check_directory(pubsdir, fail=False) and len(os.listdir(pubsdir)) > 0:
ui.error('directory {} is not empty.'.format( ui.error('directory {} is not empty.'.format(
color.dye(pubsdir, color.filepath))) color.dye_err(pubsdir, color.filepath)))
ui.exit() ui.exit()
ui.print_('Initializing pubs in {}'.format( ui.print_out('Initializing pubs in {}'.format(color.dye_out(pubsdir, color.filepath)))
color.dye(pubsdir, color.filepath)))
config().pubsdir = pubsdir config().pubsdir = pubsdir
config().docsdir = docsdir config().docsdir = docsdir

@ -49,7 +49,7 @@ def command(args):
else: else:
papers = sorted(papers, key=date_added) papers = sorted(papers, key=date_added)
if len(papers) > 0: if len(papers) > 0:
ui.print_('\n'.join( ui.print_out('\n'.join(
pretty.paper_oneliner(p, citekey_only=args.citekeys) pretty.paper_oneliner(p, citekey_only=args.citekeys)
for p in papers)) for p in papers))

@ -31,7 +31,7 @@ def command(args):
if paper.docpath is None: if paper.docpath is None:
ui.error('No document associated with the entry {}.'.format( ui.error('No document associated with the entry {}.'.format(
color.dye(citekey, color.citekey))) color.dye_err(citekey, color.citekey)))
ui.exit() ui.exit()
try: try:
@ -39,7 +39,7 @@ def command(args):
cmd = with_command.split() cmd = with_command.split()
cmd.append(docpath) cmd.append(docpath)
subprocess.Popen(cmd) subprocess.Popen(cmd)
ui.print_('{} opened.'.format(color.dye(docpath, color.filepath))) ui.print_out('{} opened.'.format(color.dye(docpath, color.filepath)))
except OSError: except OSError:
ui.error("Command does not exist: %s." % with_command) ui.error("Command does not exist: %s." % with_command)
ui.exit(127) ui.exit(127)

@ -22,12 +22,12 @@ def command(args):
if force is None: if force is None:
are_you_sure = (("Are you sure you want to delete paper(s) [{}]" are_you_sure = (("Are you sure you want to delete paper(s) [{}]"
" (this will also delete associated documents)?") " (this will also delete associated documents)?")
.format(', '.join([color.dye(c, color.citekey) for c in args.citekeys]))) .format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))
sure = ui.input_yn(question=are_you_sure, default='n') sure = ui.input_yn(question=are_you_sure, default='n')
if force or sure: if force or sure:
for c in args.citekeys: for c in args.citekeys:
rp.remove_paper(c) rp.remove_paper(c)
ui.print_('The paper(s) [{}] were removed'.format(', '.join([color.dye(c, color.citekey) for c in args.citekeys]))) ui.print_out('The paper(s) [{}] were removed'.format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))
# FIXME: print should check that removal proceeded well. # FIXME: print should check that removal proceeded well.
else: else:
ui.print_('The paper(s) [{}] were not removed'.format(', '.join([color.dye(c, color.citekey) for c in args.citekeys]))) ui.print_out('The paper(s) [{}] were *not* removed'.format(', '.join([color.dye_out(c, color.citekey) for c in args.citekeys])))

@ -83,13 +83,13 @@ def command(args):
rp = Repository(config()) rp = Repository(config())
if citekeyOrTag is None: if citekeyOrTag is None:
ui.print_(color.dye(' '.join(sorted(rp.get_tags())), color=color.blue)) ui.print_out(color.dye_out(' '.join(sorted(rp.get_tags())), color.tag))
else: else:
if rp.databroker.exists(citekeyOrTag): if rp.databroker.exists(citekeyOrTag):
p = rp.pull_paper(citekeyOrTag) p = rp.pull_paper(citekeyOrTag)
if tags == []: if tags == []:
ui.print_(color.dye(' '.join(sorted(p.tags)), ui.print_out(color.dye_out(' '.join(sorted(p.tags)),
color=color.blue)) color.tag))
else: else:
add_tags, remove_tags = _tag_groups(_parse_tags(tags)) add_tags, remove_tags = _tag_groups(_parse_tags(tags))
for tag in add_tags: for tag in add_tags:
@ -108,5 +108,5 @@ def command(args):
len(p.tags.intersection(excluded)) == 0): len(p.tags.intersection(excluded)) == 0):
papers_list.append(p) papers_list.append(p)
ui.print_('\n'.join(pretty.paper_oneliner(p) ui.print_out('\n'.join(pretty.paper_oneliner(p)
for p in papers_list)) for p in papers_list))

@ -19,15 +19,15 @@ def command(args):
repo_version = int(config().version) repo_version = int(config().version)
if repo_version == code_version: if repo_version == code_version:
ui.print_('Your pubs repository is up-to-date.') ui.print_out('Your pubs repository is up-to-date.')
sys.exit(0) sys.exit(0)
elif repo_version > code_version: elif repo_version > code_version:
ui.print_('Your repository was generated with an newer version of pubs.\n' ui.print_out('Your repository was generated with an newer version of pubs.\n'
'You should not use pubs until you install the newest version.') 'You should not use pubs until you install the newest version.')
sys.exit(0) sys.exit(0)
else: else:
msg = ("You should backup the pubs directory {} before continuing." msg = ("You should backup the pubs directory {} before continuing."
"Continue ?").format(color.dye(config().papers_dir, color.filepath)) "Continue ?").format(color.dye_out(config().papers_dir, color.filepath))
sure = ui.input_yn(question=msg, default='n') sure = ui.input_yn(question=msg, default='n')
if not sure: if not sure:
sys.exit(0) sys.exit(0)

@ -102,7 +102,7 @@ def check_content(path):
def _get_byte_url_content(path, ui=None): def _get_byte_url_content(path, ui=None):
if ui is not None: if ui is not None:
ui.print_(u'dowloading {}'.format(path)) ui.print_out(u'dowloading {}'.format(path))
response = urlopen(path) response = urlopen(path)
return response.read() return response.read()

@ -13,6 +13,8 @@ if sys.version_info[0] == 2:
# for test_usecase. # for test_usecase.
def _get_raw_stdout(): def _get_raw_stdout():
return sys.stdout return sys.stdout
def _get_raw_stderr():
return sys.stderr
ustr = unicode ustr = unicode
uchr = unichr uchr = unichr
@ -37,6 +39,13 @@ else:
from urllib.request import urlopen from urllib.request import urlopen
from http.client import HTTPConnection from http.client import HTTPConnection
# The following has to be a function so that it can be mocked
# for test_usecase.
def _get_raw_stdout():
return sys.stdout.buffer
def _get_raw_stderr():
return sys.stderr.buffer
def _fake_stdio(): def _fake_stdio():
return io.TextIOWrapper(io.BytesIO()) # Only for tests to capture std{out,err} return io.TextIOWrapper(io.BytesIO()) # Only for tests to capture std{out,err}
@ -45,10 +54,6 @@ else:
stdio.seek(0) stdio.seek(0)
return stdio.read() return stdio.read()
# The following has to be a function so that it can be mocked
# for test_usecase.
def _get_raw_stdout():
return sys.stdout.buffer
configparser = configparser configparser = configparser
input = input input = input

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

@ -46,7 +46,7 @@ def _update_check(config, ui):
'to bypass this error)') 'to bypass this error)')
sys.exit() sys.exit()
elif repo_version < code_version: elif repo_version < code_version:
ui.print_( ui.print_out(
'warning: your repository version (v{})'.format(repo_version) 'warning: your repository version (v{})'.format(repo_version)
+ 'must be updated to version {}.\n'.format(code_version) + 'must be updated to version {}.\n'.format(code_version)
+ "run 'pubs update'.") + "run 'pubs update'.")

@ -6,7 +6,7 @@ import codecs
from .content import editor_input from .content import editor_input
from . import color from . import color
from .p3 import _get_raw_stdout, input from .p3 import _get_raw_stdout, _get_raw_stderr, input
# package-shared ui that can be accessed using : # package-shared ui that can be accessed using :
@ -23,7 +23,7 @@ def _get_encoding(config):
enc = locale.getdefaultlocale()[1] enc = locale.getdefaultlocale()[1]
except ValueError: except ValueError:
pass # Keep default pass # Keep default
return config.get('terminal-encoding', enc or 'utf8') return config.get('terminal-encoding', enc or 'utf-8')
def get_ui(): def get_ui():
@ -34,34 +34,60 @@ def get_ui():
def init_ui(config): def init_ui(config):
global _ui global _ui
_ui = UI(config) _ui = InputUI(config)
class UI: class PrintUI(object):
"""UI class. Stores configuration parameters and system information.
"""
def __init__(self, config): def __init__(self, config):
color.setup(config.color) color.setup(config.color)
self.editor = config.edit_cmd
self.encoding = _get_encoding(config) self.encoding = _get_encoding(config)
self._stdout = codecs.getwriter(self.encoding)(_get_raw_stdout(), self._stdout = codecs.getwriter(self.encoding)(_get_raw_stdout(),
errors='replace') errors='replace')
self._stderr = codecs.getwriter(self.encoding)(_get_raw_stderr(),
errors='replace')
def print_(self, *strings, **kwargs): def print_out(self, *strings, **kwargs):
"""Like print, but rather than raising an error when a character """Like print, but rather than raising an error when a character
is not in the terminal's encoding's character set, just silently is not in the terminal's encoding's character set, just silently
replaces it. replaces it.
""" """
print(' '.join(strings), file=self._stdout, **kwargs) print(' '.join(strings), file=self._stdout, **kwargs)
def print_err(self, *strings, **kwargs):
"""Like print, but rather than raising an error when a character
is not in the terminal's encoding's character set, just silently
replaces it.
"""
print(' '.join(strings), file=self._stderr, **kwargs)
def error(self, message):
self.print_err('{}: {}'.format(color.dye_err('error', 'red'), message))
def warning(self, message):
self.print_err("%s: %s" % (color.dye_err('warning', 'yellow'), message))
class InputUI(PrintUI):
"""UI class. Stores configuration parameters and system information.
"""
def __init__(self, config):
super(InputUI, self).__init__(config)
self.editor = config.edit_cmd
def exit(self, error_code=1):
sys.exit(error_code)
def input(self): def input(self):
try: try:
data = input() data = input()
except EOFError: except EOFError:
self.error('Standard input ended while waiting for answer.') self.error(u'Standard input ended while waiting for answer.')
self.exit(1) self.exit(1)
return data return data.decode('utf-8')
def input_choice_ng(self, options, option_chars=None, default=None, question=''): def input_choice_ng(self, options, option_chars=None, default=None, question=''):
"""Ask the user to chose between a set of options. The iser is asked """Ask the user to chose between a set of options. The iser is asked
@ -75,7 +101,7 @@ class UI:
:returns: int :returns: int
the index of the chosen option the index of the chosen option
""" """
char_color = color.bold char_color = 'bold'
option_chars = [s[0] for s in options] option_chars = [s[0] for s in options]
displayed_chars = [c.upper() if i == default else c displayed_chars = [c.upper() if i == default else c
for i, c in enumerate(option_chars)] for i, c in enumerate(option_chars)]
@ -84,10 +110,10 @@ class UI:
option_chars = [] option_chars = []
char_color = color.end char_color = color.end
option_str = '/'.join(["{}{}".format(color.dye(c, color.bold), s[1:]) option_str = '/'.join(["{}{}".format(color.dye_out(c, 'bold'), s[1:])
for c, s in zip(displayed_chars, options)]) for c, s in zip(displayed_chars, options)])
self.print_('{} {}: '.format(question, option_str), end='') self.print_out('{} {}: '.format(question, option_str), end='')
while True: while True:
answer = self.input() answer = self.input()
if answer is None or answer == '': if answer is None or answer == '':
@ -101,8 +127,7 @@ class UI:
return option_chars.index(answer.lower()) return option_chars.index(answer.lower())
except ValueError: except ValueError:
pass pass
self.print_('Incorrect option.', option_str) self.print_out('Incorrect option.', option_str)
def input_choice(self, options, option_chars, default=None, question=''): def input_choice(self, options, option_chars, default=None, question=''):
@ -122,9 +147,9 @@ class UI:
""" """
displayed_chars = [s.upper() if i == default else s displayed_chars = [s.upper() if i == default else s
for i, s in enumerate(option_chars)] for i, s in enumerate(option_chars)]
option_str = ', '.join(["[%s]%s" % (color.dye(c, color.cyan), o) option_str = ', '.join(["[%s]%s" % (color.dye_out(c, 'cyan'), o)
for c, o in zip(displayed_chars, options)]) for c, o in zip(displayed_chars, options)])
self.print_(question, option_str) self.print_out(question, option_str)
while True: while True:
answer = self.input() answer = self.input()
if answer is None or answer == '': if answer is None or answer == '':
@ -135,21 +160,12 @@ class UI:
return option_chars.index(answer.lower()) return option_chars.index(answer.lower())
except ValueError: except ValueError:
pass pass
self.print_('Incorrect option.', option_str) self.print_out('Incorrect option.', option_str)
def input_yn(self, question='', default='y'): def input_yn(self, question='', default='y'):
d = 0 if default in (True, 'y', 'yes') else 1 d = 0 if default in (True, 'y', 'yes') else 1
answer = self.input_choice_ng(['yes', 'no'], default=d, question=question) answer = self.input_choice_ng(['yes', 'no'], default=d, question=question)
return [True, False][answer] return [True, False][answer]
def exit(self, error_code=1):
sys.exit(error_code)
def error(self, message):
self.print_("%s: %s" % (color.dye('error', color.red), message))
def warning(self, message):
self.print_("%s: %s" % (color.dye('warning', color.yellow), message))
def editor_input(self, initial="", suffix='.tmp'): def editor_input(self, initial="", suffix='.tmp'):
return editor_input(self.editor, initial=initial, suffix=suffix) return editor_input(self.editor, initial=initial, suffix=suffix)

@ -24,9 +24,7 @@ def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True):
citekey)) citekey))
for c in citekeys: for c in citekeys:
p = repo.pull_paper(c) p = repo.pull_paper(c)
ui.print_(u' {}'.format(pretty.paper_oneliner(p))) ui.print_out(u' {}'.format(pretty.paper_oneliner(p)))
if exit_on_fail: if exit_on_fail:
ui.exit() ui.exit()
return citekey return citekey

@ -11,7 +11,8 @@ setup(
url = '', url = '',
description = 'command-line scientific bibliography manager', description = 'command-line scientific bibliography manager',
packages = find_packages(), #['pubs', 'pubs.commands', 'pubs.templates', 'pubs.plugs'], #packages = find_packages(), #['pubs', 'pubs.commands', 'pubs.templates', 'pubs.plugs'],
packages = ['pubs', 'pubs.commands', 'pubs.templates', 'pubs.plugs'],
scripts = ['pubs/pubs'], scripts = ['pubs/pubs'],
install_requires = ['pyyaml', 'bibtexparser', 'python-dateutil', 'requests'], install_requires = ['pyyaml', 'bibtexparser', 'python-dateutil', 'requests'],

@ -1,3 +1,6 @@
# Adjusting paths. # Adjusting paths.
import os, sys import os, sys
sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../..'))) sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../..')))
import logging
logging.disable(logging.CRITICAL)

@ -11,14 +11,16 @@ coder = endecoder.EnDecoder()
franny_bib = """@article{Franny1961, franny_bib = """@article{Franny1961,
author = "Salinger, J. D.", author = "Salinger, J. D.",
title = "Franny and Zooey", title = "Franny and Zooey",
year = "1961"} year = "1961",
}
""" """
doe_bib = """ doe_bib = """
@article{Doe2013, @article{Doe2013,
author = "Doe, John", author = "Doe, John",
title = "Nice Title", title = "Nice Title",
year = "2013"} year = "2013",
}
""" """
dummy_metadata = {'docfile': 'docsdir://hop.la', 'tags': set(['a', 'b'])} dummy_metadata = {'docfile': 'docsdir://hop.la', 'tags': set(['a', 'b'])}

@ -29,7 +29,7 @@ bibtex_raw0 = """@techreport{
note = "Previous number = SIDL-WP-1999-0120", note = "Previous number = SIDL-WP-1999-0120",
year = "1999", year = "1999",
type = "Technical Report", type = "Technical Report",
institution = "Stanford InfoLab" institution = "Stanford InfoLab",
} }
""" """

@ -13,7 +13,7 @@ from str_fixtures import bibtex_raw0
class TestPretty(unittest.TestCase): class TestPretty(unittest.TestCase):
def setUp(self): def setUp(self):
color.setup(enable=False) color.setup()
def test_oneliner(self): def test_oneliner(self):
decoder = endecoder.EnDecoder() decoder = endecoder.EnDecoder()

@ -53,7 +53,7 @@ class TestFakeInput(unittest.TestCase):
class CommandTestCase(unittest.TestCase): class CommandTestCase(unittest.TestCase):
"""Abstract TestCase intializing the fake filesystem.""" """Abstract TestCase intializing the fake filesystem."""
maxDiff = None maxDiff = 1000000
def setUp(self): 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, configs, init_cmd, import_cmd])
@ -62,42 +62,43 @@ class CommandTestCase(unittest.TestCase):
def execute_cmds(self, cmds, capture_output=CAPTURE_OUTPUT): def execute_cmds(self, cmds, capture_output=CAPTURE_OUTPUT):
""" Execute a list of commands, and capture their output """ Execute a list of commands, and capture their output
A command can be a string, or a tuple of size 2 or 3. A command can be a string, or a tuple of size 2, 3 or 4.
In the latter case, the command is : In the latter case, the command is :
1. a string reprensenting the command to execute 1. a string reprensenting the command to execute
2. the user inputs to feed to the command during execution 2. the user inputs to feed to the command during execution
3. the output expected, verified with assertEqual. Always captures 3. the expected output on stdout, verified with assertEqual.
output in this case. 4. the expected output on stderr, verified with assertEqual.
""" """
outs = [] outs = []
for cmd in cmds: for cmd in cmds:
inputs = [] inputs = []
output = None expected_out, expected_err = None, None
actual_cmd = cmd actual_cmd = cmd
current_capture_output = capture_output
if not isinstance(cmd, p3.ustr): if not isinstance(cmd, p3.ustr):
actual_cmd = cmd[0] actual_cmd = cmd[0]
if len(cmd) == 2: # Inputs provided if len(cmd) == 2: # Inputs provided
inputs = cmd[1] inputs = cmd[1]
if len(cmd) == 3: # Expected output provided if len(cmd) == 3: # Expected output provided
current_capture_output = True capture_output = True
output = cmd[2] expected_out = color.undye(cmd[2])
if len(cmd) == 4: # Expected error output provided
expected_err = color.undye(cmd[3])
# Always set fake input: test should not ask unexpected user input # Always set fake input: test should not ask unexpected user input
input = fake_env.FakeInput(inputs, [content, uis, p3]) input = fake_env.FakeInput(inputs, [content, uis, p3])
input.as_global() input.as_global()
try: try:
if current_capture_output: if capture_output:
_, stdout, stderr = fake_env.redirect(pubs_cmd.execute)( _, stdout, stderr = fake_env.redirect(pubs_cmd.execute)(
actual_cmd.split()) actual_cmd.split())
self.assertEqual(stderr, '')
actual_out = color.undye(stdout) actual_out = color.undye(stdout)
if output is not None: actual_err = color.undye(stderr)
correct_out = color.undye(output) if expected_out is not None:
self.assertEqual(actual_out, correct_out) self.assertEqual(actual_out, expected_out)
if expected_err is not None:
self.assertEqual(actual_err, expected_err)
outs.append(color.undye(actual_out)) outs.append(color.undye(actual_out))
else: else:
pubs_cmd.execute(cmd.split()) pubs_cmd.execute(actual_cmd.split())
except fake_env.FakeInput.UnexpectedInput: except fake_env.FakeInput.UnexpectedInput:
self.fail('Unexpected input asked by command: {}.'.format( self.fail('Unexpected input asked by command: {}.'.format(
actual_cmd)) actual_cmd))
@ -252,7 +253,7 @@ class TestUsecase(DataCommandTestCase):
def test_first(self): def test_first(self):
correct = ['Initializing pubs in /paper_first\n', correct = ['Initializing pubs in /paper_first\n',
'[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \nwas added to pubs.\n', 'added to pubs:\n[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n',
'[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n', '[Page99] Page, Lawrence et al. "The PageRank Citation Ranking: Bringing Order to the Web." (1999) \n',
'\n', '\n',
'', '',

Loading…
Cancel
Save