system_messages.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. // Copyright (C) 2015 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. // Produce System Messages Manual
  15. //
  16. // This tool reads all the message files given on the command line.
  17. // It pulls all the messages and description out, sorts them by
  18. // message ID, and writes them out as a single (formatted) file.
  19. //
  20. // Invocation:
  21. // The code is invoked using the command line:
  22. //
  23. // system_messages [-o <output-file>] <files>
  24. //
  25. // If no output file is specified, output is written to stdout.
  26. // The produced format is docbook XML.
  27. #include <algorithm>
  28. #include <fstream>
  29. #include <iostream>
  30. #include <map>
  31. #include <string>
  32. #include <vector>
  33. #include <stdlib.h>
  34. #include <string.h>
  35. #include <boost/lexical_cast.hpp>
  36. typedef std::vector<std::string> LinesType;
  37. /// @brief dictionary values
  38. struct Details {
  39. std::string text;
  40. LinesType description;
  41. std::string sname;
  42. std::string filename;
  43. };
  44. /// @brief Main dictionary holding all the messages.
  45. /// The messages are accumulated here before being printed in
  46. /// alphabetical order.
  47. typedef std::map<const std::string, Details> DictionaryType;
  48. DictionaryType dictionary;
  49. /// @brief The structure of the output page
  50. //
  51. /// header
  52. /// section header
  53. /// message
  54. /// separator
  55. /// message
  56. /// separator
  57. /// :
  58. /// separator
  59. /// message
  60. /// section trailer
  61. /// separator
  62. /// section header
  63. /// :
  64. /// section trailer
  65. /// trailer
  66. //
  67. /// (Indentation is not relevant - it has only been added to the above
  68. /// illustration to make the structure clearer.) The text of these section is:
  69. /// @name Constants for the output page
  70. //@{
  71. /// @brief File header
  72. /// this is output before anything else.
  73. const std::string FILE_HEADER =
  74. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
  75. <!DOCTYPE book PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\"\n\
  76. \"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\" [\n\
  77. <!ENTITY mdash \"&#x2014;\" >\n\
  78. <!ENTITY % version SYSTEM \"version.ent\">\n\
  79. %version;\n\
  80. ]>\n\
  81. <!--\n\
  82. This XML document is generated using the system_messages tool\n\
  83. based on the .mes message files.\n\
  84. \n\
  85. Do not edit this file.\n\
  86. -->\n\
  87. <book>\n\
  88. <?xml-stylesheet href=\"kea-guide.css\" type=\"text/css\"?>\n\
  89. \n\
  90. <bookinfo>\n\
  91. <title>Kea Messages Manual</title>\n\
  92. \n\
  93. <copyright>\n\
  94. <year>2011-2015</year><holder>Internet Systems Consortium, Inc.</holder>\n\
  95. </copyright>\n\
  96. \n\
  97. <abstract>\n\
  98. <para>\n\
  99. This is the messages manual for Kea version &__VERSION__;.\n\
  100. The most up-to-date version of this document, along with\n\
  101. other documents for Kea, can be found at\n\
  102. <ulink url=\"http://kea.isc.org/docs\"/>.\n\
  103. </para>\n\
  104. </abstract>\n\
  105. \n\
  106. <releaseinfo>This is the messages manual for Kea version\n\
  107. &__VERSION__;.</releaseinfo>\n\
  108. </bookinfo>\n\
  109. \n\
  110. <chapter id=\"intro\">\n\
  111. <title>Introduction</title>\n\
  112. <para>\n\
  113. This document lists each message that can be logged by the\n\
  114. programs in the Kea package. Each entry in this manual\n\
  115. is of the form:\n\
  116. <screen>IDENTIFICATION message-text</screen>\n\
  117. ... where \"IDENTIFICATION\" is the message identification included\n\
  118. in each message logged and \"message-text\" is the accompanying\n\
  119. message text. The \"message-text\" may include placeholders of the\n\
  120. form \"%1\", \"%2\" etc.; these parameters are replaced by relevant\n\
  121. values when the message is logged.\n\
  122. </para>\n\
  123. <para>\n\
  124. Each entry is also accompanied by a description giving more\n\
  125. information about the circumstances that result in the message\n\
  126. being logged.\n\
  127. </para>\n\
  128. <para>\n\
  129. For information on configuring and using Kea logging,\n\
  130. refer to the <ulink url=\"kea-guide.html\">Kea Guide</ulink>.\n\
  131. </para>\n\
  132. </chapter>\n\
  133. \n\
  134. <chapter id=\"messages\">\n\
  135. <title>Kea Log Messages</title>\n";
  136. /// @brief Section header
  137. /// This is output one for each module. $M substitution token is the name.
  138. const std::string SECTION_HEADER = " <section id=\"$M\">\n\
  139. <title>$M Module</title>\n\
  140. <para>\n\
  141. <variablelist>\n";
  142. /// @brief message ID
  143. /// This is output once for each message. The string contains
  144. /// substitution tokens: $I is replaced by the message identification,
  145. /// $T by the message text, and $D by the message description.
  146. const std::string ID_MESSAGE =
  147. "<varlistentry id=\"$I\">\n\
  148. <term>$I $T</term>\n\
  149. <listitem><para>\n\
  150. $D</para></listitem>\n\
  151. </varlistentry>";
  152. /// @brief Blank line
  153. /// A description may contain blank lines intended to separate
  154. /// paragraphs. If so, each blank line is replaced by the following.
  155. const std::string BLANK = "</para><para>";
  156. /// @brief Separator
  157. /// The separator is copied to the output verbatim after each message except
  158. /// the last.
  159. const std::string SEPARATOR = "";
  160. /// @brief Section trailer
  161. /// The trailer is copied to the output verbatim after the last message.
  162. const std::string SECTION_TRAILER =
  163. " </variablelist>\n\
  164. </para>\n\
  165. </section>";
  166. /// @brief File trailer
  167. /// The trailier is copied to the output verbatim after the last section.
  168. const std::string FILE_TRAILER =
  169. " </chapter>\n\
  170. </book>";
  171. //@}
  172. /// @name Utility routines
  173. //@{
  174. /// @brief Report an error and exit
  175. void reportError(const std::string& filename, const std::string& what)
  176. {
  177. std::cerr << "*** ERROR in " << filename << "\n";
  178. std::cerr << "*** REASON: " << what << "\n";
  179. std::cerr << "*** System message generator terminating" << "\n";
  180. exit(1);
  181. }
  182. /// @brief Replace tag
  183. /// Replaces the '<' and '>' in text about to be inserted into the template
  184. /// sections above with &lt; and &gt; to avoid problems with message text
  185. /// being interpreted as XML text.
  186. std::string replaceTag(const std::string& src)
  187. {
  188. std::string result;
  189. for (std::string::const_iterator it = src.begin(); it != src.end(); ++it) {
  190. if (*it == '<') {
  191. result.append("&lt;");
  192. } else if (*it == '>') {
  193. result.append("&gt;");
  194. } else {
  195. result.push_back(*it);
  196. }
  197. }
  198. return (result);
  199. }
  200. /// @brief Replace shell
  201. /// Replace $c in a string (or with other words performs macro expansion
  202. /// with '$' for introducing a macro followed by a character selecting
  203. /// a specific macro.
  204. ///
  205. /// @param src source string
  206. /// @param c character selecting a macro when it follows '$'
  207. /// @param val value which
  208. ///
  209. /// @return the source string where all occurrences of $c were
  210. /// replaced by val
  211. std::string replaceShell(const std::string& src, char c,
  212. const std::string& val)
  213. {
  214. std::string result;
  215. bool shell = false;
  216. for (std::string::const_iterator it = src.begin(); it != src.end(); ++it) {
  217. if (shell) {
  218. if (*it == c) {
  219. result.append(val);
  220. } else {
  221. result.push_back('$');
  222. result.push_back(*it);
  223. }
  224. shell = false;
  225. } else if (*it == '$') {
  226. shell = true;
  227. } else {
  228. result.push_back(*it);
  229. }
  230. }
  231. return (result);
  232. }
  233. /// @brief Replace blank lines
  234. /// Replaces blank lines in an array with the contents of the 'blank' section.
  235. LinesType replaceBlankLines(const LinesType lines)
  236. {
  237. LinesType result;
  238. for (LinesType::const_iterator l = lines.begin(); l != lines.end(); ++l) {
  239. if (l->empty()) {
  240. result.push_back(BLANK);
  241. } else {
  242. result.push_back(*l);
  243. }
  244. }
  245. return (result);
  246. }
  247. //@}
  248. /// @name Printing functions
  249. //@{
  250. /// @brief Print file header
  251. void printHeader() {
  252. std::cout << FILE_HEADER << "\n";
  253. }
  254. /// @brief Print separator
  255. void printSeparator() {
  256. std::cout << SEPARATOR << "\n";
  257. }
  258. /// @brief Print section header
  259. void printSectionHeader(const std::string& sname)
  260. {
  261. // In the section name, replace "<" and ">" with XML-safe versions and
  262. // substitute into the data.
  263. std::cout << replaceShell(SECTION_HEADER, 'M', replaceTag(sname));
  264. }
  265. /// @brief print message id
  266. void printMessage(const std::string& msgid)
  267. {
  268. // In the message ID, replace "<" and ">" with XML-safe versions and
  269. // substitute into the data.
  270. const std::string m0 = ID_MESSAGE;
  271. const std::string m1 = replaceShell(m0, 'I', replaceTag(msgid));
  272. // Do the same for the message text.
  273. std::string m2 = replaceShell(m1, 'T',
  274. replaceTag(dictionary[msgid].text));
  275. // Do the same for the description then replace blank lines with the
  276. // specified separator. (We do this in that order to avoid replacing
  277. // the "<" and ">" in the XML tags in the separator.)
  278. LinesType desc0 = dictionary[msgid].description;
  279. LinesType desc1;
  280. for (LinesType::iterator l = desc0.begin(); l != desc0.end(); ++l) {
  281. desc1.push_back(replaceTag(*l));
  282. }
  283. LinesType desc2 = replaceBlankLines(desc1);
  284. // Join the lines together to form a single string and insert into
  285. // current text.
  286. std::string m3;
  287. for (LinesType::iterator l = desc2.begin(); l != desc2.end(); ++l) {
  288. m3.append(*l);
  289. m3.push_back('\n');
  290. }
  291. std::cout << replaceShell(m2, 'D', m3) << "\n";
  292. }
  293. /// @brief print section trailer
  294. void printSectionTrailer() {
  295. std::cout << SECTION_TRAILER << "\n";
  296. }
  297. /// @brief print file trailer
  298. void printTrailer() {
  299. std::cout << FILE_TRAILER << "\n";
  300. }
  301. //@}
  302. /// @brief Removes leading and trailing empty lines.
  303. ///
  304. /// A list of strings is passed as argument, some of which may be empty.
  305. /// This function removes from the start and end of list a contiguous
  306. /// sequence of empty lines and returns the result. Embedded sequence of
  307. /// empty lines are not touched.
  308. ///
  309. /// @param lines List of strings to be modified.
  310. ///
  311. /// @return Input list of strings with leading/trailing blank line
  312. /// sequences removed.
  313. LinesType removeEmptyLeadingTrailing(const LinesType& lines)
  314. {
  315. LinesType retlines = lines;
  316. // Dispose of degenerate case of empty array
  317. if (retlines.empty()) {
  318. return (retlines);
  319. }
  320. // Search for first non-blank line
  321. for (;;) {
  322. LinesType::iterator start = retlines.begin();
  323. if (start == retlines.end()) {
  324. return (retlines);
  325. }
  326. if (start->empty()) {
  327. retlines.erase(start);
  328. } else {
  329. break;
  330. }
  331. }
  332. // Search for last non-blank line
  333. for (;;) {
  334. LinesType::reverse_iterator finish = retlines.rbegin();
  335. if (finish == retlines.rend()) {
  336. return (retlines);
  337. }
  338. if (finish->empty()) {
  339. retlines.erase(retlines.end() - 1);
  340. } else {
  341. break;
  342. }
  343. }
  344. return (retlines);
  345. }
  346. /// @brief Add the current message ID and associated information to the global
  347. /// dictionary.
  348. /// If a message with that ID already exists, loop appending suffixes
  349. /// of the form "(n)" to it until one is found that doesn't.
  350. ///
  351. /// @param msgid Message ID
  352. /// @param msgtext Message text
  353. /// @param desc Message description
  354. /// @param filename File from which the message came. Currently this is
  355. /// not used, but a future enhancement may wish to
  356. /// include the name of the message file in the
  357. /// messages manual.
  358. void addToDictionary(const std::string& msgid,
  359. const std::string& msgtext,
  360. const LinesType& desc,
  361. const std::string& filename)
  362. {
  363. // If the ID is in the dictionary, append a "(n)" to the name - this will
  364. // flag that there are multiple instances. (However, this is an error -
  365. // each ID should be unique in the code.)
  366. std::string key = msgid;
  367. if (dictionary.count(key) > 0) {
  368. int i = 1;
  369. std::string s = boost::lexical_cast<std::string>(i);
  370. key = msgid + " (" + s + ")";
  371. while (dictionary.count(key) > 0) {
  372. i = i + 1;
  373. s = boost::lexical_cast<std::string>(i);
  374. key = msgid + " (" + s + ")";
  375. }
  376. }
  377. // Remove leading and trailing blank lines in the description, then
  378. // add everything into a subdictionary which is then added to the main
  379. // one.
  380. Details details;
  381. details.text = msgtext;
  382. details.description = removeEmptyLeadingTrailing(desc);
  383. size_t underscore = msgid.find_first_of('_');
  384. details.sname = msgid.substr(0, underscore);
  385. details.filename = filename;
  386. dictionary.insert(std::pair<const std::string, Details>(key, details));
  387. }
  388. /// @brief Processes file content.
  389. /// Messages and descriptions are identified and added to a dictionary
  390. /// (keyed by message ID). If the key already exists, a numeric
  391. /// suffix is added to it.
  392. /// The format of .mes files is fully described in src/lib/log/logging.dox
  393. ///
  394. /// @param filename Name of the message file being processed
  395. /// @param lines Lines read from the file
  396. void processFileContent(const std::string& filename,
  397. const LinesType& lines)
  398. {
  399. std::string prefix; // Last prefix encountered
  400. std::string msgid; // Last message ID encountered
  401. std::string msgtext; // Text of the message
  402. LinesType description; // Description
  403. for (LinesType::const_iterator l = lines.begin(); l != lines.end(); ++l) {
  404. if (l->empty()) {
  405. description.push_back(*l);
  406. } else if (l->at(0) == '$') {
  407. // Starts with "$". Ignore anything other than $PREFIX
  408. char* line = new char [l->size() + 1];
  409. std::strcpy(line, l->c_str());
  410. char* word0 = strtok(line, " \t\r\n\t\v");
  411. if (strcasecmp(word0, "$PREFIX") == 0) {
  412. char* word1 = strtok(NULL, " \t\r\n\t\v");
  413. prefix = word1;
  414. }
  415. } else if (l->at(0) == '%') {
  416. // Start of a message. Add the message we were processing to the
  417. // dictionary and clear everything apart from the file name.
  418. if (!msgid.empty()) {
  419. addToDictionary(msgid, msgtext, description, filename);
  420. }
  421. msgid.clear();
  422. msgtext.clear();
  423. description.clear();
  424. // Start of a message
  425. char* line = new char [l->size() + 1];
  426. std::strcpy(line, l->c_str());
  427. // Remove "%" and trim leading spaces
  428. size_t start = l->find_first_not_of(" \t\r\n\t\v", 1);
  429. if (start == std::string::npos) {
  430. reportError(filename, "Line with single % found");
  431. continue;
  432. }
  433. // Split into words. The first word is the message ID
  434. char* word0 = strtok(line + start, " \t\r\n\t\v");
  435. msgid = prefix;
  436. msgid.append(word0);
  437. std::transform(msgid.begin(), msgid.end(),
  438. msgid.begin(), toupper);
  439. char* word1 = strtok(NULL, " \t\r\n\t\v");
  440. start = word1 - line;
  441. size_t finish = l->find_last_not_of(" \t\r\n\t\v");
  442. msgtext = l->substr(start, finish + 1 - start);
  443. } else {
  444. // Part of a description, so add to the current description array
  445. description.push_back(*l);
  446. }
  447. }
  448. // All done, add the last message to the global dictionaty.
  449. if (!msgid.empty()) {
  450. addToDictionary(msgid, msgtext, description, filename);
  451. }
  452. }
  453. /// @brief Process a file
  454. /// Read it in and strip out all comments and and directives. Leading
  455. /// and trailing blank lines in the file are removed and the remainder
  456. /// passed for message processing.
  457. ///
  458. /// @param filename Name of the message file to process
  459. void processFile(const std::string& filename)
  460. {
  461. std::ifstream cin;
  462. cin.open(filename.c_str(), std::ios::in);
  463. if (!cin.is_open()) {
  464. reportError(filename, "open for read failure");
  465. }
  466. LinesType lines0;
  467. while (!cin.eof()) {
  468. std::string line;
  469. getline(cin, line);
  470. lines0.push_back(line);
  471. }
  472. cin.close();
  473. // Trim leading and trailing spaces from each line, and remove comments.
  474. LinesType lines1;
  475. for (LinesType::iterator l = lines0.begin(); l != lines0.end(); ++l) {
  476. std::string line = *l;
  477. // Empty lines have no spaces so are processed
  478. if (line.empty()) {
  479. lines1.push_back(line);
  480. continue;
  481. }
  482. // Trim leading spaces
  483. size_t start = line.find_first_not_of(" \t\r\n\t\v");
  484. if (start != 0) {
  485. line.erase(0, start);
  486. }
  487. // Done?
  488. if (line.empty()) {
  489. lines1.push_back(line);
  490. continue;
  491. }
  492. // Trim trailing spaces
  493. size_t finish = line.find_last_not_of(" \t\r\n\t\v");
  494. if ((finish != std::string::npos) &&
  495. (finish + 1 != line.size())) {
  496. line.erase(finish + 1);
  497. }
  498. // Done
  499. if (line.empty()) {
  500. lines1.push_back(line);
  501. continue;
  502. }
  503. // Skip comments
  504. if (line[0] != '#') {
  505. lines1.push_back(line);
  506. }
  507. }
  508. // Remove leading/trailing empty line sequences from the result
  509. LinesType lines2 = removeEmptyLeadingTrailing(lines1);
  510. // Interpret content
  511. processFileContent(filename, lines2);
  512. }
  513. /// @brief Usage error routine
  514. void usage(char* progname)
  515. {
  516. std::cerr << "Usage: " << progname <<
  517. " [--help | options] files\n";
  518. std::cerr << " options: --output file: " <<
  519. "output file name (default to stdout)\n";
  520. }
  521. /// @brief Main (entry point)
  522. int main(int argc, char* argv[])
  523. {
  524. char* progname = argv[0];
  525. std::ofstream fout;
  526. while (argc > 1) {
  527. --argc;
  528. ++argv;
  529. if (strcmp(argv[0], "--help") == 0) {
  530. usage(progname);
  531. exit(0);
  532. }
  533. // Redirect output if specified (errors are written to stderr)
  534. if ((strcmp(argv[0], "-o") == 0) ||
  535. (strcmp(argv[0], "--output") == 0)) {
  536. --argc;
  537. ++argv;
  538. if (argc == 0) {
  539. usage(progname);
  540. exit(-1);
  541. }
  542. fout.open(argv[0], std::ofstream::out | std::ofstream::trunc);
  543. if (!fout.is_open()) {
  544. reportError(argv[0], "open for write failure");
  545. }
  546. std::cout.rdbuf(fout.rdbuf());
  547. --argc;
  548. ++argv;
  549. break;
  550. }
  551. }
  552. if (argc == 0) {
  553. usage(progname);
  554. exit(-1);
  555. }
  556. for (int i = 0; i < argc; ++i) {
  557. processFile(argv[i]);
  558. }
  559. // Now just print out everything we've read (in alphabetical order).
  560. bool first = true;
  561. std::string sname;
  562. printHeader();
  563. for (DictionaryType::iterator it = dictionary.begin();
  564. it != dictionary.end();
  565. ++it) {
  566. if (sname.compare(it->second.sname) != 0) {
  567. if (!sname.empty()) {
  568. printSectionTrailer();
  569. printSeparator();
  570. }
  571. sname = it->second.sname;
  572. printSectionHeader(sname);
  573. first = true;
  574. }
  575. if (!first) {
  576. printSeparator();
  577. }
  578. first = false;
  579. printMessage(it->first);
  580. }
  581. if (!sname.empty()) {
  582. printSectionTrailer();
  583. }
  584. printTrailer();
  585. exit(0);
  586. }