Skip to content

Commit

Permalink
feat: calendar annual events
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolaDucak committed Nov 14, 2024
1 parent b25695c commit 3bd969a
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 91 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,30 @@ ssh-pub-key-path=/Users/me/.ssh/id_rsa.pub # required
repo-root=/Users/me/.caps-log/clog-entries/
remote-name=something # 'origin' is the default
main-branch-name=main # 'master' is the default

```

__Annual events__
Caps-log can also be configured to display annual events in the calendar. They
will be highlighted with green color and number of days since or until the event (if the
total number of days is less that `recent-events-window` days) will be displayed in the
small box to the left of the calendar.
```ini
[calendar-events]
recent-events-window=60

[calendar-events.birthadays.0]
name=Joe
date=18.07
[calendar-events.birthadays.1]
name=Jane
date=03.07.
[calendar-events.holidays.0]
name=New Years eve
date=01.01.
[calendar-events.holidays.1]
name=New Year
date=31.12.
```

## Building & Installing
Expand Down
26 changes: 11 additions & 15 deletions source/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <algorithm>
#include <fmt/format.h>
#include <iostream>
#include <memory>

namespace caps_log {
Expand Down Expand Up @@ -73,7 +72,6 @@ void ViewDataUpdater::updateViewAfterDataChange(const std::string &previewString
{
const auto oldSections = m_view->sectionMenuItems().getKeys();
if (oldSections.empty()) { // initial start
std::cout << "initial start" << m_data.tagsPerSection.begin()->first << std::endl;
m_view->sectionMenuItems() = makeSectionMenuItems(m_data.getAllSections());
m_view->setSelectedSection(kSelectNoneMenuEntryText);
} else {
Expand Down Expand Up @@ -130,10 +128,7 @@ void ViewDataUpdater::updateViewAfterDataChange(const std::string &previewString
}

void App::updateDataAndViewAfterLogChange(const std::chrono::year_month_day &dateOfChangedLog) {
std::cout << "PRE " << m_data.tagsPerSection.size() << std::endl;
std::cout << "date " << date::formatToString(dateOfChangedLog) << std::endl;
m_data.collect(m_repo, dateOfChangedLog, m_skipFirstLine);
std::cout << "POST " << m_data.tagsPerSection.size() << std::endl;
m_data.collect(m_repo, dateOfChangedLog, m_config.skipFirstLine);
std::string previewString = "";
if (dateOfChangedLog == m_view->getFocusedDate()) {
if (auto log = m_repo->read(m_view->getFocusedDate())) {
Expand All @@ -144,14 +139,14 @@ void App::updateDataAndViewAfterLogChange(const std::chrono::year_month_day &dat
}

App::App(std::shared_ptr<AnnualViewBase> view, std::shared_ptr<LogRepositoryBase> repo,
std::shared_ptr<EditorBase> editor, bool skipFirstLine, std::optional<GitRepo> gitRepo,
std::chrono::year year)
: m_displayedYear{year}, m_view{std::move(view)}, m_repo{std::move(repo)},
std::shared_ptr<EditorBase> editor, std::optional<GitRepo> gitRepo, AppConfig config)
: m_config{std::move(config)}, m_view{std::move(view)}, m_repo{std::move(repo)},
m_editor{std::move(editor)},
m_data{AnnualLogData::collect(m_repo, m_displayedYear, skipFirstLine)},
m_skipFirstLine{skipFirstLine}, m_viewDataUpdater{m_view, m_data} {
m_data{AnnualLogData::collect(m_repo, m_config.currentYear, m_config.skipFirstLine)},
m_viewDataUpdater{m_view, m_data} {
m_view->setInputHandler(this);
m_view->setDatesWithLogs(&m_data.datesWithLogs);
m_view->setEventDates(&m_config.events);
updateDataAndViewAfterLogChange(m_view->getFocusedDate());

if (gitRepo) {
Expand Down Expand Up @@ -218,7 +213,8 @@ void App::handleUiStarted() {
m_view->loadingScreen("Pulling from remote...");
m_gitRepo->pull([this] {
m_view->post([this] {
m_data = AnnualLogData::collect(m_repo, m_displayedYear, m_skipFirstLine);
m_data =
AnnualLogData::collect(m_repo, m_config.currentYear, m_config.skipFirstLine);
updateDataAndViewAfterLogChange(m_view->getFocusedDate());
m_view->loadingScreenOff();
});
Expand Down Expand Up @@ -252,9 +248,9 @@ void App::deleteFocusedLog() {
}

void App::handleDisplayedYearChange(int diff) {
m_displayedYear = std::chrono::year{static_cast<int>(m_displayedYear) + diff};
m_data = AnnualLogData::collect(m_repo, m_displayedYear, m_skipFirstLine);
m_view->showCalendarForYear(m_displayedYear);
m_config.currentYear = std::chrono::year{static_cast<int>(m_config.currentYear) + diff};
m_data = AnnualLogData::collect(m_repo, m_config.currentYear, m_config.skipFirstLine);
m_view->showCalendarForYear(m_config.currentYear);
m_view->setHighlightedDates(nullptr);
updateDataAndViewAfterLogChange(m_view->getFocusedDate());
}
Expand Down
19 changes: 11 additions & 8 deletions source/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
#include "view/input_handler.hpp"

#include <cassert>
#include <chrono>
#include <cstddef>
#include <iostream>
#include <memory>
#include <vector>

Expand Down Expand Up @@ -76,10 +76,8 @@ class ViewDataUpdater final {
// prepend select none
menuItems.push_back(kSelectNoneMenuEntryText);
keys.push_back(kSelectNoneMenuEntryText);
std::cout << "section: " << sections.size() << std::endl;

for (const auto &section : sections) {
std::cout << "section: " << section << std::endl;
if (section == AnnualLogData::kAnySection) {
continue;
}
Expand All @@ -91,24 +89,29 @@ class ViewDataUpdater final {
}
};

struct AppConfig {
public:
bool skipFirstLine;
std::chrono::year currentYear;
CalendarEvents events;
};

/**
* The main application class that orchestrates the view, the data and the editor.
*/
class App final : public InputHandlerBase {
std::chrono::year m_displayedYear;
AppConfig m_config;
std::shared_ptr<AnnualViewBase> m_view;
std::shared_ptr<LogRepositoryBase> m_repo;
std::shared_ptr<EditorBase> m_editor;
AnnualLogData m_data;
bool m_skipFirstLine;
std::optional<AsyncGitRepo> m_gitRepo;
ViewDataUpdater m_viewDataUpdater;

public:
App(std::shared_ptr<AnnualViewBase> view, std::shared_ptr<LogRepositoryBase> repo,
std::shared_ptr<EditorBase> editor, bool skipFirstLine = true,
std::optional<GitRepo> gitRepo = std::nullopt,
std::chrono::year year = date::getToday().year());
std::shared_ptr<EditorBase> editor, std::optional<GitRepo> gitRepo = std::nullopt,
AppConfig config = {});

void run();
bool handleInputEvent(const UIEvent &event) override;
Expand Down
105 changes: 76 additions & 29 deletions source/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,70 @@ const bool Config::kDefaultIgnoreFirstLineWhenParsingSections = true;

namespace {

std::optional<std::chrono::month_day> parseDate(const std::string &date_str) {
int day, month;
char dot1, dot2;

std::istringstream iss(date_str);
if (iss >> std::setw(2) >> day >> dot1 >> std::setw(2) >> month >> dot2 && dot1 == '.' &&
dot2 == '.' && day >= 1 && day <= 31 && month >= 1 && month <= 12) {
return std::chrono::month_day{std::chrono::month(month), std::chrono::day(day)};
}
return std::nullopt; // Return empty optional if parsing fails
}

view::CalendarEvents parseCalendarEvents(boost::property_tree::ptree &pt) {
// CalendarEvents structure to store all events
view::CalendarEvents calendar_events;

// Iterate over each section in the property tree
for (const auto &section : pt) {
const std::string &key = section.first; // e.g., "calendar-events.birthdays.0"
const boost::property_tree::ptree &data = section.second;

// Check if the key starts with "calendar-events."
if (key.rfind("calendar-events.", 0) != 0) {
// Not a calendar event section, skip it
continue;
}

// Split the key to extract event type
std::stringstream ss(key);
std::string item;
std::vector<std::string> tokens;
while (std::getline(ss, item, '.')) {
tokens.push_back(item);
}

if (tokens.size() != 3) {
throw std::runtime_error("Invalid calendar event key: " + key);
}

// Extract the category (e.g., "birthdays", "holidays", "anniversaries")
std::string category = tokens[1];

// Get the name and date from the section
std::string name = data.get<std::string>("name");
std::string date_str = data.get<std::string>("date");

// Parse the date string into day and month
int day, month;
if (sscanf(date_str.c_str(), "%02d.%02d.", &day, &month) != 2) {
throw std::runtime_error("Invalid date format: " + date_str);
}

// Create a month_day object
std::chrono::month_day date{std::chrono::month{static_cast<unsigned>(month)},
std::chrono::day{static_cast<unsigned>(day)}};

// Create a CalendarEvent and insert it into the map
view::CalendarEvent event{name, date};
calendar_events[category].insert(event);
}

return calendar_events;
}

std::filesystem::path removeTrailingSlash(const std::filesystem::path &path) {
std::string pathStr = path.string();
if (!pathStr.empty() && pathStr.back() == '/') {
Expand All @@ -33,8 +97,8 @@ void setIfValue(boost::property_tree::ptree &ptree, const std::string &key, U &d
}

bool isParentOf(const std::filesystem::path &parent, const std::filesystem::path &child) {
auto nParent = removeTrailingSlash(parent.lexically_normal());
auto nChild = removeTrailingSlash(child.lexically_normal());
const auto nParent = removeTrailingSlash(parent.lexically_normal());
const auto nChild = removeTrailingSlash(child.lexically_normal());
auto parentIter = nParent.begin();
auto childIter = nChild.begin();

Expand All @@ -49,7 +113,6 @@ bool isParentOf(const std::filesystem::path &parent, const std::filesystem::path
return parentIter == nParent.end() && childIter != nChild.end();
}

// Your existing function with modifications to use Boost Program Options' variables_map
void applyCommandlineOverrides(Config &config,
const boost::program_options::variables_map &commandLineArgs) {

Expand All @@ -70,34 +133,18 @@ void applyCommandlineOverrides(Config &config,
}
}

// Refactored function that modifies the passed config reference
void applyConfigFileOverrides(Config &config, boost::property_tree::ptree &ptree) {
// Update the config object with values from the file if they are present
auto logDirPathOpt = ptree.get_optional<std::string>("log-dir-path");
if (logDirPathOpt) {
config.logDirPath = logDirPathOpt.get();
}

auto logFilenameFormatOpt = ptree.get_optional<std::string>("log-filename-format");
if (logFilenameFormatOpt) {
config.logFilenameFormat = logFilenameFormatOpt.get();
}

auto sundayStartOpt = ptree.get_optional<bool>("sunday-start");
if (sundayStartOpt) {
config.sundayStart = sundayStartOpt.get();
}

auto ignoreFirstLineWhenParsingSectionsOpt = ptree.get_optional<bool>("first-line-section");
if (ignoreFirstLineWhenParsingSectionsOpt) {
config.ignoreFirstLineWhenParsingSections = ignoreFirstLineWhenParsingSectionsOpt.get();
}

auto passwordOpt = ptree.get_optional<std::string>("password");
if (passwordOpt) {
config.password = passwordOpt.get();
}
setIfValue<std::string>(ptree, "log-dir-path", config.logDirPath);
setIfValue<std::string>(ptree, "log-filename-format", config.logFilenameFormat);
setIfValue<bool>(ptree, "sunday-start", config.sundayStart);
setIfValue<bool>(ptree, "first-line-section", config.ignoreFirstLineWhenParsingSections);
setIfValue<std::string>(ptree, "password", config.password);
setIfValue<unsigned>(ptree, "calendar-events.recent-events-window", config.recentEventsWindow);

// Parse and update calendar events
config.calendarEvents = parseCalendarEvents(ptree);
}

void applyGitConfigIfEnabled(Config &config, boost::property_tree::ptree &ptree) {
if (ptree.get_optional<bool>("git.enable-git-log-repo").value_or(false)) {
GitRepoConfig gitConf;
Expand Down
4 changes: 4 additions & 0 deletions source/config.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "view/annual_view_base.hpp"
#include <filesystem>
#include <functional>
#include <memory>
Expand Down Expand Up @@ -35,6 +36,9 @@ struct Config {
std::string password;

std::optional<GitRepoConfig> repoConfig;

unsigned recentEventsWindow = 14;
view::CalendarEvents calendarEvents;
};

// NOLINTNEXTLINE
Expand Down
3 changes: 0 additions & 3 deletions source/log/annual_log_data.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "annual_log_data.hpp"
#include "utils/date.hpp"
#include <iostream>

namespace caps_log::log {

Expand All @@ -18,12 +17,10 @@ void collectEmpty(AnnualLogData &data, const std::shared_ptr<LogRepositoryBase>
}
input->parse(skipFirstLine);

std::cout << "COLLECT " << utils::date::formatToString(date) << std::endl;
data.datesWithLogs.insert(monthDay(date));

const auto monthDayDate = monthDay(date);
for (const auto &[section, tags] : input->getTagsPerSection()) {
std::cout << "COLLECT section: " << section << std::endl;
data.tagsPerSection[section][AnnualLogData::kAnyTag].insert(monthDayDate);
data.tagsPerSection[AnnualLogData::kAnySection][AnnualLogData::kAnyTag].insert(
monthDayDate);
Expand Down
15 changes: 12 additions & 3 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include "view/annual_view.hpp"
#include <boost/program_options.hpp>

auto md(unsigned char month, unsigned int day) {
return std::chrono::month_day{std::chrono::month{month}, std::chrono::day{day}};
}

std::string promptPassword(const caps_log::Config &config) {
using namespace ftxui;
auto screen = ScreenInteractive::Fullscreen();
Expand Down Expand Up @@ -55,7 +59,8 @@ auto makeCapsLog(const caps_log::Config &conf) {
using namespace caps_log;
const auto password = getPasswordFromTUIIfNeededAndNotProvided(conf);
auto pathProvider = log::LocalFSLogFilePathProvider{conf.logDirPath, conf.logFilenameFormat};
auto view = std::make_shared<view::AnnualView>(utils::date::getToday(), conf.sundayStart);
auto view = std::make_shared<view::AnnualView>(utils::date::getToday(), conf.sundayStart,
conf.recentEventsWindow);
auto repo = std::make_shared<log::LocalLogRepository>(pathProvider, password);
auto editor = [&]() -> std::shared_ptr<editor::EditorBase> {
const auto envEditor = std::getenv("EDITOR");
Expand All @@ -77,8 +82,12 @@ auto makeCapsLog(const caps_log::Config &conf) {
return std::nullopt;
}();

return caps_log::App{std::move(view), std::move(repo), std::move(editor),
conf.ignoreFirstLineWhenParsingSections, std::move(gitRepo)};
AppConfig appConf;
appConf.skipFirstLine = conf.ignoreFirstLineWhenParsingSections;
appConf.events = conf.calendarEvents;
appConf.currentYear = utils::date::getToday().year();
return caps_log::App{std::move(view), std::move(repo), std::move(editor), std::move(gitRepo),
std::move(appConf)};
}

void migrateToNewRepo() {
Expand Down
Loading

0 comments on commit 3bd969a

Please sign in to comment.