From a751988676906435ea2931fd54beb5b6444feeb3 Mon Sep 17 00:00:00 2001 From: ntfs-sys Date: Tue, 28 Apr 2026 01:57:59 +0400 Subject: [PATCH 1/6] feat: CLI Menu, based on nin --- menu/src/MenuLab.cppm | 121 +++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/menu/src/MenuLab.cppm b/menu/src/MenuLab.cppm index a5254b8..c9813d3 100644 --- a/menu/src/MenuLab.cppm +++ b/menu/src/MenuLab.cppm @@ -1,48 +1,85 @@ -export module MenuLab; +export module MenuLab; import std; +import nin; -export template -class Menu -{ +// ... --- ... + +export struct menu_item { + std::string name; + std::function action; + + template + menu_item(std::string n, F&& f) + : name(std::move(n)), + action(std::forward(f)) {} +}; + +export class Menu { private: - std::size_t _lab_num = 0; - std::string _author; - std::size_t _variant = 0; - std::unordered_map _options; + int _lab_num{}; + std::string _author; + int _variant{}; + std::vector _items; public: - explicit Menu(std::size_t lab_num, - std::string author, - std::size_t variant, - std::unordered_map options) - : _lab_num(lab_num), - _author(std::move(author)), - _variant(variant), - _options(options) - { - } - - void PrintHeader() const - { - std::println(""); - std::println("------------------------------"); - std::println("Лабораторная работа №: {}", _lab_num); - std::println("Автор: {}", _author); - std::println("Вариант №: {}", _variant); - std::println("------------------------------"); - std::println(""); - } - - void PrintMenu() const - { - std::println(""); - std::println("------------------------------------------------------------------------------------------------------------------------"); - for (const auto &[index, option] : _options) - { - std::println("[{}] {}", index, option); - } - std::println("------------------------------------------------------------------------------------------------------------------------"); - std::println(""); - } -}; \ No newline at end of file + Menu(int lab_num, + std::string author, + int variant, + std::vector items) + : _lab_num(lab_num), + _author(std::move(author)), + _variant(variant), + _items(std::move(items)) + {} + + template + void add_item(std::string name, F&& f) { + _items.emplace_back(std::move(name), std::forward(f)); + } + + void add_item(menu_item item) { + _items.emplace_back(std::move(item)); + } + + void print_header() const { + std::println("\n------------------------------"); + std::println("Лабораторная работа №: {}", _lab_num); + std::println("Автор: {}", _author); + std::println("Вариант №: {}", _variant); + std::println("------------------------------\n"); + } + + void run() { + if (_items.empty()) { + std::println("Меню пустое"); + return; + } + + while (true) { + for (std::size_t i = 0; i < _items.size(); ++i) { + std::println("[{}] {}", i + 1, _items[i].name); + } + std::println("[0] Выход"); + + int choice = nin::input_numeric( + "Выбор: ", + 0, + static_cast(_items.size()) + ); + + if (choice == 0) { + return; + } + + auto& item = _items[choice - 1]; + + try { + item.action(); + } + catch (const std::exception& e) { + std::println("Ошибка: {}", e.what()); + } + } + } +}; From e2781e752e46caf677f90c8c28acf93f4b0685b9 Mon Sep 17 00:00:00 2001 From: Dauxdu Date: Wed, 29 Apr 2026 12:42:56 +0400 Subject: [PATCH 2/6] feat: enable menu subdirectory in CMake configuration --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b8d59d..b043fcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,4 +15,4 @@ endif() add_subdirectory(command) add_subdirectory(input) -# add_subdirectory(menu) \ No newline at end of file +add_subdirectory(menu) From 5d75dd12c45a6e73b6511b8e13d6abeb39887aea Mon Sep 17 00:00:00 2001 From: ntfs-sys Date: Thu, 30 Apr 2026 02:41:36 +0400 Subject: [PATCH 3/6] fix: god fix(no god function) --- menu/src/MenuLab.cppm | 163 +++++++++++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 59 deletions(-) diff --git a/menu/src/MenuLab.cppm b/menu/src/MenuLab.cppm index c9813d3..8b406e7 100644 --- a/menu/src/MenuLab.cppm +++ b/menu/src/MenuLab.cppm @@ -1,85 +1,130 @@ export module MenuLab; import std; + +//👍👍👍👍👍👍👍👍👍👍👍👍 +/* import nin; +struct SafeInputPolicy { + static size_t GetChoice(std::string_view prompt, size_t min, size_t max) { + return nin::input_numeric(prompt, min, max); + } +}; +using MyMenu = Menu; +*/ -// ... --- ... +//👎👎👎 +export struct DefaultInputPolicy { + static std::size_t GetChoice(std::string_view prompt, std::size_t min, std::size_t max) { + if (!prompt.empty()) std::print("{} ", prompt); + + std::size_t value; + + if (!(std::cin >> value)) { + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + throw std::runtime_error("DefaultInputPolicy: invalid input"); + } + + std::cin.ignore(std::numeric_limits::max(), '\n'); + + if (value < min || value > max) { + throw std::runtime_error("DefaultInputPolicy: value out of range"); + } + + return value; + } +}; + +export struct DefaultOutputPolicy { + static void Print(std::string_view s) { std::print(s); } + static void Println(std::string_view s) { std::println(s); } + static void Println() { std::println(); } +}; -export struct menu_item { - std::string name; - std::function action; +export struct MenuItemExecutable { + std::string Name; + std::function Action; + + template + MenuItemExecutable(std::string n, Func&& f) + : Name(std::move(n)), Action(std::forward(f)) + {} +}; - template - menu_item(std::string n, F&& f) - : name(std::move(n)), - action(std::forward(f)) {} +export struct MenuItemReading { + std::string Name; + std::string Value; }; -export class Menu { +export template +class Menu { private: - int _lab_num{}; - std::string _author; - int _variant{}; - std::vector _items; + std::vector _readonly; + std::vector _executable; public: - Menu(int lab_num, - std::string author, - int variant, - std::vector items) - : _lab_num(lab_num), - _author(std::move(author)), - _variant(variant), - _items(std::move(items)) + Menu() = default; + + Menu(std::vector ro, std::vector ex) + : _readonly(std::move(ro)), _executable(std::move(ex)) {} - template - void add_item(std::string name, F&& f) { - _items.emplace_back(std::move(name), std::forward(f)); + void AddReadonly(std::string name, std::string value) { + _readonly.push_back({std::move(name), std::move(value)}); + } + + void AddExecutable(std::string name, std::function action) { + _executable.emplace_back(std::move(name), std::move(action)); } - void add_item(menu_item item) { - _items.emplace_back(std::move(item)); + template + void AddExecutable(std::string name, Func&& f) { + _executable.emplace_back(std::move(name), std::forward(f)); } - void print_header() const { - std::println("\n------------------------------"); - std::println("Лабораторная работа №: {}", _lab_num); - std::println("Автор: {}", _author); - std::println("Вариант №: {}", _variant); - std::println("------------------------------\n"); + void ShowHeader() const { + if (_readonly.empty()) return; + OutputPolicy::Println("\n------------------------------"); + for (const auto& item : _readonly) { + OutputPolicy::Println(item.Name + ": " + item.Value); + } + OutputPolicy::Println("------------------------------\n"); } - void run() { - if (_items.empty()) { - std::println("Меню пустое"); + void ShowActions() const { + if (_executable.empty()) { + OutputPolicy::Println("Нет действий."); return; } + for (std::size_t i = 0; i < _executable.size(); ++i) { + OutputPolicy::Println("[" + std::to_string(i + 1) + "] " + _executable[i].Name); + } + OutputPolicy::Println("[0] Выход"); + } + + int Step() { + if (_executable.empty()) { + OutputPolicy::Println("Нет действий. Завершение."); + return 1; + } + + ShowHeader(); + ShowActions(); + + try { + std::size_t choice = InputPolicy::GetChoice("Выбор: ", 0, _executable.size()); + + if (choice == 0) { + OutputPolicy::Println("Выход."); + return 1; + } - while (true) { - for (std::size_t i = 0; i < _items.size(); ++i) { - std::println("[{}] {}", i + 1, _items[i].name); - } - std::println("[0] Выход"); - - int choice = nin::input_numeric( - "Выбор: ", - 0, - static_cast(_items.size()) - ); - - if (choice == 0) { - return; - } - - auto& item = _items[choice - 1]; - - try { - item.action(); - } - catch (const std::exception& e) { - std::println("Ошибка: {}", e.what()); - } + _executable[choice - 1].Action(); + } catch (const std::exception& e) { + OutputPolicy::Println("Ошибка: " + std::string(e.what())); } + OutputPolicy::Println(); + return 0; } }; From 0e574a1a5582123ac5461e0e5ca1cbff875f626b Mon Sep 17 00:00:00 2001 From: ntfs-sys Date: Thu, 30 Apr 2026 03:32:18 +0400 Subject: [PATCH 4/6] ICommand instead of lambda --- menu/src/MenuLab.cppm | 64 ++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/menu/src/MenuLab.cppm b/menu/src/MenuLab.cppm index 8b406e7..ba64748 100644 --- a/menu/src/MenuLab.cppm +++ b/menu/src/MenuLab.cppm @@ -1,5 +1,4 @@ export module MenuLab; - import std; //👍👍👍👍👍👍👍👍👍👍👍👍 @@ -42,14 +41,9 @@ export struct DefaultOutputPolicy { static void Println() { std::println(); } }; -export struct MenuItemExecutable { - std::string Name; - std::function Action; - - template - MenuItemExecutable(std::string n, Func&& f) - : Name(std::move(n)), Action(std::forward(f)) - {} +export struct ICommand { + virtual ~ICommand() = default; + virtual void Execute() const = 0; }; export struct MenuItemReading { @@ -57,7 +51,25 @@ export struct MenuItemReading { std::string Value; }; -export template +export struct MenuItemExecutable { + std::string Name; + std::unique_ptr Action; + + MenuItemExecutable(std::string n, std::unique_ptr cmd) + : Name(std::move(n)), Action(std::move(cmd)) {} +}; + +namespace detail { + template + struct CallableCommand final : ICommand { + Func func; + explicit CallableCommand(Func f) : func(std::move(f)) {} + void Execute() const override { func(); } + }; +} + +export template class Menu { private: std::vector _readonly; @@ -67,23 +79,25 @@ public: Menu() = default; Menu(std::vector ro, std::vector ex) - : _readonly(std::move(ro)), _executable(std::move(ex)) - {} + : _readonly(std::move(ro)), _executable(std::move(ex)) {} void AddReadonly(std::string name, std::string value) { _readonly.push_back({std::move(name), std::move(value)}); } - void AddExecutable(std::string name, std::function action) { - _executable.emplace_back(std::move(name), std::move(action)); + void AddExecutable(std::string name, std::unique_ptr cmd) { + _executable.emplace_back(std::move(name), std::move(cmd)); } template void AddExecutable(std::string name, Func&& f) { - _executable.emplace_back(std::move(name), std::forward(f)); + _executable.emplace_back( + std::move(name), + std::make_unique>>(std::forward(f)) + ); } - void ShowHeader() const { + void ShowHeader() const noexcept { if (_readonly.empty()) return; OutputPolicy::Println("\n------------------------------"); for (const auto& item : _readonly) { @@ -92,7 +106,7 @@ public: OutputPolicy::Println("------------------------------\n"); } - void ShowActions() const { + void ShowActions() const noexcept { if (_executable.empty()) { OutputPolicy::Println("Нет действий."); return; @@ -113,14 +127,14 @@ public: ShowActions(); try { - std::size_t choice = InputPolicy::GetChoice("Выбор: ", 0, _executable.size()); - - if (choice == 0) { - OutputPolicy::Println("Выход."); - return 1; - } - - _executable[choice - 1].Action(); + std::size_t choice = InputPolicy::GetChoice("Выбор: ", 0, _executable.size()); + + if (choice == 0) { + OutputPolicy::Println("Выход."); + return 1; + } + + _executable[choice - 1].Action->Execute(); } catch (const std::exception& e) { OutputPolicy::Println("Ошибка: " + std::string(e.what())); } From a54575a61108b9771e1b81a210f2571526af15e0 Mon Sep 17 00:00:00 2001 From: ntfs-sys Date: Thu, 30 Apr 2026 10:24:09 +0400 Subject: [PATCH 5/6] fix:uncommented --- menu/CMakeLists.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/menu/CMakeLists.txt b/menu/CMakeLists.txt index 2ec8753..db670ca 100644 --- a/menu/CMakeLists.txt +++ b/menu/CMakeLists.txt @@ -1,12 +1,12 @@ project(menu LANGUAGES CXX) -#add_library(menu) -# -#target_sources(menu -# PUBLIC -# FILE_SET CXX_MODULES -# FILES -# src/MenuLab.cppm -#) -# +add_library(menu) + +target_sources(menu + PUBLIC + FILE_SET CXX_MODULES + FILES + src/MenuLab.cppm +) + #target_compile_features(menu PUBLIC cxx_std_23) From b370711c83df2a6249818d0d7a5a3e4857f5e7fe Mon Sep 17 00:00:00 2001 From: ntfs-sys Date: Thu, 30 Apr 2026 10:32:19 +0400 Subject: [PATCH 6/6] fix: println issue --- menu/src/MenuLab.cppm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/menu/src/MenuLab.cppm b/menu/src/MenuLab.cppm index ba64748..186d2ee 100644 --- a/menu/src/MenuLab.cppm +++ b/menu/src/MenuLab.cppm @@ -36,8 +36,8 @@ export struct DefaultInputPolicy { }; export struct DefaultOutputPolicy { - static void Print(std::string_view s) { std::print(s); } - static void Println(std::string_view s) { std::println(s); } + static void Print(std::string_view s) { std::print("{}",s); } + static void Println(std::string_view s) { std::println("{}",s); } static void Println() { std::println(); } };