message.cc 16.5 KB
Newer Older
1
// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
#include <cctype>
16
#include <cstddef>
17
#include <fstream>
18
#include <iostream>
19
20
21
22
#include <string>
#include <vector>

#include <errno.h>
23
#include <getopt.h>
24
25
26
27
28
29
30
31
32
33
34
35
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <log/filename.h>
#include <log/message_dictionary.h>
#include <log/message_exception.h>
#include <log/message_reader.h>
#include <log/messagedef.h>
#include <log/strutil.h>

#include <log/logger.h>
36
37

using namespace std;
38
using namespace isc::log;
39
40
41
42
43
44
45
46
47
48

static const char* VERSION = "1.0-0";

/// \brief Message Compiler
///
/// \b Overview<BR>
/// This is the program that takes as input a message file and produces:
///
/// \li A .h file containing message definition
/// \li A .cc file containing code that adds the messages to the program's
49
/// message dictionary at start-up time.
50
51
52
53
54
55
56
57
///
/// Alternatively, the program can produce a .py file that contains the
/// message definitions.
///

/// \b Invocation<BR>
/// The program is invoked with the command:
///
58
/// <tt>message [-v | -h | \<message-file\>]</tt>
59
60
61
62
///
/// It reads the message file and writes out two files of the same name but with
/// extensions of .h and .cc.
///
63
64
/// \-v causes it to print the version number and exit. \-h prints a help
/// message (and exits).
65
66
67
68
69
70


/// \brief Print Version
///
/// Prints the program's version number.

71
72
void
version() {
73
74
75
76
77
78
79
    cout << VERSION << "\n";
}

/// \brief Print Usage
///
/// Prints program usage to stdout.

80
81
void
usage() {
82
    cout <<
83
        "Usage: message [-h] [-v] <message-file>\n" <<
84
85
86
87
88
89
90
        "\n" <<
        "-h       Print this message and exit\n" <<
        "-v       Print the program version and exit\n" <<
        "\n" <<
        "<message-file> is the name of the input message file.\n";
}

91
92
93
94
95
96
97

/// \brief Create Time
///
/// Returns the current time as a suitably-formatted string.
///
/// \return Current time

98
99
string
currentTime() {
100

101
    // Get a text representation of the current time.
102
103
    time_t curtime;
    time(&curtime);
104
    char* buffer = ctime(&curtime);
105
106
107

    // Convert to string and strip out the trailing newline
    string current_time = buffer;
108
    return isc::strutil::trim(current_time);
109
110
111
112
113
114
115
116
117
118
119
120
121
}


/// \brief Create Header Sentinel
///
/// Given the name of a file, create an #ifdef sentinel name.  The name is
/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
/// extension less the leading period.  The sentinel will be upper-case.
///
/// \param file Filename object representing the file.
///
/// \return Sentinel name

122
123
string
sentinel(Filename& file) {
124
125
126
127
128

    string name = file.name();
    string ext = file.extension();
    string sentinel_text = "__" + name + "_" + ext.substr(1);
    isc::strutil::uppercase(sentinel_text);
129
    return sentinel_text;
130
131
132
133
134
135
136
137
138
}


/// \brief Quote String
///
/// Inserts an escape character (a backslash) prior to any double quote
/// characters.  This is used to handle the fact that the input file does not
/// contain quotes, yet the string will be included in a C++ literal string.

139
140
string
quoteString(const string& instring) {
141
142
143

    // Create the output string and reserve the space needed to hold the input
    // string. (Most input strings will not contain quotes, so this single
144
    // reservation should be all that is needed.)
145
146
147
148
149
150
151
152
153
154
155
    string outstring;
    outstring.reserve(instring.size());

    // Iterate through the input string, preceding quotes with a slash.
    for (size_t i = 0; i < instring.size(); ++i) {
        if (instring[i] == '"') {
            outstring += '\\';
        }
        outstring += instring[i];
    }

156
    return outstring;
157
158
159
160
161
162
163
164
165
166
167
168
}


/// \brief Sorted Identifiers
///
/// Given a dictionary, return a vector holding the message IDs in sorted
/// order.
///
/// \param dictionary Dictionary to examine
///
/// \return Sorted list of message IDs

169
vector<string>
170
sortedIdentifiers(MessageDictionary& dictionary) {
171
    vector<string> ident;
172

173
174
    for (MessageDictionary::const_iterator i = dictionary.begin();
         i != dictionary.end(); ++i) {
175
176
177
178
        ident.push_back(i->first);
    }
    sort(ident.begin(), ident.end());

179
    return ident;
180
181
182
}


183
184
185
186
187
188
189
190
/// \brief Split Namespace
///
/// The $NAMESPACE directive may well specify a namespace in the form a::b.
/// Unfortunately, the C++ "namespace" statement can only accept a single
/// string - to set up the namespace of "a::b" requires two statements, one
/// for "namspace a" and the other for "namespace b".
///
/// This function returns the set of namespace components as a vector of
191
192
/// strings.  A vector of one element, containing the empty string, is returned
/// if the anonymous namespace is specified.
193
///
194
/// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
195
196
/// it.)

197
198
vector<string>
splitNamespace(string ns) {
199

200
201
202
203
204
    // Namespaces components are separated by double colon characters -
    // convert to single colons.
    size_t dcolon;
    while ((dcolon = ns.find("::")) != string::npos) {
        ns.replace(dcolon, 2, ":");
205
206
    }

207
208
209
    // ... and return the vector of namespace components split on the single
    // colon.
    return isc::strutil::tokens(ns, ":");
210
211
212
}


213
214
215
216
/// \brief Write Opening Namespace(s)
///
/// Writes the lines listing the namespaces in use.
void
217
writeOpeningNamespace(ostream& output, const vector<string>& ns) {
218
    if (!ns.empty()) {
219

220
221
222
        // Output namespaces in correct order
        for (int i = 0; i < ns.size(); ++i) {
            output << "namespace " << ns[i] << " {\n";
223
224
225
226
227
228
229
230
231
232
        }
        output << "\n";
    }
}


/// \brief Write Closing Namespace(s)
///
/// Writes the lines listing the namespaces in use.
void
233
writeClosingNamespace(ostream& output, const vector<string>& ns) {
234
    if (!ns.empty()) {
235
236
        for (int i = ns.size() - 1; i >= 0; --i) {
            output << "} // namespace " << ns[i] << "\n";
237
238
239
240
241
242
        }
        output << "\n";
    }
}


243
244
/// \brief Write Header File
///
245
246
247
248
/// Writes the C++ header file containing the symbol definitions.  These are
/// "extern" references to definitions in the .cc file.  As such, they should
/// take up no space in the module in which they are included, and redundant
/// references should be removed by the compiler.
249
250
251
252
///
/// \param file Name of the message file.  The header file is written to a
/// file of the same name but with a .h suffix.
/// \param prefix Prefix string to use in symbols
253
/// \param ns Namespace in which the definitions are to be placed.  An empty
254
/// string indicates no namespace.
255
256
/// \param dictionary Dictionary holding the message definitions.

257
void
258
259
writeHeaderFile(const string& file, const string& prefix,
        const vector<string>& ns_components, MessageDictionary& dictionary)
260
261
262
263
264
265
266
267
268
269
270
271
{
    Filename message_file(file);
    Filename header_file(message_file.useAsDefault(".h"));

    // Text to use as the sentinels.
    string sentinel_text = sentinel(header_file);

    // Open the output file for writing
    ofstream hfile(header_file.fullName().c_str());

    try {
        if (hfile.fail()) {
272
            throw MessageException(MSG_OPNMSGOUT, header_file.fullName(),
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
                strerror(errno));
        }

        // Write the header preamble.  If there is an error, we'll pick it up
        // after the last write.

        hfile <<
            "// File created from " << message_file.fullName() << " on " <<
                currentTime() << "\n" <<
             "\n" <<
             "#ifndef " << sentinel_text << "\n" <<
             "#define "  << sentinel_text << "\n" <<
             "\n" <<
             "#include <log/message_types.h>\n" <<
             "\n";

289
        // Write the message identifiers, bounded by a namespace declaration
290
        writeOpeningNamespace(hfile, ns_components);
291
292
293

        vector<string> idents = sortedIdentifiers(dictionary);
        for (vector<string>::const_iterator j = idents.begin();
294
            j != idents.end(); ++j) {
295
            hfile << "extern const isc::log::MessageID " << prefix << *j << ";\n";
296
        }
297
298
        hfile << "\n";

299
        writeClosingNamespace(hfile, ns_components);
300

301
        // ... and finally the postamble
302
        hfile << "#endif // " << sentinel_text << "\n";
303
304
305

        // Report errors (if any) and exit
        if (hfile.fail()) {
306
            throw MessageException(MSG_MSGWRTERR, header_file.fullName(),
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
                strerror(errno));
        }

        hfile.close();
    }
    catch (MessageException&) {
        hfile.close();
        throw;
    }
}


/// \brief Convert Non Alpha-Numeric Characters to Underscores
///
/// Simple function for use in a call to transform

323
324
char
replaceNonAlphaNum(char c) {
325
326
327
328
329
330
    return (isalnum(c) ? c : '_');
}


/// \brief Write Program File
///
331
/// Writes the C++ source code file.  This defines the text of the message
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/// symbols, as well as the initializer object that sets the entries in the
/// global dictionary.
///
/// The construction of the initializer object loads the dictionary with the
/// message text.  However, nothing actually references it.  If the initializer
/// were in a file by itself, the lack of things referencing it would cause the
/// linker to ignore it when pulling modules out of the logging library in a
/// static link.  By including it in the file with the symbol definitions, the
/// module will get included in the link process to resolve the symbol
/// definitions, and so the initializer object will be included in the final
/// image. (Note that there are no such problems when the logging library is
/// built as a dynamically-linked library: the whole library - including the
/// initializer module - gets mapped into address space when the library is
/// loaded, after which all the initializing code (including the constructors
/// of objects declared outside functions) gets run.)
///
/// There _may_ be a problem when we come to port this to Windows.  Microsoft
/// Visual Studio contains a "Whole Program Optimisation" option, where the
/// optimisation is done at link-time, not compiler-time.  In this it _may_
/// decide to remove the initializer object because of a lack of references
/// to it.  But until BIND-10 is ported to Windows, we won't know.
353

354
355
356
void
writeProgramFile(const string& file, const string& prefix,
    const vector<string>& ns_components, MessageDictionary& dictionary)
357
358
359
360
361
362
363
364
{
    Filename message_file(file);
    Filename program_file(message_file.useAsDefault(".cc"));

    // Open the output file for writing
    ofstream ccfile(program_file.fullName().c_str());
    try {
        if (ccfile.fail()) {
365
            throw MessageException(MSG_OPNMSGOUT, program_file.fullName(),
366
367
368
369
370
371
372
373
374
375
376
                strerror(errno));
        }

        // Write the preamble.  If there is an error, we'll pick it up after
        // the last write.

        ccfile <<
            "// File created from " << message_file.fullName() << " on " <<
                currentTime() << "\n" <<
             "\n" <<
             "#include <cstddef>\n" <<
377
             "#include <log/message_types.h>\n" <<
378
             "#include <log/message_initializer.h>\n" <<
379
             "\n";
380

381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
        // Declare the message symbols themselves.

        writeOpeningNamespace(ccfile, ns_components);

        vector<string> idents = sortedIdentifiers(dictionary);
        for (vector<string>::const_iterator j = idents.begin();
            j != idents.end(); ++j) {
            ccfile << "extern const isc::log::MessageID " << prefix << *j <<
                " = \"" << *j << "\";\n";
        }
        ccfile << "\n";

        writeClosingNamespace(ccfile, ns_components);

        // Now the code for the message initialization.

        ccfile <<
398
399
400
401
402
             "namespace {\n" <<
             "\n" <<
             "const char* values[] = {\n";

        // Output the identifiers and the associated text.
403
        idents = sortedIdentifiers(dictionary);
404
        for (vector<string>::const_iterator i = idents.begin();
405
406
            i != idents.end(); ++i) {
                ccfile << "    \"" << *i << "\", \"" <<
407
                    quoteString(dictionary.getText(*i)) << "\",\n";
408
409
        }

410

411
412
413
414
415
        // ... and the postamble
        ccfile <<
            "    NULL\n" <<
            "};\n" <<
            "\n" <<
Stephen Morris's avatar
Stephen Morris committed
416
            "const isc::log::MessageInitializer initializer(values);\n" <<
417
            "\n" <<
418
            "} // Anonymous namespace\n" <<
419
            "\n";
420
421
422

        // Report errors (if any) and exit
        if (ccfile.fail()) {
423
            throw MessageException(MSG_MSGWRTERR, program_file.fullName(),
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
                strerror(errno));
        }

        ccfile.close();
    }
    catch (MessageException&) {
        ccfile.close();
        throw;
    }
}


/// \brief Warn of Duplicate Entries
///
/// If the input file contained duplicate message IDs, only the first will be
/// processed.  However, we should warn about it.
///
441
/// \param reader Message Reader used to read the file
442

443
444
void
warnDuplicates(MessageReader& reader) {
445
446
447
448

    // Get the duplicates (the overflow) and, if present, sort them into some
    // order and remove those which occur more than once (which mean that they
    // occur more than twice in the input file).
449
    MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
450
451
452
453
    if (duplicates.size() > 0) {
        cout << "Warning: the following duplicate IDs were found:\n";

        sort(duplicates.begin(), duplicates.end());
454
        MessageReader::MessageIDCollection::iterator new_end =
455
            unique(duplicates.begin(), duplicates.end());
456
        for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
457
458
459
460
461
462
463
            i != new_end; ++i) {
            cout << "    " << *i << "\n";
        }
    }
}


464
465
466
467
468
/// \brief Main Program
///
/// Parses the options then dispatches to the appropriate function.  See the
/// main file header for the invocation.

469
470
int
main(int argc, char** argv) {
471

472
473
474
475
476
    const struct option loptions[] = {          // Long options
        {"help",    no_argument, NULL, 'h'},
        {"version", no_argument, NULL, 'v'},
        {NULL,      0,           NULL, 0  }
    };
477
    const char* soptions = "hv";               // Short options
478
479
480
481
482
483
484
485

    optind = 1;             // Ensure we start a new scan
    int  opt;               // Value of the option

    while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
        switch (opt) {
            case 'h':
                usage();
486
                return 0;
487
488
489

            case 'v':
                version();
490
                return 0;
491
492
493

            default:
                // A message will have already been output about the error.
494
                return 1;
495
496
497
498
499
        }
    }

    // Do we have the message file?
    if (optind < (argc - 1)) {
500
        cout << "Error: excess arguments in command line\n";
501
        usage();
502
        return 1;
503
504
    } else if (optind >= argc) {
        cout << "Error: missing message file\n";
505
        usage();
506
        return 1;
507
    }
508
509
510
511
512
513
514
515
516
517
518
    string message_file = argv[optind];

    try {
        // Have identified the file, so process it.  First create a local
        // dictionary into which the data will be put.
        MessageDictionary dictionary;

        // Read the data into it.
        MessageReader reader(&dictionary);
        reader.readFile(message_file);

519
520
521
522
523
524
525
526
527
528
529
        // Get the namespace into which the message definitions will be put and
        // split it into components.
        vector<string> ns_components = splitNamespace(reader.getNamespace());

        // Write the header file.
        writeHeaderFile(message_file, reader.getPrefix(), ns_components,
            dictionary);

        // Write the file that defines the message symbols and text
        writeProgramFile(message_file, reader.getPrefix(), ns_components,
            dictionary);
530

531
532

        // Finally, warn of any duplicates encountered.
533
        warnDuplicates(reader);
534
535
536
    }
    catch (MessageException& e) {
        // Create an error message from the ID and the text
537
        MessageDictionary& global = MessageDictionary::globalDictionary();
538
539
        string text = e.id();
        text += ", ";
540
        text += global.getText(e.id());
541
542
543
544
545

        // Format with arguments
        text = isc::strutil::format(text, e.arguments());
        cerr << text << "\n";

546
        return 1;
547
    }
548

549
    return 0;
550
551

}