/* * Mirai. Copyright (C) 2024 Vyn * This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only) * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt */ #include "TodoMd.h" #include "TaskItem.h" #include "cpp-utils/debug.h" #include "utils.h" #include #include #include #include #include #include #include #include namespace mirai { Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata) { Tags tags; std::regex regex("(#([a-zA-Z0-1]+))"); std::smatch matches; while (std::regex_search(metadata, matches, regex)) { if (!vectorUtils::contains(tags, matches[2].str())) { tags.push_back(matches[2]); } metadata = matches.suffix(); } return tags; } std::string TodoMdFormat::fieldWithSpace(const std::string &field) { if (field.length() == 0) { return ""; } return (field + " "); } TaskItemData TodoMdFormat::stringToTask(const std::string &str, const std::string &date) { std::smatch matches; std::regex regex("- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?" ); std::regex_match(str, matches, regex); /*std::cout << "line " << str << std::endl;*/ /*std::cout << "M 0 " << matches[0] << std::endl;*/ /*std::cout << "M 1 " << matches[1] << std::endl;*/ /*std::cout << "M 2 " << matches[2] << std::endl;*/ /*std::cout << "M 3 " << matches[3] << std::endl;*/ /*std::cout << "M 4 " << matches[4] << std::endl;*/ /*std::cout << "M 5 " << matches[5] << std::endl;*/ /*std::cout << "M 6 " << matches[6] << std::endl;*/ /*std::cout << "M 7 " << matches[7] << std::endl;*/ std::string text = stringUtils::trim(matches[5]); TaskItemData taskItem{ .text = text, .state = str.substr(0, 5) == "- [X]" ? DONE : TODO, .tags = extractTagsFromMetadata(matches[7]) }; return taskItem; } Time stringToTime(const std::string &str) { if (str.length() != 5) { throw std::runtime_error("Str time length must be 5 (got '" + str + "')"); } if (str[2] != 'h') { throw std::runtime_error("Malformated time range"); } Time time{}; std::from_chars(str.data(), str.data() + 2, time.hour); std::from_chars(str.data() + 3, str.data() + 5, time.minute); return time; } EventData TodoMdFormat::stringToEvent(const std::string &str, const std::string &dateString) { std::smatch matches; std::regex regex("> (([0-9]{2}h[0-9]{2})-([0-9]{2}h[0-9]{2}) )(.*?)( -- (.*))?"); std::regex_match(str, matches, regex); /*std::cout << "line " << str << std::endl;*/ /*std::cout << "M 0 " << matches[0] << std::endl;*/ /*std::cout << "M 1 " << matches[1] << std::endl;*/ /*std::cout << "M 2 " << matches[2] << std::endl;*/ /*std::cout << "M 3 " << matches[3] << std::endl;*/ /*std::cout << "M 4 " << matches[4] << std::endl;*/ /*std::cout << "M 5 " << matches[5] << std::endl;*/ /*std::cout << "M 6 " << matches[6] << std::endl;*/ std::string text = stringUtils::trim(matches[4]); auto date = stringToDate(dateString); if (!date.has_value()) { throw std::runtime_error("Malformated date"); } EventData eventData{ .description = text, .date = date.value(), .startTime = stringToTime(matches[2]), .endTime = stringToTime(matches[3]), .tags = extractTagsFromMetadata(matches[6]) }; return eventData; } std::string TodoMdFormat::taskToString(const TaskItem &task) { std::string str = task.getText(); if (task.getTags().size() > 0) { str += " --"; for (const std::string &tag : task.getTags()) { str += " #" + tag; } } str = (task.getState() == DONE ? "- [X] " : "- [ ] ") + str; return str; } std::string TodoMdFormat::stringify(const std::string &name, const std::vector> &days) { std::string result = "# " + name + "\n\n"; std::string currentDate = ""; for (const auto &day : days) { auto &date = day->getDate(); result += "## " + std::format("{:02d}-{:02d}-{:02d}", date.year, date.month, date.day) + "\n\n"; for (const auto &event : *(day->events())) { auto &start = event->getStartTime(); auto &end = event->getEndTime(); result += "> " + std::format( "{:02d}h{:02d}-{:02d}h{:02d} {}", start.hour, start.minute, end.hour, end.minute, event->getText() ) + "\n"; for (const auto &task : *(event->tasks())) { result += taskToString(*task) + '\n'; } result += '\n'; } for (const auto &task : *(day->tasks())) { result += taskToString(*task) + '\n'; } result += '\n'; } return result; }; MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content) { cpputils::debug::Timer readMdFormatDuration; std::vector taskItems; std::string line; std::stringstream contentStream(content); if (!std::getline(contentStream, line) || line.substr(0, 2) != "# ") { std::cerr << "Couldn't find the task list name" << std::endl; } std::string name = line.substr(2); std::string currentDateString = ""; std::vector daysData; mirai::DayData *currentDay = nullptr; mirai::EventData *currentEvent = nullptr; cpputils::debug::Timer stringToTaskDuration; stringToTaskDuration.reset(); cpputils::debug::Timer gelinesDuration; while (std::getline(contentStream, line)) { if (std::string_view{line.data(), 3} == "## ") { currentDateString = line.substr(3); auto dateOpt = mirai::stringToDate(currentDateString); if (!dateOpt.has_value()) { throw std::runtime_error("Malformated date"); } daysData.push_back({.date = dateOpt.value()}); currentDay = &daysData.back(); } else if (line.starts_with("> ")) { auto event = stringToEvent(line, currentDateString); if (currentDay) { currentDay->events.push_back(event); currentEvent = ¤tDay->events.back(); } } else if (line.starts_with("- [ ]") || line.starts_with("- [X]")) { stringToTaskDuration.start(); TaskItemData taskItemData = stringToTask(line, currentDateString); stringToTaskDuration.stop(); if (currentEvent) { currentEvent->tasks.push_back(taskItemData); } else if (currentDay) { currentDay->tasks.push_back(taskItemData); } } else if (cpputils::string::trim(line) == "") { currentEvent = nullptr; } } gelinesDuration.printTimeElapsed("getlinesDuration"); stringToTaskDuration.printTimeElapsed("stringToTaskDuration"); readMdFormatDuration.printTimeElapsed("Reading MD File duration"); return {.name = name, .days = daysData}; } } // namespace mirai