示例#1
0
def test_database_read() -> None:
    """Test the `cobib.database.Database.read` method."""
    bib = Database()
    bib.read()
    # pylint: disable=protected-access
    assert Database._unsaved_entries == {}  # pylint: disable=C1803
    assert list(bib.keys()) == ["einstein", "latexcompanion", "knuthwebsite"]
示例#2
0
def test_database_pop() -> None:
    """Test the `cobib.database.Database.pop` method."""
    entries = {"dummy1": "test1", "dummy2": "test2", "dummy3": "test3"}
    bib = Database()
    bib.update(entries)  # type: ignore
    # pylint: disable=protected-access
    Database._unsaved_entries = {}
    assert Database._unsaved_entries == {}  # pylint: disable=C1803
    entry = bib.pop("dummy1")
    assert entry == "test1"
    assert "dummy1" not in bib.keys()
    assert Database._unsaved_entries == {"dummy1": None}
示例#3
0
    def execute(self, args: List[str], out: IO[Any] = sys.stdout) -> None:
        """Imports new entries from another bibliography manager.

        The source from which to import new entries is configured via the `args`. The available
        importers are provided by the `cobib.importers` module.

        Args:
            args: a sequence of additional arguments used for the execution. The following values
                are allowed for this command:
                    * `--skip-download`: skips the automatic download of attached files (like PDFs).
                    * in addition to the options above, a *mutually exclusive group* of keyword
                      arguments for all available `cobib.importers` are registered at runtime.
                      Please check the output of `cobib import --help` for the exact list.
                    * finally, you can add another set of positional arguments (preceded by `--`)
                      which will be passed on to the chosen importer. For more details see for
                      example `cobib import --zotero -- --help`.
            out: the output IO stream. This defaults to `sys.stdout`.
        """
        LOGGER.debug("Starting Import command.")
        parser = ArgumentParser(prog="import", description="Import subcommand parser.")
        parser.add_argument(
            "--skip-download",
            action="store_true",
            help="skip the automatic download of encountered PDF attachments",
        )
        parser.add_argument(
            "importer_arguments",
            nargs="*",
            help="You can pass additional arguments to the chosen importer. To ensure this works as"
            " expected you should add the pseudo-argument '--' before the remaining arguments.",
        )
        group_import = parser.add_mutually_exclusive_group()
        avail_parsers = {
            cls.name: cls for _, cls in inspect.getmembers(importers) if inspect.isclass(cls)
        }
        for name in avail_parsers.keys():
            try:
                group_import.add_argument(f"--{name}", action="store_true", help=f"{name} importer")
            except argparse.ArgumentError:
                continue

        if not args:
            parser.print_usage(sys.stderr)
            sys.exit(1)

        try:
            largs = parser.parse_args(args)
        except argparse.ArgumentError as exc:
            LOGGER.error(exc.message)
            return

        Event.PreImportCommand.fire(largs)

        imported_entries: List[Entry] = []

        for name, cls in avail_parsers.items():
            enabled = getattr(largs, name, False)
            if not enabled:
                continue
            LOGGER.debug("Importing entries from %s.", name)
            imported_entries = cls().fetch(
                largs.importer_arguments, skip_download=largs.skip_download
            )
            break

        bib = Database()
        existing_labels = set(bib.keys())

        new_entries: Dict[str, Entry] = OrderedDict()

        for entry in imported_entries:
            # check if label already exists
            if entry.label in existing_labels:
                msg = (
                    f"The label '{entry.label}' already exists. It will be disambiguated based on "
                    "the configuration option: config.database.format.label_suffix"
                )
                LOGGER.warning(msg)
                new_label = bib.disambiguate_label(entry.label, entry)
                entry.label = new_label

            bib.update({entry.label: entry})
            existing_labels.add(entry.label)
            new_entries[entry.label] = entry

        Event.PostImportCommand.fire(new_entries)
        bib.update(new_entries)

        bib.save()
示例#4
0
文件: add.py 项目: mrossinek/cobib
    def execute(self, args: List[str], out: IO[Any] = sys.stdout) -> None:
        """Adds a new entry.

        Depending on the `args`, if a keyword for one of the available `cobib.parsers` was used
        together with a matching input, that parser will be used to create the new entry.
        Otherwise, the command is only valid if the `--label` option was used to specify a new entry
        label, in which case this command will trigger the `cobib.commands.edit.EditCommand` for a
        manual entry addition.

        Args:
            args: a sequence of additional arguments used for the execution. The following values
                are allowed for this command:
                    * `-l`, `--label`: the label to give to the new entry.
                    * `-u`, `--update`: updates an existing database entry if it already exists.
                    * `-f`, `--file`: one or multiple files to associate with this entry. This data
                      will be stored in the `cobib.database.Entry.file` property.
                    * `-p`, `--path`: the path to store the downloaded associated file in. This can
                      be used to overwrite the `config.utils.file_downloader.default_location`.
                    * `--skip-download`: skips the automatic download of an associated file.
                    * `--skip-existing`: skips entry if label exists instead of running label
                      disambiguation.
                    * in addition to the options above, a *mutually exclusive group* of keyword
                      arguments for all available `cobib.parsers` are registered at runtime. Please
                      check the output of `cobib add --help` for the exact list.
                    * any *positional* arguments (i.e. those, not preceded by a keyword) are
                      interpreted as tags and will be stored in the `cobib.database.Entry.tags`
                      property.
            out: the output IO stream. This defaults to `sys.stdout`.
        """
        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(
            "-u",
            "--update",
            action="store_true",
            help="update an entry if the label exists already",
        )
        file_action = "extend" if sys.version_info[1] >= 8 else "append"
        parser.add_argument(
            "-f",
            "--file",
            type=str,
            nargs="+",
            action=file_action,
            help="files associated with this entry",
        )
        parser.add_argument("-p", "--path", type=str, help="the path for the associated file")
        parser.add_argument(
            "--skip-download",
            action="store_true",
            help="skip the automatic download of an associated file",
        )
        parser.add_argument(
            "--skip-existing",
            action="store_true",
            help="skips entry addition if existent instead of using label disambiguation",
        )
        group_add = parser.add_mutually_exclusive_group()
        avail_parsers = {
            cls.name: cls for _, cls in inspect.getmembers(parsers) if inspect.isclass(cls)
        }
        for name in avail_parsers.keys():
            try:
                group_add.add_argument(
                    f"-{name[0]}", f"--{name}", type=str, help=f"{name} object identfier"
                )
            except argparse.ArgumentError:
                try:
                    group_add.add_argument(f"--{name}", type=str, help=f"{name} object identfier")
                except argparse.ArgumentError:
                    continue
        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:
            LOGGER.error(exc.message)
            return

        Event.PreAddCommand.fire(largs)

        new_entries: Dict[str, Entry] = OrderedDict()

        edit_entries = False
        for name, cls in avail_parsers.items():
            string = getattr(largs, name, None)
            if string is None:
                continue
            LOGGER.debug("Adding entries from %s: '%s'.", name, string)
            new_entries = cls().parse(string)
            break
        else:
            if 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,
                        {"ENTRYTYPE": config.commands.edit.default_entry_type},
                    )
                }
                edit_entries = True
            else:
                msg = "Neither an input to parse nor a label for manual creation specified!"
                LOGGER.error(msg)
                return

        if largs.label is not None:
            assert len(new_entries.values()) == 1
            for value in new_entries.values():
                # logging done by cobib/database/entry.py
                value.label = largs.label
            new_entries = OrderedDict((largs.label, value) for value in new_entries.values())
        else:
            formatted_entries = OrderedDict()
            for label, value in new_entries.items():
                formatted_label = evaluate_as_f_string(
                    config.database.format.label_default, {"label": label, **value.data.copy()}
                )
                value.label = formatted_label
                formatted_entries[formatted_label] = value
            new_entries = formatted_entries

        if largs.file is not None:
            if file_action == "append":
                # We need to flatten the potentially nested list.
                # pylint: disable=import-outside-toplevel
                from itertools import chain

                largs.file = list(chain.from_iterable(largs.file))
            assert len(new_entries.values()) == 1
            for value in new_entries.values():
                # logging done by cobib/database/entry.py
                value.file = largs.file

        if largs.tags != []:
            assert len(new_entries.values()) == 1
            for value in new_entries.values():
                # logging done by cobib/database/entry.py
                value.tags = largs.tags

        bib = Database()
        existing_labels = set(bib.keys())

        for lbl, entry in new_entries.copy().items():
            # check if label already exists
            if lbl in existing_labels:
                if not largs.update:
                    msg = f"You tried to add a new entry '{lbl}' which already exists!"
                    LOGGER.warning(msg)
                    if edit_entries or largs.skip_existing:
                        msg = f"Please use `cobib edit {lbl}` instead!"
                        LOGGER.warning(msg)
                        continue
                    msg = (
                        "The label will be disambiguated based on the configuration option: "
                        "config.database.format.label_suffix"
                    )
                    LOGGER.warning(msg)
                    new_label = bib.disambiguate_label(lbl, entry)
                    entry.label = new_label
                    new_entries[new_label] = entry
                    new_entries.pop(lbl)
                else:
                    # label exists but the user asked to update an existing entry
                    existing_data = bib[lbl].data.copy()
                    existing_data.update(entry.data)
                    entry.data = existing_data.copy()
            # download associated file (if requested)
            if "_download" in entry.data.keys():
                if largs.skip_download:
                    entry.data.pop("_download")
                else:
                    path = FileDownloader().download(
                        entry.data.pop("_download"), lbl, folder=largs.path, overwrite=largs.update
                    )
                    if path is not None:
                        entry.data["file"] = str(path)
            # check journal abbreviation
            if "journal" in entry.data.keys():
                entry.data["journal"] = JournalAbbreviations.elongate(entry.data["journal"])

        Event.PostAddCommand.fire(new_entries)

        bib.update(new_entries)
        if edit_entries:
            EditCommand().execute([largs.label])

        bib.save()

        self.git(args=vars(largs))

        for label in new_entries:
            msg = f"'{label}' was added to the database."
            LOGGER.info(msg)