2017-06-09 05:11:18 +02:00
|
|
|
#!/bin/bash
|
|
|
|
set +vx
|
|
|
|
[[ $- = *i* ]] && echo "Don't source this script!" && return 1
|
2017-06-09 11:42:45 +02:00
|
|
|
version='0.11'
|
|
|
|
# tldr-lint version 0.11
|
2017-06-09 05:11:18 +02:00
|
|
|
# Linter for new syntax tldr source files
|
|
|
|
# Old syntax files $f can be changed into new syntax files by:
|
|
|
|
# sed -i -e "1s/^# //" -e 's/^- //' -e 's/^`\(.*\)`$/ \1/' "$f"
|
|
|
|
# e=$(sed '1s/./=/g;q' "$f") sed -i "2i$e" "$f"
|
|
|
|
# Part of http://github.com/pepa65/tldr-bash-client
|
|
|
|
# Requirements: coreutils, sed, grep, find
|
|
|
|
|
|
|
|
Help(){ # $1: optional message
|
|
|
|
# Use: self, version
|
|
|
|
cat <<-EOC
|
|
|
|
$B$O $self$X v$version
|
2017-06-09 05:20:10 +02:00
|
|
|
Usage: $self [-h|--help] [-V|--version] [-q|--quiet] [<dir>] [<file>]
|
2017-06-09 05:11:18 +02:00
|
|
|
- All *.md files under <dir> and subdirectories are checked
|
|
|
|
- <file> is checked regardless of extension
|
2017-06-09 05:20:10 +02:00
|
|
|
-q, --quiet: No output means check is OK
|
|
|
|
-V, --version: Output version
|
|
|
|
-h, --help: This help text
|
2017-06-09 05:11:18 +02:00
|
|
|
EOC
|
|
|
|
[[ $1 ]] && echo "$E ERROR:$X $1"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
Flag(){ # $1:message; $2:linenumber (0:not shown, missing:exit)
|
|
|
|
# Use: v, line, W, X, U, XU; Modify: flags
|
|
|
|
((++flags))
|
2017-06-09 11:42:45 +02:00
|
|
|
[[ $2 ]] && (($2)) && echo -e "$W FLAG:$X $1:\n$W$2$X:$U$line$XU" ||
|
2017-06-09 05:11:18 +02:00
|
|
|
echo -e "$W FLAG:$X $1"
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(){ # $1:message $2:OK(0),Not-OK(>0)
|
|
|
|
# Use: quiet, E, O, X
|
|
|
|
if (($2>0))
|
|
|
|
then
|
|
|
|
echo "$E NOT-OK:$X $1"
|
|
|
|
else
|
|
|
|
((quiet)) || echo "$O OK:$X $1"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
Check(){ # $1:filename
|
|
|
|
# Use: quiet, E, O, W, X, U, XU, B, XB; Modify: line, flags
|
2017-06-09 08:47:19 +02:00
|
|
|
local n f lines name len_command check description examples
|
2017-06-09 05:11:18 +02:00
|
|
|
((quiet)) || echo "$B File $O$1$X$XB"
|
|
|
|
IFS= read -rd '' f <"$1"
|
|
|
|
flags=0
|
|
|
|
# general checks
|
|
|
|
lines="$(grep -n $'\r' <<<"$f" |\
|
|
|
|
sed -e "s/^\([^:]*\):/$W\1$X:$U/g" -e "s/\r/$E^M$X/g" -e "s/$/$XU/g")"
|
|
|
|
[[ $lines ]] && Flag "Carriage returns $E^M$X not allowed:\n$lines"
|
|
|
|
lines="$(grep -n $'\t' <<<"$f" |\
|
|
|
|
sed -e "s/^\([^:]*\):/$W\1$X:$U/g" -e "s/\t/$E^T$X/g" -e "s/$/$XU/g")"
|
|
|
|
[[ $lines ]] && Flag "Tabs $E^T$X not allowed:\n$lines"
|
|
|
|
lines="$(grep -n ' $' <<<"$f" |\
|
|
|
|
sed -e "s/^\([^:]*\):/$W\1$X:$U/g" -e "s/$/$XU/g")"
|
2017-06-09 11:42:45 +02:00
|
|
|
[[ $lines ]] && Flag "Trailing spaces $U$E $X$XU not allowed:\n$lines"
|
2017-06-09 05:11:18 +02:00
|
|
|
n=0
|
|
|
|
while true
|
|
|
|
do
|
|
|
|
# header
|
|
|
|
read -r && line=$REPLY && ((++n)) || break
|
|
|
|
check=$flags
|
|
|
|
grep -qn '^[a-zA-Z0-9_]\([-a-zA-Z0-9_ .]*[a-zA-Z0-9_]\)*$' <<<"$line" ||
|
|
|
|
Flag "Command name not well-formed" $n
|
2017-06-09 11:42:45 +02:00
|
|
|
command=${line// /-}
|
2017-06-09 08:47:19 +02:00
|
|
|
name=${1##*/} name=${name%.*}
|
2017-06-09 11:42:45 +02:00
|
|
|
[[ $command = $name ]] ||
|
|
|
|
Flag "Command name different from filename $U$name$XU" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
Ok "Command name" $((flags-check))
|
|
|
|
len_command=${#line}
|
|
|
|
check=$flags
|
|
|
|
read -r && line=$REPLY && ((++n)) || break
|
|
|
|
[[ $line =~ ^=+$ ]] ||
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Only equal signs $E=$X must underline the command" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
((${#line}!=len_command)) &&
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Command $U$command$XU and underline must have the same length" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
Ok "Underline" $((flags-check))
|
|
|
|
read -r && line=$REPLY && ((++n)) || break
|
|
|
|
[[ $line ]] && Flag "Empty line required after underline" $n
|
|
|
|
read -r && line=$REPLY && ((++n)) || break
|
|
|
|
# description
|
|
|
|
check=$flags
|
|
|
|
description=0
|
|
|
|
while [[ ${line:0:2} = '> ' ]]
|
|
|
|
do
|
|
|
|
((++description))
|
|
|
|
[[ ${line:2:1} =~ [a-z] ]] &&
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Not allowed to start a description with lowercase$U a-z$XU" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
[[ ${line:2:1} = ' ' ]] &&
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Only 1 space $U$E $X$XU after the greater-than symbol $E>$X allowed" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
[[ ${line: -1} = '.' ]] ||
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Must have a dot $E.$X at the end of a command description" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
read -r && line=$REPLY && ((++n)) || break 2
|
|
|
|
done
|
2017-06-09 11:42:45 +02:00
|
|
|
((description)) ||
|
|
|
|
Flag "A command description '$U$E> $X---$E.$X$XU' is missing"
|
2017-06-09 05:11:18 +02:00
|
|
|
Ok "$description Command description lines" $((flags-check+!description))
|
|
|
|
# examples
|
|
|
|
check=$flags
|
|
|
|
examples=0
|
|
|
|
while true
|
|
|
|
do
|
|
|
|
[[ $line ]] && Flag "Empty line required before example description" $n ||
|
|
|
|
read -r && line=$REPLY && ((++n)) || break 2
|
|
|
|
[[ ${line:0:1} =~ [a-z] ]] &&
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Not allowed to start an example description with lowercase$U a-z$XU" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
[[ ${line: -1} = ':' ]] ||
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "A colon $E:$X must end each example description" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
((++examples))
|
|
|
|
read -r && line=$REPLY && ((++n)) || break 2
|
|
|
|
[[ $line ]] && Flag "Empty line required after example description" $n ||
|
|
|
|
read -r && line=$REPLY && ((++n)) || break 2
|
|
|
|
[[ ${line:0:4} = ' ' ]] ||
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "Four spaces $E$U $XU$X must precede each example command" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
[[ ${line:4:1} = ' ' ]] &&
|
2017-06-09 11:42:45 +02:00
|
|
|
Flag "No more that 4 spaces $E$U $XU$X before example commands allowed" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
read -r && line=$REPLY && ((++n)) || break 2
|
|
|
|
done
|
|
|
|
done <<<"$f"
|
2017-06-09 11:42:45 +02:00
|
|
|
[[ $line ]] && Flag "Newline required at the end of a page" $n
|
2017-06-09 05:11:18 +02:00
|
|
|
Ok "$examples Examples" $((flags-check+!examples))
|
|
|
|
Ok "$B$1$XB" $flags
|
|
|
|
}
|
|
|
|
|
|
|
|
U=$'\e[4m' XU=$'\e[24m' B=$'\e[1m' XB=$'\e[0m'
|
|
|
|
E=$'\e[31m' W=$'\e[33m' O=$'\e[32m' X=$'\e[39m'
|
|
|
|
self=${0##*/} file='' dir='' line='' flags=0 quiet=0
|
|
|
|
|
|
|
|
(($#>2)) && Help "No more than 2 arguments allowed"
|
|
|
|
while (($#))
|
|
|
|
do
|
|
|
|
case $1 in
|
2017-06-09 05:20:10 +02:00
|
|
|
-V|--version) echo "$version"; exit 0 ;;
|
2017-06-09 05:11:18 +02:00
|
|
|
-h|--help) Help ;;
|
|
|
|
-q|--quiet) quiet=1; shift ;;
|
|
|
|
*) if [[ -f $1 ]]
|
|
|
|
then
|
|
|
|
file=$1
|
|
|
|
elif [[ -d $1 ]]
|
|
|
|
then
|
|
|
|
dir=$1
|
|
|
|
else
|
|
|
|
Help "$U$1$XU is neither a file nor a directory"
|
|
|
|
fi
|
|
|
|
shift ;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
[[ $file || $dir ]] || Help "No valid file or directory given"
|
|
|
|
[[ $file ]] && Check "$file"
|
|
|
|
[[ $dir ]] &&
|
|
|
|
while read -r
|
|
|
|
do
|
|
|
|
Check "$REPLY"
|
|
|
|
done < <(find "$dir" -name '*.md')
|
|
|
|
|
|
|
|
exit 0
|