From bae67e6851ae158412e5a0eacb503bc014ada9af Mon Sep 17 00:00:00 2001 From: Vyn Date: Thu, 11 Apr 2024 11:42:13 +0200 Subject: [PATCH] Add 'Time' and 'Tags' as proper task's properties, also add raw format handling --- src/Backend.cpp | 16 ++++++++ src/Backend.h | 2 + src/TaskItem.cpp | 5 +++ src/TaskItem.h | 2 + src/core/Mirai.cpp | 17 +++++++-- src/core/Mirai.h | 1 + src/core/TaskItem.cpp | 9 +++++ src/core/TaskItem.h | 9 +++++ src/core/TasksFile.cpp | 30 ++++----------- src/core/TasksFile.h | 7 ++-- src/core/TasksView.cpp | 2 +- src/core/TodoMd.cpp | 77 +++++++++++++++++++++++++++++++------- src/core/TodoMd.h | 4 ++ src/core/using.h | 2 - src/qml/forms/TaskForm.qml | 8 ++-- 15 files changed, 142 insertions(+), 49 deletions(-) diff --git a/src/Backend.cpp b/src/Backend.cpp index 7782e58..59983ad 100644 --- a/src/Backend.cpp +++ b/src/Backend.cpp @@ -5,6 +5,7 @@ */ #include "Backend.h" +#include "core/TodoMd.h" Backend::Backend() { std::cout << "Backend created" << std::endl; @@ -43,6 +44,13 @@ void Backend::addTodo(QString newTodo, QString date) { emit tasksChanged(); } +void Backend::addTodoFromRawFormat(QString text, QString date) { + mirai.addTask(mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString())); + mirai.save(); + rebuildQMLTasksList(); + emit tasksChanged(); +} + void Backend::addTagFilter(QString tag) { view.lock()->addTagFilter(tag.toStdString()); rebuildQMLTasksList(); @@ -61,6 +69,14 @@ void Backend::removeFilters() { emit tasksChanged(); } +void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date) { + QMLTaskItem& taskItem = QMLTasks[todoIndex]; + *(taskItem.taskItem) = mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString()); + mirai.save(); + rebuildQMLTasksList(); + emit tasksChanged(); +} + void Backend::updateTodo(int todoIndex, QString state, QString text, QString date) { QMLTaskItem& taskItem = QMLTasks[todoIndex]; taskItem.taskItem->state = state == "TODO" ? mirai::TODO : mirai::DONE; diff --git a/src/Backend.h b/src/Backend.h index f843b85..84b1148 100644 --- a/src/Backend.h +++ b/src/Backend.h @@ -42,9 +42,11 @@ public: Backend(); Q_INVOKABLE void addTodo(QString newTodo, QString date); + Q_INVOKABLE void addTodoFromRawFormat(QString text, QString date); Q_INVOKABLE void addTagFilter(QString tag); Q_INVOKABLE void removeTagFilter(QString tag); Q_INVOKABLE void removeFilters(); + Q_INVOKABLE void updateTodoFromRawFormat(int todoIndex, QString text, QString date); Q_INVOKABLE void updateTodo(int todoIndex, QString state, QString text, QString date); Q_INVOKABLE void removeTodo(int todoIndex); Q_INVOKABLE void hideCompletedTasks(bool shouldHide); diff --git a/src/TaskItem.cpp b/src/TaskItem.cpp index 36765fc..64c9ccb 100644 --- a/src/TaskItem.cpp +++ b/src/TaskItem.cpp @@ -5,6 +5,11 @@ */ #include "TaskItem.h" +#include "core/TodoMd.h" + +QString QMLTaskItem::getRawFormat() { + return QString::fromStdString(mirai::TodoMdFormat::TaskToString(*taskItem)); +} QString QMLTaskItem::getText() { return QString::fromStdString(taskItem->getText()); diff --git a/src/TaskItem.h b/src/TaskItem.h index 7a9df07..62e40d4 100644 --- a/src/TaskItem.h +++ b/src/TaskItem.h @@ -17,6 +17,7 @@ struct QMLTaskItem { Q_GADGET + Q_PROPERTY(QString rawFormat READ getRawFormat) Q_PROPERTY(QString text READ getText) Q_PROPERTY(QString state READ getState) Q_PROPERTY(QString date READ getDate) @@ -26,6 +27,7 @@ struct QMLTaskItem { public: QString getText(); + QString getRawFormat(); QString getState(); QString getDate(); bool getShouldShowDate(); diff --git a/src/core/Mirai.cpp b/src/core/Mirai.cpp index 0e46b8e..d9a90fc 100644 --- a/src/core/Mirai.cpp +++ b/src/core/Mirai.cpp @@ -5,6 +5,8 @@ */ #include "Mirai.h" +#include "TaskItem.h" +#include namespace mirai { @@ -12,9 +14,11 @@ namespace mirai { auto tasksFile = TodoMdFormat::readFile(path); files->push_back(std::move(tasksFile)); tags.clear(); - for (auto& file : *files) { - for (auto& tag : file.getTags()) { - tags.push_back(tag); + for (auto& task : (*files)[0].getTasks()) { + for (auto& tag : task->getTags()) { + if (std::find(tags.begin(), tags.end(), tag) == tags.end()) { + tags.push_back(tag); + } } } } @@ -25,6 +29,13 @@ namespace mirai { } } + void Mirai::addTask(TaskItem taskItem) { + (*files)[0].addTask(taskItem); + for (auto& view : views) { + view->update(); + } + } + void Mirai::addTask(std::string text, std::string date) { /*std::time_t t = std::time(nullptr);*/ /*std::tm tm = *std::localtime(&t);*/ diff --git a/src/core/Mirai.h b/src/core/Mirai.h index e6d33d5..468db1f 100644 --- a/src/core/Mirai.h +++ b/src/core/Mirai.h @@ -22,6 +22,7 @@ namespace mirai { void loadFile(const std::string& path); void save(); + void addTask(TaskItem taskItem); void addTask(std::string text, std::string date); void removeTask(const TaskItem* taskItem); diff --git a/src/core/TaskItem.cpp b/src/core/TaskItem.cpp index 64301ae..7a1aad1 100644 --- a/src/core/TaskItem.cpp +++ b/src/core/TaskItem.cpp @@ -6,6 +6,7 @@ #include "TaskItem.h" +#include namespace mirai { @@ -28,5 +29,13 @@ namespace mirai { const std::string& TaskItem::getText() const { return text; } const TaskItemState& TaskItem::getState() const { return state; } const std::string& TaskItem::getDate() const { return date; } + const std::string& TaskItem::getStartTime() const { return startTime; } + const std::string& TaskItem::getEndTime() const { return endTime; } + const Tags& TaskItem::getTags() const { return tags; } bool TaskItem::hasDate() const { return isDate(date); } + + + bool TaskItem::hasTag(const std::string& tag) const { + return std::find(tags.begin(), tags.end(), tag) != tags.end(); + } } diff --git a/src/core/TaskItem.h b/src/core/TaskItem.h index ba77748..31c6d80 100644 --- a/src/core/TaskItem.h +++ b/src/core/TaskItem.h @@ -7,6 +7,7 @@ #ifndef MIRAI_TASKITEM_H #define MIRAI_TASKITEM_H +#include "using.h" #include "utils.h" #include #include @@ -35,14 +36,22 @@ namespace mirai { void setText(const std::string& text); + const std::string& getLine() const; const std::string& getText() const; const TaskItemState& getState() const; const std::string& getDate() const; + const std::string& getStartTime() const; + const std::string& getEndTime() const; + const Tags& getTags() const; + bool hasTag(const std::string& tag) const; bool hasDate() const; std::string text; TaskItemState state; std::string date; + std::string startTime; + std::string endTime; + Tags tags; }; } diff --git a/src/core/TasksFile.cpp b/src/core/TasksFile.cpp index f40a695..645710a 100644 --- a/src/core/TasksFile.cpp +++ b/src/core/TasksFile.cpp @@ -5,13 +5,13 @@ */ #include "TasksFile.h" -#include "using.h" +#include "TaskItem.h" +#include namespace mirai { TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path) { for (const auto& task : params.tasks) { - processTaskMetaData(task); tasks.push_back(std::make_unique(task)); } sortByDate(); @@ -20,7 +20,11 @@ namespace mirai { const std::string& TasksFile::getName() const { return name; } const std::string& TasksFile::getPath() const { return path; } TasksPtrs& TasksFile::getTasks() { return tasks; } - Tags& TasksFile::getTags() { return tags; } + + void TasksFile::addTask(TaskItem taskItem) { + tasks.push_back(std::make_unique(taskItem)); + sortByDate(); + } void TasksFile::addTask(std::string text, std::string date) { auto newTask = std::make_unique(TaskItem{ @@ -38,26 +42,6 @@ namespace mirai { })); } - void TasksFile::processTaskMetaData(const TaskItem& task) { - const std::string metaDataSeparator = " -- "; - auto separatorIndex = task.text.find(metaDataSeparator); - if (separatorIndex == std::string::npos) { - return; - } - - auto metaData = task.text.substr(separatorIndex + metaDataSeparator.length()); - std::regex regex("(#[a-zA-Z0-1]+)"); - std::smatch matches; - - while (std::regex_search(metaData, matches, regex)) - { - if (std::find(tags.begin(), tags.end(), matches[0]) == tags.end()) { - tags.push_back(matches[0]); - } - metaData = matches.suffix(); - } - } - void TasksFile::sortByDate() { std::sort(tasks.begin(), tasks.end(), [] (const std::unique_ptr& t1, const std::unique_ptr& t2) { if (t1->hasDate() && !t2->hasDate()) { diff --git a/src/core/TasksFile.h b/src/core/TasksFile.h index b1a0ce5..1dcdd6c 100644 --- a/src/core/TasksFile.h +++ b/src/core/TasksFile.h @@ -25,6 +25,9 @@ namespace mirai { + + using TasksPtrs = std::vector>; + struct TasksFileConstructor { const std::string& name; const std::string& path; @@ -38,20 +41,18 @@ namespace mirai { TasksFile(TasksFileConstructor params); void addTask(std::string text, std::string date); + void addTask(TaskItem TaskItem); void removeTask(const TaskItem* taskToRemove); - void processTaskMetaData(const TaskItem& task); void sortByDate(); const std::string& getName() const; const std::string& getPath() const; TasksPtrs& getTasks(); - Tags& getTags(); private: std::string name; std::string path; TasksPtrs tasks; - Tags tags; }; } diff --git a/src/core/TasksView.cpp b/src/core/TasksView.cpp index dbc9701..980ce1e 100644 --- a/src/core/TasksView.cpp +++ b/src/core/TasksView.cpp @@ -25,7 +25,7 @@ namespace mirai { for (auto& task : file.getTasks()) { if (tagsFilter.size() != 0 && std::find_if(tagsFilter.begin(), tagsFilter.end(), [&](const std::string& tag) { - return task->getText().find(tag) != std::string::npos; + return task->hasTag(tag); }) == tagsFilter.end()) continue; tasksToShow.push_back(task.get()); diff --git a/src/core/TodoMd.cpp b/src/core/TodoMd.cpp index 573da02..ee80b25 100644 --- a/src/core/TodoMd.cpp +++ b/src/core/TodoMd.cpp @@ -5,6 +5,7 @@ */ #include "TodoMd.h" +#include "TaskItem.h" namespace mirai { @@ -27,13 +28,8 @@ namespace mirai { if (line.substr(0, 3) == "## ") { currentDate = line.substr(3); } else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") { - std::string text = line.substr(5); - trim(text); - TaskItem taskItem = { - .text = text, - .state = line.substr(0, 5) == "- [X]" ? DONE : TODO, - .date = currentDate - }; + TaskItem taskItem = StringToTask(line, currentDate); + taskItem.setDate(currentDate); taskItems.push_back(taskItem); } } @@ -46,6 +42,22 @@ namespace mirai { return tasks; } + 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 (std::find(tags.begin(), tags.end(), matches[0]) == tags.end()) { + tags.push_back(matches[0]); + } + metadata = matches.suffix(); + } + return tags; + } + void TodoMdFormat::writeFile(TasksFile& tasks) { std::ofstream file(tasks.getPath()); if (!file.is_open()) { @@ -59,12 +71,7 @@ namespace mirai { currentDate = task->date; file << "\n## " << (task->date != "" ? task->date : "No date") << "\n\n"; } - if (task->getState() == TODO) { - file << "- [ ] "; - } else if (task->getState() == DONE) { - file << "- [X] "; - } - file << task->getText() << '\n'; + file << TaskToString(*task) << '\n'; } file.close(); } @@ -74,5 +81,49 @@ namespace mirai { return ""; return (field + " "); } + + TaskItem 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 = matches[5]; + trim(text); + + TaskItem taskItem = { + .text = text, + .state = str.substr(0, 5) == "- [X]" ? DONE : TODO, + .date = date, + .startTime = matches[3], + .endTime = matches[4], + .tags = extractTagsFromMetadata(matches[7]) + }; + return taskItem; + } + + 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; + } + } + if (task.getStartTime() != "" && task.getEndTime() != "") { + str = task.getStartTime() + "-" + task.getEndTime() + " > " + str; + } + str = (task.getState() == DONE ? "- [X] " : "- [ ] ") + str; + return str; + } } diff --git a/src/core/TodoMd.h b/src/core/TodoMd.h index 2d26d42..696939f 100644 --- a/src/core/TodoMd.h +++ b/src/core/TodoMd.h @@ -28,10 +28,14 @@ namespace mirai { static TasksFile readFile(const std::string& path); static void writeFile(TasksFile& tasks); + static std::string TaskToString(const TaskItem& task); + static TaskItem StringToTask(const std::string& str, const std::string& date); + private: static std::string fieldWithSpace(const std::string& field); static TaskItem parseLine(const std::string& line); + static Tags extractTagsFromMetadata(std::string metadata); }; } diff --git a/src/core/using.h b/src/core/using.h index 6053f17..b778971 100644 --- a/src/core/using.h +++ b/src/core/using.h @@ -7,14 +7,12 @@ #ifndef MIRAI_USING_H #define MIRAI_USING_H -#include "TaskItem.h" #include #include #include namespace mirai { using Tags = std::vector; - using TasksPtrs = std::vector>; } #endif diff --git a/src/qml/forms/TaskForm.qml b/src/qml/forms/TaskForm.qml index 9dc483e..e5206f7 100644 --- a/src/qml/forms/TaskForm.qml +++ b/src/qml/forms/TaskForm.qml @@ -18,7 +18,7 @@ ColumnLayout { signal confirmed onTaskToEditChanged: { - newTodoContent.text = taskToEdit?.text ?? "" + newTodoContent.text = taskToEdit?.rawFormat ?? "- [ ] " newTodoDate.text = taskToEdit?.date ?? "" } @@ -30,16 +30,16 @@ ColumnLayout { id: newTodoContent Layout.fillWidth: true placeholderText: "Enter your new task..." - text: taskToEdit?.text ?? "" + text: taskToEdit?.rawFormat ?? "" Keys.onReturnPressed: { if (newTodoContent.text == "") { return } if (taskToEdit && taskToEditIndex !== undefined) { - backend.updateTodo(taskToEditIndex, taskToEdit.state, newTodoContent.text, newTodoDate.text) + backend.updateTodoFromRawFormat(taskToEditIndex, newTodoContent.text, newTodoDate.text) } else { - backend.addTodo(newTodoContent.text, newTodoDate.text) + backend.addTodoFromRawFormat(newTodoContent.text, newTodoDate.text) } form.confirmed() }