Kea 2.7.0
log/compiler/message.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <cctype>
10#include <cstddef>
11#include <fstream>
12#include <iostream>
13#include <string>
14#include <vector>
15
16#include <errno.h>
17#include <getopt.h>
18#include <string.h>
19#include <time.h>
20#include <unistd.h>
21
23
24#include <util/filesystem.h>
25#include <util/str.h>
26
27#include <log/log_messages.h>
30#include <log/message_reader.h>
31
32#include <log/logger.h>
33
34using namespace std;
35using namespace isc::log;
36using namespace isc::util;
37using namespace isc::util::file;
38
61
65
66void
68 cout << VERSION << "\n";
69}
70
74
75void
77 cout <<
78 "Usage: kea-msg-compiler [-h] [-v] [-d dir] <message-file>\n" <<
79 "\n" <<
80 "-h Print this message and exit\n" <<
81 "-v Print the program version and exit\n" <<
82 "-d <dir> Place output files in given directory\n" <<
83 "\n" <<
84 "<message-file> is the name of the input message file.\n";
85}
86
96
97string
98sentinel(Path& file) {
99 string name = file.stem();
100 string ext = file.extension();
101 string sentinel_text = name + "_" + ext.substr(1);
102 str::uppercase(sentinel_text);
103 return (sentinel_text);
104}
105
111
112string
113quoteString(const string& instring) {
114
115 // Create the output string and reserve the space needed to hold the input
116 // string. (Most input strings will not contain quotes, so this single
117 // reservation should be all that is needed.)
118 string outstring;
119 outstring.reserve(instring.size());
120
121 // Iterate through the input string, preceding quotes with a slash.
122 for (size_t i = 0; i < instring.size(); ++i) {
123 if (instring[i] == '"') {
124 outstring += '\\';
125 }
126 outstring += instring[i];
127 }
128
129 return (outstring);
130}
131
140
141vector<string>
143 vector<string> ident;
144
145 for (auto const& i : dictionary) {
146 ident.push_back(i.first);
147 }
148 sort(ident.begin(), ident.end());
149
150 return (ident);
151}
152
166
167vector<string>
168splitNamespace(string ns) {
169
170 // Namespaces components are separated by double colon characters -
171 // convert to single colons.
172 size_t dcolon;
173 while ((dcolon = ns.find("::")) != string::npos) {
174 ns.replace(dcolon, 2, ":");
175 }
176
177 // ... and return the vector of namespace components split on the single
178 // colon.
179 return (str::tokens(ns, ":"));
180}
181
185void
186writeOpeningNamespace(ostream& output, const vector<string>& ns) {
187 if (!ns.empty()) {
188
189 // Output namespaces in correct order
190 for (vector<string>::size_type i = 0; i < ns.size(); ++i) {
191 output << "namespace " << ns[i] << " {\n";
192 }
193 output << "\n";
194 }
195}
196
200void
201writeClosingNamespace(ostream& output, const vector<string>& ns) {
202 if (!ns.empty()) {
203 for (int i = ns.size() - 1; i >= 0; --i) {
204 output << "} // namespace " << ns[i] << "\n";
205 }
206 output << "\n";
207 }
208}
209
225void
226writeHeaderFile(const string& file,
227 const vector<string>& ns_components,
228 MessageDictionary& dictionary,
229 const char* output_directory) {
230 Path message_file(file);
231 Path header_file(Path(file).replaceExtension(".h"));
232 if (output_directory != NULL) {
233 header_file.replaceParentPath(output_directory);
234 }
235
236 // Text to use as the sentinels.
237 string sentinel_text = sentinel(header_file);
238
239 // zero out the errno to be safe
240 errno = 0;
241
242 // Open the output file for writing
243 ofstream hfile(header_file.str());
244
245 if (hfile.fail()) {
246 isc_throw_4(MessageException, "Failed to open output file",
247 LOG_OPEN_OUTPUT_FAIL, header_file.str(),
248 strerror(errno), 0);
249 }
250
251 // Write the header preamble. If there is an error, we'll pick it up
252 // after the last write.
253
254 hfile <<
255 "// File created from " << message_file.str() << "\n" <<
256 "\n" <<
257 "#ifndef " << sentinel_text << "\n" <<
258 "#define " << sentinel_text << "\n" <<
259 "\n" <<
260 "#include <log/message_types.h>\n" <<
261 "\n";
262
263 // Write the message identifiers, bounded by a namespace declaration
264 writeOpeningNamespace(hfile, ns_components);
265
266 vector<string> idents = sortedIdentifiers(dictionary);
267 for (auto const& j : idents) {
268 hfile << "extern const isc::log::MessageID " << j << ";\n";
269 }
270 hfile << "\n";
271
272 writeClosingNamespace(hfile, ns_components);
273
274 // ... and finally the postamble
275 hfile << "#endif // " << sentinel_text << "\n";
276
277 // Report errors (if any) and exit
278 if (hfile.fail()) {
279 isc_throw_4(MessageException, "Error writing to output file",
280 LOG_WRITE_ERROR, header_file.str(), strerror(errno),
281 0);
282 }
283
284 hfile.close();
285}
286
290char
292 return (isalnum(c) ? c : '_');
293}
294
328void
329writeProgramFile(const string& file,
330 const vector<string>& ns_components,
331 MessageDictionary& dictionary,
332 const char* output_directory) {
333 Path message_file(file);
334 Path program_file(Path(file).replaceExtension(".cc"));
335 if (output_directory) {
336 program_file.replaceParentPath(output_directory);
337 }
338
339 // zero out the errno to be safe
340 errno = 0;
341
342 // Open the output file for writing
343 ofstream ccfile(program_file.str());
344
345 if (ccfile.fail()) {
346 isc_throw_4(MessageException, "Error opening output file",
347 LOG_OPEN_OUTPUT_FAIL, program_file.str(),
348 strerror(errno), 0);
349 }
350
351 // Write the preamble. If there is an error, we'll pick it up after
352 // the last write.
353
354 ccfile <<
355 "// File created from " << message_file.str() << "\n" <<
356 "\n" <<
357 "#include <cstddef>\n" <<
358 "#include <log/message_types.h>\n" <<
359 "#include <log/message_initializer.h>\n" <<
360 "\n";
361
362 // Declare the message symbols themselves.
363
364 writeOpeningNamespace(ccfile, ns_components);
365
366 vector<string> idents = sortedIdentifiers(dictionary);
367 for (auto const& j : idents) {
368 ccfile << "extern const isc::log::MessageID " << j <<
369 " = \"" << j << "\";\n";
370 }
371 ccfile << "\n";
372
373 writeClosingNamespace(ccfile, ns_components);
374
375 // Now the code for the message initialization.
376
377 ccfile <<
378 "namespace {\n" <<
379 "\n" <<
380 "const char* values[] = {\n";
381
382 // Output the identifiers and the associated text.
383 idents = sortedIdentifiers(dictionary);
384 for (auto const& i : idents) {
385 ccfile << " \"" << i << "\", \"" << quoteString(dictionary.getText(i)) << "\",\n";
386 }
387
388 // ... and the postamble
389 ccfile <<
390 " NULL\n" <<
391 "};\n" <<
392 "\n" <<
393 "const isc::log::MessageInitializer initializer(values);\n" <<
394 "\n" <<
395 "} // Anonymous namespace\n" <<
396 "\n";
397
398 // Report errors (if any) and exit
399 if (ccfile.fail()) {
400 isc_throw_4(MessageException, "Error writing to output file",
401 LOG_WRITE_ERROR, program_file.str(), strerror(errno),
402 0);
403 }
404
405 ccfile.close();
406}
407
414void
416
417 // Get the duplicates (the overflow) and, if present, sort them into some
418 // order and remove those which occur more than once (which mean that they
419 // occur more than twice in the input file).
420 MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
421 if (!duplicates.empty()) {
422 cout << "Error: the following duplicate IDs were found:\n";
423
424 sort(duplicates.begin(), duplicates.end());
425 MessageReader::MessageIDCollection::iterator new_end =
426 unique(duplicates.begin(), duplicates.end());
427 duplicates.erase(new_end, duplicates.end());
428 for (auto const& i : duplicates) {
429 cout << " " << i << "\n";
430 }
431 exit(1);
432 }
433}
434
439int
440main(int argc, char* argv[]) {
441
442 const char* soptions = "hvpd:"; // Short options
443
444 optind = 1; // Ensure we start a new scan
445 int opt; // Value of the option
446
447 const char *output_directory = NULL;
448
449 while ((opt = getopt(argc, argv, soptions)) != -1) {
450 switch (opt) {
451 case 'd':
452 output_directory = optarg;
453 break;
454
455 case 'h':
456 usage();
457 return (0);
458
459 case 'v':
460 version();
461 return (0);
462
463 default:
464 // A message will have already been output about the error.
465 return (1);
466 }
467 }
468
469 // Do we have the message file?
470 if (optind < (argc - 1)) {
471 cout << "Error: excess arguments in command line\n";
472 usage();
473 return (1);
474 } else if (optind >= argc) {
475 cout << "Error: missing message file\n";
476 usage();
477 return (1);
478 }
479 string message_file = argv[optind];
480
481 try {
482 // Have identified the file, so process it. First create a local
483 // dictionary into which the data will be put.
484 MessageDictionary dictionary;
485
486 // Read the data into it.
487 MessageReader reader(&dictionary);
488 reader.readFile(message_file);
489
490 // Error (and quit) if there are of any duplicates encountered.
491 errorDuplicates(reader);
492
493 // Get the namespace into which the message definitions will be put and
494 // split it into components.
495 vector<string> ns_components =
496 splitNamespace(reader.getNamespace());
497
498 // Write the header file.
499 writeHeaderFile(message_file, ns_components, dictionary,
500 output_directory);
501
502 // Write the file that defines the message symbols and text
503 writeProgramFile(message_file, ns_components, dictionary,
504 output_directory);
505
506 } catch (const MessageException& e) {
507 // Create an error message from the ID and the text
509 string text = e.id();
510 text += ", ";
511 text += global->getText(e.id());
512 // Format with arguments
513 vector<string> args(e.arguments());
514 for (size_t i(0); i < args.size(); ++ i) {
515 try {
516 replacePlaceholder(text, args[i], i + 1);
517 } catch (...) {
518 // Error in error handling: nothing right to do...
519 }
520 }
521
522 cerr << text << "\n";
523
524 return (1);
525 } catch (const std::exception& ex) {
526 cerr << "Fatal error: " << ex.what() << "\n";
527
528 return (1);
529 } catch (...) {
530 cerr << "Fatal error\n";
531
532 return (1);
533 }
534
535 return (0);
536}
static const MessageDictionaryPtr & globalDictionary()
Return Global Dictionary.
Read Message File.
std::vector< std::string > MessageIDCollection
Visible collection types.
#define isc_throw_4(type, stream, param1, param2, param3, param4)
Similar as isc_throw, but allows the exception to have four additional parameters (the stream/text go...
void writeHeaderFile(const string &file, const vector< string > &ns_components, MessageDictionary &dictionary, const char *output_directory)
Write Header File.
int main(int argc, char *argv[])
Main Program.
char replaceNonAlphaNum(char c)
Convert Non Alpha-Numeric Characters to Underscores.
vector< string > splitNamespace(string ns)
Split Namespace.
void usage()
Print Usage.
void writeClosingNamespace(ostream &output, const vector< string > &ns)
Write Closing Namespace(s)
void writeProgramFile(const string &file, const vector< string > &ns_components, MessageDictionary &dictionary, const char *output_directory)
Write Program File.
void writeOpeningNamespace(ostream &output, const vector< string > &ns)
Write Opening Namespace(s)
void version()
Print Version.
string quoteString(const string &instring)
Quote String.
vector< string > sortedIdentifiers(MessageDictionary &dictionary)
Sorted Identifiers.
string sentinel(Path &file)
Create Header Sentinel.
void errorDuplicates(MessageReader &reader)
Error and exit if there are duplicate entries.
const isc::log::MessageID LOG_OPEN_OUTPUT_FAIL
void replacePlaceholder(std::string &message, const string &arg, const unsigned placeholder)
The internal replacement routine.
boost::shared_ptr< MessageDictionary > MessageDictionaryPtr
Shared pointer to the MessageDictionary.
const isc::log::MessageID LOG_WRITE_ERROR
vector< string > tokens(const string &text, const string &delim, bool escape)
Split string into tokens.
Definition str.cc:52
void uppercase(string &text)
Convert string to uppercase.
Definition str.cc:124
Paths on a filesystem.
Definition filesystem.h:52
std::string extension() const
Get the extension of the file.
std::string stem() const
Get the base name of the file without the extension.