view dep/animone/src/player.cc @ 327:b5d6c27c308f

anime: refactor Anime::SeriesSeason to Season class ToLocalString has also been altered to take in both season and year because lots of locales actually treat formatting seasons differently! most notably is Russian which adds a suffix at the end to notate seasons(??)
author Paper <paper@paper.us.eu.org>
date Thu, 13 Jun 2024 01:49:18 -0400
parents b1f625b0227c
children a7d4e5107531
line wrap: on
line source

#include "animone/player.h"
#include "animone/util.h"

#include <map>
#include <sstream>
#include <string>
#include <vector>

namespace animone {

namespace internal::parser {

enum class State {
	ExpectPlayerName,
	ExpectSection,
	ExpectWindow,
	ExpectExecutable,
	ExpectStrategy,
	ExpectType,
	ExpectWindowTitle,
};

size_t GetIndentation(const std::string& line) {
	return line.find_first_not_of('\t');
}

bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
	// Each state has a definitive expected indentation
	const auto expected = [&state]() -> size_t {
		switch (state) {
			default:
			case State::ExpectPlayerName: return 0;
			case State::ExpectSection: return 1;
			case State::ExpectWindow:
			case State::ExpectExecutable:
			case State::ExpectStrategy:
			case State::ExpectType: return 2;
			case State::ExpectWindowTitle: return 3;
		}
	}();

	if (current > expected)
		return false; // Disallow excessive indentation

	if (current < expected) {
		auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; };
		switch (state) {
			case State::ExpectWindow:
				if (players.back().windows.empty())
					return false;
				fix_state();
				break;
			case State::ExpectExecutable:
				if (players.back().executables.empty())
					return false;
				fix_state();
				break;
			case State::ExpectStrategy:
				if (players.back().strategies.empty())
					return false;
				fix_state();
				break;
			case State::ExpectType: fix_state(); break;
			case State::ExpectWindowTitle: return false;
		}
	}

	return true;
}

bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
	switch (state) {
		case State::ExpectPlayerName:
			players.push_back(Player());
			players.back().name = line;
			state = State::ExpectSection;
			break;

		case State::ExpectSection: {
			static const std::map<std::string, State> sections = {
			    {"windows",     State::ExpectWindow    },
			    {"executables", State::ExpectExecutable},
			    {"strategies",  State::ExpectStrategy  },
			    {"type",        State::ExpectType      },
			};
			util::TrimRight(line, ":");
			const auto it = sections.find(line);
			if (it == sections.end())
				return false;
			state = it->second;
			break;
		}

		case State::ExpectWindow: players.back().windows.push_back(line); break;

		case State::ExpectExecutable: players.back().executables.push_back(line); break;

		case State::ExpectStrategy: {
			static const std::map<std::string, Strategy> strategies = {
			    {"window_title",  Strategy::WindowTitle },
			    {"open_files",    Strategy::OpenFiles   },
			    {"ui_automation", Strategy::UiAutomation},
			};
			util::TrimRight(line, ":");
			const auto it = strategies.find(line);
			if (it == strategies.end())
				return false;
			const auto strategy = it->second;
			players.back().strategies.push_back(strategy);
			switch (strategy) {
				case Strategy::WindowTitle: state = State::ExpectWindowTitle; break;
			}
			break;
		}

		case State::ExpectType: {
			static const std::map<std::string, PlayerType> types = {
			    {"default",     PlayerType::Default   },
			    {"web_browser", PlayerType::WebBrowser},
			};
			const auto it = types.find(line);
			if (it == types.end())
				return false;
			players.back().type = it->second;
			break;
		}

		case State::ExpectWindowTitle:
			players.back().window_title_format = line;
			state = State::ExpectStrategy;
			break;
	}

	return true;
}

} // namespace internal::parser

////////////////////////////////////////////////////////////////////////////////

bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
	if (data.empty())
		return false;

	std::istringstream stream(data);
	std::string line;
	size_t indentation = 0;
	auto state = internal::parser::State::ExpectPlayerName;

	while (std::getline(stream, line, '\n')) {
		if (line.empty())
			continue; // Ignore empty lines

		indentation = internal::parser::GetIndentation(line);

		internal::util::TrimLeft(line, "\t");
		internal::util::TrimRight(line, "\n\r");

		if (line.empty() || line.front() == '#')
			continue; // Ignore empty lines and comments

		if (!internal::parser::HandleIndentation(indentation, players, state))
			return false;

		if (!internal::parser::HandleState(line, players, state))
			return false;
	}

	return !players.empty();
}

bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
	std::string data;

	if (!internal::util::ReadFile(path, data))
		return false;

	return ParsePlayersData(data, players);
}

} // namespace animone