def test_add_overwrite_label(): """Test add command while specifying a label manually. Regression test against #4. """ # use temporary config tmp_config = "[DATABASE]\nfile=/tmp/cobib_test_database.yaml\n" with open('/tmp/cobib_test_config.ini', 'w') as file: file.write(tmp_config) CONFIG.set_config(Path('/tmp/cobib_test_config.ini')) # ensure database file exists and is empty open('/tmp/cobib_test_database.yaml', 'w').close() # freshly read in database to overwrite anything that was read in during setup() read_database(fresh=True) # add some data commands.AddCommand().execute(['-b', './test/example_literature.bib']) # add potentially duplicate entry commands.AddCommand().execute([ '-b', './test/example_duplicate_entry.bib', '--label', 'duplicate_resolver' ]) # compare with reference file with open('./test/example_literature.yaml', 'r') as expected: true_lines = expected.readlines() with open('./test/example_duplicate_entry.yaml', 'r') as extra: true_lines += extra.readlines() with open('/tmp/cobib_test_database.yaml', 'r') as file: for line, truth in zip_longest(file, true_lines): assert line == truth # clean up file system os.remove('/tmp/cobib_test_database.yaml') os.remove('/tmp/cobib_test_config.ini')
def tui(tui): """See base class.""" LOGGER.debug('Add command triggered from TUI.') # handle input via prompt tui.execute_command('add') # update database list LOGGER.debug('Updating list after Add command.') read_database(fresh=True) tui.update_list()
def test_tui_quit_prompt(setting, keys): """Test the prompt_before_quit setting of the TUI.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # set prompt_before_quit setting CONFIG.config['TUI']['prompt_before_quit'] = setting read_database() test_tui(None, keys, assert_quit, {'prompt': setting})
def test_tui_scrolling(keys, assertion, assertion_kwargs): """Test TUI scrolling behavior.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # overwrite database file CONFIG.config['DATABASE']['file'] = './test/scrolling_database.yaml' read_database() test_tui(None, keys, assertion, assertion_kwargs)
def setup(): """Setup.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # NOTE: normally you would never trigger an Add command before reading the database but in this # controlled testing scenario we can be certain that this is fine AddCommand().execute(['-b', './test/dummy_scrolling_entry.bib']) read_database() yield setup DeleteCommand().execute(['dummy_entry_for_scroll_testing'])
def test_tui_open_menu(): """Test the open prompt menu for multiple associated files.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # NOTE: normally you would never trigger an Add command before reading the database but in this # controlled testing scenario we can be certain that this is fine AddCommand().execute(['-b', './test/dummy_multi_file_entry.bib']) read_database() try: test_tui(None, 'o', assert_open, {}) finally: DeleteCommand().execute(['dummy_multi_file_entry'])
def tui(tui): """See base class.""" LOGGER.debug('Edit command triggered from TUI.') # get current label label, _ = tui.get_current_label() # populate buffer with entry data EditCommand().execute([label]) # update bibliography data read_database() # redraw total screen after closing external editor LOGGER.debug('Manually redrawing TUI to clear out any editor artefacts.') tui.resize_handler(None, None) # update database list tui.update_list()
def list_tags(args=None): """List all tags. Args: args (dict, optional): dictionary of keyword arguments. Returns: A list of all available tags in the database. """ if not args: args = {} CONFIG.set_config(args.get('config', None)) read_database() tags = list(CONFIG.config['BIB_DATA'].keys()) return tags
def test_tui_config_color(): """Test TUI color configuration.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # overwrite color configuration CONFIG.config['COLORS']['top_statusbar_bg'] = 'red' CONFIG.config['COLORS']['top_statusbar_fg'] = 'blue' CONFIG.config['COLORS']['bottom_statusbar_bg'] = 'green' CONFIG.config['COLORS']['bottom_statusbar_fg'] = 'magenta' CONFIG.config['COLORS']['cursor_line_bg'] = 'white' CONFIG.config['COLORS']['cursor_line_fg'] = 'black' read_database() test_tui(None, '', assert_config_color, {'colors': CONFIG.config['COLORS']})
def test_tui_config_keys(command, key): """Test TUI key binding configuration.""" # ensure configuration is empty CONFIG.config = {} root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) # overwrite key binding configuration CONFIG.config['KEY_BINDINGS'][command] = key # NOTE: normally you would never trigger an Add command before reading the database but in this # controlled testing scenario we can be certain that this is fine AddCommand().execute(['-b', './test/dummy_scrolling_entry.bib']) read_database() try: test_tui(None, key, assert_show, {}) finally: DeleteCommand().execute(['dummy_entry_for_scroll_testing'])
def list_filters(args=None): """List all filters. Args: args (dict, optional): dictionary of keyword arguments. Returns: A list of all field names available for filtering. """ if not args: args = {} CONFIG.set_config(args.get('config', None)) read_database() filters = set() for entry in CONFIG.config['BIB_DATA'].values(): filters.update(entry.data.keys()) return filters
def tui(tui): """See base class.""" LOGGER.debug('Delete command triggered from TUI.') if tui.selection: # use selection for command labels = list(tui.selection) tui.selection.clear() else: # get current label label, _ = tui.get_current_label() labels = [label] # delete selected entry DeleteCommand().execute(labels) # update database list LOGGER.debug('Updating list after Delete command.') read_database(fresh=True) tui.update_list() # if cursor line is below buffer height, move it one line back up if tui.current_line >= tui.buffer.height: tui.current_line -= 1
def test_add(): """Test add command.""" # use temporary config tmp_config = "[DATABASE]\nfile=/tmp/cobib_test_database.yaml\n" with open('/tmp/cobib_test_config.ini', 'w') as file: file.write(tmp_config) CONFIG.set_config(Path('/tmp/cobib_test_config.ini')) # ensure database file exists and is empty open('/tmp/cobib_test_database.yaml', 'w').close() # freshly read in database to overwrite anything that was read in during setup() read_database(fresh=True) # add some data commands.AddCommand().execute(['-b', './test/example_literature.bib']) # compare with reference file with open('/tmp/cobib_test_database.yaml', 'r') as file: with open('./test/example_literature.yaml', 'r') as expected: for line, truth in zip_longest(file, expected): assert line == truth # clean up file system os.remove('/tmp/cobib_test_database.yaml') os.remove('/tmp/cobib_test_config.ini')
def execute(self, args, out=sys.stdout): """Add new entry. Adds new entries to the database. Args: See base class. """ LOGGER.debug('Starting Add command.') parser = ArgumentParser(prog="add", description="Add subcommand parser.") parser.add_argument("-l", "--label", type=str, help="the label for the new database entry") parser.add_argument("-f", "--file", type=str, nargs="+", action="extend", help="files associated with this entry") group_add = parser.add_mutually_exclusive_group() group_add.add_argument("-a", "--arxiv", type=str, help="arXiv ID of the new references") group_add.add_argument("-b", "--bibtex", type=argparse.FileType('r'), help="BibLaTeX bibliographic data") group_add.add_argument("-d", "--doi", type=str, help="DOI of the new references") group_add.add_argument("-i", "--isbn", type=str, help="ISBN of the new references") parser.add_argument( "tags", nargs=argparse.REMAINDER, help="A list of space-separated tags to associate with this entry." + "\nYou can use quotes to specify tags with spaces in them.") if not args: parser.print_usage(sys.stderr) sys.exit(1) try: largs = parser.parse_args(args) except argparse.ArgumentError as exc: print("{}: {}".format(exc.argument_name, exc.message), file=sys.stderr) return new_entries = OrderedDict() edit_entries = False if largs.bibtex is not None: LOGGER.debug("Adding entries from BibLaTeX '%s'.", largs.bibtex) new_entries = Entry.from_bibtex(largs.bibtex) elif largs.arxiv is not None: LOGGER.debug("Adding entries from arXiv '%s'.", largs.arxiv) new_entries = Entry.from_arxiv(largs.arxiv) elif largs.doi is not None: LOGGER.debug("Adding entries from DOI '%s'.", largs.doi) new_entries = Entry.from_doi(largs.doi) elif largs.isbn is not None: LOGGER.debug("Adding entries from ISBN '%s'.", largs.isbn) new_entries = Entry.from_isbn(largs.isbn) elif largs.label is not None: LOGGER.warning( "No input to parse. Creating new entry '%s' manually.", largs.label) new_entries = { largs.label: Entry( largs.label, { 'ID': largs.label, 'ENTRYTYPE': CONFIG.config['FORMAT']['default_entry_type'] }) } edit_entries = True else: LOGGER.error( "Neither an input to parse nor a label for manual creation specified!" ) return if largs.label is not None: assert len(new_entries.values()) == 1 for value in new_entries.values(): # logging done by cobib/parser.py value.set_label = largs.label new_entries = OrderedDict( (largs.label, value) for value in new_entries.values()) if largs.file is not None: assert len(new_entries.values()) == 1 for value in new_entries.values(): # logging done by cobib/parser.py value.set_file = largs.file if largs.tags != []: assert len(new_entries.values()) == 1 for value in new_entries.values(): # logging done by cobib/parser.py value.set_tags = largs.tags # Write the new entries to the database. This destructively overwrite the variable with a # list of labels of the entries which have actually been added to the database. new_entries = write_database(new_entries) if edit_entries: if largs.label not in new_entries: msg = f"You tried to add a new entry '{largs.label}' which already exists!\n" \ + f"Please use `cobib edit {largs.label}` instead!" LOGGER.warning(msg) else: read_database(fresh=True) EditCommand().execute([largs.label]) for label in new_entries: msg = f"'{label}' was added to the database." print(msg) LOGGER.info(msg)
def main(): """Main executable. CoBib's main function used to parse optional keyword arguments and subcommands. """ if len(sys.argv) > 1 and any([a[0] == '_' for a in sys.argv]): # zsh helper function called zsh_main() sys.exit() # initialize logging log_to_stream() subcommands = [cmd.split(':')[0] for cmd in zsh_helper.list_commands()] parser = argparse.ArgumentParser(prog='CoBib', description=""" Cobib input arguments. If no arguments are given, the TUI will start as a default. """) parser.add_argument("--version", action="version", version="%(prog)s v{}".format(__version__)) parser.add_argument('--verbose', '-v', action='count', default=0) parser.add_argument("-l", "--logfile", type=argparse.FileType('w'), help="Alternative log file") parser.add_argument("-c", "--config", type=argparse.FileType('r'), help="Alternative config file") parser.add_argument('command', help="subcommand to be called", choices=subcommands, nargs='?') parser.add_argument('args', nargs=argparse.REMAINDER) args = parser.parse_args() if args.logfile: LOGGER.info('Switching to FileHandler logger in %s', args.logfile.name) log_to_file('DEBUG' if args.verbose > 1 else 'INFO', logfile=args.logfile.name) # set logging verbosity level if args.verbose == 1: logging.getLogger().setLevel(logging.INFO) LOGGER.info('Logging level set to INFO.') elif args.verbose > 1: logging.getLogger().setLevel(logging.DEBUG) LOGGER.info('Logging level set to DEBUG.') CONFIG.set_config(args.config) try: CONFIG.validate() except RuntimeError as exc: LOGGER.error(exc) sys.exit(1) if args.command == 'init': # the database file may not exist yet, thus we ensure to execute the command before trying # to read the database file subcmd = getattr(commands, 'InitCommand')() subcmd.execute(args.args) return read_database() if not args.command: if args.logfile is None: LOGGER.info('Switching to FileHandler logger in %s', '/tmp/cobib.log') log_to_file('DEBUG' if args.verbose > 1 else 'INFO') else: LOGGER.info( 'Already logging to %s. NOT switching to "/tmp/cobib.log"', args.logfile) tui() else: subcmd = getattr(commands, args.command.title() + 'Command')() subcmd.execute(args.args)
def setup(): """Setup.""" root = os.path.abspath(os.path.dirname(__file__)) CONFIG.set_config(Path(root + '/../cobib/docs/debug.ini')) read_database()