tools: add ffescape utility
[ffmpeg.git] / tools / ffescape.c
1 /*
2  * Copyright (c) 2012 Stefano Sabatini
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "config.h"
22 #if HAVE_UNISTD_H
23 #include <unistd.h>             /* getopt */
24 #endif
25
26 #include "libavutil/log.h"
27 #include "libavutil/bprint.h"
28
29 #if !HAVE_GETOPT
30 #include "compat/getopt.c"
31 #endif
32
33 /**
34  * @file
35  * escaping utility
36  */
37
38 static void usage(void)
39 {
40     printf("Escape an input string, adopting the av_get_token() escaping logic\n");
41     printf("usage: ffescape [OPTIONS]\n");
42     printf("\n"
43            "Options:\n"
44            "-e                echo each input line on output\n"
45            "-h                print this help\n"
46            "-i INFILE         set INFILE as input file, stdin if omitted\n"
47            "-l LEVEL          set the number of escaping levels, 1 if omitted\n"
48            "-m ESCAPE_MODE    select escape mode between 'full', 'lazy', 'quote', default is 'lazy'\n"
49            "-o OUTFILE        set OUTFILE as output file, stdout if omitted\n"
50            "-p PROMPT         set output prompt, is '=> ' by default\n"
51            "-s SPECIAL_CHARS  set the list of special characters\n");
52 }
53
54 #define WHITESPACES " \n\t"
55
56 enum EscapeMode {
57     ESCAPE_MODE_FULL,
58     ESCAPE_MODE_LAZY,
59     ESCAPE_MODE_QUOTE,
60 };
61
62 static int escape(char **dst, const char *src, const char *special_chars,
63                   enum EscapeMode mode)
64 {
65     AVBPrint dstbuf;
66
67     av_bprint_init(&dstbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
68
69     switch (mode) {
70     case ESCAPE_MODE_FULL:
71     case ESCAPE_MODE_LAZY:
72         /* \-escape characters */
73
74         if (mode == ESCAPE_MODE_LAZY && strchr(WHITESPACES, *src))
75             av_bprintf(&dstbuf, "\\%c", *src++);
76
77         for (; *src; src++) {
78             if ((special_chars && strchr(special_chars, *src)) ||
79                 strchr("'\\", *src) ||
80                 (mode == ESCAPE_MODE_FULL && strchr(WHITESPACES, *src)))
81                 av_bprintf(&dstbuf, "\\%c", *src);
82             else
83                 av_bprint_chars(&dstbuf, *src, 1);
84         }
85
86         if (mode == ESCAPE_MODE_LAZY && strchr(WHITESPACES, dstbuf.str[dstbuf.len-1])) {
87             char c = dstbuf.str[dstbuf.len-1];
88             dstbuf.str[dstbuf.len-1] = '\\';
89             av_bprint_chars(&dstbuf, c, 1);
90         }
91         break;
92
93     case ESCAPE_MODE_QUOTE:
94         /* enclose between '' the string */
95         av_bprint_chars(&dstbuf, '\'', 1);
96         for (; *src; src++) {
97             if (*src == '\'')
98                 av_bprintf(&dstbuf, "'\\''");
99             else
100                 av_bprint_chars(&dstbuf, *src, 1);
101         }
102         av_bprint_chars(&dstbuf, '\'', 1);
103         break;
104
105     default:
106         /* unknown escape mode */
107         return AVERROR(EINVAL);
108     }
109
110     if (!av_bprint_is_complete(&dstbuf)) {
111         av_bprint_finalize(&dstbuf, NULL);
112         return AVERROR(ENOMEM);
113     } else {
114         av_bprint_finalize(&dstbuf, dst);
115         return 0;
116     }
117 }
118
119 int main(int argc, char **argv)
120 {
121     AVBPrint src;
122     char *src_buf, *dst_buf;
123     const char *outfilename = NULL, *infilename = NULL;
124     FILE *outfile = NULL, *infile = NULL;
125     const char *prompt = "=> ";
126     enum EscapeMode escape_mode = ESCAPE_MODE_LAZY;
127     int level = 1;
128     int echo = 0;
129     char *special_chars = NULL;
130     int c;
131
132     while ((c = getopt(argc, argv, "ehi:l:o:m:p:s:")) != -1) {
133         switch (c) {
134         case 'e':
135             echo = 1;
136             break;
137         case 'h':
138             usage();
139             return 0;
140         case 'i':
141             infilename = optarg;
142             break;
143         case 'l':
144         {
145             char *tail;
146             long int li = strtol(optarg, &tail, 10);
147             if (*tail || li > INT_MAX || li < 0) {
148                 av_log(NULL, AV_LOG_ERROR,
149                         "Invalid value '%s' for option -l, argument must be a non negative integer\n",
150                         optarg);
151                 return 1;
152             }
153             level = li;
154             break;
155         }
156         case 'm':
157             if      (!strcmp(optarg, "full"))  escape_mode = ESCAPE_MODE_FULL;
158             else if (!strcmp(optarg, "lazy"))  escape_mode = ESCAPE_MODE_LAZY;
159             else if (!strcmp(optarg, "quote")) escape_mode = ESCAPE_MODE_QUOTE;
160             else {
161                 av_log(NULL, AV_LOG_ERROR,
162                        "Invalid value '%s' for option -m, "
163                        "valid arguments are 'full', 'lazy', 'quote'\n", optarg);
164                 return 1;
165             }
166             break;
167         case 'o':
168             outfilename = optarg;
169             break;
170         case 'p':
171             prompt = optarg;
172             break;
173         case 's':
174             special_chars = optarg;
175             break;
176         case '?':
177             return 1;
178         }
179     }
180
181     if (!infilename || !strcmp(infilename, "-")) {
182         infilename = "stdin";
183         infile = stdin;
184     } else {
185         infile = fopen(infilename, "r");
186     }
187     if (!infile) {
188         av_log(NULL, AV_LOG_ERROR, "Impossible to open input file '%s': %s\n", infilename, strerror(errno));
189         return 1;
190     }
191
192     if (!outfilename || !strcmp(outfilename, "-")) {
193         outfilename = "stdout";
194         outfile = stdout;
195     } else {
196         outfile = fopen(outfilename, "w");
197     }
198     if (!outfile) {
199         av_log(NULL, AV_LOG_ERROR, "Impossible to open output file '%s': %s\n", outfilename, strerror(errno));
200         return 1;
201     }
202
203     /* grab the input and store it in src */
204     av_bprint_init(&src, 1, AV_BPRINT_SIZE_UNLIMITED);
205     while ((c = fgetc(infile)) != EOF)
206         av_bprint_chars(&src, c, 1);
207     av_bprint_chars(&src, 0, 1);
208
209     if (!av_bprint_is_complete(&src)) {
210         av_log(NULL, AV_LOG_ERROR, "Could not allocate a buffer for the source string\n");
211         av_bprint_finalize(&src, NULL);
212         return 1;
213     }
214     av_bprint_finalize(&src, &src_buf);
215
216     if (echo)
217         fprintf(outfile, "%s", src_buf);
218
219     /* escape */
220     dst_buf = src_buf;
221     while (level--) {
222         if (escape(&dst_buf, src_buf, special_chars, escape_mode) < 0) {
223             av_log(NULL, AV_LOG_ERROR, "Could not escape string\n");
224             return 1;
225         }
226         av_free(src_buf);
227         src_buf = dst_buf;
228     }
229
230     fprintf(outfile, "%s%s", prompt, dst_buf);
231     av_free(dst_buf);
232     return 0;
233 }