summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2022-12-12 21:07:17 +0000
committerKarl Berry <karl@freefriends.org>2022-12-12 21:07:17 +0000
commitb1511fb94a322b9edc7c1ca91676cf5275d6bcae (patch)
tree646aaa75ba5e3739e5b3ecbbc42914d5631b0d97
parenta2a6f21e711bb27829653d592df98c8d9060a22a (diff)
bibcop (12dec22)
git-svn-id: svn://tug.org/texlive/trunk@65257 c570f23f-e606-0410-a88d-b1316a301751
-rw-r--r--Build/source/texk/texlive/linked_scripts/Makefile.am1
-rw-r--r--Build/source/texk/texlive/linked_scripts/Makefile.in1
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/bibcop/bibcop.pl492
-rw-r--r--Build/source/texk/texlive/linked_scripts/scripts.lst1
l---------Master/bin/aarch64-linux/bibcop1
l---------Master/bin/amd64-freebsd/bibcop1
l---------Master/bin/amd64-netbsd/bibcop1
l---------Master/bin/armhf-linux/bibcop1
l---------Master/bin/i386-cygwin/bibcop1
l---------Master/bin/i386-freebsd/bibcop1
l---------Master/bin/i386-linux/bibcop1
l---------Master/bin/i386-netbsd/bibcop1
l---------Master/bin/i386-solaris/bibcop1
l---------Master/bin/universal-darwin/bibcop1
-rwxr-xr-xMaster/bin/win32/bibcop.exebin0 -> 1536 bytes
l---------Master/bin/x86_64-cygwin/bibcop1
l---------Master/bin/x86_64-darwinlegacy/bibcop1
l---------Master/bin/x86_64-linux/bibcop1
l---------Master/bin/x86_64-linuxmusl/bibcop1
l---------Master/bin/x86_64-solaris/bibcop1
-rw-r--r--Master/texmf-dist/doc/latex/bibcop/DEPENDS.txt3
-rw-r--r--Master/texmf-dist/doc/latex/bibcop/LICENSE.txt21
-rw-r--r--Master/texmf-dist/doc/latex/bibcop/README.md54
-rw-r--r--Master/texmf-dist/doc/latex/bibcop/bibcop-logo.pdfbin0 -> 5100 bytes
-rw-r--r--Master/texmf-dist/doc/latex/bibcop/bibcop.pdfbin0 -> 330862 bytes
-rwxr-xr-xMaster/texmf-dist/scripts/bibcop/bibcop.pl492
-rw-r--r--Master/texmf-dist/source/latex/bibcop/bibcop.dtx346
-rw-r--r--Master/texmf-dist/source/latex/bibcop/bibcop.ins55
-rw-r--r--Master/texmf-dist/tex/latex/bibcop/bibcop.sty84
-rwxr-xr-xMaster/tlpkg/bin/tlpkg-ctan-check2
-rwxr-xr-xMaster/tlpkg/libexec/ctan2tds1
-rw-r--r--Master/tlpkg/tlpsrc/bibcop.tlpsrc4
-rw-r--r--Master/tlpkg/tlpsrc/collection-bibtexextra.tlpsrc1
33 files changed, 1572 insertions, 1 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.am b/Build/source/texk/texlive/linked_scripts/Makefile.am
index 1c6fdbc095b..cccebf6e249 100644
--- a/Build/source/texk/texlive/linked_scripts/Makefile.am
+++ b/Build/source/texk/texlive/linked_scripts/Makefile.am
@@ -111,6 +111,7 @@ texmf_other_scripts = \
authorindex/authorindex \
bib2gls/bib2gls.sh \
bib2gls/convertgls2bib.sh \
+ bibcop/bibcop.pl \
bundledoc/arlatex \
bundledoc/bundledoc \
cachepic/cachepic.tlu \
diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.in b/Build/source/texk/texlive/linked_scripts/Makefile.in
index 9dda80993c6..534d2a3571b 100644
--- a/Build/source/texk/texlive/linked_scripts/Makefile.in
+++ b/Build/source/texk/texlive/linked_scripts/Makefile.in
@@ -325,6 +325,7 @@ texmf_other_scripts = \
authorindex/authorindex \
bib2gls/bib2gls.sh \
bib2gls/convertgls2bib.sh \
+ bibcop/bibcop.pl \
bundledoc/arlatex \
bundledoc/bundledoc \
cachepic/cachepic.tlu \
diff --git a/Build/source/texk/texlive/linked_scripts/bibcop/bibcop.pl b/Build/source/texk/texlive/linked_scripts/bibcop/bibcop.pl
new file mode 100755
index 00000000000..3fa1444b92c
--- /dev/null
+++ b/Build/source/texk/texlive/linked_scripts/bibcop/bibcop.pl
@@ -0,0 +1,492 @@
+#!/usr/bin/perl
+# (The MIT License)
+#
+# Copyright (c) 2022 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;
+
+# If you want to add an extra check, just create a new procedure
+# named as "check_*".
+
+# Only these keys are allowed and only these types of items.
+my %blessed = (
+ 'article' => ['doi', 'year', 'title', 'author', 'journal', 'volume', 'number', 'publisher?'],
+ '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?'],
+);
+
+# Check the presence of mandatory keys.
+sub check_mandatory_keys {
+ my (%item) = @_;
+ my $type = $item{':type'};
+ my $mandatory = $blessed{$type};
+ foreach my $key (@$mandatory) {
+ if ($key =~ /^.*\?$/) {
+ next;
+ }
+ if (not(exists $item{$key})) {
+ my $listed = listed_keys(%item);
+ return "A mandatory '$key' key for '\@$type' is missing among $listed"
+ }
+ }
+ if (exists $blessed{$type}) {
+ my %required = map { $_ => 1 } @$mandatory;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:/) {
+ next;
+ }
+ if (not(exists $required{$key}) && not(exists $required{$key . '?'})) {
+ return "The '$key' key is not suitable for '$type', use only these: (@$mandatory)"
+ }
+ }
+ }
+}
+
+# Check that all major words are capitalized.
+sub check_capitalization {
+ my (%item) = @_;
+ my %keys = map { $_ => 1 } qw/title booktitle journal/;
+ 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 $key (keys %item) {
+ if (not exists $keys{$key}) {
+ next;
+ }
+ my $value = $item{$key};
+ 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 '$key' must be lower-cased, while '$word' (no.$pos) is not"
+ }
+ if ($word =~ /^[a-z].*/) {
+ return "All major words in the '$key' must be capitalized, while '$word' (no.$pos) is not"
+ }
+ }
+ }
+}
+
+# Check that the 'author' is formatted correctly.
+sub check_author {
+ my (%item) = @_;
+ if (exists $item{'author'} and not $item{'author'} =~ /^\{.+\}$/) {
+ my $author = clean_tex($item{'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 (%item) = @_;
+ my %keys = map { $_ => 1 } qw/title booktitle journal/;
+ foreach my $key (keys %item) {
+ if (not exists $keys{$key}) {
+ next;
+ }
+ my $value = $item{$key};
+ 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 '$key', such as '$word'"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'title' and 'booktitle.'
+sub check_titles {
+ my (%item) = @_;
+ my @keys = qw/title booktitle/;
+ foreach my $key (@keys) {
+ if (not exists($item{$key})) {
+ next;
+ }
+ my $title = $item{$key};
+ if (not $title =~ /^\{.+\}$/) {
+ return "The '$key' must be wrapped in double curled brackets"
+ }
+ }
+}
+
+# Check that no values have tailing dots.
+# Check that there are no spaces before commans.
+sub check_typography {
+ my (%item) = @_;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:.*/) {
+ next;
+ }
+ my $value = $item{$key};
+ if ($value =~ /.*\.$/ and $key ne 'author') {
+ return "The '$key' must not end with a dot"
+ }
+ if ($value =~ /.* ,.*/) {
+ return "In the '$key', do not put a space before the comma"
+ }
+ }
+}
+
+# Check the year is not mentioned in titles.
+sub check_year_in_titles {
+ my (%item) = @_;
+ my @keys = qw/title booktitle journal/;
+ foreach my $key (@keys) {
+ if (not exists($item{$key})) {
+ next;
+ }
+ my @words = only_words($item{$key});
+ foreach my $word (@words) {
+ if ($word =~ /^[1-9][0-9]{3}$/) {
+ return "The '$key' must not contain the year $word, it is enough to have the 'year' key"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'booktitle' in the 'inproceedings' item.
+sub check_booktile_of_inproceedings {
+ my (%item) = @_;
+ my $key = 'inproceedings';
+ if ($item{':type'} eq $key) {
+ if (exists $item{'booktitle'}) {
+ my @words = only_words($item{'booktitle'});
+ if (lc($words[0]) ne 'proceedings' or lc($words[1]) ne 'of' or lc($words[2]) ne 'the') {
+ return "The '$key' must start with 'Proceedings of the ...'"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'doi.'
+sub check_doi {
+ my (%item) = @_;
+ if (exists $item{'doi'}) {
+ my $doi = $item{'doi'};
+ if (not $item{'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 (%item) = @_;
+ if (exists $item{'year'}) {
+ my $year = $item{'year'};
+ if (not $item{'year'} =~ /^[0-9]{3,4}$/) {
+ return "The format of the 'year' is wrong"
+ }
+ }
+}
+
+# Check the right format of the 'month.'
+sub check_month {
+ my (%item) = @_;
+ if (exists $item{'month'}) {
+ my $month = $item{'month'};
+ if (not $item{'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 (%item) = @_;
+ if (exists $item{'volume'}) {
+ my $volume = $item{'volume'};
+ if (not $item{'volume'} =~ /^[1-9][0-9]*$/) {
+ return "The format of the 'volume' is wrong"
+ }
+ }
+}
+
+# Check the right format of the 'pages.'
+sub check_pages {
+ my (%item) = @_;
+ if (exists $item{'pages'}) {
+ my $pages = $item{'pages'};
+ if (not $item{'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] gt $parts[1]) {
+ return "The 'pages' are in the wrong order"
+ }
+ }
+ }
+}
+
+# Check one item.
+sub process_item {
+ my (%item) = @_;
+ 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->(%item);
+ 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 bibitem.
+sub bibitems {
+ my ($bib) = @_;
+ my @items;
+ my $s = 'top';
+ my %item;
+ my $acc = '';
+ my $key = '';
+ 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 ($char eq '@' and $s eq 'top') {
+ %item = ();
+ $s = 'start';
+ $acc = '';
+ } elsif ($char =~ /[a-z]/ and $s eq 'start') {
+ # @article
+ } elsif ($char eq '{' and $s eq 'start') {
+ $item{':type'} = substr($acc, 1);
+ $acc = '';
+ $s = 'body';
+ } elsif ($char =~ /[a-zA-Z]/ and $s eq 'body') {
+ $acc = '';
+ $s = 'key';
+ } elsif ($char =~ /[a-zA-Z0-9_]/ and $s eq 'key') {
+ # reading the key
+ } elsif ($char =~ /[a-zA-Z0-9]/ and $s eq 'value') {
+ # reading the value without quotes or brackets
+ } elsif ($char eq ',' and $s eq 'key') {
+ $item{':name'} = $acc;
+ $s = 'body';
+ } elsif ($char eq '=' and $s eq 'key') {
+ $key = $acc;
+ $s = 'value';
+ $acc = '';
+ } elsif ($char eq ',' and $s eq 'value') {
+ if (not exists $item{lc($key)}) {
+ my $tex = substr($acc, 1);
+ $tex =~ s/\s//g;
+ $item{lc($key)} = $tex;
+ }
+ $s = 'body';
+ } elsif ($char eq '}' and $s eq 'body') {
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '}' and $s eq 'value') {
+ if (not exists $item{lc($key)}) {
+ my $tex = substr($acc, 1);
+ $tex =~ s/\s//g;
+ $item{lc($key)} = $tex;
+ }
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '}' and $s eq 'key') {
+ $item{':name'} = $acc;
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '"' and $s eq 'value') {
+ $s = 'quote';
+ $acc = '';
+ } elsif ($char eq '"' and $s eq 'quote') {
+ $item{lc($key)} = 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) {
+ $item{lc($key)} = substr($acc, 1);
+ $s = 'value';
+ }
+ }
+ $escape = 0;
+ } else {
+ print "\\PackageWarningNoLine{bibcop}{It is impossible to parse the .bib file, because I do not know what to do with '$char' at line #$lineno (s=$s)}\n";
+ last;
+ }
+ if ($char eq ' ' and not($s =~ /quote|brackets/)) {
+ next;
+ }
+ $acc = $acc . $char;
+ }
+ return @items;
+}
+
+# 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/^\{+//g;
+ $tex =~ s/\}+$//g;
+ return $tex;
+}
+
+# Take a bibitem and print all its keys as a comma-separated string.
+sub listed_keys {
+ my (%item) = @_;
+ my @list;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:.*/) {
+ next;
+ }
+ push(@list, $key);
+ }
+ my @sorted = sort @list;
+ return '(' . join(', ', @sorted) . ')';
+}
+
+my %args = map { $_ => 1 } @ARGV;
+if (@ARGV+0 eq 0 or exists $args{'--help'}) {
+ debug("Bibcop is a Style Checker of .bib Files\n" .
+ "Usage: bibcop [<options>] <.bib file path>\n" .
+ " --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" .
+ " --latex Report errors in LaTeX format using \\PackageWarningNoLine command");
+} elsif (exists $args{'--version'}) {
+ debug('0.0.2');
+} else {
+ my ($file) = grep { not($_ =~ /^--.*$/) } @ARGV;
+ open(my $fh, '<', $file);
+ my $bib; { local $/; $bib = <$fh>; }
+ my @items = bibitems($bib);
+ if (exists $args{'--fix'}) {
+ for my $i (0..(@items+0 - 1)) {
+ my %item = %{ $items[$i] };
+ my $type = $item{':type'};
+ if (not exists $blessed{$type}) {
+ error("I don't know what to do with \@$type type of bibitem");
+ }
+ my $keys = $blessed{$item{':type'}};
+ my %allowed = map { $_ => 1 } @$keys;
+ my @lines;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:/) {
+ next;
+ }
+ if (not exists $allowed{$key} and not exists $allowed{$key . '?'}) {
+ next;
+ }
+ my $value = clean_tex($item{$key});
+ if ($key =~ /title|booktitle|journal/) {
+ $value = '{' . $value . '}';
+ }
+ push(@lines, " $key = {$value},");
+ }
+ debug("\@$type\{$item{':name'},");
+ my @sorted = sort @lines;
+ foreach my $line (@sorted) {
+ debug($line);
+ }
+ debug("}\n");
+ }
+ } else {
+ debug((@items+0) . ' bibitems found in ' . $file);
+ for my $i (0..(@items+0 - 1)) {
+ my %item = %{ $items[$i] };
+ debug("Checking $item{':name'} (#$i)...");
+ foreach my $err (process_item(%item)) {
+ warning("$err, in the '$item{':name'}' bibitem");
+ }
+ }
+ }
+}
+
+# Print ERROR message to the console and die.
+sub error {
+ my ($txt) = @_;
+ print $txt . "\n";
+ exit 1;
+}
+
+# Print DEBUG message to the console.
+sub debug {
+ 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";
+ }
+}
+
+1;
diff --git a/Build/source/texk/texlive/linked_scripts/scripts.lst b/Build/source/texk/texlive/linked_scripts/scripts.lst
index ac5a02a32e7..256822bd342 100644
--- a/Build/source/texk/texlive/linked_scripts/scripts.lst
+++ b/Build/source/texk/texlive/linked_scripts/scripts.lst
@@ -50,6 +50,7 @@ attachfile2/pdfatfi.pl
authorindex/authorindex
bib2gls/bib2gls.sh
bib2gls/convertgls2bib.sh
+bibcop/bibcop.pl
bundledoc/arlatex
bundledoc/bundledoc
cachepic/cachepic.tlu
diff --git a/Master/bin/aarch64-linux/bibcop b/Master/bin/aarch64-linux/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/aarch64-linux/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/amd64-freebsd/bibcop b/Master/bin/amd64-freebsd/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/amd64-freebsd/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/amd64-netbsd/bibcop b/Master/bin/amd64-netbsd/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/amd64-netbsd/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/armhf-linux/bibcop b/Master/bin/armhf-linux/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/armhf-linux/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/i386-cygwin/bibcop b/Master/bin/i386-cygwin/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/i386-cygwin/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/i386-freebsd/bibcop b/Master/bin/i386-freebsd/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/i386-freebsd/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/i386-linux/bibcop b/Master/bin/i386-linux/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/i386-linux/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/i386-netbsd/bibcop b/Master/bin/i386-netbsd/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/i386-netbsd/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/i386-solaris/bibcop b/Master/bin/i386-solaris/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/i386-solaris/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/universal-darwin/bibcop b/Master/bin/universal-darwin/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/universal-darwin/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/win32/bibcop.exe b/Master/bin/win32/bibcop.exe
new file mode 100755
index 00000000000..5777d90a17a
--- /dev/null
+++ b/Master/bin/win32/bibcop.exe
Binary files differ
diff --git a/Master/bin/x86_64-cygwin/bibcop b/Master/bin/x86_64-cygwin/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/x86_64-cygwin/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/x86_64-darwinlegacy/bibcop b/Master/bin/x86_64-darwinlegacy/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/x86_64-darwinlegacy/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/x86_64-linux/bibcop b/Master/bin/x86_64-linux/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/x86_64-linux/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/x86_64-linuxmusl/bibcop b/Master/bin/x86_64-linuxmusl/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/x86_64-linuxmusl/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/bin/x86_64-solaris/bibcop b/Master/bin/x86_64-solaris/bibcop
new file mode 120000
index 00000000000..cc1e422ff2a
--- /dev/null
+++ b/Master/bin/x86_64-solaris/bibcop
@@ -0,0 +1 @@
+../../texmf-dist/scripts/bibcop/bibcop.pl \ No newline at end of file
diff --git a/Master/texmf-dist/doc/latex/bibcop/DEPENDS.txt b/Master/texmf-dist/doc/latex/bibcop/DEPENDS.txt
new file mode 100644
index 00000000000..08a680f05bf
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/bibcop/DEPENDS.txt
@@ -0,0 +1,3 @@
+hard iexec
+hard fancyvrb
+hard verbatimcopy \ No newline at end of file
diff --git a/Master/texmf-dist/doc/latex/bibcop/LICENSE.txt b/Master/texmf-dist/doc/latex/bibcop/LICENSE.txt
new file mode 100644
index 00000000000..463e7d252d4
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/bibcop/LICENSE.txt
@@ -0,0 +1,21 @@
+(The MIT License)
+
+Copyright (c) 2022 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/Master/texmf-dist/doc/latex/bibcop/README.md b/Master/texmf-dist/doc/latex/bibcop/README.md
new file mode 100644
index 00000000000..b3cbd2ef8a3
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/bibcop/README.md
@@ -0,0 +1,54 @@
+<img src="https://raw.githubusercontent.com/yegor256/bibcop/master/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 found. You may also like
+[biblint](https://github.com/Kingsford-Group/biblint) tool —
+it does 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.
+
+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:
+
+```
+$ ./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).
+
+## 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
+$ ./tests.pl
+```
+
+You should see the `GREAT!` message. \ No newline at end of file
diff --git a/Master/texmf-dist/doc/latex/bibcop/bibcop-logo.pdf b/Master/texmf-dist/doc/latex/bibcop/bibcop-logo.pdf
new file mode 100644
index 00000000000..497d52dc0b0
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/bibcop/bibcop-logo.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/latex/bibcop/bibcop.pdf b/Master/texmf-dist/doc/latex/bibcop/bibcop.pdf
new file mode 100644
index 00000000000..a8320c97a13
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/bibcop/bibcop.pdf
Binary files differ
diff --git a/Master/texmf-dist/scripts/bibcop/bibcop.pl b/Master/texmf-dist/scripts/bibcop/bibcop.pl
new file mode 100755
index 00000000000..3fa1444b92c
--- /dev/null
+++ b/Master/texmf-dist/scripts/bibcop/bibcop.pl
@@ -0,0 +1,492 @@
+#!/usr/bin/perl
+# (The MIT License)
+#
+# Copyright (c) 2022 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;
+
+# If you want to add an extra check, just create a new procedure
+# named as "check_*".
+
+# Only these keys are allowed and only these types of items.
+my %blessed = (
+ 'article' => ['doi', 'year', 'title', 'author', 'journal', 'volume', 'number', 'publisher?'],
+ '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?'],
+);
+
+# Check the presence of mandatory keys.
+sub check_mandatory_keys {
+ my (%item) = @_;
+ my $type = $item{':type'};
+ my $mandatory = $blessed{$type};
+ foreach my $key (@$mandatory) {
+ if ($key =~ /^.*\?$/) {
+ next;
+ }
+ if (not(exists $item{$key})) {
+ my $listed = listed_keys(%item);
+ return "A mandatory '$key' key for '\@$type' is missing among $listed"
+ }
+ }
+ if (exists $blessed{$type}) {
+ my %required = map { $_ => 1 } @$mandatory;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:/) {
+ next;
+ }
+ if (not(exists $required{$key}) && not(exists $required{$key . '?'})) {
+ return "The '$key' key is not suitable for '$type', use only these: (@$mandatory)"
+ }
+ }
+ }
+}
+
+# Check that all major words are capitalized.
+sub check_capitalization {
+ my (%item) = @_;
+ my %keys = map { $_ => 1 } qw/title booktitle journal/;
+ 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 $key (keys %item) {
+ if (not exists $keys{$key}) {
+ next;
+ }
+ my $value = $item{$key};
+ 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 '$key' must be lower-cased, while '$word' (no.$pos) is not"
+ }
+ if ($word =~ /^[a-z].*/) {
+ return "All major words in the '$key' must be capitalized, while '$word' (no.$pos) is not"
+ }
+ }
+ }
+}
+
+# Check that the 'author' is formatted correctly.
+sub check_author {
+ my (%item) = @_;
+ if (exists $item{'author'} and not $item{'author'} =~ /^\{.+\}$/) {
+ my $author = clean_tex($item{'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 (%item) = @_;
+ my %keys = map { $_ => 1 } qw/title booktitle journal/;
+ foreach my $key (keys %item) {
+ if (not exists $keys{$key}) {
+ next;
+ }
+ my $value = $item{$key};
+ 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 '$key', such as '$word'"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'title' and 'booktitle.'
+sub check_titles {
+ my (%item) = @_;
+ my @keys = qw/title booktitle/;
+ foreach my $key (@keys) {
+ if (not exists($item{$key})) {
+ next;
+ }
+ my $title = $item{$key};
+ if (not $title =~ /^\{.+\}$/) {
+ return "The '$key' must be wrapped in double curled brackets"
+ }
+ }
+}
+
+# Check that no values have tailing dots.
+# Check that there are no spaces before commans.
+sub check_typography {
+ my (%item) = @_;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:.*/) {
+ next;
+ }
+ my $value = $item{$key};
+ if ($value =~ /.*\.$/ and $key ne 'author') {
+ return "The '$key' must not end with a dot"
+ }
+ if ($value =~ /.* ,.*/) {
+ return "In the '$key', do not put a space before the comma"
+ }
+ }
+}
+
+# Check the year is not mentioned in titles.
+sub check_year_in_titles {
+ my (%item) = @_;
+ my @keys = qw/title booktitle journal/;
+ foreach my $key (@keys) {
+ if (not exists($item{$key})) {
+ next;
+ }
+ my @words = only_words($item{$key});
+ foreach my $word (@words) {
+ if ($word =~ /^[1-9][0-9]{3}$/) {
+ return "The '$key' must not contain the year $word, it is enough to have the 'year' key"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'booktitle' in the 'inproceedings' item.
+sub check_booktile_of_inproceedings {
+ my (%item) = @_;
+ my $key = 'inproceedings';
+ if ($item{':type'} eq $key) {
+ if (exists $item{'booktitle'}) {
+ my @words = only_words($item{'booktitle'});
+ if (lc($words[0]) ne 'proceedings' or lc($words[1]) ne 'of' or lc($words[2]) ne 'the') {
+ return "The '$key' must start with 'Proceedings of the ...'"
+ }
+ }
+ }
+}
+
+# Check the right format of the 'doi.'
+sub check_doi {
+ my (%item) = @_;
+ if (exists $item{'doi'}) {
+ my $doi = $item{'doi'};
+ if (not $item{'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 (%item) = @_;
+ if (exists $item{'year'}) {
+ my $year = $item{'year'};
+ if (not $item{'year'} =~ /^[0-9]{3,4}$/) {
+ return "The format of the 'year' is wrong"
+ }
+ }
+}
+
+# Check the right format of the 'month.'
+sub check_month {
+ my (%item) = @_;
+ if (exists $item{'month'}) {
+ my $month = $item{'month'};
+ if (not $item{'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 (%item) = @_;
+ if (exists $item{'volume'}) {
+ my $volume = $item{'volume'};
+ if (not $item{'volume'} =~ /^[1-9][0-9]*$/) {
+ return "The format of the 'volume' is wrong"
+ }
+ }
+}
+
+# Check the right format of the 'pages.'
+sub check_pages {
+ my (%item) = @_;
+ if (exists $item{'pages'}) {
+ my $pages = $item{'pages'};
+ if (not $item{'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] gt $parts[1]) {
+ return "The 'pages' are in the wrong order"
+ }
+ }
+ }
+}
+
+# Check one item.
+sub process_item {
+ my (%item) = @_;
+ 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->(%item);
+ 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 bibitem.
+sub bibitems {
+ my ($bib) = @_;
+ my @items;
+ my $s = 'top';
+ my %item;
+ my $acc = '';
+ my $key = '';
+ 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 ($char eq '@' and $s eq 'top') {
+ %item = ();
+ $s = 'start';
+ $acc = '';
+ } elsif ($char =~ /[a-z]/ and $s eq 'start') {
+ # @article
+ } elsif ($char eq '{' and $s eq 'start') {
+ $item{':type'} = substr($acc, 1);
+ $acc = '';
+ $s = 'body';
+ } elsif ($char =~ /[a-zA-Z]/ and $s eq 'body') {
+ $acc = '';
+ $s = 'key';
+ } elsif ($char =~ /[a-zA-Z0-9_]/ and $s eq 'key') {
+ # reading the key
+ } elsif ($char =~ /[a-zA-Z0-9]/ and $s eq 'value') {
+ # reading the value without quotes or brackets
+ } elsif ($char eq ',' and $s eq 'key') {
+ $item{':name'} = $acc;
+ $s = 'body';
+ } elsif ($char eq '=' and $s eq 'key') {
+ $key = $acc;
+ $s = 'value';
+ $acc = '';
+ } elsif ($char eq ',' and $s eq 'value') {
+ if (not exists $item{lc($key)}) {
+ my $tex = substr($acc, 1);
+ $tex =~ s/\s//g;
+ $item{lc($key)} = $tex;
+ }
+ $s = 'body';
+ } elsif ($char eq '}' and $s eq 'body') {
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '}' and $s eq 'value') {
+ if (not exists $item{lc($key)}) {
+ my $tex = substr($acc, 1);
+ $tex =~ s/\s//g;
+ $item{lc($key)} = $tex;
+ }
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '}' and $s eq 'key') {
+ $item{':name'} = $acc;
+ push(@items, { %item });
+ $s = 'top';
+ } elsif ($char eq '"' and $s eq 'value') {
+ $s = 'quote';
+ $acc = '';
+ } elsif ($char eq '"' and $s eq 'quote') {
+ $item{lc($key)} = 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) {
+ $item{lc($key)} = substr($acc, 1);
+ $s = 'value';
+ }
+ }
+ $escape = 0;
+ } else {
+ print "\\PackageWarningNoLine{bibcop}{It is impossible to parse the .bib file, because I do not know what to do with '$char' at line #$lineno (s=$s)}\n";
+ last;
+ }
+ if ($char eq ' ' and not($s =~ /quote|brackets/)) {
+ next;
+ }
+ $acc = $acc . $char;
+ }
+ return @items;
+}
+
+# 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/^\{+//g;
+ $tex =~ s/\}+$//g;
+ return $tex;
+}
+
+# Take a bibitem and print all its keys as a comma-separated string.
+sub listed_keys {
+ my (%item) = @_;
+ my @list;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:.*/) {
+ next;
+ }
+ push(@list, $key);
+ }
+ my @sorted = sort @list;
+ return '(' . join(', ', @sorted) . ')';
+}
+
+my %args = map { $_ => 1 } @ARGV;
+if (@ARGV+0 eq 0 or exists $args{'--help'}) {
+ debug("Bibcop is a Style Checker of .bib Files\n" .
+ "Usage: bibcop [<options>] <.bib file path>\n" .
+ " --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" .
+ " --latex Report errors in LaTeX format using \\PackageWarningNoLine command");
+} elsif (exists $args{'--version'}) {
+ debug('0.0.2');
+} else {
+ my ($file) = grep { not($_ =~ /^--.*$/) } @ARGV;
+ open(my $fh, '<', $file);
+ my $bib; { local $/; $bib = <$fh>; }
+ my @items = bibitems($bib);
+ if (exists $args{'--fix'}) {
+ for my $i (0..(@items+0 - 1)) {
+ my %item = %{ $items[$i] };
+ my $type = $item{':type'};
+ if (not exists $blessed{$type}) {
+ error("I don't know what to do with \@$type type of bibitem");
+ }
+ my $keys = $blessed{$item{':type'}};
+ my %allowed = map { $_ => 1 } @$keys;
+ my @lines;
+ foreach my $key (keys %item) {
+ if ($key =~ /^:/) {
+ next;
+ }
+ if (not exists $allowed{$key} and not exists $allowed{$key . '?'}) {
+ next;
+ }
+ my $value = clean_tex($item{$key});
+ if ($key =~ /title|booktitle|journal/) {
+ $value = '{' . $value . '}';
+ }
+ push(@lines, " $key = {$value},");
+ }
+ debug("\@$type\{$item{':name'},");
+ my @sorted = sort @lines;
+ foreach my $line (@sorted) {
+ debug($line);
+ }
+ debug("}\n");
+ }
+ } else {
+ debug((@items+0) . ' bibitems found in ' . $file);
+ for my $i (0..(@items+0 - 1)) {
+ my %item = %{ $items[$i] };
+ debug("Checking $item{':name'} (#$i)...");
+ foreach my $err (process_item(%item)) {
+ warning("$err, in the '$item{':name'}' bibitem");
+ }
+ }
+ }
+}
+
+# Print ERROR message to the console and die.
+sub error {
+ my ($txt) = @_;
+ print $txt . "\n";
+ exit 1;
+}
+
+# Print DEBUG message to the console.
+sub debug {
+ 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";
+ }
+}
+
+1;
diff --git a/Master/texmf-dist/source/latex/bibcop/bibcop.dtx b/Master/texmf-dist/source/latex/bibcop/bibcop.dtx
new file mode 100644
index 00000000000..07d513b49fc
--- /dev/null
+++ b/Master/texmf-dist/source/latex/bibcop/bibcop.dtx
@@ -0,0 +1,346 @@
+% \iffalse meta-comment
+% (The MIT License)
+%
+% Copyright (c) 2022 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>
+[2022-12-12 0.0.2 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 your |.bib| files for style errors and emits
+% warning messages if any issues are found (make sure you use it 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
+% You may see warnings in the \TeX log. Fix the issues in the |main.bib| file
+% and the warnings will disappear.
+
+% If you use the |.sty| file (without installing it into the \TeX{} tree), don't forget
+% to also copy the |bibcop.pl| file --- it is the Perl script that does all the work
+% of checking your |.bib| files. The |.sty| is just a simple wrapper around it.
+
+% Make sure |\usepackage{bibcop}| stays before all other |\usepackage| commands, otherwise
+% you won't see any warnings from |bibcop|.
+
+% \section{The Rules}
+
+% This is a more or less complete list of rules we enforce on a |.bib| file:
+
+% \DescribeMacro{types}
+% Only |@article|, |@book|, |@inproceedings|, and |@misc| types of bib items are allowed.
+% Everything else, like |@manual|, |@phdthesis|, and many others are simply prohibited.
+% The mentioned four should be enough for everything.
+
+% \DescribeMacro{keys}
+% There are pretty limited lists of allowed keys for each type of bib item. The keys
+% that are not in the list are prohibited to use.
+
+% \DescribeMacro{doi}
+% Every bib item must have the |doi| key, 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.
+
+% \DescribeMacro{caps}
+% In |title|, |booktitle|, and |journal| keys, 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.
+
+% \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.
+
+% \DescribeMacro{shorts}
+% It is not allowed to shorten any words, 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 with 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| key, nowhere else.
+% In the |year| key 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{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| bib item 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
+
+% \StopEventually{}
+
+% \section{Implementation}
+% \changes{0.0.1}{2022/12/11}{First draft.}
+% \changes{0.0.2}{2022/12/12}{Documentation extended, more rules added.}
+
+% 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}
+
+% \begin{macro}{bibcop.pl}
+% Then, we copy the Perl script using |\VerbatimCopy| from
+% \href{https://ctan.org/pkg/verbatimcopy}{verbatimcopy}:
+% \begin{macrocode}
+\RequirePackage{verbatimcopy}
+\VerbatimCopy{bibcop.pl}{bibcop.tmp.pl}
+\message{bibcop: File with Perl script 'bibcop.pl' copied^^J}%
+% \end{macrocode}
+% \end{macro}
+
+% \begin{macro}{\bibliography}
+% Then, we re-define the |\bibliography| command:
+% \begin{macrocode}
+\makeatletter
+\ifdefined\bibliography\else\gdef\bibliography#1{}\fi
+\let\bibcop@oldbibliography\bibliography
+\renewcommand\bibliography[1]{%
+ \iexec{perl "./bibcop.tmp.pl" --latex '#1.bib'}%
+ \bibcop@oldbibliography{#1}%
+}
+\makeatother
+% \end{macrocode}
+% \end{macro}
+
+% \begin{macro}{\addbibresource}
+% Then, we re-define the |\addbibresource| command:
+% \begin{macrocode}
+\makeatletter
+\ifdefined\addbibresource\else\gdef\addbibresource#1{}\fi
+\let\bibcop@oldaddbibresource\addbibresource
+\renewcommand\addbibresource[1]{%
+ \iexec{perl "./bibcop.tmp.pl" --latex '#1'}%
+ \bibcop@oldaddbibresource{#1}%
+}
+\makeatother
+% \end{macrocode}
+% \end{macro}
+
+% \Finale
+
+% \clearpage
+% \printbibliography
+% \clearpage
+
+% \PrintChanges
+% \clearpage
+% \PrintIndex
diff --git a/Master/texmf-dist/source/latex/bibcop/bibcop.ins b/Master/texmf-dist/source/latex/bibcop/bibcop.ins
new file mode 100644
index 00000000000..4c619469dd8
--- /dev/null
+++ b/Master/texmf-dist/source/latex/bibcop/bibcop.ins
@@ -0,0 +1,55 @@
+%% (The MIT License)
+%%
+%% Copyright (c) 2022 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 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/Master/texmf-dist/tex/latex/bibcop/bibcop.sty b/Master/texmf-dist/tex/latex/bibcop/bibcop.sty
new file mode 100644
index 00000000000..ff48a3c02d9
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/bibcop/bibcop.sty
@@ -0,0 +1,84 @@
+%%
+%% This is file `bibcop.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% bibcop.dtx (with options: `package')
+%% (The MIT License)
+%%
+%% Copyright (c) 2022 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.
+
+
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{bibcop}
+[2022-12-12 0.0.2 Style Checker of Bibliography Files]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+\RequirePackage{iexec}
+
+\RequirePackage{verbatimcopy}
+\VerbatimCopy{bibcop.pl}{bibcop.tmp.pl}
+\message{bibcop: File with Perl script 'bibcop.pl' copied^^J}%
+
+\makeatletter
+\ifdefined\bibliography\else\gdef\bibliography#1{}\fi
+\let\bibcop@oldbibliography\bibliography
+\renewcommand\bibliography[1]{%
+ \iexec{perl "./bibcop.tmp.pl" --latex '#1.bib'}%
+ \bibcop@oldbibliography{#1}%
+}
+\makeatother
+
+\makeatletter
+\ifdefined\addbibresource\else\gdef\addbibresource#1{}\fi
+\let\bibcop@oldaddbibresource\addbibresource
+\renewcommand\addbibresource[1]{%
+ \iexec{perl "./bibcop.tmp.pl" --latex '#1'}%
+ \bibcop@oldaddbibresource{#1}%
+}
+\makeatother
+
+
+
+\endinput
+%%
+%% End of file `bibcop.sty'.
diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check
index 0ac2ec275e1..a5c477c671d 100755
--- a/Master/tlpkg/bin/tlpkg-ctan-check
+++ b/Master/tlpkg/bin/tlpkg-ctan-check
@@ -105,7 +105,7 @@ my @TLP_working = qw(
beebe begingreek begriff beilstein belleek bengali
bera berenisadf besjournals bestpapers betababel beton beuron
bewerbung bez123 bezierplot bfh-ci bgteubner bguq bhcexam
- bib-fr bib2gls bibarts biber biber-ms bibhtml
+ bib-fr bib2gls bibarts bibcop biber biber-ms bibhtml
biblatex biblatex-abnt biblatex-ajc2020unofficial
biblatex-anonymous biblatex-apa biblatex-apa6
biblatex-archaeology biblatex-arthistory-bonn
diff --git a/Master/tlpkg/libexec/ctan2tds b/Master/tlpkg/libexec/ctan2tds
index a89b147c2b3..eb95f3513e4 100755
--- a/Master/tlpkg/libexec/ctan2tds
+++ b/Master/tlpkg/libexec/ctan2tds
@@ -3541,6 +3541,7 @@ $standardttf = '\.ttf|\.TTC';
'attachfile2' => 'pdfatfi\.pl$',
'authorindex' => 'authorindex$',
'bib2gls' => '\.sh$',
+ 'bibcop' => '\.pl$',
'bibexport' => 'bibexport\.sh$',
'bundledoc' => '(arlatex|bundledoc)$',
'checkcites' => '\.lua$',
diff --git a/Master/tlpkg/tlpsrc/bibcop.tlpsrc b/Master/tlpkg/tlpsrc/bibcop.tlpsrc
new file mode 100644
index 00000000000..8a03ed5983d
--- /dev/null
+++ b/Master/tlpkg/tlpsrc/bibcop.tlpsrc
@@ -0,0 +1,4 @@
+binpattern f bin/${ARCH}/${PKGNAME}
+hard iexec
+hard fancyvrb
+hard verbatimcopy
diff --git a/Master/tlpkg/tlpsrc/collection-bibtexextra.tlpsrc b/Master/tlpkg/tlpsrc/collection-bibtexextra.tlpsrc
index dfbc7a8c5c7..30bc56d23c8 100644
--- a/Master/tlpkg/tlpsrc/collection-bibtexextra.tlpsrc
+++ b/Master/tlpkg/tlpsrc/collection-bibtexextra.tlpsrc
@@ -20,6 +20,7 @@ depend besjournals
depend bestpapers
depend bib2gls
depend bibarts
+depend bibcop
depend biber
depend biber-ms
depend bibexport