229 lines
7.0 KiB
Bash
Executable File
229 lines
7.0 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
#########################################################################
|
|
# #
|
|
# OCaml #
|
|
# #
|
|
# Damien Doligez, projet Gallium, INRIA Rocquencourt #
|
|
# #
|
|
# Copyright 2012 Institut National de Recherche en Informatique et #
|
|
# en Automatique. All rights reserved. This file is distributed #
|
|
# under the terms of the Q Public License version 1.0. #
|
|
# #
|
|
#########################################################################
|
|
|
|
# check-typo - Check typographic conventions on OCaml sources.
|
|
|
|
# This program will check files for the following rules:
|
|
|
|
# - absence of TAB characters (tab)
|
|
# - absence of non-ASCII characters (non-ascii)
|
|
# - absence of non-printing ASCII characters (non-printing)
|
|
# - absence of white space at end of line (white-at-eol)
|
|
# - absence of empty lines at end of file (white-at-eof)
|
|
# - presence of a LF character at the end of the file (missing-lf)
|
|
# - maximum line length of 80 characters (long-line)
|
|
# - presence of a copyright header (missing-header)
|
|
# - absence of a leftover "$Id" string (svn-keyword)
|
|
|
|
# Exceptions are handled with a SVN property: "ocaml:typo".
|
|
# Its value for a given file is a comma-separated list of rule names,
|
|
# which lists the rules that should be disabled for this file.
|
|
# The rule names are the ones shown above in parentheses.
|
|
|
|
# Built-in exceptions:
|
|
# - Any binary file (i.e. with svn:mime-type = application/octet-stream)
|
|
# is automatically exempt from all the rules.
|
|
# - Any file whose name matches one of the following patterns is
|
|
# automatically exempt from all rules
|
|
# *.reference
|
|
# */reference
|
|
# - Any file whose name begins with "Makefile" is automatically exempt
|
|
# from the "tabs" rule.
|
|
# - Any file whose name matches one of the following patterns is
|
|
# automatically exempt from the "missing-header" rule.
|
|
# */.depend*
|
|
# */.ignore
|
|
# *.mlpack
|
|
# *.mllib
|
|
# *.mltop
|
|
# *.odocl
|
|
# *.clib
|
|
|
|
# ASCII characters are bytes from 0 to 127. Any other byte is
|
|
# flagged as a non-ASCII character.
|
|
|
|
# For the purpose of this tool, printing ASCII characters are:
|
|
# - the non-white printable ASCII characters (33 to 126)
|
|
# - TAB (09)
|
|
# - LF (10)
|
|
# - SPC (32)
|
|
# Anything else is flagged as a non-printing ASCII character.
|
|
|
|
# This program will recursively explore the files and directories given
|
|
# on the command line (or by default the current directory), and check
|
|
# every file therein for compliance to the rules.
|
|
|
|
# Directories named .svn and _build (and their contents) are always ignored.
|
|
# This program ignores any file that is not under svn control, unless
|
|
# explicitly given on the command line.
|
|
|
|
# If a directory has the SVN property "ocaml:typo" set to "prune",
|
|
# then it and its contents are ignored.
|
|
|
|
# You can ignore a rule by giving the option -<rule> on the command
|
|
# line (before any file names).
|
|
|
|
# Special case for recursive call from the find command (see IGNORE_DIRS).
|
|
case "$1" in
|
|
--check-prune)
|
|
case `svn propget ocaml:typo "$2" 2>/dev/null` in
|
|
prune) echo "INFO: pruned directory $2 (ocaml:typo=prune)" >&2; exit 0;;
|
|
*) exit 3;;
|
|
esac;;
|
|
esac
|
|
|
|
usage () {
|
|
echo "usage: check-typo {-<rule>} [--] {<file-or-dir>}" >&2
|
|
exit 2
|
|
}
|
|
|
|
userrules=''
|
|
|
|
while : ; do
|
|
case "$1" in
|
|
-help|--help) usage;;
|
|
-*) userrules="${1#-},$userrules"; shift;;
|
|
--) shift; break;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
|
|
IGNORE_DIRS="
|
|
-name .svn -prune -o
|
|
-name _build -prune -o
|
|
-type d -exec $0 --check-prune {} ; -prune -o
|
|
"
|
|
|
|
( case $# in
|
|
0) find . $IGNORE_DIRS -type f -print;;
|
|
*) for i in "$@"; do find "$i" $IGNORE_DIRS -type f -print; done;;
|
|
esac
|
|
) | (
|
|
while read f; do
|
|
case `svn status "$f" 2>&1` in
|
|
'?'*) is_svn=false;;
|
|
I*) is_svn=false;;
|
|
svn:*"is not a working copy") is_svn=false;;
|
|
*) is_svn=true;;
|
|
esac
|
|
case "$*" in
|
|
*$f*) is_cmd_line=true;;
|
|
*) is_cmd_line=false;;
|
|
esac
|
|
if $is_svn || $is_cmd_line; then :; else continue; fi
|
|
svnrules=''
|
|
if $is_svn; then
|
|
case `svn propget svn:mime-type "$f"` in
|
|
application/octet-stream) continue;;
|
|
esac
|
|
svnrules=`svn propget ocaml:typo "$f"`
|
|
fi
|
|
rules="$userrules"
|
|
case "$f" in
|
|
Makefile*|*/Makefile*) rules="tab,$rules";;
|
|
esac
|
|
h(){ rules="missing-header,$rules"; }
|
|
case "$f" in
|
|
*/.depend*|*/.ignore) h;;
|
|
*.mlpack|*.mllib|*.mltop|*.odocl|*.itarget|*.clib) h;;
|
|
*.reference|*/reference) continue;;
|
|
esac
|
|
|
|
(cat "$f"; echo) \
|
|
| awk -v rules="$rules" -v svnrules="$svnrules" -v file="$f" \
|
|
'
|
|
function err(name, msg) {
|
|
++ counts[name];
|
|
if (("," rules svnrules ",") !~ ("[, ]" name "[, ]") \
|
|
&& counts[name] <= 10){
|
|
printf ("%s:%d.%d:", file, NR, RSTART + RLENGTH);
|
|
printf (" [%s] %s\n", name, msg);
|
|
if (counts[name] == 10){
|
|
printf ("WARNING: too many [%s] in this file.", name);
|
|
printf (" Others will not be reported.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
match($0, /\t/) {
|
|
err("tab", "TAB character(s)");
|
|
}
|
|
|
|
match($0, /[\200-\377]/) {
|
|
err("non-ascii", "non-ASCII character(s)");
|
|
}
|
|
|
|
match($0, /[^\t\200-\377 -~]/) {
|
|
err("non-printing", "non-printing ASCII character(s)");
|
|
}
|
|
|
|
match($0, /[ \t]+$/) {
|
|
err("white-at-eol", "whitespace at end of line");
|
|
}
|
|
|
|
match($0, /\$Id(: .*)?\$/) {
|
|
err("svn-keyword", "SVN keyword marker");
|
|
}
|
|
|
|
length($0) > 80 {
|
|
RSTART = 81;
|
|
RLENGTH = 0;
|
|
err("long-line", "line is over 80 characters");
|
|
}
|
|
|
|
3 <= NR && NR <= 5 \
|
|
&& (/ OCaml / || / ocamlbuild / || / OCamldoc /) {
|
|
header_ocaml = NR;
|
|
}
|
|
|
|
header_ocaml && header_ocaml + 4 <= NR && NR <= header_ocaml + 6 \
|
|
&& / Copyright / {
|
|
header_copyright = 1;
|
|
}
|
|
|
|
{
|
|
prev_line = last_line;
|
|
last_line = $0;
|
|
}
|
|
|
|
END {
|
|
if (match(last_line, /.+/)){
|
|
err("missing-lf", "missing linefeed at EOF");
|
|
prev_line = last_line;
|
|
++ NR;
|
|
empty_file = 0;
|
|
}else{
|
|
empty_file = NR == 1;
|
|
}
|
|
if (!empty_file && match(prev_line, /^$/)){
|
|
err("white-at-eof", "empty line(s) at EOF");
|
|
}
|
|
NR = 1;
|
|
RSTART = 1;
|
|
RLENGTH = 0;
|
|
if (!(header_ocaml && header_copyright)){
|
|
err("missing-header", "missing copyright header");
|
|
}
|
|
split(svnrules, r, "[, ]");
|
|
for (i in r){
|
|
name = r[i];
|
|
if (name != "" && !counts[name]){
|
|
err("unused-prop", sprintf("unused [%s] in ocaml:typo", name));
|
|
}
|
|
}
|
|
}
|
|
'
|
|
done
|
|
)
|