diff options
author | Norbert Preining <norbert@preining.info> | 2023-01-10 03:01:21 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2023-01-10 03:01:21 +0000 |
commit | decba0e4470fd7bfe288f98ee709553fc13014b8 (patch) | |
tree | 781f266fc727864e1c9bb62f07c1069dc926fbec /biblio/bibtex | |
parent | 61f8490e549cb7af263b9d903bc20d61ea37a2d7 (diff) |
CTAN sync 202301100301
Diffstat (limited to 'biblio/bibtex')
-rw-r--r-- | biblio/bibtex/utils/bibcop/DEPENDS.txt | 2 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/LICENSE.txt | 21 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/README.md | 64 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop-logo.pdf | bin | 0 -> 5100 bytes | |||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.1 | 30 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.dtx | 471 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.ins | 55 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.pdf | bin | 0 -> 342958 bytes | |||
-rwxr-xr-x | biblio/bibtex/utils/bibcop/bibcop.pl | 635 |
9 files changed, 1278 insertions, 0 deletions
diff --git a/biblio/bibtex/utils/bibcop/DEPENDS.txt b/biblio/bibtex/utils/bibcop/DEPENDS.txt new file mode 100644 index 0000000000..32acfffc04 --- /dev/null +++ b/biblio/bibtex/utils/bibcop/DEPENDS.txt @@ -0,0 +1,2 @@ +hard iexec +hard pgfopts diff --git a/biblio/bibtex/utils/bibcop/LICENSE.txt b/biblio/bibtex/utils/bibcop/LICENSE.txt new file mode 100644 index 0000000000..4351be233c --- /dev/null +++ b/biblio/bibtex/utils/bibcop/LICENSE.txt @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (c) 2022-2023 Yegor Bugayenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/biblio/bibtex/utils/bibcop/README.md b/biblio/bibtex/utils/bibcop/README.md new file mode 100644 index 0000000000..471c4dcdcb --- /dev/null +++ b/biblio/bibtex/utils/bibcop/README.md @@ -0,0 +1,64 @@ +<img src="https://raw.githubusercontent.com/yegor256/bibcop/master/bibcop-logo.svg" height="92px"/> + +[![l3build](https://github.com/yegor256/bibcop/actions/workflows/l3build.yml/badge.svg)](https://github.com/yegor256/bibcop/actions/workflows/l3build.yml) +[![CTAN](https://img.shields.io/ctan/v/bibcop)](https://ctan.org/pkg/bibcop) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/bibcop/blob/master/LICENSE.txt) + +This LaTeX package checks the quality of your `.bib` file and +emits warning message if any issues are found. You may also like +[biblint](https://github.com/Kingsford-Group/biblint) and +[biblatex-check](https://github.com/pezmc/biblatex-check) tools — +they do _almost_ the same but from the command line. + +First, [install it](https://en.wikibooks.org/wiki/LaTeX/Installing_Extra_Packages) +from [CTAN](https://ctan.org/pkg/bibcop) +and then use in the preamble (if you use BibTeX, for example): + +```tex +\documentclass{article} +\usepackage{bibcop} +\begin{document} +\bibliographystyle{plain} +\bibliography{main} +\end{document} +``` + +Otherwise, you can download [`bibcop.sty`](https://raw.githubusercontent.com/yegor256/bibcop/gh-pages/bibcop/bibcop.sty) and add to your project (together with [`bibcop.pl`](https://raw.githubusercontent.com/yegor256/bibcop/master/bibcop.pl)!). + +You can also download [`bibcop.pl`](https://raw.githubusercontent.com/yegor256/bibcop/master/bibcop.pl) +and use it as a command line tool +to check your `.bib` files and to auto-fix them +(you should have [Perl](https://www.perl.org) installed): + +``` +$ perl bibcop.pl --fix main.bib > fixed.bib +``` + +This command will read the `main.bib` file and create `fixed.bib`, which +will have the fixed and properly formatted content (well, to some extent). + +If you install the package using [`tlmgr`](https://www.tug.org/texlive/tlmgr.html), +you should be able to use `bibcop` directly, without the necessity to mention Perl: + +``` +$ tlgmr install bibcop +$ bibcop --help +``` + +## How to Contribute + +If you want to contribute yourself, make a fork, then create a branch, +then run `l3build ctan` in the root directory. +It should compile everything without errors. If not, submit an issue and wait. +Otherwise, make your changes and then run `l3build ctan` again. If the build is +still clean, submit a pull request. + +If you want to add a new check, add it as a Perl subroutine to the `bibcop.pl` file. +Don't forget to add a test to one of the test files that stay in the `perl-tests/` directory. +When ready, run this, in order to check that all tests pass: + +```bash +$ perl tests.pl +``` + +You should see the `GREAT!` message.
\ No newline at end of file diff --git a/biblio/bibtex/utils/bibcop/bibcop-logo.pdf b/biblio/bibtex/utils/bibcop/bibcop-logo.pdf Binary files differnew file mode 100644 index 0000000000..497d52dc0b --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop-logo.pdf diff --git a/biblio/bibtex/utils/bibcop/bibcop.1 b/biblio/bibtex/utils/bibcop/bibcop.1 new file mode 100644 index 0000000000..4e45ba6c02 --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop.1 @@ -0,0 +1,30 @@ +.TH bibcop 1 "2023-01-08" +.SH NAME +bibcop \- Style Checker and Fixer of BibTeX Files (.bib) +.SH SYNOPSIS +bibcop [<options>] <.bib file path> +.SH DESCRIPTION +The bibcop tool is a Perl script for checking the quality of BibTeX bibliograph +files (.bib) and automatically fixing found inconsistencies. It is not only +a command line tool, but a LaTeX package, which can check the quality +of a .bib file during the rendering of a LaTeX document. All found +issues are reported as LaTeX warnings. +.SH OPTIONS +Various options apply: +.IP --version +Print the version of the tool +.IP --help +Print the introduction screen +.IP --fix +Autho-fix all issues and print a new content to the console +.IP --latex +Print everything in LaTeX format +.IP --verbose +Print debugging information too +.SH AUTHOR +Yegor Bugayenko (yegor256@gmail.com) +.SH BUGS +Please log issues on the GitHub homepage: +https://github.com/yegor256/bibcop/issues. +.SH SEE ALSO +See bibcop.pdf for more details. diff --git a/biblio/bibtex/utils/bibcop/bibcop.dtx b/biblio/bibtex/utils/bibcop/bibcop.dtx new file mode 100644 index 0000000000..d084f00961 --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop.dtx @@ -0,0 +1,471 @@ +% \iffalse meta-comment +% (The MIT License) +% +% Copyright (c) 2022-2023 Yegor Bugayenko +% +% Permission is hereby granted, free of charge, to any person obtaining a copy +% of this software and associated documentation files (the 'Software'), to deal +% in the Software without restriction, including without limitation the rights +% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +% copies of the Software, and to permit persons to whom the Software is +% furnished to do so, subject to the following conditions: +% +% The above copyright notice and this permission notice shall be included in all +% copies or substantial portions of the Software. +% +% THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +% SOFTWARE. +% \fi + +% \CheckSum{0} +% +% \CharacterTable +% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +% Digits \0\1\2\3\4\5\6\7\8\9 +% Exclamation \! Double quote \" Hash (number) \# +% Dollar \$ Percent \% Ampersand \& +% Acute accent \' Left paren \( Right paren \) +% Asterisk \* Plus \+ Comma \, +% Minus \- Point \. Solidus \/ +% Colon \: Semicolon \; Less than \< +% Equals \= Greater than \> Question mark \? +% Commercial at \@ Left bracket \[ Backslash \\ +% Right bracket \] Circumflex \^ Underscore \_ +% Grave accent \` Left brace \{ Vertical bar \| +% Right brace \} Tilde \~} + +% \GetFileInfo{bibcop.dtx} +% \DoNotIndex{\endgroup,\begingroup,\let,\else,\s,\n,\r,\\,\1,\fi} + +% \iffalse +%<*driver> +\ProvidesFile{bibcop.dtx} +%</driver> +%<package>\NeedsTeXFormat{LaTeX2e} +%<package>\ProvidesPackage{bibcop} +%<*package> +[2023-01-08 0.0.6 Style Checker of Bibliography Files] +%</package> +%<*driver> +\documentclass{ltxdoc} +\usepackage[T1]{fontenc} +\usepackage[maxnames=1,minnames=1,maxbibnames=1,natbib=true,citestyle=authoryear,bibstyle=authoryear,doi=false,url=false,isbn=false,isbn=false]{biblatex} +\addbibresource{bibcop.bib} +\usepackage[tt=false, type1=true]{libertine} +\usepackage{microtype} +\AddToHook{env/verbatim/begin}{\microtypesetup{protrusion=false}} +\usepackage{href-ul} +\usepackage{xcolor} +\usepackage{graphicx} +\PageIndex +\EnableCrossrefs +\CodelineIndex +\RecordChanges +\begin{document} + \DocInput{bibcop.dtx} + \PrintChanges + \PrintIndex +\end{document} +%</driver> +% \fi + +% \title{\includegraphics[height=1in]{bibcop-logo.pdf} \\ |bibcop|: \LaTeX{} Package \\ for Style Checking of |.bib| Files\thanks{The sources are in GitHub at \href{https://github.com/yegor256/bibcop}{yegor256/bibcop}}} +% \author{Yegor Bugayenko \\ \texttt{yegor256@gmail.com}} +% \date{\filedate, \fileversion} +% +% \maketitle +% +% \textbf{\color{red}NB!} +% You must run \TeX{} processor with |--shell-escape| option +% and you must have \href{https://www.perl.org}{Perl} installed. +% This package doesn't work on Windows. + +% \section{Introduction} +% +% This package scans a |.bib| file for style errors and emits +% warning messages if any issues are found (the package must be included before +% all other bibliography related packages): +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +\documentclass{article} +\usepackage{bibcop} +\begin{document} +\bibliographystyle{plain} +\bibliography{main} +\end{document} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% Some warnings may be printed in the \TeX{} log. +% Once the issues in the |main.bib| file are fixed, the warnings disappear. + +% Bibcop doesn't pay much attention the formatting of the |.bib| file. It doesn't emit +% warnings if a comma is missed after the last tag in a \BibTeX{} entry or the tags +% and their equation symbols are not aligned. Instead, Bibcop is focused on the content +% of the tags and their possible inconsistencies. + +% If the |.sty| file is used directly (without installing it into the \TeX{} tree), +% the |bibcop.pl| file must also be placed next to it --- it is the Perl script that does all the work +% of checking your |.bib| files. The |.sty| is just a simple wrapper around it. + +% The |\usepackage{bibcop}| must stay right after |\usepackage{biblatex}| +% (if Bib\LaTeX{} is used), otherwise +% there won't be any warnings from |bibcop|. + +% \section{Package Options} + +% It's possible to configure the behavior of the package with the help of a few package options: + +% \DescribeMacro{verbose} +% The |verbose| package option prints all debugging messages to the \TeX{} log: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +\usepackage[verbose]{bibcop} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{script} +% The package depends on the |bibcop.pl| file, which is a Perl script that does all the work. This file is supposed to be located either in the current directory or in the |texmf-dist/scripts/bibcop/| directory. Using the |script| option the location of the script may be explicitly defined (it is not recommended to use this option unless there is a special demand for it): +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +\usepackage[script=my-perl-script.pl]{bibcop} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \section{The Rules} + +% This is a more or less complete list of rules enforced on a |.bib| file: + +% \DescribeMacro{types} +% Only |@article|, |@book|, |@inproceedings|, and |@misc| types of \BibTeX{} entries are allowed. +% Everything else, like |@manual|, |@phdthesis|, and many others are simply prohibited. +% The mentioned four should be enough for everything. + +% \DescribeMacro{tags} +% There are pretty limited lists of allowed tags for each type of \BibTeX{} entry. The tags +% that are not in the list are prohibited to use. + +% \DescribeMacro{doi} +% Every \BibTeX{} entry must have the |doi| tag, which is a unique +% \href{https://www.doi.org}{Digital Object Identifier} +% of the material that you reference. It seems to be a good practice, in order to avoid ambiguity, +% to always mention the DOI. +% It also recommended to use the \href{https://ctan.org/pkg/iexec}{doi} package, +% in order to make all ``|doi|'' fields turned into hyper links. + +% \DescribeMacro{caps} +% In the |title|, |booktitle|, and |journal| tags, all major words must be capitalized, +% as it is \href{https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case}{recommended} by APA: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +title = {A Preliminary Architecture for a Basic Data-Flow Processor} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% Here, the leading ``|A|'' is capital because it opens the title. +% The word ``|for|'' and the article ``|a|'' are minor words, that's why they are in lower case. +% Both parts of the composite word ``|Data-Flow|'' are capitalized. +% Sometimes this rule may need to be violated, when there is custom capitalization, as it is done +% by the author of the paper. In order to do this, the words with custom capitalization +% must be wrapped in curled brackets, for example: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +title = {Structured Programming {with} Go {To} Statements} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{author} +% The |author| must contain a list of authors separated by ``|and|''. Each author +% must have two parts separated by a comma. The first part is the last name of the author, +% the second part is a list of their first names separated by a space, for example: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +author = {Knuth, Donald E. and Duane, Bibby} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% When the list of authors is too long, it's possible to say ``|and others|'': +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +author = {Knuth, Donald E. and others} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% When first names are shortened to a single letter, it has to have a tailing dot. +% A specially pronounced author may be wrapped it in curled brackets, to make Bibcop ignore it: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +author = {{Some author} and {I}} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{shorts} +% It is not allowed to shorten any words aside from the |author| tag, for example this is illegal: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +journal = {J. Log. Comput.} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% This must be replaced with the following: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +journal = {Journal of Logic and Computation} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{brackets} +% The |title|, |booktitle|, and |journal| must be wrapped in double brackets, for example: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +title = {{A Survey of Symbolic Execution Techniques}} +\end{verbatim} +%\iffalse +%</verb> +%\fi +% This is necessary in order to prevent down-casing of capitalized words, which is done by some +% bibliography styles. + +% \DescribeMacro{year} +% It is not allowed to mention the year inside the title of a conference or a journal, for example, +% this would be illegal: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +booktitle = {{1994 IEEE International Conference on Computer Languages}}, +\end{verbatim} +%\iffalse +%</verb> +%\fi +% The year should only be mentioned in the |year| tag, nowhere else. +% In the |year| tag only numbers are allowed: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +year = {1994}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{month} +% The |month| may contain only a number: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +month = {12}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{volume} +% The |volume| may contain only a number: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +volume = {32}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{number} +% The |number| may contain only a number: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +number = {132}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{pages} +% The |pages| may contain either a number or two numbers separated by a double dash: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +pages = {145--163}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{proceedings} +% The |booktitle| in the |@inproceedings| entry must always start with ``|Proceedings| |of| |the|'', +% as in this example: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +booktitle = {{Proceedings of the International + Conference on Computer Languages}}, +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{arXiv} +% If the |archivePrefix| is present, the |eprint| and the |primaryClass| must also be present and must adhere to the formatting principles of \href{https://arxiv.org/help/arxiv_identifier}{arXiv identifiers}: +%\iffalse +%<*verb> +%\fi +\begin{verbatim} +@misc{bugayenko2021, + archivePrefix = {arXiv}, + eprint = {2111.13384}, + primaryClass = {cs.PL}, +} +\end{verbatim} +%\iffalse +%</verb> +%\fi + +% \DescribeMacro{typography} +% All tags in each \BibTeX{} entry are checked for obeying the basic typography rules: +% \begin{itemize} +% \item No spaces are allowed in front of a comma, a semi-colon, a colon, a dot, a question mark, and an exclamation mark; +% \item A text may not end with a dot, a comma, a semi-colon, a colon, or a dash; +% \item A triple dash must be surrounded by spaces. +% \end{itemize} + +% \StopEventually{} + +% \section{Implementation} +% \changes{0.0.1}{2022/12/11}{First draft.} +% \changes{0.0.2}{2022/12/12}{Documentation extended, more rules added.} +% \changes{0.0.4}{2022/12/14}{Extra checks for the typography, together with more extensive Perl testing.} +% \changes{0.0.4}{2022/12/14}{The \texttt{--verbose} option introduced, to enable debugging information only on demand.} +% \changes{0.0.4}{2022/12/15}{Package options introduced, the \texttt{verbose} option enables detailed logging inside the \TeX{} log.} +% \changes{0.0.6}{2023/01/08}{The \texttt{script} package option introduced, to enable explicit configuration of the location of the \texttt{bibcop.pl} Perl script.} + +% First, we include a few packages. +% We need \href{https://ctan.org/pkg/iexec}{iexec} for executing Perl scripts: +% \begin{macrocode} +\RequirePackage{iexec} +% \end{macrocode} + +% Then, we process package options: +% \begin{macrocode} +\RequirePackage{pgfopts} +\pgfkeys{ + /bibcop/.cd, + verbose/.store in=\bibcop@verbose, + script/.store in = \bibcop@script, +} +\ProcessPgfPackageOptions{/bibcop} +% \end{macrocode} + +% \begin{macro}{bibcop.pl} +% Then, we find the Perl script: +% \begin{macrocode} +\makeatletter +\ifdefined\bibcop@script\else + \IfFileExists{bibcop.pl} + {\gdef\bibcop@script{perl ./bibcop.pl}} + {\gdef\bibcop@script{bibcop}} +\fi +\message{bibcop: The Perl script is at '\bibcop@script'^^J}% +\makeatother +% \end{macrocode} +% \end{macro} + +% \begin{macro}{\bibliography} +% Then, we re-define the |\bibliography| command: +% \begin{macrocode} +\makeatletter +\ifdefined\bibliography + \let\bibcop@oldbibliography\bibliography + \renewcommand\bibliography[1]{% + \iexec{\bibcop@script\space + \ifdefined\bibcop@verbose--verbose\fi\space + --latex '#1.bib'}% + \message{bibcop: style checking finished^^J}% + \bibcop@oldbibliography{#1}% + } +\fi +\makeatother +% \end{macrocode} +% \end{macro} + +% \begin{macro}{\addbibresource} +% Then, we re-define the |\addbibresource| command: +% \begin{macrocode} +\makeatletter +\ifdefined\addbibresource + \let\bibcop@oldaddbibresource\addbibresource + \renewcommand\addbibresource[1]{% + \iexec{\bibcop@script\space + \ifdefined\bibcop@verbose--verbose\fi\space + --latex '#1'}% + \message{bibcop: style checking finished^^J}% + \bibcop@oldaddbibresource{#1}% + } +\fi +\makeatother +% \end{macrocode} +% \end{macro} + +% \begin{macrocode} +\endinput +% \end{macrocode} + +% \Finale + +% \clearpage +% \printbibliography +% \clearpage + +% \PrintChanges +% \clearpage +% \PrintIndex diff --git a/biblio/bibtex/utils/bibcop/bibcop.ins b/biblio/bibtex/utils/bibcop/bibcop.ins new file mode 100644 index 0000000000..d0ea50f43c --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop.ins @@ -0,0 +1,55 @@ +%% (The MIT License) +%% +%% Copyright (c) 2022-2023 Yegor Bugayenko +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the 'Software'), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. + +\input docstrip.tex +\keepsilent +\usedir{macros/latex/contrib/bibcop} +\preamble +(The MIT License) + +Copyright (c) 2022-2023 Yegor Bugayenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +\endpreamble +\generate{\file{bibcop.sty}{\from{bibcop.dtx}{package}}} +\obeyspaces +\Msg{To finish the installation you have to move the ".sty"} +\Msg{file into a directory searched by TeX. To produce the} +\Msg{documentation run the file ".dtx" through LaTeX. If any} +\Msg{questions, submit a new GitHub issue.} +\endbatchfile diff --git a/biblio/bibtex/utils/bibcop/bibcop.pdf b/biblio/bibtex/utils/bibcop/bibcop.pdf Binary files differnew file mode 100644 index 0000000000..1971661357 --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop.pdf diff --git a/biblio/bibtex/utils/bibcop/bibcop.pl b/biblio/bibtex/utils/bibcop/bibcop.pl new file mode 100755 index 0000000000..3deda06e45 --- /dev/null +++ b/biblio/bibtex/utils/bibcop/bibcop.pl @@ -0,0 +1,635 @@ +#!/usr/bin/perl +# (The MIT License) +# +# Copyright (c) 2022-2023 Yegor Bugayenko +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +package bibcop; + +use warnings; +use strict; + +# Hash of incoming command line arguments. +my %args = map { $_ => 1 } @ARGV; + +# If you want to add an extra check, just create a new procedure +# named as "check_*". + +# Only these tags are allowed and only these types of entries. +my %blessed = ( + 'article' => ['doi', 'year', 'title', 'author', 'journal', 'volume', 'number', 'publisher?', 'pages?'], + 'inproceedings' => ['doi', 'booktitle', 'title', 'author', 'year', 'pages?', 'organization?', 'volume?'], + 'book' => ['doi', 'title', 'author', 'year', 'publisher'], + 'misc' => ['title', 'author', 'year', 'eprint?', 'archiveprefix?', 'primaryclass?', 'publisher?', 'organization?', 'doi?', 'url?'], +); + +# Check the presence of mandatory tags. +sub check_mandatory_tags { + my (%entry) = @_; + my $type = $entry{':type'}; + my $mandatory = $blessed{$type}; + foreach my $tag (@$mandatory) { + if ($tag =~ /^.*\?$/) { + next; + } + if (not(exists $entry{$tag})) { + my $listed = listed_tags(%entry); + return "A mandatory '$tag' tag for '\@$type' is missing among $listed" + } + } + if (exists $blessed{$type}) { + my %required = map { $_ => 1 } @$mandatory; + foreach my $tag (keys %entry) { + if ($tag =~ /^:/) { + next; + } + if (not(exists $required{$tag}) && not(exists $required{$tag . '?'})) { + return "The '$tag' tag is not suitable for '$type', use only these: (@$mandatory)" + } + } + } +} + +# Check that all major words are capitalized. +sub check_capitalization { + my (%entry) = @_; + my %tags = map { $_ => 1 } qw/title booktitle journal publisher organization/; + my %minors = map { $_ => 1 } qw/in of at to by the a an and or as if up via yet nor but off on for into/; + foreach my $tag (keys %entry) { + if (not exists $tags{$tag}) { + next; + } + my $value = $entry{$tag}; + my @words = only_words($value); + my $pos = 0; + foreach my $word (@words) { + if (not $word =~ /^[A-Za-z]/) { + next; + } + $pos = $pos + 1; + if (exists $minors{$word}) { + next; + } + if (exists $minors{lc($word)} and $pos gt 1) { + return "All minor words in the '$tag' must be lower-cased, while '$word' (no.$pos) is not" + } + if ($word =~ /^[a-z].*/) { + return "All major words in the '$tag' must be capitalized, while '$word' (no.$pos) is not" + } + } + } +} + +# Check that the 'author' is formatted correctly. +sub check_author { + my (%entry) = @_; + if (exists $entry{'author'} and not $entry{'author'} =~ /^\{.+\}$/) { + my $author = clean_tex($entry{'author'}); + if (not $author =~ /^[A-Z][^ ]+(,( [A-Z][^ ]+)+)?( and [A-Z][^ ]+(,( [A-Z][^ ]+)+)?)*( and others)?$/) { + return "The format of the 'author' is wrong, use something like 'Knuth, Donald E. and Duane, Bibby'" + } + if ($author =~ /.*[A-Z]([ ,]|$).*/) { + return "A shortened name must have a tailing dot, as in 'Knuth, Donald E.'" + } + } +} + +# Check that titles don't have shortened words with a tailing dot. +sub check_shortenings { + my (%entry) = @_; + my %tags = map { $_ => 1 } qw/title booktitle journal/; + foreach my $tag (keys %entry) { + if (not exists $tags{$tag}) { + next; + } + my $value = $entry{$tag}; + my @words = only_words($value); + foreach my $word (@words) { + if (not $word =~ /^[A-Za-z]/) { + next; + } + if ($word =~ /^.*\.$/) { + return "Do not shorten the words in the '$tag', such as '$word'" + } + } + } +} + +# Check the right format of the 'title' and 'booktitle.' +sub check_titles { + my (%entry) = @_; + my @tags = qw/title booktitle/; + foreach my $tag (@tags) { + if (not exists($entry{$tag})) { + next; + } + my $title = $entry{$tag}; + if (not $title =~ /^\{.+\}$/) { + return "The '$tag' must be wrapped in double curled brackets" + } + } +} + +# Check the right format of the tags for arXiv. +# See https://arxiv.org/help/arxiv_identifier +sub check_arXiv { + my (%entry) = @_; + if (exists($entry{'archiveprefix'})) { + if (not exists $entry{'eprint'}) { + return "The 'eprint' is mandatory when 'archiveprefix' is there" + } + if (not $entry{'eprint'} =~ /^[0-9]{4}\.[0-9]{4,5}(v[0-9]+)?$/) { + return "The 'eprint' must have two integers separated by a dot" + } + my $eprint = $entry{'eprint'}; + my ($head, $tail) = split(/\./, $eprint); + my $year = substr($head, 0, 2); + my $month = substr($head, 2); + if ($month > 12) { + return "The month '$month' of the 'eprint' is wrong, it can't be bigger than 12" + } + if (not exists $entry{'primaryclass'}) { + return "The 'primaryclass' is mandatory when 'archiveprefix' is there" + } + if (not $entry{'primaryclass'} =~ /^[a-z]{2,}\.[A-Z]{2}$/) { + return "The 'primaryclass' must have two parts, like 'cs.PL'" + } + } +} + +# Check that organization is not mentioned in the booktitle. +sub check_org_in_booktitle { + my (%entry) = @_; + my @orgs = ( 'ACM', 'IEEE' ); + if (exists($entry{'booktitle'})) { + my $title = $entry{'booktitle'}; + foreach my $o (@orgs) { + if ($title =~ /^.*\Q$o\E.*$/) { + return "The '$o' organization must not be mentioned in the booktitle, use 'publisher' tag instead" + } + } + if ($title =~ /^.*(ACM|IEEE).*$/) { + return "Don't mention the" + } + } +} + +# Check that no values have tailing dots. +# Check that there are no spaces before commans. +sub check_typography { + my (%entry) = @_; + my %symbols = ( + '.' => 'dot', + ',' => 'comma', + ';' => 'semi-colon', + ':' => 'colon', + '!' => 'exclamation mark', + '?' => 'question mark', + '-' => 'dash', + '--' => 'double dash', + '---' => 'triple dash', + ); + my @spaces_around = ( '---' ); + my @no_spaces_around = ( '--', '-' ); + my @no_space_before = ( '.', ',', ';', ':', '?', '!' ); + my @bad_tails = ( '.', ',', ';', ':', '-' ); + foreach my $tag (keys %entry) { + if ($tag =~ /^:.*/) { + next; + } + my $value = $entry{$tag}; + foreach my $s (@bad_tails) { + if ($s eq '.' and $tag eq 'author') { + next; + } + if ($value =~ /^.*\Q$s\E$/) { + return "The '$tag' must not end with a $symbols{$s}" + } + } + foreach my $s (@no_space_before) { + if ($value =~ /^.*\s\Q$s\E.*$/) { + return "In the '$tag', do not put a space before a $symbols{$s}" + } + } + foreach my $s (@spaces_around) { + if ($value =~ /^.*[^\s]\Q$s\E.*$/ or $value =~ /^.*\Q$s\E[^\s].*$/) { + return "In the '$tag', put spaces around a $symbols{$s}" + } + } + foreach my $s (@no_spaces_around) { + if ($value =~ /^.*\s\Q$s\E\s.*$/) { + return "In the '$tag', don't put spaces around a $symbols{$s}" + } + } + } +} + +# Check the year is not mentioned in titles. +sub check_year_in_titles { + my (%entry) = @_; + my @tags = qw/title booktitle journal/; + foreach my $tag (@tags) { + if (not exists($entry{$tag})) { + next; + } + my @words = only_words($entry{$tag}); + foreach my $word (@words) { + if ($word =~ /^[1-9][0-9]{3}$/) { + return "The '$tag' must not contain the year $word, it is enough to have the 'year' tag" + } + } + } +} + +# Check the right format of the 'booktitle' in the 'inproceedings' entry. +sub check_booktile_of_inproceedings { + my (%entry) = @_; + my $tag = 'inproceedings'; + if ($entry{':type'} eq $tag) { + if (exists $entry{'booktitle'}) { + my @words = only_words($entry{'booktitle'}); + if (lc($words[0]) ne 'proceedings' or lc($words[1]) ne 'of' or lc($words[2]) ne 'the') { + return "The '$tag' must start with 'Proceedings of the ...'" + } + } + } +} + +# Check the right format of the 'doi.' +sub check_doi { + my (%entry) = @_; + if (exists $entry{'doi'}) { + my $doi = $entry{'doi'}; + if (not $doi =~ /^[0-9a-zA-Z.]+\/[0-9a-zA-Z._\-)(]+$/) { + return "The format of the 'doi' is wrong" + } + } +} + +# Check the right format of the 'year.' +sub check_year { + my (%entry) = @_; + if (exists $entry{'year'}) { + my $year = $entry{'year'}; + if (not $year =~ /^[0-9]{3,4}$/) { + return "The format of the 'year' is wrong" + } + } +} + +# Check the right format of the 'month.' +sub check_month { + my (%entry) = @_; + if (exists $entry{'month'}) { + my $month = $entry{'month'}; + if (not $month =~ /^[1-9]|10|11|12$/) { + return "The format of the 'month' is wrong" + } + } +} + +# Check the right format of the 'volume.' +sub check_volume { + my (%entry) = @_; + if (exists $entry{'volume'}) { + my $volume = $entry{'volume'}; + if (not $volume =~ /^[1-9][0-9]*$/) { + return "The format of the 'volume' is wrong" + } + } +} + +# Check the right format of the 'number.' +sub check_number { + my (%entry) = @_; + if (exists $entry{'number'}) { + my $number = $entry{'number'}; + if (not $number =~ /^[1-9][0-9]*$/) { + return "The format of the 'number' is wrong" + } + } +} + +# Check the right format of the 'pages.' +sub check_pages { + my (%entry) = @_; + if (exists $entry{'pages'}) { + my $pages = $entry{'pages'}; + if (not $pages =~ /^([1-9][0-9]*--[1-9][0-9]*|[1-9][0-9]*)$/) { + return "The format of the 'pages' is wrong" + } + my @parts = split(/--/, $pages); + if (@parts+0 eq 2) { + if ($parts[0] eq $parts[1]) { + return "The 'pages' mentions the same page twice, just use it once" + } + if ($parts[0] gt $parts[1]) { + return "The 'pages' are in the wrong order" + } + } + } +} + +# Check unescaped symbols. +sub check_unescaped_symbols { + my (%entry) = @_; + my @symbols = ( '&' ); + foreach my $tag (keys %entry) { + if ($tag =~ /^:.*/) { + next; + } + my $value = $entry{$tag}; + foreach my $s (@symbols) { + if ($value =~ /^(.*[^\\{]|)\Q$s\E.*$/) { + return "The '$tag' contains a dangerous unescaped symbol (wrap it in curled brackets if you really need it)" + } + } + } +} + +# Check one entry. +sub process_entry { + my (%entry) = @_; + my @checks; + foreach my $entry (keys %bibcop::) { + if ($entry =~ /^check_/) { + push(@checks, $entry); + } + } + my @sorted = sort @checks; + my @errors; + foreach my $check (@sorted) { + no strict 'refs'; + my $err = $check->(%entry); + if ($err ne '') { + push(@errors, $err); + } + } + return @errors; +} + +# Parse the incoming .bib file and return an array +# of hash-maps, where each one is a bibentry. +sub entries { + my ($bib) = @_; + my @entries; + my $s = 'top'; + my %entry; + my $acc = ''; + my $tag = ''; + my $lineno = 0; + my $nest = 0; + my $escape = 0; + for my $pos (0..length($bib)-1) { + my $char = substr($bib, $pos, 1); + if ($char eq ' ') { + # ignore the white space + } elsif ($char eq "\n") { + # ignore the EOL + $lineno = $lineno + 1; + } elsif ($s eq 'top') { + if ($char eq '@') { + %entry = (); + $s = 'start'; + $acc = ''; + } else { + warning("Each BibTeX entry must start with '\@', what is '$char'?"); + last; + } + } elsif ($char =~ /[a-z]/ and $s eq 'start') { + # @article + } elsif ($char eq '{' and $s eq 'start') { + $entry{':type'} = substr($acc, 1); + $acc = ''; + $s = 'body'; + } elsif ($char =~ /[a-zA-Z]/ and $s eq 'body') { + $acc = ''; + $s = 'tag'; + } elsif ($char =~ /[a-zA-Z0-9_]/ and $s eq 'tag') { + # reading the tag + } elsif ($char =~ /[a-zA-Z0-9]/ and $s eq 'value') { + # reading the value without quotes or brackets + } elsif ($char eq ',' and $s eq 'tag') { + $entry{':name'} = $acc; + $s = 'body'; + } elsif ($char eq '=' and $s eq 'tag') { + my $t = lc($acc); + if (exists $entry{$t}) { + warning("The tag '$t' is seen more than once"); + } + $tag = $t; + $s = 'value'; + $acc = ''; + } elsif ($char eq ',' and $s eq 'value') { + if (not exists $entry{$tag}) { + my $tex = substr($acc, 1); + $tex =~ s/\s//g; + $entry{$tag} = $tex; + } + $s = 'body'; + } elsif ($char eq '}' and $s eq 'body') { + push(@entries, { %entry }); + $s = 'top'; + } elsif ($char eq '}' and $s eq 'value') { + if (not exists $entry{$tag}) { + my $tex = substr($acc, 1); + $tex =~ s/\s//g; + $entry{$tag} = $tex; + } + push(@entries, { %entry }); + $s = 'top'; + } elsif ($char eq '}' and $s eq 'tag') { + $entry{':name'} = $acc; + push(@entries, { %entry }); + $s = 'top'; + } elsif ($char eq '"' and $s eq 'value') { + $s = 'quote'; + $acc = ''; + } elsif ($char eq '"' and $s eq 'quote') { + $entry{$tag} = substr($acc, 1); + $s = 'value'; + } elsif ($s eq 'quote') { + # nothing + } elsif ($char eq '{' and $s eq 'value') { + $nest = 1; + $s = 'brackets'; + $acc = ''; + } elsif ($s eq 'brackets') { + if ($char eq '\\') { + $escape = 1; + } elsif ($char eq '{' and $escape ne 1) { + $nest = $nest + 1; + } elsif ($char eq '}' and $escape ne 1) { + $nest = $nest - 1; + if ($nest eq 0) { + $entry{$tag} = substr($acc, 1); + $s = 'value'; + } + } + $escape = 0; + } else { + warning("It is impossible to parse the .bib file, because I do not know what to do with '$char' at line #$lineno (s=$s)"); + last; + } + if ($char eq ' ' and not($s =~ /quote|brackets/)) { + next; + } + $acc = $acc . $char; + } + return @entries; +} + +# Takes the text and returns only list of words seen there. +sub only_words { + my ($tex) = @_; + return split(/[ \-]/, clean_tex($tex)); +} + +# Take a TeX string and return a cleaner one, without redundant spaces, brackets, etc. +sub clean_tex { + my ($tex) = @_; + $tex =~ s/\s+/ /g; + $tex =~ s/^\s+//g; + $tex =~ s/\s+$//g; + while ($tex =~ s/^\{(.+)\}$/$1/g) {}; + return $tex; +} + +# Take a bibentry and print all its tags as a comma-separated string. +sub listed_tags { + my (%entry) = @_; + my @list; + foreach my $tag (keys %entry) { + if ($tag =~ /^:.*/) { + next; + } + push(@list, $tag); + } + my @sorted = sort @list; + return '(' . join(', ', @sorted) . ')'; +} + +# Print ERROR message to the console and die. +sub error { + my ($txt) = @_; + if (exists $args{'--latex'}) { + print "\\PackageError{bibcop}{$txt}{}\n"; + exit 0; + } else { + print STDERR $txt . "\n"; + exit 1; + } +} + +# Print DEBUG message to the console. +sub debug { + my ($txt) = @_; + if (exists $args{'--verbose'}) { + if (exists $args{'--latex'}) { + print "\\message{bibcop: $txt^^J}\n"; + } else { + print $txt . "\n"; + } + } +} + +# Print INFO message to the console. +sub info { + my ($txt) = @_; + if (exists $args{'--latex'}) { + print '% '; + } + print $txt . "\n"; +} + +# Print INFO message to the console. +sub warning { + my ($txt) = @_; + if (exists $args{'--latex'}) { + print "\\PackageWarningNoLine{bibcop}{$txt}\n"; + } else { + print $txt . "\n"; + } +} + +if (@ARGV+0 eq 0 or exists $args{'--help'} or exists $args{'-?'}) { + info("Bibcop is a Style Checker of BibTeX Files\n\n" . + "Usage:\n" . + " bibcop [<options>] <.bib file path>\n\n" . + "Options:\n" . + " -v, --version Print the current version of the tool and exit\n" . + " -?, --help Print this help screen\n" . + " --fix Fix the errors and print a new version of the .bib file to the console\n" . + " --verbose Print supplementary debugging information\n" . + " --latex Report errors in LaTeX format using \\PackageWarningNoLine command\n\n" . + "If any issues, report to GitHub: https://github.com/yegor256/bibcop"); +} elsif (exists $args{'--version'} or exists $args{'-v'}) { + info('0.0.6'); +} else { + my ($file) = grep { not($_ =~ /^--.*$/) } @ARGV; + if (not $file) { + error('File name must be specified'); + } + open(my $fh, '<', $file) or error('Cannot open file: ' . $file); + my $bib; { local $/; $bib = <$fh>; } + my @entries = entries($bib); + if (exists $args{'--fix'}) { + for my $i (0..(@entries+0 - 1)) { + my %entry = %{ $entries[$i] }; + my $type = $entry{':type'}; + if (not exists $blessed{$type}) { + error("I don't know what to do with \@$type type of BibTeX entry"); + } + my $tags = $blessed{$entry{':type'}}; + my %allowed = map { $_ => 1 } @$tags; + my @lines; + foreach my $tag (keys %entry) { + if ($tag =~ /^:/) { + next; + } + if (not exists $allowed{$tag} and not exists $allowed{$tag . '?'}) { + next; + } + my $value = clean_tex($entry{$tag}); + if ($tag =~ /title|booktitle|journal/) { + $value = '{' . $value . '}'; + } + push(@lines, " $tag = {$value},"); + } + info("\@$type\{$entry{':name'},"); + my @sorted = sort @lines; + foreach my $line (@sorted) { + info($line); + } + info("}\n"); + } + } else { + debug((@entries+0) . ' entries found in ' . $file); + for my $i (0..(@entries+0 - 1)) { + my %entry = %{ $entries[$i] }; + debug("Checking $entry{':name'} (no.$i)..."); + foreach my $err (process_entry(%entry)) { + warning("$err, in the '$entry{':name'}' entry"); + } + } + } +} + +1; |