diff --git a/src/cpp/project/pathfinder/Makefile b/src/cpp/project/pathfinder/Makefile index be89630b..a1be176b 100644 --- a/src/cpp/project/pathfinder/Makefile +++ b/src/cpp/project/pathfinder/Makefile @@ -13,8 +13,7 @@ else include = endif -CXXFLAGS_NOWARN = -O3 -DNDEBUG -std=c++23 -pedantic $(include) -CXXFLAGS = $(warnings) $(CXXFLAGS_NOWARN) +CXXFLAGS = $(warnings) -O3 -DNDEBUG -std=c++2c -pedantic -fpermissive $(include) LDFLAGS = $(libs) EXE = pathfinder diff --git a/src/cpp/project/pathfinder/src/options.cpp b/src/cpp/project/pathfinder/src/cli.cpp similarity index 61% rename from src/cpp/project/pathfinder/src/options.cpp rename to src/cpp/project/pathfinder/src/cli.cpp index 6c99643d..03a148a5 100644 --- a/src/cpp/project/pathfinder/src/options.cpp +++ b/src/cpp/project/pathfinder/src/cli.cpp @@ -1,4 +1,4 @@ -#include "options.hpp" +#include "cli.hpp" #include @@ -7,24 +7,22 @@ #include #include -#include -#include "command.hpp" +#include "cmd.hpp" -paf::Options::Options(int argc, char** argv) +paf::cli::cli(int argc, char** argv) { bool help = false; auto cli = lyra::cli() | lyra::help(help).description("Pathfinding interface for interactive shells."); - paf::Alias alias{ cli }; - paf::Mark mark{ cli }; - paf::Unmark unmark{ cli }; - paf::ToggleMark toggle_markj{ cli }; - paf::Jump jump{ cli }; - paf::Open open{ cli }; - paf::Print print{ cli }; - paf::List list{ cli }; + paf::alias _{ cli }; + paf::mark _{ cli }; + paf::unmark _{ cli }; + paf::jump _{ cli }; + paf::open _{ cli }; + paf::print _{ cli }; + paf::list _{ cli }; auto args = cli.parse({ argc, argv }); @@ -39,3 +37,8 @@ paf::Options::Options(int argc, char** argv) std::exit(EXIT_SUCCESS); } } + +[[noreturn]] void paf::cli::run(void) +{ + std::exit(EXIT_SUCCESS); +} diff --git a/src/cpp/project/pathfinder/src/cli.hpp b/src/cpp/project/pathfinder/src/cli.hpp new file mode 100644 index 00000000..fe56b11d --- /dev/null +++ b/src/cpp/project/pathfinder/src/cli.hpp @@ -0,0 +1,13 @@ +#ifndef HEADER_SCRIPTS_CXX_PAF_CLI_ +#define HEADER_SCRIPTS_CXX_PAF_CLI_ + +namespace paf { + class cli { + public: + cli(void) = delete; + cli(int argc, char** argv); + [[noreturn]] void run(void); + }; +} // namespace paf + +#endif /* ifndef HEADER_SCRIPTS_CXX_PAF_CLI_ */ diff --git a/src/cpp/project/pathfinder/src/cmd.cpp b/src/cpp/project/pathfinder/src/cmd.cpp new file mode 100644 index 00000000..b29e3e02 --- /dev/null +++ b/src/cpp/project/pathfinder/src/cmd.cpp @@ -0,0 +1,337 @@ +#include "cmd.hpp" + +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +#include "db.hpp" + +namespace fs = std::filesystem; + +paf::alias::alias(lyra::cli& cli) +{ + auto command = lyra::command("alias", [this](const lyra::group& group) { execute(group); }) + .help("Print aliases `eval`able by a POSIX-compatible shell.") + .add_argument(lyra::help(m_show_help)) + .optional(); + cli.add_argument(std::move(command)); +} + +paf::mark::mark(lyra::cli& cli) +{ + const auto opt_yes = + lyra::opt(m_yes)["-y"]["--yes"]("don't require manual confirmation for overwriting marks"); + const auto arg_keycode = + lyra::arg(m_keycode, "keycode")("keycode to assign the file to").cardinality(1, 1); + const auto arg_file = lyra::arg(m_file, "file")("file to mark").cardinality(0, 1); + + auto command = lyra::command("mark", [this](const lyra::group& group) { execute(group); }) + .help("Mark the given directory or file.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(opt_yes)) + .add_argument(std::move(arg_keycode)) + .add_argument(std::move(arg_file)) + .optional(); + + cli.add_argument(std::move(command)); +} + +paf::unmark::unmark(lyra::cli& cli) +{ + const auto arg_identifier = + lyra::arg(m_identifier, "identifier")("keycode, directory or file to unmark") + .cardinality(1, 1); + + auto command = lyra::command("unmark", [this](const lyra::group& group) { execute(group); }) + .help("Unmark the given keycode, directory or file.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(arg_identifier)) + .optional(); + + cli.add_argument(std::move(command)); +} + +paf::jump::jump(lyra::cli& cli) +{ + const auto arg_keycode = + lyra::arg(m_keycode, "keycode")("keycode to match the directory").cardinality(1, 1); + + auto command = lyra::command("jump", [this](const lyra::group& group) { execute(group); }) + .help("Jump to the directory for the given keycode.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(arg_keycode)) + .optional(); + + cli.add_argument(std::move(command)); +} + +paf::open::open(lyra::cli& cli) +{ + const auto arg_keycode = + lyra::arg(m_keycode, "keycode")("keycode to match the file").cardinality(1, 1); + + auto command = lyra::command("open", [this](const lyra::group& group) { execute(group); }) + .help("Open the file for the given keycode.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(arg_keycode)) + .optional(); + + cli.add_argument(std::move(command)); +} + +paf::print::print(lyra::cli& cli) +{ + const auto opt_db_type = + lyra::opt(m_db_type, "database")["-d"]["--database"]("database to search") + .choices("both", "directory", "file"); + const auto arg_keycode = + lyra::arg(m_keycode, "keycode")("keycode to match the file").cardinality(1, 1); + const auto opt_nul = lyra::opt(m_nul)["-0"]["-z"]["--null"]("use NUL as output delimiter"); + + auto command = lyra::command("print", [this](const lyra::group& group) { execute(group); }) + .help("Print the value for the given keycode.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(opt_db_type)) + .add_argument(std::move(arg_keycode)) + .add_argument(std::move(opt_nul)) + .optional(); + + cli.add_argument(std::move(command)); +} + +paf::list::list(lyra::cli& cli) +{ + const auto opt_db_type = + lyra::opt(m_db_type, "database")["-d"]["--database"]("database to list") + .choices("both", "directory", "file"); + const auto opt_nul = lyra::opt(m_nul)["-0"]["-z"]["--null"]("use NUL as output delimiter"); + + auto command = lyra::command("list", [this](const lyra::group& group) { execute(group); }) + .help("List all marks.") + .add_argument(lyra::help(m_show_help)) + .add_argument(std::move(opt_db_type)) + .add_argument(std::move(opt_nul)) + .optional(); + + cli.add_argument(std::move(command)); +} + +PAF_CMD_NORETURN void paf::alias::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + auto print_alias = [](const char* alias, const char* cmd) { + std::cout << alias << R"#(() { eval "$()#" << xph::exec_path << ' ' << cmd + << R"#( "$@")"; })#" + "\n"; + }; + + print_alias("m", "toggle-mark"); + print_alias("g", "jump"); + print_alias("e", "open"); + + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::mark::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + xph::die_if(!m_keycode.size(), " cannot be empty"); + xph::die_if(m_file && !m_file->size(), " cannot be empty"); + + if (!m_file) { + auto cwd = getcwd(nullptr, 0); + m_file = cwd; + free(cwd); + } + + auto db_type = fs::is_directory(*m_file) ? db_type::directory : db_type::file; + auto db = db::get_db(db_type); + + if (auto kc_idx = db.index_of(m_keycode); kc_idx) { + if (!m_yes) { + std::cout << "Keycode already exists. " + "Overwrite? [y/N] "; + + std::string answer; + std::getline(std::cin, answer); + + if (answer.size() > 3) + goto err; + + xph::str::makelower(answer); + if (answer != "y" && answer != "yes") + goto err; + } + + db.try_remove_mark_at(*kc_idx); + } + + db.add_mark(m_keycode, *m_file); + +err: + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::unmark::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + xph::die_if(!m_identifier.size(), " cannot be empty"); + + if (fs::exists(m_identifier)) { + auto db_type = fs::is_directory(m_identifier) ? db_type::directory : db_type::file; + auto db = db::get_db(db_type); + db.try_remove_mark(m_identifier); + } else { + auto dir_db = db::get_db(db_type::directory); + auto dir_idx = dir_db.index_of(m_identifier); + + auto file_db = db::get_db(db_type::file); + auto file_idx = file_db.index_of(m_identifier); + + bool remove_dir = true; + bool remove_file = true; + if (dir_idx && file_idx) { + std::cout << "Keycode exists in both directory & file databases. " + "Unmark which one? [b/d/f] "; + + std::string answer; + std::getline(std::cin, answer); + + if (answer.size() > 1) + goto err; + + xph::str::makelower(answer); + + if (answer == "b") + ; + else if (answer == "d") + remove_file = false; + else if (answer == "f") + remove_dir = false; + else + goto err; + } + + if (dir_idx && remove_dir) { + dir_db.try_remove_mark_at(*dir_idx); + file_db.cancel(); + } + + if (file_idx && remove_file) { + file_db.try_remove_mark_at(*file_idx); + dir_db.cancel(); + } + } + +err: + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::jump::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::open::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + const char* editor = getenv("EDITOR"); + if (!editor) + editor = getenv("VISUAL"); + + xph::die_if(!editor, "cannot determine editor, set EDITOR or VISUAL"); + + auto db = db::get_db(db_type::file); + auto file = db.try_get_mark(m_keycode); + db.cancel(); + + xph::die_if(!file, "keycode [", m_keycode, "] not found in database"); + + ::execlp(editor, editor, "--", file->c_str(), nullptr); + + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::print::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + if (m_db_type == "both" || m_db_type == "directory") { + auto db = db::get_db(db_type::directory); + auto dir = db.try_get_mark(m_keycode); + db.cancel(); + if (dir) { + std::cout << *dir; + if (!m_db_type.ends_with('/')) + std::cout << '/'; + std::cout << (m_nul ? '\0' : '\n'); + } + } + + if (m_db_type == "both" || m_db_type == "file") { + auto db = db::get_db(db_type::file); + auto file = db.try_get_mark(m_keycode); + db.cancel(); + if (file) + std::cout << *file << (m_nul ? '\0' : '\n'); + } + + PAF_CMD_EXIT(); +} + +PAF_CMD_NORETURN void paf::list::execute(const lyra::group& group) +{ + if (m_show_help) { + std::cout << group << '\n'; + std::exit(EXIT_SUCCESS); + } + + if (m_db_type == "both" || m_db_type == "directory") { + auto db = db::get_db(db_type::directory); + db.dump(m_nul ? "\0" : " : ", m_nul ? "\0" : "\n"); + db.cancel(); + } + + if (m_db_type == "both" || m_db_type == "file") { + auto db = db::get_db(db_type::file); + db.dump(m_nul ? "\0" : " : ", m_nul ? "\0" : "\n"); + db.cancel(); + } + + PAF_CMD_EXIT(); +} diff --git a/src/cpp/project/pathfinder/src/cmd.hpp b/src/cpp/project/pathfinder/src/cmd.hpp new file mode 100644 index 00000000..3c51c2ca --- /dev/null +++ b/src/cpp/project/pathfinder/src/cmd.hpp @@ -0,0 +1,104 @@ +#ifndef HEADER_SCRIPTS_CXX_PAF_CMD_ +#define HEADER_SCRIPTS_CXX_PAF_CMD_ + +#include + +#define PAF_CMD_NORETURN +#define PAF_CMD_EXIT() \ + do { \ + } while (false) + +namespace paf { + class alias { + private: + bool m_show_help = false; + + public: + alias(void) = delete; + alias(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class mark { + private: + bool m_show_help = false; + bool m_yes = false; + std::string m_keycode = ""; + std::optional m_file = {}; + + public: + mark(void) = delete; + mark(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class unmark { + private: + bool m_show_help = false; + std::string m_identifier = ""; + + public: + unmark(void) = delete; + unmark(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class jump { + private: + bool m_show_help = false; + std::string m_keycode = ""; + + public: + jump(void) = delete; + jump(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class open { + private: + bool m_show_help = false; + std::string m_keycode = ""; + + public: + open(void) = delete; + open(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class print { + private: + bool m_show_help = false; + std::string m_db_type = "both"; + std::string m_keycode = ""; + bool m_nul = false; + + public: + print(void) = delete; + print(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class list { + private: + bool m_show_help = false; + std::string m_db_type = "both"; + bool m_nul = false; + + public: + list(void) = delete; + list(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; + + class Edit { + private: + bool m_show_help = false; + + public: + Edit(void) = delete; + Edit(lyra::cli& cli); + PAF_CMD_NORETURN void execute(const lyra::group& g); + }; +} // namespace paf + +#endif /* ifndef HEADER_SCRIPTS_CXX_PAF_CMD_ */ diff --git a/src/cpp/project/pathfinder/src/command.cpp b/src/cpp/project/pathfinder/src/command.cpp deleted file mode 100644 index b341f7a4..00000000 --- a/src/cpp/project/pathfinder/src/command.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include "command.hpp" - -#include -#include - -#include - -#include - -paf::Alias::Alias(lyra::cli& cli) -{ - auto command = lyra::command("alias", [this](const lyra::group& group) { execute(group); }) - .help("Print aliases `eval`able by a argIX-compatible shell.") - .add_argument(lyra::help(m_show_help)) - .optional(); - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Alias::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - std::cout << R"#(m() { eval "$(pathfinder toggle-mark "$@")"; })#" - "\n" - R"#(g() { eval "$(pathfinder jump "$@")"; })#" - "\n" - R"#(e() { eval "$(pathfinder open "$@")"; })#" - << std::endl; - - PAF_COMMAND_EXIT(); -} - -paf::Mark::Mark(lyra::cli& cli) -{ - const auto opt_yes = - lyra::opt(m_yes)["-y"]["--yes"]("don't require manual confirmation for overwriting marks"); - const auto arg_keycode = - lyra::arg(m_keycode, "keycode")("keycode to assign the file to").cardinality(1, 1); - const auto arg_file = lyra::arg(m_file, "file")("file to mark").cardinality(0, 1); - - auto command = lyra::command("mark", [this](const lyra::group& group) { execute(group); }) - .help("Mark the given directory or file.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(opt_yes)) - .add_argument(std::move(arg_keycode)) - .add_argument(std::move(arg_file)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Mark::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::Unmark::Unmark(lyra::cli& cli) -{ - const auto arg_identifier = - lyra::arg(m_identifier, "identifier")("keycode, directory or file to unmark") - .cardinality(1, 1); - - auto command = lyra::command("unmark", [this](const lyra::group& group) { execute(group); }) - .help("Unmark the given keycode, directory or file.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(arg_identifier)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Unmark::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::ToggleMark::ToggleMark(lyra::cli& cli) -{ - const auto opt_yes = - lyra::opt(m_yes)["-y"]["--yes"]("don't require manual confirmation for overwriting marks"); - const auto arg_identifier = - lyra::arg(m_identifier, "identifier")("keycode, directory or file to mark or unmark") - .cardinality(1, 1); - const auto arg_file = - lyra::arg(m_file, "file")("directory or file to mark; if not given, $PWD is used") - .cardinality(0, 1); - - auto command = - lyra::command("toggle-mark", [this](const lyra::group& group) { execute(group); }) - .help("Mark or unmark the given keycode, directory or file.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(opt_yes)) - .add_argument(std::move(arg_identifier)) - .add_argument(std::move(arg_file)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::ToggleMark::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::Jump::Jump(lyra::cli& cli) -{ - const auto arg_keycode = - lyra::arg(m_keycode, "keycode")("keycode to match the directory").cardinality(1, 1); - - auto command = lyra::command("jump", [this](const lyra::group& group) { execute(group); }) - .help("Jump to the directory for the given keycode.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(arg_keycode)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Jump::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::Open::Open(lyra::cli& cli) -{ - const auto arg_keycode = - lyra::arg(m_keycode, "keycode")("keycode to match the file").cardinality(1, 1); - - auto command = lyra::command("open", [this](const lyra::group& group) { execute(group); }) - .help("Open the file for the given keycode.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(arg_keycode)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Open::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::Print::Print(lyra::cli& cli) -{ - const auto opt_db_type = - lyra::opt(m_db_type, "database")["-d"]["--database"]("database to search") - .choices("both", "directory", "file"); - const auto arg_keycode = - lyra::arg(m_keycode, "keycode")("keycode to match the file").cardinality(1, 1); - - auto command = lyra::command("print", [this](const lyra::group& group) { execute(group); }) - .help("Print the value for the given keycode.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(opt_db_type)) - .add_argument(std::move(arg_keycode)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::Print::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} - -paf::List::List(lyra::cli& cli) -{ - const auto opt_db_type = - lyra::opt(m_db_type, "database")["-d"]["--database"]("database to list") - .choices("both", "directory", "file"); - const auto opt_nul = lyra::opt(m_nul)["-0"]["-z"]["--null"]("use NUL as output delimiter"); - - auto command = lyra::command("list", [this](const lyra::group& group) { execute(group); }) - .help("List all marks.") - .add_argument(lyra::help(m_show_help)) - .add_argument(std::move(opt_db_type)) - .add_argument(std::move(opt_nul)) - .optional(); - - cli.add_argument(std::move(command)); -} - -PAF_COMMAND_NORETURN void paf::List::execute(const lyra::group& group) -{ - if (m_show_help) { - std::cout << group << '\n'; - std::exit(EXIT_SUCCESS); - } - - PAF_COMMAND_EXIT(); -} diff --git a/src/cpp/project/pathfinder/src/command.hpp b/src/cpp/project/pathfinder/src/command.hpp deleted file mode 100644 index 881b8b9e..00000000 --- a/src/cpp/project/pathfinder/src/command.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef HEADER_SCRIPTS_CXX_PAF_COMMAND_ -#define HEADER_SCRIPTS_CXX_PAF_COMMAND_ - -#include - -#define PAF_COMMAND_NORETURN -#define PAF_COMMAND_EXIT() \ - do { \ - } while (false) - -namespace paf { - class Alias { - private: - bool m_show_help = false; - - public: - Alias() = delete; - Alias(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Mark { - private: - bool m_show_help = false; - bool m_yes = false; - std::string m_keycode = ""; - std::optional m_file = {}; - - public: - Mark() = delete; - Mark(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Unmark { - private: - bool m_show_help = false; - std::string m_identifier = ""; - - public: - Unmark() = delete; - Unmark(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class ToggleMark { - private: - bool m_show_help = false; - bool m_yes = false; - std::string m_identifier = ""; - std::optional m_file = {}; - - public: - ToggleMark() = delete; - ToggleMark(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Jump { - private: - bool m_show_help = false; - std::string m_keycode = ""; - - public: - Jump() = delete; - Jump(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Open { - private: - bool m_show_help = false; - std::string m_keycode = ""; - - public: - Open() = delete; - Open(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Print { - private: - bool m_show_help = false; - std::string m_db_type = "both"; - std::string m_keycode = ""; - - public: - Print() = delete; - Print(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class List { - private: - bool m_show_help = false; - std::string m_db_type = "both"; - bool m_nul = false; - - public: - List() = delete; - List(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; - - class Edit { - private: - bool m_show_help = false; - - public: - Edit() = delete; - Edit(lyra::cli& cli); - PAF_COMMAND_NORETURN void execute(const lyra::group& g); - }; -} // namespace paf - -#endif /* ifndef HEADER_SCRIPTS_CXX_PAF_COMMAND_ */ diff --git a/src/cpp/project/pathfinder/src/db.cpp b/src/cpp/project/pathfinder/src/db.cpp new file mode 100644 index 00000000..2b742753 --- /dev/null +++ b/src/cpp/project/pathfinder/src/db.cpp @@ -0,0 +1,150 @@ +#include "db.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace fs = std::filesystem; + +namespace paf { + static std::optional> db_dir_vec = std::nullopt, + db_file_vec = std::nullopt; + + static std::string get_db_path(db_type type) + { + static const constexpr char* const config_prefix = "/scripts/pathfinder"; + static std::string prefix = ::confhome_s(config_prefix); + + switch (type) { + case db_type::both: + std::exit(EXIT_FAILURE); + case db_type::directory: + return prefix + "/file_db.bin"; + case db_type::file: + return prefix + "/dir_db.bin"; + default: + HEDLEY_UNREACHABLE(); + } + } + + static void load_db(std::vector& db_vec, db_type type) + { + const auto path = get_db_path(type); + std::ifstream ifs{ path }; + + for (std::string k, v; std::getline(ifs, k, '\0') && std::getline(ifs, v, '\0');) + db_vec.emplace_back(k, v); + } + + static void save_db(const std::vector& db_vec, db_type type) + { + const auto path = get_db_path(type); + const auto tmp_path = path + ".tmp"; + + for (std::ofstream ofs{ tmp_path }; const auto& item : db_vec) + ofs << item.keycode << '\0' << item.path << '\0'; + + fs::rename(tmp_path, path); + } + + db db::get_db(db_type type) + { + switch (type) { + case db_type::both: + std::exit(EXIT_FAILURE); + case db_type::directory: + if (!db_dir_vec) { + db_dir_vec = std::vector{}; + load_db(*db_dir_vec, type); + } + + return db(*db_dir_vec, type); + case db_type::file: + if (!db_file_vec) { + db_file_vec = std::vector{}; + load_db(*db_file_vec, type); + } + + return db(*db_file_vec, type); + default: + HEDLEY_UNREACHABLE(); + } + } + + db::db(std::vector& db_vec, db_type type) : type(type), m_db_vec(db_vec) {} + + db::~db() + { + this->save(); + } + + void db::save(void) + { + if (!cancelled) + save_db(m_db_vec, type); + } + + void db::cancel(void) + { + cancelled = true; + } + + void db::dump(std::string_view sep, std::string_view end) + { + for (const auto& item : m_db_vec) + std::cout << item.keycode << sep << item.path << end; + } + + std::optional db::try_get_mark(const std::string& keycode) + { + for (const auto& item : m_db_vec) { + if (item.keycode == keycode) + return item.path; + } + + return std::nullopt; + } + + void db::add_mark(const std::string& keycode, const std::string& path) + { + m_db_vec.emplace_back(keycode, path); + } + + bool db::try_remove_mark(const std::string& keycode) + { + const auto pre_size = m_db_vec.size(); + std::erase_if(m_db_vec, + [&keycode](const db_item& item) { return item.keycode == keycode; }); + return m_db_vec.size() != pre_size; + } + + bool db::try_remove_mark_at(std::size_t index) + { + if (index >= m_db_vec.size()) + return false; + + m_db_vec.erase(m_db_vec.begin() + index); + return true; + } + + std::optional db::index_of(const std::string& keycode) + { + auto pos = std::find_if(m_db_vec.begin(), m_db_vec.end(), [&keycode](const db_item& item) { + return item.keycode == keycode; + }); + + if (pos != m_db_vec.end()) + return std::distance(m_db_vec.begin(), pos); + + return std::nullopt; + } +} // namespace paf diff --git a/src/cpp/project/pathfinder/src/db.hpp b/src/cpp/project/pathfinder/src/db.hpp new file mode 100644 index 00000000..ebd9b6fb --- /dev/null +++ b/src/cpp/project/pathfinder/src/db.hpp @@ -0,0 +1,56 @@ +#ifndef HEADER_SCRIPTS_CXX_PAF_DB_ +#define HEADER_SCRIPTS_CXX_PAF_DB_ + +#include +#include +#include +#include + +namespace paf { + struct db_item { + public: + std::string keycode; + std::string path; + }; + + enum class db_type { + both, + directory, + file, + }; + + class db { + public: + const db_type type; + + private: + std::vector& m_db_vec; + bool cancelled = false; + + private: + db(void) = delete; + db(const db&) = delete; + db(db&&) = delete; + db& operator=(const db&) = delete; + db& operator=(db&&) = delete; + + db(std::vector& db_vec, db_type type); + + void save(void); + + public: + ~db(); + + static db get_db(db_type type); + + void dump(std::string_view sep, std::string_view end); + std::optional try_get_mark(const std::string& keycode); + void add_mark(const std::string& keycode, const std::string& path); + bool try_remove_mark(const std::string& keycode); + bool try_remove_mark_at(std::size_t index); + std::optional index_of(const std::string& keycode); + void cancel(void); + }; +} // namespace paf + +#endif /* ifndef HEADER_SCRIPTS_CXX_PAF_DB_ */ diff --git a/src/cpp/project/pathfinder/src/main.cpp b/src/cpp/project/pathfinder/src/main.cpp index bd1c775a..15f33c6e 100644 --- a/src/cpp/project/pathfinder/src/main.cpp +++ b/src/cpp/project/pathfinder/src/main.cpp @@ -7,13 +7,13 @@ #include #include -#include "options.hpp" +#include "cli.hpp" DEFINE_EXEC_INFO(); int main(int argc, char* argv[]) { xph::gather_exec_info(argc, argv); - paf::Options options(argc, argv); - return EXIT_SUCCESS; + paf::cli cli(argc, argv); + cli.run(); } diff --git a/src/cpp/project/pathfinder/src/options.hpp b/src/cpp/project/pathfinder/src/options.hpp deleted file mode 100644 index 7ecfc64a..00000000 --- a/src/cpp/project/pathfinder/src/options.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef HEADER_SCRIPTS_CXX_PAF_OPTIONS_ -#define HEADER_SCRIPTS_CXX_PAF_OPTIONS_ - -#include -#include - -#define DECLARE_FIELD(TYPE, NAME, DEFAULT) \ -public: \ - static const constexpr TYPE km_default_##NAME = DEFAULT; \ - \ -private: \ - TYPE m_##NAME = km_default_##NAME; - -namespace paf { - class Options { - DECLARE_FIELD(std::optional, init_dir, {}); - - public: - Options() = delete; - Options(int argc, char** argv); - }; -} // namespace paf - -#endif /* ifndef HEADER_SCRIPTS_CXX_PAF_OPTIONS_ */