├── 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 | [![Tests](https://github.com/pcubillos/bibmanager/actions/workflows/python-package.yml/badge.svg?branch=master)](https://github.com/pcubillos/bibmanager/actions/workflows/python-package.yml) 5 | [![Documentation Status](https://readthedocs.org/projects/bibmanager/badge/?version=latest)](https://bibmanager.readthedocs.io/en/latest/?badge=latest) 6 | [![PyPI](https://img.shields.io/pypi/v/bibmanager.svg)](https://pypi.org/project/bibmanager) 7 | [![Conda Version](https://img.shields.io/conda/vn/conda-forge/bibmanager.svg)](https://anaconda.org/conda-forge/bibmanager) 8 | [![GitHub](https://img.shields.io/github/license/pcubillos/bibmanager.svg?color=blue)](https://bibmanager.readthedocs.io/en/latest/license.html) 9 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2547042.svg)](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 | # bibmanager 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 | --------------------------------------------------------------------------------