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