summaryrefslogtreecommitdiffstats
path: root/misc/manlint
diff options
context:
space:
mode:
Diffstat (limited to 'misc/manlint')
-rw-r--r--misc/manlint225
1 files changed, 225 insertions, 0 deletions
diff --git a/misc/manlint b/misc/manlint
new file mode 100644
index 0000000..c919a94
--- /dev/null
+++ b/misc/manlint
@@ -0,0 +1,225 @@
+#!/usr/bin/perl -w
+
+# manlint - report "errors" in man page(s).
+
+# USAGE:
+# manlint [list of files to check]
+#
+# EXAMPLE:
+# manlint /usr/man/man*/*.* | less
+
+# An error is anything not known to be a safe construct in a man page;
+# see man(7) for more information.
+# Currently it's excessively paranoid, but that's the point -- this
+# program assumes there's a problem, and if it isn't we can add that to the
+# ruleset so that what's safe is explicitly spelled out.
+# Currently this program only examines tmac.an based pages, the normal
+# kind encountered in Linux. This is different than the BSD manddoc format,
+# which is used by a number of man pages.
+
+# (C) 1999 David A. Wheeler (dwheeler@ida.org)
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+require 5.002; # Requires Perl 5.002 because functions are prototyped.
+
+# First, set up configuration.
+
+$debug = 0;
+$errs = $totalerrs = 0;
+$goodfiles = $badfiles = $skipfiles = 0;
+$filename = '';
+
+# Allow options for small or large safe set; just printing if a file fails
+# instead of detail; auto-skip BSD files.
+
+# This is a list of "safe" macros, with their value being the
+# maximum number of allowed parameters (-1 = any, 0=no parameters allowed)
+%safemacros = (
+ 'TH' => 5,
+ # Font Control:
+ 'B' => -1, 'BI' => -1, 'BR' => -1,
+ 'I' => -1, 'IB' => -1, 'IR' => -1,
+ 'RB' => -1, 'RI' => -1, 'SB' => -1, 'SM' => -1,
+ # tmac.an other macros:
+ 'SH' => 1,
+ 'LP' => 0, 'P' => 0,
+ 'PP' => 0,
+ 'RS' => 1, 'RE' => 0,
+ 'HP' => 1, 'IP' => 2, 'TP' => 1,
+ 'DT' => 0, 'PD' => 1, 'SS' => 1,
+ # We'll allow IX (indexing).
+ 'IX' => -1,
+ # I'm adding the UR, UN, and UE macros that will permit embedded URIs.
+ 'UR' => 1,
+ 'UN' => 1,
+ 'UE' => 0,
+ # allowed troff macros
+ '\\"' => -1, # troff comments
+ 'ps' => 1, # Point size
+ 'ft' => 1, # Font commands (not recommended, may be ignored in some cases)
+ 'hy' => 1, # Hyphenation (probably ignored in translation)
+ 'bp' => 0, # Force page break; optional parameter forbidden.
+ 'ne' => 1, # Need lines (likely to be ignored in translation)
+ 'br' => 0,
+ 'nf' => 0, # No-fill; insert breaks at end of each line.
+ 'fi' => 0,
+ 'ig' => 1,
+ '.' => 0, # standard end-of-ignore/end-of-definition.
+ 'ce' => 1, # Center next N lines
+ 'ad' => 1,
+ 'na' => 0,
+ # Will probably need to handle some if.
+ 'if' => -1, # LIMITED VERSION.
+ 'ie' => -1, # LIMITED VERSION.
+ 'el' => -1,
+ 'so' => 1, # Handle 'so' for shared man pages
+ 'sp' => 1, # Vertical Space - only permit positive values.
+ 'de' => 1, # Handling 'macro define' is a pain, but many pages require it.
+ 'ds' => -1, # Allow string defines.
+ 'in' => 1, # Require that every indent be paired with a negative indent.
+ 'ti' => 1, # Temporary indent may be ignored
+ 'hy' => 1, # Hypenation almost certainly ignored by anyone else.
+ 'nh' => 1, # Again, hyphenation likely ignored.
+ 'tr' => 1, # Translations limited, see below.
+);
+
+# Allowed parameters for the ft (font) troff command.
+%allowed_ft_parameter = (
+ '1' => 1,
+ '2' => 1,
+ '3' => 1,
+ '4' => 1,
+ 'R' => 1,
+ 'I' => 1,
+ 'B' => 1,
+ 'P' => 1,
+ 'CW' => 1,
+ '' => 1,
+);
+
+%allowed_tr = (
+ '\\(ts"' => 1,
+ '\\(is\'' => 1,
+ '\\(if`' => 1,
+ '\\(pd"' => 1,
+ '\\(*W-|\(bv\*(Tr' => 1,
+ '\\*(Tr' => 1,
+);
+
+sub problem($) {
+ # Report a problem, if you should.
+ my $message = shift;
+ print "${ARGV}: $message\n";
+ $errs++;
+}
+
+sub clean_state {
+ %defined_macros = ();
+ $is_skipped = 0;
+}
+
+sub process_line {
+ # Process line already read in $_ (default input line).
+ my $macro;
+ my $parameters;
+ if (m/^[.']\s*([^\s]+)\s*(.*)?/) {
+ $macro=$1;
+ $parameters=$2;
+ $macro =~ s/\s//g;
+ print "Found macro: #${macro}#\n" if $debug;
+ if ($macro =~ m/Dd/) { # Is this the BSD macro set and not a tmac.an set?
+ problem("Uses BSD mandoc conventions instead of tmac.an");
+ $errs--; # Patch up error count.
+ # print "${ARGV}: Uses BSD mandoc conventions instead of tmac.an.\n";
+ close(ARGV); # Skip the rest of this file.
+ $is_skipped = 1;
+ return;
+ }
+ if ($macro =~ m/\\"/) {return;} # Skip troff comments.
+ if (exists($defined_macros{$macro})) {
+ return; # ??? Should examine the macro parameters.
+ }
+ if (exists($safemacros{$macro}) ) {
+ # ??? Check parameter count.
+ # ??? Check that .TH is the first macro (note: bash.1, etc., break this)
+ if ( ($macro eq 'if') || ($macro eq 'ie' )) {
+ # Only permit checking 't' or 'n' for now.
+ if ($parameters =~ m/^[tn]\s/) {
+ $_ = $parameters;
+ s/^[tn]\s+//;
+ process_line(); # Re-examine line without the if statement.
+ } else {
+ problem("unsafe use of if/ie");
+ }
+ # ??? sp: only no-parameter or positive values.
+ } elsif ($macro eq 'de') {
+ $parameters =~ m/^([^\s]+)/;
+ $is_defining = $1;
+ $defined_macros{$is_defining} = 1;
+ } elsif ($macro eq 'so') {
+ $parameters =~ m/^([^\s]+)/;
+ $new_file = $1;
+ while (<$new_file>) { process_line(); }
+ } elsif (($macro eq 'ft') && (defined($parameters))
+ && (! exists($allowed_ft_parameter{$parameters}))) {
+ problem("forbidden ft parameter $parameters");
+ } elsif (($macro eq 'tr') && (defined($parameters))
+ && (! exists($allowed_tr{$parameters}))) {
+ problem("forbidden tr parameter $parameters");
+ }
+ # ??? 'in': Require that every indent be paired with a negative indent.
+ # ??? For macros with text after them, check their text's escapes.
+ } else {
+ problem("unsafe macro $macro");
+ }
+ } else {
+ # ??? Regular text; check escape clauses.
+ }
+}
+
+
+# Main loop: Process files, looking for errors.
+
+clean_state();
+
+while (<>) {
+ if ($ARGV ne $filename) {
+ print "Processing $ARGV; up to now good=$goodfiles bad=$badfiles skip=$skipfiles\n";
+ $filename=$ARGV;
+ }
+ process_line();
+} continue {
+ if (eof) { # End of processing this file.
+ close ARGV; # Perl magic to get line #s to be accurate.
+ $totalerrs += $errs;
+ if ($errs) { $badfiles++ } else {
+ if ($is_skipped) {$skipfiles++} else {$goodfiles++};
+ }
+ $errs = 0;
+ clean_state();
+ }
+}
+
+print "Number of good files = $goodfiles\n";
+print "Number of bad files = $badfiles\n";
+print "Number of skipped files = $skipfiles\n";
+exit $errs;
+
+# ??? Handle .so better (esp. the error messages)
+# currently error messages don't report the traceback & they should.
+
+