root/isimud/exchanges/MIT/reject_codes/reject_codes.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. fixup_csv_line
  2. deoctalify
  3. strip_trailing_junk
  4. make_digits
  5. make_row
  6. remove_quotes
  7. read_csv
  8. ASCII_ify
  9. reject_code_to_enum_tag
  10. fill
  11. write_reject_code_enum
  12. write_string_table
  13. write_code_to_reason
  14. write_header
  15. main

   1 /******************************************************************************
   2 ** $Header: svn+ssh://jmmcg@svn.code.sf.net/p/libjmmcg/code/trunk/libjmmcg/isimud/exchanges/MIT/reject_codes/reject_codes.cpp 2272 2018-03-17 22:21:39Z jmmcg $
   3 **
   4 ** Copyright (c) 2016 by J.M.McGuiness, isimud@hussar.me.uk
   5 **
   6 ** This library is free software; you can redistribute it and/or
   7 ** modify it under the terms of the GNU Lesser General Public
   8 ** License as published by the Free Software Foundation; either
   9 ** version 2.1 of the License, or (at your option) any later version.
  10 **
  11 ** This library is distributed in the hope that it will be useful,
  12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14 ** Lesser General Public License for more details.
  15 **
  16 ** You should have received a copy of the GNU Lesser General Public
  17 ** License along with this library; if not, write to the Free Software
  18 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19 */
  20 
  21 #include "core/config.h"
  22 #include "core/exit_codes.hpp"
  23 
  24 #include <boost/algorithm/string/split.hpp>
  25 #include <boost/lexical_cast.hpp>
  26 #include <boost/program_options.hpp>
  27 
  28 #include <cstdint>
  29 #include <fstream>
  30 #include <iostream>
  31 #include <map>
  32 
  33 using row_t=std::vector<std::string>;
  34 using table_t=std::map<std::int32_t, row_t>;
  35 using parsed_csv_t=std::tuple<std::string, table_t, row_t::size_type, row_t::size_type>;
  36 
  37 constexpr const char copyright[]=
  38 "/******************************************************************************\n"
  39 "** Copyright (c) 2016 by J.M.McGuiness, isimud@hussar.me.uk\n"
  40 "**\n"
  41 "** This library is free software; you can redistribute it and/or\n"
  42 "** modify it under the terms of the GNU Lesser General Public\n"
  43 "** License as published by the Free Software Foundation; either\n"
  44 "** version 2.1 of the License, or (at your option) any later version.\n"
  45 "**\n"
  46 "** This library is distributed in the hope that it will be useful,\n"
  47 "** but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
  48 "** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
  49 "** Lesser General Public License for more details.\n"
  50 "**\n"
  51 "** You should have received a copy of the GNU Lesser General Public\n"
  52 "** License along with this library; if not, write to the Free Software\n"
  53 "** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n"
  54 "*/\n"
  55 "#include <cassert>\n"
  56 "#include <cstdint>\n"
  57 "#include <iostream>\n";
  58 constexpr const char header_guard_postfix[]="#endif\n";
  59 /// The reject code must always be in the first column.
  60 constexpr const row_t::size_type code_index=0;
  61 /// The maximum number of columns in the converted CSV.
  62 constexpr const row_t::size_type max_columns=5;
  63 /// The name of the enum that is created with tags composed from the printable characters from the string relating to the reject-code.
  64 constexpr const char reject_codes_enum_str[]="reject_codes_enum";
  65 
  66 /// Some of the lines in the dratted CSV don't end with commas, so ensure that they do to allow the comma-recognition code to work correctly.
  67 /**
  68         If a terminal comma is omitted, that last column will be read as empty.
  69 */
  70 inline void
  71 fixup_csv_line(std::string &line) noexcept(false) {
  72         if (*line.rbegin()!=',') {
  73                 line.push_back(',');
  74         }
  75 }
  76 
  77 /// Some of the reject-codes are prefixed with zeroes, which in C++ signifies an octal number, strip those zeroes to ensure the reject-codes are correctly treated as decimal.
  78 inline std::string
  79 deoctalify(std::string const &code) noexcept(false) {
  80         std::string ret(code);
  81         while (ret.size()>1 && ret.front()=='0') {
  82                 ret.erase(ret.begin());
  83         }
  84         return ret;
  85 }
  86 
  87 /// Sometimes the reject-code has trailing non-printable characters before the comma-separator. Strip it.
  88 inline std::string
  89 strip_trailing_junk(std::string const &code) noexcept(false) {
  90         std::string ret(code);
  91         while (ret.size()>1 && !std::isdigit(*ret.begin())) {
  92                 ret.erase(std::next(ret.begin(), ret.size()-1));
  93         }
  94         return ret;
  95 }
  96 
  97 /// Suitably clean up the reject-code.
  98 inline std::string
  99 make_digits(std::string const &code) noexcept(false) {
 100         return strip_trailing_junk(deoctalify(code));
 101 }
 102 
 103 /// Convert the scanned line from the input CSV-file into a collection of strings, one string per column.
 104 /**
 105         Also replace non-printable ASCII characters with underscores.
 106 */
 107 inline row_t
 108 make_row(std::string line) noexcept(false) {
 109         fixup_csv_line(line);
 110         assert(*line.rbegin()==',');
 111         row_t row;
 112         row.reserve(max_columns);
 113         std::string::size_type beg=0;
 114         std::string::size_type end=0;
 115         while ((end=line.find(",", beg))!=std::string::npos) {
 116                 row_t::value_type cell(std::next(line.begin(), beg), std::next(line.begin(), end));
 117                 std::transform(
 118                         cell.begin(),
 119                         cell.end(),
 120                         cell.begin(),
 121                         [](std::string::value_type v) {
 122                                 return std::isalnum(v) || std::isspace(v) || std::ispunct(v) ? v : '_';
 123                         }
 124                 );
 125                 row.emplace_back(std::move(cell));
 126                 beg=end;
 127                 if (beg>=line.size()) {
 128                         break;
 129                 }
 130                 ++beg;
 131         }
 132         return row;
 133 }
 134 
 135 inline std::string
 136 remove_quotes(std::string const &s) noexcept(false) {
 137         return std::string(std::next(s.begin()), std::next(s.begin(), s.size()-1));
 138 }
 139 
 140 /// Read in and parse the specified CSV-file.
 141 /**
 142         Note that any lines with non-digit reject-codes will be silently ignored.
 143         All FixGateway reject-codes are ignored (as they cause duplicates) & the links are binary-only.
 144         
 145         \param  fname   The suitably-qualified path to the suitably-formatted CSV file. It must be comma separated, with no embedded commas.
 146         \param  ver_headers     The string that specifies the headers for the version-table.
 147         \return An associative collection of reject-code to details, the version string and column numbers of the required details in the rows.
 148 */
 149 inline parsed_csv_t
 150 read_csv(std::string const &fname, std::string const &version_headers, std::string const &end_of_version_headers, std::vector<std::string> const &reject_code_headers, std::string const &reject_column_name) noexcept(false) {
 151         assert(!reject_code_headers.empty());
 152         assert(reject_code_headers.size()<=max_columns);
 153         const auto &check_ver=[fname, version_headers, end_of_version_headers](auto const &version) {
 154                 if (version.empty()) {
 155                         std::ostringstream os;
 156                         os<<"In: '"<<fname<<"', failed to find the version, using version headers: ["<<version_headers<<"] & end-version headers: ["<<end_of_version_headers<<"].";
 157                         throw std::runtime_error(os.str());
 158                 }
 159         };
 160         const std::string::size_type num_version_headers=std::count(version_headers.begin(), version_headers.end(), ',');
 161         assert(num_version_headers>1);
 162         std::ifstream reject_codes(fname.c_str());
 163         if (!reject_codes.is_open()) {
 164                 std::ostringstream os;
 165                 os<<"Failed to open the CSV file: '"<<fname<<"'.";
 166                 throw std::runtime_error(os.str());
 167         }
 168         std::string line;
 169         std::string version;
 170         bool found_reject_code_headers=false;
 171         bool found_version_headers=false;
 172         while (std::getline(reject_codes, line, '\n')) {
 173                 if (line.find(reject_code_headers[0])!=std::string::npos
 174                         && line.find(reject_code_headers[1])!=std::string::npos
 175                         && line.find(reject_code_headers[2])!=std::string::npos
 176                         && line.find(reject_code_headers[3])!=std::string::npos) {
 177                         found_reject_code_headers=true;
 178                         break;
 179                 } else if (line.find(version_headers)!=std::string::npos) {
 180                         found_version_headers=true;
 181                         break;
 182                 }
 183         }
 184         if (found_version_headers) {
 185                 while (std::getline(reject_codes, line, '\n')) {
 186                         if (line.find(reject_code_headers[0])!=std::string::npos
 187                                 && line.find(reject_code_headers[1])!=std::string::npos
 188                                 && line.find(reject_code_headers[2])!=std::string::npos
 189                                 && line.find(reject_code_headers[3])!=std::string::npos) {
 190                                 found_reject_code_headers=true;
 191                                 break;
 192                         } else if (line.find(end_of_version_headers)!=std::string::npos) {
 193                                 found_version_headers=false;
 194                         }
 195                         if (found_version_headers) {
 196                                 version=line;
 197                         }
 198                 }
 199                 check_ver(version);
 200         }
 201         if (!found_reject_code_headers) {
 202                 std::ostringstream os;
 203                 os<<"In: '"<<fname<<"', failed to find the reject-code headers: [";
 204                 std::copy(reject_code_headers.begin(), reject_code_headers.end(), std::ostream_iterator<std::string>(os, ","));
 205                 os<<"].";
 206                 throw std::runtime_error(os.str());
 207         }
 208         if (!found_reject_code_headers) {
 209                 std::ostringstream os;
 210                 os<<"In: '"<<fname<<"', failed to find the reject-code headers: [";
 211                 std::copy(reject_code_headers.begin(), reject_code_headers.end(), std::ostream_iterator<std::string>(os, ","));
 212                 os<<"].";
 213                 throw std::runtime_error(os.str());
 214         }
 215         table_t table;
 216         row_t row(make_row(line));
 217         assert(!row.empty());
 218         assert(row.size()>=(max_columns-1));
 219         [[maybe_unused]] auto const &length_column=std::find(row.begin(), row.end(), reject_code_headers[1]);
 220         assert(length_column!=row.end());
 221         auto const &reason_column=std::find(row.begin(), row.end(), reject_column_name);
 222         assert(reason_column!=row.end());
 223         const row_t::size_type reason_index=reason_column-row.begin();
 224         assert(reason_index>code_index);
 225         assert(reason_index<max_columns);
 226         auto const &process_column=std::find(row.begin(), row.end(), reject_code_headers[3]);
 227         assert(process_column!=row.end());
 228         const row_t::size_type process_index=process_column-row.begin();
 229         assert(process_index>reason_index);
 230         while (std::getline(reject_codes, line, '\n')) {
 231                 row_t row(make_row(line));
 232                 if (row.size()>process_index) {
 233                         if (!row[code_index].empty()
 234                                 && !row[reason_index].empty()
 235                                 && !row[process_index].empty()
 236                                 && row[process_index].find("FixGateway")==std::string::npos
 237                                 && row[process_index].find("FIX Gateway")==std::string::npos
 238                                 && row[process_index].find("FIX Trading")==std::string::npos
 239                                 && row[process_index].find("FIXTradingGateway")==std::string::npos
 240                                 && row[process_index].find("FIX/FAST")==std::string::npos
 241                                 && row[process_index].find("FAST Gateway")==std::string::npos
 242                                 && row[process_index].find("RNS Feed")==std::string::npos
 243                         ) {
 244                                 try {
 245                                         row[code_index]=make_digits(row[code_index]);
 246                                         table.insert(std::make_pair(boost::lexical_cast<std::int32_t>(row[code_index]), row));
 247                                 } catch (std::exception const &) {
 248                                         // Silently ignore any rubbish in the input CSV....
 249                                 }
 250                         }
 251                 } else if (row.size()>num_version_headers && !row[code_index].empty() && !row[num_version_headers].empty()) {
 252                         version=line;
 253                 }
 254                 row.clear();
 255         }
 256         if (version.empty()) {
 257                 std::ostringstream os;
 258                 os<<"In: '"<<fname<<"', failed to find the version headers: ["<<version_headers<<"].";
 259                 throw std::runtime_error(os.str());
 260         }
 261         assert(!table.empty());
 262         return parsed_csv_t(version, table, reason_index, process_index);
 263 }
 264 
 265 /// Replace the non-alpha-numeric characters in the input string into underscores.
 266 inline std::string
 267 ASCII_ify(std::string const &arg) noexcept(false) {
 268         std::string ret(arg);
 269         std::transform(
 270                 ret.begin(),
 271                 ret.end(),
 272                 ret.begin(),
 273                 [](char v) {
 274                         return std::isalnum(v) ? v : '_';
 275                 }
 276         );
 277         return ret;
 278 }
 279 
 280 /// Construct a suitable enum-tag from the parsed row.
 281 inline std::string
 282 reject_code_to_enum_tag(table_t::const_iterator row, const row_t::size_type reason_index, const row_t::size_type process_index) noexcept(false) {
 283         const std::string tag(ASCII_ify(row->second[reason_index]));
 284         const std::string process(ASCII_ify(row->second[process_index]));
 285         return process+"_"+tag+"_"+row->second[code_index];
 286 }
 287 
 288 /// Apply a specified operator to all but the penultimate element in the collection, to which a different operation should be applied.
 289 /**
 290         Algorithmic complexity: O(n)
 291         \param  c       The input collection.
 292         \param  o       The functor to be applied to all but the penultimate element in the input collection.
 293         \param  t       The functor to be applied to the penultimate element in the input collection.
 294 */
 295 template<class Colln, class Op, class Trail>
 296 inline void
 297 fill(Colln &c, Op const &o, Trail const &t) noexcept(false) {
 298         auto row=c.begin();
 299         while (row!=std::prev(c.end())) {
 300                 o.operator()(row);
 301                 t.operator()();
 302                 ++row;
 303         }
 304         o.operator()(row);
 305 }
 306 
 307 inline void
 308 write_reject_code_enum(table_t const &table, const row_t::size_type reason_index, const row_t::size_type process_index, std::ostream &os) noexcept(false) {
 309         assert(!table.empty());
 310         os<<"using RejectCodes_t=std::int32_t;\n"
 311                         "enum class "<<reject_codes_enum_str<<" : RejectCodes_t {\n"
 312                         "\ttag_SUCCESS=0,\n";
 313         fill(
 314                 table,
 315                 [&os, reason_index, process_index](table_t::const_iterator row) {
 316                         if (row->second.size()>reason_index) {
 317                                 const row_t::value_type cell(reject_code_to_enum_tag(row, reason_index, process_index));
 318                                 os<<"\t"<<cell<<"="<<row->first;
 319                         }
 320                 },
 321                 [&os]() {
 322                         os<<",\n";
 323                 }
 324         );
 325         os<<"\n};\n";
 326 }
 327 
 328 inline void
 329 write_string_table(table_t const &table, const row_t::size_type reason_index, std::ostream &os) noexcept(false) {
 330         assert(!table.empty());
 331         os<<"\tstatic constexpr char const * const reject_code_strs[]={\n"
 332                 "\t\t\"SUCCESS\",\n";
 333         fill(
 334                 table,
 335                 [&os, reason_index](table_t::const_iterator row) {
 336                         if (row->second.size()>reason_index) {
 337                                 os<<"\t\t\""<<row->second[reason_index]<<"\"";
 338                         }
 339                 },
 340                 [&os]() {
 341                         os<<",\n";
 342                 }
 343         );
 344         os<<"\n\t};\n";
 345 }
 346 
 347 inline void
 348 write_code_to_reason(table_t const &table, const row_t::size_type reason_index, const row_t::size_type process_index, std::ostream &os) noexcept(false) {
 349         os<<"inline std::ostream &\n"
 350                 "operator<<(std::ostream &os, const "<<reject_codes_enum_str<<" code) noexcept(true) {\n";
 351         write_string_table(table, reason_index, os);
 352         assert(!table.empty());
 353         unsigned int reject_code_strs_index=0;
 354         os<<"\tswitch (code) {\n"
 355                 "\t\tcase "<<reject_codes_enum_str<<"::tag_SUCCESS:\n"
 356                 "\t\t\tos<<reject_code_strs["<<reject_code_strs_index<<"];\n"
 357                 "\t\t\treturn os;\n";
 358         ++reject_code_strs_index;
 359         fill(
 360                 table,
 361                 [reason_index, process_index, &os, &reject_code_strs_index](table_t::const_iterator row) {
 362                         if (row->second.size()>reason_index) {
 363                                 const row_t::value_type cell(reject_code_to_enum_tag(row, reason_index, process_index));
 364                                 os<<"\t\tcase "<<reject_codes_enum_str<<"::"<<cell<<":\n"
 365                                         "\t\t\tos<<reject_code_strs["<<reject_code_strs_index<<"];"
 366                                         "\t\t\treturn os;";
 367                         }
 368                         ++reject_code_strs_index;
 369                 },
 370                 [&os]() {
 371                         os<<"\n";
 372                 }
 373         );
 374         os<<"\n\t\tdefault:\n"
 375                 "\t\t\tassert(!\"Unknown reject code.\");\n"
 376                 "\t\t\tos<<\"Unknown reject code.\";\n"
 377                 "\t\t\treturn os;\n"
 378                 "\t};\n"
 379                 "}\n";
 380 }
 381 
 382 inline void
 383 write_header(parsed_csv_t const &parsed_csv, std::string const &fname, std::string const &nm_space) noexcept(false) {
 384         struct header_guard_t {
 385                 std::ostream &os;
 386 
 387                  header_guard_t(std::string const &nm_space, std::string const &xls_version, std::ostream &s) noexcept(false)
 388                 : os(s) {
 389                         os
 390                                 <<"#ifndef libjmmcg_isimud_exchanges_MIT_reject_codes_"<<nm_space<<"_reject_codes_hpp\n"
 391                                         "#define libjmmcg_isimud_exchanges_MIT_reject_codes_"<<nm_space<<"_reject_codes_hpp\n"
 392                                 <<copyright
 393                                 <<"// Auto-built header file from auto-downloaded and converted XLS from the web, so good luck trying to identify the source.\n"
 394                                         "// DO NOT EDIT. IT WILL BE OVERWRITTEN.\n"
 395                                         "// Source XLS version info: '"<<xls_version<<"'\n"
 396                                         "// Version info: "<<libjmmcg_VERSION_NUMBER<<"\n"
 397                                         "namespace isimud { namespace exchanges { namespace MIT { namespace "<<nm_space<<" {\n";
 398                 }
 399                 ~header_guard_t() noexcept(false) {
 400                         os<<"} } } }\n"
 401                                 <<header_guard_postfix;
 402                 }
 403         };
 404         std::ofstream cpp_header(fname.c_str());
 405         const header_guard_t header_guard(nm_space, std::get<0>(parsed_csv), cpp_header);
 406         write_reject_code_enum(std::get<1>(parsed_csv), std::get<2>(parsed_csv), std::get<3>(parsed_csv), cpp_header);
 407         write_code_to_reason(std::get<1>(parsed_csv), std::get<2>(parsed_csv), std::get<3>(parsed_csv), cpp_header);
 408 }
 409 
 410 /// For MIT-based protocols, auto-generate a C++ function to convert the reject codes to strings. The mapping comes from the downloaded XLS that has been converted to a CSV file.
 411 int
 412 main(int argc, char const * const *argv) noexcept(true) {
 413         try {
 414                 boost::program_options::options_description general(
 415                         "A program to convert a table of exchange reject-codes in CSV-format to a C++ header-file. For details regarding the properties of the translator see the documentation that came with the distribution. Copyright (c) J.M.McGuiness, coder@hussar.me.uk. http://libjmmcg.sf.net/ Distributed under the terms of the GPL v2.1."
 416                 );
 417                 general.add_options()
 418                         ("help", "Print this help message.")
 419                         ("version", "Print the build number of this program.")
 420                 ;
 421                 boost::program_options::options_description prog_opts("Program options.");
 422                 prog_opts.add_options()
 423                         ("reject_codes", boost::program_options::value<std::string>()->required(), "The file-path of the reject codes.")
 424                         ("cpp_header", boost::program_options::value<std::string>()->required(), "The file-path of the output C++ header-file.")
 425                         ("namespace", boost::program_options::value<std::string>()->required(), "The namespace into which the contents of the header-file should be placed.")
 426                         ("reject_code_headers", boost::program_options::value<std::string>()->required(), "The text that identifies the column-headings for the reject-codes.")
 427                         ("reject_column_name", boost::program_options::value<std::string>()->required(), "The text that identifies the column name of the reject-codes.")
 428                         ("version_headers", boost::program_options::value<std::string>()->required(), "The text that identifies the column-headings for the version information.")
 429                         ("end_of_version_headers", boost::program_options::value<std::string>()->required(), "The text that identifies the column-headings delimiting the end of the version information. Some of the MIT-based exchanges convert to CSVs that have more than one table, this one follows the reject-code translations.")
 430                 ;
 431                 boost::program_options::options_description all("All options.");
 432                 all.add(general).add(prog_opts);
 433                 boost::program_options::variables_map vm;
 434                 boost::program_options::store(boost::program_options::parse_command_line(argc, argv, all), vm);
 435                 if (vm.count("help")) {
 436                         std::cout<<all<<std::endl;
 437                         return jmmcg::exit_print_help;
 438                 }
 439                 if (vm.count("version")) {
 440                         std::cout<<"Build: "<<libjmmcg_VERSION_NUMBER<<std::endl;
 441                         return jmmcg::exit_print_version;
 442                 }
 443                 boost::program_options::notify(vm);
 444 
 445                 std::vector<std::string> reject_codes_headers;
 446                 std::string version_headers(remove_quotes(vm["reject_code_headers"].as<std::string>()));
 447                 const parsed_csv_t parsed_csv(
 448                         read_csv(
 449                                 vm["reject_codes"].as<std::string>(),
 450                                 remove_quotes(vm["version_headers"].as<std::string>()),
 451                                 remove_quotes(vm["end_of_version_headers"].as<std::string>()),
 452                                 boost::split(
 453                                         reject_codes_headers, 
 454                                         version_headers,
 455                                         [](auto const &v) {return v==',';}
 456                                 ),
 457                                 remove_quotes(vm["reject_column_name"].as<std::string>())
 458                         )
 459                 );
 460                 write_header(
 461                         parsed_csv,
 462                         vm["cpp_header"].as<std::string>(),
 463                         vm["namespace"].as<std::string>()
 464                 );
 465                 return jmmcg::exit_codes::exit_success;
 466         } catch (std::exception const &ex) {
 467                 std::cerr<<"STL exception. Detail: "<<ex.what()<<std::endl;
 468                 return jmmcg::exit_codes::exit_stl_exception;
 469         } catch (...) {
 470                 std::cerr<<"Unknown exception."<<std::endl;
 471                 return jmmcg::exit_codes::exit_unknown_exception;
 472         }
 473         return jmmcg::exit_codes::exit_unknown_failure; 
 474 }

/* [<][>][^][v][top][bottom][index][help] */