Merge branch 'master' into fix/pyfakefs37
This commit is contained in:
commit
3d76501a5c
68
changelog.md
Normal file
68
changelog.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## Current master
|
||||
|
||||
[Full Changelog](https://github.com/pubs/pubs/compare/v0.7.0...master)
|
||||
|
||||
|
||||
### Implemented enhancements
|
||||
|
||||
- Better dialog after editing paper [(#142)](https://github.com/pubs/pubs/issues/142)
|
||||
|
||||
- Add a command to open urls ([#139](https://github.com/pubs/pubs/issues/139) by [ksunden](https://github.com/ksunden))
|
||||
|
||||
- More robust cache on version change [(#138)](https://github.com/pubs/pubs/issues/138)
|
||||
|
||||
- Allow utf8 citekeys [(#133)](https://github.com/pubs/pubs/issues/133)
|
||||
|
||||
- Adds tag list completion in `pubs add -t ` [(#130)](https://github.com/pubs/pubs/issues/130)
|
||||
|
||||
- Wider Travis coverage ([#107](https://github.com/pubs/pubs/issues/107) and [#108](https://github.com/pubs/pubs/issues/108))
|
||||
|
||||
- Uses bibtexparser bwriter instead of internal encoder and adds `--ignore-fields` option to export. [(#106)](https://github.com/pubs/pubs/issues/106)
|
||||
|
||||
- Configurable alias descriptions ([#104](https://github.com/pubs/pubs/issues/104) by [wflynny](https://github.com/wflynny))
|
||||
|
||||
- Support year ranges in query [(#102)](https://github.com/pubs/pubs/issues/102)
|
||||
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
- [[#95]](https://github.com/pubs/pubs/issues/95) Error message when editor is missing [(#141)](https://github.com/pubs/pubs/issues/141)
|
||||
|
||||
- Fixes tests for printing help on `--help` and without argument. [(#137)](https://github.com/pubs/pubs/issues/137)
|
||||
|
||||
- [[#126]](https://github.com/pubs/pubs/issues/126) Removes journal customization [(#127)](https://github.com/pubs/pubs/issues/127)
|
||||
|
||||
- Fixes Travis failure on installing python3 for OSX [(#125)](https://github.com/pubs/pubs/issues/125)
|
||||
|
||||
- [[#119]](https://github.com/pubs/pubs/issues/119) Removes link and DOI customization. [(#124)](https://github.com/pubs/pubs/issues/124)
|
||||
|
||||
- [[#122]](https://github.com/pubs/pubs/issues/122) Fixes common strings [(#123)](https://github.com/pubs/pubs/issues/123)
|
||||
|
||||
- [[#28]](https://github.com/pubs/pubs/issues/28) allow utf8 in citekeys [(#120)](https://github.com/pubs/pubs/issues/120)
|
||||
|
||||
- Fixes field orders to use 'url' and fixes broken test. [(#118)](https://github.com/pubs/pubs/issues/118)
|
||||
|
||||
- [[#25]](https://github.com/pubs/pubs/issues/25) Fix bibtex testcase [(#117)](https://github.com/pubs/pubs/issues/117)
|
||||
|
||||
- [[#103]](https://github.com/pubs/pubs/issues/103) Fixes unicode comparison [(#116)](https://github.com/pubs/pubs/issues/116)
|
||||
|
||||
- [[#95]](https://github.com/pubs/pubs/issues/95) robust handling of DOIs ([#105](https://github.com/pubs/pubs/issues/105) by [wflynny](https://github.com/wflynny))
|
||||
|
||||
- [[#99]](https://github.com/pubs/pubs/issues/99) Print help when no subcommand is provided ([#100](https://github.com/pubs/pubs/issues/100) by [wflynny](https://github.com/wflynny))
|
||||
|
||||
- Fix defaults not used in config. [(#97)](https://github.com/pubs/pubs/issues/97)
|
||||
|
||||
- Fixes content not read from urls because of call to `os.abspath` [(#96)](https://github.com/pubs/pubs/issues/96)
|
||||
|
||||
- [[#93]](https://github.com/pubs/pubs/issues/93) actually save the modifications on `edit -m`. [(#94)](https://github.com/pubs/pubs/issues/94)
|
||||
|
||||
- [[#88]](https://github.com/pubs/pubs/issues/88) Adds proper escaping for
|
||||
arguments in alias plugin. [(#91)](https://github.com/pubs/pubs/issues/91)
|
||||
|
||||
|
||||
## [v0.7.0](https://github.com/pubs/pubs/compare/v0.6.0...v0.7.0) (2017-08-06)
|
||||
|
||||
[Full Changelog](https://github.com/pubs/pubs/compare/v0.6.0...v0.7.0)
|
@ -54,10 +54,17 @@ def command(conf, args):
|
||||
new_paper = Paper(paper.citekey, paper.bibdata,
|
||||
metadata=content)
|
||||
rp.push_paper(new_paper, overwrite=True, event=False)
|
||||
ui.info(('The metadata of paper `{}` was successfully '
|
||||
'edited.'.format(citekey)))
|
||||
else:
|
||||
new_paper = Paper.from_bibentry(content,
|
||||
metadata=paper.metadata)
|
||||
rp.rename_paper(new_paper, old_citekey=paper.citekey)
|
||||
if rp.rename_paper(new_paper, old_citekey=paper.citekey):
|
||||
ui.info(('Paper `{}` was successfully edited and renamed '
|
||||
'as `{}`.'.format(citekey, new_paper.citekey)))
|
||||
else:
|
||||
ui.info(('Paper `{}` was successfully edited.'.format(
|
||||
citekey)))
|
||||
break
|
||||
|
||||
except repo.CiteKeyCollision:
|
||||
@ -71,6 +78,7 @@ def command(conf, args):
|
||||
break
|
||||
elif choice == 'overwrite':
|
||||
paper = rp.push_paper(paper, overwrite=True)
|
||||
ui.info(('Paper `{}` was overwritten.'.format(citekey)))
|
||||
break
|
||||
# else edit again
|
||||
# Also handle malformed bibtex and metadata
|
||||
|
@ -141,6 +141,13 @@ class Repository(object):
|
||||
pass
|
||||
|
||||
def rename_paper(self, paper, new_citekey=None, old_citekey=None):
|
||||
"""Move a paper from a citekey to another one.
|
||||
|
||||
Even if the new and old citekey are the same, the paper instance is
|
||||
pushed to disk.
|
||||
|
||||
:return: True if a rename happened, False if not.
|
||||
"""
|
||||
if old_citekey is None:
|
||||
old_citekey = paper.citekey
|
||||
if new_citekey is None:
|
||||
@ -149,6 +156,7 @@ class Repository(object):
|
||||
# check if new_citekey is not the same as paper.citekey
|
||||
if old_citekey == new_citekey:
|
||||
self.push_paper(paper, overwrite=True, event=False)
|
||||
return False
|
||||
else:
|
||||
# check if new_citekey does not exists
|
||||
if new_citekey in self:
|
||||
@ -171,6 +179,7 @@ class Repository(object):
|
||||
self.remove_paper(old_citekey, event=False)
|
||||
# send event
|
||||
events.RenameEvent(paper, old_citekey).send()
|
||||
return True
|
||||
|
||||
def push_doc(self, citekey, docfile, copy=None):
|
||||
p = self.pull_paper(citekey)
|
||||
|
78
pubs/uis.py
78
pubs/uis.py
@ -11,11 +11,11 @@ import subprocess
|
||||
from . import color
|
||||
from . import config
|
||||
from .p3 import _get_raw_stdout, _get_raw_stderr, input, ustr
|
||||
from .content import check_file, read_text_file, write_file, system_path
|
||||
from .content import check_file, read_text_file, write_file
|
||||
|
||||
|
||||
DEBUG = False # unhandled exceptions traces are printed
|
||||
DEBUG_ALL_TRACES = False # handled exceptions traces are printed
|
||||
DEBUG_ALL_TRACES = False # handled exceptions traces are printed
|
||||
# package-shared ui that can be accessed using :
|
||||
# from uis import get_ui
|
||||
# ui = get_ui()
|
||||
@ -38,40 +38,14 @@ def _get_encoding(conf):
|
||||
def _get_local_editor():
|
||||
"""Get the editor from environment variables.
|
||||
|
||||
Use nano as a default.
|
||||
Use vi as a default.
|
||||
"""
|
||||
return os.environ.get('EDITOR', 'nano')
|
||||
|
||||
|
||||
def _editor_input(editor, initial='', suffix='.tmp'):
|
||||
"""Use an editor to get input"""
|
||||
str_initial = initial.encode('utf-8') # TODO: make it a configuration item
|
||||
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
||||
tfile_name = temp_file.name
|
||||
temp_file.write(str_initial)
|
||||
cmd = shlex.split(editor) # this enable editor command with option, e.g. gvim -f
|
||||
cmd.append(tfile_name)
|
||||
subprocess.call(cmd)
|
||||
content = read_text_file(tfile_name)
|
||||
os.remove(tfile_name)
|
||||
return content
|
||||
|
||||
|
||||
def _edit_file(editor, path_to_file, temporary=True):
|
||||
if temporary:
|
||||
check_file(path_to_file, fail=True)
|
||||
content = read_text_file(path_to_file)
|
||||
content = _editor_input(editor, content)
|
||||
write_file(path_to_file, content)
|
||||
else:
|
||||
cmd = editor.split() # this enable editor command with option, e.g. gvim -f
|
||||
cmd.append(system_path(path_to_file))
|
||||
subprocess.call(cmd)
|
||||
return os.environ.get('EDITOR', 'vi')
|
||||
|
||||
|
||||
def get_ui():
|
||||
if _ui is None:
|
||||
return PrintUI(config.load_default_conf()) # no editor support. (#FIXME?)
|
||||
return PrintUI(config.load_default_conf()) # no editor support. (#FIXME?)
|
||||
return _ui
|
||||
|
||||
|
||||
@ -111,7 +85,7 @@ class PrintUI(object):
|
||||
kwargs['file'] = self._stderr
|
||||
print('{}: {}'.format(color.dye_err('error', 'error'), message), **kwargs)
|
||||
|
||||
if DEBUG_ALL_TRACES: # if an exception has been raised, print the trace.
|
||||
if DEBUG_ALL_TRACES: # if an exception has been raised, print the trace.
|
||||
if sys.exc_info()[0] is not None:
|
||||
traceback.print_exception(*sys.exc_info)
|
||||
|
||||
@ -130,6 +104,7 @@ class PrintUI(object):
|
||||
self.exit()
|
||||
return True # never happens
|
||||
|
||||
|
||||
class InputUI(PrintUI):
|
||||
"""UI class. Stores configuration parameters and system information.
|
||||
"""
|
||||
@ -184,7 +159,6 @@ class InputUI(PrintUI):
|
||||
pass
|
||||
self.message('Incorrect option.', option_str)
|
||||
|
||||
|
||||
def input_choice(self, options, option_chars, default=None, question=''):
|
||||
"""Ask the user to chose between a set of options. The user is asked
|
||||
to input a char corresponding to the option he chooses.
|
||||
@ -223,7 +197,41 @@ class InputUI(PrintUI):
|
||||
return [True, False][answer]
|
||||
|
||||
def editor_input(self, initial="", suffix='.tmp'):
|
||||
return _editor_input(self.editor, initial=initial, suffix=suffix)
|
||||
"""Use an editor to get input"""
|
||||
str_initial = initial.encode('utf-8') # TODO: make it a configuration item
|
||||
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
||||
tfile_name = temp_file.name
|
||||
temp_file.write(str_initial)
|
||||
self._call_editor(tfile_name)
|
||||
content = read_text_file(tfile_name)
|
||||
os.remove(tfile_name)
|
||||
return content
|
||||
|
||||
def edit_file(self, path, temporary):
|
||||
_edit_file(self.editor, path, temporary=temporary)
|
||||
if temporary:
|
||||
check_file(path, fail=True)
|
||||
content = read_text_file(path)
|
||||
content = self.editor_input(content)
|
||||
write_file(path, content)
|
||||
else:
|
||||
self._call_editor(path)
|
||||
|
||||
def _call_editor(self, path):
|
||||
"""Call the editor, and checks that no error were raised by the OS"""
|
||||
cmd = shlex.split(self.editor) # this enable editor command with option, e.g. gvim -f
|
||||
cmd.append(path)
|
||||
try:
|
||||
subprocess.call(cmd)
|
||||
except OSError as e:
|
||||
if e.errno == os.errno.ENOENT:
|
||||
self.error(("Error while calling editor '{}'. The editor may "
|
||||
"not be present. You can change the text editor "
|
||||
"that pubs uses by setting the $EDITOR environment "
|
||||
"variable, or by running `pubs conf` and setting "
|
||||
"the `edit_cmd` field."
|
||||
).format(self.editor))
|
||||
# handle file not found error.
|
||||
self.exit()
|
||||
else:
|
||||
# Something else went wrong while trying to run `wget`
|
||||
self.handle_exception(e)
|
||||
|
@ -125,3 +125,4 @@ You can access the self-documented configuration by using `pubs conf`, and all t
|
||||
- [Tyler Earnest](https://github.com/tmearnest)
|
||||
- [Dennis Wilson](https://github.com/d9w)
|
||||
- [Bill Flynn](https://github.com/wflynny)
|
||||
- [ksunden](https://github.com/ksunden)
|
||||
|
@ -74,12 +74,11 @@ class FakeInput():
|
||||
def as_global(self):
|
||||
for md in self.module_list:
|
||||
md.input = self
|
||||
md._editor_input = self
|
||||
md._edit_file = self.input_to_file
|
||||
# if mdname.endswith('files'):
|
||||
# md.editor_input = self
|
||||
if md.__name__ == 'pubs.uis':
|
||||
md.InputUI.editor_input = self
|
||||
md.InputUI.edit_file = self.input_to_file
|
||||
|
||||
def input_to_file(self, _, path_to_file, temporary=True):
|
||||
def input_to_file(self, path_to_file, temporary=True):
|
||||
content.write_file(path_to_file, self())
|
||||
|
||||
def add_input(self, inp):
|
||||
|
@ -43,9 +43,8 @@ class FakeSystemExit(Exception):
|
||||
"Exited with code: {}.".format(self.code), *args)
|
||||
|
||||
|
||||
# code for fake fs
|
||||
|
||||
class TestFakeInput(unittest.TestCase):
|
||||
class TestInput(unittest.TestCase):
|
||||
"""Test that the fake input mechanisms work correctly in the tests"""
|
||||
|
||||
def test_input(self):
|
||||
input = fake_env.FakeInput(['yes', 'no'])
|
||||
@ -63,13 +62,16 @@ class TestFakeInput(unittest.TestCase):
|
||||
color.input()
|
||||
|
||||
def test_editor_input(self):
|
||||
sample_conf = conf.load_default_conf()
|
||||
ui = uis.InputUI(sample_conf)
|
||||
|
||||
other_input = fake_env.FakeInput(['yes', 'no'],
|
||||
module_list=[uis])
|
||||
other_input.as_global()
|
||||
self.assertEqual(uis._editor_input('fake_editor'), 'yes')
|
||||
self.assertEqual(uis._editor_input('fake_editor'), 'no')
|
||||
self.assertEqual(ui.editor_input('fake_editor'), 'yes')
|
||||
self.assertEqual(ui.editor_input('fake_editor'), 'no')
|
||||
with self.assertRaises(fake_env.FakeInput.UnexpectedInput):
|
||||
uis._editor_input()
|
||||
ui.editor_input()
|
||||
|
||||
|
||||
class CommandTestCase(fake_env.TestFakeFs):
|
||||
@ -352,8 +354,8 @@ class TestList(DataCommandTestCase):
|
||||
|
||||
def test_list_several_no_date(self):
|
||||
self.execute_cmds(['pubs init -p testrepo'])
|
||||
os.chdir('/') # weird fix for shutil.rmtree invocation.
|
||||
shutil.rmtree(self.rootpath + '/testrepo')
|
||||
os.chdir('/') # weird fix for shutil.rmtree invocation.
|
||||
shutil.rmtree(os.path.join(self.rootpath, 'testrepo'))
|
||||
os.chdir(self.rootpath)
|
||||
self.fs.add_real_directory(os.path.join(self.rootpath, 'testrepo'), read_only=False)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user