Support multiple files

This commit is contained in:
Vyn 2024-04-14 14:11:41 +02:00
parent f8f49233dc
commit 689eea07a7
22 changed files with 528 additions and 131 deletions

View file

@ -6,7 +6,14 @@
#include "Mirai.h"
#include "TaskItem.h"
#include "cpp-utils/vector.h"
#include "core/TasksFile.h"
#include "utils.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <optional>
#include <ostream>
#include <vector>
namespace mirai
{
@ -16,10 +23,12 @@ void Mirai::loadFile(const std::string &path)
auto tasksFile = TodoMdFormat::readFile(path);
files->push_back(std::move(tasksFile));
tags.clear();
for (auto &task : (*files)[0].getTasks()) {
for (auto &tag : task->getTags()) {
if (!vectorUtils::contains(tags, tag)) {
tags.push_back(tag);
for (auto &file : *files) {
for (auto &task : file->getTasks()) {
for (auto &tag : task->getTags()) {
if (!vectorUtils::contains(tags, tag)) {
tags.push_back(tag);
}
}
}
}
@ -28,13 +37,16 @@ void Mirai::loadFile(const std::string &path)
void Mirai::save()
{
for (auto &file : *files) {
TodoMdFormat::writeFile(file);
if (file->isDirty()) {
TodoMdFormat::writeFile(*file);
file->setDirty(false);
}
}
}
void Mirai::addTask(TaskItem taskItem)
void Mirai::addTask(TaskItemData taskItem)
{
(*files)[0].addTask(taskItem);
(*files)[0]->addTask(taskItem);
for (auto &view : views) {
view->update();
}
@ -46,7 +58,7 @@ void Mirai::addTask(std::string text, std::string date)
/*std::tm tm = *std::localtime(&t);*/
/*std::stringstream ssCreationDate;*/
/*ssCreationDate << std::put_time(&tm, "%Y-%m-%d");*/
(*files)[0].addTask(text, date);
(*files)[0]->addTask(text, date);
for (auto &view : views) {
view->update();
}
@ -54,12 +66,31 @@ void Mirai::addTask(std::string text, std::string date)
void Mirai::removeTask(const TaskItem *taskItem)
{
(*files)[0].removeTask(taskItem);
for (auto &file : *files) {
file->removeTask(taskItem);
}
for (auto &view : views) {
view->update();
}
}
std::vector<std::unique_ptr<TasksFile>> &Mirai::getFiles()
{
return *files.get();
}
std::optional<std::reference_wrapper<TasksFile>> Mirai::getFileByPath(const std::string &path)
{
auto fileIterator = std::ranges::find_if(*files, [&](const std::unique_ptr<TasksFile> &file) {
return file->getPath() == path;
});
if (fileIterator == files->end()) {
return std::nullopt;
}
return *(fileIterator->get());
}
std::weak_ptr<TasksView> Mirai::getTasks()
{
auto view = std::make_shared<TasksView>(files);
@ -71,4 +102,5 @@ const std::vector<std::string> &Mirai::getTags()
{
return tags;
}
} // namespace mirai

View file

@ -12,7 +12,10 @@
#include "TasksView.h"
#include "TodoMd.h"
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <string>
namespace mirai
{
@ -23,10 +26,13 @@ class Mirai
public:
void loadFile(const std::string &path);
void save();
void addTask(TaskItem taskItem);
void addTask(TaskItemData taskItem);
void addTask(std::string text, std::string date);
void removeTask(const TaskItem *taskItem);
void addFile(TasksFileConfig config);
std::optional<std::reference_wrapper<TasksFile>> getFileByPath(const std::string &path);
std::vector<std::unique_ptr<TasksFile>> &getFiles();
std::weak_ptr<TasksView> getTasks();
const std::vector<std::string> &getTags();
@ -34,7 +40,8 @@ class Mirai
// The `TasksFile`s are shared to the views, their lifetime can outlive
// this (Mirai) object
// because we can't control if the caller will keep the main object alive.
std::shared_ptr<std::vector<TasksFile>> files = std::make_shared<std::vector<TasksFile>>();
std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files =
std::make_shared<std::vector<std::unique_ptr<TasksFile>>>();
// We keep a vector of shared_ptr because we need the ref counting mechanism to know
// if we have to remove the view (not used) or tell the view to update() on

View file

@ -5,68 +5,86 @@
*/
#include "TaskItem.h"
#include "cpp-utils/vector.h"
#include "TasksFile.h"
#include "core/utils.h"
#include <iostream>
namespace mirai
{
TaskItem::TaskItem(TasksFile *parent, TaskItemData data) : parent(parent), data(data)
{
}
void TaskItem::markAsDone()
{
state = DONE;
data.state = DONE;
onChange();
}
void TaskItem::markAsUndone()
{
state = TODO;
data.state = TODO;
onChange();
}
void TaskItem::setDate(const std::string &date)
{
this->date = date;
this->data.date = date;
onChange();
}
void TaskItem::setText(const std::string &text)
{
this->text = text;
this->data.text = text;
onChange();
}
const std::string &TaskItem::getText() const
{
return text;
return data.text;
}
const TaskItemState &TaskItem::getState() const
{
return state;
return data.state;
}
const std::string &TaskItem::getDate() const
{
return date;
return data.date;
}
const std::string &TaskItem::getStartTime() const
{
return startTime;
return data.startTime;
}
const std::string &TaskItem::getEndTime() const
{
return endTime;
return data.endTime;
}
const Tags &TaskItem::getTags() const
{
return tags;
return data.tags;
}
bool TaskItem::hasDate() const
{
return isDate(date);
return isDate(data.date);
}
bool TaskItem::hasTag(const std::string &tag) const
{
return vectorUtils::contains(tags, tag);
return vectorUtils::contains(data.tags, tag);
}
void TaskItem::onChange()
{
if (parent) {
parent->setDirty(true);
}
}
} // namespace mirai

View file

@ -8,24 +8,39 @@
#define MIRAI_TASKITEM_H
#include "using.h"
#include "utils.h"
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <functional>
#include <string>
#include <vector>
namespace mirai
{
enum TaskItemState { TODO, DONE };
struct TaskItem {
struct TaskItemData {
std::string text;
TaskItemState state;
std::string date;
std::string startTime;
std::string endTime;
Tags tags;
};
class TasksFile;
class TaskItem
{
friend TasksFile;
private:
public:
TaskItem(TasksFile *parent, const TaskItemData data);
TaskItem &operator=(const TaskItemData &newData)
{
data = newData;
onChange();
return *this;
};
void markAsDone();
void markAsUndone();
@ -43,12 +58,11 @@ struct TaskItem {
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;
private:
void onChange();
TasksFile *parent;
TaskItemData data;
};
} // namespace mirai

View file

@ -6,7 +6,11 @@
#include "TasksFile.h"
#include "TaskItem.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
namespace mirai
{
@ -14,11 +18,28 @@ namespace mirai
TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path)
{
for (const auto &task : params.tasks) {
tasks.push_back(std::make_unique<TaskItem>(task));
auto taskPtr = std::make_unique<TaskItem>(task);
tasks.push_back(std::move(taskPtr));
}
sortByDate();
}
void TasksFile::onTaskChanged(TaskItem &taskItem)
{
std::cout << "THIS PTR: " << this << std::endl;
setDirty(true);
}
void TasksFile::setDirty(bool shouldBeDirty)
{
isDirty_ = shouldBeDirty;
}
bool TasksFile::isDirty() const
{
return isDirty_;
}
const std::string &TasksFile::getName() const
{
return name;
@ -34,38 +55,47 @@ TasksPtrs &TasksFile::getTasks()
return tasks;
}
void TasksFile::addTask(TaskItem taskItem)
void TasksFile::addTask(TaskItemData taskItem)
{
tasks.push_back(std::make_unique<TaskItem>(taskItem));
tasks.push_back(std::make_unique<TaskItem>(this, taskItem));
setDirty(true);
sortByDate();
}
void TasksFile::addTask(std::string text, std::string date)
{
auto newTask = std::make_unique<TaskItem>(
TaskItem{.text = text, .state = TODO, .date = date == "" ? "No date" : date});
TaskItem{this, {.text = text, .state = TODO, .date = date == "" ? "No date" : date}}
);
tasks.push_back(std::move(newTask));
sortByDate();
}
void TasksFile::removeTask(const TaskItem *taskToRemove)
{
tasks.erase(std::remove_if(tasks.begin(), tasks.end(),
[&](const std::unique_ptr<TaskItem> &taskInFilter) {
return taskInFilter.get() == taskToRemove;
}));
auto findFunction = [&](const std::unique_ptr<TaskItem> &taskInFilter) {
return taskInFilter.get() == taskToRemove;
};
auto taskToDelete = std::remove_if(tasks.begin(), tasks.end(), findFunction);
if (taskToDelete == tasks.end()) {
return;
}
tasks.erase(taskToDelete);
setDirty(true);
}
void TasksFile::sortByDate()
{
std::sort(tasks.begin(), tasks.end(),
[](const std::unique_ptr<TaskItem> &t1, const std::unique_ptr<TaskItem> &t2) {
if (t1->hasDate() && !t2->hasDate()) {
return true;
} else if (!t1->hasDate() && t2->hasDate()) {
return false;
}
return t1->date < t2->date;
});
std::sort(
tasks.begin(), tasks.end(),
[](const std::unique_ptr<TaskItem> &t1, const std::unique_ptr<TaskItem> &t2) {
if (t1->hasDate() && !t2->hasDate()) {
return true;
} else if (!t1->hasDate() && t2->hasDate()) {
return false;
}
return t1->getDate() < t2->getDate();
}
);
}
} // namespace mirai

View file

@ -8,18 +8,7 @@
#define MIRAI_TASKSFILE_H
#include "TaskItem.h"
#include "using.h"
#include "utils.h"
#include <algorithm>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <ostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
@ -28,6 +17,11 @@ namespace mirai
using TasksPtrs = std::vector<std::unique_ptr<TaskItem>>;
struct TasksFileConfig {
std::string name;
std::string path;
};
struct TasksFileConstructor {
const std::string &name;
const std::string &path;
@ -38,10 +32,11 @@ class TasksFile
{
public:
TasksFile(const TasksFile &) = delete;
TasksFile &operator=(const TasksFile &) = delete;
TasksFile(TasksFileConstructor params);
void addTask(std::string text, std::string date);
void addTask(TaskItem TaskItem);
void addTask(TaskItemData TaskItem);
void removeTask(const TaskItem *taskToRemove);
void sortByDate();
@ -49,10 +44,16 @@ class TasksFile
const std::string &getPath() const;
TasksPtrs &getTasks();
void setDirty(bool shouldBeDirty);
bool isDirty() const;
private:
void onTaskChanged(TaskItem &);
std::string name;
std::string path;
TasksPtrs tasks;
bool isDirty_ = false;
};
} // namespace mirai

View file

@ -5,17 +5,18 @@
*/
#include "TasksView.h"
#include "cpp-utils/vector.h"
#include "utils.h"
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
namespace mirai
{
TasksView::TasksView(std::shared_ptr<std::vector<TasksFile>> files) : files(files)
TasksView::TasksView(std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files) : files(files)
{
update();
}
@ -34,7 +35,10 @@ void TasksView::update()
{
tasksToShow.clear();
for (auto &file : *files) {
for (auto &task : file.getTasks()) {
if (filesFilter.size() > 0 && !vectorUtils::contains(filesFilter, file->getName())) {
continue;
}
for (auto &task : file->getTasks()) {
if (tagsFilter.size() > 0 && !vectorUtils::containsAll(tagsFilter, task->getTags())) {
continue;
}
@ -51,8 +55,28 @@ void TasksView::addTagFilter(const std::string &tag)
void TasksView::removeTagFilter(const std::string &tag)
{
tagsFilter.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(),
[&](const auto &tagInFilter) { return tag == tagInFilter; }));
tagsFilter
.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), [&](const auto &tagInFilter) {
return tag == tagInFilter;
}));
update();
}
void TasksView::addFileFilter(const std::string &fileName)
{
filesFilter.push_back(fileName);
update();
}
void TasksView::removeFileFilter(const std::string &fileName)
{
filesFilter.erase(std::remove_if(
filesFilter.begin(), filesFilter.end(),
[&](const auto &fileInFilter) {
return fileName == fileInFilter;
}
));
update();
}
@ -66,4 +90,9 @@ const std::vector<std::string> &TasksView::getActiveTagsFilter()
{
return tagsFilter;
}
const std::vector<std::string> &TasksView::getActiveFilesFilter()
{
return filesFilter;
}
} // namespace mirai

View file

@ -19,7 +19,7 @@ class TasksView
{
public:
TasksView(std::shared_ptr<std::vector<TasksFile>> files);
TasksView(std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files);
TaskItem &operator[](int index);
@ -27,13 +27,17 @@ class TasksView
void update();
void addTagFilter(const std::string &tag);
void removeTagFilter(const std::string &tag);
void addFileFilter(const std::string &fileName);
void removeFileFilter(const std::string &fileName);
void removeFilters();
const Tags &getActiveTagsFilter();
const std::vector<std::string> &getActiveFilesFilter();
private:
std::shared_ptr<std::vector<TasksFile>> files;
std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files;
std::vector<TaskItem *> tasksToShow;
Tags tagsFilter;
std::vector<std::string> filesFilter;
};
} // namespace mirai
#endif

View file

@ -6,12 +6,16 @@
#include "TodoMd.h"
#include "TaskItem.h"
#include "cpp-utils/vector.h"
#include "core/TasksFile.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <memory>
namespace mirai
{
TasksFile TodoMdFormat::readFile(const std::string &path)
std::unique_ptr<TasksFile> TodoMdFormat::readFile(const std::string &path)
{
std::ifstream file(path);
if (!file.is_open()) {
@ -27,18 +31,22 @@ TasksFile TodoMdFormat::readFile(const std::string &path)
std::string name = line.substr(2);
std::string currentDate = "";
auto tasksFile = std::make_unique<TasksFile>(
TasksFileConstructor{.name = name, .path = path, .tasks = taskItems}
);
while (std::getline(file, line)) {
if (line.substr(0, 3) == "## ") {
currentDate = line.substr(3);
} else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") {
TaskItem taskItem = StringToTask(line, currentDate);
taskItem.setDate(currentDate);
taskItems.push_back(taskItem);
TaskItemData taskItemData = StringToTask(line, currentDate);
taskItemData.date = currentDate;
tasksFile->addTask(taskItemData);
}
}
file.close();
TasksFile tasks({.name = name, .path = path, .tasks = taskItems});
return tasks;
tasksFile->setDirty(false);
return tasksFile;
}
Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata)
@ -67,9 +75,9 @@ void TodoMdFormat::writeFile(TasksFile &tasks)
file << "# " << tasks.getName() << "\n";
for (const auto &task : tasks.getTasks()) {
if (currentDate != task->date) {
currentDate = task->date;
file << "\n## " << (task->date != "" ? task->date : "No date") << "\n\n";
if (currentDate != task->getDate()) {
currentDate = task->getDate();
file << "\n## " << (task->getDate() != "" ? task->getDate() : "No date") << "\n\n";
}
file << TaskToString(*task) << '\n';
}
@ -78,16 +86,17 @@ void TodoMdFormat::writeFile(TasksFile &tasks)
std::string TodoMdFormat::fieldWithSpace(const std::string &field)
{
if (field.length() == 0)
if (field.length() == 0) {
return "";
}
return (field + " ");
}
TaskItem TodoMdFormat::StringToTask(const std::string &str, const std::string &date)
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 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;*/
@ -102,12 +111,14 @@ TaskItem TodoMdFormat::StringToTask(const std::string &str, const std::string &d
std::string text = stringUtils::trim(matches[5]);
TaskItem taskItem = {.text = text,
.state = str.substr(0, 5) == "- [X]" ? DONE : TODO,
.date = date,
.startTime = matches[3],
.endTime = matches[4],
.tags = extractTagsFromMetadata(matches[7])};
TaskItemData 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;
}

View file

@ -9,16 +9,7 @@
#include "TaskItem.h"
#include "TasksFile.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <ostream>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai
{
@ -26,11 +17,11 @@ namespace mirai
class TodoMdFormat
{
public:
static TasksFile readFile(const std::string &path);
static std::unique_ptr<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);
static TaskItemData StringToTask(const std::string &str, const std::string &date);
private:
static std::string fieldWithSpace(const std::string &field);