258
+ − 1 #include "animone/player.h"
+ − 2 #include "animone/util.h"
+ − 3
+ − 4 #include <map>
+ − 5 #include <sstream>
+ − 6 #include <string>
+ − 7 #include <vector>
+ − 8
+ − 9 namespace animone {
+ − 10
+ − 11 namespace internal::parser {
+ − 12
+ − 13 enum class State {
+ − 14 ExpectPlayerName,
+ − 15 ExpectSection,
+ − 16 ExpectWindow,
+ − 17 ExpectExecutable,
+ − 18 ExpectStrategy,
+ − 19 ExpectType,
+ − 20 ExpectWindowTitle,
+ − 21 };
+ − 22
+ − 23 size_t GetIndentation(const std::string& line) {
+ − 24 return line.find_first_not_of('\t');
+ − 25 }
+ − 26
+ − 27 bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
+ − 28 // Each state has a definitive expected indentation
+ − 29 const auto expected = [&state]() -> size_t {
+ − 30 switch (state) {
+ − 31 default:
+ − 32 case State::ExpectPlayerName: return 0;
+ − 33 case State::ExpectSection: return 1;
+ − 34 case State::ExpectWindow:
+ − 35 case State::ExpectExecutable:
+ − 36 case State::ExpectStrategy:
+ − 37 case State::ExpectType: return 2;
+ − 38 case State::ExpectWindowTitle: return 3;
+ − 39 }
+ − 40 }();
+ − 41
+ − 42 if (current > expected)
+ − 43 return false; // Disallow excessive indentation
+ − 44
+ − 45 if (current < expected) {
+ − 46 auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; };
+ − 47 switch (state) {
+ − 48 case State::ExpectWindow:
+ − 49 if (players.back().windows.empty())
+ − 50 return false;
+ − 51 fix_state();
+ − 52 break;
+ − 53 case State::ExpectExecutable:
+ − 54 if (players.back().executables.empty())
+ − 55 return false;
+ − 56 fix_state();
+ − 57 break;
+ − 58 case State::ExpectStrategy:
+ − 59 if (players.back().strategies.empty())
+ − 60 return false;
+ − 61 fix_state();
+ − 62 break;
+ − 63 case State::ExpectType: fix_state(); break;
+ − 64 case State::ExpectWindowTitle: return false;
+ − 65 }
+ − 66 }
+ − 67
+ − 68 return true;
+ − 69 }
+ − 70
+ − 71 bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
+ − 72 switch (state) {
+ − 73 case State::ExpectPlayerName:
+ − 74 players.push_back(Player());
+ − 75 players.back().name = line;
+ − 76 state = State::ExpectSection;
+ − 77 break;
+ − 78
+ − 79 case State::ExpectSection: {
+ − 80 static const std::map<std::string, State> sections = {
+ − 81 {"windows", State::ExpectWindow },
+ − 82 {"executables", State::ExpectExecutable},
+ − 83 {"strategies", State::ExpectStrategy },
+ − 84 {"type", State::ExpectType },
+ − 85 };
+ − 86 util::TrimRight(line, ":");
+ − 87 const auto it = sections.find(line);
+ − 88 if (it == sections.end())
+ − 89 return false;
+ − 90 state = it->second;
+ − 91 break;
+ − 92 }
+ − 93
+ − 94 case State::ExpectWindow: players.back().windows.push_back(line); break;
+ − 95
+ − 96 case State::ExpectExecutable: players.back().executables.push_back(line); break;
+ − 97
+ − 98 case State::ExpectStrategy: {
+ − 99 static const std::map<std::string, Strategy> strategies = {
+ − 100 {"window_title", Strategy::WindowTitle },
+ − 101 {"open_files", Strategy::OpenFiles },
+ − 102 {"ui_automation", Strategy::UiAutomation},
+ − 103 };
+ − 104 util::TrimRight(line, ":");
+ − 105 const auto it = strategies.find(line);
+ − 106 if (it == strategies.end())
+ − 107 return false;
+ − 108 const auto strategy = it->second;
+ − 109 players.back().strategies.push_back(strategy);
+ − 110 switch (strategy) {
+ − 111 case Strategy::WindowTitle: state = State::ExpectWindowTitle; break;
+ − 112 }
+ − 113 break;
+ − 114 }
+ − 115
+ − 116 case State::ExpectType: {
+ − 117 static const std::map<std::string, PlayerType> types = {
+ − 118 {"default", PlayerType::Default },
+ − 119 {"web_browser", PlayerType::WebBrowser},
+ − 120 };
+ − 121 const auto it = types.find(line);
+ − 122 if (it == types.end())
+ − 123 return false;
+ − 124 players.back().type = it->second;
+ − 125 break;
+ − 126 }
+ − 127
+ − 128 case State::ExpectWindowTitle:
+ − 129 players.back().window_title_format = line;
+ − 130 state = State::ExpectStrategy;
+ − 131 break;
+ − 132 }
+ − 133
+ − 134 return true;
+ − 135 }
+ − 136
+ − 137 } // namespace internal::parser
+ − 138
+ − 139 ////////////////////////////////////////////////////////////////////////////////
+ − 140
+ − 141 bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
+ − 142 if (data.empty())
+ − 143 return false;
+ − 144
+ − 145 std::istringstream stream(data);
+ − 146 std::string line;
+ − 147 size_t indentation = 0;
+ − 148 auto state = internal::parser::State::ExpectPlayerName;
+ − 149
+ − 150 while (std::getline(stream, line, '\n')) {
+ − 151 if (line.empty())
+ − 152 continue; // Ignore empty lines
+ − 153
+ − 154 indentation = internal::parser::GetIndentation(line);
+ − 155
+ − 156 internal::util::TrimLeft(line, "\t");
+ − 157 internal::util::TrimRight(line, "\n\r");
+ − 158
+ − 159 if (line.empty() || line.front() == '#')
+ − 160 continue; // Ignore empty lines and comments
+ − 161
+ − 162 if (!internal::parser::HandleIndentation(indentation, players, state))
+ − 163 return false;
+ − 164
+ − 165 if (!internal::parser::HandleState(line, players, state))
+ − 166 return false;
+ − 167 }
+ − 168
+ − 169 return !players.empty();
+ − 170 }
+ − 171
+ − 172 bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
+ − 173 std::string data;
+ − 174
+ − 175 if (!internal::util::ReadFile(path, data))
+ − 176 return false;
+ − 177
+ − 178 return ParsePlayersData(data, players);
+ − 179 }
+ − 180
+ − 181 } // namespace animone