--- /dev/null
+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
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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 */
--- /dev/null
+#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;
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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 */
--- /dev/null
+#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,
+};
--- /dev/null
+#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,
+};
--- /dev/null
+#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,
+};
--- /dev/null
+#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,
+};
--- /dev/null
+/* 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,
+};