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
|