view dep/animia/src/player.cc @ 198:bc1ae1810855

dep/animia: switch from using classes to global functions the old idea was ok, but sort of hackish; this method doesn't use classes at all, and this way (especially important!) we can do wayland stuff AND x11 at the same time, which wasn't really possible without stupid workarounds in the other method
author Paper <mrpapersonic@gmail.com>
date Sun, 24 Dec 2023 02:59:42 -0500
parents cdf79282d647
children
line wrap: on
line source

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

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

namespace animia {

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 animia