+/* Remove empty lines. */
+static char **collapse(char **lines, unsigned int *nump)
+{
+ unsigned int i, j;
+ for (i = j = 0; lines[i]; i++) {
+ if (lines[i][0])
+ lines[j++] = lines[i];
+ }
+ if (nump)
+ *nump = j;
+ return lines;
+}
+
+static void add_info_options(struct ccan_file *info, bool mark_fails)
+{
+ struct doc_section *d;
+ unsigned int i;
+ struct ccanlint *test;
+
+ list_for_each(get_ccan_file_docs(info), d, list) {
+ if (!streq(d->type, "ccanlint"))
+ continue;
+
+ for (i = 0; i < d->num_lines; i++) {
+ char **words = collapse(strsplit(d, d->lines[i], " \t",
+ NULL), NULL);
+ if (!words[0])
+ continue;
+
+ if (strncmp(words[0], "//", 2) == 0)
+ continue;
+
+ test = find_test(words[0]);
+ if (!test) {
+ warnx("%s: unknown ccanlint test '%s'",
+ info->fullname, words[0]);
+ continue;
+ }
+
+ if (!words[1]) {
+ warnx("%s: no argument to test '%s'",
+ info->fullname, words[0]);
+ continue;
+ }
+
+ /* Known failure? */
+ if (strcasecmp(words[1], "FAIL") == 0) {
+ if (mark_fails)
+ btree_insert(info_exclude, words[0]);
+ } else {
+ if (!test->takes_options)
+ warnx("%s: %s doesn't take options",
+ info->fullname, words[0]);
+ /* Copy line exactly into options. */
+ test->options = strstr(d->lines[i], words[0])
+ + strlen(words[0]);
+ }
+ }
+ }
+}
+
+static bool depends_on(struct ccanlint *i, struct ccanlint *target)
+{
+ const struct dependent *d;
+
+ if (i == target)
+ return true;
+
+ list_for_each(&i->dependencies, d, node) {
+ if (depends_on(d->dependent, target))
+ return true;
+ }
+ return false;
+}
+
+/* O(N^2), who cares? */
+static void skip_unrelated_tests(struct ccanlint *target)
+{
+ struct ccanlint *i;
+ struct list_head *list;
+
+ foreach_ptr(list, &compulsory_tests, &normal_tests)
+ list_for_each(list, i, list)
+ if (!depends_on(i, target))
+ i->skip = "not relevant to target";
+}
+