This source file includes following definitions.
- fixup_csv_line
- deoctalify
- strip_trailing_junk
- make_digits
- make_row
- remove_quotes
- read_csv
- ASCII_ify
- reject_code_to_enum_tag
- fill
- write_reject_code_enum
- write_string_table
- write_code_to_reason
- write_header
- main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
60 constexpr const row_t::size_type code_index=0;
61
62 constexpr const row_t::size_type max_columns=5;
63
64 constexpr const char reject_codes_enum_str[]="reject_codes_enum";
65
66
67
68
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
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
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
98 inline std::string
99 make_digits(std::string const &code) noexcept(false) {
100 return strip_trailing_junk(deoctalify(code));
101 }
102
103
104
105
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
141
142
143
144
145
146
147
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
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
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
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
289
290
291
292
293
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
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 }