]> git.ozlabs.org Git - ccan/commitdiff
First primitive cut of ccanlint
authorRusty Russell <rusty@vivaldi>
Mon, 2 Jun 2008 02:16:51 +0000 (12:16 +1000)
committerRusty Russell <rusty@vivaldi>
Mon, 2 Jun 2008 02:16:51 +0000 (12:16 +1000)
ccan_tools/ccanlint/Makefile [new file with mode: 0644]
ccan_tools/ccanlint/ccanlint.c [new file with mode: 0644]
ccan_tools/ccanlint/ccanlint.h [new file with mode: 0644]
ccan_tools/ccanlint/file_analysis.c [new file with mode: 0644]
ccan_tools/ccanlint/get_file_lines.c [new file with mode: 0644]
ccan_tools/ccanlint/get_file_lines.h [new file with mode: 0644]
ccan_tools/ccanlint/has_main_header.c [new file with mode: 0644]
ccan_tools/ccanlint/has_tests.c [new file with mode: 0644]
ccan_tools/ccanlint/idempotent.c [new file with mode: 0644]
ccan_tools/ccanlint/no_info.c [new file with mode: 0644]
ccan_tools/ccanlint/trailing_whitespace.c [new file with mode: 0644]

diff --git a/ccan_tools/ccanlint/Makefile b/ccan_tools/ccanlint/Makefile
new file mode 100644 (file)
index 0000000..ddc5534
--- /dev/null
@@ -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
+
diff --git a/ccan_tools/ccanlint/ccanlint.c b/ccan_tools/ccanlint/ccanlint.c
new file mode 100644 (file)
index 0000000..001ee68
--- /dev/null
@@ -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;
+}
diff --git a/ccan_tools/ccanlint/ccanlint.h b/ccan_tools/ccanlint/ccanlint.h
new file mode 100644 (file)
index 0000000..51f555e
--- /dev/null
@@ -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 */
diff --git a/ccan_tools/ccanlint/file_analysis.c b/ccan_tools/ccanlint/file_analysis.c
new file mode 100644 (file)
index 0000000..1341e57
--- /dev/null
@@ -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;
+}
diff --git a/ccan_tools/ccanlint/get_file_lines.c b/ccan_tools/ccanlint/get_file_lines.c
new file mode 100644 (file)
index 0000000..e9ef302
--- /dev/null
@@ -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);
+}
diff --git a/ccan_tools/ccanlint/get_file_lines.h b/ccan_tools/ccanlint/get_file_lines.h
new file mode 100644 (file)
index 0000000..2f8455a
--- /dev/null
@@ -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 */
diff --git a/ccan_tools/ccanlint/has_main_header.c b/ccan_tools/ccanlint/has_main_header.c
new file mode 100644 (file)
index 0000000..75e3f91
--- /dev/null
@@ -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,
+};
diff --git a/ccan_tools/ccanlint/has_tests.c b/ccan_tools/ccanlint/has_tests.c
new file mode 100644 (file)
index 0000000..7a187ab
--- /dev/null
@@ -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,
+};
diff --git a/ccan_tools/ccanlint/idempotent.c b/ccan_tools/ccanlint/idempotent.c
new file mode 100644 (file)
index 0000000..4456557
--- /dev/null
@@ -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,
+};
diff --git a/ccan_tools/ccanlint/no_info.c b/ccan_tools/ccanlint/no_info.c
new file mode 100644 (file)
index 0000000..9ee5f70
--- /dev/null
@@ -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,
+};
diff --git a/ccan_tools/ccanlint/trailing_whitespace.c b/ccan_tools/ccanlint/trailing_whitespace.c
new file mode 100644 (file)
index 0000000..3aeec6e
--- /dev/null
@@ -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,
+};