system_messages.cc 20 KB

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