1
1
#! /bin/bash
2
2
3
+ # This script checks that the LaTeX sources stick to the rules.
4
+
5
+ failed=0
6
+
3
7
# Ignore files where rules may be violated within macro definitions.
4
8
texfiles=$( ls * .tex | grep -v macros.tex | grep -v layout.tex | grep -v tables.tex)
5
9
texlibdesc=" support.tex concepts.tex diagnostics.tex utilities.tex strings.tex containers.tex iterators.tex ranges.tex algorithms.tex numerics.tex time.tex locales.tex iostreams.tex regex.tex atomics.tex threads.tex"
6
10
texlib=" lib-intro.tex $texlibdesc "
7
11
8
- # Discover "Overfull \[hv]box" and "Reference ... undefined" messages from LaTeX.
9
- sed -n ' /\.tex/{s/^.*\/\([-a-z0-9]\+\.tex\).*$/\1/;h};
10
- /Overfull [\\][hv]box\|LaTeX Warning..Reference/{x;p;x;p}' std.log |
11
- sed ' /^.\+\.tex$/{N;s/\n/:/}' | grep . && exit 1
12
+ # Filter that reformats the error message as a "workflow command",
13
+ # for native handling by github actions.
14
+ # Prefixes each line of input with "$*: ".
15
+ function fail() {
16
+ # echo "::error file=app.js,line=10,col=15::Something went wrong"
17
+
18
+ # For some reason, the file/line/col information is not shown in the GUI.
19
+ # Make sure to leave it in the message proper.
20
+ ! sed ' s@^\(.\+\.tex\):\([0-9]\+\):@::error file=source/\1,line=\2::\1:\2:' " $* " ' : @; s@^\([^:][^:]\)@::error ::' " $* " ' : \1@' |
21
+ grep .
22
+ }
23
+
12
24
13
25
# Find non-ASCII (Unicode) characters in the source
14
- LC_ALL=C grep -ne ' [^ -~ ]' * .tex | sed ' s/$/ <--- non-ASCII character/' | grep . && exit 1
26
+ LC_ALL=C grep -ne ' [^ -~ ]' * .tex |
27
+ fail ' non-ASCII character' || failed=1
15
28
16
29
# Trailing whitespace in a line.
17
- grep -ne ' \s$' * .tex | sed ' s/$/<--- trailing whitespace/' | grep . && exit 1
30
+ grep -ne ' \s$' * .tex |
31
+ fail ' trailing whitespace' || failed=1
18
32
19
33
# Trailing empty lines
20
- for f in * .tex; do [ $( tail -c 2 $f | wc -l) -eq 1 ] || (echo " $f has trailing empty lines" ; exit 1 ) done
34
+ for f in * .tex; do
35
+ [ $( tail -c 2 $f | wc -l) -eq 1 ] ||
36
+ echo " $f " | fail ' trailing empty lines' || failed=1
37
+ done
21
38
22
39
# indented \begin{codeblock} / \end{codeblock} (causes unwanted empty space)
23
- grep -ne ' ^.\+\\\(begin\|end\){codeblock}' $texfiles && exit 1
40
+ grep -ne ' ^.\+\\\(begin\|end\){codeblock}' $texfiles |
41
+ fail ' indented codeblock env' || failed=1
24
42
25
43
# \pnum not alone on a line.
26
- grep -ne ' ^[^%]\+\\pnum' $texfiles && exit 1
27
- grep -ne ' \\pnum.\+$' $texfiles && exit 1
44
+ grep -ne ' ^[^%]\+\\pnum' $texfiles |
45
+ fail " pnum not alone on line" || failed=1
46
+ grep -ne ' \\pnum.\+$' $texfiles |
47
+ fail " pnum not alone on line" || failed=1
28
48
# Fixup: sed '/\\pnum.\+$/s/\\pnum\s*/\\pnum\n/'
29
49
30
50
# Two consecutive \pnum
31
51
for f in $texfiles ; do
32
- awk ' prev == $0 && /^\\pnum/ { print FILENAME ":" FNR ": duplicate \\pnum on consecutive lines" } { prev = $0 }' $f
33
- done | grep . && exit 1
52
+ awk ' prev == $0 && /^\\pnum/ { print FILENAME ":" FNR ":" } { prev = $0 }' $f
53
+ done |
54
+ fail ' two consecutive \\pnum' || failed=1
34
55
35
56
# punctuation after the footnote marker
36
- grep -n " \\ end{footnote" $texfiles | grep -v ' }[@)%]\?$' && exit 1
57
+ grep -n " \\ end{footnote" $texfiles | grep -v ' }[@)%]\?$' |
58
+ fail " punctuation after footnote marker" || failed=1
37
59
38
60
# \opt used incorrectly.
39
- grep -n ' \\opt[^{]' $texfiles && exit 1
40
- grep -n ' opt{}' * .tex && exit 1
61
+ grep -n ' \\opt[^{]' $texfiles |
62
+ fail ' \\opt used incorrectly' || failed=1
63
+ grep -n ' opt{}' * .tex |
64
+ fail ' \\opt used incorrectly' || failed=1
41
65
42
66
# Use \notdef instead of "not defined".
43
- grep -n " // not defined" $texfiles | sed ' s/$/ <--- use \\notdef instead/' | grep . && exit 1
67
+ grep -n " // not defined" $texfiles |
68
+ fail " use \\ notdef instead" || failed=1
44
69
45
70
# Library element introducer followed by stuff.
46
- grep -ne ' ^\\\(constraints\|mandates\|expects\|effects\|sync\|ensures\|returns\|throws\|complexity\|remarks\|errors\).\+$' $texlibdesc && exit 1
71
+ grep -ne ' ^\\\(constraints\|mandates\|expects\|effects\|sync\|ensures\|returns\|throws\|complexity\|remarks\|errors\).\+$' $texlibdesc |
72
+ fail ' stuff after library element' || failed=1
47
73
# Fixup: sed 's/^\\\(constraints\|mandates\|expects\|effects\|sync\|ensures\|returns\|throws\|complexity\|remarks\|errors\)\s*\(.\)/\\\1\n\2/'
48
74
# Fixup: sed 's/^\\ //'
49
75
50
76
# Change marker in [diff] followed by stuff.
51
- grep -Hne ' ^\\\(change\|rationale\|effect\|difficulty\|howwide\)\s.\+$' compatibility.tex && exit 1
77
+ grep -Hne ' ^\\\(change\|rationale\|effect\|difficulty\|howwide\)\s.\+$' compatibility.tex |
78
+ fail " change marker in [diff] followed by stuff" || failed=1
52
79
# Fixup: sed 's/^\\\(change\|rationale\|effect\|difficulty\|howwide\)\s\(.\)/\\\1\n\2/'q
53
80
54
81
# "template <class" (with space) in library clause.
55
- grep -ne ' template\s<class' $texlib | sed ' s/$/ <--- space between "template" and "<class"/' | grep . && exit 1
82
+ grep -ne ' template\s<class' $texlib |
83
+ fail ' space between "template" and "<class"' || failed=1
56
84
57
85
# \begin{example/note} with non-whitespace in front on the same line.
58
- grep -ne ' ^.*[^ ]\s*\\\(begin\|end\){\(example\|note\)}' $texfiles && exit 1
86
+ grep -ne ' ^.*[^ ]\s*\\\(begin\|end\){\(example\|note\)}' $texfiles |
87
+ fail " non-whitespace before note/example begins" || failed=1
59
88
# Fixup: sed 's/^\(.*[^ ]\)\s*\(\\\(begin\|end\){\(example\|note\)}\)/\1\n\2/'
60
89
61
90
# \begin/end{example/note} with stuff (except %) following on the same line.
62
- grep -ne ' \\\(begin\|end\){\(example\|note\)}[^%]\+$' $texfiles && exit 1
91
+ grep -ne ' \\\(begin\|end\){\(example\|note\)}[^%]\+$' $texfiles |
92
+ fail " content following note/example env" || failed=1
63
93
# Fixup: sed 's/\(\\\(begin\|end\){\(example\|note\)}\)\s*\([^ ].*\)$/\1\n\4/'
64
94
65
95
# \end{note} or \end{example} at the end of a table cell
66
96
grep -n -A1 ' \\end{\(example\|note\)}' $texfiles | grep -- ' - *\(\\\\\|&\)' |
67
- sed ' s/$/ <--- needs tailnote or tailexample/ ' | grep . && exit 1
97
+ fail " needs tailnote or tailexample" || failed= 1
68
98
69
99
# Blank line between "begin example" and "begin codeblock"
70
100
for f in $texfiles ; do
71
101
sed -n ' /\\begin{example}/{N;N;/\n\n\\begin{codeblock}$/{=;p}}' $f |
72
102
# prefix output with filename and line
73
103
sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/"
74
- done | grep . && exit 1
104
+ done |
105
+ fail ' blank line between "begin example" and "begin codeblock"' || failed=1
75
106
# Fixup: sed '/\\begin{example}/{N;s/\n$//}'
76
107
77
108
# Comment not aligned to multiple of four. (Ignore lines with "@".)
78
109
for f in $texfiles ; do
79
110
sed -n ' /begin{codeblock\(tu\)\?}/,/end{codeblock\(tu\)\?}/{/^[^@]*[^ @][^@]*\/\//{=;p}}' $f |
80
111
# prefix output with filename and line
81
112
sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/" |
82
- awk ' { match($0,"^[-a-z0-9]*[.]tex:[0-9]*:"); n=match(substr($0,RLENGTH+1),"[ ;]//"); if (n % 4 != 0) print $0 " <--- comment starts in column " n; }'
83
- done | grep . && exit 1
113
+ awk ' { match($0,"^[-a-z0-9]*[.]tex:[0-9]*:"); n=match(substr($0,RLENGTH+1),"[ ;]//"); if (n % 4 != 0) print "comment starts in column " n ": " $0; }'
114
+ done |
115
+ fail " comment not aligned" || failed=1
84
116
85
117
# Deleted special member function with a parameter name.
86
- grep -n " &[ 0-9a-z_]\+) = delete" $texfiles && exit 1
118
+ grep -n " &[ 0-9a-z_]\+) = delete" $texfiles |
119
+ fail ' named parameter in deleted special member' || failed=1
87
120
# to fix: sed '/= delete/s/&[ 0-9a-z_]\+)/\&)/'
88
121
89
122
# Bad characters in label. "-" is allowed due to a single remaining offender.
90
- grep -n ' ^\\rSec.\[[^]]*[^-a-z.0-9][^]]*\]{' $texfiles | sed ' s/$/ <--- bad character in label/' | grep . && exit 1
123
+ grep -n ' ^\\rSec.\[[^]]*[^-a-z.0-9][^]]*\]{' $texfiles |
124
+ fail ' bad character in label' || failed=1
91
125
92
126
# "shall", "may", or "should" inside a note
93
127
for f in $texfiles ; do
94
128
sed -n ' /begin{\(note\|footnote\)}/,/end{\(note\|footnote\)}/{/\(shall\|may\|should\)[^a-zA-Z]/{=;p}}' $f |
95
129
# prefix output with filename and line
96
- sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/" |
97
- sed ' s/$/ <--- "shall", "should", or "may" inside a note/ '
98
- done | grep . && exit 1
130
+ sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/"
131
+ done |
132
+ fail ' "shall", "should", or "may" inside a note ' || failed= 1
99
133
100
134
# Hanging paragraphs
101
135
for f in $texfiles ; do
102
136
sed -n ' /^\\rSec/{=;p};/^\\pnum/{s/^.*$/x/;=;p}' $f |
103
137
# prefix output with filename and line
104
138
sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/" |
105
139
awk -F: ' BEGIN { prevlevel = 0 } $3 ~ /^\\rSec./ { match($3, "[0-9]"); level=substr($3, RSTART, 1); if (text && level > prevlevel) { print prevsec " <-- Hanging paragraph follows" } prevlevel = level; prevsec = $3; text = 0 } $3 == "x" { text = 1 }'
106
- done | grep . && exit 1
140
+ done |
141
+ fail ' hanging paragraph' || failed=1
107
142
108
143
# Subclauses without siblings
109
144
for f in $texfiles ; do
@@ -114,29 +149,30 @@ for f in $texfiles; do
114
149
{
115
150
match($3, "[0-9]");
116
151
level = substr($3, RSTART, 1);
117
- if (level < prevlevel && secs[prevlevel] == 1) { print title[prevlevel] " <-- Subclause without siblings" }
152
+ if (level < prevlevel && secs[prevlevel] == 1) { print title[prevlevel] }
118
153
++secs[level];
119
154
title[level] = $0;
120
155
secs[level + 1] = 0;
121
156
prevlevel = level;
122
157
}'
123
- done | grep . && exit 1
158
+ done | fail ' subclause without siblings ' || failed= 1
124
159
125
160
126
161
# Library descriptive macros not immediately preceded by \pnum.
127
162
for f in $texlibdesc ; do
128
163
sed -n ' /^\\pnum/{h;:x;n;/^\\index/b x;/^\\\(constraints\|mandates\|expects\|effects\|sync\|ensures\|returns\|throws\|complexity\|remarks\|errors\)/{x;/\n/{x;=;p};d};/^\\pnum/D;H;b x}' $f |
129
164
# prefix output with filename and line
130
- sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/" |
131
- sed ' s/$/ <--- \\pnum missing/ '
132
- done | grep . && exit 1
165
+ sed ' /^[0-9]\+$/{N;s/\n/:/}' | sed " s/.*/$f :&/"
166
+ done |
167
+ fail ' \\pnum missing ' || failed= 1
133
168
134
169
# Cross-references pointing to their own section.
135
170
for f in $texfiles ; do
136
171
sed -n ' /^\\rSec/{s/^.rSec.\[/S /;s/\].*$//;=;p};/\\iref{/{s/^.*\\.\?ref{\([-a-z.0-9]\+\)}.*/R \1/g;=;p}' $f |
137
172
sed ' /^[0-9]\+$/{N;s/\n/: /}' | sed " s/.*/$f :&/" |
138
173
awk ' $2 == "S" { seclabel = $3 } $2 == "R" && $3 == seclabel { print $1 " section self-reference to [" $3 "]" }'
139
- done | grep . && exit 1
174
+ done |
175
+ fail " cross-reference to its own section" || failed=1
140
176
141
177
# \placeholder before (
142
178
# egrep 'placeholder{[-A-Za-z]*}@?\(' *.tex
@@ -155,20 +191,9 @@ done | grep . && exit 1
155
191
# different files anyway. So just check the timestamp.
156
192
for f in * .dot; do
157
193
if [ " $f " -nt " ${f% .dot} .pdf" ]; then
158
- echo -e " need to rebuild ${f% .dot} .pdf:\nmake clean-figures && make figures" >&2
159
- exit 1
194
+ echo -e " need to rebuild ${f% .dot} .pdf:\nmake clean-figures && make figures"
160
195
fi
161
- done
196
+ done |
197
+ fail ' outdated figure' || failed=1
162
198
163
- # Cross references since the previous standard.
164
- function indexentries() { sed ' s,\\glossaryentry{\(.*\)@.*,\1,' " $1 " | LANG=C sort; }
165
- function removals() { diff -u " $1 " " $2 " | grep ' ^-' | grep -v ' ^---' | sed ' s/^-//' ; }
166
- function difference() { diff -u " $1 " " $2 " | grep ' ^[-+]' | grep -v ' ^\(---\|+++\)' ; }
167
- XREFDELTA=" $( difference <( indexentries xrefdelta.glo) <( removals <( indexentries xrefprev) <( indexentries xrefindex.glo) ) ) "
168
- if [ -n " $XREFDELTA " ]; then
169
- echo " incorrect entries in xrefdelta.tex:" >&2
170
- echo " $XREFDELTA " | sed ' s,^-,spurious ,; s,^+,missing ,;' >&2
171
- exit 1
172
- fi
173
-
174
- exit 0
199
+ exit $failed
0 commit comments