├── docs
├── readthedocs_requirements.txt
├── fetch.gif
├── search.gif
├── infograph.png
├── latex
│ └── .gitignore
├── logo_bibmanager.png
├── raycast_bibmanager_screenshot.png
├── license.rst
├── contributing.rst
├── latex.rst
├── getstarted.rst
├── conf.py
├── index.rst
├── faq.rst
├── pdf.rst
├── Makefile
├── make.bat
└── ads.rst
├── requirements.txt
├── bibmanager
├── config
├── version.py
├── utils
│ └── __init__.py
├── ads_manager
│ ├── __init__.py
│ └── ads_manager.py
├── pdf_manager
│ ├── __init__.py
│ └── pdf_manager.py
├── latex_manager
│ ├── __init__.py
│ └── latex_manager.py
├── config_manager
│ ├── __init__.py
│ └── config_manager.py
├── bib_manager
│ └── __init__.py
├── __init__.py
└── examples
│ ├── sample.tex
│ ├── sample.bib
│ └── top-apj.tex
├── MANIFEST.in
├── .readthedocs.yaml
├── LICENSE
├── pyproject.toml
├── .github
└── workflows
│ └── python-package.yml
├── .gitignore
├── README.md
├── CONTRIBUTING.md
└── tests
├── test_config_manager.py
├── test_latex_manager.py
└── test_ads.py
/docs/readthedocs_requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx-rtd-theme>=2.0.0
2 |
--------------------------------------------------------------------------------
/docs/fetch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcubillos/bibmanager/HEAD/docs/fetch.gif
--------------------------------------------------------------------------------
/docs/search.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcubillos/bibmanager/HEAD/docs/search.gif
--------------------------------------------------------------------------------
/docs/infograph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcubillos/bibmanager/HEAD/docs/infograph.png
--------------------------------------------------------------------------------
/docs/latex/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/docs/logo_bibmanager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcubillos/bibmanager/HEAD/docs/logo_bibmanager.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy>=2.0.0
2 | requests>=2.19.1
3 | packaging>=17.1
4 | prompt_toolkit>=3.0.48
5 | pygments>=2.2.0
6 |
--------------------------------------------------------------------------------
/docs/raycast_bibmanager_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pcubillos/bibmanager/HEAD/docs/raycast_bibmanager_screenshot.png
--------------------------------------------------------------------------------
/bibmanager/config:
--------------------------------------------------------------------------------
1 | [BIBMANAGER]
2 | style = autumn
3 | text_editor = default
4 | paper = letter
5 | ads_token = None
6 | ads_display = 20
7 | home = HOME/
8 |
9 |
--------------------------------------------------------------------------------
/bibmanager/version.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2025 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | # bibmanager Version:
5 | __version__ = '1.4.13'
6 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include MANIFEST.in
2 | include CHANGELOG.txt
3 | include CONTRIBUTING.md
4 | include README.md
5 | include LICENSE
6 | include pyproject.toml
7 | include requirements.txt
8 | include .github/workflows/python-package.yml
9 | include tests/*
10 | include docs/*
11 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | build:
4 | os: "ubuntu-22.04"
5 | tools:
6 | python: "3.11"
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: docs/conf.py
11 |
12 | python:
13 | install:
14 | - requirements: requirements.txt
15 | - requirements: docs/readthedocs_requirements.txt
16 |
--------------------------------------------------------------------------------
/bibmanager/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .utils import *
5 | from .utils import __all__
6 |
7 | # Clean up top-level namespace--delete everything that isn't in __all__
8 | # or is a magic attribute, and that isn't a submodule of this package
9 | for varname in dir():
10 | if not ((varname.startswith('__') and varname.endswith('__')) or
11 | varname in __all__):
12 | del locals()[varname]
13 | del(varname)
14 |
--------------------------------------------------------------------------------
/bibmanager/ads_manager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .ads_manager import *
5 | from .ads_manager import __all__
6 |
7 |
8 | # Clean up top-level namespace--delete everything that isn't in __all__
9 | # or is a magic attribute, and that isn't a submodule of this package
10 | for varname in dir():
11 | if not ((varname.startswith('__') and varname.endswith('__')) or
12 | varname in __all__):
13 | del locals()[varname]
14 | del(varname)
15 |
--------------------------------------------------------------------------------
/bibmanager/pdf_manager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .pdf_manager import *
5 | from .pdf_manager import __all__
6 |
7 |
8 | # Clean up top-level namespace--delete everything that isn't in __all__
9 | # or is a magic attribute, and that isn't a submodule of this package
10 | for varname in dir():
11 | if not ((varname.startswith('__') and varname.endswith('__')) or
12 | varname in __all__):
13 | del locals()[varname]
14 | del(varname)
15 |
--------------------------------------------------------------------------------
/bibmanager/latex_manager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .latex_manager import *
5 | from .latex_manager import __all__
6 |
7 |
8 | # Clean up top-level namespace--delete everything that isn't in __all__
9 | # or is a magic attribute, and that isn't a submodule of this package
10 | for varname in dir():
11 | if not ((varname.startswith('__') and varname.endswith('__')) or
12 | varname in __all__):
13 | del locals()[varname]
14 | del(varname)
15 |
--------------------------------------------------------------------------------
/bibmanager/config_manager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .config_manager import *
5 | from .config_manager import __all__
6 |
7 |
8 | # Clean up top-level namespace--delete everything that isn't in __all__
9 | # or is a magic attribute, and that isn't a submodule of this package
10 | for varname in dir():
11 | if not ((varname.startswith('__') and varname.endswith('__')) or
12 | varname in __all__):
13 | del locals()[varname]
14 | del(varname)
15 |
--------------------------------------------------------------------------------
/bibmanager/bib_manager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | from .bib_manager import *
5 | from .browser import *
6 |
7 | __all__ = (
8 | bib_manager.__all__
9 | + browser.__all__
10 | )
11 |
12 | # Clean up top-level namespace--delete everything that isn't in __all__
13 | # or is a magic attribute, and that isn't a submodule of this package
14 | for varname in dir():
15 | if not ((varname.startswith('__') and varname.endswith('__')) or
16 | varname in __all__):
17 | del locals()[varname]
18 | del(varname)
19 |
--------------------------------------------------------------------------------
/bibmanager/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | __all__ = [
5 | "bib_manager",
6 | "config_manager",
7 | "latex_manager",
8 | "ads_manager",
9 | "pdf_manager",
10 | "utils",
11 | ]
12 |
13 | from . import bib_manager
14 | from . import config_manager
15 | from . import latex_manager
16 | from . import ads_manager
17 | from . import pdf_manager
18 | from . import utils
19 |
20 | from .version import __version__
21 |
22 |
23 | # Clean up top-level namespace--delete everything that isn't in __all__
24 | # or is a magic attribute, and that isn't a submodule of this package
25 | for varname in dir():
26 | if not ((varname.startswith('__') and varname.endswith('__')) or
27 | varname in __all__):
28 | del locals()[varname]
29 | del(varname)
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Patricio Cubillos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/license.rst:
--------------------------------------------------------------------------------
1 | .. _license:
2 |
3 | License
4 | =======
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2018-2024 Patricio Cubillos
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=77.0.3"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "bibmanager"
7 | description = "A BibTeX manager for LaTeX projects"
8 | readme = "README.md"
9 | license = "MIT"
10 | license-files = ["LICENSE"]
11 |
12 | #https://packaging.python.org/en/latest/guides/single-sourcing-package-version/
13 | dynamic = ["version"]
14 |
15 | requires-python = ">=3.9"
16 | authors = [
17 | {name = "Patricio Cubillos", email = "pcubillos@fulbrightmail.org"}
18 | ]
19 | dependencies = [
20 | 'numpy>=2.0.0',
21 | 'requests>=2.19.1',
22 | 'packaging>=17.1',
23 | 'prompt_toolkit>=3.0.48',
24 | 'pygments>=2.2.0',
25 | ]
26 |
27 | [project.optional-dependencies]
28 | test = [
29 | 'requests-mock',
30 | 'pygments>=2.11',
31 | ]
32 |
33 | [project.urls]
34 | "Homepage" = "https://github.com/pcubillos/bibmanager"
35 |
36 | #https://setuptools.pypa.io/en/stable/userguide/entry_point.html#console-scripts
37 | [project.scripts]
38 | bibm = "bibmanager.__main__:main"
39 |
40 | [tool.setuptools.dynamic]
41 | version = {attr = "bibmanager.version.__version__"}
42 |
43 | #https://setuptools.pypa.io/en/stable/userguide/datafiles.html
44 | [tool.setuptools.package-data]
45 | bibmanager = ['config']
46 | "bibmanager.examples" = ["*"]
47 |
48 |
--------------------------------------------------------------------------------
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Tests
5 |
6 | on:
7 | push:
8 | branches: [ "*" ]
9 | pull_request:
10 | branches: [ "main" ]
11 | schedule:
12 | - cron: '0 12 1 * *'
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ${{ matrix.os }}
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | python-version: ["3.9", "3.10", "3.11", "3.12"]
22 | os: [ubuntu-latest]
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Set up Python ${{ matrix.python-version }}
27 | uses: actions/setup-python@v4
28 | with:
29 | python-version: ${{ matrix.python-version }}
30 | - name: Install dependencies
31 | run: |
32 | python -m pip install --upgrade pip
33 | python -m pip install flake8 pytest
34 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
35 | python -m pip install requests-mock
36 | - name: Install package
37 | run: |
38 | python -m pip install -e .
39 | - name: Lint with flake8
40 | run: |
41 | # stop the build if there are Python syntax errors or undefined names
42 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
43 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
44 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
45 | - name: Test with pytest
46 | run: |
47 | pytest tests -v
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # VIM temporary files:
2 | *.swp
3 |
4 | # OS X:
5 | .DS_Store
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | env/
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | .hypothesis/
54 | .pytest_cache/
55 | tests/.pytest_cache/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # pyenv
82 | .python-version
83 |
84 | # celery beat schedule file
85 | celerybeat-schedule
86 |
87 | # SageMath parsed files
88 | *.sage.py
89 |
90 | # dotenv
91 | .env
92 |
93 | # virtualenv
94 | .venv
95 | venv/
96 | ENV/
97 |
98 | # Spyder project settings
99 | .spyderproject
100 | .spyproject
101 |
102 | # Rope project settings
103 | .ropeproject
104 |
105 | # mkdocs documentation
106 | /site
107 |
108 | # mypy
109 | .mypy_cache/
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bibmanager
2 | > The Next Standard in BibTeX Management
3 |
4 | [](https://github.com/pcubillos/bibmanager/actions/workflows/python-package.yml)
5 | [](https://bibmanager.readthedocs.io/en/latest/?badge=latest)
6 | [](https://pypi.org/project/bibmanager)
7 | [](https://anaconda.org/conda-forge/bibmanager)
8 | [](https://bibmanager.readthedocs.io/en/latest/license.html)
9 | [](https://doi.org/10.5281/zenodo.2547042)
10 |
11 | ### Install as:
12 | ```
13 | pip install bibmanager
14 | ```
15 | or:
16 | ```
17 | conda install -c conda-forge bibmanager
18 | ```
19 |
20 | ### Docs at:
21 |
22 |
23 | ### Cite as:
24 | ```bibtex
25 | @MISC{Cubillos2020zndoBibmanager,
26 | author = {{Cubillos}, Patricio E.},
27 | title = "{bibmanager: A BibTeX manager for LaTeX projects, Zenodo, doi 10.5281/zenodo.2547042}",
28 | year = 2020,
29 | month = feb,
30 | howpublished = {Zenodo},
31 | eid = {10.5281/zenodo.2547042},
32 | doi = {10.5281/zenodo.2547042},
33 | publisher = {Zenodo},
34 | url = {https://doi.org/10.5281/zenodo.2547042},
35 | adsurl = {https://ui.adsabs.harvard.edu/abs/2020zndo...2547042C},
36 | adsnote = {Provided by the SAO/NASA Astrophysics Data System},
37 | }
38 | ```
39 |
40 | #
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | Feel free to contribute to this repository by submitting code pull
5 | requests, raising issues, or emailing the administrator directly.
6 |
7 | Raising Issues
8 | --------------
9 |
10 | Whenever you want to raise a new issue, make sure that it has not
11 | already been mentioned in the issues list. If an issue exists, consider
12 | adding a comment if you have extra information that further describes
13 | the issue or may help to solve it.
14 |
15 | If you are reporting a bug, make sure to be fully descriptive of the
16 | bug, including steps to reproduce the bug, error output logs, etc.
17 |
18 | Make sure to designate appropriate tags to your issue.
19 |
20 | An issue asking for a new functionality must include an explanation as
21 | to why is such feature necessary. Note that if you also provide ideas,
22 | literature references, etc. that helps to the implementation of the
23 | requested functionality, there will be more chances of the issue being
24 | solved.
25 |
26 | Programming Style
27 | -----------------
28 |
29 | Everyone has his/her own programming style, I respect that. However,
30 | some people have [terrible style](http://www.abstrusegoose.com/432).
31 | Following good coding practices (see
32 | [PEP 8](https://www.python.org/dev/peps/pep-0008/),
33 | [PEP 20](https://www.python.org/dev/peps/pep-0020/), and
34 | [PEP 257](https://www.python.org/dev/peps/pep-0257/)) makes everyone
35 | happier: it will increase the chances of your code being added to the
36 | main repo, and will make me work less. I strongly recommend the
37 | following programming guidelines:
38 |
39 | - Always keep it simple.
40 | - Lines are strictly 80 character long, no more.
41 | - **Never ever! use tabs (for any reason, just don't).**
42 | - Avoid hard-coding values at all cost.
43 | - Avoid excessively short variable names (such as ``x`` or ``a``).
44 | - Avoid excessively long variable names as well (just try to write a
45 | meaningful name).
46 | - Indent with 4 spaces.
47 | - Put whitespace around operators and after commas.
48 | - Separate blocks of code with 1 empty line.
49 | - Separate classes and functions with 2 empty lines.
50 | - Separate methods with 1 empty line.
51 | - Contraptions require meaningful comments.
52 | - Prefer commenting an entire block before the code than using
53 | in-line comments.
54 | - Always, always write docstrings.
55 | - Use ``is`` to compare with ``None``, ``True``, and ``False``.
56 | - Limit try--except clauses to the bare minimum.
57 | - If you added a new functionality, make sure to also add its repective tests.
58 | - Make sure that your modifications pass the automated tests (travis).
59 |
60 | Good pieces of code that do not follow these principles will
61 | still be gratefully accepted, but with a frowny face.
62 |
63 |
64 | Pull Requests
65 | -------------
66 |
67 | To submit a pull request you will need to first (only once) fork the
68 | repository into your account. Edit the changes in your
69 | repository. When making a commit, always include a descriptive message
70 | of what changed. Then, click on the pull request button.
71 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. _contributing:
2 |
3 | Contributing
4 | ============
5 |
6 | Feel free to contribute to this repository by submitting code pull
7 | requests, raising issues, or emailing the administrator directly.
8 |
9 | Raising Issues
10 | --------------
11 |
12 | Whenever you want to raise a new issue, make sure that it has not
13 | already been mentioned in the issues list. If an issue exists, consider
14 | adding a comment if you have extra information that further describes
15 | the issue or may help to solve it.
16 |
17 | If you are reporting a bug, make sure to be fully descriptive of the
18 | bug, including steps to reproduce the bug, error output logs, etc.
19 |
20 | Make sure to designate appropriate tags to your issue.
21 |
22 | An issue asking for a new functionality must include the ``wish list``
23 | tag. These issues must include an explanation as to why is such
24 | feature necessary. Note that if you also provide ideas, literature
25 | references, etc. that contribute to the implementation of the
26 | requested functionality, there will be more chances of the issue being
27 | solved.
28 |
29 | Programming Style
30 | -----------------
31 |
32 | Everyone has his/her own programming style, I respect that. However,
33 | some people have `terrible style `_.
34 | Following good coding practices (see `PEP 8
35 | `_, `PEP 20
36 | `_, and `PEP 257
37 | `_) makes everyone happier: it
38 | will increase the chances of your code being added to the main repo,
39 | and will make me work less. I strongly recommend the following
40 | programming guidelines:
41 |
42 | - Always keep it simple.
43 | - Lines are strictly 80 character long, no more.
44 | - **Never ever! use tabs (for any reason, just don't).**
45 | - Avoid hard-coding values at all cost.
46 | - Avoid excessively short variable names (such as ``x`` or ``a``).
47 | - Avoid excessively long variable names as well (just try to write a
48 | meaningful name).
49 | - Indent with 4 spaces.
50 | - Put whitespace around operators and after commas.
51 | - Separate blocks of code with 1 empty line.
52 | - Separate classes and functions with 2 empty lines.
53 | - Separate methods with 1 empty line.
54 | - Contraptions require meaningful comments.
55 | - Prefer commenting an entire block before the code than using
56 | in-line comments.
57 | - Always, always write docstrings.
58 | - Use ``is`` to compare with ``None``, ``True``, and ``False``.
59 | - Limit try--except clauses to the bare minimum.
60 | - If you added a new functionality, make sure to also add its repective tests.
61 | - Make sure that your modifications pass the automated tests (travis).
62 |
63 | Good pieces of code that do not follow these principles will
64 | still be gratefully accepted, but with a frowny face.
65 |
66 |
67 | Pull Requests
68 | -------------
69 |
70 | To submit a pull request you will need to first (only once) fork the
71 | repository into your account. Edit the changes in your
72 | repository. When making a commit, always include a descriptive message
73 | of what changed. Then, click on the pull request button.
74 |
--------------------------------------------------------------------------------
/bibmanager/examples/sample.tex:
--------------------------------------------------------------------------------
1 | \input{top-apj}
2 |
3 | \shorttitle{\textsc{bibmanager}}
4 | \shortauthors{Cubillos}
5 |
6 |
7 | \begin{document}
8 |
9 | \title{\textsc{bibmanager}: The Next Standard in BibTeX Management}
10 |
11 | %% AUTHOR/INSTITUTIONS FOR AASTEX6.2:
12 | \author{Patricio~E.~Cubillos}
13 | \affiliation{Space Research Institute, Austrian Academy of Sciences,
14 | Schmiedlstrasse 6, A-8042, Graz, Austria}
15 |
16 | \email{patricio.cubillos@oeaw.ac.at}
17 |
18 | \begin{abstract}
19 |
20 | \textsc{bibmanager} is a command-line based application to
21 | facilitate the management of BibTeX entries, allowing the user to:
22 | (1) Unify all BibTeX entries into a single database, (2) Automate
23 | BibTeX file file generation (the .bib file) when compiling a LaTeX
24 | project, (3) Automate duplicate detection and updates from arXiv to
25 | peer-reviewed.
26 |
27 | \textsc{bibmanager} also simplifies many other BibTeX-related tasks:
28 | (1) easily add new entries into the \textsc{bibmanager} database: manually,
29 | from your existing BibTeX files, or from ADS, without risking having
30 | duplicates; (2) querry entries in the \textsc{bibmanager} database by author,
31 | year, title words, BibTeX key, or ADS bibcode; (3) generate .bib
32 | from your .tex files; (4) compile LaTeX projects with the latex or
33 | pdflatex directives; (5) perform querries into ADS and add entries
34 | by ADS bibcode.
35 |
36 | To download or get more information, take a look at the \textsc{bibmanager}
37 | documentation available at \href{http://pcubillos.github.io/bibmanager}
38 | {http://pcubillos.github.io/bibmanager}.
39 | \end{abstract}
40 |
41 | % http://journals.aas.org/authors/keywords2013.html
42 | \keywords{general: miscellaneous, standards --
43 | astronomical databases: miscellaneous }
44 |
45 |
46 | \section{Introduction}
47 | \label{introduction}
48 |
49 | Whenever you start a new LaTeX project, do you ever wonder whether to
50 | start a new BibTeX file from scratch or reuse an old one (probably
51 | from a massive file)? If so, you probably also need to remember which
52 | was your more current BibTeX file, check and update the references
53 | that were on arXiv, and so on. Well, fear no more,
54 | \textsc{bibmanager} helps you to automate all of these processes.
55 |
56 | \acknowledgments
57 |
58 | We thank contributors to the Python Programming Language and the free and
59 | open-source community (see Software section, I didn't really used all
60 | of these resources, but it's good to have a reference to them
61 | somewhere to give the proper credit when it corresponds). We drafted
62 | this article using the aastex6.2 template
63 | \citep{AASteamHendrickson2018aastex62}, with some further style
64 | modifications that are available at
65 | \href{https://github.com/pcubillos/ApJtemplate}
66 | {https://github.com/pcubillos/ApJtemplate}.
67 |
68 | \software{
69 | \textsc{Numpy} \citep{HarrisEtal2020natNumpy},
70 | \textsc{SciPy} \citep{VirtanenEtal2020natmeScipy},
71 | \textsc{Matplotlib} \citep{Hunter2007ieeeMatplotlib},
72 | \textsc{IPython} \citep{PerezGranger2007cseIPython}
73 | \textsc{sympy} \citep{MeurerEtal2017pjcsSYMPY},
74 | \textsc{Astropy} \citep{Astropycollab2013aaAstropy},
75 | AASTeX6.2 \citep{AASteamHendrickson2018aastex62},
76 | \textsc{bibmanager} \citep{Cubillos2019zndoBibmanager}.
77 | }
78 | \bibliography{texsample}
79 |
80 | \end{document}
81 |
82 |
--------------------------------------------------------------------------------
/docs/latex.rst:
--------------------------------------------------------------------------------
1 | .. _latex:
2 |
3 | LaTeX Management
4 | ================
5 |
6 | bibtex
7 | ------
8 |
9 | Generate a bibtex file from a tex file.
10 |
11 | **Usage**
12 |
13 | .. code-block:: shell
14 |
15 | bibm bibtex [-h] texfile [bibfile]
16 |
17 | **Description**
18 |
19 | This command generates a BibTeX file by searching for the citation
20 | keys in the input LaTeX file, and stores the output into BibTeX file,
21 | named after the argument in the `\\bibliography{bib_file}` call in the
22 | LaTeX file. Alternatively, the user can specify the name of the
23 | output BibTeX file with the ``bibfile`` argument.
24 |
25 | Any citation key not found in the bibmanager database, will be
26 | shown on the screen prompt.
27 |
28 | **Options**
29 |
30 | | **texfile**
31 | | Path to an existing LaTeX file.
32 | |
33 | | **bibfile**
34 | | Path to an output BibTeX file.
35 | |
36 | | **-h, -\\-help**
37 | | Show this help message and exit.
38 |
39 | **Examples**
40 |
41 | .. code-block:: shell
42 |
43 | # Generate a BibTeX file with references cited in my_file.tex:
44 | bibm bibtex my_file.tex
45 |
46 | # Generate a BibTeX file with references cited in my_file.tex,
47 | # naming the output file 'this_file.bib':
48 | bibm bibtex my_file.tex this_file.bib
49 |
50 | ----------------------------------------------------------------------
51 |
52 | latex
53 | -----
54 |
55 | Compile a LaTeX file with the latex command.
56 |
57 | **Usage**
58 |
59 | .. code-block:: shell
60 |
61 | bibm latex [-h] texfile [paper]
62 |
63 | **Description**
64 |
65 | This command compiles a LaTeX file using the latex command,
66 | executing the following calls:
67 |
68 | - Compute a BibTex file out of the citation calls in the .tex file.
69 | - Remove all outputs from previous compilations.
70 | - Call latex, bibtex, latex, latex to produce a .dvi file.
71 | - Call dvi2ps and ps2pdf to produce the final .pdf file.
72 |
73 | Prefer this command over the :code:`bibm pdflatex` command when the LaTeX file
74 | contains .ps or .eps figures (as opposed to .pdf, .png, or .jpeg).
75 |
76 | Note that the user does not necessarily need to be in the dir
77 | where the LaTeX files are.
78 |
79 | **Options**
80 |
81 | | **texfile**
82 | | Path to an existing LaTeX file.
83 | |
84 | | **paper**
85 | | Paper format, e.g., letter or A4 (default=letter).
86 | |
87 | | **-h, -\\-help**
88 | | Show this help message and exit.
89 |
90 | **Examples**
91 |
92 | .. code-block:: shell
93 |
94 | # Compile a LaTeX project:
95 | bibm latex my_file.tex
96 |
97 | # File extension can be ommited:
98 | bibm latex my_file
99 |
100 | # Compile, setting explicitely the output paper format:
101 | bibm latex my_file A4
102 |
103 | ----------------------------------------------------------------------
104 |
105 | pdflatex
106 | --------
107 |
108 | Compile a LaTeX file with the pdflatex command.
109 |
110 | **Usage**
111 |
112 | .. code-block:: shell
113 |
114 | bibm pdflatex [-h] texfile
115 |
116 | **Description**
117 |
118 | This command compiles a LaTeX file using the pdflatex command,
119 | executing the following calls:
120 |
121 | - Compute a BibTeX file out of the citation calls in the LaTeX file.
122 | - Remove all outputs from previous compilations.
123 | - Call pdflatex, bibtex, pdflatex, pdflatex to produce a .pdf file.
124 |
125 | Prefer this command over the :code:`bibm latex` command when the LaTeX file
126 | contains .pdf, .png, or .jpeg figures (as opposed to .ps or .eps).
127 |
128 | Note that the user does not necessarily need to be in the dir
129 | where the LaTeX files are.
130 |
131 | **Options**
132 |
133 | | **texfile**
134 | | Path to an existing LaTeX file.
135 | |
136 | | **-h, -\\-help**
137 | | Show this help message and exit.
138 |
139 | **Examples**
140 |
141 | .. code-block:: shell
142 |
143 | # Compile a LaTeX project:
144 | bibm pdflatex my_file.tex
145 |
146 | # File extension can be ommited:
147 | bibm pdflatex my_file
148 |
149 |
--------------------------------------------------------------------------------
/docs/getstarted.rst:
--------------------------------------------------------------------------------
1 | .. _getstarted:
2 |
3 | Getting Started
4 | ===============
5 |
6 | ``bibmanager`` offers command-line tools to automate the management of BibTeX entries for LaTeX projects.
7 |
8 | ``bibmanager`` places all of the user's bibtex entries in a centralized database, which is beneficial because it allows ``bibmanager`` to automate duplicates detection, arxiv-to-peer review updates, and generate bibfiles with only the entries required for a specific LaTeX file.
9 |
10 | There are four main categories for the ``bibmanager`` tools:
11 |
12 | * :ref:`bibtex` tools help to create, edit, browse, and query from a
13 | ``bibmanager`` database, containing all BibTeX entries that a user may need.
14 |
15 | * :ref:`latex` tools help to generate (automatically) a bib file
16 | for specific LaTeX files, and compile LaTeX files without worring for
17 | maintaining/updating its bib file.
18 |
19 | * :ref:`ads` tools help to makes queries into ADS, add entries
20 | from ADS, and cross-check the ``bibmanager`` database against ADS, to
21 | update arXiv-to-peer reviewed entries.
22 |
23 | * :ref:`pdf` tools help to maintain a database of the PDF files associated
24 | to the BibTex entries: Fetch from ADS, set manually, and open in a
25 | PDF viewer.
26 |
27 | Once installed (see below), take a look at the ``bibmanager`` main menu by executing the following command:
28 |
29 | .. code-block:: shell
30 |
31 | # Display bibmanager main help menu:
32 | bibm -h
33 |
34 | From there, take a look at the sub-command helps or the rest of these docs for further details, or see the :ref:`qexample` for an introductory worked example.
35 |
36 | System Requirements
37 | -------------------
38 |
39 | ``bibmanager`` is compatible with Python3.9+ and has been `tested `_ to work in both Linux and OS X.
40 |
41 | .. _install:
42 |
43 | Install
44 | -------
45 |
46 | To install ``bibmanager`` run the following command from the terminal:
47 |
48 | .. code-block:: shell
49 |
50 | pip install bibmanager
51 |
52 | Or if you prefer conda:
53 |
54 | .. code-block:: shell
55 |
56 | conda install -c conda-forge bibmanager
57 |
58 | Alternatively (e.g., for developers), clone the repository to your local machine with the following terminal commands:
59 |
60 | .. code-block:: shell
61 |
62 | git clone https://github.com/pcubillos/bibmanager
63 | cd bibmanager
64 | pip install -e .
65 |
66 |
67 | .. note:: To enable the ADS functionality, first you need to obtain an `ADS token `_, and set it into the ``ads_tokend`` config parameter. To do this:
68 |
69 | 1. Create an account and login into the new `ADS system `_.
70 |
71 | 2. Get your token (or generate a new one) from `here `_.
72 |
73 | 3. Set the ``ads_token`` bibmanager parameter:
74 |
75 | .. code-block:: shell
76 |
77 | # Set ads_token to 'my_ads_token':
78 | bibm config ads_token my_ads_token
79 |
80 |
81 | .. _qexample:
82 |
83 | Quick Example
84 | -------------
85 |
86 | Adding your BibTeX file into ``bibmanager`` is as simple as one command:
87 |
88 | .. code-block:: shell
89 |
90 | # Add this sample bibfile into the bibmanager database:
91 | bibm merge ~/.bibmanager/examples/sample.bib
92 |
93 | Compiling a LaTeX file that uses those BibTeX entries is equally simple:
94 |
95 | .. code-block:: shell
96 |
97 | # Compile your LaTeX project:
98 | bibm latex ~/.bibmanager/examples/sample.tex
99 |
100 | This command produced a BibTeX file according to the citations in sample.tex; then executed latex, bibtex, latex, latex; and finally produced a pdf file out of it. You can see the results in `~/.bibmanager/examples/sample.pdf`.
101 |
102 | As long as the citation keys are in the ``bibmanager`` database, you won't need to worry about maintaining a bibfile anymore. The next sections will show all of the capabilities that ``bibmanager`` offers.
103 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Configuration file for the Sphinx documentation builder.
4 | #
5 | # This file does only contain a selection of the most common options. For a
6 | # full list see the documentation:
7 | # http://www.sphinx-doc.org/en/master/config
8 |
9 | # -- Path setup --------------------------------------------------------------
10 |
11 | # If extensions (or modules to document with autodoc) are in another directory,
12 | # add these directories to sys.path here. If the directory is relative to the
13 | # documentation root, use os.path.abspath to make it absolute, like shown here.
14 |
15 | import os
16 | import sys
17 | from datetime import date
18 |
19 | sys.path.insert(0, os.path.abspath(".."))
20 | import bibmanager as bm
21 |
22 | # -- Project information -----------------------------------------------------
23 |
24 | project = 'bibmanager'
25 | copyright = f'2018-{date.today().year}, Patricio Cubillos'
26 | author = 'Patricio Cubillos'
27 |
28 | # The short X.Y version
29 | version = bm.__version__
30 | # The full version, including alpha/beta/rc tags
31 | release = bm.__version__
32 |
33 | # -- General configuration ---------------------------------------------------
34 |
35 | # If your documentation needs a minimal Sphinx version, state it here.
36 | #
37 | # needs_sphinx = '1.0'
38 |
39 | # Add any Sphinx extension module names here, as strings. They can be
40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
41 | # ones.
42 | extensions = [
43 | 'sphinx.ext.autodoc',
44 | 'sphinx.ext.mathjax',
45 | 'sphinx.ext.viewcode',
46 | ]
47 |
48 | # Add any paths that contain templates here, relative to this directory.
49 | templates_path = ['_templates']
50 |
51 | # The suffix(es) of source filenames.
52 | # You can specify multiple suffix as a list of string:
53 | #
54 | # source_suffix = ['.rst', '.md']
55 | source_suffix = '.rst'
56 |
57 | # The master toctree document.
58 | master_doc = 'index'
59 |
60 | # The language for content autogenerated by Sphinx. Refer to documentation
61 | # for a list of supported languages.
62 | #
63 | # This is also used if you do content translation via gettext catalogs.
64 | # Usually you set "language" from the command line for these cases.
65 | language = 'en'
66 |
67 | # List of patterns, relative to source directory, that match files and
68 | # directories to ignore when looking for source files.
69 | # This pattern also affects html_static_path and html_extra_path .
70 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
71 |
72 | # The name of the Pygments (syntax highlighting) style to use.
73 | pygments_style = 'sphinx'
74 |
75 |
76 | # -- Options for HTML output -------------------------------------------------
77 |
78 | # The theme to use for HTML and HTML Help pages. See the documentation for
79 | # a list of builtin themes.
80 | html_theme = 'sphinx_rtd_theme'
81 |
82 | # Theme options are theme-specific and customize the look and feel of a theme
83 | # further. For a list of options available for each theme, see the
84 | # documentation.
85 | #
86 | # html_theme_options = {}
87 |
88 | # The name of an image file (relative to this directory) to place at the top
89 | # of the sidebar.
90 | html_logo = 'logo_bibmanager.png'
91 |
92 | # The name of an image file (within the static path) to use as favicon of the
93 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
94 | # pixels large.
95 | #html_favicon = "bibmanager_favicon.ico"
96 |
97 | # Add any paths that contain custom static files (such as style sheets) here,
98 | # relative to this directory. They are copied after the builtin static files,
99 | # so a file named "default.css" will overwrite the builtin "default.css".
100 | html_static_path = ['_static']
101 |
102 | # Custom sidebar templates, must be a dictionary that maps document names
103 | # to template names.
104 | #
105 | # The default sidebars (for documents that don't match any pattern) are
106 | # defined by theme itself. Builtin themes are using these templates by
107 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
108 | # 'searchbox.html']``.
109 | #
110 | # html_sidebars = {}
111 |
112 |
113 | # -- Options for HTMLHelp output ---------------------------------------------
114 |
115 | # Output file base name for HTML help builder.
116 | htmlhelp_basename = 'bibmanagerdoc'
117 |
118 |
119 | # -- Options for LaTeX output ------------------------------------------------
120 |
121 | latex_elements = {
122 | # The paper size ('letterpaper' or 'a4paper').
123 | #
124 | 'papersize': 'letterpaper',
125 |
126 | # The font size ('10pt', '11pt' or '12pt').
127 | #
128 | 'pointsize': '10pt',
129 |
130 | # Additional stuff for the LaTeX preamble.
131 | #
132 | # 'preamble': '',
133 |
134 | # Latex figure (float) alignment
135 | #
136 | # 'figure_align': 'htbp',
137 | }
138 |
139 | # Grouping the document tree into LaTeX files. List of tuples
140 | # (source start file, target name, title,
141 | # author, documentclass [howto, manual, or own class]).
142 | latex_documents = [
143 | (master_doc, 'bibmanager.tex', 'bibmanager Documentation',
144 | 'Patricio Cubillos', 'manual'),
145 | ]
146 |
147 |
148 | # -- Options for manual page output ------------------------------------------
149 |
150 | # One entry per manual page. List of tuples
151 | # (source start file, name, description, authors, manual section).
152 | man_pages = [
153 | (master_doc, 'bibmanager', 'bibmanager Documentation',
154 | [author], 1)
155 | ]
156 |
157 |
158 | # -- Options for Texinfo output ----------------------------------------------
159 |
160 | # Grouping the document tree into Texinfo files. List of tuples
161 | # (source start file, target name, title, author,
162 | # dir menu entry, description, category)
163 | texinfo_documents = [
164 | (master_doc, 'bibmanager', 'bibmanager Documentation',
165 | author, 'bibmanager', 'One line description of project.',
166 | 'Miscellaneous'),
167 | ]
168 |
169 |
170 | # -- Extension configuration -------------------------------------------------
171 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. bibmanager documentation master file, created by
2 | sphinx-quickstart on Thu Jan 3 08:27:43 2019.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | bibmanager
7 | ==========
8 |
9 | **The Next Standard in BibTeX Management**
10 |
11 | |Build Status| |docs| |PyPI| |conda| |License| |DOI|
12 |
13 | -------------------------------------------------------------------
14 |
15 |
16 | :Author: Patricio Cubillos and contributors (see :ref:`team`)
17 | :Contact: `pcubillos[at]fulbrightmail.org`_
18 | :Organizations: `Space Research Institute (IWF)`_
19 | :Web Site: https://github.com/pcubillos/bibmanager
20 | :Date: |today|
21 |
22 | Features
23 | ========
24 |
25 | ``bibmanager`` is a Python 3.9+ command-line based application to facilitate the management of BibTeX entries, allowing the user to:
26 |
27 | * Unify all BibTeX entries into a single database
28 | * Automate .bib file generation when compiling a LaTeX project
29 | * Automate duplicate detection and updates from arXiv to peer-reviewed
30 | * Clean up (remove duplicates, ADS update) any external bibfile (since version 1.1.2)
31 | * Keep a database of the entries' PDFs and fetch PDFs from ADS (since version 1.2)
32 | * Browse interactively through the database (since version 1.3)
33 | * Keep track of the more relevant entries using custom-set tags (since version 1.4)
34 |
35 | ``bibmanager`` also simplifies many other BibTeX-related tasks:
36 |
37 | * Add or modify entries into the ``bibmanager`` database:
38 |
39 | * Merging user's .bib files
40 | * Manually adding or editing entries
41 | * Add entries from ADS bibcodes
42 |
43 | * entry adding via your default text editor
44 | * Query entries in the ``bibmanager`` database by author, year, or title keywords
45 | * Generate .bib files built from your .tex files
46 | * Compile LaTeX projects with the ``latex`` or ``pdflatex`` directives
47 | * Perform queries into ADS and add entries by bibcode
48 | * Fetch PDF files from ADS (via their bibcode, new since version 1.2)
49 |
50 |
51 | Be Kind
52 | =======
53 |
54 | If ``bibmanager`` was useful for your research, please consider
55 | acknowledging the effort of the developers of this project. Here's a BibTeX
56 | entry for that:
57 |
58 | .. code-block:: bibtex
59 |
60 | @MISC{Cubillos2020zndoBibmanager,
61 | author = {{Cubillos}, Patricio E.},
62 | title = "{bibmanager: A BibTeX manager for LaTeX projects, Zenodo, doi 10.5281/zenodo.2547042}",
63 | year = 2020,
64 | month = feb,
65 | howpublished = {Zenodo},
66 | eid = {10.5281/zenodo.2547042},
67 | doi = {10.5281/zenodo.2547042},
68 | publisher = {Zenodo},
69 | url = {https://doi.org/10.5281/zenodo.2547042},
70 | adsurl = {https://ui.adsabs.harvard.edu/abs/2020zndo...2547042C},
71 | adsnote = {Provided by the SAO/NASA Astrophysics Data System},
72 | }
73 |
74 | .. note::
75 |
76 | Did you know that `Aaron David Schneider`_ built this totally
77 | amazing bibmanager graphic interface?
78 |
79 | This extension lets you quickly browse through your database,
80 | retrieve metadata (title, date, tags), open in ADS or PDF
81 | (download if needed), or just copy things to the clipboard.
82 | **I've tried it and I can only recommend to checking it out!**
83 |
84 | This is implemented via `Raycast
85 | `_, which is
86 | available for Mac OS X users. To install Raycast and
87 | bibmanager extension check :ref:`these simple instructions `.
88 |
89 | .. figure:: raycast_bibmanager_screenshot.png
90 |
91 | Check out this video tutorial to get started with ``bibmanager``:
92 |
93 | .. raw:: html
94 |
95 |
96 |
97 | And this one covering some other features:
98 |
99 | .. raw:: html
100 |
101 |
102 |
103 |
104 | .. _team:
105 |
106 | Contributors
107 | ============
108 |
109 | ``bibmanager`` was created and is maintained by
110 | `Patricio Cubillos`_ (`pcubillos[at]fulbrightmail.org`_).
111 |
112 | Thanks to
113 | ---------
114 |
115 | These people have directly contributed to make the software better:
116 |
117 | - `K.-Michael Aye `_
118 | - `Ellert van der Velden `_
119 | - `Aaron David Schneider `_
120 |
121 | Documentation
122 | =============
123 |
124 | .. toctree::
125 | :maxdepth: 2
126 |
127 | getstarted
128 | bibtex
129 | latex
130 | ads
131 | pdf
132 | faq
133 | api
134 | contributing
135 | license
136 |
137 | .. * :ref:`genindex`
138 | * :ref:`modindex`
139 | * :ref:`search`
140 |
141 | Featured Articles
142 | =================
143 |
144 | | `ADS Blog `_: **User-Developed Tools for ADS**
145 | | *(30 Jul 2019)*
146 | | http://adsabs.github.io/blog/3rd-party-tools
147 |
148 |
149 | | `AstroBetter `_: **Bibmanager: A BibTex Manager Designed for Astronomers**
150 | | *(17 Feb 2020)*
151 | | https://www.astrobetter.com/blog/2020/02/17/bibmanager-a-bibtex-manager-designed-for-astronomers/
152 |
153 | ---------------------------------------------------------------------------
154 |
155 | Please send any feedback or inquiries to:
156 |
157 | Patricio Cubillos (`pcubillos[at]fulbrightmail.org`_)
158 |
159 | .. _Patricio Cubillos: https://github.com/pcubillos/
160 | .. _pcubillos[at]fulbrightmail.org: pcubillos@fulbrightmail.org
161 | .. _Space Research Institute (IWF): http://iwf.oeaw.ac.at/
162 |
163 | .. _Aaron Schneider: https://github.com/AaronDavidSchneider/
164 |
165 |
166 | .. |Build Status| image:: https://github.com/pcubillos/bibmanager/actions/workflows/python-package.yml/badge.svg?branch=master
167 | :target: https://github.com/pcubillos/bibmanager/actions/workflows/python-package.yml?query=branch%3Amaster
168 |
169 | .. |docs| image:: https://readthedocs.org/projects/bibmanager/badge/?version=latest
170 | :target: https://bibmanager.readthedocs.io/en/latest/?badge=latest
171 | :alt: Documentation Status
172 |
173 | .. |PyPI| image:: https://img.shields.io/pypi/v/bibmanager.svg
174 | :target: https://pypi.org/project/bibmanager/
175 | :alt: Latest Version
176 |
177 | .. |conda| image:: https://img.shields.io/conda/vn/conda-forge/bibmanager.svg
178 | :target: https://anaconda.org/conda-forge/bibmanager
179 |
180 | .. |License| image:: https://img.shields.io/github/license/pcubillos/bibmanager.svg?color=blue
181 | :target: https://pcubillos.github.io/bibmanager/license.html
182 |
183 | .. |DOI| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.2547042.svg
184 | :target: https://doi.org/10.5281/zenodo.2547042
185 |
186 |
187 |
--------------------------------------------------------------------------------
/docs/faq.rst:
--------------------------------------------------------------------------------
1 | .. _scenarios:
2 |
3 | FAQs and Resources
4 | ==================
5 |
6 | Frequently Asked Questions
7 | --------------------------
8 |
9 | Why should I use ``bibmanager``? I have already my working ecosystem.
10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 |
12 | ``bibmanager`` simply makes your life easier, keeping all of your references
13 | at the tip of your fingers:
14 |
15 | - No need to wonder whether to start a new BibTeX file from scratch or reuse
16 | an old one (probably a massive file), nor to think which was the most current.
17 | - Easily add new entries: manually, from your existing BibTeX files, or
18 | from ADS, without risking having duplicates.
19 | - Generate BibTeX files and compile a LaTeX project with a single command.
20 | - You can stay up to date with ADS with a single command.
21 |
22 | ----------------------------------------------------------------------
23 |
24 |
25 | I use several machines to work, can I use a single database across all of them?
26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27 |
28 | Yes!, since vesion 1.2 ``bibmanager`` has a ``home`` config parameter
29 | which sets the location of the database. By default ``home`` points
30 | at *~/.bibmanager*; however, you can set the ``home`` parameter into a
31 | folder in a Dropbox-type of system. The only nuance is that you'll
32 | need to install and configure ``bibmanager`` in each machine, but now
33 | all of them will be pointing to the same database.
34 |
35 | Note that the folder containing the associated PDF files (i.e.,
36 | ``home``/pdf) will also be moved into the new location.
37 |
38 | ----------------------------------------------------------------------
39 |
40 | I compiled my LaTeX file before merging its bibfile, did I just overwite my own BibTeX file?
41 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 |
43 | No, if ``bibmanager`` has to overwrite a bibfile edited by the user (say,
44 | `'myrefs.bib'`), it saves the old file (and date) as
45 | `'orig_yyyy-mm-dd_myrefs.bib'`.
46 |
47 | ----------------------------------------------------------------------
48 |
49 | I meged the BibTeX file for my LaTeX project, but it says there are missing references when I compile. What's going on?
50 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51 |
52 | Probably, there were duplicate entries with previous entries in the
53 | ``bibmanager`` database, but they had different keys. Simply, do a search
54 | of your missing reference, to check it's key, something like:
55 |
56 | .. code-block:: shell
57 |
58 | # Surely, first author and year have not changed:
59 | bibm search
60 | author:"^Author" year:the_year
61 |
62 | Now, you can update the key in the LaTeX file (and as a bonus, you wont
63 | run into having duplicate entries in the future).
64 |
65 | ----------------------------------------------------------------------
66 |
67 | .. _raycast:
68 |
69 | That Raycast extension looks sweet! How do I install it?
70 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71 |
72 | Right, Raycast rocks. To install Raycast, simply go to their homepage
73 | (https://www.raycast.com/), click on the ``Download`` tab in the upper
74 | right corner and follow the instruction of the installer.
75 |
76 | To install the ``bibmanager`` extension, click on the ``Store`` tab
77 | (from Raycast home's page), and search for bibmanager. Once
78 | redirected, you'll see a ``Install Extension`` tab, click it and
79 | follow the instructions.
80 |
81 |
82 | ----------------------------------------------------------------------
83 |
84 | I installed ``bibmanager`` while being in a virtual environment. But I don't want to start the virtual env every time I want to use ``bibm``.
85 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86 |
87 | (This is not a question!, please state your FAQ in the form of a
88 | question) Anyway, no worries, the ``bibm`` executable entry point is
89 | safe to use even if you are not in the virtual environment.
90 | What you can do is to add the path to the entry point into your bash:
91 |
92 | .. code-block:: shell
93 |
94 | # first, search for the entry-point executable (while in the virtual environment):
95 | which bibm
96 |
97 | /home/username/py36/bin/bibm
98 |
99 | Then, add an alias with that path into your bash, e.g.: ``alias bibm='/home/username/py36/bin/bibm'``. Now, you can access ``bibm`` at any time.
100 |
101 | ----------------------------------------------------------------------
102 |
103 | A unique database? Does it mean I need to have better keys to differentiate my entries?
104 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
105 |
106 | Certainly, as a database grows, short BibTeX keys like `'LastnameYYYY'`
107 | are sub-optimal, since they may conflict with other entries, and are not
108 | descriptive enough.
109 | A good practice is to adopt a longer, more descriptive format.
110 | I personally suggests this one:
111 |
112 | ======= ================================ ===============================
113 | Authors Format Example
114 | ======= ================================ ===============================
115 | 1 LastYYYYjournalDescription Shapley1918apjDistanceGClusters
116 | 2 Last1Last2YYYYjournalDescription PerezGranger2007cseIPython
117 | 3 LastEtalYYYYjournalDescription AstropycollabEtal2013aaAstropy
118 | ======= ================================ ===============================
119 |
120 | That is:
121 |
122 | - the first-author last name (capitalized)
123 | - either nothing, the second-author last name (capitalized), or `'Etal'`
124 | - the publication year
125 | - the journal initials if any (and lower-cased)
126 | - a couple words from the title that describe the article
127 | (capitalized or best format at user's discretion).
128 |
129 | These long keys will keep you from running into issues, and will make
130 | the citations in your LaTeX documents nearly unambiguous at sight.
131 |
132 |
133 | ----------------------------------------------------------------------
134 |
135 | The code breaks with ``UnicodeEncodeError`` when running over ssh. What's going on?
136 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
137 |
138 | As correctly guessed in this `Stack Overflow post
139 | `_, Python cannot
140 | determine the terminal encoding, and falls back to ASCII. You can fix
141 | this by setting the following environment variable, e.g., into your
142 | bash:
143 |
144 | ``export PYTHONIOENCODING=utf-8``
145 |
146 | ----------------------------------------------------------------------
147 |
148 | Resources
149 | ---------
150 |
151 | | Docs for queries in the new ADS:
152 | | http://adsabs.github.io/help/search/search-syntax
153 |
154 | | The ADS API:
155 | | https://github.com/adsabs/adsabs-dev-api
156 |
157 | | BibTeX author format:
158 | | http://mirror.easyname.at/ctan/info/bibtex/tamethebeast/ttb_en.pdf
159 | | http://texdoc.net/texmf-dist/doc/bibtex/base/btxdoc.pdf
160 |
161 | | Pygment style BibTeX options:
162 | | http://pygments.org/demo/6693571/
163 |
164 | | Set up conda:
165 | | https://github.com/conda-forge/staged-recipes
166 |
167 | | Testing:
168 | | https://docs.pytest.org/
169 | | http://pythontesting.net/framework/pytest/pytest-fixtures-nuts-bolts/
170 | | https://blog.dbrgn.ch/2016/2/18/overriding_default_arguments_in_pytest/
171 | | https://www.patricksoftwareblog.com/monkeypatching-with-pytest/
172 | | https://requests-mock.readthedocs.io/en/
173 |
174 | | Useful info from stackoverflow:
175 | | https://stackoverflow.com/questions/17317219
176 | | https://stackoverflow.com/questions/18011902
177 | | https://stackoverflow.com/questions/26899001
178 | | https://stackoverflow.com/questions/2241348
179 | | https://stackoverflow.com/questions/1158076
180 | | https://stackoverflow.com/questions/17374526
181 | | https://stackoverflow.com/questions/43165341
182 |
--------------------------------------------------------------------------------
/docs/pdf.rst:
--------------------------------------------------------------------------------
1 | .. _pdf:
2 |
3 | PDF Management
4 | ==============
5 |
6 | Since version 1.2, ``bibmanager`` also doubles as a PDF database. The
7 | following commands describe how to fetch PDF entries from ADS, or
8 | manually link and open the PDF files associated to the ``bibmanager``
9 | database. All PDF files are stored in the ``home``/pdf folder
10 | (see :ref:`config`, for more info to set ``home``).
11 |
12 | PDF files can also be manually linked to the database entries via the
13 | ``bibm edit`` command (see :ref:`meta`).
14 |
15 | ----------------------------------------------------------------------
16 |
17 | fetch
18 | -----
19 |
20 | Fetch a PDF file from ADS.
21 |
22 | **Usage**
23 |
24 | .. code-block:: shell
25 |
26 | bibm fetch [-h] [-o] [keycode] [filename]
27 |
28 | **Description**
29 |
30 | This command attempts to fetch from ADS the PDF file associated to a
31 | Bibtex entry in the ``bibmanager`` database. The request is made to the
32 | Journal, then the ADS server, and lastly to ArXiv until one succeeds.
33 | The entry is specified by either the BibTex key or ADS bibcode, these
34 | can be specified on the initial command, or will be queried after
35 | through the prompt (see examples).
36 |
37 | If the output PDF filename is not specified, the routine will guess a
38 | name with this syntax: LastnameYYYY_Journal_vol_page.pdf
39 |
40 | | Requests for entries not in the database can be made only
41 | by ADS bibcode (and auto-completion wont be able to predict their
42 | bibcode IDs).
43 | | *(New since version 1.2)*
44 |
45 | **Options**
46 |
47 | | **keycode**
48 | | Either a BibTex key or an ADS bibcode identifier.
49 | |
50 | | **filename**
51 | | Name for fetched PDF file.
52 | |
53 | | **-h, -\\-help**
54 | | Show this help message and exit
55 | |
56 | | **-o, -\\-open**
57 | | Open the fetched PDF if the request succeeded.
58 |
59 | **Examples**
60 |
61 | .. note:: These examples assume that you have this entry into the database: Rubin, V. C. et al. (1980), ApJ, 238, 471. E.g., with: ``bibm ads-add 1980ApJ...238..471R RubinEtal1980apjGalaxiesRotation``
62 |
63 |
64 | A ``bibm fetch`` call without arguments triggers a prompt search with
65 | auto-complete help:
66 |
67 | .. figure:: fetch.gif
68 |
69 | Note that as you navigate through the options, the display shows info
70 | about the entries at the bottom. Also, as long as the user
71 | provides a valid bibcode, you can fetch any PDF (no need to be an
72 | entry in the database).
73 |
74 |
75 | .. code-block:: shell
76 |
77 | # Fetch PDF for entry by BibTex key:
78 | bibm fetch RubinEtal1980apjGalaxiesRotation
79 |
80 | Fetching PDF file from Journal website:
81 | Request failed with status code 404: NOT FOUND
82 | Fetching PDF file from ADS website:
83 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980_ApJ_238_471.pdf'.
84 |
85 | To open the PDF file, execute:
86 | bibm open RubinEtal1980apjGalaxiesRotation
87 |
88 | # Fetch PDF fir entry by ADS bibcode:
89 | bibm fetch 1980ApJ...238..471R
90 | ...
91 | Fetching PDF file from ADS website:
92 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980_ApJ_238_471.pdf'.
93 |
94 | To open the PDF file, execute:
95 | bibm open RubinEtal1980apjGalaxiesRotation
96 |
97 |
98 | # Fetch and set the output filename:
99 | bibm fetch 1980ApJ...238..471R Rubin1980_gals_rotation.pdf
100 | ...
101 | Fetching PDF file from ADS website:
102 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980_gals_rotation.pdf'.
103 |
104 | To open the PDF file, execute:
105 | bibm open RubinEtal1980apjGalaxiesRotation
106 |
107 |
108 | A ``bibm fetch`` call with the ``-o/--open`` flag automatically opens
109 | the PDF file after a successful fetch:
110 |
111 | .. code-block:: shell
112 |
113 | # Use prompt to find the BibTex entry (and open the PDF right after fetching):
114 | bibm fetch RubinEtal1980apjGalaxiesRotation -o
115 |
116 | Fetching PDF file from Journal website:
117 | Request failed with status code 404: NOT FOUND
118 | Fetching PDF file from ADS website:
119 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980_ApJ_238_471.pdf'.
120 |
121 | ----------------------------------------------------------------------
122 |
123 | open
124 | ----
125 |
126 | Open the PDF file of a BibTex entry in the database.
127 |
128 | **Usage**
129 |
130 | .. code-block:: shell
131 |
132 | bibm open [-h] [keycode]
133 |
134 | **Description**
135 |
136 | This command opens the PDF file associated to a Bibtex entry in the
137 | ``bibmanager`` database. The entry is specified by either its BibTex key,
138 | its ADS bibcode, or its PDF filename. These can be specified on the
139 | initial command, or will be queried through the prompt (with
140 | auto-complete help).
141 |
142 | | If the user requests a PDF for an entry without a PDF file but with an
143 | ADS bibcode, ``bibmanager`` will ask if the user wants to fetch the PDF
144 | from ADS.
145 | | *(New since version 1.2)*
146 |
147 | **Options**
148 |
149 | | **keycode**
150 | | Either a key or an ADS bibcode identifier.
151 | |
152 | | **-h, -\\-help**
153 | | Show this help message and exit
154 |
155 | **Examples**
156 |
157 | .. code-block:: shell
158 |
159 | # Open setting the BibTex key:
160 | bibm open RubinEtal1980apjGalaxiesRotation
161 |
162 | # Open setting the ADS bibcode:
163 | bibm open 1980ApJ...238..471R
164 |
165 | # Open setting the PDF filename:
166 | bibm open Rubin1980_ApJ_238_471.pdf
167 |
168 | .. code-block:: shell
169 |
170 | # Use the prompt to find the BibTex entry:
171 | bibm open
172 | Syntax is: key: KEY_VALUE
173 | or: bibcode: BIBCODE_VALUE
174 | or: pdf: PDF_VALUE
175 | (Press 'tab' for autocomplete)
176 | key: RubinEtal1980apjGalaxiesRotation
177 |
178 | ----------------------------------------------------------------------
179 |
180 | pdf
181 | ---
182 |
183 | Link a PDF file to a BibTex entry in the database.
184 |
185 | **Usage**
186 |
187 | .. code-block:: shell
188 |
189 | bibm pdf [-h] [keycode pdf] [name]
190 |
191 | **Description**
192 |
193 | This command manually links an existing PDF file to a Bibtex entry in
194 | the ``bibmanager`` database. The PDF file is moved to the *'home/pdf'*
195 | folder (see :ref:`config`).
196 | The entry is specified by either the BibTex key or ADS bibcode, these
197 | can be specified on the initial command, or will be queried after
198 | through the prompt (see examples).
199 |
200 | | If the output PDF filename is not specified, the code will preserve
201 | the file name. If the user sets *'guess'* as filename, the code will
202 | guess a name based on the BibTex information.
203 | | *(New since version 1.2)*
204 |
205 | **Options**
206 |
207 |
208 | | **keycode**
209 | | Either a key or an ADS bibcode identifier.
210 | |
211 | | **pdf**
212 | | Path to PDF file to link to entry.
213 | |
214 | | **filename**
215 | | New name for the linked PDF file.
216 | |
217 | | **-h, -\\-help**
218 | | Show this help message and exit
219 |
220 | **Examples**
221 |
222 | Say you already have an article's PDF file here: *~/Downloads/Rubin1980.pdf*
223 |
224 | .. code-block:: shell
225 |
226 | # Link a downloaded PDF file to an entry:
227 | bibm pdf 1980ApJ...238..471R ~/Downloads/Rubin1980.pdf
228 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980.pdf'.
229 |
230 | # Link a downloaded PDF file (guessing the name from BibTex):
231 | bibm pdf 1980ApJ...238..471R ~/Downloads/Rubin1980.pdf guess
232 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980_ApJ_238_471.pdf'.
233 |
234 | # Link a downloaded PDF file (renaming the file):
235 | bibm pdf 1980ApJ...238..471R ~/Downloads/Burbidge1957.pdf RubinEtal_1980.pdf
236 | Saved PDF to: '/home/user/.bibmanager/pdf/RubinEtal_1980.pdf'.
237 |
238 | .. code-block:: shell
239 |
240 | # Use the prompt to find the BibTex entry:
241 | bibm pdf
242 | Syntax is: key: KEY_VALUE PDF_FILE FILENAME
243 | or: bibcode: BIBCODE_VALUE PDF_FILE FILENAME
244 | (output FILENAME is optional, set it to guess for automated naming)
245 |
246 | key: RubinEtal1980apjGalaxiesRotation ~/Downloads/Rubin1980.pdf
247 | Saved PDF to: '/home/user/.bibmanager/pdf/Rubin1980.pdf'.
248 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = ../../sphinxdoc-bibmanager
9 | PDFBUILDDIR = ./latex/tmp
10 |
11 | # Mute compilation
12 | Q =
13 | O =
14 | ifdef MUTE
15 | ifeq ("$(origin MUTE)", "command line")
16 | Q = @
17 | O = > /dev/null
18 | endif
19 | endif
20 |
21 | # Internal variables.
22 | PAPEROPT_a4 = -D latex_paper_size=a4
23 | PAPEROPT_letter = -D latex_paper_size=letter
24 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
25 | # the i18n builder cannot share the environment and doctrees with the others
26 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
27 |
28 | .PHONY: help
29 | help:
30 | @echo "Please use \`make ' where is one of"
31 | @echo " html to make standalone HTML files"
32 | @echo " dirhtml to make HTML files named index.html in directories"
33 | @echo " singlehtml to make a single large HTML file"
34 | @echo " pickle to make pickle files"
35 | @echo " json to make JSON files"
36 | @echo " htmlhelp to make HTML files and a HTML help project"
37 | @echo " qthelp to make HTML files and a qthelp project"
38 | @echo " applehelp to make an Apple Help Book"
39 | @echo " devhelp to make HTML files and a Devhelp project"
40 | @echo " epub to make an epub"
41 | @echo " epub3 to make an epub3"
42 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
43 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
44 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
45 | @echo " text to make text files"
46 | @echo " man to make manual pages"
47 | @echo " texinfo to make Texinfo files"
48 | @echo " info to make Texinfo files and run them through makeinfo"
49 | @echo " gettext to make PO message catalogs"
50 | @echo " changes to make an overview of all changed/added/deprecated items"
51 | @echo " xml to make Docutils-native XML files"
52 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
53 | @echo " linkcheck to check all external links for integrity"
54 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
55 | @echo " coverage to run coverage check of the documentation (if enabled)"
56 | @echo " dummy to check syntax errors of document sources"
57 |
58 | .PHONY: clean
59 | clean:
60 | rm -rf $(BUILDDIR)/*
61 |
62 | .PHONY: html
63 | html:
64 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
65 | @echo
66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
67 |
68 | .PHONY: dirhtml
69 | dirhtml:
70 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
71 | @echo
72 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
73 |
74 | .PHONY: singlehtml
75 | singlehtml:
76 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
77 | @echo
78 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
79 |
80 | .PHONY: pickle
81 | pickle:
82 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
83 | @echo
84 | @echo "Build finished; now you can process the pickle files."
85 |
86 | .PHONY: json
87 | json:
88 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
89 | @echo
90 | @echo "Build finished; now you can process the JSON files."
91 |
92 | .PHONY: htmlhelp
93 | htmlhelp:
94 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
95 | @echo
96 | @echo "Build finished; now you can run HTML Help Workshop with the" \
97 | ".hhp project file in $(BUILDDIR)/htmlhelp."
98 |
99 | .PHONY: qthelp
100 | qthelp:
101 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
102 | @echo
103 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
104 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
105 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bibmanager.qhcp"
106 | @echo "To view the help file:"
107 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bibmanager.qhc"
108 |
109 | .PHONY: applehelp
110 | applehelp:
111 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
112 | @echo
113 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
114 | @echo "N.B. You won't be able to view it unless you put it in" \
115 | "~/Library/Documentation/Help or install it in your application" \
116 | "bundle."
117 |
118 | .PHONY: devhelp
119 | devhelp:
120 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
121 | @echo
122 | @echo "Build finished."
123 | @echo "To view the help file:"
124 | @echo "# mkdir -p $$HOME/.local/share/devhelp/bibmanager"
125 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bibmanager"
126 | @echo "# devhelp"
127 |
128 | .PHONY: epub
129 | epub:
130 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
131 | @echo
132 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
133 |
134 | .PHONY: epub3
135 | epub3:
136 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
137 | @echo
138 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
139 |
140 | .PHONY: latex
141 | latex:
142 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
143 | @echo
144 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
145 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
146 | "(use \`make latexpdf' here to do that automatically)."
147 |
148 | .PHONY: latexpdf
149 | latexpdf:
150 | $(Q) $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(PDFBUILDDIR) $(O)
151 | @echo "Running LaTeX files through pdflatex..."
152 | $(Q) $(MAKE) -C $(PDFBUILDDIR) all-pdf $(O)
153 | $(Q) mv $(PDFBUILDDIR)/bibmanager.pdf $(PDFBUILDDIR)/../ $(O)
154 | $(Q) rm -fr $(PDFBUILDDIR) $(O)
155 | @echo "pdflatex finished; the PDF files are in ./latex/bibmanager.pdf"
156 |
157 | .PHONY: latexpdfja
158 | latexpdfja:
159 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
160 | @echo "Running LaTeX files through platex and dvipdfmx..."
161 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
162 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
163 |
164 | .PHONY: text
165 | text:
166 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
167 | @echo
168 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
169 |
170 | .PHONY: man
171 | man:
172 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
173 | @echo
174 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
175 |
176 | .PHONY: texinfo
177 | texinfo:
178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
179 | @echo
180 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
181 | @echo "Run \`make' in that directory to run these through makeinfo" \
182 | "(use \`make info' here to do that automatically)."
183 |
184 | .PHONY: info
185 | info:
186 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
187 | @echo "Running Texinfo files through makeinfo..."
188 | make -C $(BUILDDIR)/texinfo info
189 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
190 |
191 | .PHONY: gettext
192 | gettext:
193 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
194 | @echo
195 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
196 |
197 | .PHONY: changes
198 | changes:
199 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
200 | @echo
201 | @echo "The overview file is in $(BUILDDIR)/changes."
202 |
203 | .PHONY: linkcheck
204 | linkcheck:
205 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
206 | @echo
207 | @echo "Link check complete; look for any errors in the above output " \
208 | "or in $(BUILDDIR)/linkcheck/output.txt."
209 |
210 | .PHONY: doctest
211 | doctest:
212 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
213 | @echo "Testing of doctests in the sources finished, look at the " \
214 | "results in $(BUILDDIR)/doctest/output.txt."
215 |
216 | .PHONY: coverage
217 | coverage:
218 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
219 | @echo "Testing of coverage in the sources finished, look at the " \
220 | "results in $(BUILDDIR)/coverage/python.txt."
221 |
222 | .PHONY: xml
223 | xml:
224 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
225 | @echo
226 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
227 |
228 | .PHONY: pseudoxml
229 | pseudoxml:
230 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
231 | @echo
232 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
233 |
234 | .PHONY: dummy
235 | dummy:
236 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
237 | @echo
238 | @echo "Build finished. Dummy builder generates no files."
239 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. epub3 to make an epub3
31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
32 | echo. text to make text files
33 | echo. man to make manual pages
34 | echo. texinfo to make Texinfo files
35 | echo. gettext to make PO message catalogs
36 | echo. changes to make an overview over all changed/added/deprecated items
37 | echo. xml to make Docutils-native XML files
38 | echo. pseudoxml to make pseudoxml-XML files for display purposes
39 | echo. linkcheck to check all external links for integrity
40 | echo. doctest to run all doctests embedded in the documentation if enabled
41 | echo. coverage to run coverage check of the documentation if enabled
42 | echo. dummy to check syntax errors of document sources
43 | goto end
44 | )
45 |
46 | if "%1" == "clean" (
47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
48 | del /q /s %BUILDDIR%\*
49 | goto end
50 | )
51 |
52 |
53 | REM Check if sphinx-build is available and fallback to Python version if any
54 | %SPHINXBUILD% 1>NUL 2>NUL
55 | if errorlevel 9009 goto sphinx_python
56 | goto sphinx_ok
57 |
58 | :sphinx_python
59 |
60 | set SPHINXBUILD=python -m sphinx.__init__
61 | %SPHINXBUILD% 2> nul
62 | if errorlevel 9009 (
63 | echo.
64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
65 | echo.installed, then set the SPHINXBUILD environment variable to point
66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
67 | echo.may add the Sphinx directory to PATH.
68 | echo.
69 | echo.If you don't have Sphinx installed, grab it from
70 | echo.http://sphinx-doc.org/
71 | exit /b 1
72 | )
73 |
74 | :sphinx_ok
75 |
76 |
77 | if "%1" == "html" (
78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
79 | if errorlevel 1 exit /b 1
80 | echo.
81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
82 | goto end
83 | )
84 |
85 | if "%1" == "dirhtml" (
86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
87 | if errorlevel 1 exit /b 1
88 | echo.
89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
90 | goto end
91 | )
92 |
93 | if "%1" == "singlehtml" (
94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
95 | if errorlevel 1 exit /b 1
96 | echo.
97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
98 | goto end
99 | )
100 |
101 | if "%1" == "pickle" (
102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
103 | if errorlevel 1 exit /b 1
104 | echo.
105 | echo.Build finished; now you can process the pickle files.
106 | goto end
107 | )
108 |
109 | if "%1" == "json" (
110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
111 | if errorlevel 1 exit /b 1
112 | echo.
113 | echo.Build finished; now you can process the JSON files.
114 | goto end
115 | )
116 |
117 | if "%1" == "htmlhelp" (
118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
119 | if errorlevel 1 exit /b 1
120 | echo.
121 | echo.Build finished; now you can run HTML Help Workshop with the ^
122 | .hhp project file in %BUILDDIR%/htmlhelp.
123 | goto end
124 | )
125 |
126 | if "%1" == "qthelp" (
127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
128 | if errorlevel 1 exit /b 1
129 | echo.
130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
131 | .qhcp project file in %BUILDDIR%/qthelp, like this:
132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bibmanager.qhcp
133 | echo.To view the help file:
134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bibmanager.ghc
135 | goto end
136 | )
137 |
138 | if "%1" == "devhelp" (
139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
140 | if errorlevel 1 exit /b 1
141 | echo.
142 | echo.Build finished.
143 | goto end
144 | )
145 |
146 | if "%1" == "epub" (
147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
148 | if errorlevel 1 exit /b 1
149 | echo.
150 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
151 | goto end
152 | )
153 |
154 | if "%1" == "epub3" (
155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
156 | if errorlevel 1 exit /b 1
157 | echo.
158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
159 | goto end
160 | )
161 |
162 | if "%1" == "latex" (
163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
164 | if errorlevel 1 exit /b 1
165 | echo.
166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdf" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "latexpdfja" (
181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
182 | cd %BUILDDIR%/latex
183 | make all-pdf-ja
184 | cd %~dp0
185 | echo.
186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
187 | goto end
188 | )
189 |
190 | if "%1" == "text" (
191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
192 | if errorlevel 1 exit /b 1
193 | echo.
194 | echo.Build finished. The text files are in %BUILDDIR%/text.
195 | goto end
196 | )
197 |
198 | if "%1" == "man" (
199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
200 | if errorlevel 1 exit /b 1
201 | echo.
202 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
203 | goto end
204 | )
205 |
206 | if "%1" == "texinfo" (
207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
208 | if errorlevel 1 exit /b 1
209 | echo.
210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
211 | goto end
212 | )
213 |
214 | if "%1" == "gettext" (
215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
216 | if errorlevel 1 exit /b 1
217 | echo.
218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
219 | goto end
220 | )
221 |
222 | if "%1" == "changes" (
223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
224 | if errorlevel 1 exit /b 1
225 | echo.
226 | echo.The overview file is in %BUILDDIR%/changes.
227 | goto end
228 | )
229 |
230 | if "%1" == "linkcheck" (
231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
232 | if errorlevel 1 exit /b 1
233 | echo.
234 | echo.Link check complete; look for any errors in the above output ^
235 | or in %BUILDDIR%/linkcheck/output.txt.
236 | goto end
237 | )
238 |
239 | if "%1" == "doctest" (
240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
241 | if errorlevel 1 exit /b 1
242 | echo.
243 | echo.Testing of doctests in the sources finished, look at the ^
244 | results in %BUILDDIR%/doctest/output.txt.
245 | goto end
246 | )
247 |
248 | if "%1" == "coverage" (
249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
250 | if errorlevel 1 exit /b 1
251 | echo.
252 | echo.Testing of coverage in the sources finished, look at the ^
253 | results in %BUILDDIR%/coverage/python.txt.
254 | goto end
255 | )
256 |
257 | if "%1" == "xml" (
258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
259 | if errorlevel 1 exit /b 1
260 | echo.
261 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
262 | goto end
263 | )
264 |
265 | if "%1" == "pseudoxml" (
266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
267 | if errorlevel 1 exit /b 1
268 | echo.
269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
270 | goto end
271 | )
272 |
273 | if "%1" == "dummy" (
274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
275 | if errorlevel 1 exit /b 1
276 | echo.
277 | echo.Build finished. Dummy builder generates no files.
278 | goto end
279 | )
280 |
281 | :end
282 |
--------------------------------------------------------------------------------
/bibmanager/config_manager/config_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | __all__ = [
5 | 'help',
6 | 'display',
7 | 'get',
8 | 'set',
9 | 'update_keys',
10 | ]
11 |
12 | import os
13 | import shutil
14 | import configparser
15 | import textwrap
16 | import pathlib
17 | from packaging import version
18 |
19 | from pygments.styles import STYLE_MAP
20 |
21 | from .. import bib_manager as bm
22 | from .. import utils as u
23 | from ..__init__ import __version__
24 |
25 |
26 | styles = textwrap.fill(
27 | ", ".join(style for style in iter(STYLE_MAP)),
28 | width=79,
29 | initial_indent=" ",
30 | subsequent_indent=" ")
31 |
32 |
33 | def help(key):
34 | """
35 | Display help information.
36 |
37 | Parameters
38 | ----------
39 | key: String
40 | A bibmanager config parameter.
41 | """
42 | style_text = (
43 | f"\nThe '{key}' parameter sets the color-syntax style of displayed "
44 | "BibTeX entries.\nThe default style is 'autumn'. Available options "
45 | f"are:\n{styles}\nSee http://pygments.org/demo/6780986/ for a demo "
46 | f"of the style options.\n\nThe current style is '{get(key)}'.")
47 |
48 | editor_text = (
49 | f"\nThe '{key}' parameter sets the text editor to use when "
50 | "editing the\nbibmanager manually (i.e., a call to: bibm edit). By "
51 | "default, bibmanager\nuses the OS-default text editor.\n\n"
52 | "Typical text editors are: emacs, vim, gedit.\n"
53 | "To set the OS-default editor, set text_editor to 'default'.\n"
54 | "Note that aliases defined in the .bash are not accessible.\n\n"
55 | f"The current text editor is '{get(key)}'.")
56 |
57 | paper_text = (
58 | f"\nThe '{key}' parameter sets the default paper format for latex "
59 | "compilation outputs\n(not for pdflatex, which is automatic). "
60 | "Typical options are 'letter'\n(e.g., for ApJ articles) or 'A4' "
61 | f"(e.g., for A&A).\n\nThe current paper format is: '{get(key)}'.")
62 |
63 | token_text = (
64 | f"\nThe '{key}' parameter sets the ADS token required for ADS requests."
65 | "\nTo obtain a token, follow the steps described here:"
66 | "\n https://github.com/adsabs/adsabs-dev-api#access"
67 | f"\n\nThe current ADS token is '{get(key)}'")
68 |
69 | display_text = (
70 | f"\nThe '{key}' parameter sets the number of entries to show at "
71 | "a time,\nfor an ADS search query.\n\n"
72 | f"The current number of entries to display is {get(key)}.")
73 |
74 | home_text = (
75 | f"\nThe '{key}' parameter sets the home directory for the Bibmanager "
76 | f"database.\n\nThe current directory is '{get(key)}'.")
77 |
78 | if key == 'style':
79 | print(style_text)
80 | elif key == 'text_editor':
81 | print(editor_text)
82 | elif key == 'paper':
83 | print(paper_text)
84 | elif key == 'ads_token':
85 | print(token_text)
86 | elif key == 'ads_display':
87 | print(display_text)
88 | elif key == 'home':
89 | print(home_text)
90 | else:
91 | # Call get() to trigger exception:
92 | get(key)
93 |
94 |
95 | def display(key=None):
96 | """
97 | Display the value(s) of the bibmanager config file on the prompt.
98 |
99 | Parameters
100 | ----------
101 | key: String
102 | bibmanager config parameter to display. Leave as None to display
103 | the values from all parameters.
104 |
105 | Examples
106 | --------
107 | >>> import bibmanager.config_manager as cm
108 | >>> # Show all parameters and values:
109 | >>> cm.display()
110 | bibmanager configuration file:
111 | PARAMETER VALUE
112 | ----------- -----
113 | style autumn
114 | text_editor default
115 | paper letter
116 | ads_token None
117 | ads_display 20
118 | home /home/user/.bibmanager/
119 |
120 | >>> # Show an specific parameter:
121 | >>> cm.display('text_editor')
122 | text_editor: default
123 | """
124 | if key is not None:
125 | print(f"{key}: {get(key)}")
126 | else:
127 | config = configparser.ConfigParser()
128 | config.read(u.HOME + 'config')
129 | print("\nbibmanager configuration file:"
130 | "\nPARAMETER VALUE"
131 | "\n----------- -----")
132 | for key, value in config['BIBMANAGER'].items():
133 | print(f"{key:11} {value}")
134 |
135 |
136 | def get(key):
137 | """
138 | Get the value of a parameter in the bibmanager config file.
139 |
140 | Parameters
141 | ----------
142 | key: String
143 | The requested parameter name.
144 |
145 | Returns
146 | -------
147 | value: String
148 | Value of the requested parameter.
149 |
150 | Examples
151 | --------
152 | >>> import bibmanager.config_manager as cm
153 | >>> cm.get('paper')
154 | 'letter'
155 | >>> cm.get('style')
156 | 'autumn'
157 | """
158 | config = configparser.ConfigParser()
159 | config.read(u.HOME + 'config')
160 |
161 | if not config.has_option('BIBMANAGER', key):
162 | rconfig = configparser.ConfigParser()
163 | rconfig.read(u.ROOT+'config')
164 | raise ValueError(
165 | f"'{key}' is not a valid bibmanager config parameter.\n"
166 | f"The available parameters are:\n {rconfig.options('BIBMANAGER')}")
167 | return config.get('BIBMANAGER', key)
168 |
169 |
170 | def set(key, value):
171 | """
172 | Set the value of a bibmanager config parameter.
173 |
174 | Parameters
175 | ----------
176 | key: String
177 | bibmanager config parameter to set.
178 | value: String
179 | Value to set for input parameter.
180 |
181 | Examples
182 | --------
183 | >>> import bibmanager.config_manager as cm
184 | >>> # Update text editor:
185 | >>> cm.set('text_editor', 'vim')
186 | text_editor updated to: vim.
187 |
188 | >>> # Invalid bibmanager parameter:
189 | >>> cm.set('styles', 'arduino')
190 | ValueError: 'styles' is not a valid bibmanager config parameter.
191 | The available parameters are:
192 | ['style', 'text_editor', 'paper', 'ads_token', 'ads_display', 'home']
193 |
194 | >>> # Attempt to set an invalid style:
195 | >>> cm.set('style', 'fake_style')
196 | ValueError: 'fake_style' is not a valid style option. Available options are:
197 | default, emacs, friendly, colorful, autumn, murphy, manni, monokai, perldoc,
198 | pastie, borland, trac, native, fruity, bw, vim, vs, tango, rrt, xcode, igor,
199 | paraiso-light, paraiso-dark, lovelace, algol, algol_nu, arduino,
200 | rainbow_dash, abap
201 |
202 | >>> # Attempt to set an invalid command for text_editor:
203 | >>> cm.set('text_editor', 'my_own_editor')
204 | ValueError: 'my_own_editor' is not a valid text editor.
205 |
206 | >>> # Beware, one can still set a valid command that doesn't edit text:
207 | >>> cm.set('text_editor', 'less')
208 | text_editor updated to: less.
209 | """
210 | config = configparser.ConfigParser()
211 | config.read(u.HOME + 'config')
212 |
213 | # Use get on invalid key to raise an error:
214 | if not config.has_option('BIBMANAGER', key):
215 | get(key)
216 |
217 | # Check for exceptions:
218 | if key == 'style' and value not in STYLE_MAP.keys():
219 | raise ValueError(
220 | f"'{value}' is not a valid style option. "
221 | f"Available options are:\n{styles}")
222 |
223 | # The code identifies invalid commands, but cannot assure that a
224 | # command actually applies to a text file.
225 | if key == 'text_editor' \
226 | and value != 'default' \
227 | and shutil.which(value) is None:
228 | raise ValueError(f"'{value}' is not a valid text editor.")
229 |
230 | if key == 'ads_display' and (not value.isnumeric() or value=='0'):
231 | raise ValueError(f"The {key} value must be a positive integer.")
232 |
233 | if key == 'home':
234 | value = os.path.abspath(os.path.expanduser(value)) + '/'
235 | new_home = pathlib.Path(value)
236 | if not new_home.parent.is_dir():
237 | raise ValueError(
238 | f"The {key} value must have an existing parent folder")
239 | if new_home.suffix != '':
240 | raise ValueError(f"The {key} value cannot have a file extension")
241 |
242 | # Make sure folders will exist:
243 | new_home.mkdir(exist_ok=True)
244 | pathlib.Path(f'{value}pdf').mkdir(exist_ok=True)
245 |
246 | # Files to move (config has to stay at u.HOME):
247 | bm_files = [
248 | u.BM_DATABASE(),
249 | u.BM_BIBFILE(),
250 | u.BM_CACHE(),
251 | u.BM_HISTORY_SEARCH(),
252 | u.BM_HISTORY_ADS(),
253 | u.BM_HISTORY_PDF(),
254 | ]
255 | pdf_files = [
256 | f'{u.BM_PDF()}{bib.pdf}' for bib in bm.load()
257 | if bib.pdf is not None
258 | if os.path.isfile(f'{u.BM_PDF()}{bib.pdf}')
259 | ]
260 |
261 | # Merge if there is already a Bibmanager database in new_home:
262 | new_database = f'{new_home}/{os.path.basename(u.BM_DATABASE())}'
263 | if os.path.isfile(new_database):
264 | pickle_ver = bm.get_version(new_database)
265 | if version.parse(__version__) < version.parse(pickle_ver):
266 | print(f"Bibmanager version ({__version__}) is older than saved "
267 | f"database. Please update to a version >= {pickle_ver}.")
268 | return
269 | new_biblio = f'{new_home}/{os.path.basename(u.BM_BIBFILE())}'
270 | bm.merge(new=bm.read_file(new_biblio), take='new')
271 |
272 | # Move (overwrite) database files:
273 | bm_files = [bm_file for bm_file in bm_files if os.path.isfile(bm_file)]
274 | new_files = os.listdir(new_home)
275 | for bm_file in bm_files:
276 | if os.path.basename(bm_file) in new_files:
277 | os.remove(f'{new_home}/{os.path.basename(bm_file)}')
278 | shutil.move(bm_file, str(new_home))
279 |
280 | # Move (overwrite) PDF files:
281 | new_pdfs = os.listdir(f'{new_home}/pdf')
282 | for pdf_file in pdf_files:
283 | if os.path.basename(pdf_file) in new_pdfs:
284 | os.remove(f'{new_home}/pdf/{os.path.basename(pdf_file)}')
285 | shutil.move(pdf_file, f'{new_home}/pdf/')
286 |
287 | # Set value if there were no exceptions raised:
288 | config.set('BIBMANAGER', key, value)
289 | with open(u.HOME+'config', 'w', encoding='utf-8') as configfile:
290 | config.write(configfile)
291 | print(f'{key} updated to: {value}.')
292 |
293 |
294 | def update_keys():
295 | """Update config in HOME with keys from ROOT, without overwriting values."""
296 | config_root = configparser.ConfigParser()
297 | config_root.read(u.ROOT+'config')
298 | config_root.set('BIBMANAGER', 'home', u.HOME)
299 | # Won't complain if HOME+'config' does not exist (keep ROOT values):
300 | config_root.read(u.HOME+'config')
301 | with open(u.HOME+'config', 'w', encoding='utf-8') as configfile:
302 | config_root.write(configfile)
303 |
--------------------------------------------------------------------------------
/bibmanager/examples/sample.bib:
--------------------------------------------------------------------------------
1 | @misc{AASteamHendrickson2018aastex62,
2 | author = {{AAS Journals Team} and
3 | {Hendrickson}, Amy},
4 | title = {{AASJournals/AASTeX60: Version 6.2 official release}},
5 | month = feb,
6 | year = 2018,
7 | doi = {10.5281/zenodo.1209290},
8 | url = {https://doi.org/10.5281/zenodo.1209290}
9 | }
10 |
11 | @ARTICLE{Astropycollab2013aaAstropy,
12 | author = {{Astropy Collaboration} and {Robitaille}, T.~P. and {Tollerud}, E.~J. and
13 | {Greenfield}, P. and {Droettboom}, M. and {Bray}, E. and {Aldcroft}, T. and
14 | {Davis}, M. and {Ginsburg}, A. and {Price-Whelan}, A.~M. and
15 | {Kerzendorf}, W.~E. and {Conley}, A. and {Crighton}, N. and
16 | {Barbary}, K. and {Muna}, D. and {Ferguson}, H. and {Grollier}, F. and
17 | {Parikh}, M.~M. and {Nair}, P.~H. and {Unther}, H.~M. and {Deil}, C. and
18 | {Woillez}, J. and {Conseil}, S. and {Kramer}, R. and {Turner}, J.~E.~H. and
19 | {Singer}, L. and {Fox}, R. and {Weaver}, B.~A. and {Zabalza}, V. and
20 | {Edwards}, Z.~I. and {Azalee Bostroem}, K. and {Burke}, D.~J. and
21 | {Casey}, A.~R. and {Crawford}, S.~M. and {Dencheva}, N. and
22 | {Ely}, J. and {Jenness}, T. and {Labrie}, K. and {Lim}, P.~L. and
23 | {Pierfederici}, F. and {Pontzen}, A. and {Ptak}, A. and {Refsdal}, B. and
24 | {Servillat}, M. and {Streicher}, O.},
25 | title = "{Astropy: A community Python package for astronomy}",
26 | journal = {\aap},
27 | archivePrefix = "arXiv",
28 | eprint = {1307.6212},
29 | primaryClass = "astro-ph.IM",
30 | keywords = {methods: data analysis, methods: miscellaneous, virtual observatory tools},
31 | year = 2013,
32 | month = oct,
33 | volume = 558,
34 | eid = {A33},
35 | pages = {A33},
36 | doi = {10.1051/0004-6361/201322068},
37 | adsurl = {http://adsabs.harvard.edu/abs/2013A%26A...558A..33A},
38 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
39 | }
40 |
41 | @ARTICLE{BeaulieuEtal2010arxivGJ436b,
42 | author = {{Beaulieu}, J.-P. and {Tinetti}, G. and {Kipping}, D.~M. and
43 | {Ribas}, I. and {Barber}, R.~J. and {Cho}, J.~Y.-K. and {Polichtchouk}, I. and
44 | {Tennyson}, J. and {Yurchenko}, S.~N. and {Griffith}, C.~A. and
45 | {Batista}, V. and {Waldmann}, I. and {Miller}, S. and {Carey}, S. and
46 | {Mousis}, O. and {Fossey}, S.~J. and {Aylward}, A.},
47 | title = "Methane in the Atmosphere of the Transiting Hot {Neptune GJ436b}?",
48 | journal = {\apj},
49 | eprint = {arXiv:1007.0324},
50 | primaryClass = "astro-ph.EP",
51 | keywords = {planetary systems, techniques: spectroscopic},
52 | year = 2010,
53 | month = apr,
54 | volume = 731,
55 | pages = {16},
56 | doi = {10.1088/0004-637X/731/1/16},
57 | adsurl = {http://adsabs.harvard.edu/abs/2010arXiv1007.0324B},
58 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
59 | }
60 |
61 | @ARTICLE{BurbidgeEtal1957rvmpStellarElementSynthesis,
62 | author = {{Burbidge}, E. Margaret and {Burbidge}, G.~R. and {Fowler}, William A.
63 | and {Hoyle}, F.},
64 | title = "{Synthesis of the Elements in Stars}",
65 | journal = {Reviews of Modern Physics},
66 | year = 1957,
67 | month = Jan,
68 | volume = {29},
69 | pages = {547-650},
70 | doi = {10.1103/RevModPhys.29.547},
71 | adsurl = {https://ui.adsabs.harvard.edu/abs/1957RvMP...29..547B},
72 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
73 | }
74 |
75 | freeze
76 | @MISC{Cubillos2019zndoBibmanager,
77 | author = {{Cubillos}, Patricio E.},
78 | title = "{bibmanager: A BibTeX manager for LaTeX projects, Zenodo, doi:\href{https://zenodo.org/record/2547042}{10.5281/zenodo.2547042}}",
79 | year = 2019,
80 | month = apr,
81 | eid = {10.5281/zenodo.2547042},
82 | doi = {10.5281/zenodo.2547042},
83 | version = {v1.1.0},
84 | publisher = {Zenodo},
85 | adsurl = {https://ui.adsabs.harvard.edu/abs/2019zndo...2547042C},
86 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
87 | }
88 |
89 | @ARTICLE{Curtis1917paspIslandUniverseTheory,
90 | author = {{Curtis}, H.~D.},
91 | title = "{Novae in the Spiral Nebulae and the Island Universe Theory}",
92 | journal = {\pasp},
93 | year = 1917,
94 | month = oct,
95 | volume = 29,
96 | pages = {206-207},
97 | doi = {10.1086/122632},
98 | adsurl = {http://adsabs.harvard.edu/abs/1917PASP...29..206C},
99 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
100 | }
101 |
102 | @ARTICLE{HarrisEtal2020natNumpy,
103 | author = {{Harris}, Charles R. and {Millman}, K. Jarrod and {van der Walt}, St{\'e}fan J. and {Gommers}, Ralf and {Virtanen}, Pauli and {Cournapeau}, David and {Wieser}, Eric and {Taylor}, Julian and {Berg}, Sebastian and {Smith}, Nathaniel J. and {Kern}, Robert and {Picus}, Matti and {Hoyer}, Stephan and {van Kerkwijk}, Marten H. and {Brett}, Matthew and {Haldane}, Allan and {del R{\'\i}o}, Jaime Fern{\'a}ndez and {Wiebe}, Mark and {Peterson}, Pearu and {G{\'e}rard-Marchant}, Pierre and {Sheppard}, Kevin and {Reddy}, Tyler and {Weckesser}, Warren and {Abbasi}, Hameer and {Gohlke}, Christoph and {Oliphant}, Travis E.},
104 | title = "{Array programming with NumPy}",
105 | journal = {\nat},
106 | keywords = {Computer Science - Mathematical Software, Statistics - Computation},
107 | year = 2020,
108 | month = sep,
109 | volume = {585},
110 | number = {7825},
111 | pages = {357-362},
112 | doi = {10.1038/s41586-020-2649-2},
113 | archivePrefix = {arXiv},
114 | eprint = {2006.10256},
115 | primaryClass = {cs.MS},
116 | adsurl = {https://ui.adsabs.harvard.edu/abs/2020Natur.585..357H},
117 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
118 | }
119 |
120 | @Article{Hunter2007ieeeMatplotlib,
121 | Author = {{Hunter}, J. D.},
122 | Title = {Matplotlib: A 2D graphics environment},
123 | Journal = {Computing In Science \& Engineering},
124 | Volume = {9},
125 | Number = {3},
126 | Pages = {90--95},
127 | abstract = {Matplotlib is a 2D graphics package used for Python
128 | for application development, interactive scripting, and
129 | publication-quality image generation across user
130 | interfaces and operating systems.},
131 | publisher = {IEEE COMPUTER SOC},
132 | doi = {10.1109/MCSE.2007.55},
133 | year = 2007
134 | }
135 |
136 | @article{MeurerEtal2017pjcsSYMPY,
137 | title = {SymPy: symbolic computing in Python},
138 | author = {Meurer, Aaron and Smith, Christopher P. and Paprocki, Mateusz and \v{C}ert\'{i}k, Ond\v{r}ej and Kirpichev, Sergey B. and Rocklin, Matthew and Kumar, AMiT and Ivanov, Sergiu and Moore, Jason K. and Singh, Sartaj and Rathnayake, Thilina and Vig, Sean and Granger, Brian E. and Muller, Richard P. and Bonazzi, Francesco and Gupta, Harsh and Vats, Shivam and Johansson, Fredrik and Pedregosa, Fabian and Curry, Matthew J. and Terrel, Andy R. and Rou\v{c}ka, \v{S}t\v{e}p\'{a}n and Saboo, Ashutosh and Fernando, Isuru and Kulal, Sumith and Cimrman, Robert and Scopatz, Anthony},
139 | year = 2017,
140 | month = jan,
141 | keywords = {Python, Computer algebra system, Symbolics},
142 | volume = 3,
143 | pages = {e103},
144 | journal = {PeerJ Computer Science},
145 | issn = {2376-5992},
146 | url = {https://doi.org/10.7717/peerj-cs.103},
147 | doi = {10.7717/peerj-cs.103}
148 | }
149 |
150 | @Article{PerezGranger2007cseIPython,
151 | Author = {{P\'erez}, F. and {Granger}, B.~E.},
152 | Title = {{IP}ython: a System for Interactive Scientific Computing},
153 | Journal = {Computing in Science and Engineering},
154 | Volume = {9},
155 | Number = {3},
156 | Pages = {21--29},
157 | month = may,
158 | year = 2007,
159 | url = "http://ipython.org",
160 | ISSN = "1521-9615",
161 | doi = {10.1109/MCSE.2007.53},
162 | publisher = {IEEE Computer Society},
163 | }
164 |
165 | @ARTICLE{Shapley1918apjDistanceGlobularClusters,
166 | author = {{Shapley}, H.},
167 | title = "{Studies based on the colors and magnitudes in stellar clusters. VII. The distances, distribution in space, and dimensions of 69 globular clusters.}",
168 | journal = {\apj},
169 | year = 1918,
170 | month = oct,
171 | volume = 48,
172 | doi = {10.1086/142423},
173 | adsurl = {http://adsabs.harvard.edu/abs/1918ApJ....48..154S},
174 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
175 | }
176 |
177 | @ARTICLE{ShowmanEtal2009apjRadGCM,
178 | author = {{Showman}, Adam P. and {Fortney}, Jonathan J. and {Lian}, Yuan and
179 | {Marley}, Mark S. and {Freedman}, Richard S. and {Knutson},
180 | Heather A. and {Charbonneau}, David},
181 | title = "{Atmospheric Circulation of Hot Jupiters: Coupled Radiative-Dynamical General Circulation Model Simulations of HD 189733b and HD 209458b}",
182 | journal = {\apj},
183 | keywords = {atmospheric effects, methods: numerical, planets and satellites: general, planets and satellites: individual: HD 209458b HD 189733b, Astrophysics},
184 | year = 2009,
185 | month = Jul,
186 | volume = {699},
187 | pages = {564-584},
188 | doi = {10.1088/0004-637X/699/1/564},
189 | archivePrefix = {arXiv},
190 | eprint = {0809.2089},
191 | primaryClass = {astro-ph},
192 | adsurl = {https://ui.adsabs.harvard.edu/abs/2009ApJ...699..564S},
193 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
194 | }
195 |
196 | pdf: Slipher1913.pdf
197 | @ARTICLE{Slipher1913lobAndromedaRarialVelocity,
198 | author = {{Slipher}, V.~M.},
199 | title = "{The radial velocity of the Andromeda Nebula}",
200 | journal = {Lowell Observatory Bulletin},
201 | keywords = {GALAXIES: MOTION IN LINE OF SIGHT, ANDROMEDA GALAXY},
202 | year = 1913,
203 | month = Jan,
204 | volume = {1},
205 | pages = {56-57},
206 | adsurl = {https://ui.adsabs.harvard.edu/abs/1913LowOB...2...56S},
207 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
208 | }
209 |
210 | @ARTICLE{VirtanenEtal2020natmeScipy,
211 | author = {{Virtanen}, Pauli and {Gommers}, Ralf and {Oliphant}, Travis E. and {Haberland}, Matt and {Reddy}, Tyler and {Cournapeau}, David and {Burovski}, Evgeni and {Peterson}, Pearu and {Weckesser}, Warren and {Bright}, Jonathan and {van der Walt}, St{\'e}fan J. and {Brett}, Matthew and {Wilson}, Joshua and {Millman}, K. Jarrod and {Mayorov}, Nikolay and {Nelson}, Andrew R.~J. and {Jones}, Eric and {Kern}, Robert and {Larson}, Eric and {Carey}, C.~J. and {Polat}, {\.I}lhan and {Feng}, Yu and {Moore}, Eric W. and {VanderPlas}, Jake and {Laxalde}, Denis and {Perktold}, Josef and {Cimrman}, Robert and {Henriksen}, Ian and {Quintero}, E.~A. and {Harris}, Charles R. and {Archibald}, Anne M. and {Ribeiro}, Ant{\^o}nio H. and {Pedregosa}, Fabian and {van Mulbregt}, Paul and {SciPy 1. 0 Contributors}},
212 | title = "{SciPy 1.0: fundamental algorithms for scientific computing in Python}",
213 | journal = {Nature Methods},
214 | keywords = {Computer Science - Mathematical Software, Computer Science - Data Structures and Algorithms, Computer Science - Software Engineering, Physics - Computational Physics},
215 | year = 2020,
216 | month = feb,
217 | volume = {17},
218 | pages = {261-272},
219 | doi = {10.1038/s41592-019-0686-2},
220 | archivePrefix = {arXiv},
221 | eprint = {1907.10121},
222 | primaryClass = {cs.MS},
223 | adsurl = {https://ui.adsabs.harvard.edu/abs/2020NatMe..17..261V},
224 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
225 | }
226 |
227 | @Misc{JonesNoYearScipy,
228 | author = {Eric Jones},
229 | title = {{SciPy}: Scientific Python},
230 | }
231 |
232 | @MISC{1978windEnergyReport,
233 | title = "{Wind energy systems: Program summary}",
234 | year = 1978,
235 | month = dec,
236 | adsurl = {https://ui.adsabs.harvard.edu/abs/1978wes..rept......},
237 | }
238 |
239 |
--------------------------------------------------------------------------------
/bibmanager/examples/top-apj.tex:
--------------------------------------------------------------------------------
1 | % latbibdo template
2 |
3 | \newcommand\apjcls{1}
4 | \newcommand\aastexcls{2}
5 | \newcommand\othercls{3}
6 |
7 | % Select ony one pair of \papercls and class file:
8 |
9 | % AASTEX61 cls:
10 | \newcommand\papercls{\aastexcls}
11 | %\documentclass[tighten, times, twocolumn]{aastex62}
12 | \documentclass[tighten, times, trackchanges, twocolumn]{aastex62}
13 | %\documentclass[tighten, times, twocolumn, twocolappendix]{aastex62}
14 | %\documentclass[tighten, times, manuscript]{aastex62} % Onecolumn, doublespaced
15 |
16 | % Emulate ApJ cls:
17 | %\newcommand\papercls{\apjcls}
18 | %\documentclass[iop]{emulateapj}
19 |
20 | % Other cls:
21 | %\newcommand\papercls{\othercls}
22 | %\documentclass[letterpaper,12pt]{article}
23 |
24 |
25 | %% :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
26 | % These are latex packages that enable various capability.
27 |
28 | \if\papercls \apjcls
29 | \usepackage{apjfonts}
30 | \else\if\papercls \othercls
31 | \usepackage{epsfig}
32 | \usepackage{margin}
33 | % times font (for \othercls):
34 | \usepackage{times}
35 | \fi\fi
36 | \usepackage{ifthen}
37 | \usepackage{natbib}
38 | \usepackage{amssymb}
39 | \usepackage[fleqn]{amsmath}
40 | \usepackage{appendix}
41 | \usepackage{etoolbox}
42 | \usepackage[T1]{fontenc}
43 | \usepackage{paralist}
44 |
45 | % This one defines a few more journals (DPS and AAS abstracts) for bibtex:
46 | \if\papercls \apjcls
47 | \newcommand\aas{\ref@jnl{AAS Meeting Abstracts}}% *** added by jh
48 | % American Astronomical Society Meeting Abstracts
49 | \newcommand\dps{\ref@jnl{AAS/DPS Meeting Abstracts}}% *** added by jh
50 | % American Astronomical Society/Division for Planetary Sciences Meeting Abstracts
51 | \newcommand\maps{\ref@jnl{MAPS}}% *** added by jh
52 | % Meteoritics and Planetary Science
53 | \else\if\papercls \othercls
54 | \usepackage{astjnlabbrev-jh}
55 | \fi\fi
56 |
57 | % Bibliographystyle chooses a bibtex .bst file, which defines the
58 | % format of the references. It's important to pick one that works for
59 | % the journal you are writing for and that has hyperlinks for the
60 | % actual paper online.
61 | \bibliographystyle{apj_hyperref}
62 | %\bibliographystyle{aasjournal}
63 |
64 | % Enable this for packed reference list:
65 | %\setlength\bibsep{0.0pt}
66 |
67 | % Enable this to remove section numbers:
68 | %\setcounter{secnumdepth}{0}
69 |
70 | %% % Enable this for bullet-point separated references:
71 | %% \usepackage{paralist}
72 | %% \renewenvironment{thebibliography}[1]{\let\par\relax%
73 | %% \section*{\refname}\inparaitem}{\endinparaitem}
74 | %% \let\oldbibitem\bibitem
75 | %% \renewcommand{\bibitem}{\item[\textbullet]\oldbibitem}
76 |
77 |
78 | % Setup hyperreferences style:
79 | \if\papercls \aastexcls
80 | \hypersetup{citecolor=blue, % color for \cite{...} links
81 | linkcolor=blue, % color for \ref{...} links
82 | menucolor=blue, % color for Acrobat menu buttons
83 | urlcolor=blue} % color for \url{...} links
84 | \else
85 | \usepackage[%pdftex, % hyper-references for pdflatex
86 | bookmarks=true, %%% generate bookmarks ...
87 | bookmarksnumbered=true, %%% ... with numbers
88 | colorlinks=true, % links are colored
89 | citecolor=blue, % color for \cite{...} links
90 | linkcolor=blue, % color for \ref{...} links
91 | menucolor=blue, % color for Acrobat menu buttons
92 | urlcolor=blue, % color for \url{...} links
93 | linkbordercolor={0 0 1}, %%% blue frames around links
94 | pdfborder={0 0 1},
95 | frenchlinks=true]{hyperref}
96 | \fi
97 |
98 | % These macross generate the hyperlinks in the References section:
99 | \if\papercls \othercls
100 | \newcommand{\eprint}[1]{\href{http://arxiv.org/abs/#1}{#1}}
101 | \else
102 | \renewcommand{\eprint}[1]{\href{http://arxiv.org/abs/#1}{#1}}
103 | \fi
104 | \newcommand{\ISBN}[1]{\href{http://cosmologist.info/ISBN/#1}{ISBN: #1}}
105 | \providecommand{\adsurl}[1]{\href{#1}{ADS}}
106 |
107 | % hyper ref only the year in citations:
108 | \makeatletter
109 | % Patch case where name and year are separated by aysep
110 | \patchcmd{\NAT@citex}
111 | {\@citea\NAT@hyper@{%
112 | \NAT@nmfmt{\NAT@nm}%
113 | \hyper@natlinkbreak{\NAT@aysep\NAT@spacechar}{\@citeb\@extra@b@citeb}%
114 | \NAT@date}}
115 | {\@citea\NAT@nmfmt{\NAT@nm}%
116 | \NAT@aysep\NAT@spacechar\NAT@hyper@{\NAT@date}}{}{}
117 |
118 | % Patch case where name and year are separated by opening bracket
119 | \patchcmd{\NAT@citex}
120 | {\@citea\NAT@hyper@{%
121 | \NAT@nmfmt{\NAT@nm}%
122 | \hyper@natlinkbreak{\NAT@spacechar\NAT@@open\if*#1*\else#1\NAT@spacechar\fi}%
123 | {\@citeb\@extra@b@citeb}%
124 | \NAT@date}}
125 | {\@citea\NAT@nmfmt{\NAT@nm}%
126 | \NAT@spacechar\NAT@@open\if*#1*\else#1\NAT@spacechar\fi\NAT@hyper@{\NAT@date}}
127 | {}{}
128 | \makeatother
129 |
130 | % Define lowcase: a MakeLowercase that doesn't break on subtitles:
131 | \makeatletter
132 | \DeclareRobustCommand{\lowcase}[1]{\@lowcase#1\@nil}
133 | \def\@lowcase#1\@nil{\if\relax#1\relax\else\MakeLowercase{#1}\fi}
134 | \pdfstringdefDisableCommands{\let\lowcase\@firstofone}
135 | \makeatother
136 |
137 | % unslanted mu, for ``micro'' abbrev.
138 | \DeclareSymbolFont{UPM}{U}{eur}{m}{n}
139 | \DeclareMathSymbol{\umu}{0}{UPM}{"16}
140 | \let\oldumu=\umu
141 | \renewcommand\umu{\ifmmode\oldumu\else\math{\oldumu}\fi}
142 | \newcommand\micro{\umu}
143 | \if\papercls \othercls
144 | \newcommand\micron{\micro m}
145 | \else
146 | \renewcommand\micron{\micro m}
147 | \fi
148 | \newcommand\microns{\micron}
149 | \newcommand\microbar{\micro bar}
150 |
151 | % These define commands outside of math mode:
152 | % \sim
153 | \let\oldsim=\sim
154 | \renewcommand\sim{\ifmmode\oldsim\else\math{\oldsim}\fi}
155 | % \pm
156 | \let\oldpm=\pm
157 | \renewcommand\pm{\ifmmode\oldpm\else\math{\oldpm}\fi}
158 | % \times
159 | \newcommand\by{\ifmmode\times\else\math{\times}\fi}
160 | % Ten-to-the-X and times-ten-to-the-X:
161 | \newcommand\ttt[1]{10\sp{#1}}
162 | \newcommand\tttt[1]{\by\ttt{#1}}
163 |
164 | % A tablebox lets you define some lines in a block, using \\ to end
165 | % them. The block moves as a unit. Good for addresses, quick lists, etc.
166 | \newcommand\tablebox[1]{\begin{tabular}[t]{@{}l@{}}#1\end{tabular}}
167 | % These commands are blank space exactly the width of various
168 | % numerical components, for spacing out tables.
169 | \newbox{\wdbox}
170 | \renewcommand\c{\setbox\wdbox=\hbox{,}\hspace{\wd\wdbox}}
171 | \renewcommand\i{\setbox\wdbox=\hbox{i}\hspace{\wd\wdbox}}
172 | \newcommand\n{\hspace{0.5em}}
173 |
174 | % \marnote puts a note in the margin:
175 | \newcommand\marnote[1]{\marginpar{\raggedright\tiny\ttfamily\baselineskip=9pt #1}}
176 | % \herenote makes a bold note and screams at you when you compile the
177 | % document. Good for reminding yourself to do something before the
178 | % document is done.
179 | \newcommand\herenote[1]{{\bfseries #1}\typeout{======================> note on page \arabic{page} <====================}}
180 | % These are common herenotes:
181 | \newcommand\fillin{\herenote{fill in}}
182 | \newcommand\fillref{\herenote{ref}}
183 | \newcommand\findme[1]{\herenote{FINDME #1}}
184 |
185 | % \now is the current time. Convenient for saying when the draft was
186 | % last modified.
187 | \newcount\timect
188 | \newcount\hourct
189 | \newcount\minct
190 | \newcommand\now{\timect=\time \divide\timect by 60
191 | \hourct=\timect \multiply\hourct by 60
192 | \minct=\time \advance\minct by -\hourct
193 | \number\timect:\ifnum \minct < 10 0\fi\number\minct}
194 |
195 | % This is pretty much like \citealp
196 | \newcommand\citeauthyear[1]{\citeauthor{#1} \citeyear{#1}}
197 |
198 | % These are short for multicolumn, to shorten the length of table lines.
199 | \newcommand\mc{\multicolumn}
200 | \newcommand\mctc{\multicolumn{2}{c}}
201 |
202 | % Joetex character unreservations.
203 | % This file frees most of TeX's reserved characters, and provides
204 | % several alternatives for their functions.
205 |
206 | % Tue Mar 29 22:23:03 EST 1994
207 | % modified 12 Oct 2000 for AASTeX header
208 |
209 | % utility
210 | \catcode`@=11
211 |
212 | % Define comment command:
213 | \newcommand\comment[1]{}
214 |
215 | % Undefine '%' as special character:
216 | \newcommand\commenton{\catcode`\%=14}
217 | \newcommand\commentoff{\catcode`\%=12}
218 |
219 | % Undefine '$' as special character:
220 | \renewcommand\math[1]{$#1$}
221 | \newcommand\mathshifton{\catcode`\$=3}
222 | \newcommand\mathshiftoff{\catcode`\$=12}
223 |
224 | % Undefine '&' as special character:
225 | \let\atab=&
226 | \newcommand\atabon{\catcode`\&=4}
227 | \newcommand\ataboff{\catcode`\&=12}
228 |
229 | % Define \sp and \sb for superscripts and subscripts:
230 | \let\oldmsp=\sp
231 | \let\oldmsb=\sb
232 | \def\sp#1{\ifmmode
233 | \oldmsp{#1}%
234 | \else\strut\raise.85ex\hbox{\scriptsize #1}\fi}
235 | \def\sb#1{\ifmmode
236 | \oldmsb{#1}%
237 | \else\strut\raise-.54ex\hbox{\scriptsize #1}\fi}
238 | \newbox\@sp
239 | \newbox\@sb
240 | \def\sbp#1#2{\ifmmode%
241 | \oldmsb{#1}\oldmsp{#2}%
242 | \else
243 | \setbox\@sb=\hbox{\sb{#1}}%
244 | \setbox\@sp=\hbox{\sp{#2}}%
245 | \rlap{\copy\@sb}\copy\@sp
246 | \ifdim \wd\@sb >\wd\@sp
247 | \hskip -\wd\@sp \hskip \wd\@sb
248 | \fi
249 | \fi}
250 | \def\msp#1{\ifmmode
251 | \oldmsp{#1}
252 | \else \math{\oldmsp{#1}}\fi}
253 | \def\msb#1{\ifmmode
254 | \oldmsb{#1}
255 | \else \math{\oldmsb{#1}}\fi}
256 |
257 | % Undefine '^' as special character:
258 | \def\supon{\catcode`\^=7}
259 | \def\supoff{\catcode`\^=12}
260 |
261 | % Undefine '_' as special character:
262 | \def\subon{\catcode`\_=8}
263 | \def\suboff{\catcode`\_=12}
264 |
265 | \def\supsubon{\supon \subon}
266 | \def\supsuboff{\supoff \suboff}
267 |
268 | % Undefine '~' as special character:
269 | \newcommand\actcharon{\catcode`\~=13}
270 | \newcommand\actcharoff{\catcode`\~=12}
271 |
272 | % Undefine '#' as special character:
273 | \newcommand\paramon{\catcode`\#=6}
274 | \newcommand\paramoff{\catcode`\#=12}
275 |
276 | \comment{And now to turn us totally on and off...}
277 |
278 | \newcommand\reservedcharson{ \commenton \mathshifton \atabon \supsubon
279 | \actcharon \paramon}
280 |
281 | \newcommand\reservedcharsoff{\commentoff \mathshiftoff \ataboff \supsuboff
282 | \actcharoff \paramoff}
283 |
284 | \newcommand\nojoe[1]{\reservedcharson #1 \reservedcharsoff}
285 | \catcode`@=12
286 | \reservedcharson
287 |
288 |
289 | \if\papercls \apjcls
290 | \newcommand\widedeltab{deluxetable}
291 | \else
292 | \newcommand\widedeltab{deluxetable*}
293 | \fi
294 |
295 |
296 | %% :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
297 | %% Convenience macross:
298 | \newcommand\tnm[1]{\tablenotemark{#1}}
299 | %% Spitzer:
300 | \newcommand\SST{{\em Spitzer Space Telescope}}
301 | \newcommand\Spitzer{{\em Spitzer}}
302 | %% chi-squared:
303 | \newcommand\chisq{\ifmmode{\chi\sp{2}}\else\math{\chi\sp{2}}\fi}
304 | \newcommand\redchisq{\ifmmode{ \chi\sp{2}\sb{\rm red}}
305 | \else\math{\chi\sp{2}\sb{\rm red}}\fi}
306 | %% Equilibrium temperature:
307 | \newcommand\Teq{\ifmmode{T\sb{\rm eq}}\else$T$\sb{eq}\fi}
308 | %% Jupiter mass, radius:
309 | \newcommand\mjup{\ifmmode{M\sb{\rm Jup}}\else$M$\sb{Jup}\fi}
310 | \newcommand\rjup{\ifmmode{R\sb{\rm Jup}}\else$R$\sb{Jup}\fi}
311 | %% Solar mass, radius:
312 | \newcommand\msun{\ifmmode{M\sb{\odot}}\else$M\sb{\odot}$\fi}
313 | \newcommand\rsun{\ifmmode{R\sb{\odot}}\else$R\sb{\odot}$\fi}
314 | %% Earth mass, radius:
315 | \newcommand\mearth{\ifmmode{M\sb{\oplus}}\else$M\sb{\oplus}$\fi}
316 | \newcommand\rearth{\ifmmode{R\sb{\oplus}}\else$R\sb{\oplus}$\fi}
317 | %% Molecules:
318 | \newcommand\molhyd{\ifmmode{{\rm H}\sb{2}}\else{H$\sb{2}$}\fi}
319 | \newcommand\methane{\ifmmode{{\rm CH}\sb{4}}\else{CH$\sb{4}$}\fi}
320 | \newcommand\water{\ifmmode{{\rm H}\sb{2}{\rm O}}\else{H$\sb{2}$O}\fi}
321 | \newcommand\carbdiox{\ifmmode{{\rm CO}\sb{2}}\else{CO$\sb{2}$}\fi}
322 | \newcommand\carbmono{\ifmmode{{\rm CO}}\else{CO}\fi}
323 | \newcommand\ammonia{\ifmmode{{\rm NH}\sb{3}}\else{NH$\sb{3}$}\fi}
324 | \newcommand\acetylene{\ifmmode{{\rm C}\sb{2}{\rm H}\sb{2}}
325 | \else{C$\sb{2}$H$\sb{2}$}\fi}
326 | \newcommand\ethylene{\ifmmode{{\rm C}\sb{2}{\rm H}\sb{4}}
327 | \else{C$\sb{2}$H$\sb{4}$}\fi}
328 | \newcommand\cyanide{\ifmmode{{\rm HCN}}\else{HCN}\fi}
329 | \newcommand\nitrogen{\ifmmode{{\rm N}\sb{2}}\else{N$\sb{2}$}\fi}
330 |
331 |
332 | %% Units:
333 | \newcommand\ms{m\;s$\sp{-1}$}
334 | \newcommand\cms{cm\;s$\sp{-1}$}
335 |
336 | \newcommand\degree{\degr}
337 | \newcommand\degrees{\degree}
338 | \newcommand\vs{\emph{vs.}}
339 |
340 |
--------------------------------------------------------------------------------
/tests/test_config_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | import os
5 | import filecmp
6 | import shutil
7 | import textwrap
8 | import pathlib
9 | import pytest
10 |
11 | from pygments.styles import STYLE_MAP
12 |
13 | import bibmanager.utils as u
14 | import bibmanager.bib_manager as bm
15 | import bibmanager.config_manager as cm
16 | import bibmanager.ads_manager as am
17 |
18 |
19 | def test_help_style(capsys, mock_init):
20 | styles = textwrap.fill(", ".join(style for style in iter(STYLE_MAP)),
21 | width=79, initial_indent=" ", subsequent_indent=" ")
22 | cm.help("style")
23 | captured = capsys.readouterr()
24 | assert captured.out == f"""
25 | The 'style' parameter sets the color-syntax style of displayed BibTeX entries.
26 | The default style is 'autumn'. Available options are:
27 | {styles}
28 | See http://pygments.org/demo/6780986/ for a demo of the style options.
29 |
30 | The current style is 'autumn'.\n"""
31 |
32 |
33 | def test_help_editor(capsys, mock_init):
34 | cm.help("text_editor")
35 | captured = capsys.readouterr()
36 | assert captured.out == """
37 | The 'text_editor' parameter sets the text editor to use when editing the
38 | bibmanager manually (i.e., a call to: bibm edit). By default, bibmanager
39 | uses the OS-default text editor.
40 |
41 | Typical text editors are: emacs, vim, gedit.
42 | To set the OS-default editor, set text_editor to 'default'.
43 | Note that aliases defined in the .bash are not accessible.
44 |
45 | The current text editor is 'default'.\n"""
46 |
47 |
48 | def test_help_paper(capsys, mock_init):
49 | cm.help("paper")
50 | captured = capsys.readouterr()
51 | assert captured.out == """
52 | The 'paper' parameter sets the default paper format for latex compilation outputs
53 | (not for pdflatex, which is automatic). Typical options are 'letter'
54 | (e.g., for ApJ articles) or 'A4' (e.g., for A&A).
55 |
56 | The current paper format is: 'letter'.\n"""
57 |
58 |
59 | def test_help_ads_token(capsys, mock_init):
60 | cm.help("ads_token")
61 | captured = capsys.readouterr()
62 | assert captured.out == """
63 | The 'ads_token' parameter sets the ADS token required for ADS requests.
64 | To obtain a token, follow the steps described here:
65 | https://github.com/adsabs/adsabs-dev-api#access
66 |
67 | The current ADS token is 'None'\n"""
68 |
69 |
70 | def test_help_ads_display(capsys, mock_init):
71 | cm.help("ads_display")
72 | captured = capsys.readouterr()
73 | assert captured.out == """
74 | The 'ads_display' parameter sets the number of entries to show at a time,
75 | for an ADS search query.
76 |
77 | The current number of entries to display is 20.\n"""
78 |
79 |
80 | def test_help_home(capsys, mock_init):
81 | cm.help("home")
82 | captured = capsys.readouterr()
83 | assert captured.out == f"""
84 | The 'home' parameter sets the home directory for the Bibmanager database.
85 |
86 | The current directory is '{u.HOME}'.\n"""
87 |
88 |
89 | def test_help_raise(mock_init):
90 | # Note that match only matches until the linebreak character.
91 | with pytest.raises(ValueError,
92 | match="'invalid_param' is not a valid bibmanager config parameter."):
93 | cm.help("invalid_param")
94 |
95 |
96 | def test_display_all(capsys, mock_init):
97 | cm.display()
98 | captured = capsys.readouterr()
99 | assert captured.out == (
100 | "\nbibmanager configuration file:\n"
101 | "PARAMETER VALUE\n"
102 | "----------- -----\n"
103 | "style autumn\n"
104 | "text_editor default\n"
105 | "paper letter\n"
106 | "ads_token None\n"
107 | "ads_display 20\n"
108 | f"home {u.HOME}\n")
109 |
110 |
111 | def test_display_each(capsys, mock_init):
112 | cm.display("style")
113 | captured = capsys.readouterr()
114 | assert captured.out == "style: autumn\n"
115 | cm.display("text_editor")
116 | captured = capsys.readouterr()
117 | assert captured.out == "text_editor: default\n"
118 | cm.display("paper")
119 | captured = capsys.readouterr()
120 | assert captured.out == "paper: letter\n"
121 | cm.display("ads_token")
122 | captured = capsys.readouterr()
123 | assert captured.out == "ads_token: None\n"
124 | cm.display("ads_display")
125 | captured = capsys.readouterr()
126 | assert captured.out == "ads_display: 20\n"
127 |
128 |
129 | def test_display_each_raises(mock_init):
130 | with pytest.raises(ValueError,
131 | match="'invalid_param' is not a valid bibmanager config parameter."):
132 | cm.display("invalid_param")
133 |
134 |
135 | def test_update_default(mock_init):
136 | cm.update_keys()
137 | with open(u.HOME+"config", 'r') as f:
138 | home = f.read()
139 | with open(u.ROOT+"config", 'r') as f:
140 | root = f.read()
141 | assert home == root.replace('HOME/', u.HOME)
142 |
143 |
144 | def test_get(mock_init):
145 | assert cm.get("style") == "autumn"
146 | assert cm.get("text_editor") == "default"
147 | assert cm.get("paper") == "letter"
148 | assert cm.get("ads_token") == "None"
149 | assert cm.get("ads_display") == "20"
150 |
151 |
152 | def test_get_raise(mock_init):
153 | with pytest.raises(ValueError,
154 | match="'invalid_param' is not a valid bibmanager config parameter."):
155 | cm.get("invalid_param")
156 |
157 |
158 | def test_set_style(mock_init):
159 | cm.set("style", "fruity")
160 | assert cm.get("style") == "fruity"
161 |
162 |
163 | def test_set_style_raises(mock_init):
164 | # Value must be a valid pygments style:
165 | with pytest.raises(ValueError, match="'invalid_pygment' is not a valid style option. Available options are:"):
166 | cm.set("style", "invalid_pygment")
167 |
168 |
169 | def test_set_editor(mock_init):
170 | # This is the only way to make sure vi is valid I can think of:
171 | if shutil.which("vi") is not None:
172 | cm.set("text_editor", "vi")
173 | assert cm.get("text_editor") == "vi"
174 | else:
175 | pass
176 |
177 |
178 | def test_set_editor_default(mock_init):
179 | cm.set("text_editor", "default")
180 | assert cm.get("text_editor") == "default"
181 |
182 |
183 | def test_set_editor_raises(mock_init):
184 | # Value must be a valid pygments style:
185 | with pytest.raises(ValueError,
186 | match="'invalid_editor' is not a valid text editor."):
187 | cm.set("text_editor", "invalid_editor")
188 |
189 |
190 | def test_set_paper(mock_init):
191 | cm.set("paper", "A4")
192 | assert cm.get("paper") == "A4"
193 |
194 |
195 | def test_set_ads_token(mock_init):
196 | cm.set("ads_token", "abc12345")
197 | assert cm.get("ads_token") == "abc12345"
198 |
199 |
200 | def test_set_ads_display(mock_init):
201 | cm.set("ads_display", "50")
202 | assert cm.get("ads_display") == "50"
203 |
204 |
205 | def test_set_ads_display_raises(mock_init):
206 | error = "The ads_display value must be a positive integer"
207 | with pytest.raises(ValueError, match=error):
208 | cm.set("ads_display", "fifty")
209 |
210 |
211 | def test_set_home_success(capsys, tmp_path, mock_init_sample):
212 | new_home = f'{tmp_path}/bm'
213 | cm.set('home', new_home)
214 | assert cm.get('home') == new_home + '/'
215 | # 'constants' now point to new home:
216 | assert u.BM_DATABASE() == f'{new_home}/bm_database.pickle'
217 | assert u.BM_BIBFILE() == f'{new_home}/bm_bibliography.bib'
218 | assert u.BM_TMP_BIB() == f'{new_home}/tmp_bibliography.bib'
219 | assert u.BM_CACHE() == f'{new_home}/cached_ads_query.pickle'
220 | assert u.BM_HISTORY_SEARCH() == f'{new_home}/history_search'
221 | assert u.BM_HISTORY_ADS() == f'{new_home}/history_ads_search'
222 | assert u.BM_PDF() == f'{new_home}/pdf/'
223 | captured = capsys.readouterr()
224 | assert captured.out == f'home updated to: {new_home}/.\n'
225 | # These files/folders stay:
226 | assert set(os.listdir(u.HOME)) == set(["config", "examples", "pdf"])
227 | # These files have been moved/created:
228 | assert set(os.listdir(str(new_home))) == \
229 | set(['pdf', 'bm_bibliography.bib', 'bm_database.pickle'])
230 |
231 |
232 | def test_set_home_pdf_success(tmp_path, mock_init_sample):
233 | new_home = f'{tmp_path}/bm'
234 | old_pdf_home = u.BM_PDF()
235 | pathlib.Path(f"{u.BM_PDF()}Rubin1980.pdf").touch()
236 | pathlib.Path(f"{u.BM_PDF()}Slipher1913.pdf").touch()
237 | cm.set('home', new_home)
238 | # Not registered in database, not moved:
239 | assert 'Rubin1980.pdf' in os.listdir(f'{old_pdf_home}')
240 | # Moved:
241 | assert 'Slipher1913.pdf' in os.listdir(f'{new_home}/pdf/')
242 | assert 'Slipher1913.pdf' in os.listdir(u.BM_PDF())
243 |
244 |
245 | def test_set_home_overwrite(tmp_path, mock_init_sample):
246 | new_home = f'{tmp_path}/bm'
247 | pathlib.Path(f"{u.BM_PDF()}Slipher1913.pdf").touch()
248 | os.mkdir(f'{new_home}')
249 | os.mkdir(f'{new_home}/pdf')
250 | pathlib.Path(f"{u.BM_HISTORY_SEARCH()}").touch()
251 | pathlib.Path(f"{new_home}/history_search").touch()
252 | pathlib.Path(f"{new_home}/pdf/Slipher1913.pdf").touch()
253 | cm.set('home', new_home)
254 | # No errors <=> pass
255 |
256 |
257 | def test_set_home_merge(tmp_path, bibs, mock_init):
258 | new_home = f'{tmp_path}/bm'
259 | os.mkdir(f'{new_home}')
260 | bib1, bib2 = bibs["beaulieu_apj"], bibs["stodden"]
261 | bib1.pdf, bib2.pdf = 'file1.pdf', 'file2.pdf'
262 | bm.save([bib1])
263 | shutil.copy(u.BM_DATABASE(), f'{new_home}/bm_database.pickle')
264 | bm.export([bib1], f'{new_home}/bm_bibliography.bib', meta=True)
265 | bm.export([bib2], f'{new_home}/other.bib', meta=True)
266 | bm.init(f'{new_home}/other.bib')
267 | cm.set('home', new_home)
268 | # Check DBs are merged:
269 | bibs = bm.load()
270 | assert len(bibs) == 2
271 | assert bibs[0].content == bib1.content
272 | assert bibs[1].content == bib2.content
273 | # Check both meta exist:
274 | assert bibs[0].pdf == 'file1.pdf'
275 | assert bibs[1].pdf == 'file2.pdf'
276 |
277 |
278 | def test_set_home_none_success(tmp_path, mock_init):
279 | new_home = f'{tmp_path}/bm'
280 | cm.set('home', new_home)
281 | # These files/folders stay:
282 | assert set(os.listdir(u.HOME)) == set(["config", "examples", "pdf"])
283 | # These files have been moved/created:
284 | assert os.listdir(str(new_home)) == ['pdf']
285 |
286 |
287 | def test_set_home_and_edit(tmp_path, mock_init, bibs, reqs):
288 | new_home = f'{tmp_path}/bm'
289 | cm.set('home', new_home)
290 | assert cm.get('home') == new_home + '/'
291 | # Test merge:
292 | bm.merge(new=[bibs["beaulieu_apj"]], take='new')
293 | # Test search:
294 | matches = bm.search(authors="beaulieu")
295 | assert len(matches) == 1
296 | assert 'BeaulieuEtal2011apjGJ436bMethane' in matches[0].key
297 | # Test ADS add:
298 | am.add_bibtex(['1925PhDT.........1P'], ['Payne1925phdStellarAtmospheres'])
299 | # Test load:
300 | current_bibs = bm.load()
301 | assert len(current_bibs) == 2
302 | assert 'Payne1925phdStellarAtmospheres' in current_bibs[1].key
303 | # These files/folders stay:
304 | assert set(os.listdir(u.HOME)) == set(["config", "examples", "pdf"])
305 | # These files have been moved/created:
306 | assert set(os.listdir(str(new_home))) == \
307 | set(['pdf', 'bm_bibliography.bib', 'bm_database.pickle'])
308 |
309 |
310 | def test_set_home_no_parent(mock_init_sample):
311 | with pytest.raises(ValueError,
312 | match="The home value must have an existing parent folder"):
313 | cm.set("home", "fake_parent/some_dir")
314 |
315 |
316 | def test_set_home_file_extension(mock_init_sample):
317 | with pytest.raises(ValueError,
318 | match="The home value cannot have a file extension"):
319 | cm.set("home", "./new.home")
320 |
321 |
322 | def test_set_raises(mock_init):
323 | with pytest.raises(ValueError,
324 | match="'invalid_param' is not a valid bibmanager config parameter."):
325 | cm.set("invalid_param", "value")
326 |
327 |
328 | def test_update_edited(mock_init):
329 | # Simulate older config with a missing parameter, but not default values:
330 | with open(u.HOME+"config", "w") as f:
331 | f.writelines("[BIBMANAGER]\n"
332 | "style = autumn\n"
333 | "text_editor = vi\n"
334 | "paper = letter\n")
335 | cm.update_keys()
336 | assert not filecmp.cmp(u.HOME+"config", u.ROOT+"config")
337 | assert cm.get("style") == "autumn"
338 | assert cm.get("text_editor") == "vi"
339 | assert cm.get("paper") == "letter"
340 | assert cm.get("ads_token") == "None"
341 | assert cm.get("ads_display") == "20"
342 |
--------------------------------------------------------------------------------
/tests/test_latex_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | import os
5 | import pytest
6 | import pathlib
7 |
8 | import numpy as np
9 | from conftest import cd
10 |
11 | import bibmanager.utils as u
12 | import bibmanager.bib_manager as bm
13 | import bibmanager.latex_manager as lm
14 |
15 |
16 | def test_get_bibfile_with_extension(tmp_path):
17 | os.chdir(tmp_path)
18 | texfile = f'{tmp_path}/texfile.tex'
19 | text = r"""
20 | \begin{document}
21 | Hello, this is latex.
22 | \bibliography{bibfile.bib}
23 | \end{document}
24 | """
25 | with open(texfile, 'w') as f:
26 | f.write(text)
27 |
28 | bibfile = lm.get_bibfile(texfile)
29 | assert bibfile == 'bibfile.bib'
30 |
31 |
32 | def test_get_bibfile_no_extension(tmp_path):
33 | os.chdir(tmp_path)
34 | texfile = f'{tmp_path}/texfile.tex'
35 | text = r"""
36 | \begin{document}
37 | Hello, this is latex.
38 | \bibliography{bibfile}
39 | \end{document}
40 | """
41 | with open(texfile, 'w') as f:
42 | f.write(text)
43 |
44 | bibfile = lm.get_bibfile(texfile)
45 | assert bibfile == 'bibfile.bib'
46 |
47 |
48 | def test_get_bibfile_no_bibliography(tmp_path):
49 | os.chdir(tmp_path)
50 | texfile = f'{tmp_path}/texfile.tex'
51 | text = r"""
52 | \begin{document}
53 | Hello, this is latex.
54 | \end{document}
55 | """
56 | with open(texfile, 'w') as f:
57 | f.write(text)
58 |
59 | error = "No 'bibiliography' call found in tex file"
60 | with pytest.raises(ValueError, match=error):
61 | bibfile = lm.get_bibfile(texfile)
62 |
63 |
64 | def test_no_comments():
65 | assert lm.no_comments("") == ""
66 | assert lm.no_comments("Hello world.") == "Hello world."
67 | assert lm.no_comments("inline comment % comment") == "inline comment"
68 | assert lm.no_comments("% comment line.") == ""
69 | assert lm.no_comments("percentage \\%") == "percentage \\%"
70 | # If first line is comment, '\n' stays:
71 | assert lm.no_comments("% comment line.\nThen this") == "\nThen this"
72 | # If not, entire line is removed (including '\n'):
73 | assert lm.no_comments("Line\n%comment\nanother line") \
74 | == "Line\nanother line"
75 |
76 |
77 | def test_citations1():
78 | cites = lm.citations("\\citep{Author}.")
79 | assert next(cites) == "Author"
80 | cites = lm.citations("\\citep{\n Author }.")
81 | assert next(cites) == "Author"
82 | cites = lm.citations("\\citep[pre]{Author}.")
83 | assert next(cites) == "Author"
84 | cites = lm.citations("\\citep[pre][post]{Author}.")
85 | assert next(cites) == "Author"
86 | cites = lm.citations("\\citep\n[][]{Author}.")
87 | assert next(cites) == "Author"
88 | cites = lm.citations("\\citep [pre] [post] {Author}.")
89 | assert next(cites) == "Author"
90 | cites = lm.citations("\\citep[{\\pre},][post]{Author}.")
91 | assert next(cites) == "Author"
92 | # Outer commas are ignored:
93 | cites = lm.citations("\\citep{,Author,}.")
94 | assert next(cites) == "Author"
95 |
96 | def test_citations2():
97 | # Multiple citations:
98 | cites = lm.citations("\\citep[{\\pre},][post]{Author1, Author2}.")
99 | assert next(cites) == "Author1"
100 | assert next(cites) == "Author2"
101 | cites = lm.citations(
102 | "\\citep[pre][post]{Author1} and \\citep[pre][post]{Author2}.")
103 | assert next(cites) == "Author1"
104 | assert next(cites) == "Author2"
105 | cites = lm.citations("\\citep[pre\n ][post] {Author1, Author2}")
106 | assert next(cites) == "Author1"
107 | assert next(cites) == "Author2"
108 |
109 | def test_citations3():
110 | # Recursive citations:
111 | cites = lm.citations(
112 | "\\citep[see also \\citealp{Author1}][\\citealp{Author3}]{Author2}")
113 | assert next(cites) == "Author1"
114 | assert next(cites) == "Author2"
115 | assert next(cites) == "Author3"
116 |
117 |
118 | def test_citations4():
119 | # Match all of these:
120 | assert next(lm.citations("\\cite{AuthorA}")) == "AuthorA"
121 | assert next(lm.citations("\\nocite{AuthorB}")) == "AuthorB"
122 | assert next(lm.citations("\\defcitealias{AuthorC}")) == "AuthorC"
123 | assert next(lm.citations("\\citet{AuthorD}")) == "AuthorD"
124 | assert next(lm.citations("\\citet*{AuthorE}")) == "AuthorE"
125 | assert next(lm.citations("\\Citet{AuthorF}")) == "AuthorF"
126 | assert next(lm.citations("\\Citet*{AuthorG}")) == "AuthorG"
127 | assert next(lm.citations("\\citep{AuthorH}")) == "AuthorH"
128 | assert next(lm.citations("\\citep*{AuthorI}")) == "AuthorI"
129 | assert next(lm.citations("\\Citep{AuthorJ}")) == "AuthorJ"
130 | assert next(lm.citations("\\Citep*{AuthorK}")) == "AuthorK"
131 | assert next(lm.citations("\\citealt{AuthorL}")) == "AuthorL"
132 | assert next(lm.citations("\\citealt*{AuthorM}")) == "AuthorM"
133 | assert next(lm.citations("\\Citealt{AuthorN}")) == "AuthorN"
134 | assert next(lm.citations("\\Citealt*{AuthorO}")) == "AuthorO"
135 | assert next(lm.citations("\\citealp{AuthorP}")) == "AuthorP"
136 | assert next(lm.citations("\\citealp*{AuthorQ}")) == "AuthorQ"
137 | assert next(lm.citations("\\Citealp{AuthorR}")) == "AuthorR"
138 | assert next(lm.citations("\\Citealp*{AuthorS}")) == "AuthorS"
139 | assert next(lm.citations("\\citeauthor{AuthorT}")) == "AuthorT"
140 | assert next(lm.citations("\\citeauthor*{AuthorU}")) == "AuthorU"
141 | assert next(lm.citations("\\Citeauthor{AuthorV}")) == "AuthorV"
142 | assert next(lm.citations("\\Citeauthor*{AuthorW}")) == "AuthorW"
143 | assert next(lm.citations("\\citeyear{AuthorX}")) == "AuthorX"
144 | assert next(lm.citations("\\citeyear*{AuthorY}")) == "AuthorY"
145 | assert next(lm.citations("\\citeyearpar{AuthorZ}")) == "AuthorZ"
146 | assert next(lm.citations("\\citeyearpar*{AuthorAA}")) == "AuthorAA"
147 |
148 |
149 | def test_citations5():
150 | # The sample tex file:
151 | texfile = os.path.expanduser('~') + "/.bibmanager/examples/sample.tex"
152 | with open(texfile) as f:
153 | tex = f.read()
154 | tex = lm.no_comments(tex)
155 | cites = [citation for citation in lm.citations(tex)]
156 | assert cites == [
157 | 'AASteamHendrickson2018aastex62',
158 | 'HarrisEtal2020natNumpy',
159 | 'VirtanenEtal2020natmeScipy',
160 | 'Hunter2007ieeeMatplotlib',
161 | 'PerezGranger2007cseIPython',
162 | 'MeurerEtal2017pjcsSYMPY',
163 | 'Astropycollab2013aaAstropy',
164 | 'AASteamHendrickson2018aastex62',
165 | 'Cubillos2019zndoBibmanager',
166 | ]
167 |
168 |
169 | def test_parse_subtex_files(tmp_path):
170 | os.chdir(tmp_path)
171 | os.mkdir('dir1')
172 | os.mkdir('dir1/dir2')
173 | subfile1 = f'{tmp_path}/subfile1.tex'
174 | subfile2 = f'{tmp_path}/dir1/subfile2.tex'
175 | subfileB = f'{tmp_path}/dir1/subfileB.tex'
176 | subfileC = f'{tmp_path}/dir1/dir2/subfileC.tex'
177 | subtex1 = "This is subfile 1\n\\include{dir1/subfile2}\n"
178 | subtex2 = "This is subfile 2\n%\\input{dir1/subfileB}\n"
179 | subtexB = "This is subfile B\n\\subfile{dir1/dir2/subfileC}\n"
180 | subtexC = "This is subfile C\n"
181 | with open(subfile1, 'w') as f:
182 | f.write(subtex1)
183 | with open(subfile2, 'w') as f:
184 | f.write(subtex2)
185 | with open(subfileB, 'w') as f:
186 | f.write(subtexB)
187 | with open(subfileC, 'w') as f:
188 | f.write(subtexC)
189 |
190 | tex = r"""
191 | \begin{document}
192 |
193 | \input{subfile1}
194 | \include{dir1/subfileB}
195 |
196 | \bibliography{rate}
197 | \end{document}
198 | """
199 | parsed = lm.parse_subtex_files(tex)
200 | assert parsed == tex + subtex1 + subtex2[:subtex2.index('%')] \
201 | + subtexB + subtexC
202 |
203 |
204 | def test_build_bib_inplace(mock_init):
205 | bm.merge(u.HOME+"examples/sample.bib")
206 | with cd(u.HOME+'examples'):
207 | missing = lm.build_bib("sample.tex")
208 | files = os.listdir(".")
209 | assert "texsample.bib" in files
210 | # Now check content:
211 | np.testing.assert_array_equal(missing, np.zeros(0,dtype="U"))
212 | bibs = bm.read_file("texsample.bib")
213 | assert len(bibs) == 8
214 | keys = [bib.key for bib in bibs]
215 | assert "AASteamHendrickson2018aastex62" in keys
216 | assert "HarrisEtal2020natNumpy" in keys
217 | assert "VirtanenEtal2020natmeScipy" in keys
218 | assert "Hunter2007ieeeMatplotlib" in keys
219 | assert "PerezGranger2007cseIPython" in keys
220 | assert "MeurerEtal2017pjcsSYMPY" in keys
221 | assert "Astropycollab2013aaAstropy" in keys
222 | assert "Cubillos2019zndoBibmanager" in keys
223 |
224 |
225 | def test_build_bib_remote(mock_init):
226 | bm.merge(u.HOME+"examples/sample.bib")
227 | lm.build_bib(u.HOME+"examples/sample.tex")
228 | files = os.listdir(u.HOME+"examples/")
229 | assert "texsample.bib" in files
230 |
231 |
232 | def test_build_bib_user_bibfile(tmp_path, mock_init):
233 | bibfile = f'{tmp_path}/my_file.bib'
234 | bm.merge(u.HOME+"examples/sample.bib")
235 | lm.build_bib(u.HOME+"examples/sample.tex", bibfile=bibfile)
236 | assert "my_file.bib" in os.listdir(str(tmp_path))
237 |
238 |
239 | def test_build_bib_missing(capsys, tmp_path, mock_init):
240 | # Assert screen output:
241 | bibfile = f'{tmp_path}/my_file.bib'
242 | bm.merge(u.HOME+"examples/sample.bib")
243 | captured = capsys.readouterr()
244 | texfile = u.HOME+"examples/mock_file.tex"
245 | with open(texfile, "w") as f:
246 | f.write("\\cite{Astropycollab2013aaAstropy} \\cite{MissingEtal2019}.\n")
247 | missing = lm.build_bib(texfile, bibfile)
248 | captured = capsys.readouterr()
249 | assert captured.out == "References not found:\nMissingEtal2019\n"
250 | # Check content:
251 | np.testing.assert_array_equal(missing, np.array(["MissingEtal2019"]))
252 | bibs = bm.read_file(bibfile)
253 | assert len(bibs) == 1
254 | assert "Astropycollab2013aaAstropy" in bibs[0].key
255 |
256 |
257 | def test_build_bib_raise(mock_init):
258 | bm.merge(u.HOME+"examples/sample.bib")
259 | with open(u.HOME+"examples/mock_file.tex", "w") as f:
260 | f.write("\\cite{Astropycollab2013aaAstropy}")
261 | match = "No 'bibiliography' call found in tex file"
262 | with pytest.raises(Exception, match=match):
263 | lm.build_bib(u.HOME+"examples/mock_file.tex")
264 |
265 |
266 | @pytest.mark.skip(reason="TBD")
267 | def test_update_keys():
268 | pass
269 |
270 |
271 | def test_clear_latex(mock_init):
272 | # Mock some 'latex output' files:
273 | pathlib.Path(u.HOME+"examples/sample.pdf").touch()
274 | pathlib.Path(u.HOME+"examples/sample.ps").touch()
275 | pathlib.Path(u.HOME+"examples/sample.bbl").touch()
276 | pathlib.Path(u.HOME+"examples/sample.dvi").touch()
277 | pathlib.Path(u.HOME+"examples/sample.out").touch()
278 | pathlib.Path(u.HOME+"examples/sample.blg").touch()
279 | pathlib.Path(u.HOME+"examples/sample.log").touch()
280 | pathlib.Path(u.HOME+"examples/sample.aux").touch()
281 | pathlib.Path(u.HOME+"examples/sample.lof").touch()
282 | pathlib.Path(u.HOME+"examples/sample.lot").touch()
283 | pathlib.Path(u.HOME+"examples/sample.toc").touch()
284 | pathlib.Path(u.HOME+"examples/sampleNotes.bib").touch()
285 | # Here they are:
286 | files = os.listdir(u.HOME+"examples")
287 | assert len(files) == 17
288 | lm.clear_latex(u.HOME+"examples/sample.tex")
289 | # Now they are gone:
290 | files = os.listdir(u.HOME+"examples")
291 | assert set(files) \
292 | == set(['aastex62.cls', 'apj_hyperref.bst', 'sample.bib', 'sample.tex',
293 | 'top-apj.tex'])
294 |
295 | @pytest.mark.skip(reason="Need to either mock latex, bibtex, dvi2df calls or learn how to enable them in travis CI")
296 | def test_compile_latex():
297 | # Either mock heavily the latex, bibtex, dvi-pdf calls or learn how
298 | # to integrate them to CI.
299 | pass
300 |
301 |
302 | def test_compile_latex_bad_extension(mock_init):
303 | with pytest.raises(ValueError,
304 | match="Input file does not have a .tex extension"):
305 | lm.compile_latex("mock_file.tecs")
306 |
307 |
308 | def test_compile_latex_no_extension_not_found(mock_init):
309 | with pytest.raises(ValueError,
310 | match="Input .tex file does not exist"):
311 | lm.compile_latex("mock_file")
312 |
313 |
314 | @pytest.mark.skip(reason="Need to either mock pdflatex and bibtex calls or learn how to enable them in travis CI")
315 | def test_compile_pdflatex():
316 | # Same as test_compile_latex.
317 | pass
318 |
319 |
320 | def test_compile_pdflatex_bad_extension(mock_init):
321 | with pytest.raises(ValueError,
322 | match="Input file does not have a .tex extension"):
323 | lm.compile_pdflatex("mock_file.tecs")
324 |
325 |
326 | def test_compile_pdflatex_no_extension_not_found(mock_init):
327 | with pytest.raises(ValueError,
328 | match="Input .tex file does not exist"):
329 | lm.compile_latex("mock_file")
330 |
331 |
--------------------------------------------------------------------------------
/tests/test_ads.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | import os
5 | import pytest
6 |
7 | import bibmanager.bib_manager as bm
8 | import bibmanager.ads_manager as am
9 | import bibmanager.config_manager as cm
10 | import bibmanager.utils as u
11 |
12 |
13 | expected_output1 = '\r\nTitle: A deeper look at Jupiter\r\nAuthors: Fortney, Jonathan\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2018Natur.555..168F\r\nbibcode: 2018Natur.555..168F\r\n\r\nTitle: The Hunt for Planet Nine: Atmosphere, Spectra, Evolution, and\r\n Detectability\r\nAuthors: Fortney, Jonathan J.; et al.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2016ApJ...824L..25F\r\nbibcode: 2016ApJ...824L..25F\r\n\n'
14 |
15 | expected_output2 = '\r\nTitle: A Framework for Characterizing the Atmospheres of Low-mass Low-density\r\n Transiting Planets\r\nAuthors: Fortney, Jonathan J.; et al.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2013ApJ...775...80F\r\nbibcode: 2013ApJ...775...80F\r\n\r\nTitle: On the Carbon-to-oxygen Ratio Measurement in nearby Sun-like Stars:\r\n Implications for Planet Formation and the Determination of Stellar\r\n Abundances\r\nAuthors: Fortney, Jonathan J.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2012ApJ...747L..27F\r\nbibcode: 2012ApJ...747L..27F\r\n\n'
16 |
17 |
18 | def test_key_update_journal_year():
19 | # Case of journal does not matter:
20 | assert am.key_update("BeaulieuEtal2010ArXiVGJ436b", "2011ApJ...731...16B",
21 | "2010arXiv1007.0324B") == "BeaulieuEtal2011apjGJ436b"
22 | assert am.key_update("BeaulieuEtal2010arXivGJ436b", "2011ApJ...731...16B",
23 | "2010arXiv1007.0324B") == "BeaulieuEtal2011apjGJ436b"
24 | assert am.key_update("BeaulieuEtal2010arxivGJ436b", "2011ApJ...731...16B",
25 | "2010arXiv1007.0324B") == "BeaulieuEtal2011apjGJ436b"
26 |
27 |
28 | def test_key_update_year():
29 | assert am.key_update("BeaulieuEtal2010apjGJ436b", "2011ApJ...731...16B",
30 | "2010arXiv1007.0324B") == "BeaulieuEtal2011apjGJ436b"
31 | # Year does not get updated if it does not match:
32 | assert am.key_update("BeaulieuEtal2009arxivGJ436b", "2011ApJ...731...16B",
33 | "2010arXiv1007.0324B") == "BeaulieuEtal2009apjGJ436b"
34 |
35 |
36 | def test_key_update_journal():
37 | assert am.key_update("BeaulieuEtal2011arxivGJ436b", "2011ApJ...731...16B",
38 | "2011arXiv1007.0324B") == "BeaulieuEtal2011apjGJ436b"
39 |
40 |
41 | def test_search(reqs, ads_entries, mock_init):
42 | query = 'author:"^mayor" year:1995 property:refereed'
43 | results, nmatch = am.search(query)
44 | assert nmatch == 1
45 | assert results == [ads_entries['mayor']]
46 |
47 |
48 | def test_search_limit_cache(reqs, ads_entries, mock_init):
49 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
50 | results, nmatch = am.search(query, start=0, cache_rows=2)
51 | # There are 26 matches:
52 | assert nmatch == 26
53 | # But requested only two entries:
54 | assert len(results) == 2
55 | assert results == [ads_entries['fortney2018'], ads_entries['fortney2016']]
56 |
57 |
58 | def test_search_start(reqs, ads_entries, mock_init):
59 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
60 | results, nmatch = am.search(query, start=2, cache_rows=2)
61 | # There are 26 matches:
62 | assert nmatch == 26
63 | # But requested only two entries:
64 | assert len(results) == 2
65 | # But results start from third match:
66 | assert results == [ads_entries['fortney2013'], ads_entries['fortney2012']]
67 |
68 |
69 | def test_search_unauthorized(reqs, mock_init):
70 | cm.set("ads_token", "None")
71 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
72 | with pytest.raises(ValueError, match='Unauthorized access to ADS. '
73 | 'Check that the ADS token is valid.'):
74 | results, nmatch = am.search(query)
75 |
76 |
77 | def test_display_all(capsys, mock_init, ads_entries):
78 | results = [ads_entries['fortney2018'], ads_entries['fortney2016']]
79 | start = 0
80 | index = 0
81 | rows = 2
82 | nmatch = 2
83 | am.display(results, start, index, rows, nmatch, short=True)
84 | captured = capsys.readouterr()
85 | assert captured.out == \
86 | expected_output1 + 'Showing entries 1--2 out of 2 matches.\n'
87 |
88 |
89 | def test_display_first_batch(capsys, mock_init, ads_entries):
90 | results = [
91 | ads_entries['fortney2018'],
92 | ads_entries['fortney2016'],
93 | ads_entries['fortney2013'],
94 | ads_entries['fortney2012']]
95 | start = 0
96 | index = 0
97 | rows = 2
98 | nmatch = 4
99 | am.display(results, start, index, rows, nmatch, short=True)
100 | captured = capsys.readouterr()
101 | assert captured.out == (
102 | expected_output1 +
103 | 'Showing entries 1--2 out of 4 matches. '
104 | 'To show the next set, execute:\nbibm ads-search -n\n')
105 |
106 |
107 | def test_display_second_batch(capsys, mock_init, ads_entries):
108 | results = [
109 | ads_entries['fortney2018'],
110 | ads_entries['fortney2016'],
111 | ads_entries['fortney2013'],
112 | ads_entries['fortney2012']]
113 | start = 0
114 | index = 2
115 | rows = 2
116 | nmatch = 4
117 | am.display(results, start, index, rows, nmatch, short=True)
118 | captured = capsys.readouterr()
119 | assert captured.out == \
120 | expected_output2 + 'Showing entries 3--4 out of 4 matches.\n'
121 |
122 |
123 | def test_display_over(capsys, mock_init, ads_entries):
124 | results = [
125 | ads_entries['fortney2018'],
126 | ads_entries['fortney2016'],
127 | ads_entries['fortney2013'],
128 | ads_entries['fortney2012']]
129 | start = 0
130 | index = 3
131 | rows = 2
132 | nmatch = 4
133 | am.display(results, start, index, rows, nmatch, short=True)
134 | captured = capsys.readouterr()
135 | expected_output = '\r\nTitle: On the Carbon-to-oxygen Ratio Measurement in nearby Sun-like Stars:\r\n Implications for Planet Formation and the Determination of Stellar\r\n Abundances\r\nAuthors: Fortney, Jonathan J.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2012ApJ...747L..27F\r\nbibcode: 2012ApJ...747L..27F\r\n\nShowing entries 4--4 out of 4 matches.\n'
136 | assert captured.out == expected_output
137 |
138 |
139 | @pytest.mark.skip(reason='TBD')
140 | def test_display_no_author_entry(capsys, mock_init, ads_entries):
141 | pass
142 |
143 |
144 | def test_add_bibtex_success(capsys, reqs, mock_init):
145 | captured = capsys.readouterr()
146 | bibcodes = ['1925PhDT.........1P']
147 | keys = ['Payne1925phdStellarAtmospheres']
148 | am.add_bibtex(bibcodes, keys)
149 | captured = capsys.readouterr()
150 | assert captured.out == "\nMerged 1 new entries.\n"
151 | loaded_bibs = bm.load()
152 | assert len(loaded_bibs) == 1
153 | assert loaded_bibs[0].content == \
154 | """@PHDTHESIS{Payne1925phdStellarAtmospheres,
155 | author = {{Payne}, Cecilia Helena},
156 | title = "{Stellar Atmospheres; a Contribution to the Observational Study of High Temperature in the Reversing Layers of Stars.}",
157 | keywords = {Astronomy},
158 | school = {RADCLIFFE COLLEGE.},
159 | year = 1925,
160 | month = Jan,
161 | adsurl = {https://ui.adsabs.harvard.edu/abs/1925PhDT.........1P},
162 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
163 | }"""
164 |
165 |
166 | def test_add_bibtex_none_found(reqs, mock_init):
167 | bibcodes = ['1925PhDT.....X...1P']
168 | keys = ['Payne1925phdStellarAtmospheres']
169 | with pytest.raises(ValueError,
170 | match="There were no entries found for the requested bibcodes."):
171 | am.add_bibtex(bibcodes, keys)
172 |
173 |
174 | def test_add_bibtex_warning(capsys, reqs, mock_init):
175 | # A partially failing call will still add those that succeed:
176 | captured = capsys.readouterr()
177 | bibcodes = ['1925PhDT.....X...1P', '2018MNRAS.481.5286F']
178 | keys = ['Payne1925phdStellarAtmospheres', 'FolsomEtal2018mnrasHD219134']
179 | am.add_bibtex(bibcodes, keys)
180 | loaded_bibs = bm.load()
181 | assert len(loaded_bibs) == 1
182 | captured = capsys.readouterr()
183 | assert captured.out == """
184 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
185 | Warning:
186 |
187 | There were bibcodes unmatched or not found in ADS:
188 | - 1925PhDT.....X...1P
189 |
190 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
191 |
192 |
193 | Merged 1 new entries.
194 | """
195 |
196 |
197 | def test_add_bibtex_with_tags(capsys, reqs, mock_init):
198 | captured = capsys.readouterr()
199 | bibcodes = ['1925PhDT.........1P']
200 | keys = ['Payne1925phdStellarAtmospheres']
201 | tags = [['stars']]
202 | am.add_bibtex(bibcodes, keys, tags=tags)
203 | captured = capsys.readouterr()
204 | assert captured.out == "\nMerged 1 new entries.\n"
205 | loaded_bibs = bm.load()
206 | assert len(loaded_bibs) == 1
207 | assert repr(loaded_bibs[0]) == \
208 | """tags: stars
209 | @PHDTHESIS{Payne1925phdStellarAtmospheres,
210 | author = {{Payne}, Cecilia Helena},
211 | title = "{Stellar Atmospheres; a Contribution to the Observational Study of High Temperature in the Reversing Layers of Stars.}",
212 | keywords = {Astronomy},
213 | school = {RADCLIFFE COLLEGE.},
214 | year = 1925,
215 | month = Jan,
216 | adsurl = {https://ui.adsabs.harvard.edu/abs/1925PhDT.........1P},
217 | adsnote = {Provided by the SAO/NASA Astrophysics Data System}
218 | }"""
219 |
220 |
221 | @pytest.mark.skip(reason="Can I test this without monkeypatching the request?")
222 | def test_update(capsys, mock_init_sample):
223 | captured = capsys.readouterr()
224 | am.update()
225 | captured = capsys.readouterr()
226 | assert captured.out == """
227 | Merged 0 new entries.
228 | (Not counting updated references)
229 |
230 | There were 1 entries updated from ArXiv to their peer-reviewed version.
231 | These ones changed their key:
232 | BeaulieuEtal2010arxivGJ436b -> BeaulieuEtal2011apjGJ436b\n"""
233 |
234 |
235 | @pytest.mark.skip(reason="Can I test this without monkeypatching the request?")
236 | def test_update_with_tags(mock_init_sample):
237 | bibcodes = ['1925PhDT.........1P']
238 | keys = ['Payne1925phdStellarAtmospheres']
239 | tags = [['stars']]
240 | am.add_bibtex(bibcodes, keys, tags=tags)
241 | am.update()
242 | assert bm.find(key=keys[0]).tags == tags[0]
243 |
244 |
245 | def test_manager_none(capsys, reqs, ads_entries, mock_init):
246 | am.manager(None)
247 | captured = capsys.readouterr()
248 | assert captured.out == "There are no more entries for this query.\n"
249 |
250 |
251 | def test_manager_query_no_caching(capsys, reqs, ads_entries, mock_init):
252 | query = 'author:"^mayor" year:1995 property:refereed'
253 | am.manager(query)
254 | captured = capsys.readouterr()
255 | expected_output = '\r\nTitle: A Jupiter-mass companion to a solar-type star\r\nAuthors: Mayor, Michel and Queloz, Didier\r\nADS URL: https://ui.adsabs.harvard.edu/abs/1995Natur.378..355M\r\nbibcode: 1995Natur.378..355M\r\n\nShowing entries 1--1 out of 1 matches.\n'
256 | assert captured.out == expected_output
257 |
258 |
259 | def test_manager_query_caching(capsys, reqs, ads_entries, mock_init):
260 | cm.set('ads_display', '2')
261 | captured = capsys.readouterr()
262 | am.search.__defaults__ = 0, 4, 'pubdate+desc'
263 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
264 | am.manager(query)
265 | captured = capsys.readouterr()
266 | assert os.path.exists(u.BM_CACHE())
267 | assert captured.out == (
268 | expected_output1 +
269 | 'Showing entries 1--2 out of 26 matches. To show the next set, '
270 | 'execute:\nbibm ads-search -n\n')
271 |
272 |
273 | def test_manager_from_cache(capsys, reqs, ads_entries, mock_init):
274 | cm.set('ads_display', '2')
275 | captured = capsys.readouterr()
276 | am.search.__defaults__ = 0, 4, 'pubdate+desc'
277 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
278 | am.manager(query)
279 | captured = capsys.readouterr()
280 | am.manager(None)
281 | captured = capsys.readouterr()
282 | assert captured.out == expected_output2 + 'Showing entries 3--4 out of 26 matches. To show the next set, execute:\nbibm ads-search -n\n'
283 |
284 |
285 | def test_manager_cache_trigger_search(capsys, reqs, ads_entries, mock_init):
286 | cm.set('ads_display', '2')
287 | am.search.__defaults__ = 0, 4, 'pubdate+desc'
288 | query = 'author:"^fortney, j" year:2000-2018 property:refereed'
289 | am.manager(query)
290 | am.manager(None)
291 | captured = capsys.readouterr()
292 | am.manager(None)
293 | captured = capsys.readouterr()
294 | expected_output = "\r\nTitle: Discovery and Atmospheric Characterization of Giant Planet Kepler-12b:\r\n An Inflated Radius Outlier\r\nAuthors: Fortney, Jonathan J.; et al.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2011ApJS..197....9F\r\nbibcode: 2011ApJS..197....9F\r\n\r\nTitle: Self-consistent Model Atmospheres and the Cooling of the Solar System's\r\n Giant Planets\r\nAuthors: Fortney, J. J.; et al.\r\nADS URL: https://ui.adsabs.harvard.edu/abs/2011ApJ...729...32F\r\nbibcode: 2011ApJ...729...32F\r\n\nShowing entries 5--6 out of 26 matches. To show the next set, execute:\nbibm ads-search -n\n"
295 | assert captured.out == expected_output
296 |
--------------------------------------------------------------------------------
/docs/ads.rst:
--------------------------------------------------------------------------------
1 | .. _ads:
2 |
3 | ADS Management
4 | ==============
5 |
6 | .. note:: To enable the ADS functionality, first you need to obtain an ADS token [#ADStoken]_, and set it into the ``ads_tokend`` config parameter. To do this:
7 |
8 | 1. Create an account and login into the new `ADS system `_.
9 |
10 | 2. Get your token (or generate a new one) from `here `_.
11 |
12 | 3. Set the ``ads_token`` bibmanager parameter:
13 |
14 | .. code-block:: shell
15 |
16 | # Set ads_token to 'my_ads_token':
17 | bibm config ads_token my_ads_token
18 |
19 | ----------------------------------------------------------------------
20 |
21 | ads-search
22 | ----------
23 |
24 | Do a query on ADS.
25 |
26 | **Usage**
27 |
28 | .. code-block:: shell
29 |
30 | bibm ads-search [-h] [-n] [-a] [-f] [-o]
31 |
32 | **Description**
33 |
34 | This command enables ADS queries. The query syntax is identical to
35 | a query in the new ADS's one-box search engine:
36 | https://ui.adsabs.harvard.edu.
37 | Here there is a detailed documentations for ADS searches:
38 | https://adsabs.github.io/help/search/search-syntax
39 | See below for typical query examples.
40 |
41 | | If you set the ``-a/--add`` flag, the code will prompt to add
42 | entries to the database right after showing the ADS search results.
43 | Similarly, set the ``-f/--fetch`` or ``-o/--open`` flags to prompt
44 | to fetch or open PDF files right after showing the ADS search
45 | results. Note that you can combine these to add and fetch/open at
46 | the same time (e.g., ``bibm ads-search -a -o``), or you can
47 | fetch/open PDFs that are not in the database (e.g., ``bibm
48 | ads-search -o``).
49 | | *(New since version 1.2.7)*
50 |
51 | .. note:: Note that a query will display at most 'ads_display' entries on
52 | screen at once (see ``bibm config ads_display``). If a query matches
53 | more entries, the user can execute ``bibm ads-search -n``
54 | to display the next set of entries.
55 |
56 | .. caution:: When making an ADS query, note that
57 | ADS requires the field values (when necessary) to use `double` quotes.
58 | For example: `author:"^Fortney, J"`.
59 |
60 | **Options**
61 |
62 | | **-n, -\\-next**
63 | | Display next set of entries that matched the previous query.
64 | |
65 | | **-a, -\\-add**
66 | | Query to add an entry after displaying the search results.
67 | | *(New since version 1.2.7)*
68 | |
69 | | **-f, -\\-fetch**
70 | | Query to fetch a PDF after displaying the search results.
71 | | *(New since version 1.2.7)*
72 | |
73 | | **-o, -\\-open**
74 | | Query to fetch/open a PDF after displaying the search results.
75 | | *(New since version 1.2.7)*
76 | |
77 | | **-h, -\\-help**
78 | | Show this help message and exit.
79 |
80 |
81 | **Examples**
82 |
83 | .. code-block:: shell
84 |
85 | # Search entries for given author (press tab to prompt the autocompleter):
86 | bibm ads-search
87 | (Press 'tab' for autocomplete)
88 | author:"^Fortney, J"
89 |
90 | Title: Exploring A Photospheric Radius Correction to Model Secondary Eclipse
91 | Spectra for Transiting Exoplanets
92 | Authors: Fortney, Jonathan J.; et al.
93 | adsurl: https://ui.adsabs.harvard.edu/abs/2019arXiv190400025F
94 | bibcode: 2019arXiv190400025F
95 |
96 | Title: Laboratory Needs for Exoplanet Climate Modeling
97 | Authors: Fortney, J. J.; et al.
98 | adsurl: https://ui.adsabs.harvard.edu/abs/2018LPICo2065.2068F
99 | bibcode: 2018LPICo2065.2068F
100 |
101 | ...
102 |
103 | Showing entries 1--20 out of 74 matches. To show the next set, execute:
104 | bibm ads-search -n
105 |
106 |
107 | Basic author search examples:
108 |
109 | .. code-block:: shell
110 |
111 | # Search by author in article:
112 | bibm ads-search
113 | (Press 'tab' for autocomplete)
114 | author:"Fortney, J"
115 |
116 | # Search by first author:
117 | bibm ads-search
118 | (Press 'tab' for autocomplete)
119 | author:"^Fortney, J"
120 |
121 | # Search multiple authors:
122 | bibm ads-search
123 | (Press 'tab' for autocomplete)
124 | author:("Fortney, J" AND "Showman, A")
125 |
126 | Search combining multiple fields:
127 |
128 | .. code-block:: shell
129 |
130 | # Search by author AND year:
131 | bibm ads-search
132 | (Press 'tab' for autocomplete)
133 | author:"Fortney, J" year:2010
134 |
135 | # Search by author AND year range:
136 | bibm ads-search
137 | (Press 'tab' for autocomplete)
138 | author:"Fortney, J" year:2010-2019
139 |
140 | # Search by author AND words/phrases in title:
141 | bibm ads-search
142 | (Press 'tab' for autocomplete)
143 | author:"Fortney, J" title:Spitzer
144 |
145 | # Search by author AND words/phrases in abstract:
146 | bibm ads-search
147 | (Press 'tab' for autocomplete)
148 | author:"Fortney, J" abs:"HD 209458b"
149 |
150 | Restrict searches to articles or peer-reviewed articles:
151 |
152 | .. code-block:: shell
153 |
154 | # Search by author AND request only articles:
155 | bibm ads-search
156 | (Press 'tab' for autocomplete)
157 | author:"Fortney, J" property:article
158 |
159 | # Search by author AND request only peer-reviewed articles:
160 | bibm ads-search
161 | (Press 'tab' for autocomplete)
162 | author:"Fortney, J" property:refereed
163 |
164 | Add entries and fetch/open PDFs right after the ADS search:
165 |
166 | .. code-block:: shell
167 | :emphasize-lines: 2, 4, 16
168 |
169 | # Search and prompt to open a PDF right after (fetched PDF is not stored in database):
170 | bibm ads-search -o
171 | (Press 'tab' for autocomplete)
172 | author:"^Fortney, J" property:refereed year:2015-2019
173 |
174 | Title: Exploring a Photospheric Radius Correction to Model Secondary Eclipse
175 | Spectra for Transiting Exoplanets
176 | Authors: Fortney, Jonathan J.; et al.
177 | adsurl: https://ui.adsabs.harvard.edu/abs/2019ApJ...880L..16F
178 | bibcode: 2019ApJ...880L..16F
179 | ...
180 |
181 | Fetch/open entry from ADS:
182 | Syntax is: key: KEY_VALUE FILENAME
183 | or: bibcode: BIBCODE_VALUE FILENAME
184 | bibcode: 2019ApJ...880L..16F Fortney2019.pdf
185 |
186 | .. code-block:: shell
187 | :emphasize-lines: 2, 4, 16
188 |
189 | # Search and prompt to add entry to database right after:
190 | bibm ads-search -a
191 | (Press 'tab' for autocomplete)
192 | author:"^Fortney, J" property:refereed year:2015-2019
193 |
194 | Title: Exploring a Photospheric Radius Correction to Model Secondary Eclipse
195 | Spectra for Transiting Exoplanets
196 | Authors: Fortney, Jonathan J.; et al.
197 | adsurl: https://ui.adsabs.harvard.edu/abs/2019ApJ...880L..16F
198 | bibcode: 2019ApJ...880L..16F
199 | ...
200 |
201 | Add entry from ADS:
202 | Enter pairs of ADS bibcodes and BibTeX keys, one pair per line
203 | separated by blanks (press META+ENTER or ESCAPE ENTER when done):
204 | 2019ApJ...880L..16F FortneyEtal2019apjPhotosphericRadius
205 |
206 | .. code-block:: shell
207 | :emphasize-lines: 2, 4, 16
208 |
209 | # Search and prompt to add entry and fetch/open its PDF right after:
210 | bibm ads-search -a -f
211 | (Press 'tab' for autocomplete)
212 | author:"^Fortney, J" property:refereed year:2015-2019
213 |
214 | Title: Exploring a Photospheric Radius Correction to Model Secondary Eclipse
215 | Spectra for Transiting Exoplanets
216 | Authors: Fortney, Jonathan J.; et al.
217 | adsurl: https://ui.adsabs.harvard.edu/abs/2019ApJ...880L..16F
218 | bibcode: 2019ApJ...880L..16F
219 | ...
220 |
221 | Add entry from ADS:
222 | Enter pairs of ADS bibcodes and BibTeX keys, one pair per line
223 | separated by blanks (press META+ENTER or ESCAPE ENTER when done):
224 | 2019ApJ...880L..16F FortneyEtal2019apjPhotosphericRadius
225 |
226 |
227 | ----------------------------------------------------------------------
228 |
229 | ads-add
230 | -------
231 |
232 | Add entries from ADS by bibcode into the bibmanager database.
233 |
234 | **Usage**
235 |
236 | .. code-block:: shell
237 |
238 | bibm ads-add [-h] [-f] [-o] [bibcode key] [tag1 [tag2 ...]]
239 |
240 | **Description**
241 |
242 | This command add BibTeX entries from ADS by specifying pairs of
243 | ADS bibcodes and BibTeX keys.
244 |
245 | Executing this command without arguments (i.e., ``bibm ads-add``) launches
246 | an interactive prompt session allowing the user to enter multiple
247 | bibcode, key pairs.
248 |
249 | By default, added entries replace previously existent entries in the
250 | bibmanager database.
251 |
252 | | With the optional arguments ``-f/--fetch`` or ``-o/--open``, the
253 | code will attempt to fetch and fetch/open (respectively) the
254 | associated PDF files of the added entries.
255 | | *(New since version 1.2.7)*
256 |
257 | | Either at ``bibm ads-add`` or later via the prompt you can specify
258 | tags for the entries to be add.
259 | | *(New since version 1.4)*
260 |
261 | **Options**
262 |
263 | | **bibcode**
264 | | The ADS bibcode of an entry.
265 | |
266 | | **key**
267 | | BibTeX key to assign to the entry.
268 | |
269 | | **tags**
270 | | Optional BibTeX tags to assign to the entries.
271 | | *(New since version 1.4)*
272 | |
273 | | **-f, -\\-fetch**
274 | | Fetch the PDF of the added entries.
275 | | *(New since version 1.2.7)*
276 | |
277 | | **-o, -\\-open**
278 | | Fetch and open the PDF of the added entries.
279 | | *(New since version 1.2.7)*
280 | |
281 | | **-h, -\\-help**
282 | | Show this help message and exit.
283 |
284 | **Examples**
285 |
286 | .. code-block:: shell
287 | :emphasize-lines: 2, 4
288 |
289 | # Let's search and add the greatest astronomy PhD thesis of all times:
290 | bibm ads-search
291 | (Press 'tab' for autocomplete)
292 | author:"^payne, cecilia" doctype:phdthesis
293 |
294 | Title: Stellar Atmospheres; a Contribution to the Observational Study of High
295 | Temperature in the Reversing Layers of Stars.
296 | Authors: Payne, Cecilia Helena
297 | adsurl: https://ui.adsabs.harvard.edu/abs/1925PhDT.........1P
298 | bibcode: 1925PhDT.........1P
299 |
300 | .. code-block:: shell
301 |
302 | # Add the entry to the bibmanager database:
303 | bibm ads-add 1925PhDT.........1P Payne1925phdStellarAtmospheres
304 |
305 | The user can optionally assign tags or request to fetch/open PDFs:
306 |
307 | .. code-block:: shell
308 | :emphasize-lines: 2, 6, 9
309 |
310 | # Add the entry and assign a 'stars' tag to it:
311 | bibm ads-add 1925PhDT.........1P Payne1925phdStellarAtmospheres stars
312 |
313 |
314 | # Add the entry and fetch its PDF:
315 | bibm ads-add -f 1925PhDT.........1P Payne1925phdStellarAtmospheres
316 |
317 | # Add the entry and fetch/open its PDF:
318 | bibm ads-add -o 1925PhDT.........1P Payne1925phdStellarAtmospheres
319 |
320 | Alternatively, the call can be done without arguments, which allow the user to request multiple entries at once (and as above, set tags to each entry as desired):
321 |
322 | .. code-block:: shell
323 | :emphasize-lines: 2, 6, 9, 13, 14
324 |
325 | # A call without bibcode,key arguments (interactive prompt):
326 | bibm ads-add
327 | Enter pairs of ADS bibcodes and BibTeX keys (plus optional tags)
328 | Use one line for each BibTeX entry, separate fields with blank spaces.
329 | (press META+ENTER or ESCAPE ENTER when done):
330 | 1925PhDT.........1P Payne1925phdStellarAtmospheres stars
331 |
332 | # Multiple entries at once, assigning tags (interactive prompt):
333 | bibm ads-add
334 | Enter pairs of ADS bibcodes and BibTeX keys (plus optional tags)
335 | Use one line for each BibTeX entry, separate fields with blank spaces.
336 | (press META+ENTER or ESCAPE ENTER when done):
337 | 1925PhDT.........1P Payne1925phdStellarAtmospheres stars
338 | 1957RvMP...29..547B BurbidgeEtal1957rvmpStellarSynthesis stars nucleosynthesis
339 |
340 | ----------------------------------------------------------------------
341 |
342 | .. _ads-update:
343 |
344 | ads-update
345 | ----------
346 |
347 | Update bibmanager database cross-checking entries with ADS.
348 |
349 | **Usage**
350 |
351 | .. code-block:: shell
352 |
353 | bibm ads-update [-h] [update_keys]
354 |
355 | **Description**
356 |
357 | This command triggers an ADS search of all entries in the ``bibmanager``
358 | database that have a ``bibcode``. Replacing these entries with
359 | the output from ADS.
360 | The main utility of this command is to auto-update entries that
361 | were added as arXiv version, with their published version.
362 |
363 | For arXiv updates, this command updates automatically the year and
364 | journal of the key (where possible). This is done by searching for
365 | the year and the string `'arxiv'` in the key, using the bibcode info.
366 | For example, an entry with key `'NameEtal2010arxivGJ436b'` whose bibcode
367 | changed from `'2010arXiv1007.0324B'` to `'2011ApJ...731...16B'`, will have
368 | a new key `'NameEtal2011apjGJ436b'`.
369 | To disable this feature, set the ``update_keys`` optional argument to `'no'`.
370 |
371 | **Options**
372 |
373 | | **update_keys**
374 | | Update the keys of the entries. (choose from: {no, arxiv}, default: arxiv).
375 | |
376 | | **-h, -\\-help**
377 | | Show this help message and exit.
378 |
379 | **Examples**
380 |
381 | .. note:: These example outputs assume that you merged the sample bibfile
382 | already, i.e.: ``bibm merge ~/.bibmanager/examples/sample.bib``
383 |
384 | .. code-block:: shell
385 |
386 | # Look at this entry with old info from arXiv:
387 | bibm search -v
388 | author:"^Beaulieu"
389 |
390 | Title: Methane in the Atmosphere of the Transiting Hot Neptune GJ436b?, 2010
391 | Authors: {Beaulieu}, J.-P.; et al.
392 | bibcode: 2010arXiv1007.0324B
393 | ADS url: http://adsabs.harvard.edu/abs/2010arXiv1007.0324B
394 | arXiv url: http://arxiv.org/abs/arXiv:1007.0324
395 | key: BeaulieuEtal2010arxivGJ436b
396 |
397 |
398 | # Update bibmanager entries that are in ADS:
399 | bibm ads-update
400 |
401 | Merged 0 new entries.
402 | (Not counting updated references)
403 | There were 1 entries updated from ArXiv to their peer-reviewed version.
404 | These ones changed their key:
405 | BeaulieuEtal2010arxivGJ436b -> BeaulieuEtal2011apjGJ436b
406 |
407 |
408 | # Let's take a look at this entry again:
409 | bibm search -v
410 | author:"^Beaulieu"
411 |
412 | Title: Methane in the Atmosphere of the Transiting Hot Neptune GJ436B?, 2011
413 | Authors: {Beaulieu}, J. -P.; et al.
414 | bibcode: 2011ApJ...731...16B
415 | ADS url: https://ui.adsabs.harvard.edu/abs/2011ApJ...731...16B
416 | arXiv url: http://arxiv.org/abs/1007.0324
417 | key: BeaulieuEtal2011apjGJ436b
418 |
419 | .. note:: There might be cases when one does not want to ADS-update an
420 | entry. To prevent this to happen, the user can set the *freeze*
421 | meta-parameter through the ``bibm edit`` command (see :ref:`edit`).
422 |
423 | ----------------------------------------------------------------------
424 |
425 | **References**
426 |
427 | .. [#ADStoken] https://github.com/adsabs/adsabs-dev-api#access
428 |
--------------------------------------------------------------------------------
/bibmanager/pdf_manager/pdf_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | __all__ = [
5 | 'guess_name',
6 | 'open',
7 | 'set_pdf',
8 | 'request_ads',
9 | 'fetch',
10 | ]
11 |
12 | import re
13 | import os
14 | import sys
15 | import shutil
16 | import urllib
17 | import subprocess
18 | import webbrowser
19 | # Be explicit about builtin to avoid conflict with pm.open
20 | from io import open as builtin_open
21 |
22 | import requests
23 |
24 | from .. import bib_manager as bm
25 | from .. import utils as u
26 |
27 |
28 | def guess_name(bib, arxiv=False):
29 | r"""
30 | Guess a PDF filename for a BibTex entry. Include at least author
31 | and year. If entry has a bibtex, include journal info.
32 |
33 | Parameters
34 | ----------
35 | bib: A Bib() instance
36 | BibTex entry to generate a PDF filename for.
37 | arxiv: Bool
38 | True if this PDF comes from ArXiv. If so, prepend 'arxiv_' into
39 | the output name.
40 |
41 | Returns
42 | -------
43 | guess_filename: String
44 | Suggested name for a PDF file of the entry.
45 |
46 | Examples
47 | --------
48 | >>> import bibmanager.bib_manager as bm
49 | >>> import bibmanager.pdf_manager as pm
50 | >>> bibs = bm.load()
51 | >>> # Entry without bibcode:
52 | >>> bib = bm.Bib('''@misc{AASteam2016aastex61,
53 | >>> author = {{AAS Journals Team} and {Hendrickson}, A.},
54 | >>> title = {AASJournals/AASTeX60: Version 6.1},
55 | >>> year = 2016,
56 | >>> }''')
57 | >>> print(pm.guess_name(bib))
58 | AASJournalsTeam2016.pdf
59 |
60 | >>> # Entry with bibcode:
61 | >>> bib = bm.Bib('''@ARTICLE{HuangEtal2014jqsrtCO2,
62 | >>> author = {{Huang (黄新川)}, Xinchuan and {Gamache}, Robert R.},
63 | >>> title = "{Reliable infrared line lists for 13 CO$_{2}$}",
64 | >>> year = "2014",
65 | >>> adsurl = {https://ui.adsabs.harvard.edu/abs/2014JQSRT.147..134H},
66 | >>> }''')
67 | >>> print(pm.guess_name(bib))
68 | >>> Huang2014_JQSRT_147_134.pdf
69 |
70 | >>> # Say, we are querying from ArXiv:
71 | >>> print(pm.guess_name(bib, arxiv=True))
72 | Huang2014_arxiv_JQSRT_147_134.pdf
73 | """
74 | # Remove non-ascii and non-letter characters:
75 | author = bib.get_authors(format='ushort')
76 | author = author.encode('ascii', errors='ignore').decode()
77 | author = re.sub('\W', '', author)
78 |
79 | year = '' if bib.year is None else bib.year
80 | guess_filename = f"{author}{year}.pdf"
81 |
82 | if author == '' and year == '':
83 | raise ValueError(
84 | 'Could not guess a good filename since entry does not '
85 | 'have author nor year fields')
86 | if bib.bibcode is not None:
87 | journal = re.sub('(\.|&)', '', bib.bibcode[4:9])
88 | if arxiv and journal.lower() != 'arxiv':
89 | journal = f'arxiv_{journal}'
90 |
91 | vol = bib.bibcode[ 9:13].replace('.', '')
92 | if vol != '':
93 | vol = f'_{vol}'
94 |
95 | page = bib.bibcode[14:18].replace('.', '')
96 | if page != '':
97 | page = f'_{page}'
98 | guess_filename = f'{author}{year}_{journal}{vol}{page}.pdf'
99 | return guess_filename
100 |
101 |
102 | def open(pdf=None, key=None, bibcode=None, pdf_file=None):
103 | """
104 | Open the PDF file associated to the entry matching the input key
105 | or bibcode argument.
106 |
107 | Parameters
108 | ----------
109 | pdf: String
110 | PDF file to open. This refers to a filename located in
111 | home/pdf/. Thus, it should not contain the file path.
112 | key: String
113 | Key of Bibtex entry to open it's PDF (ignored if pdf is not None).
114 | bibcode: String
115 | Bibcode of Bibtex entry to open it's PDF (ignored if pdf or key
116 | is not None).
117 | pdf_file: String
118 | Absolute path to PDF file to open. If not None, this argument
119 | takes precedence over pdf, key, and bibcode.
120 | """
121 | if pdf is None and key is None and bibcode is None and pdf_file is None:
122 | raise ValueError("At least one of the arguments must be not None")
123 |
124 | # Set pdf_file:
125 | if pdf_file is not None:
126 | pass
127 | elif pdf is not None:
128 | pdf_file = u.BM_PDF() + pdf
129 | else:
130 | bib = bm.find(key=key, bibcode=bibcode)
131 | if bib is None:
132 | raise ValueError('Requested entry does not exist in database')
133 | if bib.pdf is None:
134 | raise ValueError('Entry does not have a PDF in the database')
135 | pdf_file = u.BM_PDF() + bib.pdf
136 |
137 | if not os.path.isfile(pdf_file):
138 | path, pdf = os.path.split(pdf_file)
139 | raise ValueError(f"Requested PDF file '{pdf}' does not exist in "
140 | f"database PDF dir '{path}'")
141 |
142 | # Always use default PDF viewers:
143 | if sys.platform == "win32":
144 | os.startfile(pdf_file)
145 | else:
146 | opener = "open" if sys.platform == "darwin" else "xdg-open"
147 | subprocess.run([opener, pdf_file], stdout=subprocess.DEVNULL)
148 |
149 |
150 | def set_pdf(
151 | bib, pdf=None, bin_pdf=None, filename=None,
152 | arxiv=False, replace=False,
153 | ):
154 | """
155 | Update the PDF file of the given BibTex entry in database
156 | If pdf is not None, move the file into the database pdf folder.
157 |
158 | Parameters
159 | ----------
160 | bibcode: String or Bib() instance
161 | Entry to be updated (must exist in the Bibmanager database).
162 | If string, the ADS bibcode of key ID of the entry.
163 | pdf: String
164 | Path to an existing PDF file.
165 | Only one of pdf and bin_pdf must be not None.
166 | bin_pdf: String
167 | PDF content in binary format (e.g., as in req.content).
168 | Only one of pdf and bin_pdf must be not None.
169 | arxiv: Bool
170 | Flag indicating the source of the PDF. If True, insert
171 | 'arxiv' into a guessed name.
172 | filename: String
173 | Filename to assign to the PDF file. If None, take name from
174 | pdf input argument, or else from guess_name().
175 | replace: Bool
176 | Replace without asking if the entry already has a PDF assigned;
177 | else, ask the user.
178 |
179 | Returns
180 | -------
181 | filename: String
182 | If bib.pdf is not None at the end of this operation,
183 | return the absolute path to the bib.pdf file (even if this points
184 | to a pre-existing file).
185 | Else, return None.
186 | """
187 | if isinstance(bib, str):
188 | e = bm.find(key=bib)
189 | bib = bm.find(bibcode=bib) if e is None else e
190 | if bib is None:
191 | raise ValueError('BibTex entry is not in Bibmanager database')
192 |
193 | if (pdf is None) + (bin_pdf is None) != 1:
194 | raise ValueError('Exactly one of pdf or bin_pdf must be not None')
195 |
196 | # Let's have a guess, if needed:
197 | guess_filename = guess_name(bib, arxiv=arxiv)
198 | if filename is None:
199 | filename = os.path.basename(pdf) if pdf is not None else guess_filename
200 |
201 | if not filename.lower().endswith('.pdf'):
202 | raise ValueError('Invalid filename, must have a .pdf extension')
203 | if os.path.dirname(filename) != '':
204 | raise ValueError('filename must not have a path')
205 |
206 | if pdf is not None and bib.pdf is not None:
207 | pdf_is_not_bib_pdf = os.path.abspath(pdf) != f'{u.BM_PDF()}{bib.pdf}'
208 | else:
209 | pdf_is_not_bib_pdf = True
210 |
211 | # PDF files in BM_PDF (except for the entry being fetched):
212 | pdf_names = [
213 | file
214 | for file in os.listdir(u.BM_PDF())
215 | if os.path.splitext(file)[1].lower() == '.pdf']
216 | with u.ignored(ValueError):
217 | pdf_names.remove(bib.pdf)
218 | if pdf == f'{u.BM_PDF()}{filename}':
219 | pdf_names.remove(filename)
220 |
221 | if not replace and bib.pdf is not None and pdf_is_not_bib_pdf:
222 | rep = u.req_input(f"Bibtex entry already has a PDF file: '{bib.pdf}' "
223 | "Replace?\n[]yes, [n]o.\n", options=['', 'y', 'yes', 'n', 'no'])
224 | if rep in ['n', 'no']:
225 | return f"{u.BM_PDF()}{bib.pdf}"
226 |
227 | while filename in pdf_names:
228 | overwrite = input(
229 | f"A filename '{filename}' already exists. Overwrite?\n"
230 | f"[]yes, [n]o, or type new file name (e.g., {guess_filename}).\n")
231 | if overwrite in ['n', 'no']:
232 | return
233 | elif overwrite in ['', 'y', 'yes']:
234 | break
235 | elif overwrite.lower().endswith('.pdf'):
236 | filename = overwrite
237 |
238 | # Delete pre-existing file only if not merely renaming:
239 | if pdf is None or pdf_is_not_bib_pdf:
240 | with u.ignored(OSError):
241 | os.remove(f"{u.BM_PDF()}{bib.pdf}")
242 |
243 | if pdf is not None:
244 | shutil.move(pdf, f"{u.BM_PDF()}{filename}")
245 | else:
246 | with builtin_open(f"{u.BM_PDF()}{filename}", 'wb') as f:
247 | f.write(bin_pdf)
248 | print(f"Saved PDF to: '{u.BM_PDF()}{filename}'.")
249 |
250 | # Update entry and database:
251 | bibs = bm.load()
252 | index = bibs.index(bib)
253 | bib.pdf = filename
254 | bibs[index] = bib
255 | bm.save(bibs)
256 | bm.export(bibs, meta=True)
257 |
258 | return f"{u.BM_PDF()}{filename}"
259 |
260 |
261 | def request_ads(bibcode, source='journal'):
262 | """
263 | Request a PDF from ADS.
264 |
265 | Parameters
266 | ----------
267 | bibcode: String
268 | ADS bibcode of entry to request PDF.
269 | source: String
270 | Flag to indicate from which source make the request.
271 | Choose between: 'journal', 'ads', or 'arxiv'.
272 |
273 | Returns
274 | -------
275 | req: requests.Response instance
276 | The server's response to the HTTP request.
277 | Return None if it failed to establish a connection.
278 |
279 | Note
280 | ----
281 | If the request succeeded, but the response content is not a PDF,
282 | this function modifies the value of req.status_code (in a desperate
283 | attempt to give a meaningful answer).
284 |
285 | Examples
286 | --------
287 | >>> import bibmanager.pdf_manager as pm
288 | >>> bibcode = '2017AJ....153....3C'
289 | >>> req = pm.request_ads(bibcode)
290 |
291 | >>> # On successful request, you can save the PDF file as, e.g.:
292 | >>> with open('fetched_file.pdf', 'wb') as f:
293 | >>> f.write(r.content)
294 |
295 | >>> # Nature articles are not directly accessible from Journal:
296 | >>> bibcode = '2018NatAs...2..220D'
297 | >>> req = pm.request_ads(bibcode)
298 | Request failed with status code 404: NOT FOUND
299 | >>> # Get ArXiv instead:
300 | >>> req = pm.request_ads(bibcode, source='arxiv')
301 | """
302 | sources = {
303 | 'journal': 'PUB_PDF',
304 | 'arxiv': 'EPRINT_PDF',
305 | 'ads': 'ADS_PDF',
306 | }
307 | if source not in sources:
308 | raise ValueError(f"Source argument must be one of {list(sources)}")
309 |
310 | query = (
311 | 'https://ui.adsabs.harvard.edu/link_gateway/'
312 | f'{urllib.parse.quote(bibcode)}/{sources[source]}'
313 | )
314 |
315 | # This fixed MNRAS requests and CAPTCHA issues:
316 | # (take from https://stackoverflow.com/questions/43165341)
317 | headers = requests.utils.default_headers()
318 | headers['User-Agent'] = (
319 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
320 | '(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
321 | )
322 |
323 | #headers['Accept-Encoding'] = 'gzip, deflate, sdch'
324 | #headers['Accept-Language'] = 'en-US,en;q=0.8'
325 | #headers['Upgrade-Insecure-Requests'] = '1'
326 | #headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
327 | #headers['Cache-Control'] = 'max-age=0'
328 |
329 | # Make the request:
330 | try:
331 | s = requests.Session()
332 | s.max_redirects = 5
333 | req = s.get(query, headers=headers)
334 | except requests.exceptions.ConnectionError:
335 | print('Failed to establish a web connection.')
336 | return None
337 | # https://stackoverflow.com/questions/42237672/python-toomanyredirects
338 | except requests.exceptions.TooManyRedirects as e:
339 | req = requests.Response()
340 | req.status_code = 302
341 |
342 | if not req.ok:
343 | print(
344 | f'Request failed with status code {req.status_code}: {req.reason}'
345 | )
346 |
347 | elif req.status_code == 302:
348 | print("File not found after too many redirects")
349 |
350 | elif req.headers['Content-Type'].startswith('text/html') \
351 | and 'CAPTCHA' in req.content.decode():
352 | browse = u.req_input(
353 | 'There are issues with CAPTCHA verification, '
354 | 'try to open PDF in browser?\n[]yes [n]o.\n',
355 | options=['', 'y', 'yes', 'n', 'no'],
356 | )
357 | if browse in ['', 'y', 'yes']:
358 | webbrowser.open(query, new=2)
359 | print(
360 | "\nIf you managed to download the PDF, add the PDF into "
361 | "the database\nwith the following command (and right path):\n"
362 | f"bibm pdf '{bibcode}' PATH/TO/FILE.pdf guess",
363 | )
364 | req.status_code = -101
365 | else:
366 | req.status_code = -102
367 |
368 | # Request is OK, but output is not a PDF:
369 | elif not req.headers['Content-Type'].startswith('application/pdf'):
370 | print('Request succeeded, but fetched content is not a PDF (might '
371 | 'have been\nredirected to website due to paywall).')
372 | req.status_code = -100
373 |
374 | return req
375 |
376 |
377 | def fetch(bibcode, filename=None, replace=None):
378 | """
379 | Attempt to fetch a PDF file from ADS. If successful, then
380 | add it into the database. If the fetch succeeds but the bibcode is
381 | not in the database, download file to current folder.
382 |
383 | Parameters
384 | ----------
385 | bibcode: String
386 | ADS bibcode of entry to update.
387 | filename: String
388 | Filename to assign to the PDF file. If None, get from
389 | guess_name() function.
390 | Replace: Bool
391 | If True, enforce replacing a PDF regardless of a pre-existing one.
392 | If None (default), only ask when fetched PDF comes from arxiv.
393 |
394 | Returns
395 | -------
396 | filename: String
397 | If successful, return the full path of the file name.
398 | If not, return None.
399 | """
400 | arxiv = False
401 | print('Fetching PDF file from Journal website:')
402 | req = request_ads(bibcode, source='journal')
403 | if req is None:
404 | return
405 |
406 | if req.status_code != 200:
407 | print('Fetching PDF file from ADS website:')
408 | req = request_ads(bibcode, source='ads')
409 | if req is None:
410 | return
411 |
412 | if req.status_code != 200:
413 | print('Fetching PDF file from ArXiv website:')
414 | req = request_ads(bibcode, source='arxiv')
415 | arxiv = True
416 | if replace is None:
417 | replace = False
418 | if req is None:
419 | return
420 | if replace is None:
421 | replace = True
422 |
423 | if req.status_code == 200:
424 | if bm.find(bibcode=bibcode) is None:
425 | if filename is None:
426 | filename = f'{bibcode}.pdf'
427 | with builtin_open(filename, 'wb') as f:
428 | f.write(req.content)
429 | print(f"Saved PDF to: '{filename}'.\n"
430 | "(Note that BibTex entry is not in the Bibmanager database)")
431 | else:
432 | filename = set_pdf(
433 | bibcode, bin_pdf=req.content, filename=filename, arxiv=arxiv,
434 | replace=replace)
435 | return filename
436 |
437 | print('Could not fetch PDF from any source.')
438 |
439 |
--------------------------------------------------------------------------------
/bibmanager/ads_manager/ads_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | __all__ = [
5 | 'manager',
6 | 'search',
7 | 'display',
8 | 'add_bibtex',
9 | 'update',
10 | 'key_update',
11 | ]
12 |
13 | import json
14 | import os
15 | import pickle
16 | import sys
17 | import textwrap
18 | import urllib
19 |
20 | import prompt_toolkit
21 | import pygments
22 | from pygments.token import Token
23 | import requests
24 |
25 | from .. import bib_manager as bm
26 | from .. import config_manager as cm
27 | from .. import utils as u
28 |
29 |
30 | def manager(query=None):
31 | """
32 | A manager, it doesn't really do anything, it just delegates.
33 | """
34 | rows = int(cm.get('ads_display'))
35 | if query is None and not os.path.exists(u.BM_CACHE()):
36 | print("There are no more entries for this query.")
37 | return
38 |
39 | if query is None:
40 | with open(u.BM_CACHE(), 'rb') as handle:
41 | results, query, start, index, nmatch = pickle.load(handle)
42 | last = start + len(results)
43 | if last < nmatch and index + rows > last:
44 | new_results, nmatch = search(query, start=last)
45 | results = results[index-start:] + new_results
46 | start = index
47 | last = start + len(results)
48 | else:
49 | start = 0
50 | index = start
51 | results, nmatch = search(query, start=start)
52 |
53 | display(results, start, index, rows, nmatch)
54 | index += rows
55 | if index >= nmatch:
56 | with u.ignored(OSError):
57 | os.remove(u.BM_CACHE())
58 | else:
59 | with open(u.BM_CACHE(), 'wb') as handle:
60 | pickle.dump(
61 | [results, query, start, index, nmatch], handle, protocol=4)
62 |
63 |
64 | def search(query, start=0, cache_rows=200, sort='pubdate+desc'):
65 | """
66 | Make a query from ADS.
67 |
68 | Parameters
69 | ----------
70 | query: String
71 | A query string like an entry in the new ADS interface:
72 | https://ui.adsabs.harvard.edu/
73 | start: Integer
74 | Starting index of entry to return.
75 | cache_rows: Integer
76 | Maximum number of entries to return.
77 | sort: String
78 | Sorting field and direction to use.
79 |
80 | Returns
81 | -------
82 | results: List of dicts
83 | Query outputs between indices start and start+rows.
84 | nmatch: Integer
85 | Total number of entries matched by the query.
86 |
87 | Resources
88 | ---------
89 | A comprehensive description of the query format:
90 | - http://adsabs.github.io/help/search/
91 | Description of the query parameters:
92 | - https://github.com/adsabs/adsabs-dev-api/blob/master/Search_API.ipynb
93 |
94 | Examples
95 | --------
96 | >>> import bibmanager.ads_manager as am
97 | >>> # Search entries by author (note the need for double quotes,
98 | >>> # otherwise, the search might produce bogus results):
99 | >>> query = 'author:"cubillos, p"'
100 | >>> results, nmatch = am.search(query)
101 | >>> # Search entries by first author:
102 | >>> query = 'author:"^cubillos, p"'
103 | >>> # Combine search by first author and year:
104 | >>> query = 'author:"^cubillos, p" year:2017'
105 | >>> # Restrict search to article-type entries:
106 | >>> query = 'author:"^cubillos, p" property:article'
107 | >>> # Restrict search to peer-reviewed articles:
108 | >>> query = 'author:"^cubillos, p" property:refereed'
109 |
110 | >>> # Attempt with invalid token:
111 | >>> results, nmatch = am.search(query)
112 | ValueError: Invalid ADS request: Unauthorized, check you have a valid ADS token.
113 | >>> # Attempt with invalid query ('properties' instead of 'property'):
114 | >>> results, nmatch = am.search('author:"^cubillos, p" properties:refereed')
115 | ValueError: Invalid ADS request:
116 | org.apache.solr.search.SyntaxError: org.apache.solr.common.SolrException: undefined field properties
117 | """
118 | token = cm.get('ads_token')
119 | query = urllib.parse.quote(query)
120 |
121 | r = requests.get(
122 | 'https://api.adsabs.harvard.edu/v1/search/query?'
123 | f'q={query}&start={start}&rows={cache_rows}'
124 | f'&sort={sort}&fl=title,author,year,bibcode,pub',
125 | headers={'Authorization': f'Bearer {token}'})
126 | if not r.ok:
127 | if r.status_code == 401:
128 | raise ValueError(
129 | 'Unauthorized access to ADS. Check that the ADS token is valid.')
130 | try:
131 | reason = r.json()['error']
132 | except:
133 | reason = r.text
134 | raise ValueError(f'HTTP request failed ({r.status_code}): {reason}')
135 |
136 | resp = r.json()
137 |
138 | nmatch = resp['response']['numFound']
139 | results = resp['response']['docs']
140 |
141 | return results, nmatch
142 |
143 |
144 | def display(results, start, index, rows, nmatch, short=True):
145 | """
146 | Show on the prompt a list of entries from an ADS search.
147 |
148 | Parameters
149 | ----------
150 | results: List of dicts
151 | Subset of entries returned by a query.
152 | start: Integer
153 | Index assigned to first entry in results.
154 | index: Integer
155 | First index to display.
156 | rows: Integer
157 | Number of entries to display.
158 | nmatch: Integer
159 | Total number of entries corresponding to query (not necessarily
160 | the number of entries in results).
161 | short: Bool
162 | Format for author list. If True, truncate with 'et al' after
163 | the second author.
164 |
165 | Examples
166 | --------
167 | >>> import bibmanager.ads_manager as am
168 | >>> start = index = 0
169 | >>> query = 'author:"^cubillos, p" property:refereed'
170 | >>> results, nmatch = am.search(query, start=start)
171 | >>> display(results, start, index, rows, nmatch)
172 | """
173 | for result in results[index-start:index-start+rows]:
174 | tokens = [(Token.Text, '\n')]
175 | title = textwrap.fill(
176 | f"Title: {result['title'][0]}",
177 | width=78,
178 | subsequent_indent=' ')
179 | tokens += u.tokenizer('Title', title[7:])
180 |
181 | if 'author' in result:
182 | author_list = [u.parse_name(author) for author in result['author']]
183 | author_format = 'short' if short else 'long'
184 | authors = textwrap.fill(
185 | f"Authors: {u.get_authors(author_list, format=author_format)}",
186 | width=78,
187 | subsequent_indent=' ',
188 | )
189 | else:
190 | authors = 'Authors: ---'
191 | tokens += u.tokenizer('Authors', authors[9:])
192 |
193 | adsurl = f"https://ui.adsabs.harvard.edu/abs/{result['bibcode']}"
194 | tokens += u.tokenizer('ADS URL', adsurl)
195 |
196 | bibcode = result['bibcode']
197 | tokens += u.tokenizer('bibcode', bibcode, Token.Name.Label)
198 |
199 | style = prompt_toolkit.styles.style_from_pygments_cls(
200 | pygments.styles.get_style_by_name(cm.get('style')))
201 | prompt_toolkit.print_formatted_text(
202 | prompt_toolkit.formatted_text.PygmentsTokens(tokens),
203 | end="",
204 | style=style,
205 | output=prompt_toolkit.output.defaults.create_output(sys.stdout))
206 |
207 | if index + rows < nmatch:
208 | more = " To show the next set, execute:\nbibm ads-search -n"
209 | else:
210 | more = ""
211 | print(
212 | f"\nShowing entries {index+1}--{min(index+rows, nmatch)} out of "
213 | f"{nmatch} matches.{more}")
214 |
215 |
216 | def add_bibtex(
217 | input_bibcodes, input_keys, eprints=[], dois=[],
218 | update_keys=True, base=None, tags=None, return_replacements=False,
219 | ):
220 | """
221 | Add bibtex entries from a list of ADS bibcodes, with specified keys.
222 | New entries will replace old ones without asking if they are
223 | duplicates.
224 |
225 | Parameters
226 | ----------
227 | input_bibcodes: List of strings
228 | A list of ADS bibcodes.
229 | input_keys: List of strings
230 | BibTeX keys to assign to each bibcode.
231 | eprints: List of strings
232 | List of ArXiv IDs corresponding to the input bibcodes.
233 | dois: List of strings
234 | List of DOIs corresponding to the input bibcodes.
235 | update_keys: Bool
236 | If True, attempt to update keys of entries that were updated
237 | from arxiv to published versions.
238 | base: List of Bib() objects
239 | If None, merge new entries into the bibmanager database.
240 | If not None, merge new entries into base.
241 | tags: Nested list of strings
242 | The list of tags for each input bibcode.
243 | return_replacements: Bool
244 | If True, also return a dictionary of replaced keys.
245 |
246 | Returns
247 | -------
248 | bibs: List of Bib() objects
249 | Updated list of BibTeX entries.
250 | reps: Dict
251 | A dictionary of replaced key names.
252 |
253 | Examples
254 | --------
255 | >>> import bibmanager.ads_manager as am
256 | >>> # A successful add call:
257 | >>> bibcodes = ['1925PhDT.........1P']
258 | >>> keys = ['Payne1925phdStellarAtmospheres']
259 | >>> am.add_bibtex(bibcodes, keys)
260 | >>> # A failing add call:
261 | >>> bibcodes = ['1925PhDT....X....1P']
262 | >>> am.add_bibtex(bibcodes, keys)
263 | Error: There were no entries found for the input bibcodes.
264 |
265 | >>> # A successful add call with multiple entries:
266 | >>> bibcodes = ['1925PhDT.........1P', '2018MNRAS.481.5286F']
267 | >>> keys = ['Payne1925phdStellarAtmospheres', 'FolsomEtal2018mnrasHD219134']
268 | >>> am.add_bibtex(bibcodes, keys)
269 | >>> # A partially failing call will still add those that succeed:
270 | >>> bibcodes = ['1925PhDT.....X...1P', '2018MNRAS.481.5286F']
271 | >>> am.add_bibtex(bibcodes, keys)
272 | Warning: bibcode '1925PhDT.....X...1P' not found.
273 | """
274 | token = cm.get('ads_token')
275 | # Keep the originals untouched (copies will be modified):
276 | bibcodes, keys = input_bibcodes.copy(), input_keys.copy()
277 |
278 | if tags is None:
279 | tags = [[] for _ in bibcodes]
280 |
281 | # Make request:
282 | size = 2000
283 | bibcode_chunks = [bibcodes[i:i+size] for i in range(0,len(bibcodes), size)]
284 |
285 | nfound = 0
286 | results = ''
287 | for bc_chunk in bibcode_chunks:
288 | r = requests.post(
289 | "https://api.adsabs.harvard.edu/v1/export/bibtex",
290 | headers={"Authorization": f'Bearer {token}',
291 | "Content-type": "application/json"},
292 | data=json.dumps({"bibcode":bc_chunk}))
293 | # No valid outputs:
294 | if not r.ok:
295 | if r.status_code == 500:
296 | raise ValueError(
297 | 'HTTP request has failed (500): '
298 | 'Internal Server Error')
299 | if r.status_code == 401:
300 | raise ValueError(
301 | 'Unauthorized access to ADS. '
302 | 'Check that the ADS token is valid.')
303 | if r.status_code == 404:
304 | raise ValueError(
305 | 'There were no entries found for the requested bibcodes.')
306 | try:
307 | reason = r.json()['error']
308 | except:
309 | reason = r.text
310 | raise ValueError(f'HTTP request failed ({r.status_code}): {reason}')
311 | resp = r.json()
312 | nfound += int(resp['msg'].split()[1])
313 | results += resp["export"]
314 |
315 | # Keep counts of things:
316 | nreqs = len(bibcodes)
317 |
318 | # Split output into separate BibTeX entries (keep as strings):
319 | results = results.strip().split("\n\n")
320 |
321 | new_keys = {}
322 | new_bibs = []
323 | founds = [False for _ in bibcodes]
324 | arxiv_updates = 0
325 | # Match results to bibcodes,keys:
326 | for result in reversed(results):
327 | ibib = None
328 | new = bm.Bib(result)
329 | # Output bibcode is one of the input bibcodes:
330 | if new.bibcode in bibcodes:
331 | ibib = bibcodes.index(new.bibcode)
332 | # Else, check for bibcode updates in remaining bibcodes:
333 | elif new.eprint is not None and new.eprint in eprints:
334 | ibib = eprints.index(new.eprint)
335 | elif new.doi is not None and new.doi in dois:
336 | ibib = dois.index(new.doi)
337 |
338 | if ibib is not None:
339 | new.tags = tags[ibib]
340 | new_key = keys[ibib]
341 | updated_key = key_update(new_key, new.bibcode, bibcodes[ibib])
342 | if update_keys and updated_key != new_key:
343 | new_key = updated_key
344 | new_keys[keys[ibib]] = updated_key
345 | if 'arXiv' in bibcodes[ibib] and 'arXiv' not in new.bibcode:
346 | arxiv_updates += 1
347 |
348 | new.update_key(new_key)
349 | new_bibs.append(new)
350 | founds[ibib] = True
351 | results.remove(result)
352 |
353 | # Warnings:
354 | if nfound < nreqs or len(results) > 0:
355 | warning = u.BANNER + "Warning:\n"
356 | # bibcodes not found
357 | missing = [
358 | bibcode
359 | for bibcode,found in zip(bibcodes, founds)
360 | if not found]
361 | if nfound < nreqs:
362 | warning += (
363 | '\nThere were bibcodes unmatched or not found in ADS:\n - '
364 | + '\n - '.join(missing) + "\n")
365 | # bibcodes not matched:
366 | if len(results) > 0:
367 | warning += '\nThese ADS results did not match input bibcodes:\n\n'
368 | warning += '\n\n'.join(results) + "\n"
369 | warning += u.BANNER
370 | print(warning)
371 |
372 | n_new = len(new_bibs)
373 | if base is None:
374 | nbibs = len(bm.load())
375 | else:
376 | nbibs = len(base)
377 |
378 | # Add to bibmanager database or base:
379 | updated = bm.merge(new=new_bibs, take='new', base=base)
380 | actually_new = len(updated) - nbibs
381 | updated_existing = n_new - actually_new
382 | if updated_existing > 0:
383 | print(f'Updated {updated_existing} existing entries.')
384 |
385 | # Report arXiv updates:
386 | if arxiv_updates > 0:
387 | print(
388 | f"\nThere were {arxiv_updates} entries updated from ArXiv to "
389 | "their peer-reviewed version."
390 | )
391 | if len(new_keys) > 0:
392 | print("These entries changed their key:")
393 | for old_key,new_key in new_keys.items():
394 | print(f' {old_key} -> {new_key}')
395 |
396 | if return_replacements:
397 | return updated, new_keys
398 | return updated
399 |
400 |
401 | def update(update_keys=True, base=None, return_replacements=False):
402 | """
403 | Do an ADS query by bibcode for all entries that have an ADS bibcode.
404 | Replacing old entries with the new ones. The main use of
405 | this function is to update arxiv version of articles.
406 |
407 | Parameters
408 | ----------
409 | update_keys: Bool
410 | If True, attempt to update keys of entries that were updated
411 | from arxiv to published versions.
412 | base: List of Bib() objects
413 | The bibfile entries to update. If None, use the entries from
414 | the bibmanager database as base.
415 | return_replacements: Bool
416 | If True, also return a dictionary of replaced keys.
417 |
418 | Returns
419 | -------
420 | reps: Dict
421 | A dictionary of replaced key names.
422 | """
423 | if base is None:
424 | bibs = bm.load()
425 | else:
426 | bibs = base
427 |
428 | # Filter entries that have a bibcode and not frozen:
429 | keys = [
430 | bib.key for bib in bibs
431 | if bib.bibcode is not None and not bib.freeze]
432 | bibcodes = [
433 | bib.bibcode for bib in bibs
434 | if bib.bibcode is not None and not bib.freeze]
435 | eprints = [
436 | bib.eprint for bib in bibs
437 | if bib.bibcode is not None and not bib.freeze]
438 | dois = [
439 | bib.doi for bib in bibs
440 | if bib.bibcode is not None and not bib.freeze]
441 | tags = [
442 | bib.tags for bib in bibs
443 | if bib.bibcode is not None and not bib.freeze]
444 | # Query-replace:
445 | bibs, replacements = add_bibtex(
446 | bibcodes, keys, eprints, dois, update_keys, base, tags,
447 | return_replacements=True,
448 | )
449 |
450 | if return_replacements:
451 | return bibs, replacements
452 | return bibs
453 |
454 |
455 | def key_update(key, bibcode, alternate_bibcode):
456 | r"""
457 | Update key with year and journal of arxiv version of a key.
458 |
459 | This function will search and update the year in a key,
460 | and the journal if the key contains the word 'arxiv' (case
461 | insensitive).
462 |
463 | The function extracts the info from the old and new bibcodes.
464 | ADS bibcode format: http://adsabs.github.io/help/actions/bibcode
465 |
466 | Examples
467 | --------
468 | >>> import bibmanager.ads_manager as am
469 | >>> key = 'BeaulieuEtal2010arxivGJ436b'
470 | >>> bibcode = '2011ApJ...731...16B'
471 | >>> alternate_bibcode = '2010arXiv1007.0324B'
472 | >>> new_key = am.key_update(key, bibcode, alternate_bibcode)
473 | >>> print(f'{key}\n{new_key}')
474 | BeaulieuEtal2010arxivGJ436b
475 | BeaulieuEtal2011apjGJ436b
476 |
477 | >>> key = 'CubillosEtal2018arXivRetrievals'
478 | >>> bibcode = '2019A&A...550A.100B'
479 | >>> alternate_bibcode = '2018arXiv123401234B'
480 | >>> new_key = am.key_update(key, bibcode, alternate_bibcode)
481 | >>> print(f'{key}\n{new_key}')
482 | CubillosEtal2018arXivRetrievals
483 | CubillosEtal2019aaRetrievals
484 | """
485 | old_year = alternate_bibcode[0:4]
486 | year = bibcode[0:4]
487 | # Update year:
488 | if old_year != year and old_year in key:
489 | key = key.replace(old_year, year, 1)
490 |
491 | # Update journal:
492 | journal = bibcode[4:9].replace('.','').replace('&','').lower()
493 | # Search for the word 'arxiv' in key:
494 | ijournal = key.lower().find('arxiv')
495 | if ijournal >= 0:
496 | key = "".join([key[:ijournal], journal, key[ijournal+5:]])
497 |
498 | return key
499 |
--------------------------------------------------------------------------------
/bibmanager/latex_manager/latex_manager.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018-2024 Patricio Cubillos.
2 | # bibmanager is open-source software under the MIT license (see LICENSE).
3 |
4 | __all__ = [
5 | 'get_bibfile',
6 | 'no_comments',
7 | 'citations',
8 | 'parse_subtex_files',
9 | 'build_bib',
10 | 'update_keys',
11 | 'clear_latex',
12 | 'compile_latex',
13 | 'compile_pdflatex',
14 | ]
15 |
16 | import datetime
17 | import os
18 | import re
19 | import shutil
20 | import subprocess
21 | import numpy as np
22 |
23 | from .. import bib_manager as bm
24 | from .. import config_manager as cm
25 | from .. import utils as u
26 |
27 |
28 | class Replacer():
29 | """
30 | Object to keep track of comments and key changes in a .tex file
31 | Used in update_keys() function.
32 | """
33 | def __init__(self, reps):
34 | self.index = 0
35 | self.reps = reps
36 | self.comments = {}
37 |
38 | def mask_comments(self, text):
39 | self.index += 1
40 | mask = f'BIBM_COMMENT_{self.index:06d}_'
41 | self.comments[mask] = text.group()
42 | return mask
43 |
44 | def recover_comments(self, text):
45 | for key, comment in self.comments.items():
46 | text = text.replace(key, comment, 1)
47 | return text
48 |
49 | def replace(self, text):
50 | refs = text.split(',')
51 | citations = []
52 | for ref in refs:
53 | old_ref = ref.strip()
54 | if old_ref in self.reps.keys():
55 | new_ref = self.reps[old_ref]
56 | ref = ref.replace(ref.strip(), new_ref)
57 | citations.append(ref)
58 | return ','.join(citations)
59 |
60 |
61 | def get_bibfile(texfile):
62 | r"""
63 | Find and extract the bibfile used by a .tex file.
64 | This is done by looking for a '\bibliography{}' call.
65 |
66 | Parameters
67 | ----------
68 | texfile: String
69 | Name of an input tex file.
70 |
71 | Returns
72 | -------
73 | bibfile: String
74 | bib file referenced in texfile.
75 | """
76 | with open(texfile, 'r', encoding='utf-8') as f:
77 | text = f.read()
78 |
79 | # Start at the beginning:
80 | beginning = text.find(r'\begin{document}')
81 | if beginning > 0:
82 | text = text[beginning:]
83 |
84 | # Remove comments, go through recursive .tex files referenced in text:
85 | text = parse_subtex_files(text)
86 |
87 | # Extract bibfile name from texfile:
88 | biblio = re.findall(r"\\bibliography{([^}]+)", text)
89 | if len(biblio) == 0:
90 | raise ValueError("No 'bibiliography' call found in tex file")
91 | # Ensure explicit file extension in bibfile:
92 | bibfile, extension = os.path.splitext(biblio[0].strip())
93 | bibfile += '.bib'
94 | return bibfile
95 |
96 |
97 | def no_comments(text):
98 | r"""
99 | Remove comments from tex file, partially inspired by this:
100 | https://stackoverflow.com/questions/2319019
101 |
102 | Parameters
103 | ----------
104 | text: String
105 | Content from a latex file.
106 |
107 | Returns
108 | -------
109 | no_comments_text: String
110 | Input text with removed comments (as defined by latex format).
111 |
112 | Examples
113 | --------
114 | >>> import bibmanager.latex_manager as lm
115 | >>> text = r'''
116 | Hello, this is dog.
117 | % This is a comment line.
118 | This line ends with a comment. % A comment
119 | However, this is a percentage \%, not a comment.
120 | OK, bye.'''
121 | >>> print(lm.no_comments(text))
122 | Hello, this is dog.
123 | This line ends with a comment.
124 | However, this is a percentage \%, not a comment.
125 | OK, bye.
126 | """
127 | return re.sub(r"\A%.*|[^\\]%.*", "", text)
128 |
129 |
130 | def citations(text):
131 | r"""
132 | Generator to find citations in a tex text. Partially inspired
133 | by this: https://stackoverflow.com/questions/29976397
134 |
135 | Notes
136 | -----
137 | Act recursively in case there are references inside the square
138 | brackets of the cite call. Only failing case I can think so far
139 | is if there are nested square brackets.
140 |
141 | Parameters
142 | ----------
143 | text: String
144 | String where to search for the latex citations.
145 |
146 | Yields
147 | ------
148 | citation: String
149 | The citation key.
150 |
151 | Examples
152 | --------
153 | >>> import bibmanager.latex_manager as lm
154 | >>> import os
155 | >>> # Syntax matches any of these calls:
156 | >>> tex = r'''
157 | \citep{AuthorA}.
158 | \citep[pre]{AuthorB}.
159 | \citep[pre][post]{AuthorC}.
160 | \citep [pre] [post] {AuthorD}.
161 | \citep[{\pre},][post]{AuthorE, AuthorF}.
162 | \citep[pre][post]{AuthorG} and \citep[pre][post]{AuthorH}.
163 | \citep{
164 | AuthorI}.
165 | \citep
166 | [][]{AuthorJ}.
167 | \citep[pre
168 | ][post] {AuthorK, AuthorL}
169 | \citep[see also \citealp{AuthorM}][]{AuthorN}'''
170 | >>> for citation in lm.citations(tex):
171 | >>> print(citation, end=" ")
172 | AuthorA AuthorB AuthorC AuthorD AuthorE AuthorF AuthorG AuthorH AuthorI AuthorJ AuthorK AuthorL AuthorM AuthorN
173 |
174 | >>> # Match all of these cite calls:
175 | >>> tex = r'''
176 | \cite{AuthorA}, \nocite{AuthorB}, \defcitealias{AuthorC}.
177 | \citet{AuthorD}, \citet*{AuthorE}, \Citet{AuthorF}, \Citet*{AuthorG}.
178 | \citep{AuthorH}, \citep*{AuthorI}, \Citep{AuthorJ}, \Citep*{AuthorK}.
179 | \citealt{AuthorL}, \citealt*{AuthorM},
180 | \Citealt{AuthorN}, \Citealt*{AuthorO}.
181 | \citealp{AuthorP}, \citealp*{AuthorQ},
182 | \Citealp{AuthorR}, \Citealp*{AuthorS}.
183 | \citeauthor{AuthorT}, \citeauthor*{AuthorU}.
184 | \Citeauthor{AuthorV}, \Citeauthor*{AuthorW}.
185 | \citeyear{AuthorX}, \citeyear*{AuthorY}.
186 | \citeyearpar{AuthorZ}, \citeyearpar*{AuthorAA}.'''
187 | >>> for citation in lm.citations(tex):
188 | >>> print(citation, end=" ")
189 | AuthorA AuthorB AuthorC AuthorD AuthorE AuthorF AuthorG AuthorH AuthorI AuthorJ AuthorK AuthorL AuthorM AuthorN AuthorO AuthorP AuthorQ AuthorR AuthorS AuthorT AuthorU AuthorV AuthorW AuthorX AuthorY AuthorZ AuthorAA
190 |
191 | >>> texfile = os.path.expanduser('~')+"/.bibmanager/examples/sample.tex"
192 | >>> with open(texfile, encoding='utf-8') as f:
193 | >>> tex = f.read()
194 | >>> tex = lm.no_comments(tex)
195 | >>> cites = [citation for citation in lm.citations(tex)]
196 | >>> for key in np.unique(cites):
197 | >>> print(key)
198 | AASteamHendrickson2018aastex62
199 | Astropycollab2013aaAstropy
200 | Hunter2007ieeeMatplotlib
201 | JonesEtal2001scipy
202 | MeurerEtal2017pjcsSYMPY
203 | PerezGranger2007cseIPython
204 | vanderWaltEtal2011numpy
205 | """
206 | # This RE pattern matches:
207 | # - Latex commands: \defcitealias, \nocite, \cite
208 | # - Natbib commands: \cite + p, t, alp, alt, author, year, yearpar
209 | # (as well as their capitalized and starred versions).
210 | # - Zero or one square brackets (with everything in between).
211 | # - Zero or one square brackets (with everything in between).
212 | # - The content of the curly braces.
213 | # With zero or more blanks in between each expression.
214 | p = re.compile(
215 | r"\\(?:defcitealias|nocite|cite|"
216 | r"(?:[Cc]ite(?:p|alp|t|alt|author|year|yearpar)\*?))"
217 | r"[\s]*(\[[^\]]*\])?"
218 | r"[\s]*(\[[^\]]*\])?"
219 | r"[\s]*{([^}]+)"
220 | )
221 | # Parse matches, do recursive call on the brakets content, yield keys:
222 | for left, right, cites in p.findall(text):
223 | # Remove blanks, strip outer commas:
224 | cites = "".join(cites.split()).strip(",")
225 |
226 | for cite in citations(left):
227 | yield cite
228 | for cite in cites.split(","):
229 | yield cite
230 | for cite in citations(right):
231 | yield cite
232 |
233 |
234 | def parse_subtex_files(tex):
235 | """
236 | Recursively search for subfiles included in tex. Append their
237 | content at the end of tex and return.
238 |
239 | Parameters
240 | ----------
241 | tex: String
242 | String to parse.
243 |
244 | Returns
245 | -------
246 | tex: String
247 | String with appended content from any subfile.
248 | """
249 | # Remove blanks, strip outer commas:
250 | tex = no_comments(tex)
251 | # This RE pattern matches:
252 | # - The command: \input or \include or \subfile
253 | # - The content of the curly braces.
254 | # With zero or more blanks in between each expression.
255 | p = re.compile(r"\\(?:input|include|subfile)[\s]*{([^}]+)")
256 | # Parse matches, do recursive call on the brackets content, yield keys:
257 | for input_file in p.findall(tex):
258 | path, input_file = os.path.split(os.path.realpath(input_file))
259 | input_file, extension = os.path.splitext(input_file.strip())
260 | with open(f"{path}/{input_file}.tex", "r", encoding='utf-8') as f:
261 | input_tex = parse_subtex_files(f.read())
262 | tex += input_tex
263 | return tex
264 |
265 |
266 | def build_bib(texfile, bibfile=None):
267 | """
268 | Generate a .bib file from a given tex file.
269 |
270 | Parameters
271 | ----------
272 | texfile: String
273 | Name of an input tex file.
274 | bibfile: String
275 | Name of an output bib file. If None, get bibfile name from
276 | bibliography call inside the tex file.
277 |
278 | Returns
279 | -------
280 | missing: List of strings
281 | List of the bibkeys not found in the bibmanager database.
282 | """
283 | # Extract path:
284 | path, texfile = os.path.split(os.path.realpath(texfile))
285 | # Remove extension:
286 | texfile, extension = os.path.splitext(texfile)
287 |
288 | if extension != ".tex":
289 | raise ValueError("Input file does not have a .tex extension.")
290 |
291 | with open(f'{path}/{texfile}.tex', "r", encoding='utf-8') as f:
292 | tex = f.read()
293 |
294 | # Start at the beginning:
295 | beginning = tex.find(r'\begin{document}')
296 | if beginning > 0:
297 | tex = tex[beginning:]
298 |
299 | # Implemented into a separate function to get recursion rolling:
300 | tex = parse_subtex_files(tex)
301 |
302 | # Extract bibfile name from texfile:
303 | if bibfile is None:
304 | bibfile = get_bibfile(f'{path}/{texfile}.tex')
305 | bibfile = f'{path}/{bibfile}'
306 |
307 | # Extract citation keys from texfile:
308 | cites = [citation for citation in citations(tex)]
309 | tex_keys = np.unique(cites)
310 |
311 | # Collect BibTeX references from keys in database:
312 | bibs = bm.load()
313 | db_keys = [bib.key for bib in bibs]
314 |
315 | found = np.in1d(tex_keys, db_keys, assume_unique=True)
316 | missing = tex_keys[np.where(np.invert(found))]
317 | if not np.all(found):
318 | print("References not found:\n{:s}".format('\n'.join(missing)))
319 |
320 | bibs = [bibs[db_keys.index(key)] for key in tex_keys[found]]
321 | bm.export(bibs, bibfile=bibfile)
322 |
323 | return missing
324 |
325 |
326 | def update_keys(texfile, key_replacements, is_main):
327 | r"""
328 | Update citation keys in a tex file according to the replace_dict.
329 | Work out way recursively into sub-files.
330 |
331 | Parameters
332 | ----------
333 | textfile: String
334 | Path to an existing .tex file.
335 | is_main: Bool
336 | If True, ignore everything up to '\beging{document}' call.
337 | """
338 | with open(texfile, 'r', encoding='utf-8') as f:
339 | tex = f.read()
340 | if is_main:
341 | beginning = tex.find(r'\begin{document}')
342 | else:
343 | beginning = 0
344 | # Temporarily replace comments, keep a recod of them:
345 | replacer = Replacer(key_replacements)
346 | text = re.sub(r"\A%.*|[^\\]%.*", replacer.mask_comments, tex[beginning:])
347 |
348 | # See citations() for an explanation of this pattern:
349 | p = re.compile(
350 | r"\\(?:defcitealias|nocite|cite|"
351 | r"(?:[Cc]ite(?:p|alp|t|alt|author|year|yearpar)\*?))"
352 | r"[\s]*(\[[^\]]*\])?"
353 | r"[\s]*(\[[^\]]*\])?"
354 | r"[\s]*{([^}]+)"
355 | )
356 | # Reconstruct text, replacing citations as needed:
357 | new_text = tex[0:beginning]
358 | start = 0
359 | while True:
360 | match = p.search(text, start)
361 | if match is None:
362 | new_text += text[start:]
363 | break
364 | # Text up to citations:
365 | pos, _ = match.span(3)
366 | new_text += text[start:pos]
367 | new_text += replacer.replace(match.groups()[2])
368 | start = match.end()
369 |
370 | # Put comments back in:
371 | new_text = replacer.recover_comments(new_text)
372 |
373 | path, tfile = os.path.split(os.path.realpath(texfile))
374 | today = str(datetime.date.today())
375 | shutil.copy(
376 | texfile,
377 | f"{path}/orig_{today}_{tfile}",
378 | )
379 | with open(texfile, 'w', encoding='utf-8') as f:
380 | f.write(new_text)
381 |
382 | # Recursive calls into referenced .tex files:
383 | p = re.compile(r"\\(?:input|include|subfile)[\s]*{([^}]+)")
384 | for input_file in p.findall(tex):
385 | input_file = os.path.realpath(input_file)
386 | input_file, extension = os.path.splitext(input_file.strip())
387 | update_keys(f'{input_file}.tex', key_replacements, is_main=False)
388 |
389 |
390 | def clear_latex(texfile):
391 | """
392 | Remove by-products of previous latex compilations.
393 |
394 | Parameters
395 | ----------
396 | texfile: String
397 | Path to an existing .tex file.
398 |
399 | Notes
400 | -----
401 | For an input argument texfile='filename.tex', this function deletes
402 | the files that begin with 'filename' followed by:
403 | .bbl, .blg, .out, .dvi,
404 | .log, .aux, .lof, .lot,
405 | .toc, .ps, .pdf, Notes.bib
406 | """
407 | clears = [
408 | '.bbl', '.blg', '.out', '.dvi',
409 | '.log', '.aux', '.lof', '.lot',
410 | '.toc', '.ps', '.pdf', 'Notes.bib']
411 |
412 | # Remove extension:
413 | texfile = os.path.splitext(texfile)[0]
414 |
415 | # Delete without complaining:
416 | for clear in clears:
417 | with u.ignored(OSError):
418 | os.remove(f'{texfile}{clear}')
419 |
420 |
421 | def compile_latex(texfile, paper=None):
422 | """
423 | Compile a .tex file into a .pdf file using latex calls.
424 |
425 | Parameters
426 | ----------
427 | texfile: String
428 | Path to an existing .tex file.
429 | paper: String
430 | Paper size for output. For example, ApJ articles use letter
431 | format, whereas A&A articles use A4 format.
432 |
433 | Notes
434 | -----
435 | This function executes the following calls:
436 | - compute a bibfile out of the citation calls in the .tex file.
437 | - removes all outputs from previous compilations (see clear_latex())
438 | - calls latex, bibtex, latex, latex to produce a .dvi file
439 | - calls dvips to produce a .ps file, redirecting the output to
440 | ps2pdf to produce the final .pdf file.
441 | """
442 | # Extract path:
443 | path, texfile = os.path.split(os.path.realpath(texfile))
444 | # Remove extension:
445 | texfile, extension = os.path.splitext(texfile)
446 |
447 | if extension not in [".tex", ""]:
448 | raise ValueError("Input file does not have a .tex extension")
449 |
450 | if extension == "" and os.path.isfile(f"{path}/{texfile}.tex"):
451 | extension = ".tex"
452 |
453 | if not os.path.isfile(f"{path}/{texfile}.tex"):
454 | raise ValueError("Input .tex file does not exist")
455 |
456 | # Default paper format:
457 | if paper is None:
458 | paper = cm.get('paper')
459 |
460 | # Proceed in place:
461 | with u.cd(path):
462 | # Re-generate bib file if necessary.
463 | missing = build_bib(f'{texfile}.tex')
464 |
465 | # Clean up:
466 | clear_latex(texfile)
467 |
468 | # Compile into dvi:
469 | subprocess.call(['latex', texfile], shell=False)
470 | subprocess.call(['bibtex', texfile], shell=False)
471 | subprocess.call(['latex', texfile], shell=False)
472 | subprocess.call(['latex', texfile], shell=False)
473 |
474 | # dvi to pdf:
475 | # I could actually split the dvips and ps2pdf calls to make the code
476 | # easier to follow, but piping the outputs actually make it faster:
477 | subprocess.call(
478 | f'dvips -R0 -P pdf -t {paper} -f {texfile} | '
479 | 'ps2pdf -dCompatibilityLevel=1.3 -dEmbedAllFonts=true '
480 | f'-dMaxSubsetPct=100 -dSubsetFonts=true - - > {texfile}.pdf',
481 | shell=True)
482 | # Some notes:
483 | # (1) '-P pdf' makes the file to look good on screen, says STScI:
484 | # http://www.stsci.edu/hst/proposing/info/how-to-make-pdf
485 | # (2) See 'man ps2pdf' to understand the dashes.
486 | # (3) See https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDFCreationSettings_v9.pdf for ps2pdf options.
487 |
488 | if len(missing) > 0:
489 | print(f"\n{texfile}.tex has some references not found:")
490 | for key in missing:
491 | print("- " + key)
492 |
493 |
494 | def compile_pdflatex(texfile):
495 | """
496 | Compile a .tex file into a .pdf file using pdflatex calls.
497 |
498 | Parameters
499 | ----------
500 | texfile: String
501 | Path to an existing .tex file.
502 |
503 | Notes
504 | -----
505 | This function executes the following calls:
506 | - compute a bibfile out of the citation calls in the .tex file.
507 | - removes all outputs from previous compilations (see clear_latex())
508 | - calls pdflatex, bibtex, pdflatex, pdflatex to produce a .pdf file
509 | """
510 | # Extract path:
511 | path, texfile = os.path.split(os.path.realpath(texfile))
512 | # Remove extension:
513 | texfile, extension = os.path.splitext(texfile)
514 |
515 | if extension not in [".tex", ""]:
516 | raise ValueError("Input file does not have a .tex extension")
517 |
518 | if extension == "" and os.path.isfile(f"{path}/{texfile}.tex"):
519 | extension = ".tex"
520 |
521 | if not os.path.isfile(f"{path}/{texfile}.tex"):
522 | raise ValueError("Input .tex file does not exist")
523 |
524 | # Proceed in place:
525 | with u.cd(path):
526 | # Re-generate bib file if necessary.
527 | missing = build_bib(f'{texfile}.tex')
528 |
529 | # Clean up:
530 | clear_latex(texfile)
531 |
532 | # Compile into pdf:
533 | subprocess.call(['pdflatex', texfile], shell=False)
534 | subprocess.call(['bibtex', texfile], shell=False)
535 | subprocess.call(['pdflatex', texfile], shell=False)
536 | subprocess.call(['pdflatex', texfile], shell=False)
537 |
538 | if len(missing) > 0:
539 | print(f"\n{texfile}.tex has some references not found:")
540 | for key in missing:
541 | print("- " + key)
542 |
--------------------------------------------------------------------------------