/ externals / catch / src / catch2 / internal / catch_reporter_spec_parser.cpp
catch_reporter_spec_parser.cpp
  1  
  2  //              Copyright Catch2 Authors
  3  // Distributed under the Boost Software License, Version 1.0.
  4  //   (See accompanying file LICENSE.txt or copy at
  5  //        https://www.boost.org/LICENSE_1_0.txt)
  6  
  7  // SPDX-License-Identifier: BSL-1.0
  8  
  9  #include <catch2/internal/catch_reporter_spec_parser.hpp>
 10  
 11  #include <catch2/interfaces/catch_interfaces_config.hpp>
 12  #include <catch2/internal/catch_move_and_forward.hpp>
 13  
 14  #include <algorithm>
 15  
 16  namespace Catch {
 17  
 18      namespace {
 19          struct kvPair {
 20              StringRef key, value;
 21          };
 22  
 23          kvPair splitKVPair(StringRef kvString) {
 24              auto splitPos = static_cast<size_t>(
 25                  std::find( kvString.begin(), kvString.end(), '=' ) -
 26                  kvString.begin() );
 27  
 28              return { kvString.substr( 0, splitPos ),
 29                       kvString.substr( splitPos + 1, kvString.size() ) };
 30          }
 31      }
 32  
 33      namespace Detail {
 34          std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) {
 35              static constexpr auto separator = "::";
 36              static constexpr size_t separatorSize = 2;
 37  
 38              size_t separatorPos = 0;
 39              auto findNextSeparator = [&reporterSpec]( size_t startPos ) {
 40                  static_assert(
 41                      separatorSize == 2,
 42                      "The code below currently assumes 2 char separator" );
 43  
 44                  auto currentPos = startPos;
 45                  do {
 46                      while ( currentPos < reporterSpec.size() &&
 47                              reporterSpec[currentPos] != separator[0] ) {
 48                          ++currentPos;
 49                      }
 50                      if ( currentPos + 1 < reporterSpec.size() &&
 51                           reporterSpec[currentPos + 1] == separator[1] ) {
 52                          return currentPos;
 53                      }
 54                      ++currentPos;
 55                  } while ( currentPos < reporterSpec.size() );
 56  
 57                  return static_cast<size_t>( -1 );
 58              };
 59  
 60              std::vector<std::string> parts;
 61  
 62              while ( separatorPos < reporterSpec.size() ) {
 63                  const auto nextSeparator = findNextSeparator( separatorPos );
 64                  parts.push_back( static_cast<std::string>( reporterSpec.substr(
 65                      separatorPos, nextSeparator - separatorPos ) ) );
 66  
 67                  if ( nextSeparator == static_cast<size_t>( -1 ) ) {
 68                      break;
 69                  }
 70                  separatorPos = nextSeparator + separatorSize;
 71              }
 72  
 73              // Handle a separator at the end.
 74              // This is not a valid spec, but we want to do validation in a
 75              // centralized place
 76              if ( separatorPos == reporterSpec.size() ) {
 77                  parts.emplace_back();
 78              }
 79  
 80              return parts;
 81          }
 82  
 83          Optional<ColourMode> stringToColourMode( StringRef colourMode ) {
 84              if ( colourMode == "default" ) {
 85                  return ColourMode::PlatformDefault;
 86              } else if ( colourMode == "ansi" ) {
 87                  return ColourMode::ANSI;
 88              } else if ( colourMode == "win32" ) {
 89                  return ColourMode::Win32;
 90              } else if ( colourMode == "none" ) {
 91                  return ColourMode::None;
 92              } else {
 93                  return {};
 94              }
 95          }
 96      } // namespace Detail
 97  
 98  
 99      bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) {
100          return lhs.m_name == rhs.m_name &&
101                 lhs.m_outputFileName == rhs.m_outputFileName &&
102                 lhs.m_colourMode == rhs.m_colourMode &&
103                 lhs.m_customOptions == rhs.m_customOptions;
104      }
105  
106      Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ) {
107          auto parts = Detail::splitReporterSpec( reporterSpec );
108  
109          assert( parts.size() > 0 && "Split should never return empty vector" );
110  
111          std::map<std::string, std::string> kvPairs;
112          Optional<std::string> outputFileName;
113          Optional<ColourMode> colourMode;
114  
115          // First part is always reporter name, so we skip it
116          for ( size_t i = 1; i < parts.size(); ++i ) {
117              auto kv = splitKVPair( parts[i] );
118              auto key = kv.key, value = kv.value;
119  
120              if ( key.empty() || value.empty() ) {
121                  return {};
122              } else if ( key[0] == 'X' ) {
123                  // This is a reporter-specific option, we don't check these
124                  // apart from basic sanity checks
125                  if ( key.size() == 1 ) {
126                      return {};
127                  }
128  
129                  auto ret = kvPairs.emplace( std::string(kv.key), std::string(kv.value) );
130                  if ( !ret.second ) {
131                      // Duplicated key. We might want to handle this differently,
132                      // e.g. by overwriting the existing value?
133                      return {};
134                  }
135              } else if ( key == "out" ) {
136                  // Duplicated key
137                  if ( outputFileName ) {
138                      return {};
139                  }
140                  outputFileName = static_cast<std::string>( value );
141              } else if ( key == "colour-mode" ) {
142                  // Duplicated key
143                  if ( colourMode ) {
144                      return {};
145                  }
146                  colourMode = Detail::stringToColourMode( value );
147                  // Parsing failed
148                  if ( !colourMode ) {
149                      return {};
150                  }
151              } else {
152                  // Unrecognized option
153                  return {};
154              }
155          }
156  
157          return ReporterSpec{ CATCH_MOVE( parts[0] ),
158                               CATCH_MOVE( outputFileName ),
159                               CATCH_MOVE( colourMode ),
160                               CATCH_MOVE( kvPairs ) };
161      }
162  
163  ReporterSpec::ReporterSpec(
164          std::string name,
165          Optional<std::string> outputFileName,
166          Optional<ColourMode> colourMode,
167          std::map<std::string, std::string> customOptions ):
168          m_name( CATCH_MOVE( name ) ),
169          m_outputFileName( CATCH_MOVE( outputFileName ) ),
170          m_colourMode( CATCH_MOVE( colourMode ) ),
171          m_customOptions( CATCH_MOVE( customOptions ) ) {}
172  
173  } // namespace Catch