+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <stdint.h>
+#include <getopt.h>
#include <errno.h>
-#include <limits.h>
-#include <assert.h>
-#include <yaml.h>
+#include <ecoli_init.h>
+#include <ecoli_strvec.h>
#include <ecoli_node.h>
-#include <ecoli_config.h>
-
-/* associate a yaml node to a ecoli node */
-struct pair {
- const yaml_node_t *ynode;
- struct ec_node *enode;
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_yaml.h>
+#include <ecoli_editline.h>
+#include <ecoli_node_sh_lex.h>
+
+static char *input_file;
+static char *output_file;
+static bool complete;
+
+static const char short_options[] =
+ "h" /* help */
+ "i:" /* input-file */
+ "o:" /* output-file */
+ "c" /* complete */
+ ;
+
+#define OPT_HELP "help"
+#define OPT_INPUT_FILE "input-file"
+#define OPT_OUTPUT_FILE "output-file"
+#define OPT_COMPLETE "complete"
+
+static const struct option long_options[] = {
+ {OPT_HELP, 0, NULL, 'h'},
+ {OPT_INPUT_FILE, 1, NULL, 'i'},
+ {OPT_OUTPUT_FILE, 1, NULL, 'o'},
+ {OPT_COMPLETE, 0, NULL, 'c'},
+ {NULL, 0, NULL, 0}
};
-/* store the ecoli node tree and the associations yaml_node <-> ec_node */
-struct enode_tree {
- struct ec_node *root;
- struct pair *table;
- size_t table_len;
-};
-
-static struct ec_node *
-parse_ec_node(struct enode_tree *tree,
- const yaml_document_t *document, const yaml_node_t *ynode);
-
-static struct ec_config *
-parse_ec_config_list(struct enode_tree *tree,
- const struct ec_config_schema *schema,
- const yaml_document_t *document, const yaml_node_t *ynode);
-
-static struct ec_config *
-parse_ec_config_dict(struct enode_tree *tree,
- const struct ec_config_schema *schema,
- const yaml_document_t *document, const yaml_node_t *ynode);
+static void usage(const char *prgname)
+{
+ fprintf(stderr, "%s -o <file.sh> -i <file.yaml>\n"
+ " -h\n"
+ " --"OPT_HELP"\n"
+ " Show this help.\n"
+ " -i <input-file>\n"
+ " --"OPT_INPUT_FILE"=<file>\n"
+ " Set the yaml input file describing the grammar.\n"
+ " -o <output-file>\n"
+ " --"OPT_OUTPUT_FILE"=<file>\n"
+ " Set the output file.\n"
+ " -c\n"
+ " --"OPT_COMPLETE"\n"
+ " Output the completion list."
+ , prgname);
+}
-/* XXX to utils.c ? */
-static int
-parse_llint(const char *str, int64_t *val)
+static int parse_args(int argc, char **argv)
{
- char *endptr;
- int save_errno = errno;
+ int ret, opt;
- errno = 0;
- *val = strtoll(str, &endptr, 0);
+ while ((opt = getopt_long(argc, argv, short_options,
+ long_options, NULL)) != EOF) {
- if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
- (errno != 0 && *val == 0))
- return -1;
+ switch (opt) {
+ case 'h': /* help */
+ usage(argv[0]);
+ exit(0);
- if (*endptr != 0) {
- errno = EINVAL;
- return -1;
- }
+ case 'i': /* input-file */
+ input_file = strdup(optarg);
+ break;
- errno = save_errno;
- return 0;
-}
+ case 'o': /* output-file */
+ output_file = strdup(optarg);
+ break;
-static int
-parse_ullint(const char *str, uint64_t *val)
-{
- char *endptr;
- int save_errno = errno;
+ case 'c': /* complete */
+ complete = 1;
+ break;
- /* since a negative input is silently converted to a positive
- * one by strtoull(), first check that it is positive */
- if (strchr(str, '-'))
- return -1;
+ default:
+ usage(argv[0]);
+ return -1;
+ }
- errno = 0;
- *val = strtoull(str, &endptr, 0);
+ }
- if ((errno == ERANGE && *val == ULLONG_MAX) ||
- (errno != 0 && *val == 0))
+ if (input_file == NULL) {
+ fprintf(stderr, "No input file\n");
+ usage(argv[0]);
return -1;
-
- if (*endptr != 0)
+ }
+ if (output_file == NULL) {
+ fprintf(stderr, "No output file\n");
+ usage(argv[0]);
return -1;
+ }
- errno = save_errno;
- return 0;
-}
+ ret = optind - 1;
+ optind = 1;
-static int
-parse_bool(const char *str, bool *val)
-{
- if (!strcasecmp(str, "true")) {
- *val = true;
- return 0;
- } else if (!strcasecmp(str, "false")) {
- *val = false;
- return 0;
- }
- errno = EINVAL;
- return -1;
+ return ret;
}
static int
-add_in_table(struct enode_tree *tree, const yaml_node_t *ynode,
- struct ec_node *enode)
+__dump_as_shell(FILE *f, const struct ec_parse *parse, size_t *seq)
{
- struct pair *table = NULL;
-
- table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table));
- if (table == NULL)
- return -1;
+ const struct ec_node *node = ec_parse_get_node(parse);
+ struct ec_parse *child;
+ size_t cur_seq, i, len;
+ const char *s;
- ec_node_clone(enode);
- table[tree->table_len].ynode = ynode;
- table[tree->table_len].enode = enode;
- tree->table = table;
- tree->table_len++;
+ (*seq)++;
+ cur_seq = *seq;
- return 0;
-}
+ // XXX protect strings
-static void
-free_tree(struct enode_tree *tree)
-{
- size_t i;
- if (tree->root != NULL)
- ec_node_free(tree->root);
- for (i = 0; i < tree->table_len; i++)
- ec_node_free(tree->table[i].enode);
- free(tree->table);
-}
+ fprintf(f, "ec_node%zu_id='%s'\n", cur_seq, ec_node_id(node));
+ fprintf(f, "ec_node%zu_type='%s'\n", cur_seq,
+ ec_node_type_name(ec_node_type(node)));
-static struct ec_config *
-parse_ec_config(struct enode_tree *tree,
- const struct ec_config_schema *schema_elt,
- const yaml_document_t *document, const yaml_node_t *ynode)
-{
- const struct ec_config_schema *subschema;
- struct ec_config *config = NULL;
- struct ec_node *enode = NULL;
- enum ec_config_type type;
- const char *value_str;
- uint64_t u64;
- int64_t i64;
- bool boolean;
-
- type = ec_config_schema_type(schema_elt);
-
- switch (type) {
- case EC_CONFIG_TYPE_BOOL:
- if (ynode->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Boolean should be scalar\n");
- goto fail;
- }
- value_str = (const char *)ynode->data.scalar.value;
- if (parse_bool(value_str, &boolean) < 0) {
- fprintf(stderr, "Failed to parse boolean\n");
- goto fail;
- }
- config = ec_config_bool(boolean);
- if (config == NULL) {
- fprintf(stderr, "Failed to create config\n");
- goto fail;
- }
- break;
- case EC_CONFIG_TYPE_INT64:
- if (ynode->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Int64 should be scalar\n");
- goto fail;
- }
- value_str = (const char *)ynode->data.scalar.value;
- if (parse_llint(value_str, &i64) < 0) {
- fprintf(stderr, "Failed to parse i64\n");
- goto fail;
- }
- config = ec_config_i64(i64);
- if (config == NULL) {
- fprintf(stderr, "Failed to create config\n");
- goto fail;
- }
- break;
- case EC_CONFIG_TYPE_UINT64:
- if (ynode->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Uint64 should be scalar\n");
- goto fail;
- }
- value_str = (const char *)ynode->data.scalar.value;
- if (parse_ullint(value_str, &u64) < 0) {
- fprintf(stderr, "Failed to parse u64\n");
- goto fail;
- }
- config = ec_config_u64(u64);
- if (config == NULL) {
- fprintf(stderr, "Failed to create config\n");
- goto fail;
- }
- break;
- case EC_CONFIG_TYPE_STRING:
- if (ynode->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "String should be scalar\n");
- goto fail;
- }
- value_str = (const char *)ynode->data.scalar.value;
- config = ec_config_string(value_str);
- if (config == NULL) {
- fprintf(stderr, "Failed to create config\n");
- goto fail;
- }
- break;
- case EC_CONFIG_TYPE_NODE:
- enode = parse_ec_node(tree, document, ynode);
- if (enode == NULL)
- goto fail;
- config = ec_config_node(enode);
- if (config == NULL) {
- fprintf(stderr, "Failed to create config\n");
- goto fail;
- }
- break;
- case EC_CONFIG_TYPE_LIST:
- subschema = ec_config_schema_sub(schema_elt);
- if (subschema == NULL) {
- fprintf(stderr, "List has no subschema\n");
- goto fail;
- }
- config = parse_ec_config_list(tree, subschema, document, ynode);
- if (config == NULL)
- goto fail;
- break;
- case EC_CONFIG_TYPE_DICT:
- subschema = ec_config_schema_sub(schema_elt);
- if (subschema == NULL) {
- fprintf(stderr, "Dict has no subschema\n");
- goto fail;
- }
- config = parse_ec_config_dict(tree, subschema, document, ynode);
- if (config == NULL)
- goto fail;
- break;
- default:
- fprintf(stderr, "Invalid config type %d\n", type);
- goto fail;
+ len = ec_strvec_len(ec_parse_strvec(parse));
+ fprintf(f, "ec_node%zu_strvec_len=%zu\n", cur_seq, len);
+ for (i = 0; i < len; i++) {
+ s = ec_strvec_val(ec_parse_strvec(parse), i);
+ fprintf(f, "ec_node%zu_str%zu='%s'\n", cur_seq, i, s);
}
- return config;
-
-fail:
- ec_node_free(enode);
- ec_config_free(config);
- return NULL;
-}
-
-static struct ec_config *
-parse_ec_config_list(struct enode_tree *tree,
- const struct ec_config_schema *schema,
- const yaml_document_t *document, const yaml_node_t *ynode)
-{
- struct ec_config *config = NULL, *subconfig = NULL;
- const yaml_node_item_t *item;
- const yaml_node_t *value;
-
- (void)tree;
- (void)schema;
- (void)document;
-
- if (ynode->type != YAML_SEQUENCE_NODE) {
- fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
- goto fail;
+ if (ec_parse_get_first_child(parse) != NULL) {
+ fprintf(f, "ec_node%zu_first_child='ec_node%zu'\n",
+ cur_seq, cur_seq + 1);
}
- config = ec_config_list();
- if (config == NULL) {
- fprintf(stderr, "Failed to allocate config\n");
- goto fail;
+ EC_PARSE_FOREACH_CHILD(child, parse) {
+ fprintf(f, "ec_node%zu_parent='ec_node%zu'\n",
+ *seq + 1, cur_seq);
+ __dump_as_shell(f, child, seq);
}
- for (item = ynode->data.sequence.items.start;
- item < ynode->data.sequence.items.top; item++) {
- value = document->nodes.start + (*item) - 1; // XXX -1 ?
- subconfig = parse_ec_config(tree, schema, document, value);
- if (subconfig == NULL)
- goto fail;
- if (ec_config_list_add(config, subconfig) < 0) {
- fprintf(stderr, "Failed to add list entry\n");
- goto fail;
- }
+ if (ec_parse_get_next(parse) != NULL) {
+ fprintf(f, "ec_node%zu_next='ec_node%zu'\n",
+ cur_seq, *seq + 1);
}
- return config;
-
-fail:
- ec_config_free(config);
- return NULL;
+ return 0;
}
-static struct ec_config *
-parse_ec_config_dict(struct enode_tree *tree,
- const struct ec_config_schema *schema,
- const yaml_document_t *document, const yaml_node_t *ynode)
+static int
+dump_as_shell(const struct ec_parse *parse)
{
- const struct ec_config_schema *schema_elt;
- struct ec_config *config = NULL, *subconfig = NULL;
- const yaml_node_t *key, *value;
- const yaml_node_pair_t *pair;
- const char *key_str;
-
- if (ynode->type != YAML_MAPPING_NODE) {
- fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
- goto fail;
- }
+ FILE *f;
+ size_t seq = 0;
+ int ret;
- config = ec_config_dict();
- if (config == NULL) {
- fprintf(stderr, "Failed to allocate config\n");
- goto fail;
- }
+ f = fopen(output_file, "w");
+ if (f == NULL)
+ return -1;
- for (pair = ynode->data.mapping.pairs.start;
- pair < ynode->data.mapping.pairs.top; pair++) {
- key = document->nodes.start + pair->key - 1; // XXX -1 ?
- value = document->nodes.start + pair->value - 1;
- key_str = (const char *)key->data.scalar.value;
-
- if (ec_config_key_is_reserved(key_str))
- continue;
- schema_elt = ec_config_schema_lookup(schema, key_str);
- if (schema_elt == NULL) {
- fprintf(stderr, "No such config %s\n", key_str);
- goto fail;
- }
- subconfig = parse_ec_config(tree, schema_elt, document, value);
- if (subconfig == NULL)
- goto fail;
- if (ec_config_dict_set(config, key_str, subconfig) < 0) {
- fprintf(stderr, "Failed to set dict entry\n");
- goto fail;
- }
- }
+ ret = __dump_as_shell(f, parse, &seq);
- return config;
+ fclose(f);
-fail:
- ec_config_free(config);
- return NULL;
+ return ret;
}
-static struct ec_node *
-parse_ec_node(struct enode_tree *tree,
- const yaml_document_t *document, const yaml_node_t *ynode)
+static int
+interact(struct ec_node *node)
{
- const struct ec_config_schema *schema;
- const struct ec_node_type *type = NULL;
- const char *id = NULL, *help = NULL;
- struct ec_config *config = NULL;
- const yaml_node_t *attrs = NULL;
- const yaml_node_t *key, *value;
- const yaml_node_pair_t *pair;
- const char *key_str, *value_str;
- struct ec_node *enode = NULL;
-
- if (ynode->type != YAML_MAPPING_NODE) {
- fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
+ struct ec_editline *editline = NULL;
+ struct ec_parse *parse = NULL;
+ struct ec_node *shlex = NULL;
+ char *line = NULL;
+
+ shlex = ec_node_sh_lex(EC_NO_ID, ec_node_clone(node)); //XXX
+ if (shlex == NULL) {
+ fprintf(stderr, "Failed to add lexer node\n");
goto fail;
}
- for (pair = ynode->data.mapping.pairs.start;
- pair < ynode->data.mapping.pairs.top; pair++) {
- key = document->nodes.start + pair->key - 1; // XXX -1 ?
- value = document->nodes.start + pair->value - 1;
- key_str = (const char *)key->data.scalar.value;
- value_str = (const char *)value->data.scalar.value;
-
- if (!strcmp(key_str, "type")) {
- if (type != NULL) {
- fprintf(stderr, "Duplicate type\n");
- goto fail;
- }
- if (value->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Type must be a string\n");
- goto fail;
- }
- type = ec_node_type_lookup(value_str);
- if (type == NULL) {
- fprintf(stderr, "Cannot find type %s\n",
- value_str);
- goto fail;
- }
- } else if (!strcmp(key_str, "attrs")) {
- if (attrs != NULL) {
- fprintf(stderr, "Duplicate attrs\n");
- goto fail;
- }
- if (value->type != YAML_MAPPING_NODE) {
- fprintf(stderr, "Attrs must be a maping\n");
- goto fail;
- }
- attrs = value;
- } else if (!strcmp(key_str, "id")) {
- if (id != NULL) {
- fprintf(stderr, "Duplicate id\n");
- goto fail;
- }
- if (value->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Id must be a scalar\n");
- goto fail;
- }
- id = value_str;
- } else if (!strcmp(key_str, "help")) {
- if (help != NULL) {
- fprintf(stderr, "Duplicate help\n");
- goto fail;
- }
- if (value->type != YAML_SCALAR_NODE) {
- fprintf(stderr, "Help must be a scalar\n");
- goto fail;
- }
- help = value_str;
- }
- }
-
- /* create the ecoli node */
- if (id == NULL)
- id = EC_NO_ID;
- enode = ec_node_from_type(type, id);
- if (enode == NULL) {
- fprintf(stderr, "Cannot create ecoli node\n");
- goto fail;
- }
- if (add_in_table(tree, ynode, enode) < 0) {
- fprintf(stderr, "Cannot add node in table\n");
+ editline = ec_editline("ecoli", stdin, stdout, stderr, 0);
+ if (editline == NULL) {
+ fprintf(stderr, "Failed to initialize editline\n");
goto fail;
}
- if (tree->root == NULL) {
- ec_node_clone(enode);
- tree->root = enode;
- }
- /* create its config */
- schema = ec_node_type_schema(type);
- if (schema == NULL) {
- fprintf(stderr, "No configuration schema for type %s\n",
- ec_node_type_name(type));
+ parse = ec_editline_parse(editline, shlex);
+ if (parse == NULL)
goto fail;
- }
- config = parse_ec_config_dict(tree, schema, document, ynode);
- if (config == NULL)
+ if (!ec_parse_matches(parse))
goto fail;
- if (ec_node_set_config(enode, config) < 0) {
- fprintf(stderr, "Failed to set config\n");
+ //ec_parse_dump(stdout, parse);
+
+ if (dump_as_shell(parse) < 0) {
+ fprintf(stderr, "Failed to dump the parsed result\n");
goto fail;
}
- /* add attributes (all as string) */
- //XXX
-
- return enode;
+ ec_parse_free(parse);
+ ec_editline_free(editline);
+ ec_node_free(shlex);
+ return 0;
fail:
- ec_node_free(enode);
- ec_config_free(config);
- return NULL;
+ ec_parse_free(parse);
+ ec_editline_free(editline);
+ free(line);
+ ec_node_free(shlex);
+ return -1;
}
static int
-parse_document(struct enode_tree *tree, const yaml_document_t *document)
+complete_words(const struct ec_node *node, int argc, char *argv[])
{
- yaml_node_t *node;
+ struct ec_comp *comp = NULL;
+ struct ec_strvec *strvec = NULL;
+ struct ec_comp_iter *iter = NULL;
+ struct ec_comp_item *item = NULL;
+ size_t count;
- node = document->nodes.start;
- if (parse_ec_node(tree, document, node) == NULL)
- return -1;
+ if (argc <= 1)
+ goto fail;
+ strvec = ec_strvec_from_array((const char * const *)&argv[1],
+ argc - 1);
+ if (strvec == NULL)
+ goto fail;
- return 0;
-}
+ comp = ec_node_complete_strvec(node, strvec);
+ if (comp == NULL)
+ goto fail;
-static int parse_file(struct enode_tree *tree, const char *filename)
-{
- FILE *file;
- yaml_parser_t parser;
- yaml_document_t document;
-
- file = fopen(filename, "rb");
- if (file == NULL) {
- fprintf(stderr, "Failed to open file %s\n", filename);
- goto fail_no_doc;
- }
+ count = ec_comp_count(comp, EC_COMP_UNKNOWN | EC_COMP_FULL |
+ EC_COMP_PARTIAL);
- if (yaml_parser_initialize(&parser) == 0) {
- fprintf(stderr, "Failed to initialize yaml parser\n");
- goto fail_no_doc;
- }
+ iter = ec_comp_iter(comp,
+ EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
+ if (iter == NULL)
+ goto fail;
- yaml_parser_set_input_file(&parser, file);
+ while ((item = ec_comp_iter_next(iter)) != NULL) {
- if (yaml_parser_load(&parser, &document) == 0) {
- fprintf(stderr, "Failed to load yaml document\n");
- goto fail_no_doc;
- }
-
- if (yaml_document_get_root_node(&document) == NULL) {
- fprintf(stderr, "Incomplete document\n"); //XXX check err
- goto fail;
- }
+ /* only one match, display it fully */
+ if (count == 1) {
+ printf("%s\n", ec_comp_item_get_str(item));
+ break;
+ }
- if (parse_document(tree, &document) < 0) {
- fprintf(stderr, "Failed to parse document\n");
- goto fail;
+ /* else show the 'display' part only */
+ printf("%s\n", ec_comp_item_get_display(item));
}
- yaml_document_delete(&document);
- yaml_parser_delete(&parser);
- fclose(file);
-
+ ec_comp_iter_free(iter);
+ ec_comp_free(comp);
+ ec_strvec_free(strvec);
return 0;
fail:
- yaml_document_delete(&document);
-fail_no_doc:
- yaml_parser_delete(&parser);
- if (file != NULL)
- fclose(file);
-
+ ec_comp_free(comp);
+ ec_strvec_free(strvec);
return -1;
}
int
main(int argc, char *argv[])
{
- struct enode_tree tree;
-
- memset(&tree, 0, sizeof(tree));
+ struct ec_node *node = NULL;
+ int ret;
- if (argc != 2) {
- fprintf(stderr, "Invalid args\n");
+ ret = parse_args(argc, argv);
+ if (ret < 0)
goto fail;
+
+ argc -= ret;
+ argv += ret;
+
+ if (ec_init() < 0) {
+ fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
+ return 1;
}
- if (parse_file(&tree, argv[1]) < 0) {
+
+ node = ec_yaml_import(input_file);
+ if (node == NULL) {
fprintf(stderr, "Failed to parse file\n");
goto fail;
}
- printf("root=%p len=%zd\n", tree.root, tree.table_len);
- ec_node_dump(stdout, tree.root);
- free_tree(&tree);
+ //ec_node_dump(stdout, node);
+
+ if (complete) {
+ if (complete_words(node, argc, argv) < 0)
+ goto fail;
+ } else {
+ if (interact(node) < 0)
+ goto fail;
+ }
+
+ ec_node_free(node);
return 0;
fail:
- free_tree(&tree);
+ ec_node_free(node);
return 1;
}