forked from libevent/libevent
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support: - files - files-diffs - patches - git-refs
- Loading branch information
Showing
1 changed file
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
#!/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 <<EOL | ||
$0 [ OPTS ] [ file-or-gitref [ ... ] ] | ||
Example: | ||
# Chech HEAD git ref | ||
$ $0 -r | ||
$ $0 -r HEAD | ||
# Check patch | ||
$ git format-patch --stdout -1 | $0 -p | ||
$ git show -1 | $0 -p | ||
# Or via regular files | ||
$ git format-patch --stdout -2 | ||
$ $0 *.patch | ||
# Over a file | ||
$ $0 -d event.c | ||
$ $0 -d < event.c | ||
# And print the whole file not only summary | ||
$ $0 -f event.c | ||
$ $0 -f < event.c | ||
OPTS: | ||
-p - treat as patch | ||
-f - treat as regular file | ||
-f - treat as regular file and print diff | ||
-r - treat as git revision (default) | ||
-C - check using clang-format (default) | ||
-U - check with uncrustify | ||
-c - config for clang-format/uncrustify | ||
-h - print this message | ||
EOL | ||
} | ||
function cfg() | ||
{ | ||
[ -z "${options[cfg]}" ] || { | ||
echo "${options[cfg]}" | ||
return | ||
} | ||
|
||
local dir="$(dirname "${BASH_SOURCE[0]}")" | ||
[ "${options[clang]}" -eq 0 ] || { | ||
echo "$dir/.clang-format" | ||
return | ||
} | ||
[ "${options[uncrustify]}" -eq 0 ] || { | ||
echo "$dir/.uncrustify" | ||
return | ||
} | ||
} | ||
function abort() | ||
{ | ||
local msg="$1" | ||
shift | ||
|
||
printf "$msg\n" "$@" >&2 | ||
exit 1 | ||
} | ||
function default_arg() | ||
{ | ||
if [ "${options[ref]}" -eq 1 ]; then | ||
echo "HEAD" | ||
else | ||
[ ! -t 0 ] || abort "<stdin> 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() { clang-format-diff --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 |