message.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. // $Id$
  15. #include <cctype>
  16. #include <cstddef>
  17. #include <fstream>
  18. #include <iostream>
  19. #include <string>
  20. #include <vector>
  21. #include <errno.h>
  22. #include <getopt.h>
  23. #include <string.h>
  24. #include <time.h>
  25. #include <unistd.h>
  26. #include <log/filename.h>
  27. #include <log/message_dictionary.h>
  28. #include <log/message_exception.h>
  29. #include <log/message_reader.h>
  30. #include <log/messagedef.h>
  31. #include <log/strutil.h>
  32. #include <log/logger.h>
  33. using namespace std;
  34. using namespace isc::log;
  35. static const char* VERSION = "1.0-0";
  36. /// \brief Message Compiler
  37. ///
  38. /// \b Overview<BR>
  39. /// This is the program that takes as input a message file and produces:
  40. ///
  41. /// \li A .h file containing message definition
  42. /// \li A .cc file containing code that adds the messages to the program's
  43. /// message disctionary at start-up time.
  44. ///
  45. /// Alternatively, the program can produce a .py file that contains the
  46. /// message definitions.
  47. ///
  48. /// \b Invocation<BR>
  49. /// The program is invoked with the command:
  50. ///
  51. /// <tt>message [-p] \<message-file\></tt>
  52. ///
  53. /// It reads the message file and writes out two files of the same name but with
  54. /// extensions of .h and .cc.
  55. ///
  56. /// If \c -p is specified, the C++ files are not written; instead a Python file
  57. /// of the same name (but with the file extension .py) is written.
  58. /// \brief Print Version
  59. ///
  60. /// Prints the program's version number.
  61. void
  62. version() {
  63. cout << VERSION << "\n";
  64. }
  65. /// \brief Print Usage
  66. ///
  67. /// Prints program usage to stdout.
  68. void
  69. usage() {
  70. cout <<
  71. "Usage: message [-h] [-p] [-v] <message-file>\n" <<
  72. "\n" <<
  73. "-h Print this message and exit\n" <<
  74. "-p Output a Python module holding the message definitions.\n" <<
  75. " By default a C++ header file and implementation file are\n" <<
  76. " written.\n" <<
  77. "-v Print the program version and exit\n" <<
  78. "\n" <<
  79. "<message-file> is the name of the input message file.\n";
  80. }
  81. /// \brief Create Time
  82. ///
  83. /// Returns the current time as a suitably-formatted string.
  84. ///
  85. /// \return Current time
  86. string
  87. currentTime() {
  88. // Get a text representation of the current time.
  89. time_t curtime;
  90. time(&curtime);
  91. char* buffer = ctime(&curtime);
  92. // Convert to string and strip out the trailing newline
  93. string current_time = buffer;
  94. return isc::strutil::trim(current_time);
  95. }
  96. /// \brief Create Header Sentinel
  97. ///
  98. /// Given the name of a file, create an #ifdef sentinel name. The name is
  99. /// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
  100. /// extension less the leading period. The sentinel will be upper-case.
  101. ///
  102. /// \param file Filename object representing the file.
  103. ///
  104. /// \return Sentinel name
  105. string
  106. sentinel(Filename& file) {
  107. string name = file.name();
  108. string ext = file.extension();
  109. string sentinel_text = "__" + name + "_" + ext.substr(1);
  110. isc::strutil::uppercase(sentinel_text);
  111. return sentinel_text;
  112. }
  113. /// \brief Quote String
  114. ///
  115. /// Inserts an escape character (a backslash) prior to any double quote
  116. /// characters. This is used to handle the fact that the input file does not
  117. /// contain quotes, yet the string will be included in a C++ literal string.
  118. string
  119. quoteString(const string& instring) {
  120. // Create the output string and reserve the space needed to hold the input
  121. // string. (Most input strings will not contain quotes, so this single
  122. // reservation should be all that is needed.)
  123. string outstring;
  124. outstring.reserve(instring.size());
  125. // Iterate through the input string, preceding quotes with a slash.
  126. for (size_t i = 0; i < instring.size(); ++i) {
  127. if (instring[i] == '"') {
  128. outstring += '\\';
  129. }
  130. outstring += instring[i];
  131. }
  132. return outstring;
  133. }
  134. /// \brief Sorted Identifiers
  135. ///
  136. /// Given a dictionary, return a vector holding the message IDs in sorted
  137. /// order.
  138. ///
  139. /// \param dictionary Dictionary to examine
  140. ///
  141. /// \return Sorted list of message IDs
  142. vector<string>
  143. sortedIdentifiers(MessageDictionary* dictionary) {
  144. vector<string> ident;
  145. for (MessageDictionary::const_iterator i = dictionary->begin();
  146. i != dictionary->end(); ++i) {
  147. ident.push_back(i->first);
  148. }
  149. sort(ident.begin(), ident.end());
  150. return ident;
  151. }
  152. /// \brief Split Namespace
  153. ///
  154. /// The $NAMESPACE directive may well specify a namespace in the form a::b.
  155. /// Unfortunately, the C++ "namespace" statement can only accept a single
  156. /// string - to set up the namespace of "a::b" requires two statements, one
  157. /// for "namspace a" and the other for "namespace b".
  158. ///
  159. /// This function returns the set of namespace components as a vector of
  160. /// strings. A vector of one element, containing the empty string, is returned
  161. /// if the anonymous namespace is specified.
  162. ///
  163. /// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
  164. /// it.)
  165. vector<string>
  166. splitNamespace(string ns) {
  167. vector<string> components;
  168. if (ns == "::") {
  169. // Unnamed namespace
  170. components.push_back("");
  171. } else {
  172. // Namespaces components are separated by double colon characters -
  173. // convert to single colons.
  174. size_t dcolon;
  175. while ((dcolon = ns.find("::")) != string::npos) {
  176. ns.replace(dcolon, 2, ":");
  177. }
  178. // ... and return the vector of namespace components split on the single
  179. // colon.
  180. components = isc::strutil::tokens(ns, ":");
  181. }
  182. return components;
  183. }
  184. /// \brief Write Opening Namespace(s)
  185. ///
  186. /// Writes the lines listing the namespaces in use.
  187. void
  188. writeOpeningNamespace(ostream& output, vector<string>& ns) {
  189. if (!ns.empty()) {
  190. if (ns[0].empty()) {
  191. // Empty namespace
  192. output << "namespace {\n";
  193. } else {
  194. // Output namespaces in correct order
  195. for (int i = 0; i < ns.size(); ++i) {
  196. output << "namespace " << ns[i] << " {\n";
  197. }
  198. }
  199. output << "\n";
  200. }
  201. }
  202. /// \brief Write Closing Namespace(s)
  203. ///
  204. /// Writes the lines listing the namespaces in use.
  205. void
  206. writeClosingNamespace(ostream& output, vector<string>& ns) {
  207. if (!ns.empty()) {
  208. if (ns[0].empty()) {
  209. output << "} // Unnamed namespace\n";
  210. } else {
  211. for (int i = ns.size() - 1; i >= 0; --i) {
  212. output << "} // namespace " << ns[i] << "\n";
  213. }
  214. }
  215. output << "\n";
  216. }
  217. }
  218. /// \brief Write Header File
  219. ///
  220. /// Writes the C++ header file containing the symbol definitions.
  221. ///
  222. /// \param file Name of the message file. The header file is written to a
  223. /// file of the same name but with a .h suffix.
  224. /// \param prefix Prefix string to use in symbols
  225. /// \param ns Namespace in which the definitions are to be placed. An empty
  226. /// string indicates no namespace.
  227. /// \param dictionary Dictionary holding the message definitions.
  228. void
  229. writeHeaderFile(const string& file, const string& prefix, const string& ns,
  230. string& mi_name, MessageDictionary* dictionary)
  231. {
  232. Filename message_file(file);
  233. Filename header_file(message_file.useAsDefault(".h"));
  234. // Text to use as the sentinels.
  235. string sentinel_text = sentinel(header_file);
  236. // Open the output file for writing
  237. ofstream hfile(header_file.fullName().c_str());
  238. try {
  239. if (hfile.fail()) {
  240. throw MessageException(MSG_OPENOUT, header_file.fullName(),
  241. strerror(errno));
  242. }
  243. // Write the header preamble. If there is an error, we'll pick it up
  244. // after the last write.
  245. hfile <<
  246. "// File created from " << message_file.fullName() << " on " <<
  247. currentTime() << "\n" <<
  248. "\n" <<
  249. "#ifndef " << sentinel_text << "\n" <<
  250. "#define " << sentinel_text << "\n" <<
  251. "\n" <<
  252. "#include <log/message_types.h>\n" <<
  253. "#include <log/message_initializer.h>\n" <<
  254. "\n";
  255. // Namespaces
  256. vector<string> ns_components = splitNamespace(ns);
  257. writeOpeningNamespace(hfile, ns_components);
  258. // Now the m,essage identifications themselves.
  259. vector<string> idents = sortedIdentifiers(dictionary);
  260. for (vector<string>::const_iterator j = idents.begin();
  261. j != idents.end(); ++j) {
  262. hfile << "static const isc::log::MessageID " << prefix << *j <<
  263. " = \"" << *j << "\";\n";
  264. }
  265. hfile << "\n";
  266. // Close off namespaces if appropriate.
  267. writeClosingNamespace(hfile, ns_components);
  268. // Now create the reference to the message initializer to ensure that
  269. // it gets run at program startup. Note that even the instantiator
  270. // object is given its own unique name - multiple message header files
  271. // might be included in the file, and identical multiple static names
  272. // would clash.
  273. hfile <<
  274. "namespace isc {\n" <<
  275. "namespace log {\n" <<
  276. "\n" <<
  277. "// The next two objects are needed to bring the default message\n" <<
  278. "// definitions into the program. They make sure that the file\n" <<
  279. "// containing the message text is included in the link process.\n" <<
  280. "//\n" <<
  281. "// The objects are uniquely named (with file name and date and\n" <<
  282. "// time of compilation) to avoid clashes with other objects of\n" <<
  283. "// the same type, either by another #include or as a global\n" <<
  284. "// symbol in another module.\n" <<
  285. "\n" <<
  286. "extern MessageInitializer " << mi_name << ";\n" <<
  287. "static MessageInstantiator instantiate_" << mi_name << "(\n" <<
  288. " &" << mi_name << ");\n" <<
  289. "\n" <<
  290. "} // namespace log\n" <<
  291. "} // namespace isc\n";
  292. // ... and finally the postamble
  293. hfile << "#endif // " << sentinel_text << "\n";
  294. // Report errors (if any) and exit
  295. if (hfile.fail()) {
  296. throw MessageException(MSG_WRITERR, header_file.fullName(),
  297. strerror(errno));
  298. }
  299. hfile.close();
  300. }
  301. catch (MessageException&) {
  302. hfile.close();
  303. throw;
  304. }
  305. }
  306. /// \brief Convert Non Alpha-Numeric Characters to Underscores
  307. ///
  308. /// Simple function for use in a call to transform
  309. char
  310. replaceNonAlphaNum(char c) {
  311. return (isalnum(c) ? c : '_');
  312. }
  313. /// \brief Write Program File
  314. ///
  315. /// Writes the C++ source code file. This defines an external objects whose
  316. /// constructor is run at initialization time. The constructor adds the message
  317. /// definitions to the main global dictionary.
  318. string
  319. writeProgramFile(const string& file, MessageDictionary* dictionary)
  320. {
  321. Filename message_file(file);
  322. Filename program_file(message_file.useAsDefault(".cc"));
  323. // Open the output file for writing
  324. ofstream ccfile(program_file.fullName().c_str());
  325. try {
  326. if (ccfile.fail()) {
  327. throw MessageException(MSG_OPENOUT, program_file.fullName(),
  328. strerror(errno));
  329. }
  330. // Write the preamble. If there is an error, we'll pick it up after
  331. // the last write.
  332. ccfile <<
  333. "// File created from " << message_file.fullName() << " on " <<
  334. currentTime() << "\n" <<
  335. "\n" <<
  336. "#include <cstddef>\n" <<
  337. "#include <log/message_initializer.h>\n" <<
  338. "\n" <<
  339. "namespace {\n" <<
  340. "\n" <<
  341. "const char* values[] = {\n";
  342. // Output the identifiers and the associated text.
  343. vector<string> idents = sortedIdentifiers(dictionary);
  344. for (vector<string>::const_iterator i = idents.begin();
  345. i != idents.end(); ++i) {
  346. ccfile << " \"" << *i << "\", \"" <<
  347. quoteString(dictionary->getText(*i)) << "\",\n";
  348. }
  349. // ... and the postamble
  350. ccfile <<
  351. " NULL\n" <<
  352. "};\n" <<
  353. "\n" <<
  354. "} // Anonymous namespace\n" <<
  355. "\n";
  356. // Now construct a unique name. We don't put the message initializer as
  357. // a static variable or in an anonymous namespace lest the C++
  358. // compiler's optimizer decides it can optimise it away.
  359. string unique_name = program_file.name() + program_file.extension() +
  360. "_" + currentTime();
  361. transform(unique_name.begin(), unique_name.end(), unique_name.begin(),
  362. replaceNonAlphaNum);
  363. // ... and write the initialization code
  364. ccfile <<
  365. "namespace isc {\n" <<
  366. "namespace log {\n" <<
  367. "\n" <<
  368. "MessageInitializer " << unique_name << "(values);\n" <<
  369. "\n" <<
  370. "} // namespace log\n" <<
  371. "} // namespace isc\n" <<
  372. "\n";
  373. // Report errors (if any) and exit
  374. if (ccfile.fail()) {
  375. throw MessageException(MSG_WRITERR, program_file.fullName(),
  376. strerror(errno));
  377. }
  378. ccfile.close();
  379. return unique_name;
  380. }
  381. catch (MessageException&) {
  382. ccfile.close();
  383. throw;
  384. }
  385. }
  386. /// \brief Warn of Duplicate Entries
  387. ///
  388. /// If the input file contained duplicate message IDs, only the first will be
  389. /// processed. However, we should warn about it.
  390. ///
  391. /// \param reader Message Reader used to read the file
  392. void
  393. warnDuplicates(MessageReader& reader) {
  394. // Get the duplicates (the overflow) and, if present, sort them into some
  395. // order and remove those which occur more than once (which mean that they
  396. // occur more than twice in the input file).
  397. MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
  398. if (duplicates.size() > 0) {
  399. cout << "Warning: the following duplicate IDs were found:\n";
  400. sort(duplicates.begin(), duplicates.end());
  401. MessageReader::MessageIDCollection::iterator new_end =
  402. unique(duplicates.begin(), duplicates.end());
  403. for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
  404. i != new_end; ++i) {
  405. cout << " " << *i << "\n";
  406. }
  407. }
  408. }
  409. /// \brief Main Program
  410. ///
  411. /// Parses the options then dispatches to the appropriate function. See the
  412. /// main file header for the invocation.
  413. int
  414. main(int argc, char** argv) {
  415. const struct option loptions[] = { // Long options
  416. {"help", no_argument, NULL, 'h'},
  417. {"version", no_argument, NULL, 'v'},
  418. {NULL, 0, NULL, 0 }
  419. };
  420. const char* soptions = "hv"; // Short options
  421. optind = 1; // Ensure we start a new scan
  422. int opt; // Value of the option
  423. while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
  424. switch (opt) {
  425. case 'h':
  426. usage();
  427. return 0;
  428. case 'v':
  429. version();
  430. return 0;
  431. default:
  432. // A message will have already been output about the error.
  433. return 1;
  434. }
  435. }
  436. // Do we have the message file?
  437. if (optind < (argc - 1)) {
  438. cout << "Error: excess arguments in command line\n";
  439. usage();
  440. return 1;
  441. } else if (optind >= argc) {
  442. cout << "Error: missing message file\n";
  443. usage();
  444. return 1;
  445. }
  446. string message_file = argv[optind];
  447. try {
  448. // Have identified the file, so process it. First create a local
  449. // dictionary into which the data will be put.
  450. MessageDictionary dictionary;
  451. // Read the data into it.
  452. MessageReader reader(&dictionary);
  453. reader.readFile(message_file);
  454. // Write the file that defines the message text
  455. std::string mi_name =
  456. writeProgramFile(message_file, &dictionary);
  457. // Now write the header file.
  458. writeHeaderFile(message_file, reader.getPrefix(), reader.getNamespace(),
  459. mi_name, &dictionary);
  460. // Finally, warn of any duplicates encountered.
  461. warnDuplicates(reader);
  462. }
  463. catch (MessageException& e) {
  464. // Create an error message from the ID and the text
  465. MessageDictionary* global = MessageDictionary::globalDictionary();
  466. string text = e.id();
  467. text += ", ";
  468. text += global->getText(e.id());
  469. // Format with arguments
  470. text = isc::strutil::format(text, e.arguments());
  471. cerr << text << "\n";
  472. return 1;
  473. }
  474. return 0;
  475. }