Libecoli  0.11.3
Extensible COmmand LIne library
readline/main.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4 
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 
39 #include <readline/history.h>
40 #include <readline/readline.h>
41 
42 #include <ecoli.h>
43 
44 static struct ec_node *commands;
45 
46 static char *my_completion_entry(const char *s, int state)
47 {
48  static struct ec_comp *c;
49  static struct ec_comp_item *item;
50  enum ec_comp_type item_type;
51  const char *item_str, *item_display;
52 
53  (void)s;
54 
55  /* Don't append a quote. Note: there are still some bugs when
56  * completing a quoted token. */
57  rl_completion_suppress_quote = 1;
58  rl_completer_quote_characters = "\"'";
59 
60  if (state == 0) {
61  char *line;
62 
63  ec_comp_free(c);
64  line = strdup(rl_line_buffer);
65  if (line == NULL)
66  return NULL;
67  line[rl_point] = '\0';
68 
69  c = ec_complete(commands, line);
70  free(line);
71  if (c == NULL)
72  return NULL;
73 
74  free(item);
76  if (item == NULL)
77  return NULL;
78  } else {
80  if (item == NULL)
81  return NULL;
82  }
83 
84  item_str = ec_comp_item_get_str(item);
86  /* don't add the trailing space for partial completions */
87  if (state == 0) {
88  item_type = ec_comp_item_get_type(item);
89  if (item_type == EC_COMP_FULL)
90  rl_completion_suppress_append = 0;
91  else
92  rl_completion_suppress_append = 1;
93  }
94 
95  return strdup(item_str);
96  } else if (rl_completion_type == '?') {
97  /* on second try only show the display part */
98  item_display = ec_comp_item_get_display(item);
99  return strdup(item_display);
100  }
101 
102  return strdup(item_str);
103 }
104 
105 static char **my_attempted_completion(const char *text, int start, int end)
106 {
107  (void)start;
108  (void)end;
109 
110  /* remove default file completion */
111  rl_attempted_completion_over = 1;
112 
113  return rl_completion_matches(text, my_completion_entry);
114 }
115 
116 /* this function builds the help string */
117 static char *get_node_help(const struct ec_comp_item *item)
118 {
119  const struct ec_comp_group *grp;
120  const struct ec_pnode *pstate;
121  const struct ec_node *node;
122  char *help = NULL;
123  const char *node_help = NULL;
124  char *node_desc = NULL;
125 
126  grp = ec_comp_item_get_grp(item);
127  for (pstate = ec_comp_group_get_pstate(grp); pstate != NULL;
128  pstate = ec_pnode_get_parent(pstate)) {
129  node = ec_pnode_get_node(pstate);
130  if (node_help == NULL)
131  node_help = ec_dict_get(ec_node_attrs(node), "help");
132  if (node_desc == NULL)
133  node_desc = ec_node_desc(node);
134  }
135 
136  if (node_help == NULL)
137  node_help = "-";
138  if (node_desc == NULL)
139  return NULL;
140 
141  if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0)
142  return NULL;
143 
144  free(node_desc);
145 
146  return help;
147 }
148 
149 static int show_help(int ignore, int invoking_key)
150 {
151  const struct ec_comp_group *grp, *prev_grp = NULL;
152  struct ec_comp_item *item = NULL;
153  struct ec_comp *c = NULL;
154  struct ec_pnode *p = NULL;
155  char *line = NULL;
156  size_t count = 0;
157  char **helps = NULL;
158  int match = 0;
159  int ret = 1;
160  int cols;
161 
162  (void)ignore;
163  (void)invoking_key;
164 
165  line = strdup(rl_line_buffer);
166  if (line == NULL)
167  goto fail;
168 
169  /* check if the current line matches */
170  p = ec_parse(commands, line);
171  if (ec_pnode_matches(p))
172  match = 1;
173  ec_pnode_free(p);
174  p = NULL;
175 
176  /* complete at current cursor position */
177  line[rl_point] = '\0';
178  c = ec_complete(commands, line);
179  free(line);
180  line = NULL;
181  if (c == NULL)
182  goto fail;
183 
184  /* strangely, rl_display_match_list() expects first index at 1 */
185  helps = calloc(match + 1, sizeof(char *));
186  if (helps == NULL)
187  goto fail;
188  if (match)
189  helps[1] = "<return>";
190 
191  /* let's display one contextual help per node */
193  char **tmp;
194 
195  /* keep one help per group, skip other items */
196  grp = ec_comp_item_get_grp(item);
197  if (grp == prev_grp)
198  continue;
199 
200  prev_grp = grp;
201 
202  tmp = realloc(helps, (count + match + 2) * sizeof(char *));
203  if (tmp == NULL)
204  goto fail;
205  helps = tmp;
206  helps[count + match + 1] = get_node_help(item);
207  count++;
208  }
209 
210  /* ensure not more than 1 entry per line */
211  rl_get_screen_size(NULL, &cols);
212  rl_display_match_list(helps, count + match, cols);
213  rl_forced_update_display();
214 
215  ret = 0;
216 
217 fail:
218  free(item);
219  ec_pnode_free(p);
220  free(line);
221  ec_comp_free(c);
222  if (helps != NULL) {
223  while (count--)
224  free(helps[count + match + 1]);
225  }
226  free(helps);
227 
228  return ret;
229 }
230 
231 static int create_commands(void)
232 {
233  struct ec_node *cmdlist = NULL, *cmd = NULL;
234 
235  cmdlist = ec_node("or", EC_NO_ID);
236  if (cmdlist == NULL)
237  goto fail;
238 
239  cmd = EC_NODE_SEQ(
240  EC_NO_ID,
241  ec_node_str(EC_NO_ID, "hello"),
242  EC_NODE_OR(
243  "name",
244  ec_node_str("john", "john"),
245  ec_node_str(EC_NO_ID, "johnny"),
246  ec_node_str(EC_NO_ID, "mike")
247  ),
248  ec_node_option(EC_NO_ID, ec_node_int("int", 0, 10, 10))
249  );
250  if (cmd == NULL)
251  goto fail;
252  ec_dict_set(ec_node_attrs(cmd), "help", "say hello to someone several times", NULL);
253  ec_dict_set(
254  ec_node_attrs(ec_node_find(cmd, "john")), "help", "specific help for john", NULL
255  );
256  ec_dict_set(
257  ec_node_attrs(ec_node_find(cmd, "name")), "help", "the name of the person", NULL
258  );
259  ec_dict_set(ec_node_attrs(ec_node_find(cmd, "int")), "help", "an integer (0-10)", NULL);
260  if (ec_node_or_add(cmdlist, cmd) < 0)
261  goto fail;
262 
263  cmd = EC_NODE_CMD(
264  EC_NO_ID,
265  "good morning name [count]",
266  EC_NODE_CMD("name", "bob|bobby|michael"),
267  ec_node_int("count", 0, 10, 10)
268  );
269  if (cmd == NULL)
270  goto fail;
271  ec_dict_set(ec_node_attrs(cmd), "help", "say good morning to someone several times", NULL);
272  ec_dict_set(ec_node_attrs(ec_node_find(cmd, "name")), "help", "the person to greet", NULL);
273  ec_dict_set(
274  ec_node_attrs(ec_node_find(cmd, "count")),
275  "help",
276  "how many times to greet (0-10)",
277  NULL
278  );
279  if (ec_node_or_add(cmdlist, cmd) < 0)
280  goto fail;
281 
282  cmd = EC_NODE_CMD(EC_NO_ID, "buy potatoes,carrots,pumpkins");
283  if (cmd == NULL)
284  goto fail;
285  ec_dict_set(ec_node_attrs(cmd), "help", "buy some vegetables", NULL);
286  if (ec_node_or_add(cmdlist, cmd) < 0)
287  goto fail;
288 
289  cmd = EC_NODE_CMD(
290  EC_NO_ID,
291  "eat vegetables",
292  ec_node_many(
293  "vegetables",
294  EC_NODE_OR(
295  EC_NO_ID,
296  ec_node_str(EC_NO_ID, "potatoes"),
299  ),
300  1,
301  0
302  )
303  );
304  if (cmd == NULL)
305  goto fail;
306  ec_dict_set(ec_node_attrs(cmd), "help", "eat vegetables (take some more potatoes)", NULL);
307  if (ec_node_or_add(cmdlist, cmd) < 0)
308  goto fail;
309 
310  cmd = EC_NODE_SEQ(EC_NO_ID, ec_node_str(EC_NO_ID, "bye"));
311  ec_dict_set(ec_node_attrs(cmd), "help", "say bye", NULL);
312  if (ec_node_or_add(cmdlist, cmd) < 0)
313  goto fail;
314 
315  cmd = EC_NODE_SEQ(EC_NO_ID, ec_node_str(EC_NO_ID, "load"), ec_node("file", EC_NO_ID));
316  ec_dict_set(ec_node_attrs(cmd), "help", "load a file", NULL);
317  if (ec_node_or_add(cmdlist, cmd) < 0)
318  goto fail;
319 
320  commands = ec_node_sh_lex(EC_NO_ID, cmdlist);
321  if (commands == NULL)
322  goto fail;
323 
324  return 0;
325 
326 fail:
327  fprintf(stderr, "cannot initialize nodes\n");
328  ec_node_free(cmdlist);
329  return -1;
330 }
331 
332 int main(void)
333 {
334  struct ec_pnode *p;
335  char *line;
336 
337  if (ec_init() < 0) {
338  fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
339  return 1;
340  }
341 
342  if (create_commands() < 0)
343  return 1;
344 
345  rl_bind_key('?', show_help);
346  rl_attempted_completion_function = my_attempted_completion;
347 
348  while (1) {
349  line = readline("> ");
350  if (line == NULL)
351  break;
352 
353  p = ec_parse(commands, line);
354  ec_pnode_dump(stdout, p);
355  add_history(line);
356  ec_pnode_free(p);
357  }
358 
359  ec_node_free(commands);
360  return 0;
361 }
struct ec_comp * ec_comp(void)
const struct ec_pnode * ec_comp_group_get_pstate(const struct ec_comp_group *grp)
const char * ec_comp_item_get_display(const struct ec_comp_item *item)
struct ec_comp_item * ec_comp_iter_next(struct ec_comp_item *item, enum ec_comp_type type)
const struct ec_comp_group * ec_comp_item_get_grp(const struct ec_comp_item *item)
struct ec_comp_item * ec_comp_iter_first(const struct ec_comp *comp, enum ec_comp_type type)
const char * ec_comp_item_get_str(const struct ec_comp_item *item)
size_t ec_comp_count(const struct ec_comp *comp, enum ec_comp_type type)
void ec_comp_free(struct ec_comp *comp)
struct ec_comp * ec_complete(const struct ec_node *node, const char *str)
ec_comp_type
Definition: complete.h:47
#define EC_COMP_FOREACH(item, comp, type)
Definition: complete.h:522
enum ec_comp_type ec_comp_item_get_type(const struct ec_comp_item *item)
@ EC_COMP_FULL
Definition: complete.h:49
@ EC_COMP_PARTIAL
Definition: complete.h:50
@ EC_COMP_UNKNOWN
Definition: complete.h:48
void * ec_dict_get(const struct ec_dict *dict, const char *key)
int ec_dict_set(struct ec_dict *dict, const char *key, void *val, ec_dict_elt_free_t free_cb)
int ec_init(void)
#define EC_NODE_CMD(args...)
Definition: node_cmd.h:30
struct ec_node * ec_node_int(const char *id, int64_t min, int64_t max, unsigned int base)
struct ec_node * ec_node_many(const char *id, struct ec_node *child, unsigned int min, unsigned int max)
struct ec_node * ec_node_once(const char *id, struct ec_node *child)
struct ec_node * ec_node_option(const char *id, struct ec_node *node)
int ec_node_or_add(struct ec_node *node, struct ec_node *child)
#define EC_NODE_OR(args...)
Definition: node_or.h:23
#define EC_NODE_SEQ(args...)
Definition: node_seq.h:29
struct ec_node * ec_node_sh_lex(const char *id, struct ec_node *child)
struct ec_node * ec_node_str(const char *id, const char *str)
#define EC_NO_ID
Definition: node.h:64
char * ec_node_desc(const struct ec_node *node)
struct ec_dict * ec_node_attrs(const struct ec_node *node)
struct ec_node * ec_node_find(struct ec_node *node, const char *id)
struct ec_node * ec_node(const char *typename, const char *id)
void ec_node_free(struct ec_node *node)
bool ec_pnode_matches(const struct ec_pnode *pnode)
void ec_pnode_dump(FILE *out, const struct ec_pnode *pnode)
struct ec_pnode * ec_pnode_get_parent(const struct ec_pnode *pnode)
void ec_pnode_free(struct ec_pnode *pnode)
struct ec_pnode * ec_parse(const struct ec_node *node, const char *str)
struct ec_pnode * ec_pnode(const struct ec_node *node)
const struct ec_node * ec_pnode_get_node(const struct ec_pnode *pnode)