summaryrefslogtreecommitdiff
path: root/textproc/mdoclint
diff options
context:
space:
mode:
authorwiz <wiz>2009-03-01 21:25:39 +0000
committerwiz <wiz>2009-03-01 21:25:39 +0000
commit5b6c857905717cc09eb1683e994e275c86142631 (patch)
treebc1b2cf846e474070f436bd99158a24640b19188 /textproc/mdoclint
parent8ad8f4918d1bb60a50c8d8a2d3d879d7f2d95cd4 (diff)
downloadpkgsrc-5b6c857905717cc09eb1683e994e275c86142631.tar.gz
Initial import of mdoclint-1.0:
mdoclint is a tool to verify that man pages follow the conventions for NetBSD (and most other BSDs) regarding structure and macro usage.
Diffstat (limited to 'textproc/mdoclint')
-rw-r--r--textproc/mdoclint/DESCR3
-rw-r--r--textproc/mdoclint/Makefile35
-rw-r--r--textproc/mdoclint/PLIST3
-rwxr-xr-xtextproc/mdoclint/files/TODO24
-rwxr-xr-xtextproc/mdoclint/files/mdoclint589
-rwxr-xr-xtextproc/mdoclint/files/mdoclint.1157
6 files changed, 811 insertions, 0 deletions
diff --git a/textproc/mdoclint/DESCR b/textproc/mdoclint/DESCR
new file mode 100644
index 00000000000..a9b84e75b7e
--- /dev/null
+++ b/textproc/mdoclint/DESCR
@@ -0,0 +1,3 @@
+mdoclint is a tool to verify that man pages follow the conventions
+for NetBSD (and most other BSDs) regarding structure and macro
+usage.
diff --git a/textproc/mdoclint/Makefile b/textproc/mdoclint/Makefile
new file mode 100644
index 00000000000..e6a0a56e4e7
--- /dev/null
+++ b/textproc/mdoclint/Makefile
@@ -0,0 +1,35 @@
+# $NetBSD: Makefile,v 1.1.1.1 2009/03/01 21:25:39 wiz Exp $
+
+DISTNAME= mdoclint-1.0
+CATEGORIES= textproc
+MASTER_SITES= # none
+DISTFILES= # none
+
+MAINTAINER= wiz@NetBSD.org
+#HOMEPAGE= #
+COMMENT= Tool for verifying man pages
+
+PKG_DESTDIR_SUPPORT= user-destdir
+INSTALLATION_DIRS= bin ${PKGMANDIR}/man1
+
+WRKSRC= ${WRKDIR}
+NO_CHECKSUM= yes
+NO_BUILD= yes
+USE_LANGUAGES= # none
+USE_TOOLS+= perl:run
+
+SUBST_CLASSES+= perl
+SUBST_STAGE.perl= do-configure
+SUBST_FILES.perl= mdoclint
+SUBST_SED.perl+= -e 's,@PERL5@,${PERL5},g'
+SUBST_MESSAGE.perl= Fixing path to perl.
+
+do-extract:
+ cd ${FILESDIR} && cp mdoclint* ${WRKSRC}/
+
+do-install:
+ ${INSTALL_SCRIPT} ${WRKSRC}/mdoclint ${DESTDIR}${PREFIX}/bin
+ ${INSTALL_MAN} ${FILESDIR}/mdoclint.1 \
+ ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1
+
+.include "../../mk/bsd.pkg.mk"
diff --git a/textproc/mdoclint/PLIST b/textproc/mdoclint/PLIST
new file mode 100644
index 00000000000..429ae76fe56
--- /dev/null
+++ b/textproc/mdoclint/PLIST
@@ -0,0 +1,3 @@
+@comment $NetBSD: PLIST,v 1.1.1.1 2009/03/01 21:25:39 wiz Exp $
+bin/mdoclint
+man/man1/mdoclint.1
diff --git a/textproc/mdoclint/files/TODO b/textproc/mdoclint/files/TODO
new file mode 100755
index 00000000000..c53b319940d
--- /dev/null
+++ b/textproc/mdoclint/files/TODO
@@ -0,0 +1,24 @@
+# $NetBSD: TODO,v 1.1.1.1 2009/03/01 21:25:39 wiz Exp $
+. check for font changes in .Bd -literal (.Fd, .No, ...)
+. add checks for only allowed values after .Bl/.Bd/...
+. compare internal with external section name
+. complain if NULL is used without .Dv
+. complain if error values are used without .Er
+[. Nm doesn't need an argument, most of the time (?)]
+. .Nm "": remove if not needed
+. count correct matching of .Bd/.Ed, .Rs/.Re and similar
+. check AUTHORS sections for usage of .An name Aq email@host
+. recognize more section headers?
+. check .Rs/.Re contents?
+. empty lines in .Bd -literal ignored -- groff ignores them, too...
+. ignore .Bd -literal for most checks (.Nm/.Xr, new sentence, new line, ...)
+. ignore matching braces in macro arguments
+. fix bogus report if no .Xr, but .Rs in SEE ALSO section
+. recognize which sections should be in a particular type of manpage
+ (like RETURN VALUES in 2&3, EXIT STATUS in 1&8 et al.)
+. check paths pointed at by .Pa directives?
+. sort in WARNS and ERRS and add switches for those two
+. complain about `,', `;' and similar on beginning of line
+. complain about ',' after last .Nm entry
+. complain about NULL without .Dv
+. complain about -1 (not \-1)
diff --git a/textproc/mdoclint/files/mdoclint b/textproc/mdoclint/files/mdoclint
new file mode 100755
index 00000000000..304618ab7ba
--- /dev/null
+++ b/textproc/mdoclint/files/mdoclint
@@ -0,0 +1,589 @@
+#!@PERL5@
+#
+# $OpenBSD: mdoclint,v 1.13 2008/12/31 10:55:11 espie Exp $
+# $NetBSD: mdoclint,v 1.1.1.1 2009/03/01 21:25:39 wiz Exp $
+#
+# Copyright (c) 2001-2009 Thomas Klausner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR, THOMAS KLAUSNER,
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+use strict;
+use warnings;
+
+$| = 1;
+
+package Parser;
+use Getopt::Std;
+
+use constant {
+ OPENBSD => 0,
+ NETBSD => 1
+};
+
+use vars qw(
+ $opt_a $opt_D $opt_d $opt_e $opt_F $opt_f $opt_H $opt_h $opt_m
+ $opt_n $opt_o $opt_P $opt_p $opt_r $opt_S $opt_s $opt_v
+ $opt_X $opt_x
+);
+
+
+my $arch=`uname -m`;
+chomp($arch);
+my $options="aDdeFfHhmnoPprSsvXx";
+
+sub usage
+ {
+ my $default = OPENBSD ? "-aDdfHmnoPprSsXx" : "-aDdefHmnoPprSsXx";
+
+ print STDERR <<"EOF";
+mdoclint: verify man page correctness
+usage: mdoclint [-$options] file ...
+ -a warn about SEE ALSO section problems
+ -D warn about bad casing and archs in .Dt
+ -d warn about bad date strings (in .Dd only)
+ -e warn about unsorted errors (for functions)
+ -F fix whitespace problems (asks before overwriting)
+ -f warn about possible incorrect .Fn syntax
+ -H warn about characters that produce problems in HTML output
+ -h display this help text
+ -m warn about man pages that are not in mdoc(7) format
+ -n warn about .Nd's ending in '.'
+ -o warn about non-empty .Os strings
+ -P warn about paragraph problems
+ -p warn about punctuation problems
+ -r warn about missing RCS Id
+ -S warn about any .Sh weirdness
+ -s warn about whitespace problems
+ -v verbose output
+ -X warn about explicit mentions of FreeBSD, NetBSD, or OpenBSD
+ -x warn about cross-references with missing targets
+Default is $default if no flag is specified.
+EOF
+ exit(0);
+}
+
+
+my %short = (
+ "Free" => ".Fx",
+ "Net" => ".Nx",
+ "Open" => ".Ox"
+);
+
+# constants to build
+my %sections;
+my $arches_re;
+my $sections_re;
+my $esections_re;
+my $valid_date_re;
+# and the code that builds them
+{
+ my @sections = (
+ "NAME",
+ NETBSD ? "LIBRARY" : undef,
+ "SYNOPSIS",
+ "DESCRIPTION",
+ NETBSD ? "EXIT STATUS" : undef,
+ "RETURN VALUES",
+ "ENVIRONMENT",
+ "FILES",
+ "EXAMPLES",
+ "DIAGNOSTICS",
+ "ERRORS",
+ "SEE ALSO",
+ "STANDARDS",
+ "HISTORY",
+ "AUTHORS",
+ "CAVEATS",
+ "BUGS",
+ NETBSD ? "SECURITY CONSIDERATIONS" : undef
+ );
+
+ my $i = 1;
+ for my $sh (@sections) {
+ if (defined $sh) {
+ $sections{$sh} = $i++;
+ }
+ }
+ my @arches;
+ if (OPENBSD) {
+ @arches =
+ (qw(alpha amd64 arm armish aviion cats hp300 hppa
+ hppa64 i386 landisk luna88k mac68k macppc mvme68k
+ mvme88k sgi socppc sparc sparc64 vax zaurus));
+ }
+ if (NETBSD) {
+ @arches =
+ (qw(acorn26 acorn32 algor alpha amiga arc atari
+ bebox cats cesfic cobalt dreamcast evbarm evbmips
+ evbsh3 evbsh5 hp300 hp700 hpcarm hpcmips hpcsh
+ i386 ibmnws luna68k mac68k macppc mipsco mmeye
+ mvme68k mvmeppc netwinder news68k newsmips next68k
+ pc532 playstation2 pmax pmppc prep sandpoint sbmips
+ sgimips shark sparc sparc64 sun2 sun3 vax walnut
+ x68k x86_64));
+ }
+ my $a = join('|', @arches);
+ $arches_re = qr{(?:$a)}o;
+ if (OPENBSD) {
+ $sections_re = qr{(?:3p|[1-9])}o;
+ $esections_re = qr{(?:3p|[0-9])}o;
+ }
+ if (NETBSD) {
+ $sections_re = qr{[1-9]}o;
+ $esections_re = qr{[0-9]}o;
+ }
+ if (OPENBSD) {
+ $valid_date_re = qr{\$Mdocdate\b};
+ }
+ if (NETBSD) {
+ $valid_date_re = qr{(?:January|February|March|April|May|June|July|August|September|October|November|December)\s*[1-9][0-9]*,\s*(?:198[0-9]|199[0-9]|200[012345678])$}o;
+ }
+}
+
+sub debug
+{
+ my $self = shift;
+ print STDOUT "debug: $self->{fn}:$self->{ln}: @_\n" if $opt_v;
+}
+
+sub warning
+{
+ my $self = shift;
+ print STDOUT "$self->{fn}:$self->{current_section_header}:$self->{ln}: ", join('', @_), "\n";
+}
+
+sub handle_options
+{
+ getopts($options);
+ $opt_h and usage();
+
+ # default to all warnings if no flag is set
+ unless ($opt_a or $opt_D or $opt_d or $opt_e or $opt_f or $opt_H
+ or $opt_m or $opt_n or $opt_o or $opt_P or $opt_p or $opt_r
+ or $opt_S or $opt_s or $opt_X or $opt_x) {
+ $opt_a = $opt_D = $opt_d = $opt_f = $opt_H = $opt_m =
+ $opt_n = $opt_o = $opt_P = $opt_p = $opt_r = $opt_S =
+ $opt_s = $opt_X = $opt_x = 1;
+ $opt_e = 1 if NETBSD;
+ }
+}
+
+
+sub verify_xref
+{
+ my ($self, $page, $section, $pre, $post) = @_;
+ if ("$page.$section" eq $self->{fn}) {
+ $self->warning("Xref to itself (use .Nm instead)");
+ }
+ # try to find corresponding man page
+ for my $dir ("/usr/share/man",
+ OPENBSD ? "/usr/X11R6/man" : "/usr/X11R7/man") {
+ for my $a ("", $arch) {
+ for my $page ("cat$section/$a/$page.0",
+ "man$section/$a/$page.$section") {
+ return 1 if -f "$dir/$page";
+ }
+ }
+ }
+ return 1 if -f "./$page.$section";
+
+ $self->warning($pre."trailing Xref to $page($section)$post");
+ return 0;
+}
+
+sub new
+{
+ my ($class, $fn) = @_;
+
+ my $o = {
+ mandoc_p => 1,
+ all => '',
+ lastline => '',
+ changes => 0,
+ oxrcsidseen => 0,
+ nxrcsidseen => 0,
+ lastsh => 0,
+ sasection => 0,
+ saname => '',
+ sarest => ',',
+ insa => 0,
+ inliteral => 0,
+ shseen => {},
+ last_error_name => '',
+ current_section_header => '',
+ fn => $fn
+ };
+ open my $input, '<', $fn or die "can't open input file $fn";
+ $o->{file} = $input;
+ $o->{ln} = 0;
+ bless $o, $class;
+}
+
+sub next_line
+{
+ my ($self) = @_;
+
+ my $l = readline($self->{file});
+ if (defined $l) {
+ $self->{ln}++;
+ }
+ return $l;
+}
+
+sub close
+{
+ my ($self) = @_;
+
+ close($self->{file});
+}
+
+sub parse_macro_args
+{
+ my ($s, $string) = @_;
+ my $_ = $string;
+ my @params = ();
+ while (!/^$/) {
+ if (s/^\"(.*?)\"\s*//) {
+ push(@params, $1);
+ } elsif (s/^(\S+)\s*//) {
+ push(@params, $1);
+ }
+ }
+ if (@params > 9 and OPENBSD) {
+ $s->warning("$string holds >9 parameters");
+ }
+ return @params;
+}
+
+sub set_section_header
+{
+ my ($s, $section_header) = @_;
+ $section_header = join(' ', $s->parse_macro_args($section_header));
+ if ($section_header eq 'SEE ALSO') {
+ $s->{insa} = 1;
+ } elsif ($s->{insa} == 1) {
+ if (not $s->{sarest} eq "") {
+ $s->warning("unneeded characters at end of ",
+ "SEE ALSO: ", "`$s->{sarest}'") if $opt_a;
+ # to avoid a second warning at EOF
+ $s->{sarest} = "";
+ }
+ # finished SEE ALSO section
+ $s->{insa} = 2;
+ }
+ if ($opt_S) {
+ if (not $sections{$section_header}) {
+ $s->warning("unknown section header: ",
+ "`$section_header'");
+ } else {
+ if ($s->{lastsh} >= $sections{$section_header}) {
+ $s->warning("section header ",
+ "`$section_header' in wrong order");
+ }
+ $s->{shseen}->{$section_header} = 1;
+ $s->{lastsh} = $sections{$section_header};
+ }
+ }
+ if ($s->{lastline} =~ /^\.Pp/o) {
+ $s->warning("Paragraph problem: section header after .Pp");
+ }
+
+ $s->{current_section_header} = $section_header;
+}
+
+sub process_line
+{
+ my ($s, $_) = @_;
+ chomp;
+ # always cut trailing spaces
+ if (/\s+$/o) {
+ $s->warning("trailing space: `$_'") if $opt_s;
+ s/\s+$//o;
+ $s->{changes} = 1;
+ }
+ if (/\$OpenBSD\b.*\$/o) {
+ $s->{oxrcsidseen} = 1;
+ # nothing else to do
+ return;
+ }
+ if (/\$NetBSD\b.*\$/o) {
+ $s->{nxrcsidseen} = 1;
+ # nothing else to do
+ return;
+ }
+ # comments
+ if (/^\.\\\"/) {
+ return;
+ }
+ if (/^\.TH\s+/o) {
+ $s->warning("not mandoc") if $opt_m;
+ $s->{mandoc_p} = 0;
+# /^.TH\s*[\w-_".]+\s*([1-9])/;
+# $section = $1;
+ return;
+ }
+# if (/^.Dt\s*[\w-_".]+\s*([1-9])/) {
+# $section = $1;
+# }
+ if ($opt_D and /^\.Dt\s+/o) {
+ if (! /^\.Dt\s+(?:[A-Z\d._-]+)\s+$sections_re(?:\s+$arches_re)?$/o) {
+ $s->warning("bad .Dt: `$_'");
+ }
+ }
+
+ if ($s->{mandoc_p}) {
+ if (/^\.Sh\s+(.*)$/o) {
+ $s->set_section_header($1);
+ return;
+ }
+ } else {
+ if (/^\.SH\s+(.*)$/o) {
+ $s->set_section_header($1);
+ return;
+ }
+ }
+
+ if ($opt_a) {
+ if ($s->{insa} == 1) {
+ if (/^\.Xr\s+(\S+)\s+($sections_re)\s?(.*)?$/o) {
+ my ($saname, $sasection, $sarest) = ($1, $2, $3);
+ $saname =~ s/^\\&//o;
+ if ($s->{sasection} gt $sasection
+ or ($s->{sasection} eq $sasection and
+ ($s->{saname} cmp $saname) > 0)) {
+ $s->warning("SEE ALSO: `.Xr $s->{saname} $s->{sasection}' should "
+ . "be after `.Xr $saname $sasection'");
+ }
+ if ($s->{sarest} ne ",") {
+ $s->warning("SEE ALSO: .Xr not separated by ".
+ "comma, but `$s->{sarest}'");
+ }
+ $s->{saname} = $saname;
+ $s->{sasection} = $sasection;
+ $s->{sarest} = $sarest;
+ }
+ if (/^\.Rs(?:\s+|$)/o) {
+ if ($s->{sarest} ne "") {
+ $s->warning("SEE ALSO: Not necessary to separate".
+ " .Xr from .Rs by `$s->{sarest}'");
+ }
+ $s->{sarest} = "";
+ }
+ }
+ }
+
+ if ($opt_f and /^\.Fn.*,.+/o) {
+ $s->warning("possible .Fn misuse: `$_'");
+ }
+ if (OPENBSD) {
+ if ($opt_H and (/^(?:[<>])/o or /[^\\][<>]/o)) {
+ $s->warning("use \*(Lt \*(Gt (or .Aq) ",
+ "instead of < >: `$_'");
+ }
+ }
+ if (NETBSD) {
+ if ($opt_H and (/^(?:[<>&])/o or /[^\\][<>&]/o)) {
+ $s->warning("use \*[Lt] \*[Gt] (or .Aq) \*[Am] ",
+ "instead of < > &: `$_'");
+ }
+ }
+
+ if ($opt_X) {
+ if (/\b(Free|Net|Open)BSD\b/o
+ and not /\b(?:www|ftp)\.(?:Free|Net|Open)BSD\.org\b/o
+ and not /\bOpenBSD\::.*3p\b/o
+ and not /\/pub\/OpenBSD\//o
+ and not /\@(?:Free|Net|Open)BSD\.(?i:org)\b/o) {
+ $s->warning("verbose mention of `$1BSD' instead of "
+ . "`$short{$1}': `$_'");
+ }
+ if (/^\./o and (/Bx (Open)/o or /Bx (Free)/o or /Bx (Net)/o)) {
+ $s->warning("`.Bx $1' found -- use $short{$1} instead");
+ }
+ }
+ if ($opt_o) {
+ if (/^\.Os\s+(.+)/o) {
+ $s->warning(".Os used with argument `$1'");
+ }
+ }
+
+ if ($opt_n) {
+ if (/^\.Nd.*\.$/o) {
+ $s->warning(".Nd ends with a dot: `$_'");
+ }
+ }
+ if ($opt_p) {
+ if (/\w\w\.\s+[A-Z]/o) {
+ $s->warning("new sentence, new line: `$_'");
+ }
+ if (/^\... .*[^\s][\.();,\[\]\{\}:]$/o
+ and not /\s\.\.\.$/o and not /\\&.$/o) {
+ $s->warning("punctuation in format string ",
+ "without space: `$_'");
+ }
+ if (/^\./o and /Ns [\.();,\[\]\{\}:]/o) {
+ $s->warning("possible Ns abuse: `$_'");
+ }
+ if (/(\w+)\(\)/o) {
+ $s->warning("use .Fn or .Xr for functions: `$1()'");
+ }
+ }
+ if ($opt_x) {
+ if ($s->{mandoc_p}) {
+ my $destruct = $_;
+ $destruct =~ s/\\\&([\w\.])/$1/o;
+ if ($destruct =~ /^\.Xr\s+([\w\:\.\-\+\/]+)\s+($esections_re)(.*)/o) {
+ $s->debug("Xref to $1($2) found: `$_'");
+ $s->verify_xref($1, $2, "", "");
+ if ($3 =~ /^\S/o) {
+ $s->warning("No space after section number in Xref: `$_'");
+ }
+ } elsif ($destruct =~ /^\.Xr/o) {
+ $s->warning("Weird Xref found: `$_'");
+ }
+ } else {
+ my $destruct = $_;
+ $destruct =~ s/\\f.//go;
+ if ($destruct !~ /^\.\\\"/o) {
+ while ($destruct =~ s/([-\w.]+)\s*\(($esections_re)\)//o) {
+ $s->debug("possible Xref to $1($2) found: `$_'");
+ $s->verify_xref($1, $2, "possible ", ": `$_'");
+ # so that we have a chance to find more than one
+ # per line
+ $destruct =~ s/(\w+)\s*\(($sections_re)\)//o;
+ }
+ }
+ }
+ }
+ if ($opt_d) {
+ if (/^\.Dd/o and not /^\.Dd\s+$valid_date_re/o) {
+ $s->warning("Invalid date found: `$_'");
+ }
+ }
+
+ if ($opt_P) {
+ if (/^\.Bd\b.*-literal\b/o) {
+ $s->{inliteral} = 1;
+ }
+ if ($s->{inliteral} == 1) {
+ if (/^\.Ed\b/o) {
+ $s->{inliteral} = 0;
+ }
+ } elsif (/^$/o) {
+ $s->warning("Paragraph problem: empty line -- ",
+ "use .Pp for paragraphs");
+ }
+ if ($s->{lastline} =~ /^\.Pp/o and /^(\.Ss|\.Pp)/o) {
+ $s->warning("Paragraph problem: $1 after .Pp");
+ }
+ if (/^\.Pp/o and $s->{lastline} =~ /^(\.S[Ssh])/o) {
+ $s->warning("Paragraph problem: .Pp after $1");
+ }
+ }
+
+ # Check whether the list of possible errors for a function is
+ # sorted alphabetically.
+ #
+ if ($opt_e) {
+ # Error names should not be sorted across different lists.
+ # (see bind(2) for an example.)
+ #
+ /^\.Bl\s+/o and $s->{last_error_name} = "";
+
+ if ($s->{current_section_header} eq "ERRORS" and
+ /^\.It\s+Bq\s+Er\s+(E[\w_]+)$/o) {
+ my $current_error_name = $1;
+
+ if ($s->{last_error_name} eq $current_error_name) {
+ $s->warning("Duplicate item for "
+ . "$current_error_name.");
+ } elsif ($current_error_name lt $s->{last_error_name}) {
+ $s->warning("$s->{last_error_name} and "
+ . "$current_error_name are not in "
+ . "alphabetical order.");
+ }
+ $s->{last_error_name} = $current_error_name;
+ }
+ }
+
+ $s->{lastline} = $_;
+ $s->{all} .= "$_\n";
+}
+
+sub finish
+{
+ my ($s) = @_;
+
+ if (NETBSD and not $s->{nxrcsidseen}) {
+ $s->warning("Missing RCS Id") if $opt_r;
+ }
+ if (OPENBSD and not $s->{oxrcsidseen}) {
+ $s->warning("Missing RCS Id") if $opt_r;
+ }
+
+ if ($opt_P and $s->{lastline} =~ /^\.Pp/o) {
+ $s->warning("Paragraph problem: .Pp at EOF");
+ }
+
+ if ($opt_a and $s->{insa} > 0 and not $s->{sarest} eq "") {
+ $s->warning("unneeded characters at end of SEE ALSO: `$s->{sarest}'");
+ }
+
+# if (not ($fn =~ /$section$/)) {
+# $s->warning("section doesn't match (internal value: $section)");
+# }
+ if ($s->{mandoc_p} and $opt_S) {
+ foreach my $i (qw (NAME SYNOPSIS DESCRIPTION)) {
+ if (not ($s->{shseen}{$i})) {
+ $s->warning("missing $i section");
+ }
+ }
+ }
+}
+
+package main;
+
+sub handle_file
+{
+ my $parser = Parser->new($_[0]);
+
+ while (my $_ = $parser->next_line) {
+ $parser->process_line($_);
+ }
+
+ $parser->finish;
+ $parser->close;
+ if ($Parser::opt_F and $parser->{changes}) {
+ open OUT, ">$_[0].new" or
+ die "can't open output file `$_[0].new'";
+ print OUT $parser->{all};
+ close OUT;
+ system("mv -i $_[0].new $_[0]");
+ }
+}
+
+Parser->handle_options;
+foreach my $file (@ARGV) {
+ handle_file($file);
+}
diff --git a/textproc/mdoclint/files/mdoclint.1 b/textproc/mdoclint/files/mdoclint.1
new file mode 100755
index 00000000000..0c51b88aaf8
--- /dev/null
+++ b/textproc/mdoclint/files/mdoclint.1
@@ -0,0 +1,157 @@
+.\" $OpenBSD: mdoclint.1,v 1.5 2008/11/23 17:07:36 jmc Exp $
+.\" $NetBSD: mdoclint.1,v 1.1.1.1 2009/03/01 21:25:39 wiz Exp $
+.\"
+.\" Copyright (c) 2001-2008 Thomas Klausner
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR, THOMAS KLAUSNER,
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd November 21, 2008
+.Dt MDOCLINT 1
+.Os
+.Sh NAME
+.Nm mdoclint
+.Nd man page verifier
+.Sh SYNOPSIS
+.Nm
+.Op Fl aDdeFfHhmnoPprSsvXx
+.Ar
+.Sh DESCRIPTION
+.Nm
+is a man page verifier.
+It tries to automatically find as many common
+errors that occur when writing man pages as possible.
+If no flags are given,
+.Fl aDdefHmnoPprSsXx
+is assumed (that is, everything except
+.Fl Fhv ) .
+.Pp
+The options are as follows:
+.Bl -tag -width xxxx -compact
+.It Fl a
+Warn about some possible problems in the
+.Sx SEE ALSO
+section, like incorrect order (correct order: first by section
+numbers, then by name), or incorrect or superfluous punctuation
+between or after the cross-references.
+.It Fl D
+Warn about bad casing and architectures in the .Dt macro.
+.It Fl d
+Warn about bad date strings (in the .Dd macro).
+.It Fl e
+Warn about unsorted errors (for functions).
+.It Fl F
+Fix whitespace problems (see also
+.Fl s ) .
+.It Fl f
+Warn about possible .Fn abuse; its arguments should be put in
+.Sq \&"
+separately, not together and separated by commas.
+Those will be automatically added by mdoc.
+.It Fl H
+Show warnings for characters that might generate problems in
+HTML output:
+.Sq \*[Lt] ,
+.Sq \*[Gt] ,
+and
+.Sq \*[Am] .
+Replace a pair of angle quotes with the .Aq macro.
+Otherwise, the replacements are
+.Dq \e*[Lt] ,
+.Dq \e*[Gt] ,
+and
+.Dq \e*[Am] .
+.It Fl h
+Display usage.
+.It Fl m
+Warn if man page is not in
+.Xr mdoc 7
+format.
+.It Fl n
+Warn when the .Nd macro's argument ends in a dot, that is
+.Sq \&. .
+.It Fl o
+Warn when the .Os macro has an argument (it shouldn't have one at
+least in the base system, because on
+.Nx
+the current version is default).
+.It Fl P
+Warn about paragraph problems, like empty lines or .Pp macros before
+section macros like .Ss and .Sh.
+.It Fl p
+Warn about possible punctuation problems at the end of macro arguments,
+abuse of .Ns to get punctuation directly next to a word,
+and sentences not starting on a new line.
+.It Fl r
+Warn about missing RCS Id.
+.It Fl S
+Warn about any unknown sections or about a section that comes in the
+wrong order (see
+.Xr mdoc 7 ) .
+.It Fl s
+Warn about superfluous whitespace at the end of line.
+.It Fl v
+Verbose output.
+.It Fl X
+Warn about explicit mentions of the words
+.Dq FreeBSD ,
+.Dq NetBSD ,
+and
+.Dq OpenBSD ,
+which should be replaced by .Fx, .Nx, and .Ox respectively.
+Also notices occurrences of
+.Dq \&.Bx Free ,
+.Dq \&.Bx Net ,
+and
+.Dq \&.Bx Open ,
+for which the same applies.
+.It Fl x
+Warn about cross-references whose target is missing, cross-references
+to itself, or plain bogus cross-references.
+.Pp
+For
+.Dq .Xr name X ,
+the following files are checked:
+.Pa /usr/share/man/manX/name.X ,
+.Pa /usr/share/man/manX/`uname -m`/name.X ,
+and
+.Pa ./name.X .
+.El
+.Sh SEE ALSO
+.Xr mdoc 7 ,
+.Xr mdoc.samples 7
+.Sh AUTHORS
+.An Thomas Klausner
+.Aq wiz@netbsd.org
+.Sh BUGS
+The
+.Fl o
+and
+.Fl p
+flags currently produce too many bogus warnings.
+.Pp
+The
+.Fl x
+flag sometimes erroneously warns about xrefs to man pages for
+machine-dependent drivers that are not for the architecture
+.Nm
+is running on.