904 lines
14 KiB
C
904 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2019-2021 Cyril Hrubis <chrubis@suse.cz>
|
|
* Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <search.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <libgen.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "data_storage.h"
|
|
|
|
#define INCLUDE_PATH_MAX 5
|
|
|
|
static int verbose;
|
|
static char *cmdline_includepath[INCLUDE_PATH_MAX];
|
|
static unsigned int cmdline_includepaths;
|
|
static char *includepath;
|
|
|
|
#define WARN(str) fprintf(stderr, "WARNING: " str "\n")
|
|
|
|
static void oneline_comment(FILE *f)
|
|
{
|
|
int c;
|
|
|
|
do {
|
|
c = getc(f);
|
|
} while (c != '\n');
|
|
}
|
|
|
|
static const char *eat_asterisk_space(const char *c)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
while (isspace(c[i]))
|
|
i++;
|
|
|
|
if (c[i] == '*') {
|
|
if (isspace(c[i+1]))
|
|
i++;
|
|
return &c[i+1];
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
static void multiline_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c;
|
|
int state = 0;
|
|
char buf[4096];
|
|
unsigned int bufp = 0;
|
|
|
|
for (;;) {
|
|
c = getc(f);
|
|
|
|
if (doc) {
|
|
if (c == '\n') {
|
|
struct data_node *line;
|
|
buf[bufp] = 0;
|
|
line = data_node_string(eat_asterisk_space(buf));
|
|
if (data_node_array_add(doc, line))
|
|
WARN("doc string comment truncated");
|
|
bufp = 0;
|
|
continue;
|
|
}
|
|
|
|
if (bufp + 1 >= sizeof(buf))
|
|
continue;
|
|
|
|
buf[bufp++] = c;
|
|
}
|
|
|
|
switch (state) {
|
|
case 0:
|
|
if (c == '*')
|
|
state = 1;
|
|
break;
|
|
case 1:
|
|
switch (c) {
|
|
case '/':
|
|
return;
|
|
case '*':
|
|
continue;
|
|
default:
|
|
state = 0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static const char doc_prefix[] = "\\\n";
|
|
|
|
static void maybe_doc_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c, i;
|
|
|
|
for (i = 0; doc_prefix[i]; i++) {
|
|
c = getc(f);
|
|
|
|
if (c == doc_prefix[i])
|
|
continue;
|
|
|
|
if (c == '*')
|
|
ungetc(c, f);
|
|
|
|
multiline_comment(f, NULL);
|
|
return;
|
|
}
|
|
|
|
multiline_comment(f, doc);
|
|
}
|
|
|
|
static void maybe_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c = getc(f);
|
|
|
|
switch (c) {
|
|
case '/':
|
|
oneline_comment(f);
|
|
break;
|
|
case '*':
|
|
maybe_doc_comment(f, doc);
|
|
break;
|
|
default:
|
|
ungetc(c, f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static char *next_token2(FILE *f, char *buf, size_t buf_len, struct data_node *doc)
|
|
{
|
|
size_t i = 0;
|
|
int c;
|
|
int in_str = 0;
|
|
|
|
buf_len--;
|
|
|
|
for (;;) {
|
|
c = fgetc(f);
|
|
|
|
if (c == EOF)
|
|
goto exit;
|
|
|
|
if (in_str) {
|
|
if (c == '"') {
|
|
if (i == 0 || buf[i-1] != '\\')
|
|
goto exit;
|
|
}
|
|
|
|
if (i < buf_len)
|
|
buf[i++] = c;
|
|
continue;
|
|
}
|
|
|
|
switch (c) {
|
|
case '{':
|
|
case '}':
|
|
case ';':
|
|
case '(':
|
|
case ')':
|
|
case '=':
|
|
case ',':
|
|
case '[':
|
|
case ']':
|
|
case '#':
|
|
if (i) {
|
|
ungetc(c, f);
|
|
goto exit;
|
|
}
|
|
|
|
if (i < buf_len)
|
|
buf[i++] = c;
|
|
goto exit;
|
|
case '0' ... '9':
|
|
case 'a' ... 'z':
|
|
case 'A' ... 'Z':
|
|
case '.':
|
|
case '_':
|
|
case '-':
|
|
buf[i++] = c;
|
|
break;
|
|
case '/':
|
|
maybe_comment(f, doc);
|
|
break;
|
|
case '"':
|
|
in_str = 1;
|
|
break;
|
|
case ' ':
|
|
case '\n':
|
|
case '\t':
|
|
if (i)
|
|
goto exit;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
if (i == 0 && !in_str)
|
|
return NULL;
|
|
|
|
buf[i] = 0;
|
|
return buf;
|
|
}
|
|
|
|
static char *next_token(FILE *f, struct data_node *doc)
|
|
{
|
|
static char buf[4096];
|
|
|
|
return next_token2(f, buf, sizeof(buf), doc);
|
|
}
|
|
|
|
static FILE *open_file(const char *dir, const char *fname)
|
|
{
|
|
FILE *f;
|
|
char *path;
|
|
|
|
if (asprintf(&path, "%s/%s", dir, fname) < 0)
|
|
return NULL;
|
|
|
|
f = fopen(path, "r");
|
|
|
|
free(path);
|
|
|
|
return f;
|
|
}
|
|
|
|
static FILE *open_include(FILE *f)
|
|
{
|
|
char buf[256], *fname;
|
|
FILE *inc;
|
|
unsigned int i;
|
|
|
|
if (!fscanf(f, "%s\n", buf))
|
|
return NULL;
|
|
|
|
if (buf[0] != '"')
|
|
return NULL;
|
|
|
|
fname = buf + 1;
|
|
|
|
if (!buf[0])
|
|
return NULL;
|
|
|
|
fname[strlen(fname)-1] = 0;
|
|
|
|
inc = open_file(includepath, fname);
|
|
if (inc) {
|
|
if (verbose)
|
|
fprintf(stderr, "INCLUDE %s/%s\n", includepath, fname);
|
|
|
|
return inc;
|
|
}
|
|
|
|
for (i = 0; i < cmdline_includepaths; i++) {
|
|
inc = open_file(cmdline_includepath[i], fname);
|
|
|
|
if (!inc)
|
|
continue;
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "INCLUDE %s/%s\n",
|
|
cmdline_includepath[i], fname);
|
|
}
|
|
|
|
return inc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void close_include(FILE *inc)
|
|
{
|
|
if (verbose)
|
|
fprintf(stderr, "INCLUDE END\n");
|
|
|
|
fclose(inc);
|
|
}
|
|
|
|
static int parse_array(FILE *f, struct data_node *node)
|
|
{
|
|
const char *token;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, NULL)))
|
|
return 1;
|
|
|
|
if (!strcmp(token, "{")) {
|
|
struct data_node *ret = data_node_array();
|
|
parse_array(f, ret);
|
|
|
|
if (data_node_array_len(ret))
|
|
data_node_array_add(node, ret);
|
|
else
|
|
data_node_free(ret);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(token, "}"))
|
|
return 0;
|
|
|
|
if (!strcmp(token, ","))
|
|
continue;
|
|
|
|
if (!strcmp(token, "NULL"))
|
|
continue;
|
|
|
|
struct data_node *str = data_node_string(token);
|
|
|
|
data_node_array_add(node, str);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void try_apply_macro(char **res)
|
|
{
|
|
ENTRY macro = {
|
|
.key = *res,
|
|
};
|
|
|
|
ENTRY *ret;
|
|
|
|
ret = hsearch(macro, FIND);
|
|
|
|
if (!ret)
|
|
return;
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "APPLYING MACRO %s=%s\n", ret->key, (char*)ret->data);
|
|
|
|
*res = ret->data;
|
|
}
|
|
|
|
static int parse_get_array_len(FILE *f)
|
|
{
|
|
const char *token;
|
|
int cnt = 0, depth = 0, prev_comma = 0;
|
|
|
|
if (!(token = next_token(f, NULL)))
|
|
return 0;
|
|
|
|
if (strcmp(token, "{"))
|
|
return 0;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, NULL)))
|
|
return 0;
|
|
|
|
if (!strcmp(token, "{"))
|
|
depth++;
|
|
|
|
if (!strcmp(token, "}"))
|
|
depth--;
|
|
else
|
|
prev_comma = 0;
|
|
|
|
if (!strcmp(token, ",") && !depth) {
|
|
prev_comma = 1;
|
|
cnt++;
|
|
}
|
|
|
|
if (depth < 0)
|
|
return cnt + !prev_comma;
|
|
}
|
|
}
|
|
|
|
static void look_for_array_size(FILE *f, const char *arr_id, struct data_node **res)
|
|
{
|
|
const char *token;
|
|
char buf[2][2048] = {};
|
|
int cur_buf = 0;
|
|
int prev_buf = 1;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token2(f, buf[cur_buf], sizeof(buf[cur_buf]), NULL)))
|
|
break;
|
|
|
|
if (!strcmp(token, "=") && !strcmp(buf[prev_buf], arr_id)) {
|
|
int arr_len = parse_get_array_len(f);
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "ARRAY %s LENGTH = %i\n", arr_id, arr_len);
|
|
|
|
*res = data_node_int(arr_len);
|
|
|
|
break;
|
|
}
|
|
|
|
if (strcmp(buf[cur_buf], "]") && strcmp(buf[cur_buf], "[")) {
|
|
cur_buf = !cur_buf;
|
|
prev_buf = !prev_buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int parse_array_size(FILE *f, struct data_node **res)
|
|
{
|
|
const char *token;
|
|
char *arr_id;
|
|
long pos;
|
|
int hash = 0;
|
|
|
|
*res = NULL;
|
|
|
|
if (!(token = next_token(f, NULL)))
|
|
return 1;
|
|
|
|
if (strcmp(token, "("))
|
|
return 1;
|
|
|
|
if (!(token = next_token(f, NULL)))
|
|
return 1;
|
|
|
|
arr_id = strdup(token);
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "COMPUTING ARRAY '%s' LENGHT\n", arr_id);
|
|
|
|
pos = ftell(f);
|
|
|
|
rewind(f);
|
|
|
|
look_for_array_size(f, arr_id, res);
|
|
|
|
if (!*res) {
|
|
FILE *inc;
|
|
|
|
rewind(f);
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, NULL)))
|
|
break;
|
|
|
|
if (token[0] == '#') {
|
|
hash = 1;
|
|
continue;
|
|
}
|
|
|
|
if (!hash)
|
|
continue;
|
|
|
|
if (!strcmp(token, "include")) {
|
|
inc = open_include(f);
|
|
|
|
if (inc) {
|
|
look_for_array_size(inc, arr_id, res);
|
|
close_include(inc);
|
|
}
|
|
}
|
|
|
|
if (*res)
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(arr_id);
|
|
|
|
if (fseek(f, pos, SEEK_SET))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
|
|
{
|
|
char *token;
|
|
char *id = NULL;
|
|
int state = 0;
|
|
struct data_node *ret;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, doc)))
|
|
return 1;
|
|
|
|
if (!strcmp(token, "}"))
|
|
return 0;
|
|
|
|
switch (state) {
|
|
case 0:
|
|
id = strdup(token);
|
|
state = 1;
|
|
continue;
|
|
case 1:
|
|
if (!strcmp(token, "="))
|
|
state = 2;
|
|
else
|
|
WARN("Expected '='");
|
|
continue;
|
|
case 2:
|
|
if (!strcmp(token, "(")) {
|
|
state = 3;
|
|
continue;
|
|
}
|
|
break;
|
|
case 3:
|
|
if (!strcmp(token, ")"))
|
|
state = 2;
|
|
continue;
|
|
|
|
case 4:
|
|
if (!strcmp(token, ","))
|
|
state = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(token, "{")) {
|
|
ret = data_node_array();
|
|
parse_array(f, ret);
|
|
} else if (!strcmp(token, "ARRAY_SIZE")) {
|
|
if (parse_array_size(f, &ret))
|
|
return 1;
|
|
} else {
|
|
try_apply_macro(&token);
|
|
ret = data_node_string(token);
|
|
}
|
|
|
|
if (!ret)
|
|
continue;
|
|
|
|
const char *key = id;
|
|
if (key[0] == '.')
|
|
key++;
|
|
|
|
data_node_hash_add(node, key, ret);
|
|
free(id);
|
|
state = 4;
|
|
}
|
|
}
|
|
|
|
static const char *tokens[] = {
|
|
"static",
|
|
"struct",
|
|
"tst_test",
|
|
"test",
|
|
"=",
|
|
"{",
|
|
};
|
|
|
|
static void macro_get_string(FILE *f, char *buf, char *buf_end)
|
|
{
|
|
int c;
|
|
char *buf_start = buf;
|
|
|
|
for (;;) {
|
|
c = fgetc(f);
|
|
|
|
switch (c) {
|
|
case EOF:
|
|
*buf = 0;
|
|
return;
|
|
case '"':
|
|
if (buf == buf_start || buf[-1] != '\\') {
|
|
*buf = 0;
|
|
return;
|
|
}
|
|
buf[-1] = '"';
|
|
break;
|
|
default:
|
|
if (buf < buf_end)
|
|
*(buf++) = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void macro_get_val(FILE *f, char *buf, size_t buf_len)
|
|
{
|
|
int c, prev = 0;
|
|
char *buf_end = buf + buf_len - 1;
|
|
|
|
while (isspace(c = fgetc(f)));
|
|
|
|
if (c == '"') {
|
|
macro_get_string(f, buf, buf_end);
|
|
return;
|
|
}
|
|
|
|
for (;;) {
|
|
switch (c) {
|
|
case '\n':
|
|
if (prev == '\\') {
|
|
buf--;
|
|
} else {
|
|
*buf = 0;
|
|
return;
|
|
}
|
|
break;
|
|
case EOF:
|
|
*buf = 0;
|
|
return;
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
default:
|
|
if (buf < buf_end)
|
|
*(buf++) = c;
|
|
}
|
|
|
|
prev = c;
|
|
c = fgetc(f);
|
|
}
|
|
}
|
|
|
|
static void parse_macro(FILE *f)
|
|
{
|
|
char name[128];
|
|
char val[256];
|
|
|
|
if (!fscanf(f, "%s[^\n]", name))
|
|
return;
|
|
|
|
if (fgetc(f) == '\n')
|
|
return;
|
|
|
|
macro_get_val(f, val, sizeof(val));
|
|
|
|
if (name[0] == '_')
|
|
return;
|
|
|
|
ENTRY e = {
|
|
.key = strdup(name),
|
|
.data = strdup(val),
|
|
};
|
|
|
|
if (verbose)
|
|
fprintf(stderr, " MACRO %s=%s\n", e.key, (char*)e.data);
|
|
|
|
hsearch(e, ENTER);
|
|
}
|
|
|
|
static void parse_include_macros(FILE *f)
|
|
{
|
|
FILE *inc;
|
|
const char *token;
|
|
int hash = 0;
|
|
|
|
inc = open_include(f);
|
|
if (!inc)
|
|
return;
|
|
|
|
while ((token = next_token(inc, NULL))) {
|
|
if (token[0] == '#') {
|
|
hash = 1;
|
|
continue;
|
|
}
|
|
|
|
if (!hash)
|
|
continue;
|
|
|
|
if (!strcmp(token, "define"))
|
|
parse_macro(inc);
|
|
|
|
hash = 0;
|
|
}
|
|
|
|
close_include(inc);
|
|
}
|
|
|
|
static struct data_node *parse_file(const char *fname)
|
|
{
|
|
int state = 0, found = 0;
|
|
const char *token;
|
|
|
|
if (access(fname, F_OK)) {
|
|
fprintf(stderr, "file %s does not exist\n", fname);
|
|
return NULL;
|
|
}
|
|
|
|
FILE *f = fopen(fname, "r");
|
|
|
|
includepath = dirname(strdup(fname));
|
|
|
|
struct data_node *res = data_node_hash();
|
|
struct data_node *doc = data_node_array();
|
|
|
|
while ((token = next_token(f, doc))) {
|
|
if (state < 6 && !strcmp(tokens[state], token)) {
|
|
state++;
|
|
} else {
|
|
if (token[0] == '#') {
|
|
token = next_token(f, doc);
|
|
if (token) {
|
|
if (!strcmp(token, "define"))
|
|
parse_macro(f);
|
|
|
|
if (!strcmp(token, "include"))
|
|
parse_include_macros(f);
|
|
}
|
|
}
|
|
|
|
state = 0;
|
|
}
|
|
|
|
if (state < 6)
|
|
continue;
|
|
|
|
found = 1;
|
|
parse_test_struct(f, doc, res);
|
|
}
|
|
|
|
if (data_node_array_len(doc)) {
|
|
data_node_hash_add(res, "doc", doc);
|
|
found = 1;
|
|
} else {
|
|
data_node_free(doc);
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (!found) {
|
|
data_node_free(res);
|
|
return NULL;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static struct typemap {
|
|
const char *id;
|
|
enum data_type type;
|
|
} tst_test_typemap[] = {
|
|
{.id = "test_variants", .type = DATA_INT},
|
|
{}
|
|
};
|
|
|
|
static void convert_str2int(struct data_node *res, const char *id, const char *str_val)
|
|
{
|
|
long val;
|
|
char *endptr;
|
|
|
|
errno = 0;
|
|
val = strtol(str_val, &endptr, 10);
|
|
|
|
if (errno || *endptr) {
|
|
fprintf(stderr, "Cannot convert %s value %s to int!\n", id, str_val);
|
|
exit(1);
|
|
}
|
|
|
|
if (verbose)
|
|
fprintf(stderr, "NORMALIZING %s TO INT %li\n", id, val);
|
|
|
|
data_node_hash_del(res, id);
|
|
data_node_hash_add(res, id, data_node_int(val));
|
|
}
|
|
|
|
static void check_normalize_types(struct data_node *res)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; tst_test_typemap[i].id; i++) {
|
|
struct data_node *n;
|
|
struct typemap *typemap = &tst_test_typemap[i];
|
|
|
|
n = data_node_hash_get(res, typemap->id);
|
|
if (!n)
|
|
continue;
|
|
|
|
if (n->type == typemap->type)
|
|
continue;
|
|
|
|
if (n->type == DATA_STRING && typemap->type == DATA_INT) {
|
|
convert_str2int(res, typemap->id, n->string.val);
|
|
continue;
|
|
}
|
|
|
|
fprintf(stderr, "Cannot convert %s from %s to %s!\n",
|
|
typemap->id, data_type_name(n->type),
|
|
data_type_name(typemap->type));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static const char *filter_out[] = {
|
|
"bufs",
|
|
"cleanup",
|
|
"mntpoint",
|
|
"setup",
|
|
"tcnt",
|
|
"test",
|
|
"test_all",
|
|
NULL
|
|
};
|
|
|
|
static struct implies {
|
|
const char *flag;
|
|
const char **implies;
|
|
} implies[] = {
|
|
{"mount_device", (const char *[]) {"format_device", "needs_device",
|
|
"needs_tmpdir", NULL}},
|
|
{"format_device", (const char *[]) {"needs_device", "needs_tmpdir",
|
|
NULL}},
|
|
{"all_filesystems", (const char *[]) {"needs_device", "needs_tmpdir",
|
|
NULL}},
|
|
{"needs_device", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{"needs_checkpoints", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{"resource_files", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{NULL, (const char *[]) {NULL}}
|
|
};
|
|
|
|
const char *strip_name(char *path)
|
|
{
|
|
char *name = basename(path);
|
|
size_t len = strlen(name);
|
|
|
|
if (len > 2 && name[len-1] == 'c' && name[len-2] == '.')
|
|
name[len-2] = '\0';
|
|
|
|
return name;
|
|
}
|
|
|
|
static void print_help(const char *prgname)
|
|
{
|
|
printf("usage: %s [-vh] input.c\n\n", prgname);
|
|
printf("-v sets verbose mode\n");
|
|
printf("-I add include path\n");
|
|
printf("-h prints this help\n\n");
|
|
exit(0);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
unsigned int i, j;
|
|
struct data_node *res;
|
|
int opt;
|
|
|
|
while ((opt = getopt(argc, argv, "hI:v")) != -1) {
|
|
switch (opt) {
|
|
case 'h':
|
|
print_help(argv[0]);
|
|
break;
|
|
case 'I':
|
|
if (cmdline_includepaths >= INCLUDE_PATH_MAX) {
|
|
fprintf(stderr, "Too much include paths!");
|
|
exit(1);
|
|
}
|
|
|
|
cmdline_includepath[cmdline_includepaths++] = optarg;
|
|
break;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
fprintf(stderr, "No input filename.c\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!hcreate(128)) {
|
|
fprintf(stderr, "Failed to initialize hash table\n");
|
|
return 1;
|
|
}
|
|
|
|
res = parse_file(argv[optind]);
|
|
if (!res)
|
|
return 0;
|
|
|
|
/* Filter out useless data */
|
|
for (i = 0; filter_out[i]; i++)
|
|
data_node_hash_del(res, filter_out[i]);
|
|
|
|
/* Normalize the result */
|
|
for (i = 0; implies[i].flag; i++) {
|
|
if (data_node_hash_get(res, implies[i].flag)) {
|
|
for (j = 0; implies[i].implies[j]; j++) {
|
|
if (data_node_hash_get(res, implies[i].implies[j]))
|
|
fprintf(stderr, "%s: useless tag: %s\n",
|
|
argv[optind], implies[i].implies[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Normalize types */
|
|
check_normalize_types(res);
|
|
|
|
for (i = 0; implies[i].flag; i++) {
|
|
if (data_node_hash_get(res, implies[i].flag)) {
|
|
for (j = 0; implies[i].implies[j]; j++) {
|
|
if (!data_node_hash_get(res, implies[i].implies[j]))
|
|
data_node_hash_add(res, implies[i].implies[j],
|
|
data_node_string("1"));
|
|
}
|
|
}
|
|
}
|
|
|
|
data_node_hash_add(res, "fname", data_node_string(argv[optind]));
|
|
printf(" \"%s\": ", strip_name(argv[optind]));
|
|
data_to_json(res, stdout, 2);
|
|
data_node_free(res);
|
|
|
|
return 0;
|
|
}
|