diff options
Diffstat (limited to 'biblio/bibtex')
-rw-r--r-- | biblio/bibtex/utils/bibcop/README.md | 73 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.1 | 2 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.dtx | 2 | ||||
-rw-r--r-- | biblio/bibtex/utils/bibcop/bibcop.pdf | bin | 347801 -> 347804 bytes | |||
-rwxr-xr-x | biblio/bibtex/utils/bibcop/bibcop.pl | 190 |
5 files changed, 208 insertions, 59 deletions
diff --git a/biblio/bibtex/utils/bibcop/README.md b/biblio/bibtex/utils/bibcop/README.md index e1263b09f3..04e5f0810f 100644 --- a/biblio/bibtex/utils/bibcop/README.md +++ b/biblio/bibtex/utils/bibcop/README.md @@ -1,4 +1,6 @@ -<img src="https://raw.githubusercontent.com/yegor256/bibcop/master/bibcop-logo.svg" height="92px"/> +# Bibcop: Style Checker of BibTeX .bib files + +![bibcop logo](bibcop-logo.png) [![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) @@ -10,12 +12,12 @@ emits warning message if any issues are found. You may also like [biblatex-check](https://github.com/pezmc/biblatex-check) tools — they do _almost_ the same but from the command line. -Read [this blog post](https://www.yegor256.com/2023/09/05/style-checker-for-bibtex-files.html), -in order to understand the motivation behind this package. +Read [this blog post][BLOG],in order to understand +the motivation behind this package. -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](http://www.bibtex.org/), for example): +First, [install it][INSTALL] from [CTAN](https://ctan.org/pkg/bibcop) +and then use in the preamble +(if you use [BibTeX](http://www.bibtex.org/), for example): ```tex \documentclass{article} @@ -26,45 +28,66 @@ and then use in the preamble (if you use [BibTeX](http://www.bibtex.org/), for e \end{document} ``` -Otherwise, you can download [`bibcop.sty`](https://raw.githubusercontent.com/yegor256/bibcop/gh-pages/bibcop.sty) and add to your project (together with [`bibcop.pl`](https://raw.githubusercontent.com/yegor256/bibcop/gh-pages/bibcop.pl)!). +You can also add it as a GitHub Action to your +GitHub repository, with the help of +[bibcop-action](https://github.com/yegor256/bibcop-action). + +Otherwise, you can download +[`bibcop.sty`](https://yegor256.github.io/bibcop/bibcop.sty) +and add to your project (together with +[`bibcop.pl`](https://yegor256.github.io/bibcop/bibcop.pl)!). -You can also download [`bibcop.pl`](https://raw.githubusercontent.com/yegor256/bibcop/gh-pages/bibcop.pl) +You can also download +[`bibcop.pl`](https://yegor256.github.io/bibcop/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 +```bash +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). +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). Be careful, all comments will be removed. -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: +You can also make changes inline, not creating a new file: +```bash +perl bibcop.pl --fix --in-place main.bib ``` -$ tlgmr install bibcop -$ bibcop --help + +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: + +```bash +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 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. +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 +perl tests.pl ``` You should see the `GREAT!` message. -Copyright (c) 2022-2024 Yegor Bugayenko, MIT License
\ No newline at end of file +Copyright (c) 2022-2024 Yegor Bugayenko, MIT License + +[BLOG]: https://www.yegor256.com/2023/09/05/style-checker-for-bibtex-files.html +[INSTALL]: https://en.wikibooks.org/wiki/LaTeX/Installing_Extra_Packages diff --git a/biblio/bibtex/utils/bibcop/bibcop.1 b/biblio/bibtex/utils/bibcop/bibcop.1 index 8e8c5cfc0d..05d608d815 100644 --- a/biblio/bibtex/utils/bibcop/bibcop.1 +++ b/biblio/bibtex/utils/bibcop/bibcop.1 @@ -1,4 +1,4 @@ -.TH bibcop 1 "2024-03-15" +.TH bibcop 1 "2024-05-03" .SH NAME bibcop \- Style Checker and Fixer of BibTeX Files (.bib) .SH SYNOPSIS diff --git a/biblio/bibtex/utils/bibcop/bibcop.dtx b/biblio/bibtex/utils/bibcop/bibcop.dtx index e1ab46aa8e..5fe60e62c5 100644 --- a/biblio/bibtex/utils/bibcop/bibcop.dtx +++ b/biblio/bibtex/utils/bibcop/bibcop.dtx @@ -50,7 +50,7 @@ %<package>\NeedsTeXFormat{LaTeX2e} %<package>\ProvidesPackage{bibcop} %<*package> -[2024-03-15 0.0.20 Style Checker of Bibliography Files] +[2024-05-03 0.0.21 Style Checker of Bibliography Files] %</package> %<*driver> \documentclass{ltxdoc} diff --git a/biblio/bibtex/utils/bibcop/bibcop.pdf b/biblio/bibtex/utils/bibcop/bibcop.pdf Binary files differindex 57e80dcc82..74b981beb0 100644 --- a/biblio/bibtex/utils/bibcop/bibcop.pdf +++ b/biblio/bibtex/utils/bibcop/bibcop.pdf diff --git a/biblio/bibtex/utils/bibcop/bibcop.pl b/biblio/bibtex/utils/bibcop/bibcop.pl index e35e984962..f33f85be2f 100755 --- a/biblio/bibtex/utils/bibcop/bibcop.pl +++ b/biblio/bibtex/utils/bibcop/bibcop.pl @@ -21,13 +21,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# 2024-03-15 0.0.20 +# 2024-05-03 0.0.21 package bibcop; use warnings; use strict; +use POSIX; use File::Basename; -use Time::Piece; # Hash of incoming command line arguments. my %args = map { $_ => 1 } @ARGV; @@ -40,11 +40,12 @@ my %blessed = ( 'article' => ['doi', 'year', 'title', 'author', 'journal', 'volume', 'number', 'month?', 'publisher?', 'pages?'], 'inproceedings' => ['doi', 'booktitle', 'title', 'author', 'year', 'pages?', 'month?', 'organization?', 'volume?'], 'book' => ['title', 'author', 'year', 'publisher', 'doi?'], - 'misc' => ['title', 'author', 'year', 'eprint?', 'archiveprefix?', 'primaryclass?', 'month?', 'publisher?', 'organization?', 'doi?', 'howpublished?', 'note?'], + 'phdthesis' => ['title', 'author', 'year', 'school', 'doi?'], + 'misc' => ['title', 'author', 'year', 'eprint?', 'archiveprefix?', 'primaryclass?', 'month?', 'publisher?', 'organization?', 'doi?', 'howpublished?', 'note?', 'pages?', 'number?', 'volume?'], ); # See https://research.arizona.edu/faq/what-do-you-mean-when-you-say-use-title-case-proposalproject-titles -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/; +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 vs/; # Check the presence of mandatory tags. sub check_mandatory_tags { @@ -53,6 +54,9 @@ sub check_mandatory_tags { } my (%entry) = @_; my $type = $entry{':type'}; + if (not exists $blessed{$type}) { + return "The type of entry is not allowed: '$type'" + } my $mandatory = $blessed{$type}; foreach my $tag (@$mandatory) { if ($tag =~ /^.*\?$/) { @@ -90,29 +94,35 @@ sub check_capitalization { if (not exists $tags{$tag}) { next; } - my $tailed = qr/^.+(:|\?)$/; + my @ends = qw/ ; ? . --- : ! /; my $value = $entry{$tag}; my @words = only_words($value); my $pos = 0; foreach my $word (@words) { + $word =~ s/\.$//g; $pos = $pos + 1; if (not $word =~ /^[A-Za-z]/) { next; } + if ($word =~ /^\{.*|.*\}$/) { + next; + } if (exists $minors{$word}) { if ($pos eq 1) { - return "The minor word in the '$tag' must be upper-cased since it is the first one" + return "The minor word '$word' in the '$tag' must be upper-cased since it is the first one" } - if (not $words[$pos - 2] =~ $tailed) { - next; + my $before = $words[$pos - 2]; + if (grep(/^$before$/, @ends)) { + return "The minor word '$word' in the '$tag' must be upper-cased, because it follows the '$before'" } - return "The minor word in the '$tag' must be upper-cased, because it follows the colon" + next; } if (exists $minors{lc($word)}) { if ($pos eq 1) { next; } - if ($words[$pos - 2] =~ $tailed) { + my $before = $words[$pos - 2]; + if (grep(/^$before$/, @ends)) { next; } return "All minor words in the '$tag' must be lower-cased, while @{[as_position($pos)]} word '$word' is not" @@ -156,6 +166,9 @@ sub check_author { if ($name =~ /^[A-Z][^.]+$/) { next } + if ($name =~ /^(van|de|der|dos)$/) { + next + } if ($name =~ /^[A-Z]$/) { return "A shortened name must have a tailing dot in @{[as_position($pos)]} 'author', as in 'Knuth, Donald E.'"; } @@ -173,11 +186,17 @@ sub check_shortenings { next; } my $value = $entry{$tag}; - my @words = only_words($value); + my @words = split(/ /, clean_tex($value)); foreach my $word (@words) { if (not $word =~ /^[A-Za-z]/) { next; } + if ($word eq 'vs.') { + next; + } + if ($word =~ /\.\.\.$/) { + next; + } if ($word =~ /^.*\.$/) { return "Do not shorten the words in the '$tag', such as '$word'" } @@ -275,6 +294,7 @@ sub check_typography { my @no_space_after = ( '(', '[' ); my @space_before = ( '(', '[' ); my @space_after = ( ')', ']' ); + my @good_tails = ( 'Inc.', 'Ltd.' ); my @bad_tails = ( '.', ',', ';', ':', '-' ); foreach my $tag (keys %entry) { if ($tag =~ /^:.*/) { @@ -288,8 +308,16 @@ sub check_typography { if ($s eq '.' and $tag eq 'author') { next; } - if ($value =~ /^.*\Q$s\E$/) { - return "The '$tag' must not end with a $symbols{$s}" + my $good = 0; + foreach my $s (@good_tails) { + if ($value =~ /^.*\Q$s\E$/) { + $good = 1; + } + } + if (not $good) { + if ($value =~ /^.*\Q$s\E$/) { + return "The '$tag' must not end with a $symbols{$s}" + } } } foreach my $s (@no_space_before) { @@ -309,7 +337,7 @@ sub check_typography { } foreach my $s (@space_after) { my $p = join('', @no_space_before); - if ($value =~ /^.*\Q$s\E[^\}\s\Q$p\E].*$/) { + if ($value =~ /^.*[^\\]\Q$s\E[^\}\s\Q$p\E].*$/) { return "In the '$tag', put a space after the $symbols{$s}" } } @@ -401,7 +429,7 @@ sub check_doi { my (%entry) = @_; if (exists $entry{'doi'}) { my $doi = $entry{'doi'}; - if (not $doi =~ /^[0-9a-zA-Z.]+\/[0-9a-zA-Z._\-)(><:;]+$/) { + if (not $doi =~ /^[0-9a-zA-Z.]+\/[0-9a-zA-Z._\-)(><:;\/]+$/) { return "The format of the 'doi' is wrong" } } @@ -543,7 +571,7 @@ sub entry_fix { } my $value = clean_tex($entry{$tag}); if ($tag eq 'url') { - my $today = localtime->strftime('%d-%m-%Y'); + my $today = strftime('%d-%m-%Y', localtime(time)); push(@lines, " howpublished = {\\url{$value}},"); push(@lines, " note = {[Online; accessed $today]},"); next; @@ -557,6 +585,7 @@ sub entry_fix { no strict 'refs'; $value = $fixer->($value); } + $value = fix_unicode($value); if ($tag =~ /title|booktitle|journal/) { $value = '{' . $value . '}'; } @@ -579,17 +608,18 @@ sub fix_author { my @authors = split(/\s+and\s+/, $value); foreach my $author (@authors) { $author =~ s/^\s+|\s+$//g; - if (index($author, '{') != -1 or index($author, '}') != -1) { + if (index($author, ' {') != -1 or index($author, '} ') != -1) { next; } $author =~ s/ ([A-Z])($| )/ $1.$2/g; - if (index($author, ',') eq -1) { + if (index($author, ',') == -1) { my @words = split(/\s+/, $author); my $total = @words+0; - if ($total gt 1) { + if ($total > 1) { $author = $words[$total - 1] . ', ' . join(' ', @words[0 .. $total - 2]); } } + $author =~ s/([A-Z])\.(?![ ,])/$1. /g; $author =~ s/^\s+|\s+$//g; } return join(' and ', @authors); @@ -601,6 +631,35 @@ sub fix_number { return $value; } +sub fix_month { + my ($value) = @_; + my %months = ( + '1' => 'jan', + '2' => 'feb', + '3' => 'mar', + '4' => 'apr', + '5' => 'may', + '6' => 'jun', + '7' => 'jul', + '8' => 'aug', + '9' => 'sep', + '10' => 'oct', + '11' => 'nov', + '12' => 'dec', + ); + $value =~ s/^0+//g; + if ($value =~ /^11|12|[0-9]$/) { + $value = $months{$value}; + } else { + my %rev = reverse %months; + my $lc = substr(lc($value), 0, 3); + if (exists $rev{$lc}) { + $value = $lc; + } + } + return $value; +} + sub fix_capitalization { my ($value) = @_; my @words = split(/\s+/, $value); @@ -610,17 +669,37 @@ sub fix_capitalization { if (not $word =~ /^[A-Za-z]/) { next; } - my $lc = lc($word); - if (exists $minors{$lc} and $pos gt 1 and not $words[$pos - 2] =~ /:$/) { - $word = $lc; - next; - } - if ($word =~ /^[a-z].*/) { - $word =~ s/^([a-z])/\U$1/g; + my $start = 1; + if ($pos > 1) { + my $before = $words[$pos - 2]; + if (not $before =~ /(:|\?|!|;|-)$/) { + $start = 0; + } } - if (index($word, '-') != -1) { - $word =~ s/-([a-z])/-\U$1/g; + my @parts = split(/-/, $word, -1); + my $p = 0; + foreach my $part (@parts) { + $p += 1; + my $lcp = lc($part); + my $head = $lcp; + $head =~ s/[,\.!\?;:]$//g; + if (exists $minors{$head}) { + if ($p > 1) { + my $pre = $parts[$p - 2]; + if (not $pre eq '') { + $part = $lcp; + next; + } + } elsif (@parts+0 == 1) { + if (not $start) { + $part = $lcp; + next; + } + } + } + $part =~ s/^([a-z])/\U$1/g; } + $word = join('-', @parts); } return join(' ', @words); } @@ -628,6 +707,8 @@ sub fix_capitalization { sub fix_title { my ($value) = @_; $value = fix_capitalization($value); + $value =~ s/([^ ])---/$1 ---/g; + $value =~ s/---([^ ])/--- $1/g; return $value; } @@ -653,7 +734,9 @@ sub fix_pages { if ($left !~ /^[0-9]*$/ or $right !~ /^[0-9]*$/) { return $value; } - if ($left + 0 gt $right + 0) { + $left = $left + 0; + $right = $right + 0; + if ($left > $right) { my $tmp = $left; $left = $right; $right = $tmp; @@ -675,6 +758,22 @@ sub fix_booktitle { foreach my $org (@orgs) { $value =~ s/ \Q$org\E / /g; } + my %numbers = ( + 'First' => '1st', + 'Second' => '2nd', + 'Third' => '3rd', + 'Fourth' => '4th', + 'Fifth' => '5th', + 'Sixth' => '6th', + 'Seventh' => '7th', + 'Eighth' => '8th', + 'Nineth' => '9th', + 'Tenth' => '10th' + ); + keys %numbers; + while(my($left, $right) = each %numbers) { + $value =~ s/^Proceedings of the \Q$left\E /Proceedings of the $right /g; + } return $value; } @@ -707,6 +806,28 @@ sub fix_organization { return $value; } +sub fix_unicode { + my ($value) = @_; + my %literals = ( + 'ò' => '\`{o}', 'ó' => '\\\'{o}', 'ô' => '\^{o}', 'ö' => '\"{o}', 'ő' => '\H{o}', 'ǒ' => '\v{o}', 'õ' => '\~{o}', + 'à' => '\`{a}', 'á' => '\\\'{a}', 'â' => '\^{a}', 'ä' => '\"{a}', 'å' => '\r{a}', 'ą' => '\k{a}', 'ǎ' => '\v{a}', 'ã' => '\~{a}', + 'ù' => '\`{u}', 'ú' => '\\\'{u}', 'û' => '\^{u}', 'ü' => '\"{u}', 'ů' => '\r{u}', 'ǔ' => '\v{u}', 'ũ' => '\~{u}', + 'ì' => '\`{i}', 'í' => '\\\'{i}', 'î' => '\^{i}', 'ï' => '\"{i}', 'ǐ' => '\v{i}', 'ĩ' => '\~{i}', + 'ń' => '\\\'{n}', 'ň' => '\v{n}', 'ñ' => '\~{n}', + 'ç' => '\c{c}', + 'ł' => '\l{}', + 'ı' => '{\i}', + 'ø' => '\o{}', + '–' => '--', '—' => '---', + '’' => '\'' + ); + keys %literals; + while(my($k, $v) = each %literals) { + $value =~ s/\Q$k\E/$v/g; + } + return $value; +} + # Parse the incoming .bib file and return an array # of hash-maps, where each one is a bibentry. sub entries { @@ -832,7 +953,12 @@ sub entries { # Takes the text and returns only list of words seen there. sub only_words { my ($tex) = @_; - return split(/[ \-]/, clean_tex($tex)); + my $t = clean_tex($tex); + $t =~ s/([^a-zA-Z0-9\\'])/ $1 /g; + $t =~ s/- +- +-/---/g; + $t =~ s/{ /{/g; + $t =~ s/ }/}/g; + return split(/ +/, $t); } # Take a TeX string and return a cleaner one, without redundant spaces, brackets, etc. @@ -958,7 +1084,7 @@ if (@ARGV+0 eq 0 or exists $args{'--help'} or exists $args{'-?'}) { " --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.20 2024-03-15'); + info('0.0.21 2024-05-03'); } else { my ($file) = grep { not($_ =~ /^-.*$/) } @ARGV; if (not $file) { @@ -997,7 +1123,7 @@ if (@ARGV+0 eq 0 or exists $args{'--help'} or exists $args{'-?'}) { $found += 1; } } - if ($found gt 0) { + if ($found > 0) { debug("$found problem(s) found"); fail(); } |