Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type hints #1614

Merged
merged 5 commits into from
Nov 5, 2022
Merged
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
12 changes: 6 additions & 6 deletions jrnl/DayOneJournal.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, **kwargs):
self.can_be_encrypted = False
super().__init__(**kwargs)

def open(self):
def open(self) -> "DayOne":
filenames = []
for root, dirnames, f in os.walk(self.config["journal"]):
for filename in fnmatch.filter(f, "*.doentry"):
Expand Down Expand Up @@ -113,7 +113,7 @@ def open(self):
self.sort()
return self

def write(self):
def write(self) -> None:
"""Writes only the entries that have been modified into plist files."""
for entry in self.entries:
if entry.modified:
Expand Down Expand Up @@ -177,20 +177,20 @@ def write(self):
)
os.remove(filename)

def editable_str(self):
def editable_str(self) -> str:
"""Turns the journal into a string of entries that can be edited
manually and later be parsed with eslf.parse_editable_str."""
return "\n".join([f"{str(e)}\n# {e.uuid}\n" for e in self.entries])

def _update_old_entry(self, entry, new_entry):
def _update_old_entry(self, entry: Entry, new_entry: Entry) -> None:
for attr in ("title", "body", "date"):
old_attr = getattr(entry, attr)
new_attr = getattr(new_entry, attr)
if old_attr != new_attr:
entry.modified = True
setattr(entry, attr, new_attr)

def _get_and_remove_uuid_from_entry(self, entry):
def _get_and_remove_uuid_from_entry(self, entry: Entry) -> Entry:
uuid_regex = "^ *?# ([a-zA-Z0-9]+) *?$"
m = re.search(uuid_regex, entry.body, re.MULTILINE)
entry.uuid = m.group(1) if m else None
Expand All @@ -201,7 +201,7 @@ def _get_and_remove_uuid_from_entry(self, entry):

return entry

def parse_editable_str(self, edited):
def parse_editable_str(self, edited: str) -> None:
"""Parses the output of self.editable_str and updates its entries."""
# Method: create a new list of entries from the edited text, then match
# UUIDs of the new entries against self.entries, updating the entries
Expand Down
24 changes: 17 additions & 7 deletions jrnl/Entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@
import logging
import os
import re
from typing import TYPE_CHECKING

import ansiwrap

from .color import colorize
from .color import highlight_tags_with_background_color

if TYPE_CHECKING:
from .Journal import Journal


class Entry:
def __init__(self, journal, date=None, text="", starred=False):
def __init__(
self,
journal: "Journal",
date: datetime.datetime | None = None,
text: str = "",
starred: bool = False,
):
self.journal = journal # Reference to journal mainly to access its config
self.date = date or datetime.datetime.now()
self.text = text
Expand All @@ -24,7 +34,7 @@ def __init__(self, journal, date=None, text="", starred=False):
self.modified = False

@property
def fulltext(self):
def fulltext(self) -> str:
return self.title + " " + self.body

def _parse_text(self):
Expand Down Expand Up @@ -68,11 +78,11 @@ def tags(self, x):
self._tags = x

@staticmethod
def tag_regex(tagsymbols):
def tag_regex(tagsymbols: str) -> re.Pattern:
pattern = rf"(?<!\S)([{tagsymbols}][-+*#/\w]+)"
return re.compile(pattern)

def _parse_tags(self):
def _parse_tags(self) -> set[str]:
tagsymbols = self.journal.config["tagsymbols"]
return {
tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)
Expand All @@ -90,7 +100,7 @@ def __str__(self):
body=self.body.rstrip("\n "),
)

def pprint(self, short=False):
def pprint(self, short: bool = False) -> str:
"""Returns a pretty-printed version of the entry.
If short is true, only print the title."""
# Handle indentation
Expand Down Expand Up @@ -197,7 +207,7 @@ def __repr__(self):
def __hash__(self):
return hash(self.__repr__())

def __eq__(self, other):
def __eq__(self, other: "Entry"):
if (
not isinstance(other, Entry)
or self.title.strip() != other.title.strip()
Expand Down Expand Up @@ -230,7 +240,7 @@ def __ne__(self, other):
SENTENCE_SPLITTER_ONLY_NEWLINE = re.compile("\n")


def split_title(text):
def split_title(text: str) -> tuple[str, str]:
"""Splits the first sentence off from a text."""
sep = SENTENCE_SPLITTER_ONLY_NEWLINE.search(text.lstrip())
if not sep:
Expand Down
18 changes: 11 additions & 7 deletions jrnl/FolderJournal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import codecs
import fnmatch
import os
from typing import TYPE_CHECKING

from jrnl import Journal
from jrnl import time

if TYPE_CHECKING:
from jrnl.Entry import Entry

def get_files(journal_config):

def get_files(journal_config: str) -> list[str]:
"""Searches through sub directories starting with journal_config and find all text files"""
filenames = []
for root, dirnames, f in os.walk(journal_config):
Expand All @@ -21,13 +25,13 @@ def get_files(journal_config):
class Folder(Journal.Journal):
"""A Journal handling multiple files in a folder"""

def __init__(self, name="default", **kwargs):
def __init__(self, name: str = "default", **kwargs):
self.entries = []
self._diff_entry_dates = []
self.can_be_encrypted = False
super().__init__(name, **kwargs)

def open(self):
def open(self) -> "Folder":
filenames = []
self.entries = []
filenames = get_files(self.config["journal"])
Expand All @@ -38,7 +42,7 @@ def open(self):
self.sort()
return self

def write(self):
def write(self) -> None:
"""Writes only the entries that have been modified into proper files."""
# Create a list of dates of modified entries. Start with diff_entry_dates
modified_dates = self._diff_entry_dates
Expand Down Expand Up @@ -81,13 +85,13 @@ def write(self):
if os.stat(filename).st_size <= 0:
os.remove(filename)

def delete_entries(self, entries_to_delete):
def delete_entries(self, entries_to_delete: list["Entry"]) -> None:
"""Deletes specific entries from a journal."""
for entry in entries_to_delete:
self.entries.remove(entry)
self._diff_entry_dates.append(entry.date)

def change_date_entries(self, date):
def change_date_entries(self, date: str) -> None:
"""Changes entry dates to given date."""

date = time.parse(date)
Expand All @@ -98,7 +102,7 @@ def change_date_entries(self, date):
self._diff_entry_dates.append(entry.date)
entry.date = date

def parse_editable_str(self, edited):
def parse_editable_str(self, edited: str) -> None:
"""Parses the output of self.editable_str and updates its entries."""
mod_entries = self._parse(edited)
diff_entries = set(self.entries) - set(mod_entries)
Expand Down
4 changes: 2 additions & 2 deletions jrnl/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
class WrappingFormatter(argparse.RawTextHelpFormatter):
"""Used in help screen"""

def _split_lines(self, text, width):
def _split_lines(self, text: str, width: int) -> list[str]:
text = text.split("\n\n")
text = map(lambda t: self._whitespace_matcher.sub(" ", t).strip(), text)
text = map(lambda t: textwrap.wrap(t, width=56), text)
text = [item for sublist in text for item in sublist]
return text


def parse_args(args=[]):
def parse_args(args: list[str] = []) -> argparse.Namespace:
"""
Argument parsing that is doable before the config is available.
Everything else goes into "text" for later parsing.
Expand Down
4 changes: 2 additions & 2 deletions jrnl/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from jrnl.output import print_msg


def configure_logger(debug=False):
def configure_logger(debug: bool = False) -> None:
if not debug:
logging.disable()
return
Expand All @@ -31,7 +31,7 @@ def configure_logger(debug=False):
logging.getLogger("keyring.backend").setLevel(logging.ERROR)


def cli(manual_args=None):
def cli(manual_args: list[str] | None = None) -> int:
try:
if manual_args is None:
manual_args = sys.argv[1:]
Expand Down
14 changes: 10 additions & 4 deletions jrnl/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
import re
from string import punctuation
from string import whitespace
from typing import TYPE_CHECKING

import colorama

from jrnl.os_compat import on_windows

if TYPE_CHECKING:
from jrnl.Entry import Entry

if on_windows():
colorama.init()


def colorize(string, color, bold=False):
def colorize(string: str, color: str, bold: bool = False) -> str:
"""Returns the string colored with colorama.Fore.color. If the color set by
the user is "NONE" or the color doesn't exist in the colorama.Fore attributes,
it returns the string without any modification."""
Expand All @@ -26,7 +30,9 @@ def colorize(string, color, bold=False):
return colorama.Style.BRIGHT + color_escape + string + colorama.Style.RESET_ALL


def highlight_tags_with_background_color(entry, text, color, is_title=False):
def highlight_tags_with_background_color(
entry: "Entry", text: str, color: str, is_title: bool = False
) -> str:
"""
Takes a string and colorizes the tags in it based upon the config value for
color.tags, while colorizing the rest of the text based on `color`.
Expand All @@ -45,9 +51,9 @@ def colorized_text_generator(fragments):
:returns [(colorized_str, original_str)]"""
for part in fragments:
if part and part[0] not in config["tagsymbols"]:
yield (colorize(part, color, bold=is_title), part)
yield colorize(part, color, bold=is_title), part
elif part:
yield (colorize(part, config["colors"]["tags"], bold=True), part)
yield colorize(part, config["colors"]["tags"], bold=True), part

config = entry.journal.config
if config["highlight"]: # highlight tags
Expand Down
23 changes: 13 additions & 10 deletions jrnl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import argparse
import logging
import os
from typing import Any
from typing import Callable

import colorama
Expand Down Expand Up @@ -57,7 +58,7 @@ def make_yaml_valid_dict(input: list) -> dict:
return runtime_modifications


def save_config(config, alt_config_path=None):
def save_config(config: dict, alt_config_path: str | None = None) -> None:
"""Supply alt_config_path if using an alternate config through --config-file."""
config["version"] = __version__

Expand All @@ -72,7 +73,7 @@ def save_config(config, alt_config_path=None):
yaml.dump(config, f)


def get_config_path():
def get_config_path() -> str:
try:
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
except FileExistsError:
Expand All @@ -91,7 +92,7 @@ def get_config_path():
return os.path.join(config_directory_path or home_dir(), DEFAULT_CONFIG_NAME)


def get_default_config():
def get_default_config() -> dict[str, Any]:
return {
"version": __version__,
"journals": {"default": {"journal": get_default_journal_path()}},
Expand All @@ -114,12 +115,12 @@ def get_default_config():
}


def get_default_journal_path():
def get_default_journal_path() -> str:
journal_data_path = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or home_dir()
return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME)


def scope_config(config, journal_name):
def scope_config(config: dict, journal_name: str) -> dict:
if journal_name not in config["journals"]:
return config
config = config.copy()
Expand All @@ -139,7 +140,7 @@ def scope_config(config, journal_name):
return config


def verify_config_colors(config):
def verify_config_colors(config: dict) -> bool:
"""
Ensures the keys set for colors are valid colorama.Fore attributes, or "None"
:return: True if all keys are set correctly, False otherwise
Expand All @@ -164,7 +165,7 @@ def verify_config_colors(config):
return all_valid_colors


def load_config(config_path):
def load_config(config_path: str) -> dict:
"""Tries to load a config file from YAML."""
try:
with open(config_path, encoding=YAML_FILE_ENCODING) as f:
Expand All @@ -187,13 +188,15 @@ def load_config(config_path):
return yaml.load(f)


def is_config_json(config_path):
def is_config_json(config_path: str) -> bool:
with open(config_path, "r", encoding="utf-8") as f:
config_file = f.read()
return config_file.strip().startswith("{")


def update_config(config, new_config, scope, force_local=False):
def update_config(
config: dict, new_config: dict, scope: str | None, force_local: bool = False
) -> None:
"""Updates a config dict with new values - either global if scope is None
or config['journals'][scope] is just a string pointing to a journal file,
or within the scope"""
Expand All @@ -206,7 +209,7 @@ def update_config(config, new_config, scope, force_local=False):
config.update(new_config)


def get_journal_name(args, config):
def get_journal_name(args: argparse.Namespace, config: dict) -> argparse.Namespace:
args.journal_name = DEFAULT_JOURNAL_KEY

# The first arg might be a journal name
Expand Down
4 changes: 2 additions & 2 deletions jrnl/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from jrnl.output import print_msg


def get_text_from_editor(config, template=""):
def get_text_from_editor(config: dict, template: str = "") -> str:
suffix = ".jrnl"
if config["template"]:
template_filename = Path(config["template"]).name
Expand Down Expand Up @@ -50,7 +50,7 @@ def get_text_from_editor(config, template=""):
return raw


def get_text_from_stdin():
def get_text_from_stdin() -> str:
print_msg(
Message(
MsgText.WritingEntryStart,
Expand Down
Loading