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 customize screenshot +# 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