Line data Source code
1 : // Copyright (c) 2005-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/as2js
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program 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
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // snapdev
20 : //
21 : #include <snapdev/file_contents.h>
22 : #include <snapdev/hexadecimal_string.h>
23 : #include <snapdev/pathinfo.h>
24 : #include <snapdev/string_replace_many.h>
25 :
26 :
27 : // C
28 : //
29 : #include <string.h>
30 :
31 :
32 :
33 : /** \file
34 : * \brief Tool used to convert text files to a C string.
35 : *
36 : * Often, I would like to have a "resource" built from an external text file
37 : * which gets compiled so the resulting library or tool has the resource
38 : * within its .DATA section instead of having to load a file.
39 : *
40 : * This tool coverts such text files to a .ci (C include) file with a string
41 : * composed of the input file converted to lines, "\\n", and also a length
42 : * for the string. The length is useful to create an std::string or when the
43 : * input may include "\\0" characters.
44 : *
45 : * If the input may include binary, use the --binary command line option and
46 : * all the bytes that are not ASCII will be transformed to the `\xXX` syntax.
47 : */
48 :
49 :
50 : namespace
51 : {
52 :
53 :
54 : class as_rc
55 : {
56 : public:
57 : as_rc(int argc, char * argv[]);
58 : as_rc(as_rc const & rhs) = delete;
59 : as_rc & operator = (as_rc const & rhs) = delete;
60 :
61 : int init();
62 : int run();
63 :
64 : private:
65 : void usage();
66 :
67 : int f_argc = 0;
68 : char ** f_argv = nullptr;
69 : std::vector<std::string> f_filenames = {};
70 : std::string f_output = std::string();
71 : std::string f_header = std::string();
72 : std::string f_name = std::string();
73 : std::string f_namespace = std::string();
74 : bool f_binary = false;
75 : bool f_verbose = false;
76 : };
77 :
78 :
79 0 : void as_rc::usage()
80 : {
81 0 : std::cout << "Usage: as-rc [--opts] [--] <in1> <in2> ... <inN>\n";
82 0 : std::cout << "where [--opts] is one of more of the following:\n";
83 0 : std::cout << " -h | --help print out this help screen.\n";
84 0 : std::cout << " -o | --output <filename> specify the output filename.\n";
85 0 : std::cout << " -n | --name <name> name of the final string variable.\n";
86 0 : std::cout << " -n | --namespace <name> place variables in a C++ namespace.\n";
87 0 : std::cout << " -b | --binary input is binary, not text.\n";
88 0 : std::cout << " -v | --verbose display messages.\n";
89 0 : std::cout << " -- anything after this are input filenames.\n";
90 0 : }
91 :
92 :
93 4 : as_rc::as_rc(int argc, char * argv[])
94 4 : : f_argc(argc)
95 4 : , f_argv(argv)
96 : {
97 4 : }
98 :
99 :
100 4 : int as_rc::init()
101 : {
102 4 : bool more_options(true);
103 21 : for(int i(1); i < f_argc; ++i)
104 : {
105 17 : if(more_options
106 17 : && f_argv[i][0] == '-')
107 : {
108 13 : if(f_argv[i][1] == '-')
109 : {
110 : // long form
111 : //
112 13 : if(f_argv[i][2] == '\0')
113 : {
114 0 : more_options = false;
115 : }
116 13 : else if(strcmp(f_argv[i] + 2, "help") == 0)
117 : {
118 0 : usage();
119 0 : return 1;
120 : }
121 13 : else if(strcmp(f_argv[i] + 2, "name") == 0)
122 : {
123 4 : ++i;
124 4 : if(i >= f_argc)
125 : {
126 0 : std::cerr << "error:as-rc: --name expect a parameter.\n";
127 0 : return 1;
128 : }
129 4 : if(!f_name.empty())
130 : {
131 0 : std::cerr << "error:as-rc: --name already defined.\n";
132 0 : return 1;
133 : }
134 4 : f_name = f_argv[i];
135 : }
136 9 : else if(strcmp(f_argv[i] + 2, "namespace") == 0)
137 : {
138 4 : ++i;
139 4 : if(i >= f_argc)
140 : {
141 0 : std::cerr << "error:as-rc: --namespace expect a parameter.\n";
142 0 : return 1;
143 : }
144 4 : if(!f_namespace.empty())
145 : {
146 0 : std::cerr << "error:as-rc: --namespace already defined.\n";
147 0 : return 1;
148 : }
149 4 : f_namespace = f_argv[i];
150 : }
151 5 : else if(strcmp(f_argv[i] + 2, "output") == 0)
152 : {
153 4 : ++i;
154 4 : if(i >= f_argc)
155 : {
156 0 : std::cerr << "error:as-rc: --output expect a parameter.\n";
157 0 : return 1;
158 : }
159 4 : if(!f_output.empty())
160 : {
161 0 : std::cerr << "error:as-rc: --output already defined.\n";
162 0 : return 1;
163 : }
164 4 : f_output = f_argv[i];
165 : }
166 1 : else if(strcmp(f_argv[i] + 2, "verbose") == 0)
167 : {
168 1 : f_verbose = true;
169 : }
170 : else
171 : {
172 : std::cerr << "error:as-rc: unknown command line option \""
173 0 : << f_argv[i]
174 0 : << "\".\n";
175 0 : return 1;
176 : }
177 : }
178 : else
179 : {
180 0 : std::size_t const len(strlen(f_argv[i]));
181 0 : for(std::size_t j(1); j < len; ++j)
182 : {
183 : // short form
184 : //
185 0 : switch(f_argv[i][j])
186 : {
187 0 : case 'h':
188 0 : usage();
189 0 : return 1;
190 : break;
191 :
192 0 : case 'n':
193 0 : if(i + 1 >= f_argc)
194 : {
195 0 : std::cerr << "error:as-rc: -o expect a parameter.\n";
196 0 : return 1;
197 : }
198 0 : if(!f_name.empty())
199 : {
200 0 : std::cerr << "error:as-rc: -n already defined.\n";
201 0 : return 1;
202 : }
203 0 : ++i;
204 0 : f_name = f_argv[i];
205 0 : break;
206 :
207 0 : case 'o':
208 0 : if(i + 1 >= f_argc)
209 : {
210 0 : std::cerr << "error:as-rc: -o expect a parameter.\n";
211 0 : return 1;
212 : }
213 0 : if(!f_output.empty())
214 : {
215 0 : std::cerr << "error:as-rc: -o already defined.\n";
216 0 : return 1;
217 : }
218 0 : ++i;
219 0 : f_output = f_argv[i];
220 0 : break;
221 :
222 0 : case 'v':
223 0 : f_verbose = true;
224 0 : break;
225 :
226 : }
227 : }
228 : }
229 13 : }
230 : else
231 : {
232 4 : f_filenames.push_back(f_argv[i]);
233 : }
234 : }
235 :
236 4 : if(f_filenames.empty())
237 : {
238 0 : std::cerr << "error:as-rc: at least one input filename must be specified.\n";
239 0 : return 1;
240 : }
241 :
242 4 : if(f_output.empty())
243 : {
244 0 : if(f_filenames.size() == 1)
245 : {
246 0 : f_output = snapdev::pathinfo::replace_suffix(f_filenames[0], ".ci");
247 0 : if(f_filenames[0] == f_output)
248 : {
249 0 : std::cerr << "error:as-rc: your input file is a .ci file, you must specify a --output in this case.\n";
250 0 : return 1;
251 : }
252 : }
253 : else
254 : {
255 : // default when not specified
256 : //
257 : //f_output = "-"; -- not supported because we generate a header as well
258 0 : std::cerr << "error:as-rc: an output file name is required.\n";
259 0 : return 1;
260 : }
261 : }
262 :
263 4 : if(std::find(f_filenames.begin(), f_filenames.end(), f_output) != f_filenames.end())
264 : {
265 : std::cerr
266 : << "error:as-rc: one of your input filename is the same as the output filename: \""
267 0 : << f_output
268 0 : << "\".\n";
269 0 : return 1;
270 : }
271 :
272 4 : if(f_output == "-")
273 : {
274 0 : f_verbose = false;
275 : }
276 :
277 4 : f_header = snapdev::pathinfo::replace_suffix(f_output, ".*", ".h");
278 :
279 4 : if(f_name.empty())
280 : {
281 0 : if(f_filenames.size() != 1)
282 : {
283 : std::cerr << "error:as-rc: when you have more than one filename,"
284 0 : " you must specify a --name to define the string name.\n";
285 0 : return 1;
286 : }
287 0 : f_name = snapdev::pathinfo::basename(f_filenames[0]);
288 0 : if(f_name.empty())
289 : {
290 : std::cerr << "error:as-rc: could not auto-define a string name,"
291 0 : " try again with the --name command line option.\n";
292 0 : return 1;
293 : }
294 : }
295 :
296 4 : return 0;
297 : }
298 :
299 :
300 4 : int as_rc::run()
301 : {
302 4 : std::string input;
303 8 : for(auto const & f : f_filenames)
304 : {
305 4 : if(f_verbose)
306 : {
307 : std::cout
308 : << "as-rc:info: reading \""
309 : << f
310 1 : << "\".\n";
311 : }
312 :
313 4 : snapdev::file_contents in(f);
314 4 : if(!in.read_all())
315 : {
316 0 : int const e(errno);
317 : std::cerr << "error:as-rc: could not open \""
318 : << f
319 0 : << "\" for reading: "
320 : << e
321 : << " -- "
322 0 : << strerror(e)
323 0 : << "\n";
324 0 : return 1;
325 : }
326 4 : input += in.contents();
327 4 : }
328 :
329 : // we've got all the contents in memory, convert to a C literal string
330 : //
331 4 : std::string output(
332 : "/* AUTO-GENERATED FILE -- DO NOT EDIT -- see as-rc(1) for details */\n"
333 12 : "#include \"" + f_header + "\"\n"
334 20 : + (f_namespace.empty() ? "" : "namespace " + f_namespace + "{\n")
335 32 : + "size_t const " + f_name + "_size=" + std::to_string(input.length()) + ";\n"
336 12 : "char const * " + f_name + "=\n");
337 :
338 4 : if(f_binary)
339 : {
340 0 : unsigned pos(0);
341 0 : for(auto const & byte : input)
342 : {
343 0 : if((pos & 16) == 0)
344 : {
345 0 : if(pos > 0)
346 : {
347 0 : output += "\"\n\"";
348 : }
349 : else
350 : {
351 0 : output += '"';
352 : }
353 : }
354 0 : ++pos;
355 :
356 0 : if(byte == '"')
357 : {
358 0 : output += "\\\"";
359 : }
360 0 : else if(static_cast<std::uint8_t>(byte) >= ' '
361 0 : && static_cast<std::uint8_t>(byte) <= 0x7F)
362 : {
363 0 : output += byte;
364 : }
365 : else
366 : {
367 0 : output += "\\x";
368 0 : output += snapdev::int_to_hex(byte, false, 2);
369 : }
370 : }
371 0 : if(pos == 0)
372 : {
373 : // case where the input is completely empty
374 : //
375 0 : output += '"';
376 : }
377 0 : output += "\";\n";
378 : }
379 : else
380 : {
381 4 : output += '"';
382 16 : output += snapdev::string_replace_many(input, {
383 : // WARNING: the order is important
384 : {"\"", "\\\""},
385 : {"\n", "\\n\"\n\""},
386 4 : });
387 4 : output += "\";\n";
388 : }
389 4 : if(!f_namespace.empty())
390 : {
391 4 : output += "}\n";
392 : }
393 :
394 4 : if(f_verbose)
395 : {
396 : std::cout
397 : << "as-rc:info: writing to \""
398 1 : << f_output
399 1 : << "\".\n";
400 : }
401 :
402 4 : std::ofstream out;
403 4 : out.open(f_output);
404 4 : if(!out)
405 : {
406 : std::cerr << "error:as-rc: could not open \""
407 0 : << f_output
408 0 : << "\" for writing the output.\n";
409 0 : return 1;
410 : }
411 4 : out << output;
412 4 : if(!out)
413 : {
414 : std::cerr << "error:as-rc: errors happened while writing to \""
415 0 : << f_output
416 0 : << "\".\n";
417 0 : return 1;
418 : }
419 :
420 4 : if(f_verbose)
421 : {
422 : std::cout
423 : << "as-rc:info: writing to \""
424 1 : << f_header
425 1 : << "\".\n";
426 : }
427 :
428 4 : std::ofstream header;
429 4 : header.open(f_header);
430 4 : if(!header)
431 : {
432 : std::cerr
433 : << "error:as-rc: could not open \""
434 0 : << f_header
435 0 : << "\" for writing the header.\n";
436 0 : return 1;
437 : }
438 : header
439 : << "/* AUTO-GENERATED FILE -- DO NOT EDIT -- see as-rc(1) for details */\n"
440 : "#include <stddef.h>\n"
441 8 : << (f_namespace.empty() ? "" : "namespace " + f_namespace + "{\n")
442 4 : << "extern size_t const " << f_name << "_size;\n"
443 4 : "extern char const * " << f_name << ";\n"
444 8 : << (f_namespace.empty() ? "" : "}\n");
445 4 : if(!header)
446 : {
447 : std::cerr << "error:as-rc: errors happened while writing to \""
448 0 : << f_header
449 0 : << "\".\n";
450 0 : return 1;
451 : }
452 :
453 4 : if(f_verbose)
454 : {
455 : std::cout
456 1 : << "as-rc:info: success.\n";
457 : }
458 :
459 4 : return 0;
460 4 : }
461 :
462 :
463 : } // no name namespace
464 :
465 4 : int main(int argc, char * argv[])
466 : {
467 4 : as_rc rc(argc, argv);
468 4 : int r(rc.init());
469 4 : if(r != 0)
470 : {
471 0 : return r;
472 : }
473 4 : return rc.run();
474 4 : }
475 :
476 : // vim: ts=4 sw=4 et
|