Add simple indent wrapper tool for style(9) checking GIT/SVN patches.

The indent_wrapper tool only accepts full context diffs and works by
identifying the surrounding C-block touched by a diff and passing only
that to indent for styling. In the end a diff is produced or an
external tool like meld can be invoked, to show the styling
differences.
This commit is contained in:
Hans Petter Selasky 2015-11-25 13:26:42 +00:00
parent 03b7b4bf05
commit 10ac20335c
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=291300
3 changed files with 754 additions and 0 deletions

View File

@ -33,6 +33,7 @@ hcomp Compress header files by removing comments and whitespace.
html-mv Rename HTML generated filenames to human readable filenames. html-mv Rename HTML generated filenames to human readable filenames.
ifinfo Uses the interface MIB to print out all the information ifinfo Uses the interface MIB to print out all the information
an interface exports in an ugly form. an interface exports in an ugly form.
indent_wrapper Tool for style(9) checking SVN/GIT patches.
iso Tool to compare the iso3166 and iso639 files in iso Tool to compare the iso3166 and iso639 files in
/usr/share/misc with the data from the master sites. /usr/share/misc with the data from the master sites.
iwi Tools specific to the Intel PRO/Wireless 2200BG/2225BG/2915ABG iwi Tools specific to the Intel PRO/Wireless 2200BG/2225BG/2915ABG

View File

@ -0,0 +1,9 @@
# $FreeBSD$
PREFIX?= /usr/local
LOCALBASE?= /usr/local
BINDIR= ${PREFIX}/sbin
PROG= indent_wrapper
MAN=
SRCS+= indent_wrapper.c
.include <bsd.prog.mk>

View File

@ -0,0 +1,744 @@
/*-
* Copyright (c) 2015 Hans Petter Selasky. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <stdio.h>
#include <stdint.h>
#include <sys/queue.h>
#include <sysexits.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
extern char **environ;
static int opt_verbose;
static char *opt_diff_tool;
#define BLOCK_SIZE 4096
#define BLOCK_MASK 0x100
#define BLOCK_ADD 0x200
struct block {
TAILQ_ENTRY(block) entry;
uint32_t length;
uint32_t flags;
uint8_t *data;
uint8_t *mask;
};
typedef TAILQ_HEAD(, block) block_head_t;
static void
sigpipe(int sig)
{
}
static struct block *
alloc_block(void)
{
struct block *pb;
size_t size = sizeof(*pb) + (2 * BLOCK_SIZE);
pb = malloc(size);
if (pb == NULL)
errx(EX_SOFTWARE, "Out of memory");
memset(pb, 0, size);
pb->data = (void *)(pb + 1);
pb->mask = pb->data + BLOCK_SIZE;
pb->length = BLOCK_SIZE;
return (pb);
}
static int
write_block(int fd, block_head_t *ph)
{
struct block *ptr;
if (fd < 0)
return (-1);
TAILQ_FOREACH(ptr, ph, entry) {
if (write(fd, ptr->data, ptr->length) != ptr->length)
return (-1);
}
return (0);
}
static uint16_t
peek_block(block_head_t *pbh, uint64_t off)
{
struct block *ptr;
TAILQ_FOREACH(ptr, pbh, entry) {
if (off < ptr->length)
break;
off -= ptr->length;
}
if (ptr == NULL)
return (0);
return (ptr->data[off] | (ptr->mask[off] << 8));
}
static void
set_block(block_head_t *pbh, uint64_t off, uint16_t ch)
{
struct block *ptr;
TAILQ_FOREACH(ptr, pbh, entry) {
if (off < ptr->length)
break;
off -= ptr->length;
}
if (ptr == NULL)
return;
ptr->data[off] = ch & 0xFF;
ptr->mask[off] = (ch >> 8) & 0xFF;
}
static uint64_t
size_block(block_head_t *pbh)
{
struct block *ptr;
uint64_t off = 0;
TAILQ_FOREACH(ptr, pbh, entry)
off += ptr->length;
return (off);
}
static int
diff_tool(block_head_t *pa, block_head_t *pb)
{
char ca[] = {"/tmp/diff.orig.XXXXXX"};
char cb[] = {"/tmp/diff.styled.XXXXXX"};
char cc[256];
uint64_t sa;
uint64_t sb;
uint64_t s;
uint64_t x;
int fa;
int fb;
sa = size_block(pa);
sb = size_block(pb);
s = (sa > sb) ? sa : sb;
for (x = 0; x != s; x++) {
char cha = peek_block(pa, x) & 0xFF;
char chb = peek_block(pb, x) & 0xFF;
if (cha != chb) {
/* false positive */
if (cha == '\n' && chb == 0 && x == sa - 1)
return (0);
break;
}
}
if (x == s)
return (0); /* identical */
fa = mkstemp(ca);
fb = mkstemp(cb);
if (write_block(fa, pa) < 0 || write_block(fb, pb) < 0) {
close(fa);
close(fb);
unlink(ca);
unlink(cb);
err(EX_SOFTWARE, "Could not write data to temporary files");
}
close(fa);
close(fb);
snprintf(cc, sizeof(cc), "%s %s %s", opt_diff_tool, ca, cb);
system(cc);
unlink(ca);
unlink(cb);
return (-1);
}
static int
diff_block(block_head_t *pa, block_head_t *pb)
{
uint64_t sa = size_block(pa);
uint64_t sb = size_block(pb);
uint64_t s;
uint64_t x;
uint64_t y;
uint64_t n;
s = (sa > sb) ? sa : sb;
for (y = x = 0; x != s; x++) {
char cha = peek_block(pa, x) & 0xFF;
char chb = peek_block(pb, x) & 0xFF;
if (cha != chb) {
int nonspace;
/* false positive */
if (cha == '\n' && chb == 0 && x == sa - 1)
return (0);
n = x - y;
printf("Style error:\n");
nonspace = 0;
for (n = y; n < sa; n++) {
char ch = peek_block(pa, n) & 0xFF;
if (nonspace && ch == '\n')
break;
printf("%c", ch);
if (!isspace(ch))
nonspace = 1;
}
printf("\n");
printf("Style corrected:\n");
nonspace = 0;
for (n = y; n < sb; n++) {
char ch = peek_block(pb, n) & 0xFF;
if (nonspace && ch == '\n')
break;
printf("%c", ch);
if (!isspace(ch))
nonspace = 1;
}
printf("\n");
for (n = y; n != x; n++) {
if ((peek_block(pa, n) & 0xFF) == '\t')
printf("\t");
else
printf(" ");
}
printf("^ %sdifference%s\n",
(isspace(cha) || isspace(chb)) ? "whitespace " : "",
(x >= sa || x >= sb) ? " in the end of a block" : "");
return (1);
} else if (cha == '\n') {
y = x + 1;
}
}
return (0);
}
static void
free_block(block_head_t *pbh)
{
struct block *ptr;
while ((ptr = TAILQ_FIRST(pbh))) {
TAILQ_REMOVE(pbh, ptr, entry);
free(ptr);
}
}
static void
cmd_popen(char *command, FILE **iop)
{
char *argv[4];
int pdes[4];
int pid;
if (pipe(pdes) < 0)
goto error;
if (pipe(pdes + 2) < 0) {
close(pdes[0]);
close(pdes[1]);
goto error;
}
argv[0] = "sh";
argv[1] = "-c";
argv[2] = command;
argv[3] = NULL;
switch ((pid = vfork())) {
case -1: /* Error. */
close(pdes[0]);
close(pdes[1]);
close(pdes[2]);
close(pdes[3]);
goto error;
case 0: /* Child. */
dup2(pdes[1], STDOUT_FILENO);
dup2(pdes[2], STDIN_FILENO);
close(pdes[0]);
close(pdes[3]);
execve("/bin/sh", argv, environ);
exit(127);
default:
break;
}
iop[0] = fdopen(pdes[3], "w");
iop[1] = fdopen(pdes[0], "r");
close(pdes[1]);
close(pdes[2]);
return;
error:
iop[0] = iop[1] = NULL;
}
static void
cmd_block_process(block_head_t *pbh_in, block_head_t *pbh_out, char *cmd_str)
{
FILE *pfd[2];
struct block *ptr;
TAILQ_INIT(pbh_out);
cmd_popen(cmd_str, pfd);
if (pfd[0] == NULL || pfd[1] == NULL)
errx(EX_SOFTWARE, "Cannot invoke command '%s'", cmd_str);
if (pbh_in != NULL) {
TAILQ_FOREACH(ptr, pbh_in, entry) {
if (fwrite(ptr->data, 1, ptr->length, pfd[0]) != ptr->length)
err(EX_SOFTWARE, "Cannot write all data to command '%s'", cmd_str);
}
fflush(pfd[0]);
}
fclose(pfd[0]);
while (1) {
int len;
ptr = alloc_block();
len = fread(ptr->data, 1, BLOCK_SIZE, pfd[1]);
if (len <= 0) {
free(ptr);
break;
}
ptr->length = len;
TAILQ_INSERT_TAIL(pbh_out, ptr, entry);
}
fclose(pfd[1]);
}
static void
usage(void)
{
fprintf(stderr,
"indent_wrapper [-v] [-d] [-D] [-g <githash>]\n"
"\t" "[-s <svnrevision> ] [ -t <tool> ] [ -c <command> ]\n"
"\t" "-v Increase verbosity\n"
"\t" "-d Check output from git diff\n"
"\t" "-D Check output from svn diff\n"
"\t" "-g <hash> Check output from git hash\n"
"\t" "-s <rev> Check output from svn revision\n"
"\t" "-t <tool> Launch external diff tool\n"
"\n"
"Examples:\n"
"\t" "indent_wrapper -D\n"
"\t" "indent_wrapper -D -t meld\n");
exit(EX_SOFTWARE);
}
int
main(int argc, char **argv)
{
block_head_t diff_head;
block_head_t diff_a_head;
block_head_t diff_b_head;
block_head_t indent_in_head;
block_head_t indent_out_head;
struct block *p1 = NULL;
struct block *p2 = NULL;
uint64_t size;
uint64_t x;
uint64_t y1 = 0;
uint64_t y2 = 0;
int recurse = 0;
int inside_string = 0;
int escape_char = 0;
int do_parse = 0;
char cmdbuf[256];
uint16_t ch;
uint16_t chn;
int c;
int retval = 0;
signal(SIGPIPE, &sigpipe);
cmdbuf[0] = 0;
while ((c = getopt(argc, argv, "dDvg:s:c:ht:")) != -1) {
switch (c) {
case 'v':
opt_verbose++;
break;
case 't':
opt_diff_tool = optarg;
break;
case 'g':
snprintf(cmdbuf, sizeof(cmdbuf), "git show -U1000000 %s", optarg);
break;
case 'd':
snprintf(cmdbuf, sizeof(cmdbuf), "git diff -U1000000");
break;
case 'D':
snprintf(cmdbuf, sizeof(cmdbuf), "svn diff --diff-cmd=diff -x -U1000000");
break;
case 's':
snprintf(cmdbuf, sizeof(cmdbuf), "svn diff --diff-cmd=diff -x -U1000000 -r %s", optarg);
break;
case 'c':
snprintf(cmdbuf, sizeof(cmdbuf), "%s", optarg);
break;
default:
usage();
}
}
if (cmdbuf[0] == 0)
usage();
cmd_block_process(NULL, &diff_head, cmdbuf);
TAILQ_INIT(&diff_a_head);
TAILQ_INIT(&diff_b_head);
size = size_block(&diff_head);
p1 = alloc_block();
y1 = 0;
p2 = alloc_block();
y2 = 0;
for (x = 0; x < size;) {
ch = peek_block(&diff_head, x);
switch (ch & 0xFF) {
case '+':
if (ch == peek_block(&diff_head, x + 1) &&
ch == peek_block(&diff_head, x + 2) &&
' ' == (peek_block(&diff_head, x + 3) & 0xFF))
goto parse_filename;
if (do_parse == 0)
break;
for (x++; x != size; x++) {
ch = peek_block(&diff_head, x);
p1->mask[y1] = BLOCK_ADD >> 8;
p1->data[y1++] = ch;
if (y1 == BLOCK_SIZE) {
TAILQ_INSERT_TAIL(&diff_a_head, p1, entry);
p1 = alloc_block();
y1 = 0;
}
if ((ch & 0xFF) == '\n')
break;
}
break;
case '-':
if (ch == peek_block(&diff_head, x + 1) &&
ch == peek_block(&diff_head, x + 2) &&
' ' == (peek_block(&diff_head, x + 3) & 0xFF))
goto parse_filename;
if (do_parse == 0)
break;
for (x++; x != size; x++) {
ch = peek_block(&diff_head, x);
p2->data[y2++] = ch;
if (y2 == BLOCK_SIZE) {
TAILQ_INSERT_TAIL(&diff_b_head, p2, entry);
p2 = alloc_block();
y2 = 0;
}
if ((ch & 0xFF) == '\n')
break;
}
break;
case ' ':
if (do_parse == 0)
break;
for (x++; x != size; x++) {
ch = peek_block(&diff_head, x);
p1->data[y1++] = ch;
if (y1 == BLOCK_SIZE) {
TAILQ_INSERT_TAIL(&diff_a_head, p1, entry);
p1 = alloc_block();
y1 = 0;
}
p2->data[y2++] = ch;
if (y2 == BLOCK_SIZE) {
TAILQ_INSERT_TAIL(&diff_b_head, p2, entry);
p2 = alloc_block();
y2 = 0;
}
if ((ch & 0xFF) == '\n')
break;
}
break;
parse_filename:
for (x += 3; x != size; x++) {
ch = peek_block(&diff_head, x);
chn = peek_block(&diff_head, x + 1);
if ((ch & 0xFF) == '.') {
/* only accept .c and .h files */
do_parse = ((chn & 0xFF) == 'c' || (chn & 0xFF) == 'h');
}
if ((ch & 0xFF) == '\n')
break;
}
default:
break;
}
/* skip till end of line */
for (; x < size; x++) {
ch = peek_block(&diff_head, x);
if ((ch & 0xFF) == '\n') {
x++;
break;
}
}
}
p1->length = y1;
p2->length = y2;
TAILQ_INSERT_TAIL(&diff_a_head, p1, entry);
TAILQ_INSERT_TAIL(&diff_b_head, p2, entry);
/* first pass - verify input */
size = size_block(&diff_a_head);
for (x = 0; x != size; x++) {
ch = peek_block(&diff_a_head, x) & 0xFF;
if (!(ch & 0x80) && ch != '\t' && ch != '\r' && ch != '\n' &&
ch != ' ' && !isprint(ch))
errx(EX_SOFTWARE, "Non printable characters are not allowed: '%c'", ch);
else if (ch & 0x80) {
set_block(&diff_a_head, x, ch | BLOCK_MASK);
}
}
/* second pass - identify all comments */
for (x = 0; x < size; x++) {
ch = peek_block(&diff_a_head, x);
chn = peek_block(&diff_a_head, x + 1);
if ((ch & 0xFF) == '/' && (chn & 0xFF) == '/') {
set_block(&diff_a_head, x, ch | BLOCK_MASK);
set_block(&diff_a_head, x + 1, chn | BLOCK_MASK);
for (x += 2; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if ((ch & 0xFF) == '\n')
break;
set_block(&diff_a_head, x, ch | BLOCK_MASK);
}
} else if ((ch & 0xFF) == '/' && (chn & 0xFF) == '*') {
set_block(&diff_a_head, x, ch | BLOCK_MASK);
set_block(&diff_a_head, x + 1, chn | BLOCK_MASK);
for (x += 2; x < size; x++) {
ch = peek_block(&diff_a_head, x);
chn = peek_block(&diff_a_head, x + 1);
if ((ch & 0xFF) == '*' && (chn & 0xFF) == '/') {
set_block(&diff_a_head, x, ch | BLOCK_MASK);
set_block(&diff_a_head, x + 1, chn | BLOCK_MASK);
x++;
break;
}
set_block(&diff_a_head, x, ch | BLOCK_MASK);
}
}
}
/* third pass - identify preprocessor tokens and strings */
for (x = 0; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if (ch & BLOCK_MASK)
continue;
if (inside_string == 0 && (ch & 0xFF) == '#') {
int skip_newline = 0;
set_block(&diff_a_head, x, ch | BLOCK_MASK);
for (x++; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if ((ch & 0xFF) == '\n') {
if (!skip_newline)
break;
skip_newline = 0;
}
if (ch & BLOCK_MASK)
continue;
if ((ch & 0xFF) == '\\')
skip_newline = 1;
set_block(&diff_a_head, x, ch | BLOCK_MASK);
}
}
if ((ch & 0xFF) == '"' || (ch & 0xFF) == '\'') {
if (inside_string == 0) {
inside_string = (ch & 0xFF);
} else {
if (escape_char == 0 && inside_string == (ch & 0xFF))
inside_string = 0;
}
escape_char = 0;
set_block(&diff_a_head, x, ch | BLOCK_MASK);
} else if (inside_string != 0) {
if ((ch & 0xFF) == '\\')
escape_char = !escape_char;
else
escape_char = 0;
set_block(&diff_a_head, x, ch | BLOCK_MASK);
}
}
/* fourth pass - identify function blocks */
if (opt_verbose > 0) {
chn = peek_block(&diff_a_head, x);
printf("L%02d%c|", recurse,
(chn & BLOCK_ADD) ? '+' : ' ');
}
for (x = 0; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if (opt_verbose > 0) {
printf("%c", ch & 0xFF);
if ((ch & 0xFF) == '\n') {
chn = peek_block(&diff_a_head, x + 1);
printf("L%02d%c|", recurse,
(chn & BLOCK_ADD) ? '+' : ' ');
}
}
if (ch & BLOCK_MASK)
continue;
switch (ch & 0xFF) {
case '{':
case '(':
recurse++;
break;
default:
break;
}
if (recurse != 0)
set_block(&diff_a_head, x, ch | BLOCK_MASK);
switch (ch & 0xFF) {
case '}':
case ')':
recurse--;
break;
default:
break;
}
}
if (opt_verbose > 0)
printf("\n");
if (recurse != 0)
errx(EX_SOFTWARE, "Unbalanced parenthesis");
if (inside_string != 0)
errx(EX_SOFTWARE, "String without end");
/* fifth pass - on the same line statements */
for (x = 0; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if (ch & BLOCK_MASK)
continue;
switch (ch & 0xFF) {
case '\n':
break;
default:
set_block(&diff_a_head, x, ch | BLOCK_MASK);
break;
}
}
/* sixth pass - output relevant blocks to indent */
for (y1 = x = 0; x < size; x++) {
ch = peek_block(&diff_a_head, x);
if (ch & BLOCK_ADD) {
TAILQ_INIT(&indent_in_head);
p2 = alloc_block();
y2 = 0;
for (; y1 < size; y1++) {
ch = peek_block(&diff_a_head, y1);
if (y1 > x && !(ch & (BLOCK_MASK | BLOCK_ADD)))
break;
p2->data[y2++] = ch & 0xFF;
if (y2 == BLOCK_SIZE) {
TAILQ_INSERT_TAIL(&indent_in_head, p2, entry);
p2 = alloc_block();
y2 = 0;
}
}
if (p2->data[y2] != '\n')
p2->data[y2++] = '\n';
p2->length = y2;
TAILQ_INSERT_TAIL(&indent_in_head, p2, entry);
cmd_block_process(&indent_in_head, &indent_out_head,
"indent "
"-Tbool "
"-Tclass "
"-TFILE "
"-TLIST_ENTRY "
"-TLIST_HEAD "
"-TSLIST_ENTRY "
"-TSLIST_HEAD "
"-TSTAILQ_ENTRY "
"-TSTAILQ_HEAD "
"-TTAILQ_ENTRY "
"-TTAILQ_HEAD "
"-T__aligned "
"-T__packed "
"-T__unused "
"-T__used "
"-Tfd_set "
"-Toss_mixerinfo "
"-Tu_char "
"-Tu_int "
"-Tu_long "
"-Tu_short "
"-ta -st -bad -bap -nbbb -nbc -br -nbs "
"-c41 -cd41 -cdb -ce -ci4 -cli0 -d0 -di8 -ndj -ei -nfc1 "
"-nfcb -i8 -ip8 -l79 -lc77 -ldi0 -nlp -npcs -psl -sc "
"-nsob -nv "
" | "
"sed "
"-e 's/_HEAD [(]/_HEAD(/g' "
"-e 's/_ENTRY [(]/_ENTRY(/g' "
"-e 's/\t__aligned/ __aligned/g' "
"-e 's/\t__packed/ __packed/g' "
"-e 's/\t__unused/ __unused/g' "
"-e 's/\t__used/ __used/g' "
"-e 's/^#define /#define\t/g'");
if (opt_diff_tool != NULL) {
if (diff_tool(&indent_in_head, &indent_out_head))
retval = 1;
} else {
if (diff_block(&indent_in_head, &indent_out_head))
retval = 1;
}
free_block(&indent_in_head);
free_block(&indent_out_head);
x = y1;
} else if (!(ch & BLOCK_MASK)) {
y1 = x + 1;
}
}
return (retval);
}