system_messages.cc 19 KB

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