Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/daylio_to_md/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def main():
Librarian(
cli_options.filepath,
cli_options.destination,
force_overwrite=cli_options.force,
entries_from_builder=file_template
).output_all()

Expand Down
1 change: 0 additions & 1 deletion src/daylio_to_md/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def parse_console(args: List[Any]) -> argparse.Namespace:
type=str,
help="Path to folder to output finished files into."
)
# TODO: Force-argument does nothing yet.
main_settings.add_argument(
"--force",
choices=["accept", "refuse"],
Expand Down
21 changes: 20 additions & 1 deletion src/daylio_to_md/entry/mood.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import logging
from typing import List

from daylio_to_md import errors
from daylio_to_md import errors, utils
from daylio_to_md.utils import JsonLoader

DEFAULT_DAYLIO_MOOD_GROUPS = "rad good neutral bad awful"

Expand Down Expand Up @@ -186,3 +187,21 @@ def get_moods(self) -> dict[str, str]:
:return: ``dict[str, str]`` where keys are moods and their values are mood groups they belong to
"""
return self.__known_moods


def create_from(filepath: str = None) -> 'Moodverse':
"""
Overwrite the standard mood-set with a custom one. Mood-sets are used in colour-coding each dated entry.

:param filepath: path to the .JSON file with a non-standard mood set.
Should have five keys: ``rad``, ``good``, ``neutral``, ``bad`` and ``awful``.
Each of those keys should hold an array of any number of strings indicating various moods.
**Example**: ``[{"good": ["good"]},...]``
:returns: reference to the :class:`Moodverse` object
"""
try:
with JsonLoader().load(filepath) as file:
return Moodverse(file)
except utils.CouldNotLoadFileError:
# oh, no! anyway... just load up a default moodverse then
return Moodverse()
22 changes: 19 additions & 3 deletions src/daylio_to_md/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,32 @@ def emit(self, record):
self.stream.write(f"{csi}{color}m{formatted_msg}{csi}m\n")


class DuplicateFilter(logging.Filter):
# Class-level attribute to store logged messages
logged_messages = set()

def filter(self, record):
# Create a unique identifier for the log message
current_log = (record.module, record.levelno, record.msg)

if current_log in DuplicateFilter.logged_messages:
return False # Filter out the message if it's a duplicate

DuplicateFilter.logged_messages.add(current_log)
return True # Allow the log through if it's not a duplicate


# Create a console handler for the root logger
# noinspection SpellCheckingInspection
console_log_handler = ColorHandler(sys.stdout)
console_log_handler.addFilter(DuplicateFilter())
# interesting discussion on why setLevel on both handler AND logger: https://stackoverflow.com/a/17668861/8527654
console_log_handler.setLevel(logging.INFO)

console_log_handler.setLevel(logging.INFO)
# noinspection SpellCheckingInspection
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
console_log_handler.setFormatter(formatter)
formatter = logging.Formatter("%(name)s\t%(levelname)s\t%(message)s")

console_log_handler.setFormatter(formatter)
# Add the handlers to the root logger
logging.getLogger().addHandler(console_log_handler)
logging.getLogger().setLevel(logging.INFO)
Expand Down
24 changes: 11 additions & 13 deletions src/daylio_to_md/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
all notes -> _NOTES WRITTEN ON A PARTICULAR DATE_ -> a particular note
"""
from __future__ import annotations

import os
import pathlib
from dataclasses import dataclass, field

import io
Expand All @@ -18,6 +21,7 @@
from daylio_to_md.config import DEFAULTS
from daylio_to_md.journal_entry import Entry
from daylio_to_md.entry.mood import Moodverse
from daylio_to_md.utils import IncompleteDataRow

# TODO: dependency_injector lib
# TODO: fixtures
Expand All @@ -35,14 +39,6 @@ def __init__(self, key, date):
super().__init__()


class IncompleteDataRow(Exception):
"""Passed a row of data from CSV file that does not have all required fields."""


class TriedCreatingDuplicateDatedEntryError(Exception):
"""Tried to create object of :class:`DatedEntry` that would be a duplicate of one that already exists."""


class ErrorMsg(errors.ErrorMsgBase):
CSV_ROW_INCOMPLETE_DATA = "Passed .csv contains rows with invalid data. Tried to parse {} as date."

Expand Down Expand Up @@ -131,9 +127,8 @@ def create_entry(self, line: dict[str, str]) -> None:
Create :class:`Entry` object with the specified parameters.
Field with date is ignored, because :class:`Entry` inherits this field from parent :class:`EntriesFrom`.
The assumption here is that .create_entry() should never be called for an entry from a different date.
:raises TriedCreatingDuplicateDatedEntryError: if it would result in making a duplicate :class:`DatedEntry`
:raises IncompleteDataRow: if ``line`` does not have ``time mood`` keys at the very least, or either is empty
:raises ValueError: re-raises ValueError from :class:`DatedEntry`
:raises ValueError: re-raises ValueError from :class:`Entry`
:param line: a dictionary of strings. Required keys: mood, activities, note_title & note.
"""
# TODO: test case this
Expand All @@ -142,10 +137,10 @@ def create_entry(self, line: dict[str, str]) -> None:
try:
line[key]
except KeyError as err:
raise IncompleteDataRow(key) from err
raise IncompleteDataRow(key, '') from err
# is it empty then, maybe?
if not line[key]:
raise IncompleteDataRow(key)
raise IncompleteDataRow(key, '')

# TODO: date mismatch - this object has a different date than the full_date in line

Expand Down Expand Up @@ -246,12 +241,15 @@ def known_entries(self):
return self.__known_entries

@property
def date(self):
def date(self) -> datetime.date:
"""
:return: :class:`datetime.date` object that identifies this instance of :class:`EntriesFrom`.
"""
return self.uid

def path(self, *dest: typing.Union[os.PathLike, str]) -> pathlib.Path:
return pathlib.Path(*dest, str(self.date.year), str(self.date.month), f"{self.date}.md")

def __eq__(self, other) -> bool:
"""Enables direct comparison with a :class:`datetime.date` or a date string. """
# TODO: check if all known entries match as well, this date check is too superfluous right now
Expand Down
8 changes: 5 additions & 3 deletions src/daylio_to_md/journal_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,13 @@ def output(self, stream: io.IOBase | typing.IO) -> int:
# e.g. "## great | 11:00 AM | Oh my, what a night!"
# header_multiplier is an int that multiplies the # to create headers in markdown
header_elements = [
self.__header_multiplier * "#" + ' ' + self.__mood,
self.__prefix,
self.__mood,
self.time.strftime("%H:%M"),
self.__title
self.__title,
self.__suffix
]
header = ' | '.join([el for el in header_elements if el is not None])
header = self.__header_multiplier * "#" + ' ' + ' | '.join([el for el in header_elements if el])
chars_written += stream.write(header)
# ACTIVITIES
# e.g. "bicycle skating pool swimming"
Expand Down
Loading