#!/usr/bin/env bash # TODO: # - inline replace # - clang-format-diff replacement # - uncrustify for patches (not git refs) # - maybe integrate into travis-ci? function usage() { cat <&2 exit 1 } function default_arg() { if [ "${options[ref]}" -eq 1 ]; then echo "HEAD" else [ ! -t 0 ] || abort " is a tty" echo "/dev/stdin" fi } function parse_options() { options[patch]=0 options[file]=0 options[file_diff]=0 options[ref]=1 options[clang]=1 options[uncrustify]=0 options[cfg]= local OPTARG OPTIND c while getopts "pfrdCUc:h?" c; do case "$c" in p) options[patch]=1 options[ref]=0 options[file]=0 options[file_diff]=0 ;; f) options[file]=1 options[ref]=0 options[patch]=0 options[file_diff]=0 ;; r) options[ref]=1 options[file]=0 options[patch]=0 options[file_diff]=0 ;; d) options[file_diff]=1 options[file]=0 options[patch]=0 options[ref]=0 ;; C) options[clang]=1 options[uncrustify]=0 ;; U) options[uncrustify]=1 options[clang]=0 ;; c) options[cfg]="$OPTIND" ;; ?|h) usage exit 0 ;; *) usage exit 1 ;; esac done options[cfg]="$(cfg)" [ -f "${options[cfg]}" ] || \ abort "Config '%s' does not exist" "${options[cfg]}" shift $((OPTIND - 1)) args=( "$@" ) if [ ${#args[@]} -eq 0 ]; then # exit on error globally, not only in subshell default_arg > /dev/null args=( "$(default_arg)" ) fi if [ "${args[0]}" = "/dev/stdin" ]; then TMP_FILE="/tmp/libevent.checkpatch.$RANDOM" cat > "$TMP_FILE" trap "rm '$TMP_FILE'" EXIT args[0]="$TMP_FILE" fi } function diff() { command diff --color=always "$@"; } function clang_style() { local c="${options[cfg]}" echo "{ $(sed -e 's/#.*//' -e '/---/d' -e '/\.\.\./d' "$c" | tr $'\n' ,) }" } function clang_format() { clang-format -style="$(clang_style)" "$@"; } function clang_format_diff() { cat "$@" | clang-format-diff -p1 -style="$(clang_style)"; } # for non-bare repo will work function clang_format_git() { git format-patch --stdout "$@" -1 | clang_format_diff; } function uncrustify() { command uncrustify -c "${options[cfg]}" "$@"; } function uncrustify_frag() { uncrustify -l C --frag "$@"; } function uncrustify_indent_off() { echo '/* *INDENT-OFF* */'; } function uncrustify_indent_on() { echo '/* *INDENT-ON* */'; } function git_hunk() { local ref=$1 f=$2 shift 2 git cat-file -p $ref:$f } function uncrustify_git_indent_hunk() { local start=$1 end=$2 shift 2 # Will be beatier with tee(1), but doh bash async substitution { uncrustify_indent_off; git_hunk "$@" | head -n$((start - 1)); } { uncrustify_indent_on; git_hunk "$@" | head -n$((end - 1)) | tail -n+$start; } { uncrustify_indent_off; git_hunk "$@" | tail -n+$((end + 1)); } } function strip() { local start=$1 end=$2 shift 2 # seek indent_{on,off}() let start+=2 head -n$end | tail -n+$start } function patch_ranges() { egrep -o '^@@ -[0-9]+(,[0-9]+|) \+[0-9]+(,[0-9]+|) @@' | \ cut -d' ' -f3 } function git_ranges() { local ref=$1 f=$2 shift 2 git diff -W $ref^..$ref -- $f | patch_ranges } function diff_substitute() { local f="$1" shift sed \ -e "s#^--- /dev/fd.*\$#--- a/$f#" \ -e "s#^+++ /dev/fd.*\$#+++ b/$f#" } function uncrustify_git() { local ref=$1 r f start end length shift local files=( $(git diff --name-only $ref^..$ref | egrep "\.(c|h)$") ) for f in "${files[@]}"; do local ranges=( $(git_ranges $ref "$f") ) for r in "${ranges[@]}"; do [[ ! "$r" =~ ^\+([0-9]+)(,([0-9]+)|)$ ]] && continue start=${BASH_REMATCH[1]} [ -n "${BASH_REMATCH[3]}" ] && \ length=${BASH_REMATCH[3]} || \ length=1 end=$((start + length)) echo "Range: $start:$end ($length)" >&2 diff -u \ <(uncrustify_git_indent_hunk $start $end $ref "$f" | strip $start $end) \ <(uncrustify_git_indent_hunk $start $end $ref "$f" | uncrustify_frag | strip $start $end) \ | diff_substitute "$f" done done } function uncrustify_diff() { abort "Not implemented"; } function uncrustify_file() { uncrustify -f "$@"; } function checker() { local c=$1 u=$2 shift 2 [ "${options[clang]}" -eq 0 ] || { $c "$@" return } [ "${options[uncrustify]}" -eq 0 ] || { $u "$@" return } } function check_patch() { checker clang_format_diff uncrustify_diff "$@"; } function check_file() { checker clang_format uncrustify_file "$@"; } function check_ref() { checker clang_format_git uncrustify_git "$@"; } function check_arg() { [ "${options[patch]}" -eq 0 ] || { check_patch "$@" return } [ "${options[file]}" -eq 0 ] || { check_file "$@" return } [ "${options[file_diff]}" -eq 0 ] || { diff -u "$@" <(check_file "$@") | diff_substitute "$@" return } [ "${options[ref]}" -eq 0 ] || { check_ref "$@" return } } function main() { local a for a in "${args}"; do check_arg "$a" done } declare -A options parse_options "$@" main "$@" | less -FRSX