From 237caa29d7b22b7ac1a45a6f8001db1e44d01cca Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 7 Feb 2024 01:02:00 +0800 Subject: [PATCH] feat: PiCli configurator, and init interactor --- .../MaaProjectInterfaceCli/configurator.cpp | 76 +++++-- source/MaaProjectInterfaceCli/configurator.h | 12 +- source/MaaProjectInterfaceCli/interactor.cpp | 185 ++++++++++++++++++ source/MaaProjectInterfaceCli/interactor.h | 23 +++ source/ProjectInterface/Parser/Parser.cpp | 33 +++- source/include/ProjectInterface/Parser.h | 3 + source/include/ProjectInterface/Types.h | 12 +- 7 files changed, 314 insertions(+), 30 deletions(-) create mode 100644 source/MaaProjectInterfaceCli/interactor.cpp create mode 100644 source/MaaProjectInterfaceCli/interactor.h diff --git a/source/MaaProjectInterfaceCli/configurator.cpp b/source/MaaProjectInterfaceCli/configurator.cpp index 231bec474..07f100d66 100644 --- a/source/MaaProjectInterfaceCli/configurator.cpp +++ b/source/MaaProjectInterfaceCli/configurator.cpp @@ -3,23 +3,29 @@ #include #include "ProjectInterface/Parser.h" +#include "Utils/Logger.h" +#include "Utils/Platform.h" using namespace MAA_PROJECT_INTERFACE_NS; bool Configurator::load(const std::filesystem::path& project_dir) { + LogFunc << VAR(project_dir); + auto data_opt = Parser::parse_interface(project_dir / kInterfaceFilename); if (!data_opt) { + LogError << "Failed to parse interface.json"; return false; } data_ = *data_opt; + if (data_.resource.empty()) { + LogError << "Resource is empty"; + return false; + } Configuration config; - auto cfg_json_opt = json::open(project_dir / kConfigFilename); - if (cfg_json_opt) { - if (auto cfg_opt = Parser::parse_config(*cfg_json_opt)) { - config = *cfg_opt; - } + if (auto cfg_opt = Parser::parse_config(project_dir / kConfigFilename)) { + config = *cfg_opt; } project_dir_ = project_dir; @@ -31,29 +37,67 @@ void Configurator::save() std::ofstream(project_dir_ / kInterfaceFilename) << config_.to_json(); } -void Configurator::check_config() +std::optional Configurator::generate_runtime() const { + RuntimeParam runtime; + auto resource_iter = std::ranges::find_if(data_.resource, [&](const auto& resource) { return resource.name == config_.resource; }); if (resource_iter == data_.resource.end()) { - select_resource(); + LogWarn << "Resource not found"; + return std::nullopt; } - else { - runtime_.resource_path = resource_iter->path; + + runtime.resource_path = resource_iter->path; + + for (const auto& config_task : config_.task) { + auto task_opt = generate_runtime_task(config_task); + if (!task_opt) { + LogWarn << "Task not found, ignore" << VAR(config_task.name); + continue; + } + runtime.task.emplace_back(*std::move(task_opt)); } - for (const auto& config_task : config_.task) {} + runtime.executor = data_.executor; - runtime_.executor = data_.executor; + LogTrace << VAR(runtime); + return runtime; } -void Configurator::select_resource() +std::optional Configurator::generate_runtime_task(const Configuration::Task& config_task) const { - Resource res; + auto data_iter = std::ranges::find_if(data_.entry, [&](const auto& task) { return task.name == config_task.name; }); + if (data_iter == data_.entry.end()) { + LogWarn << "Task not found, remove" << VAR(config_task.name); + return std::nullopt; + } + const Entry& data_entry = *data_iter; + + RuntimeParam::Task result { .entry = data_entry.name, .param = data_entry.param }; + + for (const auto& [config_option, config_option_value] : config_task.option) { + auto data_option_iter = + std::ranges::find_if(data_entry.options, [&](const auto& opt) { return opt.name == config_option; }); + if (data_option_iter == data_entry.options.end()) { + LogWarn << "Option not found, remove" << VAR(config_task.name) << VAR(config_option); + return std::nullopt; + } + const Option& data_option = *data_option_iter; - // TODO + auto case_iter = + std::ranges::find_if(data_option.cases, [&](const auto& c) { return c.name == config_option_value; }); + if (case_iter == data_option.cases.end()) { + LogWarn << "Case not found, remove" << VAR(config_task.name) << VAR(config_option) + << VAR(config_option_value); + return std::nullopt; + } + const Option::Case& data_case = *case_iter; + + // data_case first, duplicate keys will be overwritten by data_case.param + result.param = data_case.param | std::move(result.param); + } - config_.resource = res.name; - runtime_.resource_path = res.path; + return result; } diff --git a/source/MaaProjectInterfaceCli/configurator.h b/source/MaaProjectInterfaceCli/configurator.h index ba484831f..ec0505c8b 100644 --- a/source/MaaProjectInterfaceCli/configurator.h +++ b/source/MaaProjectInterfaceCli/configurator.h @@ -14,15 +14,19 @@ class Configurator bool load(const std::filesystem::path& project_dir); void save(); - void check_config(); - void select_resource(); + std::optional generate_runtime() const; - const auto& get_runtime() const { return runtime_; } + const auto& interface_data() const { return data_; } + + const auto& configuration() const { return config_; } + auto& configuration() { return config_; } private: + std::optional generate_runtime_task( + const MAA_PROJECT_INTERFACE_NS::Configuration::Task& config_task) const; + std::filesystem::path project_dir_; MAA_PROJECT_INTERFACE_NS::InterfaceData data_; MAA_PROJECT_INTERFACE_NS::Configuration config_; - MAA_PROJECT_INTERFACE_NS::RuntimeParam runtime_; }; diff --git a/source/MaaProjectInterfaceCli/interactor.cpp b/source/MaaProjectInterfaceCli/interactor.cpp new file mode 100644 index 000000000..106f540a3 --- /dev/null +++ b/source/MaaProjectInterfaceCli/interactor.cpp @@ -0,0 +1,185 @@ +#include "interactor.h" + +#include + +#include "Utils/Logger.h" +#include "Utils/Platform.h" + +// return [1, size] +int input(size_t size) +{ + std::cout << std::format("Please Input [1-{}]:", size); + int value = 0; + + while (true) { + std::cin >> value; + if (value > 0 && value <= size) { + break; + } + std::cout << std::format("Invalid value, please Input [1-{}]:", size); + } + + return value; +} + +void Interactor::interact_for_first_time_use() +{ + select_resource(); + add_task(); + interact(); +} + +void Interactor::interact() +{ + while (true) { + interact_once(); + } +} + +void Interactor::interact_once() +{ + std::cout << "\n\n\n"; + std::cout << "### Current configuration ###\n\n"; + std::cout << "Resource:\n\n"; + std::cout << "\t" << config_.configuration().resource << "\n\n"; + std::cout << "Tasks:\n\n"; + for (const auto& task : config_.configuration().task) { + std::cout << "\t- " << task.name << "\n"; + for (const auto& [key, value] : task.option) { + std::cout << "\t\t- " << key << ": " << value << "\n"; + } + } + + std::cout << "### Select action ###\n\n"; + std::cout << "\t1. Run tasks\n"; + std::cout << "\t2. Select resource\n"; + std::cout << "\t3. Add task\n"; + std::cout << "\t4. Edit task\n"; + std::cout << "\t5. Remove task\n"; + std::cout << "\t6. Move task\n"; + + int action = input(6); + + switch (action) { + case 1: + // run_tasks(); + break; + case 2: + select_resource(); + break; + case 3: + add_task(); + break; + case 4: + edit_task(); + break; + case 5: + remove_task(); + break; + case 6: + move_task(); + break; + } +} + +void Interactor::select_resource() +{ + using namespace MAA_PROJECT_INTERFACE_NS; + + const auto& all_resources = config_.interface_data().resource; + if (all_resources.empty()) { + LogError << "Resource is empty"; + return; + } + + int index = 0; + if (all_resources.size() != 1) { + std::cout << "\n\n\n"; + std::cout << "### Select resource ###\n\n"; + for (size_t i = 0; i < all_resources.size(); ++i) { + std::cout << std::format("\t{}. {}\n", i + 1, all_resources[i].name); + } + index = input(all_resources.size()) - 1; + } + else { + std::cout << "\n\n\n"; + std::cout << "### Only one resource, use it ###\n\n"; + index = 0; + } + const Resource& resource = all_resources[index]; + + config_.configuration().resource = resource.name; + config_.save(); +} + +void Interactor::add_task() +{ + using namespace MAA_PROJECT_INTERFACE_NS; + + const auto& all_tasks = config_.interface_data().entry; + if (all_tasks.empty()) { + LogError << "Task is empty"; + return; + } + + std::cout << "\n\n\n"; + std::cout << "### Add task ###\n\n"; + for (size_t i = 0; i < all_tasks.size(); ++i) { + std::cout << std::format("\t{}. {}\n", i + 1, all_tasks[i].name); + } + int task_index = input(all_tasks.size()) - 1; + const Entry& task = all_tasks[task_index]; + + std::unordered_map option_map; + for (const Option& opt : task.options) { + if (!opt.default_case.empty()) { + option_map.emplace(opt.name, opt.default_case); + continue; + } + std::cout << std::format("\n\n\n## Input option for \"{}\" ##\n\n", opt.name); + for (size_t i = 0; i < opt.cases.size(); ++i) { + std::cout << std::format("\t{}. {}\n", i + 1, opt.cases[i]); + } + int case_index = input(opt.cases.size()) - 1; + option_map.emplace(opt.name, opt.cases[case_index]); + } + + config_.configuration().task.emplace_back( + Configuration::Task { .name = task.name, .option = std::move(option_map) }); + config_.save(); +} + +void Interactor::edit_task() +{ + using namespace MAA_PROJECT_INTERFACE_NS; + + const auto& all_tasks = config_.configuration().task; + if (all_tasks.empty()) { + LogError << "Task is empty"; + return; + } + + std::cout << "\n\n\n"; + std::cout << "### Edit task ###\n\n"; + for (size_t i = 0; i < all_tasks.size(); ++i) { + std::cout << std::format("\t{}. {}\n", i + 1, all_tasks[i].name); + } + int task_index = input(all_tasks.size()) - 1; + + Configuration::Task& task = config_.configuration().task[task_index]; + + const auto& all_tasks_in_interface = config_.interface_data().entry; + auto task_in_interface = + std::ranges::find_if(all_tasks_in_interface, [&](const auto& entry) { return entry.name == task.name; }); + + if (task_in_interface == all_tasks_in_interface.end()) { + LogError << "Task not found in interface" << VAR(task.name); + return; + } + + // TODO +} + +void Interactor::remove_task() {} + +void Interactor::move_task() {} diff --git a/source/MaaProjectInterfaceCli/interactor.h b/source/MaaProjectInterfaceCli/interactor.h new file mode 100644 index 000000000..eb8b28c81 --- /dev/null +++ b/source/MaaProjectInterfaceCli/interactor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "configurator.h" + +class Interactor +{ +public: + const auto& configurator() const { return config_; } + auto& configurator() { return config_; } + + void interact_for_first_time_use(); + void interact(); + +private: + void interact_once(); + void select_resource(); + void add_task(); + void edit_task(); + void remove_task(); + void move_task(); + + Configurator config_; +}; diff --git a/source/ProjectInterface/Parser/Parser.cpp b/source/ProjectInterface/Parser/Parser.cpp index 4fdd5e907..e70cfa278 100644 --- a/source/ProjectInterface/Parser/Parser.cpp +++ b/source/ProjectInterface/Parser/Parser.cpp @@ -8,20 +8,39 @@ std::optional Parser::parse_interface(const std::filesystem::path { LogFunc << VAR(path); - auto root_opt = json::open(path); - if (!root_opt) { + auto json_opt = json::open(path); + if (!json_opt) { LogError << "failed to parse" << path; return std::nullopt; } - const json::value& root = *root_opt; + const json::value& json = *json_opt; + return parse_interface(json); +} + +std::optional Parser::parse_interface(const json::value& json) +{ std::string error_key; - if (!InterfaceData().check_json(root, error_key)) { - LogError << "json is not an InterfaceData" << VAR(error_key) << VAR(root); + if (!InterfaceData().check_json(json, error_key)) { + LogError << "json is not an InterfaceData" << VAR(error_key) << VAR(json); return std::nullopt; } - return root.as(); + return json.as(); +} + +std::optional Parser::parse_config(const std::filesystem::path& path) +{ + LogFunc << VAR(path); + + auto json_opt = json::open(path); + if (!json_opt) { + LogError << "failed to parse" << path; + return std::nullopt; + } + + const json::value& json = *json_opt; + return parse_config(json); } std::optional Parser::parse_config(const json::value& json) @@ -37,4 +56,6 @@ std::optional Parser::parse_config(const json::value& json) return json.as(); } +void Parser::check_configuration(const InterfaceData& data, Configuration& config) {} + MAA_PROJECT_INTERFACE_NS_END diff --git a/source/include/ProjectInterface/Parser.h b/source/include/ProjectInterface/Parser.h index 2a081c235..ce64575f8 100644 --- a/source/include/ProjectInterface/Parser.h +++ b/source/include/ProjectInterface/Parser.h @@ -13,7 +13,10 @@ class Parser { public: static std::optional parse_interface(const std::filesystem::path& path); + static std::optional parse_interface(const json::value& json); + static std::optional parse_config(const std::filesystem::path& path); static std::optional parse_config(const json::value& json); + static void check_configuration(const InterfaceData& data, Configuration& config); }; MAA_PROJECT_INTERFACE_NS_END diff --git a/source/include/ProjectInterface/Types.h b/source/include/ProjectInterface/Types.h index d841aa713..5917c6e17 100644 --- a/source/include/ProjectInterface/Types.h +++ b/source/include/ProjectInterface/Types.h @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include @@ -23,7 +23,7 @@ struct Option struct Case { std::string name; - json::value param; + json::object param; MEO_JSONIZATION(name, param); }; @@ -38,7 +38,7 @@ struct Option struct Entry { std::string name; - json::value param; + json::object param; std::vector