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