Browse Source

First primitive cut of ccanlint

Rusty Russell 17 years ago
parent
commit
c8acddea39

+ 31 - 0
ccan_tools/ccanlint/Makefile

@@ -0,0 +1,31 @@
+OBJS := ccan_tools/ccanlint/no_info.o \
+	ccan_tools/ccanlint/has_main_header.o \
+	ccan_tools/ccanlint/has_tests.o \
+	ccan_tools/ccanlint/trailing_whitespace.o \
+	ccan_tools/ccanlint/idempotent.o \
+
+FUTURE:=ccan_tools/ccanlint/if_have_not_ifdef.o \
+	ccan_tools/ccanlint/needs_depends.o \
+	ccan_tools/ccanlint/has_info_documentation.o \
+	ccan_tools/ccanlint/has_header_documentation.o \
+	ccan_tools/ccanlint/has_tests.o \
+	ccan_tools/ccanlint/builds_ok.o \
+	ccan_tools/ccanlint/builds_ok_all_have_variants.o \
+	ccan_tools/ccanlint/run_tests.o \
+	ccan_tools/ccanlint/test_coverage.o \
+
+ccan_tools/ccanlint/generated-init-tests: $(OBJS)
+	cat $(OBJS:.o=.c) | sed -n 's/^struct ccanlint \([A-Za-z0-9_]*\) = {/{ extern struct ccanlint \1; list_add(\&tests, \&\1.list); }/p' >$@
+
+ccan_tools/ccanlint/ccanlint.o: ccan_tools/ccanlint/generated-init-tests
+
+ccan_tools/ccanlint/ccanlint: \
+	$(OBJS)			\
+	ccan_tools/ccanlint/ccanlint.o \
+	ccan_tools/ccanlint/get_file_lines.o \
+	ccan_tools/ccanlint/file_analysis.o \
+	talloc/talloc.o noerr/noerr.o
+
+ccanlint-clean:
+	$(RM) ccan_tools/ccanlint/generated-init-tests
+

+ 165 - 0
ccan_tools/ccanlint/ccanlint.c

@@ -0,0 +1,165 @@
+/*
+ * ccanlint: assorted checks and advice for a ccan package
+ * Copyright (C) 2008 Rusty Russell
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "ccanlint.h"
+#include <unistd.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <ctype.h>
+
+static unsigned int verbose = 0;
+static LIST_HEAD(tests);
+
+static void init_tests(void)
+{
+#include "generated-init-tests" 
+}
+
+static void usage(const char *name)
+{
+	fprintf(stderr, "Usage: %s [-s] [-v] [-d <dirname>]\n"
+		"   -v: verbose mode\n"
+		"   -s: simply give one line per FAIL and total score\n"
+		"   -d: use this directory instead of the current one\n",
+		name);
+	exit(1);
+}
+
+static void indent_print(const char *string)
+{
+	while (*string) {
+		unsigned int line = strcspn(string, "\n");
+		printf("\t%.*s", line, string);
+		if (string[line] == '\n') {
+			printf("\n");
+			line++;
+		}
+		string += line;
+	}
+}
+
+bool ask(const char *question)
+{
+	char reply[2];
+
+	printf("%s ", question);
+	fflush(stdout);
+
+	return fgets(reply, sizeof(reply), stdin) != NULL
+		&& toupper(reply[0]) == 'Y';
+}
+
+static bool run_test(const struct ccanlint *i,
+		     bool summary,
+		     unsigned int *score,
+		     unsigned int *total_score,
+		     struct manifest *m)
+{
+	void *result;
+	unsigned int this_score;
+
+	if (i->total_score)
+		*total_score += i->total_score;
+
+	result = i->check(m);
+	if (!result) {
+		if (verbose)
+			printf("  %s: OK\n", i->name);
+		if (i->total_score)
+			*score += i->total_score;
+		return true;
+	}
+
+	if (i->score)
+		this_score = i->score(m, result);
+	else
+		this_score = 0;
+
+	*score += this_score;
+	if (summary) {
+		printf("%s FAILED (%u/%u)\n",
+		       i->name, this_score, i->total_score);
+
+		if (verbose)
+			indent_print(i->describe(m, result));
+		return false;
+	}
+
+	printf("%s\n", i->describe(m, result));
+
+	if (i->handle)
+		i->handle(m, result);
+
+	return false;
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+	bool summary = false;
+	unsigned int score, total_score;
+	struct manifest *m;
+	const struct ccanlint *i;
+
+	/* I'd love to use long options, but that's not standard. */
+	/* FIXME: getopt_long ccan package? */
+	while ((c = getopt(argc, argv, "sd:v")) != -1) {
+		switch (c) {
+		case 'd':
+			if (chdir(optarg) != 0)
+				err(1, "Changing into directory '%s'", optarg);
+			break;
+		case 's':
+			summary = true;
+			break;
+		case 'v':
+			verbose++;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind < argc)
+		usage(argv[0]);
+
+	m = get_manifest();
+
+	init_tests();
+
+	/* If you don't pass the compulsory tests, you don't even get a score */
+	if (verbose)
+		printf("Compulsory tests:\n");
+	list_for_each(&tests, i, list)
+		if (!i->total_score && !run_test(i, summary, NULL, NULL, m))
+			exit(1);
+
+	if (verbose)
+		printf("\nNormal tests:\n");
+	score = total_score = 0;
+	list_for_each(&tests, i, list)
+		if (i->total_score)
+			run_test(i, summary, &score, &total_score, m);
+
+	printf("Total score: %u/%u\n", score, total_score);
+
+	return 0;
+}

+ 76 - 0
ccan_tools/ccanlint/ccanlint.h

@@ -0,0 +1,76 @@
+#ifndef CCAN_LINT_H
+#define CCAN_LINT_H
+#include <list/list.h>
+#include <stdbool.h>
+
+struct manifest {
+	char *basename;
+	struct ccan_file *info_file;
+
+	struct list_head c_files;
+	struct list_head h_files;
+
+	struct list_head run_tests;
+	struct list_head compile_ok_tests;
+	struct list_head compile_fail_tests;
+	struct list_head other_test_files;
+
+	struct list_head other_files;
+};
+
+struct manifest *get_manifest(void);
+
+struct ccanlint {
+	struct list_node list;
+
+	/* Unique name of test */
+	const char *name;
+
+	/* Total score that this test is worth.  0 means compulsory tests. */
+	unsigned int total_score;
+
+	/* If this returns non-NULL, it means the check failed. */
+	void *(*check)(struct manifest *m);
+
+	/* The non-NULL return from check is passed to one of these: */
+
+	/* So, what did this get out of the total_score?  (NULL means 0). */
+	unsigned int (*score)(struct manifest *m, void *check_result);
+
+	/* Verbose description of what was wrong. */
+	const char *(*describe)(struct manifest *m, void *check_result);
+
+	/* Can we do something about it? (NULL if not) */
+	void (*handle)(struct manifest *m, void *check_result);
+};
+
+/* Ask the user a yes/no question: the answer is NO if there's an error. */
+bool ask(const char *question);
+
+struct ccan_file {
+	struct list_node list;
+
+	char *name;
+
+	unsigned int num_lines;
+	char **lines;
+};
+
+/* Use this rather than accessing f->lines directly: loads on demand. */
+char **get_ccan_file_lines(struct ccan_file *f);
+
+/* Call the reporting on every line in the file.  sofar contains
+ * previous results. */
+char *report_on_lines(struct list_head *files,
+		      char *(*report)(const char *),
+		      char *sofar);
+
+/* The critical tests which mean fail if they don't pass. */
+extern struct ccanlint no_info;
+extern struct ccanlint has_main_header;
+
+/* Normal tests. */
+extern struct ccanlint trailing_whitespace;
+
+
+#endif /* CCAN_LINT_H */

+ 149 - 0
ccan_tools/ccanlint/file_analysis.c

@@ -0,0 +1,149 @@
+#include "ccanlint.h"
+#include "get_file_lines.h"
+#include <talloc/talloc.h>
+#include <string/string.h>
+#include <noerr/noerr.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <dirent.h>
+
+char **get_ccan_file_lines(struct ccan_file *f)
+{
+	if (!f->lines)
+		f->lines = get_file_lines(f, f->name, &f->num_lines);
+	return f->lines;
+}
+
+static void add_files(struct manifest *m, const char *dir)
+{
+	DIR *d;
+	struct dirent *ent;
+
+	if (dir[0])
+		d = opendir(dir);
+	else
+		d = opendir(".");
+	if (!d)
+		err(1, "Opening directory %s", dir[0] ? dir : ".");
+
+	while ((ent = readdir(d)) != NULL) {
+		struct stat st;
+		struct ccan_file *f;
+		struct list_head *dest;
+		bool is_c_src;
+
+		if (ent->d_name[0] == '.')
+			continue;
+
+		f = talloc(m, struct ccan_file);
+		f->lines = NULL;
+		f->name = talloc_asprintf(f, "%s%s", dir, ent->d_name);
+		if (lstat(f->name, &st) != 0)
+			err(1, "lstat %s", f->name);
+
+		if (S_ISDIR(st.st_mode)) {
+			f->name = talloc_append_string(f->name, "/");
+			add_files(m, f->name);
+			continue;
+		}
+		if (!S_ISREG(st.st_mode)) {
+			talloc_free(f);
+			continue;
+		}
+
+		if (streq(f->name, "_info.c")) {
+			m->info_file = f;
+			continue;
+		}
+
+		is_c_src = strends(f->name, ".c");
+		if (!is_c_src && !strends(f->name, ".h"))
+			dest = &m->other_files;
+		else if (!strchr(f->name, '/')) {
+			if (is_c_src)
+				dest = &m->c_files;
+			else
+				dest = &m->h_files;
+		} else if (strstarts(f->name, "test/")) {
+			if (is_c_src) {
+				if (strstarts(f->name, "test/run"))
+					dest = &m->run_tests;
+				else if (strstarts(f->name, "test/compile_ok"))
+					dest = &m->compile_ok_tests;
+				else if (strstarts(f->name, "test/compile_fail"))
+					dest = &m->compile_fail_tests;
+				else
+					dest = &m->other_test_files;
+			} else
+				dest = &m->other_test_files;
+		} else
+			dest = &m->other_files;
+
+		list_add(dest, &f->list);
+	}
+	closedir(d);
+}
+
+char *report_on_lines(struct list_head *files,
+		      char *(*report)(const char *),
+		      char *sofar)
+{
+	struct ccan_file *f;
+
+	list_for_each(files, f, list) {
+		unsigned int i;
+		char **lines = get_ccan_file_lines(f);
+
+		for (i = 0; i < f->num_lines; i++) {
+			char *r = report(lines[i]);
+			if (!r)
+				continue;
+
+			sofar = talloc_asprintf_append(sofar,
+						       "%s:%u:%s\n",
+						       f->name, i+1, r);
+			talloc_free(r);
+		}
+	}
+	return sofar;
+}
+
+struct manifest *get_manifest(void)
+{
+	struct manifest *m = talloc(NULL, struct manifest);
+	unsigned int len;
+
+	m->info_file = NULL;
+	list_head_init(&m->c_files);
+	list_head_init(&m->h_files);
+	list_head_init(&m->run_tests);
+	list_head_init(&m->compile_ok_tests);
+	list_head_init(&m->compile_fail_tests);
+	list_head_init(&m->other_test_files);
+	list_head_init(&m->other_files);
+
+	/* *This* is why people hate C. */
+	len = 32;
+	m->basename = talloc_array(m, char, len);
+	while (!getcwd(m->basename, len)) {
+		if (errno != ERANGE)
+			err(1, "Getting current directory");
+		m->basename = talloc_realloc(m, m->basename, char, len *= 2);
+	}
+
+	len = strlen(m->basename);
+	while (len && m->basename[len-1] == '/')
+		m->basename[--len] = '\0';
+
+	m->basename = strrchr(m->basename, '/');
+	if (!m->basename)
+		errx(1, "I don't expect to be run from the root directory");
+	m->basename++;
+
+	add_files(m, "");
+	return m;
+}

+ 85 - 0
ccan_tools/ccanlint/get_file_lines.c

@@ -0,0 +1,85 @@
+#include "get_file_lines.h"
+#include <talloc/talloc.h>
+#include <string/string.h>
+#include <noerr/noerr.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <err.h>
+#include <dirent.h>
+
+static void *grab_fd(const void *ctx, int fd)
+{
+	int ret;
+	unsigned int max = 16384, size = 0;
+	char *buffer;
+
+	buffer = talloc_array(ctx, char, max+1);
+	while ((ret = read(fd, buffer + size, max - size)) > 0) {
+		size += ret;
+		if (size == max)
+			buffer = talloc_realloc(ctx, buffer, char, max*=2 + 1);
+	}
+	if (ret < 0) {
+		talloc_free(buffer);
+		buffer = NULL;
+	} else
+		buffer[size] = '\0';
+
+	return buffer;
+}
+
+/* This version adds one byte (for nul term) */
+static void *grab_file(const void *ctx, const char *filename)
+{
+	int fd;
+	char *buffer;
+
+	if (streq(filename, "-"))
+		fd = dup(STDIN_FILENO);
+	else
+		fd = open(filename, O_RDONLY, 0);
+
+	if (fd < 0)
+		return NULL;
+
+	buffer = grab_fd(ctx, fd);
+	close_noerr(fd);
+	return buffer;
+}
+
+/* This is a dumb one which copies.  We could mangle instead. */
+static char **split(const void *ctx, const char *text, const char *delims,
+		    unsigned int *nump)
+{
+	char **lines = NULL;
+	unsigned int max = 64, num = 0;
+
+	lines = talloc_array(ctx, char *, max+1);
+
+	while (*text != '\0') {
+		unsigned int len = strcspn(text, delims);
+		lines[num] = talloc_array(lines, char, len + 1);
+		memcpy(lines[num], text, len);
+		lines[num][len] = '\0';
+		text += len;
+		text += strspn(text, delims);
+		if (++num == max)
+			lines = talloc_realloc(ctx, lines, char *, max*=2 + 1);
+	}
+	lines[num] = NULL;
+	if (nump)
+		*nump = num;
+	return lines;
+}
+
+char **get_file_lines(void *ctx, const char *name, unsigned int *num_lines)
+{
+	char *buffer = grab_file(ctx, name);
+
+	if (!buffer)
+		err(1, "Getting file %s", name);
+
+	return split(buffer, buffer, "\n", num_lines);
+}

+ 6 - 0
ccan_tools/ccanlint/get_file_lines.h

@@ -0,0 +1,6 @@
+#ifndef GET_FILE_LINES_H
+#define GET_FILE_LINES_H
+
+char **get_file_lines(void *ctx, const char *name, unsigned int *num_lines);
+
+#endif /* GET_FILE_LINES_H */

+ 41 - 0
ccan_tools/ccanlint/has_main_header.c

@@ -0,0 +1,41 @@
+#include "ccanlint.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string/string.h>
+#include <talloc/talloc.h>
+#include <noerr/noerr.h>
+
+static void *check_has_main_header(struct manifest *m)
+{
+	struct ccan_file *f;
+
+	list_for_each(&m->h_files, f, list) {
+		if (strstarts(f->name, m->basename)
+		    && strlen(f->name) == strlen(m->basename) + 2)
+			return NULL;
+	}
+	return m;
+}
+
+static const char *describe_has_main_header(struct manifest *m,
+					    void *check_result)
+{
+	return talloc_asprintf(m,
+	"You have no %s/%s.h header file.\n\n"
+	"CCAN modules have a name, the same as the directory name.  They're\n"
+	"expected to have an interface in the header of the same name.\n",
+			       m->basename, m->basename);
+}
+
+struct ccanlint has_main_header = {
+	.name = "No main header file",
+	.check = check_has_main_header,
+	.describe = describe_has_main_header,
+};

+ 118 - 0
ccan_tools/ccanlint/has_tests.c

@@ -0,0 +1,118 @@
+#include "ccanlint.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string/string.h>
+#include <talloc/talloc.h>
+#include <noerr/noerr.h>
+
+static char test_is_not_dir[] = "test is not a directory";
+
+static void *check_has_tests(struct manifest *m)
+{
+	struct stat st;
+
+	if (lstat("test", &st) != 0) {
+		if (errno != ENOENT)
+			err(1, "statting test/");
+		return "You have no test directory";
+	}
+
+	if (!S_ISDIR(st.st_mode))
+		return test_is_not_dir;
+
+	if (list_empty(&m->run_tests) && list_empty(&m->compile_ok_tests)) {
+		if (list_empty(&m->compile_fail_tests)) 
+			return "You have no tests in the test directory";
+		else
+			return "You have no positive tests in the test directory";
+	}
+	return NULL;
+}
+
+static const char *describe_has_tests(struct manifest *m, void *check_result)
+{
+	return talloc_asprintf(m, "%s\n\n"
+        "CCAN modules have a directory called test/ which contains tests.\n"
+	"There are three kinds of tests: run, compile_ok and compile_fail:\n"
+	"you can tell which type of test a C file is by its name, eg 'run.c'\n"
+	"and 'run-simple.c' are both run tests.\n\n"
+	"The simplest kind of test is a run test, which must compile with no\n"
+	"warnings, and then run: it is expected to use libtap to report its\n"
+	"results in a simple and portable format.\n"
+	"compile_ok tests are a subset of run tests: they must compile and\n"
+	"link, but aren't run.\n"
+	"compile_fail tests are tests which should fail to compile (or emit\n"
+	"warnings) or link when FAIL is defined, but should compile and link\n"
+	"when it's not defined: this helps ensure unrelated errors don't make\n"
+	"compilation fail.\n\n"
+	"Note that the tests are not linked against the files in the\n"
+	"above: you should directly #include those C files you want.  This\n"
+	"allows access to static functions and use special effects inside\n"
+	"test files\n", (char *)check_result);
+}
+
+static void handle_no_tests(struct manifest *m, void *check_result)
+{
+	FILE *run;
+	struct ccan_file *i;
+
+	if (check_result == test_is_not_dir)
+		return;
+
+	if (!ask("Should I create a template test/run.c file for you?"))
+		return;
+
+	if (mkdir("test", 0600) != 0) {
+		if (errno != EEXIST)
+			err(1, "Creating test/ directory");
+	}
+
+	run = fopen("test/run.c", "w");
+	if (!run)
+		err(1, "Trying to create a test/run.c");
+
+	fputs("/* Include the main header first, to test it works */\n", run);
+	fprintf(run, "#include \"%s/%s.h\"\n", m->basename, m->basename);
+	fputs("/* Include the C files directly. */\n", run);
+	list_for_each(&m->c_files, i, list)
+		fprintf(run, "#include \"%s/%s\"\n", m->basename, i->name);
+	fputs("#include \"tap/tap.h\"\n", run);
+	fputs("\n", run);
+
+	fputs("int main(int argc, char *argv[])\n", run);
+	fputs("{\n", run);
+	fputs("\t/* This is how many tests you plan to run\n", run);
+	fputs("\tplan_tests(3);\n", run);
+	fputs("\n", run);
+	fputs("\t/* Simple thing we expect to succeed */\n", run);
+	fputs("\tok1(some_test())\n", run);
+	fputs("\t/* Same, with an explicit description of the test. */\n", run);
+	fputs("\tok(some_test(), \"%s with no args should return 1\", \"some_test\")\n", run);
+	fputs("\t/* How to print out messages for debugging. */\n", run);
+	fputs("\tdiag(\"Address of some_test is %p\", &some_test)\n", run);
+	fputs("\t/* Conditional tests must be explicitly skipped. */\n", run);
+	fputs("#if HAVE_SOME_FEATURE\n", run);
+	fputs("\tok1(test_some_feature())\n", run);
+	fputs("#else\n", run);
+	fputs("\tskip(1, \"Don\'t have SOME_FEATURE\")\n", run);
+	fputs("#endif\n", run);
+	fputs("\n", run);
+	fputs("\t/* This exits depending on whether all tests passed */\n", run);
+	fputs("\return exit_status()\n", run);
+
+	fclose(run);
+}	
+
+struct ccanlint has_tests = {
+	.name = "No tests",
+	.check = check_has_tests,
+	.describe = describe_has_tests,
+	.handle = handle_no_tests,
+};

+ 71 - 0
ccan_tools/ccanlint/idempotent.c

@@ -0,0 +1,71 @@
+#include "ccanlint.h"
+#include <talloc/talloc.h>
+#include <string/string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+
+static const char explain[] 
+= "Headers usually start with the C preprocessor lines to prevent multiple\n"
+  "inclusions.  These look like the following:\n"
+  "#ifndef MY_HEADER_H\n"
+  "#define MY_HEADER_H\n"
+  "...\n"
+  "#endif /* MY_HEADER_H */\n";
+
+static char *report_idem(struct ccan_file *f, char *sofar)
+{
+	char **lines;
+	char *secondline;
+
+	lines = get_ccan_file_lines(f);
+	if (f->num_lines < 3)
+		/* FIXME: We assume small headers probably uninteresting. */
+		return NULL;
+
+	if (!strstarts(lines[0], "#ifndef "))
+		return talloc_asprintf_append(sofar,
+			"%s:1:expect first line to be #ifndef.\n", f->name);
+
+	secondline = talloc_asprintf(f, "#define %s",
+				     lines[0] + strlen("#ifndef "));
+	if (!streq(lines[1], secondline))
+		return talloc_asprintf_append(sofar,
+			"%s:2:expect second line to be '%s'.\n",
+			f->name, secondline);
+
+	return sofar;
+}
+
+static void *check_idempotent(struct manifest *m)
+{
+	struct ccan_file *f;
+	char *report = NULL;
+
+	list_for_each(&m->h_files, f, list)
+		report = report_idem(f, report);
+
+	return report;
+}
+
+static const char *describe_idempotent(struct manifest *m, void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "Some headers not idempotent:\n"
+			       "%s\n%s", (char *)check_result,
+			       explain);
+}
+
+struct ccanlint idempotent = {
+	.name = "Headers are #ifndef/#define idempotent wrapped",
+	.total_score = 1,
+	.check = check_idempotent,
+	.describe = describe_idempotent,
+};

+ 78 - 0
ccan_tools/ccanlint/no_info.c

@@ -0,0 +1,78 @@
+#include "ccanlint.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <string.h>
+#include <noerr/noerr.h>
+
+static void *check_no_info(struct manifest *m)
+{
+	if (m->info_file)
+		return NULL;
+	return m;
+}
+
+static const char *describe_no_info(struct manifest *m, void *check_result)
+{
+	return "You have no _info.c file.\n\n"
+	"The file _info.c contains the metadata for a ccan package: things\n"
+	"like the dependencies, the documentation for the package as a whole\n"
+	"and license information.\n";
+}
+
+static const char template[] = 
+	"#include <string.h>\n"
+	"#include \"config.h\"\n"
+	"\n"
+	"/**\n"
+	" * %s - YOUR-ONE-LINE-DESCRIPTION-HERE\n"
+	" *\n"
+	" * This code ... YOUR-BRIEF-SUMMARY-HERE\n"
+	" *\n"
+	" * Example:\n"
+	" *	FULLY-COMPILABLE-INDENTED-TRIVIAL-BUT-USEFUL-EXAMPLE-HERE\n"
+	" */\n"
+	"int main(int argc, char *argv[])\n"
+	"{\n"
+	"	/* Expect exactly one argument\n"
+	"	if (argc != 2)\n"
+	"		return 1;\n"
+	"\n"
+	"	if (strcmp(argv[1], \"depends\") == 0) {\n"
+	"		PRINTF-CCAN-PACKAGES-YOU-NEED-ONE-PER-LINE-IF-ANY\n"
+	"		return 0;\n"
+	"	}\n"
+	"\n"
+	"	return 1;\n"
+	"}\n";
+
+static void create_info_template(struct manifest *m, void *check_result)
+{
+	FILE *info;
+
+	if (!ask("Should I create a template _info.c file for you?"))
+		return;
+
+	info = fopen("_info.c", "w");
+	if (!info)
+		err(1, "Trying to create a template _info.c");
+
+	if (fprintf(info, template, m->basename) < 0) {
+		unlink_noerr("_info.c");
+		err(1, "Writing template into _info.c");
+	}
+	fclose(info);
+}
+
+struct ccanlint no_info = {
+	.name = "No _info.c file",
+	.check = check_no_info,
+	.describe = describe_no_info,
+	.handle = create_info_template,
+};

+ 42 - 0
ccan_tools/ccanlint/trailing_whitespace.c

@@ -0,0 +1,42 @@
+/* Trailing whitespace test.  Almost embarrassing, but trivial. */
+#include "ccanlint.h"
+#include <talloc/talloc.h>
+#include <string/string.h>
+
+static char *report_on_trailing_whitespace(const char *line)
+{
+	if (!strends(line, " ") && !strends(line, "\t"))
+		return NULL;
+
+	if (strlen(line) > 20)
+		return talloc_asprintf(line, "...'%s'",
+				       line + strlen(line) - 20);
+	return talloc_asprintf(line, "'%s'", line);
+}
+
+static void *check_trailing_whitespace(struct manifest *m)
+{
+	char *report;
+
+	report = report_on_lines(&m->c_files, report_on_trailing_whitespace,
+				 NULL);
+	report = report_on_lines(&m->h_files, report_on_trailing_whitespace,
+				 report);
+
+	return report;
+}
+
+static const char *describe_trailing_whitespace(struct manifest *m,
+						void *check_result)
+{
+	return talloc_asprintf(check_result, 
+			       "Some source files have trailing whitespace:\n"
+			       "%s", (char *)check_result);
+}
+
+struct ccanlint trailing_whitespace = {
+	.name = "Lines with unnecessary trailing whitespace",
+	.total_score = 1,
+	.check = check_trailing_whitespace,
+	.describe = describe_trailing_whitespace,
+};