quarto-utils 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qtils/__init__.py ADDED
File without changes
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/python3
2
+ import sys
3
+ from qtils.utils import update_references
4
+ import pathlib as pl
5
+
6
+ for x in sys.argv:
7
+ if not isinstance(x, str):
8
+ print("Inputs must be strings")
9
+ sys.exit(0)
10
+
11
+ # choose a paper!
12
+ qmdfile = pl.Path(sys.argv[0])
13
+ bibfile = pl.Path(sys.argv[1])
14
+
15
+ print(qmdfile)
16
+ print(bibfile)
17
+
18
+ update_references(qmdfile,bibfile,inplace=True)
@@ -0,0 +1,7 @@
1
+ from qtils.utils import update_references
2
+ import pathlib as pl
3
+
4
+ # choose a paper!
5
+ qmdfile = pl.Path('test.qmd')
6
+ bibfile = pl.Path('tmp.bib')
7
+ update_references(qmdfile, bibfile)
File without changes
@@ -0,0 +1,5 @@
1
+ from qtils.utils import update_references
2
+ update_references(DATA_DIR / 'example.dois.qmd',
3
+ DATA_DIR / 'references.bib',
4
+ False,
5
+ None)
@@ -0,0 +1,95 @@
1
+ import pytest
2
+ import pathlib as pl
3
+ import urllib.request
4
+ from urllib.error import HTTPError, URLError
5
+ import shutil, os
6
+ from text_unidecode import unidecode
7
+
8
+ DATA_DIR = pl.Path('qtils/tests/data')
9
+
10
+ def test_url():
11
+ doi = 'hTTps://doi.org/10.1038/nclimate2425'
12
+ BASE_URL = 'https://dx.doi.org/'
13
+ url = BASE_URL + doi.lower().replace('https://doi.org','').replace(' ','')
14
+ req = urllib.request.Request(url,
15
+ headers={'Accept': 'text/bibliography; style=bibtex'} )
16
+
17
+ with urllib.request.urlopen(req) as f:
18
+ bibtex = f.read().decode()
19
+
20
+
21
+ def test_getbib():
22
+ from qtils.utils import doi2bib
23
+ bib = '\n\t'.join(doi2bib('hTTps://doi.org/10.1038/nclimate2425'))
24
+ refbib = unidecode(''.join([i for i in open(DATA_DIR / 'one_entry.tst', 'r').readlines()]))
25
+ assert refbib == bib
26
+
27
+ def test_update_bib():
28
+ from qtils.utils import update_bibfile
29
+ # make a copy to test with
30
+ shutil.copy2(DATA_DIR / 'ref.bib_backup',
31
+ DATA_DIR / 'ref.bib')
32
+ # set up dois to update with
33
+ dois = ['junkus fail','10.1111/gwat.12536',
34
+ '10.1111/gwat.13083',
35
+ '10.3133/tm7C9',
36
+ '10.1007/978-3-030-45367-1_2',
37
+ 'https://doi.org/10.1080/02626660009492371']
38
+
39
+ # run the update - one should fail
40
+ update_bibfile(DATA_DIR / 'ref.bib', dois)
41
+
42
+
43
+ def test_doi_parser():
44
+ from qtils.utils import _strip_doi
45
+ qfile = DATA_DIR / 'example.dois.qmd'
46
+ dat = qfile.read_text().split('_doi:')
47
+ dois = [_strip_doi(i) for i in dat[1:]]
48
+ assert dois == ['10.1145/2492517.2500290',
49
+ 'hTTps://doi.org/10.1038/nclimate2425',
50
+ '10.1080/14650040590946584',
51
+ '10.1029/WR020i004p00415',
52
+ 'https://doi.org/10.1577/1548-8659(2001)130<0809:SVIDOL>2.0.CO;2',
53
+ 'https://doi.org/10.1890/1051-0761(2006)016[2035:BIRFUP]2.0.CO;2',
54
+ 'https://doi.org/10.1641/0006-3568(2000)050[0053:EAECON]2.3.CO;2',
55
+ 'https://doi.org/10.2193/0022-541X(2006)70[255:EAAIHE]2.0.CO;2']
56
+
57
+ def test_underscore_warning():
58
+ from qtils.utils import update_references
59
+ if (DATA_DIR / 'underscore.bib').exists():
60
+ os.remove(DATA_DIR / 'underscore.bib')
61
+ update_references(DATA_DIR / 'underscore_test.qmd',
62
+ DATA_DIR / 'underscore.bib',
63
+ False,
64
+ DATA_DIR / 'underscoreout.qmd')
65
+
66
+
67
+ def test_dois_to_bibtex_qmd():
68
+ from qtils.utils import update_references
69
+ # make a copy to test with
70
+ shutil.copy2(DATA_DIR / 'ref.bib_backup',
71
+ DATA_DIR / 'references.bib')
72
+
73
+ # try with automatic update of filename
74
+ update_references(DATA_DIR / 'example.dois.qmd',
75
+ DATA_DIR / 'references.bib',
76
+ False,
77
+ None)
78
+
79
+ # now try inplace test
80
+ shutil.copy2(DATA_DIR / 'ref.bib_backup',
81
+ DATA_DIR / 'references.bib')
82
+ shutil.copy2(DATA_DIR / 'example.dois.qmd',
83
+ DATA_DIR / 'inplace.qmd')
84
+ update_references(DATA_DIR / 'inplace.qmd',
85
+ DATA_DIR / 'references.bib',
86
+ True,
87
+ None)
88
+
89
+ # and, finally try writing to custom filename
90
+ update_references(DATA_DIR / 'inplace.qmd',
91
+ DATA_DIR / 'references.bib',
92
+ False,
93
+ DATA_DIR / 'newfile.qmd')
94
+
95
+
qtils/utils.py ADDED
@@ -0,0 +1,159 @@
1
+ import urllib.request
2
+ from urllib.error import HTTPError, URLError
3
+ import pathlib as pl
4
+ from text_unidecode import unidecode
5
+
6
+ BASE_URL = 'http://dx.doi.org/'
7
+
8
+
9
+ def doi2bib(doi='10.1016/j.jhydrol.2014.04.061', skip_url=True):
10
+ """function to return a bibTex entry for a doi string
11
+
12
+
13
+ Args:
14
+ doi (str, optional): string for doi. Defaults to '10.1016/j.jhydrol.2014.04.061'.
15
+ skip_url (bool, optional): flag for skipping the url entry from bibTex.
16
+ Defaults to True
17
+ Returns:
18
+ list: list of lines for bibTex entry, skipping url if skip_url is True
19
+ """
20
+ url = BASE_URL + doi.lower().replace('https://doi.org','').replace(' ','')
21
+ req = urllib.request.Request(url)
22
+ req.add_header('Accept', 'application/x-bibtex')
23
+ try:
24
+ with urllib.request.urlopen(req) as f:
25
+ bibtex = unidecode(f.read().decode())
26
+ return bibtex.strip().split(None, 1)
27
+ # return [i for i in bibtex.split('\n') if not i.strip().startswith('url')]
28
+ except HTTPError as e:
29
+ print(e)
30
+ if e.code == 404:
31
+ print('DOI not found.')
32
+ else:
33
+ print('Service unavailable.')
34
+ return f'FAIL'
35
+ except URLError as e:
36
+ return e.reason.strerror
37
+
38
+ def update_bibfile(bib_file, dois):
39
+ """updates an bibliography file with a new list of dois. If the bibliography
40
+ file doesn't exist, it is created.
41
+
42
+ Args:
43
+ bib_file (str): Name of bib file to update
44
+ dois (list): List of dois (strings) to add to bib_file
45
+ Returns:
46
+ bib_dict (dict): Dictionary with keys of value dois and values of entry
47
+ records in the resulting bib file
48
+ """
49
+ # read in the existing bib file for updating
50
+ bib_file = pl.Path(bib_file)
51
+ if not bib_file.exists():
52
+ bib_file.touch(exist_ok = False)
53
+ inbib = ' '.join(open(bib_file, 'r', encoding='utf-8').readlines())
54
+
55
+ # get the bibtex entries
56
+ with open(bib_file, 'a') as ofp:
57
+ bibs = [doi2bib(cdoi) for cdoi in dois]
58
+ for cdoi, cbib in zip(dois,bibs):
59
+ if cbib == 'FAIL':
60
+ # doi2bib failed - just warn and move on
61
+ print (f'Could not obtain bibTex entry for doi="{cdoi}"')
62
+ elif 'failed' in cbib:
63
+ print(f'Could not obtain bibTex entry for doi="{cdoi}". Error message from urllib:\n{cbib}')
64
+ elif cbib[0].strip() in inbib:
65
+ # entry already in the bib file
66
+ print(f'doi: "{cdoi}" already in {bib_file}. Skipping')
67
+ else:
68
+ # write the new entry into the bib file
69
+ print(f'Adding doi: "{cdoi}" to bib file')
70
+ ofp.write('\n' + '\n '.join(cbib))
71
+ # make a dictionary with the dois and the key for references
72
+ bib_dict = {}
73
+ for cdoi,i in zip(dois,bibs):
74
+ if i != 'FAIL' and 'failed' not in i:
75
+ bib_dict[cdoi] = i[0].split('{')[1].strip().replace(',','')
76
+ return bib_dict
77
+
78
+ def _strip_doi(doistring):
79
+ """Function to get the doi ID out of a string
80
+
81
+ Args:
82
+ doistring (_type_): string containing a doi
83
+ """
84
+ doistring = doistring.strip().replace("\t",' ').replace("\n",' ')
85
+ locbraks = locbrakp = locsep = locsepend = locspace = 1e6
86
+ locbackslash = loccomma = locbrakpars = locbracketend= 1e6
87
+ if "; " in doistring:
88
+ locsep = doistring.index('; ')
89
+ if doistring.endswith(';'):
90
+ locsepend = doistring.index(';')
91
+ if doistring.endswith(']'):
92
+ locbracketend = doistring.index(']')
93
+ if '] ' in doistring:
94
+ locbraks = doistring.index('] ')
95
+ if '].' in doistring:
96
+ locbrakp = doistring.index('].')
97
+ if '])' in doistring:
98
+ locbrakpars = doistring.index('])')
99
+ if ' ' in doistring:
100
+ locspace = doistring.index(' ')
101
+ if ',' in doistring:
102
+ loccomma = doistring.index(',')
103
+ if "\\" in doistring:
104
+ locbackslash = doistring.index('\\')
105
+
106
+ doilimit = min((locbraks, locbrakp, locsep, locsepend, locbrakpars,
107
+ locspace, locbackslash, loccomma, locbracketend))
108
+ return(doistring[:doilimit])
109
+
110
+ def _write_updated_qmd(qmd_file, qmd_text, inplace=False, new_qmd_file=None):
111
+ if inplace == True:
112
+ with open(qmd_file, 'w') as ofp:
113
+ ofp.write(qmd_text)
114
+ elif new_qmd_file is None:
115
+ with open(pl.Path(f'{qmd_file.parent}/{qmd_file.stem}.updated.qmd'), 'w') as ofp:
116
+ ofp.write(qmd_text)
117
+ else:
118
+ with open(new_qmd_file, 'w') as ofp:
119
+ ofp.write(qmd_text)
120
+
121
+ def update_references(qmd_file, bibfile,
122
+ inplace=False, new_qmd_file=None):
123
+ """Updates an entire qmd file, replacing doi callouts (which must
124
+ be listed as `_doi:<actual_doi_here>` in the text) with references
125
+ to the updated bibliography file.
126
+
127
+ Args:
128
+ qmd_file (str,): input qmd file.
129
+ bibfile (str, optional): original bib file.
130
+ inplace (bool, optional): Option to replace existing file or to save as a copy.
131
+ If False, and if new_qmd_file is provided, the new file is written to new_qmd_file.
132
+ If False and no new_wmd_file value is provided, the new file is written to a file
133
+ that simply updates qmd_file to include "updated" in the filename. Defaults to True.
134
+ new_qmd_file (_type_, optional): New file to which updated qmd file is written.
135
+ Ignored if inplace==True. Defaults to None.
136
+ """
137
+ # make sure the paths are legit pathlib objects
138
+ qmd_file = pl.Path(qmd_file)
139
+ if new_qmd_file is not None:
140
+ new_qmd_file = pl.Path(new_qmd_file)
141
+ # first find the dois
142
+ qmd_text = qmd_file.read_text(encoding='utf-8')
143
+ if '\_doi' in qmd_text:
144
+ doiflag = '\_doi'
145
+ else:
146
+ doiflag = '_doi'
147
+ dois = qmd_text.split('_doi:')
148
+ if len(dois)>1:
149
+ dois = [_strip_doi(i) for i in dois[1:]]
150
+ # next update the references
151
+ bib_dict = update_bibfile(bibfile, dois)
152
+ for ck,cv in bib_dict.items():
153
+ qmd_text = qmd_text.replace(f'{doiflag}:{ck}',f'@{cv}')
154
+ else:
155
+ print('no dois found. No updates to make.')
156
+
157
+ # finally write out the updated file
158
+ _write_updated_qmd(qmd_file, qmd_text,
159
+ inplace=inplace, new_qmd_file=new_qmd_file)
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: quarto-utils
3
+ Version: 1.0.0
4
+ Summary: Utilities to update references in Quarto using DOIs
5
+ Home-page: https://github.com/mnfienen/quarto-utils
6
+ Author: Michael N. Fienen, Richard A. Erickson
7
+ Author-email: Michael Fienen <mnfienen@usgs.gov>, Richard Erickson <rerickson@usgs.gov>
8
+ Maintainer-email: Michael Fienen <mnfienen@usgs.gov>
9
+ Project-URL: documentation, https://github.com/DOI-USGS/quarto-utils
10
+ Project-URL: repository, https://github.com/DOI-USGS/quarto-utils
11
+ Keywords: quarto,references,doi
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.md
18
+ Requires-Dist: requests
19
+ Requires-Dist: jupyter
20
+ Requires-Dist: notebook
21
+ Requires-Dist: ipykernel
22
+ Requires-Dist: text-unidecode
23
+ Requires-Dist: pytest
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest; extra == "test"
26
+ Requires-Dist: pytest-timeout; extra == "test"
27
+ Dynamic: author
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+ Dynamic: requires-python
31
+
32
+ # quarto-utils
33
+
34
+ **Package note:** A static copy of this project exists on code.usgs.gov as the official release.
35
+ The dynamic, living version of this project exists on GitHub at https://github.com/DOI-USGS/quarto-utils.
36
+ The code.usgs.gov page will be updated with major releases with active development occuring on the GitHub repo.
37
+
38
+ #### Authors: Michael N. Fienen, Richard A. Erickson
39
+ #### Point of contact: Michael N. Fienen (mnfienen@usgs.gov)
40
+ #### Repository Type: Formal Python package
41
+ #### Year of Origin: 2024 (original publication)
42
+ #### Year of Version: 2024
43
+ #### Version: 1.0.0
44
+ #### Digital Object Identifier (DOI): https://doi.org/10.5066/P1GZPONT
45
+ #### USGS Information Product Data System (IPDS) no.: IP-158658
46
+
47
+ ***
48
+
49
+ _Suggested Citation:_
50
+
51
+ Fienen MN, Erickson RA. 2024.
52
+ quarto-utils
53
+ U.S. Geological Survey software release. Reston, Va.,
54
+ https://doi.org/10.5066/P1GZPONT.
55
+
56
+ _Authors' [ORCID](https://orcid.org) nos.:_
57
+
58
+ - Michael N. Fienen, [0000-0002-7756-4651](https://orcid.org/0000-0002-7756-4651)
59
+ - Richard A. Erickson, [0000-0003-4649-482X](https://orcid.org/0000-0003-4649-482X)
60
+
61
+
62
+ ***
63
+ ***
64
+
65
+ This python package works with Quarto MarkDown files to allow authors to supply `dois` for references while writing a document. Tools in this utilities package provide the ability to automatically query https://doi.org to return valid `BiBTeX` entries from `dois`.
66
+
67
+ Further utilities allow users to supply a list of `dois`, look up the `BiBTeX` entries, and update them into a new or existing `BiBTeX` file.
68
+
69
+ Finally, this package allows authors to compose Quarto MarkDown documents (`.qmd` files) using tagged `dois` as references. From these, the code replaces such `dois` with valid `BiBTeX` references which are also downloaded into a `.bib` file.
70
+
71
+ The jupyter notebook in `/qtils/examples/examples.ipynb` includes example applications of all the functions in the package.
72
+
73
+ # Getting Started
74
+
75
+ This code will work with python 3.10 and greater with `requests` and `text-unidecode` packages (optional additional packages include `jupyter notebook` to run the notebook example and `pytest` to evaluate the tests).
76
+
77
+ To install, download this repo (such as `git clone ...`) and then run `python setup.py install` (or possibly `python3` depending upon your local path configurations).
78
+
79
+ # SSL certificates
80
+
81
+ Department of Interior users may experience SSL certificate errors due to The site https://code.usgs.gov/usgs/best-practices/-/blob/master/ssl/WorkingWithinSSLIntercept.md?ref_type=heads provides some best practices for working with these security settings.
82
+
83
+ # Repository Files
84
+
85
+ This repository contains a Quarto-based template for the _Journal of Fish and Wildlife Management_.
86
+ To support this, the following files are located here:
87
+
88
+ - `README.md` is this file.
89
+ - `LICENSE.md` is the Official USGS License.
90
+ - `code.json` is the code metadata.
91
+ - `CONTRIBUTING.md` describes how to contribute to this project.
92
+ - `DISCLAIMER.md` is the standard USGS disclaimer.
93
+ - `.gitignore` is a file telling git which files to not track.
94
+ - `ci` a folder for automated testing of this repository and contains a `yaml` file.
95
+ - `quarto-utils` a folder with the Python package an examples. This includes an `examples` folder and `test` folder.
96
+ - `setup.py` is a Python file for the package.
97
+
98
+ # Background knowledge
99
+
100
+ This template assumes the user knows, or at least wants to learn, how to use Markdown-based text programs such as Quarto.
101
+ Knowing basic Markdown commands will help.
102
+ Additionally, knowing LaTeX will assist in helping with advanced formatting.
103
+
104
+ # Explanation of examples and other tips
105
+
106
+ The `test.qmd` and `example.dois.qmd` include references that Fienen created to demonstrate the package.
107
+ The content of the example is not meaningful, but the references are real - just inasmcuh as they can be pulled from the web.
108
+
109
+ `test.qmd` becomes `test.updated.qmd` after processing and the second file should be rendered as a Quarto file.
110
+
111
+ References in a bib files often require manual formatting.
112
+ For example, proper nouns like Mississippi River may need an extra `{}` to be capitalized, for example `title = {{Mississippi River:} my story on {Old Man River}}` to keep "Mississippi River" and "Old Man River capitalized.
113
+ The automatic references also often do not have correct capitalization and these may need to be changed by the user.
114
+
115
+ # Run time
116
+
117
+ This code takes minimal run time (< 1 minute) under most situations.
118
+
119
+ # Known Issues
120
+
121
+ - In some cases, special characters (such as em-dash or en-dash) in BiBTeX will be encoded as short character strings when written to the bib file. These characters will be seen on render or may crash quarto rendering. Users are advised to inspect references after they are downloaded.
122
+ - Some DOIs contain the underscore character (`_`) and this cause this package to not find any new DOIs or otherwise fail. If this occurs, use `\_doi` rather than `_doi` to tag your DOIs in the Quarto file.
123
+
124
+ # Acknowledgments
125
+
126
+ We thank the U.S. Geological Survey Biological Threats and Invasive Species Research Program and U.S. Geological Survey Water Mission Area Integrated Information Dissemination Division for funding.
127
+ Any use of trade, firm, or product names is for descriptive purposes only and does not imply endorsement by the U.S. Government.
128
+ The findings and opinions expressed in this manuscript are those of the authors and do not necessarily represent the views of the US Fish and Wildlife Service.
129
+
130
+ [quarto]: https://quarto.org/
@@ -0,0 +1,12 @@
1
+ qtils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ qtils/utils.py,sha256=C6aoCB9PO1wAIRUOdJ8sETSeNLajjyaahccuwJ5Dn94,6352
3
+ qtils/examples/update_refs.py,sha256=qOzh4TUsw2Tm00w245QVixySOfu6sB1u2fi7_ATdJ8g,373
4
+ qtils/examples/update_refs_example.py,sha256=xjXjwpjSAPXAhc3RfhsdoGRog3-JUp3jextVkdkm5nk,176
5
+ qtils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ qtils/tests/test_all.py,sha256=AiHUc4kL7r0lQOepXfaBNXI2XxuPp2DDvSeZ_F5v3a8,3425
7
+ qtils/tests/data/update_test.py,sha256=G8W0a6OMPQEoqsALb762IijZggiYYoFrVycYG91eiT0,192
8
+ quarto_utils-1.0.0.dist-info/licenses/LICENSE.md,sha256=j3-fCgUr1LmIPipq2I71L94P7PBedauuZ0xMegcuMEM,1650
9
+ quarto_utils-1.0.0.dist-info/METADATA,sha256=pY_WfKKJKSVuTpqs7OHQxqx6CdAUVm_C2K3l1p2bab8,6603
10
+ quarto_utils-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
11
+ quarto_utils-1.0.0.dist-info/top_level.txt,sha256=RFzShkQWZhr-_XWihGGl46u4k4EbmyPh_tWFyxv0VBo,6
12
+ quarto_utils-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,46 @@
1
+ License
2
+ =======
3
+
4
+ Unless otherwise noted, This project is in the public domain in the United
5
+ States because it contains materials that originally came from the United
6
+ States Geological Survey, an agency of the United States Department of
7
+ Interior. For more information, see the official USGS copyright policy at
8
+ https://www.usgs.gov/information-policies-and-instructions/copyrights-and-credits
9
+
10
+ Additionally, we waive copyright and related rights in the work
11
+ worldwide through the CC0 1.0 Universal public domain dedication.
12
+
13
+
14
+ CC0 1.0 Universal Summary
15
+ -------------------------
16
+
17
+ This is a human-readable summary of the
18
+ [Legal Code (read the full text)][1].
19
+
20
+
21
+ ### No Copyright
22
+
23
+ The person who associated a work with this deed has dedicated the work to
24
+ the public domain by waiving all of his or her rights to the work worldwide
25
+ under copyright law, including all related and neighboring rights, to the
26
+ extent allowed by law.
27
+
28
+ You can copy, modify, distribute and perform the work, even for commercial
29
+ purposes, all without asking permission.
30
+
31
+
32
+ ### Other Information
33
+
34
+ In no way are the patent or trademark rights of any person affected by CC0,
35
+ nor are the rights that other persons may have in the work or in how the
36
+ work is used, such as publicity or privacy rights.
37
+
38
+ Unless expressly stated otherwise, the person who associated a work with
39
+ this deed makes no warranties about the work, and disclaims liability for
40
+ all uses of the work, to the fullest extent permitted by applicable law.
41
+ When using or citing the work, you should not imply endorsement by the
42
+ author or the affirmer.
43
+
44
+
45
+
46
+ [1]: https://creativecommons.org/publicdomain/zero/1.0/legalcode
@@ -0,0 +1 @@
1
+ qtils