diff --git a/README.md b/README.md
index ca7d3b9..7945023 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,22 @@ Alternative location of pages cache
+# tldr-lint
+
+### Linter for new syntax tldr source files
+
+```
+Usage: tldr-lint [-h|--help] [-q|--quiet] [
] []
+ - All *.md files under and subdirectories are checked
+ - is checked regardless of extension
+ No output means OK, unless -v or --verbose is given
+ -q, --quiet: No output means check is OK
+ -h, --help: This help text
+```
+
+### Prerequisites
+coreutils, sed, grep, find
+
## Contributing
Please file an issue for a question, a bug or a feature request.
diff --git a/tldr-lint b/tldr-lint
new file mode 100755
index 0000000..e13825b
--- /dev/null
+++ b/tldr-lint
@@ -0,0 +1,154 @@
+#!/bin/bash
+set +vx
+[[ $- = *i* ]] && echo "Don't source this script!" && return 1
+version='0.33'
+# tldr-lint version 0.33
+# 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
+ Usage: $self [-h|--help] [-q|--quiet] [] []
+ - All *.md files under and subdirectories are checked
+ - is checked regardless of extension
+ -q, --quiet: No output means check is OK
+ -h, --help: This help text
+ 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))
+ [[ $2 ]] && (($2)) && echo -e "$W FLAG:$X $1\n$W$2$X:$U$line$XU" ||
+ 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
+ local n f lines len_command check description examples
+ ((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")"
+ [[ $lines ]] && Flag "Trailing spaces not allowed:\n$lines"
+ 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
+ Ok "Command name" $((flags-check))
+ len_command=${#line}
+ check=$flags
+ read -r && line=$REPLY && ((++n)) || break
+ [[ $line =~ ^=+$ ]] ||
+ Flag "Command must be underlined by equal signs: $E=$X" $n
+ ((${#line}!=len_command)) &&
+ Flag "Underline must have the same length as command" $n
+ 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] ]] &&
+ Flag "No lowercase allowed for the start of a description" $n
+ [[ ${line:2:1} = ' ' ]] &&
+ Flag "Only 1 space after the greater-than symbol $E>$X allowed" $n
+ [[ ${line: -1} = '.' ]] ||
+ Flag "Command description must end with a dot $E.$X" $n
+ read -r && line=$REPLY && ((++n)) || break 2
+ done
+ ((description)) || Flag "Command description '$U$E> $X---$E.$X$XU' missing"
+ 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] ]] &&
+ Flag "No lowercase allowed for the start of an example description" $n
+ [[ ${line: -1} = ':' ]] ||
+ Flag "Example description must end in colon $E:$X" $n
+ ((++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} = ' ' ]] ||
+ Flag "Example command must be preceded by 4 spaces $E$U $XU$X" $n
+ [[ ${line:4:1} = ' ' ]] &&
+ Flag "No more that 4 spaces $E$U $XU$X before example command allowed" $n
+ read -r && line=$REPLY && ((++n)) || break 2
+ done
+ done <<<"$f"
+ [[ $line ]] && Flag "Newline required at the end" $n
+ 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
+ -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