Kea 3.1.5
log/compiler/message.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2025 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#include <kea_version.h>
9
10#include <cctype>
11#include <cstddef>
12#include <fstream>
13#include <iostream>
14#include <string>
15#include <vector>
16
17#include <errno.h>
18#include <getopt.h>
19#include <string.h>
20#include <time.h>
21#include <unistd.h>
22
24
25#include <util/filesystem.h>
26#include <util/str.h>
27
28#include <log/log_messages.h>
31#include <log/message_reader.h>
32
33#include <log/logger.h>
34
35using namespace std;
36using namespace isc::log;
37using namespace isc::util;
38using namespace isc::util::file;
39
62
66
67void
69 cout << VERSION << "\n";
70}
71
72void
74 cout << VERSION << " (" << SOURCE_OF_INSTALLATION << ")\n";
75}
76
80
81void
83 cout <<
84 "Usage: kea-msg-compiler [-h] [-v] [-d dir] <message-file>\n" <<
85 "\n" <<
86 "-h Print this message and exit\n" <<
87 "-v Print the program version and exit\n" <<
88 "-d <dir> Place output files in given directory\n" <<
89 "\n" <<
90 "<message-file> is the name of the input message file.\n";
91}
92
102
103string
105 string name = file.stem();
106 string ext = file.extension();
107 string sentinel_text = name + "_" + ext.substr(1);
108 str::uppercase(sentinel_text);
109 return (sentinel_text);
110}
111
117
118string
119quoteString(const string& instring) {
120
121 // Create the output string and reserve the space needed to hold the input
122 // string. (Most input strings will not contain quotes, so this single
123 // reservation should be all that is needed.)
124 string outstring;
125 outstring.reserve(instring.size());
126
127 // Iterate through the input string, preceding quotes with a slash.
128 for (size_t i = 0; i < instring.size(); ++i) {
129 if (instring[i] == '"') {
130 outstring += '\\';
131 }
132 outstring += instring[i];
133 }
134
135 return (outstring);
136}
137
146
147vector<string>
149 vector<string> ident;
150
151 for (auto const& i : dictionary) {
152 ident.push_back(i.first);
153 }
154 sort(ident.begin(), ident.end());
155
156 return (ident);
157}
158
172
173vector<string>
174splitNamespace(string ns) {
175
176 // Namespaces components are separated by double colon characters -
177 // convert to single colons.
178 size_t dcolon;
179 while ((dcolon = ns.find("::")) != string::npos) {
180 ns.replace(dcolon, 2, ":");
181 }
182
183 // ... and return the vector of namespace components split on the single
184 // colon.
185 return (str::tokens(ns, ":"));
186}
187
191void
192writeOpeningNamespace(ostream& output, const vector<string>& ns) {
193 if (!ns.empty()) {
194
195 // Output namespaces in correct order
196 for (vector<string>::size_type i = 0; i < ns.size(); ++i) {
197 output << "namespace " << ns[i] << " {\n";
198 }
199 output << "\n";
200 }
201}
202
206void
207writeClosingNamespace(ostream& output, const vector<string>& ns) {
208 if (!ns.empty()) {
209 for (int i = ns.size() - 1; i >= 0; --i) {
210 output << "} // namespace " << ns[i] << "\n";
211 }
212 output << "\n";
213 }
214}
215
231void
232writeHeaderFile(const string& file,
233 const vector<string>& ns_components,
234 MessageDictionary& dictionary,
235 const char* output_directory) {
236 Path message_file(file);
237 Path header_file(Path(file).replaceExtension(".h"));
238 if (output_directory != NULL) {
239 header_file.replaceParentPath(output_directory);
240 }
241
242 // Text to use as the sentinels.
243 string sentinel_text = sentinel(header_file);
244
245 // zero out the errno to be safe
246 errno = 0;
247
248 // Open the output file for writing
249 ofstream hfile(header_file.str());
250
251 if (hfile.fail()) {
252 isc_throw_4(MessageException, "Failed to open output file",
253 LOG_OPEN_OUTPUT_FAIL, header_file.str(),
254 strerror(errno), 0);
255 }
256
257 // Write the header preamble. If there is an error, we'll pick it up
258 // after the last write.
259
260 hfile <<
261 "// File created from " << message_file.str() << "\n" <<
262 "\n" <<
263 "#ifndef " << sentinel_text << "\n" <<
264 "#define " << sentinel_text << "\n" <<
265 "\n" <<
266 "#include <log/message_types.h>\n" <<
267 "\n";
268
269 // Write the message identifiers, bounded by a namespace declaration
270 writeOpeningNamespace(hfile, ns_components);
271
272 vector<string> idents = sortedIdentifiers(dictionary);
273 for (auto const& j : idents) {
274 hfile << "extern const isc::log::MessageID " << j << ";\n";
275 }
276 hfile << "\n";
277
278 writeClosingNamespace(hfile, ns_components);
279
280 // ... and finally the postamble
281 hfile << "#endif // " << sentinel_text << "\n";
282
283 // Report errors (if any) and exit
284 if (hfile.fail()) {
285 isc_throw_4(MessageException, "Error writing to output file",
286 LOG_WRITE_ERROR, header_file.str(), strerror(errno),
287 0);
288 }
289
290 hfile.close();
291}
292
296char
298 return (isalnum(c) ? c : '_');
299}
300
334void
335writeProgramFile(const string& file,
336 const vector<string>& ns_components,
337 MessageDictionary& dictionary,
338 const char* output_directory) {
339 Path message_file(file);
340 Path program_file(Path(file).replaceExtension(".cc"));
341 if (output_directory) {
342 program_file.replaceParentPath(output_directory);
343 }
344
345 // zero out the errno to be safe
346 errno = 0;
347
348 // Open the output file for writing
349 ofstream ccfile(program_file.str());
350
351 if (ccfile.fail()) {
352 isc_throw_4(MessageException, "Error opening output file",
353 LOG_OPEN_OUTPUT_FAIL, program_file.str(),
354 strerror(errno), 0);
355 }
356
357 // Write the preamble. If there is an error, we'll pick it up after
358 // the last write.
359
360 ccfile <<
361 "// File created from " << message_file.str() << "\n" <<
362 "\n" <<
363 "#include <cstddef>\n" <<
364 "#include <log/message_types.h>\n" <<
365 "#include <log/message_initializer.h>\n" <<
366 "\n";
367
368 // Declare the message symbols themselves.
369
370 writeOpeningNamespace(ccfile, ns_components);
371
372 vector<string> idents = sortedIdentifiers(dictionary);
373 for (auto const& j : idents) {
374 ccfile << "extern const isc::log::MessageID " << j <<
375 " = \"" << j << "\";\n";
376 }
377 ccfile << "\n";
378
379 writeClosingNamespace(ccfile, ns_components);
380
381 // Now the code for the message initialization.
382
383 ccfile <<
384 "namespace {\n" <<
385 "\n" <<
386 "const char* values[] = {\n";
387
388 // Output the identifiers and the associated text.
389 idents = sortedIdentifiers(dictionary);
390 for (auto const& i : idents) {
391 ccfile << " \"" << i << "\", \"" << quoteString(dictionary.getText(i)) << "\",\n";
392 }
393
394 // ... and the postamble
395 ccfile <<
396 " NULL\n" <<
397 "};\n" <<
398 "\n" <<
399 "const isc::log::MessageInitializer initializer(values);\n" <<
400 "\n" <<
401 "} // Anonymous namespace\n" <<
402 "\n";
403
404 // Report errors (if any) and exit
405 if (ccfile.fail()) {
406 isc_throw_4(MessageException, "Error writing to output file",
407 LOG_WRITE_ERROR, program_file.str(), strerror(errno),
408 0);
409 }
410
411 ccfile.close();
412}
413
420void
422
423 // Get the duplicates (the overflow) and, if present, sort them into some
424 // order and remove those which occur more than once (which mean that they
425 // occur more than twice in the input file).
427 if (!duplicates.empty()) {
428 cout << "Error: the following duplicate IDs were found:\n";
429
430 sort(duplicates.begin(), duplicates.end());
431 MessageReader::MessageIDCollection::iterator new_end =
432 unique(duplicates.begin(), duplicates.end());
433 duplicates.erase(new_end, duplicates.end());
434 for (auto const& i : duplicates) {
435 cout << " " << i << "\n";
436 }
437 exit(1);
438 }
439}
440
445int
446main(int argc, char* argv[]) {
447
448 const char* soptions = "hvVpd:"; // Short options
449
450 optind = 1; // Ensure we start a new scan
451 int opt; // Value of the option
452
453 const char *output_directory = NULL;
454
455 while ((opt = getopt(argc, argv, soptions)) != -1) {
456 switch (opt) {
457 case 'd':
458 output_directory = optarg;
459 break;
460
461 case 'h':
462 usage();
463 return (0);
464
465 case 'v':
466 version();
467 return (0);
468
469 case 'V':
471 return (0);
472
473 default:
474 // A message will have already been output about the error.
475 return (1);
476 }
477 }
478
479 // Do we have the message file?
480 if (optind < (argc - 1)) {
481 cout << "Error: excess arguments in command line\n";
482 usage();
483 return (1);
484 } else if (optind >= argc) {
485 cout << "Error: missing message file\n";
486 usage();
487 return (1);
488 }
489 string message_file = argv[optind];
490
491 try {
492 // Have identified the file, so process it. First create a local
493 // dictionary into which the data will be put.
494 MessageDictionary dictionary;
495
496 // Read the data into it.
497 MessageReader reader(&dictionary);
498 reader.readFile(message_file);
499
500 // Error (and quit) if there are of any duplicates encountered.
501 errorDuplicates(reader);
502
503 // Get the namespace into which the message definitions will be put and
504 // split it into components.
505 vector<string> ns_components =
507
508 // Write the header file.
509 writeHeaderFile(message_file, ns_components, dictionary,
510 output_directory);
511
512 // Write the file that defines the message symbols and text
513 writeProgramFile(message_file, ns_components, dictionary,
514 output_directory);
515
516 } catch (const MessageException& e) {
517 // Create an error message from the ID and the text
519 string text = e.id();
520 text += ", ";
521 text += global->getText(e.id());
522 // Format with arguments
523 vector<string> args(e.arguments());
524 for (size_t i(0); i < args.size(); ++ i) {
525 try {
526 replacePlaceholder(text, args[i], i + 1);
527 } catch (...) {
528 // Error in error handling: nothing right to do...
529 }
530 }
531
532 cerr << text << "\n";
533
534 return (1);
535 } catch (const std::exception& ex) {
536 cerr << "Fatal error: " << ex.what() << "\n";
537
538 return (1);
539 } catch (...) {
540 cerr << "Fatal error\n";
541
542 return (1);
543 }
544
545 return (0);
546}
static const MessageDictionaryPtr & globalDictionary()
Return Global Dictionary.
virtual const std::string & getText(const MessageID &ident) const
Get Message Text.
std::vector< std::string > arguments() const
Return Arguments.
MessageID id() const
Return Message ID.
Read Message File.
std::vector< std::string > MessageIDCollection
Visible collection types.
MessageIDCollection getNotAdded() const
Get Not-Added List.
virtual std::string getNamespace() const
Get Namespace.
virtual void readFile(const std::string &file, Mode mode=ADD)
Read File.
#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.
void extended_version()
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:110
Path & replaceParentPath(std::string const &replacement=std::string())
Trims {replacement} and replaces this instance's parent path with it.
std::string str() const
Get the path in textual format.