SiP and "solo overrides mutes" tweak:
[ardour.git] / libs / evoral / src / libsmf / smfsh.c
1 /*-
2  * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
15  * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
18  * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27
28 /**
29  * \file
30  *
31  * "SMF shell", command line utility.
32  */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <sysexits.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <assert.h>
41 #include "smf.h"
42 #include "config.h"
43
44 #ifdef HAVE_LIBREADLINE
45 #include <readline/readline.h>
46 #include <readline/history.h>
47 #endif
48
49 smf_track_t *selected_track = NULL;
50 smf_event_t *selected_event = NULL;
51 smf_t *smf = NULL;
52 char *last_file_name = NULL;
53
54 #define COMMAND_LENGTH 10
55
56 static void
57 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
58 {
59         if (strcmp(log_domain, "smfsh") == 0)
60                 fprintf(stderr, "%s\n", message);
61         else
62                 fprintf(stderr, "%s: %s\n", log_domain, message);
63 }
64
65 static int cmd_track(char *arg);
66
67 static int
68 cmd_load(char *file_name)
69 {
70         char *decoded;
71
72         if (file_name == NULL) {
73                 if (last_file_name == NULL) {
74                         g_critical("Please specify file name.");
75                         return (-1);
76                 }
77
78                 file_name = strdup(last_file_name);
79         } else {
80                 file_name = strdup(file_name);
81         }
82
83         selected_track = NULL;
84         selected_event = NULL;
85
86         if (smf != NULL) {
87                 smf_delete(smf);
88                 smf = NULL;
89         }
90
91         if (last_file_name != NULL)
92                 free(last_file_name);
93         last_file_name = strdup(file_name);
94
95         smf = smf_load(file_name);
96         if (smf == NULL) {
97                 g_critical("Couldn't load '%s'.", file_name);
98
99                 smf = smf_new();
100                 if (smf == NULL) {
101                         g_critical("Cannot initialize smf_t.");
102                         return (-1);
103                 }
104
105                 return (-2);
106         }
107
108         g_message("File '%s' loaded.", file_name);
109         decoded = smf_decode(smf);
110         g_message("%s.", decoded);
111         free(decoded);
112
113         cmd_track("1");
114
115         free(file_name);
116
117         return (0);
118 }
119
120 static int
121 cmd_save(char *file_name)
122 {
123         int ret;
124
125         if (file_name == NULL) {
126                 if (last_file_name == NULL) {
127                         g_critical("Please specify file name.");
128                         return (-1);
129                 }
130
131                 file_name = strdup(last_file_name);
132         } else {
133                 file_name = strdup(file_name);
134         }
135
136         if (last_file_name != NULL)
137                 free(last_file_name);
138         last_file_name = strdup(file_name);
139
140         ret = smf_save(smf, file_name);
141         if (ret) {
142                 g_critical("Couldn't save '%s'", file_name);
143                 return (-1);
144         }
145
146         g_message("File '%s' saved.", file_name);
147
148         free(file_name);
149
150         return (0);
151 }
152
153 static int
154 cmd_ppqn(char *new_ppqn)
155 {
156         int tmp;
157         char *end;
158
159         if (new_ppqn == NULL) {
160                 g_message("Pulses Per Quarter Note (aka Division) is %d.", smf->ppqn);
161         } else {
162                 tmp = strtol(new_ppqn, &end, 10);
163                 if (end - new_ppqn != strlen(new_ppqn)) {
164                         g_critical("Invalid PPQN, garbage characters after the number.");
165                         return (-1);
166                 }
167
168                 if (tmp <= 0) {
169                         g_critical("Invalid PPQN, valid values are greater than zero.");
170                         return (-2);
171                 }
172
173                 if (smf_set_ppqn(smf, tmp)) {
174                         g_message("smf_set_ppqn failed.");
175                         return (-3);
176                 }
177
178                 g_message("Pulses Per Quarter Note changed to %d.", smf->ppqn);
179         }
180         
181         return (0);
182 }
183
184 static int
185 cmd_format(char *new_format)
186 {
187         int tmp;
188         char *end;
189
190         if (new_format == NULL) {
191                 g_message("Format is %d.", smf->format);
192         } else {
193                 tmp = strtol(new_format, &end, 10);
194                 if (end - new_format != strlen(new_format)) {
195                         g_critical("Invalid format value, garbage characters after the number.");
196                         return (-1);
197                 }
198
199                 if (tmp < 0 || tmp > 2) {
200                         g_critical("Invalid format value, valid values are in range 0 - 2, inclusive.");
201                         return (-2);
202                 }
203
204                 if (smf_set_format(smf, tmp)) {
205                         g_critical("smf_set_format failed.");
206                         return (-3);
207                 }
208
209                 g_message("Forma changed to %d.", smf->format);
210         }
211         
212         return (0);
213 }
214
215 static int
216 cmd_tracks(char *notused)
217 {
218         if (smf->number_of_tracks > 0)
219                 g_message("There are %d tracks, numbered from 1 to %d.", smf->number_of_tracks, smf->number_of_tracks);
220         else
221                 g_message("There are no tracks.");
222
223         return (0);
224 }
225
226 static int
227 parse_track_number(const char *arg)
228 {
229         int num;
230         char *end;
231
232         if (arg == NULL) {
233                 if (selected_track == NULL) {
234                         g_message("No track currently selected and no track number given.");
235                         return (-1);
236                 } else {
237                         return (selected_track->track_number);
238                 }
239         }
240
241         num = strtol(arg, &end, 10);
242         if (end - arg != strlen(arg)) {
243                 g_critical("Invalid track number, garbage characters after the number.");
244                 return (-1);
245         }
246
247         if (num < 1 || num > smf->number_of_tracks) {
248                 if (smf->number_of_tracks > 0) {
249                         g_critical("Invalid track number specified; valid choices are 1 - %d.", smf->number_of_tracks);
250                 } else {
251                         g_critical("There are no tracks.");
252                 }
253
254                 return (-1);
255         }
256
257         return (num);
258 }
259
260 static int
261 cmd_track(char *arg)
262 {
263         int num;
264
265         if (arg == NULL) {
266                 if (selected_track == NULL)
267                         g_message("No track currently selected.");
268                 else
269                         g_message("Currently selected is track number %d, containing %d events.",
270                                 selected_track->track_number, selected_track->number_of_events);
271         } else {
272                 if (smf->number_of_tracks == 0) {
273                         g_message("There are no tracks.");
274                         return (-1);
275                 }
276
277                 num = parse_track_number(arg);
278                 if (num < 0)
279                         return (-1);
280
281                 selected_track = smf_get_track_by_number(smf, num);
282                 if (selected_track == NULL) {
283                         g_critical("smf_get_track_by_number() failed, track not selected.");
284                         return (-3);
285                 }
286
287                 selected_event = NULL;
288
289                 g_message("Track number %d selected; it contains %d events.",
290                                 selected_track->track_number, selected_track->number_of_events);
291         }
292
293         return (0);
294 }
295
296 static int
297 cmd_trackadd(char *notused)
298 {
299         selected_track = smf_track_new();
300         if (selected_track == NULL) {
301                 g_critical("smf_track_new() failed, track not created.");
302                 return (-1);
303         }
304
305         smf_add_track(smf, selected_track);
306
307         selected_event = NULL;
308
309         g_message("Created new track; track number %d selected.", selected_track->track_number);
310
311         return (0);
312 }
313
314 static int
315 cmd_trackrm(char *arg)
316 {
317         int num = parse_track_number(arg);
318
319         if (num < 0)
320                 return (-1);
321
322         if (selected_track != NULL && num == selected_track->track_number) {
323                 selected_track = NULL;
324                 selected_event = NULL;
325         }
326
327         smf_track_delete(smf_get_track_by_number(smf, num));
328
329         g_message("Track %d removed.", num);
330
331         return (0);
332 }
333
334 #define BUFFER_SIZE 1024
335
336 static int
337 show_event(smf_event_t *event)
338 {
339         int off = 0, i;
340         char *decoded, *type;
341
342         if (smf_event_is_metadata(event))
343                 type = "Metadata";
344         else
345                 type = "Event";
346         
347         decoded = smf_event_decode(event);
348
349         if (decoded == NULL) {
350                 decoded = malloc(BUFFER_SIZE);
351                 if (decoded == NULL) {
352                         g_critical("show_event: malloc failed.");
353                         return (-1);
354                 }
355
356                 off += snprintf(decoded + off, BUFFER_SIZE - off, "Unknown event:");
357
358                 for (i = 0; i < event->midi_buffer_length && i < 5; i++)
359                         off += snprintf(decoded + off, BUFFER_SIZE - off, " 0x%x", event->midi_buffer[i]);
360         }
361
362         g_message("%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->event_number, type, decoded,
363             event->time_seconds, event->time_pulses, event->delta_time_pulses);
364
365         free(decoded);
366
367         return (0);
368 }
369
370 static int
371 cmd_events(char *notused)
372 {
373         smf_event_t *event;
374
375         if (selected_track == NULL) {
376                 g_critical("No track selected - please use 'track <number>' command first.");
377                 return (-1);
378         }
379
380         if (selected_track->number_of_events == 0) {
381                 g_message("Selected track is empty.");
382                 return (0);
383         }
384
385         g_message("List of events in track %d follows:", selected_track->track_number);
386
387         smf_rewind(smf);
388
389         while ((event = smf_track_get_next_event(selected_track)) != NULL)
390                 show_event(event);
391
392         smf_rewind(smf);
393
394         return (0);
395 }
396
397 static int
398 parse_event_number(const char *arg)
399 {
400         int num;
401         char *end;
402
403         if (selected_track == NULL) {
404                 g_critical("You need to select track first (using 'track <number>').");
405                 return (-1);
406         }
407
408         if (arg == NULL) {
409                 if (selected_event == NULL) {
410                         g_message("No event currently selected and no event number given.");
411                         return (-1);
412                 } else {
413                         return (selected_event->event_number);
414                 }
415         }
416
417         num = strtol(arg, &end, 10);
418         if (end - arg != strlen(arg)) {
419                 g_critical("Invalid event number, garbage characters after the number.");
420                 return (-1);
421         }
422
423         if (num < 1 || num > selected_track->number_of_events) {
424                 if (selected_track->number_of_events > 0)
425                         g_critical("Invalid event number specified; valid choices are 1 - %d.", selected_track->number_of_events);
426                 else
427                         g_critical("There are no events in currently selected track.");
428
429                 return (-1);
430         }
431
432         return (num);
433 }
434
435 static int
436 cmd_event(char *arg)
437 {
438         int num;
439
440         if (arg == NULL) {
441                 if (selected_event == NULL) {
442                         g_message("No event currently selected.");
443                 } else {
444                         g_message("Currently selected is event %d, track %d.", selected_event->event_number, selected_track->track_number);
445                         show_event(selected_event);
446                 }
447         } else {
448                 num = parse_event_number(arg);
449                 if (num < 0)
450                         return (-1);
451
452                 selected_event = smf_track_get_event_by_number(selected_track, num);
453                 if (selected_event == NULL) {
454                         g_critical("smf_get_event_by_number() failed, event not selected.");
455                         return (-2);
456                 }
457
458                 g_message("Event number %d selected.", selected_event->event_number);
459                 show_event(selected_event);
460         }
461
462         return (0);
463 }
464
465 static int
466 decode_hex(char *str, unsigned char **buffer, int *length)
467 {
468         int i, value, midi_buffer_length;
469         char buf[3];
470         unsigned char *midi_buffer = NULL;
471         char *end = NULL;
472
473         if ((strlen(str) % 2) != 0) {
474                 g_critical("Hex value should have even number of characters, you know.");
475                 goto error;
476         }
477
478         midi_buffer_length = strlen(str) / 2;
479         midi_buffer = malloc(midi_buffer_length);
480         if (midi_buffer == NULL) {
481                 g_critical("malloc() failed.");
482                 goto error;
483         }
484
485         for (i = 0; i < midi_buffer_length; i++) {
486                 buf[0] = str[i * 2];
487                 buf[1] = str[i * 2 + 1];
488                 buf[2] = '\0';
489                 value = strtoll(buf, &end, 16);
490
491                 if (end - buf != 2) {
492                         g_critical("Garbage characters detected after hex.");
493                         goto error;
494                 }
495
496                 midi_buffer[i] = value;
497         }
498
499         *buffer = midi_buffer;
500         *length = midi_buffer_length;
501
502         return (0);
503
504 error:
505         if (midi_buffer != NULL)
506                 free(midi_buffer);
507
508         return (-1);
509 }
510
511 static void
512 eventadd_usage(void)
513 {
514         g_message("Usage: add <time-in-seconds> <midi-in-hex> - for example, 'add 1 903C7F' will add");
515         g_message("Note On event, note C4, velocity 127, channel 1, one second from the start of song, channel 1.");
516 }
517
518 static int
519 cmd_eventadd(char *str)
520 {
521         int midi_buffer_length;
522         double seconds;
523         unsigned char *midi_buffer;
524         char *time, *endtime;
525
526         if (selected_track == NULL) {
527                 g_critical("Please select a track first, using 'track <number>' command.");
528                 return (-1);
529         }
530
531         if (str == NULL) {
532                 eventadd_usage();
533                 return (-2);
534         }
535
536         /* Extract the time.  Don't use strsep(3), it doesn't work on SunOS. */
537         time = str;
538         str = strchr(str, ' ');
539         if (str != NULL) {
540                 *str = '\0';
541                 str++;
542         }
543
544         seconds = strtod(time, &endtime);
545         if (endtime - time != strlen(time)) {
546                 g_critical("Time is supposed to be a number, without trailing characters.");
547                 return (-3);
548         }
549
550         /* Called with one parameter? */
551         if (str == NULL) {
552                 eventadd_usage();
553                 return (-4);
554         }
555
556         if (decode_hex(str, &midi_buffer, &midi_buffer_length)) {
557                 eventadd_usage();
558                 return (-5);
559         }
560
561         selected_event = smf_event_new();
562         if (selected_event == NULL) {
563                 g_critical("smf_event_new() failed, event not created.");
564                 return (-6);
565         }
566
567         selected_event->midi_buffer = midi_buffer;
568         selected_event->midi_buffer_length = midi_buffer_length;
569
570         if (smf_event_is_valid(selected_event) == 0) {
571                 g_critical("Event is invalid from the MIDI specification point of view, not created.");
572                 smf_event_delete(selected_event);
573                 selected_event = NULL;
574                 return (-7);
575         }
576
577         smf_track_add_event_seconds(selected_track, selected_event, seconds);
578
579         g_message("Event created.");
580
581         return (0);
582 }
583
584 static int
585 cmd_text(char *str)
586 {
587         double seconds, type;
588         char *time, *typestr, *end;
589
590         if (selected_track == NULL) {
591                 g_critical("Please select a track first, using 'track <number>' command.");
592                 return (-1);
593         }
594
595         if (str == NULL) {
596                 g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
597                 return (-2);
598         }
599
600         /* Extract the time.  Don't use strsep(3), it doesn't work on SunOS. */
601         time = str;
602         str = strchr(str, ' ');
603         if (str != NULL) {
604                 *str = '\0';
605                 str++;
606         }
607
608         seconds = strtod(time, &end);
609         if (end - time != strlen(time)) {
610                 g_critical("Time is supposed to be a number, without trailing characters.");
611                 return (-3);
612         }
613
614         /* Called with one parameter? */
615         if (str == NULL) {
616                 g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
617                 return (-4);
618         }
619
620         /* Extract the event type. */
621         typestr = str;
622         str = strchr(str, ' ');
623         if (str != NULL) {
624                 *str = '\0';
625                 str++;
626         }
627
628         type = strtod(typestr, &end);
629         if (end - typestr != strlen(typestr)) {
630                 g_critical("Type is supposed to be a number, without trailing characters.");
631                 return (-4);
632         }
633
634         if (type < 1 || type > 9) {
635                 g_critical("Valid values for type are 1 - 9, inclusive.");
636                 return (-5);
637         }
638
639         /* Called with one parameter? */
640         if (str == NULL) {
641                 g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
642                 return (-4);
643         }
644
645         selected_event = smf_event_new_textual(type, str);
646         if (selected_event == NULL) {
647                 g_critical("smf_event_new_textual() failed, event not created.");
648                 return (-6);
649         }
650
651         assert(smf_event_is_valid(selected_event));
652
653         smf_track_add_event_seconds(selected_track, selected_event, seconds);
654
655         g_message("Event created.");
656
657         return (0);
658 }
659
660
661 static int
662 cmd_eventaddeot(char *time)
663 {
664         double seconds;
665         char *end;
666
667         if (selected_track == NULL) {
668                 g_critical("Please select a track first, using 'track <number>' command.");
669                 return (-1);
670         }
671
672         if (time == NULL) {
673                 g_critical("Please specify the time, in seconds.");
674                 return (-2);
675         }
676
677         seconds = strtod(time, &end);
678         if (end - time != strlen(time)) {
679                 g_critical("Time is supposed to be a number, without trailing characters.");
680                 return (-3);
681         }
682
683         if (smf_track_add_eot_seconds(selected_track, seconds)) {
684                 g_critical("smf_track_add_eot() failed.");
685                 return (-4);
686         }
687
688         g_message("Event created.");
689
690         return (0);
691 }
692
693 static int
694 cmd_eventrm(char *number)
695 {
696         int num = parse_event_number(number);
697
698         if (num < 0)
699                 return (-1);
700
701         if (selected_event != NULL && num == selected_event->event_number)
702                 selected_event = NULL;
703
704         smf_event_delete(smf_track_get_event_by_number(selected_track, num));
705
706         g_message("Event #%d removed.", num);
707
708         return (0);
709 }
710
711 static int
712 cmd_tempo(char *notused)
713 {
714         int i;
715         smf_tempo_t *tempo;
716
717         for (i = 0;; i++) {
718                 tempo = smf_get_tempo_by_number(smf, i);
719                 if (tempo == NULL)
720                         break;
721
722                 g_message("Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.",
723                     i, tempo->time_pulses, tempo->time_seconds, tempo->microseconds_per_quarter_note,
724                     60000000.0 / (double)tempo->microseconds_per_quarter_note);
725                 g_message("Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.",
726                     tempo->numerator, tempo->denominator, tempo->clocks_per_click, tempo->notes_per_note);
727         }
728
729         return (0);
730 }
731
732 static int
733 cmd_length(char *notused)
734 {
735         g_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf), smf_get_length_seconds(smf));
736
737         return (0);
738 }
739
740 static int
741 cmd_version(char *notused)
742 {
743         g_message("libsmf version %s.", smf_get_version());
744
745         return (0);
746 }
747
748 static int
749 cmd_exit(char *notused)
750 {
751         g_debug("Good bye.");
752         exit(0);
753 }
754
755 static int cmd_help(char *notused);
756
757 static struct command_struct {
758         char *name;
759         int (*function)(char *command);
760         char *help;
761 } commands[] = {{"help", cmd_help, "Show this help."},
762                 {"?", cmd_help, NULL},
763                 {"load", cmd_load, "Load named file."},
764                 {"open", cmd_load},
765                 {"save", cmd_save, "Save to named file."},
766                 {"ppqn", cmd_ppqn, "Show ppqn (aka division), or set ppqn if used with parameter."},
767                 {"format", cmd_format, "Show format, or set format if used with parameter."},
768                 {"tracks", cmd_tracks, "Show number of tracks."},
769                 {"track", cmd_track, "Show number of currently selected track, or select a track."},
770                 {"trackadd", cmd_trackadd, "Add a track and select it."},
771                 {"trackrm", cmd_trackrm, "Remove currently selected track."},
772                 {"events", cmd_events, "Show events in the currently selected track."},
773                 {"event", cmd_event, "Show number of currently selected event, or select an event."},
774                 {"add", cmd_eventadd, "Add an event and select it."},
775                 {"text", cmd_text, "Add textual event and select it."},
776                 {"eventadd", cmd_eventadd, NULL},
777                 {"eot", cmd_eventaddeot, "Add an End Of Track event."},
778                 {"eventaddeot", cmd_eventaddeot, NULL},
779                 {"eventrm", cmd_eventrm, NULL},
780                 {"rm", cmd_eventrm, "Remove currently selected event."},
781                 {"tempo", cmd_tempo, "Show tempo map."},
782                 {"length", cmd_length, "Show length of the song."},
783                 {"version", cmd_version, "Show libsmf version."},
784                 {"exit", cmd_exit, "Exit to shell."},
785                 {"quit", cmd_exit, NULL},
786                 {"bye", cmd_exit, NULL},
787                 {NULL, NULL, NULL}};
788
789 static int
790 cmd_help(char *notused)
791 {
792         int i, padding_length;
793         char padding[COMMAND_LENGTH + 1];
794         struct command_struct *tmp;
795
796         g_message("Available commands:");
797
798         for (tmp = commands; tmp->name != NULL; tmp++) {
799                 /* Skip commands with no help string. */
800                 if (tmp->help == NULL)
801                         continue;
802
803                 padding_length = COMMAND_LENGTH - strlen(tmp->name);
804                 assert(padding_length >= 0);
805                 for (i = 0; i < padding_length; i++)
806                         padding[i] = ' ';
807                 padding[i] = '\0';
808
809                 g_message("%s:%s%s", tmp->name, padding, tmp->help);
810         }
811
812         return (0);
813 }
814
815 /**
816  * Removes (in place) all whitespace characters before the first
817  * non-whitespace and all trailing whitespace characters.  Replaces
818  * more than one consecutive whitespace characters with one.
819  */
820 static void
821 strip_unneeded_whitespace(char *str, int len)
822 {
823         char *src, *dest;
824         int skip_white = 1;
825
826         for (src = str, dest = str; src < dest + len; src++) {
827                 if (*src == '\n' || *src == '\0') {
828                         *dest = '\0';
829                         break;
830                 }
831
832                 if (isspace(*src)) {
833                         if (skip_white)
834                                 continue;
835
836                         skip_white = 1;
837                 } else {
838                         skip_white = 0;
839                 }
840
841                 *dest = *src;
842                 dest++;
843         }
844
845         /* Remove trailing whitespace. */
846         len = strlen(dest);
847         if (isspace(dest[len - 1]))
848                 dest[len - 1] = '\0';
849 }
850
851 static char *
852 read_command(void)
853 {
854         char *buf;
855         int len;
856
857 #ifdef HAVE_LIBREADLINE
858         buf = readline("smfsh> ");
859 #else
860         buf = malloc(1024);
861         if (buf == NULL) {
862                 g_critical("Malloc failed.");
863                 return (NULL);
864         }
865
866         fprintf(stdout, "smfsh> ");
867         fflush(stdout);
868
869         buf = fgets(buf, 1024, stdin);
870 #endif
871
872         if (buf == NULL) {
873                 fprintf(stdout, "exit\n");
874                 return (strdup("exit"));
875         }
876
877         strip_unneeded_whitespace(buf, 1024);
878
879         len = strlen(buf);
880
881         if (len == 0)
882                 return (read_command());
883
884 #ifdef HAVE_LIBREADLINE
885         add_history(buf);
886 #endif
887
888         return (buf);
889 }
890
891 static int
892 execute_command(char *line)
893 {
894         char *command, *args;
895         struct command_struct *tmp;
896
897         command = line;
898         args = strchr(line, ' ');
899         if (args != NULL) {
900                 *args = '\0';
901                 args++;
902         }
903
904         for (tmp = commands; tmp->name != NULL; tmp++) {
905                 if (strcmp(tmp->name, command) == 0)
906                         return ((tmp->function)(args));
907         }
908
909         g_warning("No such command: '%s'.  Type 'help' to see available commands.", command);
910
911         return (-1);
912 }
913
914 static void
915 read_and_execute_command(void)
916 {
917         int ret;
918         char *command_line, *command, *next_command;
919
920         command = command_line = read_command();
921
922         do {
923                 next_command = strchr(command, ';');
924                 if (next_command != NULL) {
925                         *next_command = '\0';
926                         next_command++;
927                 }
928
929                 strip_unneeded_whitespace(command, 1024);
930                 if (strlen(command) > 0) {
931                         ret = execute_command(command);
932                         if (ret)
933                                 g_warning("Command finished with error.");
934                 }
935
936                 command = next_command;
937
938         } while (command);
939
940         free(command_line);
941 }
942
943 #ifdef HAVE_LIBREADLINE
944
945 static char *
946 smfsh_command_generator(const char *text, int state)
947 {
948         static struct command_struct *command = commands;
949         char *tmp;
950
951         if (state == 0)
952                 command = commands;
953
954         while (command->name != NULL) {
955                 tmp = command->name;
956                 command++;
957
958                 if (strncmp(tmp, text, strlen(text)) == 0)
959                         return (strdup(tmp));
960         }
961
962         return (NULL);
963 }
964
965 static char **
966 smfsh_completion(const char *text, int start, int end)
967 {
968         int i;
969
970         /* Return NULL if "text" is not the first word in the input line. */
971         if (start != 0) {
972                 for (i = 0; i < start; i++) {
973                         if (!isspace(rl_line_buffer[i]))
974                                 return (NULL);
975                 }
976         }
977
978         return (rl_completion_matches(text, smfsh_command_generator));
979 }
980
981 #endif
982
983 static void
984 usage(void)
985 {
986         fprintf(stderr, "usage: smfsh [-V | file]\n");
987
988         exit(EX_USAGE);
989 }
990
991 int
992 main(int argc, char *argv[])
993 {
994         int ch;
995
996         while ((ch = getopt(argc, argv, "V")) != -1) {
997                 switch (ch) {
998                 case 'V':
999                         cmd_version(NULL);
1000                         exit(EX_OK);
1001
1002                 case '?':
1003                 default:
1004                         usage();
1005                 }
1006         }
1007
1008         if (argc > 2)
1009                 usage();
1010
1011         g_log_set_default_handler(log_handler, NULL);
1012
1013         smf = smf_new();
1014         if (smf == NULL) {
1015                 g_critical("Cannot initialize smf_t.");
1016                 return (-1);
1017         }
1018
1019         if (argc == 2)
1020                 cmd_load(argv[1]);
1021         else
1022                 cmd_trackadd(NULL);
1023
1024 #ifdef HAVE_LIBREADLINE
1025         rl_readline_name = "smfsh";
1026         rl_attempted_completion_function = smfsh_completion;
1027 #endif
1028
1029         for (;;)
1030                 read_and_execute_command();
1031
1032         return (0);
1033 }
1034