zoomsh: do not use readline for stdin (no tty) YAZ-724
[yaz-moved-to-github.git] / zoom / zoomsh.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5 /** \file zoomsh.c
6     \brief ZOOM C command line tool (shell)
7 */
8 #if HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #if HAVE_UNISTD_H
13 #include <unistd.h>
14 #endif
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <yaz/wrbuf.h>
20 #include <yaz/log.h>
21 #include <yaz/options.h>
22
23 #if HAVE_READLINE_READLINE_H
24 #include <readline/readline.h>
25 #endif
26 #if HAVE_READLINE_HISTORY_H
27 #include <readline/history.h>
28 #endif
29
30 #include <yaz/log.h>
31 #include <yaz/zoom.h>
32
33 #define MAX_CON 100
34
35 static void process_events(ZOOM_connection *c)
36 {
37     int i;
38
39     yaz_log(YLOG_DEBUG, "process_events");
40     while ((i = ZOOM_event(MAX_CON, c)) != 0)
41     {
42         int peek = ZOOM_connection_peek_event(c[i-1]);
43         int event = ZOOM_connection_last_event(c[i-1]);
44         yaz_log(YLOG_DEBUG, "no = %d peek = %d event = %d %s", i-1,
45                 peek,
46                 event,
47                 ZOOM_get_event_str(event));
48     }
49 }
50
51 static int next_token_chars(const char **cpp, const char **t_start,
52                             const char *tok_chars)
53 {
54     int len = 0;
55     const char *cp = *cpp;
56     while (*cp == ' ')
57         cp++;
58     if (*cp == '"')
59     {
60         cp++;
61         *t_start = cp;
62         while (*cp && *cp != '"')
63         {
64             cp++;
65             len++;
66         }
67         if (*cp)
68             cp++;
69     }
70     else
71     {
72         *t_start = cp;
73         while (*cp && !strchr(tok_chars, *cp))
74         {
75             cp++;
76             len++;
77         }
78         if (len == 0)
79             len = -1;
80     }
81     *cpp = cp;
82     return len;  /* return -1 if no token was read .. */
83 }
84
85 static int next_token(const char **cpp, const char **t_start)
86 {
87     return next_token_chars(cpp, t_start, "\r\n ");
88 }
89
90
91 static WRBUF next_token_new_wrbuf(const char **cpp)
92 {
93     WRBUF w = 0;
94     const char *start;
95     int len = next_token(cpp, &start);
96     if (len < 0)
97         return 0;
98     w = wrbuf_alloc();
99     if (len > 0)
100         wrbuf_write(w, start, len);
101     return w;
102 }
103
104 static int is_command(const char *cmd_str, const char *this_str, int this_len)
105 {
106     int cmd_len = strlen(cmd_str);
107     if (cmd_len != this_len)
108         return 0;
109     if (memcmp(cmd_str, this_str, cmd_len))
110         return 0;
111     return 1;
112 }
113
114 static int cmd_set(ZOOM_connection *c, ZOOM_resultset *r,
115                    ZOOM_options options,
116                    const char **args)
117 {
118     WRBUF key;
119     const char *val_buf;
120     int val_len;
121
122     if (!(key = next_token_new_wrbuf(args)))
123     {
124         printf("missing argument for set\n");
125         return 1;
126     }
127     val_len = next_token_chars(args, &val_buf, "");
128     if (val_len != -1)
129         ZOOM_options_setl(options, wrbuf_cstr(key), val_buf, val_len);
130     else
131         ZOOM_options_set(options, wrbuf_cstr(key), 0);
132     wrbuf_destroy(key);
133     return 0;
134 }
135
136 static int cmd_get(ZOOM_connection *c, ZOOM_resultset *r,
137                    ZOOM_options options,
138                    const char **args)
139 {
140     WRBUF key;
141     if (!(key = next_token_new_wrbuf(args)))
142     {
143         printf("missing argument for get\n");
144         return 1;
145     }
146     else
147     {
148         const char *val = ZOOM_options_get(options, wrbuf_cstr(key));
149         printf("%s = %s\n", wrbuf_cstr(key), val ? val : "<null>");
150         wrbuf_destroy(key);
151     }
152     return 0;
153 }
154
155 static int cmd_shell(ZOOM_connection *c, ZOOM_resultset *r,
156                      ZOOM_options options, const char **args)
157 {
158     int ret = system(*args);
159     if (ret)
160          printf("system command returned %d\n", ret);
161     return 0;
162 }
163
164 static int cmd_rget(ZOOM_connection *c, ZOOM_resultset *r,
165                     ZOOM_options options,
166                      const char **args)
167 {
168     WRBUF key;
169     if (!(key = next_token_new_wrbuf(args)))
170     {
171         printf("missing argument for get\n");
172         return 1;
173     }
174     else
175     {
176         int i;
177         for (i = 0; i<MAX_CON; i++)
178         {
179             const char *val;
180             if (!r[i])
181                 continue;
182
183             val = ZOOM_resultset_option_get(r[i], wrbuf_cstr(key));
184             printf("%s = %s\n", wrbuf_cstr(key), val ? val : "<null>");
185         }
186         wrbuf_destroy(key);
187     }
188     return 0;
189 }
190
191 static int cmd_close(ZOOM_connection *c, ZOOM_resultset *r,
192                      ZOOM_options options,
193                      const char **args)
194 {
195     WRBUF host;
196     int i;
197     host = next_token_new_wrbuf(args);
198     for (i = 0; i<MAX_CON; i++)
199     {
200         const char *h;
201         if (!c[i])
202             continue;
203         if (!host)
204         {
205             ZOOM_connection_destroy(c[i]);
206             c[i] = 0;
207         }
208         else if ((h = ZOOM_connection_option_get(c[i], "host"))
209                  && !strcmp(h, wrbuf_cstr(host)))
210         {
211             ZOOM_connection_destroy(c[i]);
212             c[i] = 0;
213         }
214     }
215     if (host)
216         wrbuf_destroy(host);
217     return 0;
218 }
219
220 static void display_records(ZOOM_connection c,
221                             ZOOM_resultset r,
222                             size_t start, size_t count, const char *type)
223 {
224     size_t i;
225     for (i = 0; i < count; i++)
226     {
227         size_t pos = i + start;
228         ZOOM_record rec = ZOOM_resultset_record(r, pos);
229         const char *db = ZOOM_record_get(rec, "database", 0);
230
231         if (ZOOM_record_error(rec, 0, 0, 0))
232         {
233             const char *msg;
234             const char *addinfo;
235             const char *diagset;
236             int error = ZOOM_record_error(rec, &msg, &addinfo, &diagset);
237
238             printf("%lld %s: %s (%s:%d) %s\n", (long long) pos,
239                    (db ? db : "unknown"),
240                    msg, diagset, error, addinfo ? addinfo : "none");
241         }
242         else
243         {
244             int len;
245             const char *render = ZOOM_record_get(rec, type, &len);
246             const char *syntax = ZOOM_record_get(rec, "syntax", 0);
247             const char *schema = ZOOM_record_get(rec, "schema", 0);
248             /* if rec is non-null, we got a record for display */
249             if (rec)
250             {
251                 printf("%lld database=%s syntax=%s schema=%s\n",
252                        (long long) pos, (db ? db : "unknown"), syntax,
253                        schema ? schema : "unknown");
254                 if (render)
255                 {
256                     if (fwrite(render, 1, len, stdout) != (size_t) len)
257                     {
258                         printf("write to stdout failed\n");
259                     }
260                 }
261                 printf("\n");
262             }
263         }
264     }
265 }
266
267 static int cmd_show(ZOOM_connection *c, ZOOM_resultset *r,
268                     ZOOM_options options,
269                     const char **args)
270 {
271     int i;
272     size_t start = 0, count = 1;
273     const char *type = "render";
274     WRBUF render_str = 0;
275     int ret = 0;
276
277     {
278         WRBUF tmp;
279
280         if ((tmp = next_token_new_wrbuf(args)))
281         {
282             start = atoi(wrbuf_cstr(tmp));
283             wrbuf_destroy(tmp);
284         }
285
286         if ((tmp = next_token_new_wrbuf(args)))
287         {
288             count = atoi(wrbuf_cstr(tmp));
289             wrbuf_destroy(tmp);
290         }
291         render_str = next_token_new_wrbuf(args);
292     }
293     if (render_str)
294         type = wrbuf_cstr(render_str);
295
296     for (i = 0; i < MAX_CON; i++)
297         ZOOM_resultset_records(r[i], 0, start, count);
298     process_events(c);
299
300     for (i = 0; i < MAX_CON; i++)
301     {
302         int error;
303         const char *errmsg, *addinfo, *dset;
304         /* display errors if any */
305         if (!c[i])
306             continue;
307         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
308         {
309             printf("%s error: %s (%s:%d) %s\n",
310                    ZOOM_connection_option_get(c[i], "host"), errmsg,
311                    dset, error, addinfo);
312             ret = 1;
313         }
314         else if (r[i])
315         {
316             /* OK, no major errors. Display records... */
317             display_records(c[i], r[i], start, count, type);
318         }
319     }
320     if (render_str)
321         wrbuf_destroy(render_str);
322     return ret;
323 }
324
325 static void display_facets(ZOOM_facet_field *facets, int count) {
326     int index;
327     printf("Facets: \n");
328     for (index = 0; index <  count; index++) {
329         int term_index;
330         const char *facet_name = ZOOM_facet_field_name(facets[index]);
331         printf("  %s: \n", facet_name);
332         for (term_index = 0; term_index < ZOOM_facet_field_term_count(facets[index]); term_index++) {
333             int freq = 0;
334             const char *term = ZOOM_facet_field_get_term(facets[index], term_index, &freq);
335             printf("    %s(%d) \n", term,  freq);
336         }
337     }
338 }
339
340 static int cmd_facets(ZOOM_connection *c, ZOOM_resultset *r,
341                       ZOOM_options options,
342                       const char **args)
343 {
344     int i;
345     int ret = 0;
346
347     process_events(c);
348
349     for (i = 0; i < MAX_CON; i++)
350     {
351         int error;
352         const char *errmsg, *addinfo, *dset;
353         /* display errors if any */
354         if (!c[i])
355             continue;
356         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
357         {
358             printf("%s error: %s (%s:%d) %s\n",
359                    ZOOM_connection_option_get(c[i], "host"), errmsg,
360                    dset, error, addinfo);
361             ret = 1;
362         }
363         else if (r[i])
364         {
365             int num_facets = ZOOM_resultset_facets_size(r[i]);
366             if (num_facets) {
367                 ZOOM_facet_field  *facets = ZOOM_resultset_facets(r[i]);
368                 display_facets(facets, num_facets);
369             }
370         }
371     }
372     return ret;
373 }
374
375 static int cmd_suggestions(ZOOM_connection *c, ZOOM_resultset *r, ZOOM_options options, const char **args)
376 {
377     int i;
378     int ret = 0;
379
380     process_events(c);
381
382     for (i = 0; i < MAX_CON; i++)
383     {
384         int error;
385         const char *errmsg, *addinfo, *dset;
386         /* display errors if any */
387         if (!c[i])
388             continue;
389         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
390         {
391             printf("%s error: %s (%s:%d) %s\n",
392                    ZOOM_connection_option_get(c[i], "host"), errmsg,
393                    dset, error, addinfo);
394             ret = 1;
395         }
396         else if (r[i])
397         {
398             const char *suggestions = ZOOM_resultset_option_get(r[i], "suggestions");
399             if (suggestions) {
400                 printf("Suggestions: \n%s\n", suggestions);
401             }
402         }
403     }
404     return ret;
405 }
406
407
408 static int cmd_ext(ZOOM_connection *c, ZOOM_resultset *r,
409                    ZOOM_options options,
410                    const char **args)
411 {
412     ZOOM_package p[MAX_CON];
413     int i;
414     int ret = 0;
415     WRBUF ext_type_str = next_token_new_wrbuf(args);
416
417     if (!ext_type_str)
418     {
419         printf("es: missing type "
420                "(itemorder, create, drop, commit, update, xmlupdate)\n");
421         return 1;
422     }
423     for (i = 0; i<MAX_CON; i++)
424     {
425         if (c[i])
426         {
427             p[i] = ZOOM_connection_package(c[i], 0);
428             ZOOM_package_send(p[i], ext_type_str ? wrbuf_cstr(ext_type_str):0);
429         }
430         else
431             p[i] = 0;
432     }
433
434     process_events(c);
435
436     for (i = 0; i<MAX_CON; i++)
437     {
438         int error;
439         const char *errmsg, *addinfo, *dset;
440         /* display errors if any */
441         if (!p[i])
442             continue;
443         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
444         {
445             printf("%s error: %s (%s:%d) %s\n",
446                    ZOOM_connection_option_get(c[i], "host"), errmsg,
447                    dset, error, addinfo);
448             ret = 1;
449         }
450         else if (p[i])
451         {
452             const char *v;
453             printf("ok\n");
454             v = ZOOM_package_option_get(p[i], "targetReference");
455             if (v)
456                 printf("targetReference: %s\n", v);
457             v = ZOOM_package_option_get(p[i], "xmlUpdateDoc");
458             if (v)
459                 printf("xmlUpdateDoc: %s\n", v);
460         }
461         ZOOM_package_destroy(p[i]);
462     }
463     if (ext_type_str)
464         wrbuf_destroy(ext_type_str);
465     return ret;
466 }
467
468 static int cmd_debug(ZOOM_connection *c, ZOOM_resultset *r,
469                      ZOOM_options options,
470                      const char **args)
471 {
472     yaz_log_init_level(YLOG_ALL);
473     return 0;
474 }
475
476 static int cmd_search(ZOOM_connection *c, ZOOM_resultset *r,
477                       ZOOM_options options,
478                       const char **args)
479 {
480     ZOOM_query s;
481     const char *query_str = *args;
482     int i;
483     int ret = 0;
484
485     s = ZOOM_query_create();
486     while (*query_str == ' ')
487         query_str++;
488     if (memcmp(query_str, "cql:", 4) == 0)
489     {
490         ZOOM_query_cql(s, query_str + 4);
491     }
492     else if (ZOOM_query_prefix(s, query_str))
493     {
494         printf("Bad PQF: %s\n", query_str);
495         ZOOM_query_destroy(s);
496         return 1;
497     }
498     for (i = 0; i<MAX_CON; i++)
499     {
500
501         if (c[i])
502         {
503             ZOOM_resultset_destroy(r[i]);
504             r[i] = 0;
505         }
506         if (c[i])
507             r[i] = ZOOM_connection_search(c[i], s);
508     }
509     ZOOM_query_destroy(s);
510
511     process_events(c);
512
513     for (i = 0; i<MAX_CON; i++)
514     {
515         int error;
516         const char *errmsg, *addinfo, *dset;
517         /* display errors if any */
518         if (!c[i])
519             continue;
520         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
521         {
522             printf("%s error: %s (%s:%d) %s\n",
523                    ZOOM_connection_option_get(c[i], "host"), errmsg,
524                    dset, error, addinfo);
525             ret = 1;
526         }
527         else if (r[i])
528         {
529             /* OK, no major errors. Look at the result count */
530             int start = ZOOM_options_get_int(options, "start", 0);
531             int count = ZOOM_options_get_int(options, "count", 0);
532             int facet_num;
533
534             printf("%s: %lld hits\n", ZOOM_connection_option_get(c[i], "host"),
535                    (long long int) ZOOM_resultset_size(r[i]));
536
537             facet_num = ZOOM_resultset_facets_size(r[i]);
538             if (facet_num)
539             {
540                 ZOOM_facet_field *facets = ZOOM_resultset_facets(r[i]);
541                 int facet_idx;
542                 for (facet_idx = 0; facet_idx < facet_num; facet_idx++)
543                 {
544                     const char *name = ZOOM_facet_field_name(facets[facet_idx]);
545                     size_t term_idx;
546                     size_t term_num = ZOOM_facet_field_term_count(facets[facet_idx]);
547                     printf("facet: %s\n", name);
548                     for (term_idx = 0; term_idx < term_num; term_idx++ )
549                     {
550                         int freq;
551                         const char *term =
552                             ZOOM_facet_field_get_term(facets[facet_idx], term_idx, &freq);
553                         printf("term: %s %d\n", term, freq);
554                     }
555                 }
556             }
557             /* and display */
558             display_records(c[i], r[i], start, count, "render");
559         }
560     }
561     return ret;
562 }
563
564 static int cmd_scan(ZOOM_connection *c, ZOOM_resultset *r,
565                     ZOOM_options options,
566                     const char **args)
567 {
568     const char *query_str = *args;
569     ZOOM_query query = ZOOM_query_create();
570     int i;
571     int ret = 0;
572     ZOOM_scanset s[MAX_CON];
573
574     while (*query_str == ' ')
575         query_str++;
576
577     if (memcmp(query_str, "cql:", 4) == 0)
578     {
579         ZOOM_query_cql(query, query_str + 4);
580     }
581     else if (ZOOM_query_prefix(query, query_str))
582     {
583         printf("Bad PQF: %s\n", query_str);
584         ZOOM_query_destroy(query);
585         return 1;
586     }
587
588     for (i = 0; i<MAX_CON; i++)
589     {
590         if (c[i])
591             s[i] = ZOOM_connection_scan1(c[i], query);
592         else
593             s[i] = 0;
594     }
595     ZOOM_query_destroy(query);
596
597     process_events(c);
598
599     for (i = 0; i<MAX_CON; i++)
600     {
601         int error;
602         const char *errmsg, *addinfo, *dset;
603         /* display errors if any */
604         if (!c[i])
605             continue;
606         if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
607         {
608             printf("%s error: %s (%s:%d) %s\n",
609                    ZOOM_connection_option_get(c[i], "host"), errmsg,
610                    dset, error, addinfo);
611             ret = 1;
612         }
613         if (s[i])
614         {
615             size_t p, sz = ZOOM_scanset_size(s[i]);
616             for (p = 0; p < sz; p++)
617             {
618                 size_t occ = 0;
619                 size_t len = 0;
620                 const char *term = ZOOM_scanset_display_term(s[i], p,
621                                                              &occ, &len);
622                 printf("%.*s %lld\n", (int) len, term, (long long int) occ);
623             }
624             ZOOM_scanset_destroy(s[i]);
625         }
626     }
627     return ret;
628 }
629
630 static int cmd_sort(ZOOM_connection *c, ZOOM_resultset *r,
631                     ZOOM_options options,
632                     const char **args)
633 {
634     const char *sort_spec = *args;
635     int i;
636     int ret = 0;
637
638     while (*sort_spec == ' ')
639         sort_spec++;
640
641     for (i = 0; i<MAX_CON; i++)
642     {
643         if (r[i])
644             ZOOM_resultset_sort(r[i], "yaz", sort_spec);
645     }
646     process_events(c);
647     return ret;
648 }
649
650 static int cmd_help(ZOOM_connection *c, ZOOM_resultset *r,
651                     ZOOM_options options,
652                     const char **args)
653 {
654     printf("connect <zurl>\n");
655     printf("search <pqf>\n");
656     printf("show [<start> [<count> [<type]]]\n");
657     printf("facets\n");
658     printf("scan <term>\n");
659     printf("quit\n");
660     printf("close <zurl>\n");
661     printf("ext <type>\n");
662     printf("set <option> [<value>]\n");
663     printf("get <option>\n");
664     printf("shell cmdline\n");
665     printf("\n");
666     printf("options:\n");
667     printf(" start\n");
668     printf(" count\n");
669     printf(" databaseName\n");
670     printf(" preferredRecordSyntax\n");
671     printf(" proxy\n");
672     printf(" elementSetName\n");
673     printf(" maximumRecordSize\n");
674     printf(" preferredRecordSize\n");
675     printf(" async\n");
676     printf(" piggyback\n");
677     printf(" group\n");
678     printf(" user\n");
679     printf(" password\n");
680     printf(" implementationName\n");
681     printf(" charset\n");
682     printf(" lang\n");
683     printf(" timeout\n");
684     printf(" facets\n");
685     printf(" extraArgs\n");
686     printf(" suggestions\n");
687     return 0;
688 }
689
690 static int cmd_connect(ZOOM_connection *c, ZOOM_resultset *r,
691                        ZOOM_options options,
692                        const char **args)
693 {
694     int ret = 0;
695     int error;
696     const char *errmsg, *addinfo, *dset;
697     int j, i;
698     WRBUF host = next_token_new_wrbuf(args);
699     if (!host)
700     {
701         printf("missing host after connect\n");
702         return 1;
703     }
704     for (j = -1, i = 0; i<MAX_CON; i++)
705     {
706         const char *h;
707         if (c[i] && (h = ZOOM_connection_option_get(c[i], "host")) &&
708             !strcmp(h, wrbuf_cstr(host)))
709         {
710             ZOOM_connection_destroy(c[i]);
711             break;
712         }
713         else if (c[i] == 0 && j == -1)
714             j = i;
715     }
716     if (i == MAX_CON)  /* no match .. */
717     {
718         if (j == -1)
719         {
720             printf("no more connection available\n");
721             wrbuf_destroy(host);
722             return 1;
723         }
724         i = j;   /* OK, use this one is available */
725     }
726     c[i] = ZOOM_connection_create(options);
727     ZOOM_connection_connect(c[i], wrbuf_cstr(host), 0);
728
729     if ((error = ZOOM_connection_error_x(c[i], &errmsg, &addinfo, &dset)))
730     {
731         printf("%s error: %s (%s:%d) %s\n",
732                ZOOM_connection_option_get(c[i], "host"), errmsg,
733                dset, error, addinfo);
734         ret = 1;
735     }
736     wrbuf_destroy(host);
737     return ret;
738 }
739
740 /** \brief parse and execute zoomsh command
741     \param c connections
742     \param r result sets
743     \param options ZOOM options
744     \param buf command string and arguments
745     \retval 0 OK
746     \retval 1 failure to execute
747     \retval -1 EOF (no more commands or quit seen)
748 */
749 static int cmd_parse(ZOOM_connection *c, ZOOM_resultset *r,
750                      ZOOM_options options,
751                      const char **buf)
752 {
753     int cmd_len;
754     const char *cmd_str;
755     int ret = 0;
756
757     cmd_len = next_token(buf, &cmd_str);
758     if (cmd_len < 0)
759         return 0;
760     if (is_command("quit", cmd_str, cmd_len))
761         return -1;
762     else if (is_command("set", cmd_str, cmd_len))
763         ret = cmd_set(c, r, options, buf);
764     else if (is_command("get", cmd_str, cmd_len))
765         ret = cmd_get(c, r, options, buf);
766     else if (is_command("rget", cmd_str, cmd_len))
767         ret = cmd_rget(c, r, options, buf);
768     else if (is_command("connect", cmd_str, cmd_len))
769         ret = cmd_connect(c, r, options, buf);
770     else if (is_command("open", cmd_str, cmd_len))
771         ret = cmd_connect(c, r, options, buf);
772     else if (is_command("search", cmd_str, cmd_len))
773         ret = cmd_search(c, r, options, buf);
774     else if (is_command("facets", cmd_str, cmd_len))
775         ret = cmd_facets(c, r, options, buf);
776     else if (is_command("find", cmd_str, cmd_len))
777         ret = cmd_search(c, r, options, buf);
778     else if (is_command("show", cmd_str, cmd_len))
779         ret = cmd_show(c, r, options, buf);
780     else if (is_command("suggestions", cmd_str, cmd_len))
781         ret = cmd_suggestions(c, r, options, buf);
782     else if (is_command("close", cmd_str, cmd_len))
783         ret = cmd_close(c, r, options, buf);
784     else if (is_command("help", cmd_str, cmd_len))
785         ret = cmd_help(c, r, options, buf);
786     else if (is_command("ext", cmd_str, cmd_len))
787         ret = cmd_ext(c, r, options, buf);
788     else if (is_command("debug", cmd_str, cmd_len))
789         ret = cmd_debug(c, r, options, buf);
790     else if (is_command("scan", cmd_str, cmd_len))
791         ret = cmd_scan(c, r, options, buf);
792     else if (is_command("sort", cmd_str, cmd_len))
793         ret = cmd_sort(c, r, options, buf);
794     else if (is_command("shell", cmd_str, cmd_len))
795         ret = cmd_shell(c, r, options, buf);
796     else
797     {
798         printf("unknown command %.*s\n", cmd_len, cmd_str);
799         ret = 1;
800     }
801     return ret;
802 }
803
804 static int shell(ZOOM_connection *c, ZOOM_resultset *r,
805                  ZOOM_options options, int exit_on_error)
806 {
807     int res = 0;
808     while (res == 0)
809     {
810         char buf[100000];
811         char *cp;
812         const char *bp = buf;
813         char *line_in = 0;
814 #if HAVE_READLINE_READLINE_H
815         if (isatty(0))
816         {
817             line_in = readline("ZOOM>");
818             if (!line_in)
819             {
820                 putchar('\n');
821                 res = -1;
822                 break;
823             }
824 #if HAVE_READLINE_HISTORY_H
825             if (*line_in)
826                 add_history(line_in);
827 #endif
828             if (strlen(line_in) > sizeof(buf)-1)
829             {
830                 printf("Input line too long\n");
831                 res = 1;
832                 break;
833             }
834             strcpy(buf,line_in);
835             free(line_in);
836         }
837 #endif
838         if (!line_in) /* no line buffer via readline or not enabled at all */
839         {
840             printf("ZOOM>"); fflush(stdout);
841             if (!fgets(buf, sizeof(buf)-1, stdin))
842             {
843                 res = -1;
844                 break;
845             }
846         }
847         if ((cp = strchr(buf, '\n')))
848             *cp = '\0';
849         res = cmd_parse(c, r, options, &bp);
850         if (res == -1)
851             break;
852         if (!exit_on_error && res > 0)
853             res = 0;
854     }
855     return res;
856 }
857
858 static int zoomsh(int argc, char **argv)
859 {
860     ZOOM_options zoom_options = ZOOM_options_create();
861     int i, res = 0; /* -1: EOF; 0 = OK, > 0 ERROR */
862     int exit_on_error = 0;
863     ZOOM_connection z39_con[MAX_CON];
864     ZOOM_resultset  z39_res[MAX_CON];
865
866     for (i = 0; i<MAX_CON; i++)
867     {
868         z39_con[i] = 0;
869         z39_res[i] = 0;
870     }
871     while (res == 0)
872     {
873         int mask;
874         char *arg = 0;
875         int option_ret = options("ev:", argv, argc, &arg);
876         const char *bp = arg;
877         switch (option_ret)
878         {
879         case 0:
880             res = cmd_parse(z39_con, z39_res, zoom_options, &bp);
881             /* returns res == -1 on quit */
882             if (!exit_on_error && res > 0)
883                 res = 0;  /* hide error */
884             break;
885         case YAZ_OPTIONS_EOF:
886             res = shell(z39_con, z39_res, zoom_options, exit_on_error);
887             break;
888         case 'e':
889             exit_on_error = 1;
890             break;
891         case 'v':
892             mask = yaz_log_mask_str(arg);
893             yaz_log_init_level(mask);
894             break;
895         default:
896             fprintf(stderr, "zoomsh: [-e] [-v] [commands]\n");
897             res = 1;
898         }
899     }
900
901     for (i = 0; i<MAX_CON; i++)
902     {
903         ZOOM_connection_destroy(z39_con[i]);
904         ZOOM_resultset_destroy(z39_res[i]);
905     }
906     ZOOM_options_destroy(zoom_options);
907     if (res == -1) /* quit .. which is not an error */
908         res = 0;
909     return res;
910 }
911
912 int main(int argc, char **argv)
913 {
914     int ret = zoomsh(argc, argv);
915     exit(ret);
916 }
917 /*
918  * Local variables:
919  * c-basic-offset: 4
920  * c-file-style: "Stroustrup"
921  * indent-tabs-mode: nil
922  * End:
923  * vim: shiftwidth=4 tabstop=8 expandtab
924  */
925