Add type hints (#1614)

* Add type hints

* Fix linters

* Add remaining type hints

* Fix type-checking linter

* Update jrnl/DayOneJournal.py

Co-authored-by: Jonathan Wren <jonathan@nowandwren.com>
This commit is contained in:
outa 2022-11-05 23:29:50 +01:00 committed by GitHub
parent c1eb0c54a3
commit 30b41fdb88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 219 additions and 120 deletions

View file

@ -38,7 +38,7 @@ class DayOne(Journal.Journal):
self.can_be_encrypted = False self.can_be_encrypted = False
super().__init__(**kwargs) super().__init__(**kwargs)
def open(self): def open(self) -> "DayOne":
filenames = [] filenames = []
for root, dirnames, f in os.walk(self.config["journal"]): for root, dirnames, f in os.walk(self.config["journal"]):
for filename in fnmatch.filter(f, "*.doentry"): for filename in fnmatch.filter(f, "*.doentry"):
@ -113,7 +113,7 @@ class DayOne(Journal.Journal):
self.sort() self.sort()
return self return self
def write(self): def write(self) -> None:
"""Writes only the entries that have been modified into plist files.""" """Writes only the entries that have been modified into plist files."""
for entry in self.entries: for entry in self.entries:
if entry.modified: if entry.modified:
@ -177,12 +177,12 @@ class DayOne(Journal.Journal):
) )
os.remove(filename) os.remove(filename)
def editable_str(self): def editable_str(self) -> str:
"""Turns the journal into a string of entries that can be edited """Turns the journal into a string of entries that can be edited
manually and later be parsed with eslf.parse_editable_str.""" 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]) 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"): for attr in ("title", "body", "date"):
old_attr = getattr(entry, attr) old_attr = getattr(entry, attr)
new_attr = getattr(new_entry, attr) new_attr = getattr(new_entry, attr)
@ -190,7 +190,7 @@ class DayOne(Journal.Journal):
entry.modified = True entry.modified = True
setattr(entry, attr, new_attr) 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]+) *?$" uuid_regex = "^ *?# ([a-zA-Z0-9]+) *?$"
m = re.search(uuid_regex, entry.body, re.MULTILINE) m = re.search(uuid_regex, entry.body, re.MULTILINE)
entry.uuid = m.group(1) if m else None entry.uuid = m.group(1) if m else None
@ -201,7 +201,7 @@ class DayOne(Journal.Journal):
return 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.""" """Parses the output of self.editable_str and updates its entries."""
# Method: create a new list of entries from the edited text, then match # Method: create a new list of entries from the edited text, then match
# UUIDs of the new entries against self.entries, updating the entries # UUIDs of the new entries against self.entries, updating the entries

View file

@ -5,15 +5,25 @@ import datetime
import logging import logging
import os import os
import re import re
from typing import TYPE_CHECKING
import ansiwrap import ansiwrap
from .color import colorize from .color import colorize
from .color import highlight_tags_with_background_color from .color import highlight_tags_with_background_color
if TYPE_CHECKING:
from .Journal import Journal
class Entry: 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.journal = journal # Reference to journal mainly to access its config
self.date = date or datetime.datetime.now() self.date = date or datetime.datetime.now()
self.text = text self.text = text
@ -24,7 +34,7 @@ class Entry:
self.modified = False self.modified = False
@property @property
def fulltext(self): def fulltext(self) -> str:
return self.title + " " + self.body return self.title + " " + self.body
def _parse_text(self): def _parse_text(self):
@ -68,11 +78,11 @@ class Entry:
self._tags = x self._tags = x
@staticmethod @staticmethod
def tag_regex(tagsymbols): def tag_regex(tagsymbols: str) -> re.Pattern:
pattern = rf"(?<!\S)([{tagsymbols}][-+*#/\w]+)" pattern = rf"(?<!\S)([{tagsymbols}][-+*#/\w]+)"
return re.compile(pattern) return re.compile(pattern)
def _parse_tags(self): def _parse_tags(self) -> set[str]:
tagsymbols = self.journal.config["tagsymbols"] tagsymbols = self.journal.config["tagsymbols"]
return { return {
tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text) tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)
@ -90,7 +100,7 @@ class Entry:
body=self.body.rstrip("\n "), 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. """Returns a pretty-printed version of the entry.
If short is true, only print the title.""" If short is true, only print the title."""
# Handle indentation # Handle indentation
@ -197,7 +207,7 @@ class Entry:
def __hash__(self): def __hash__(self):
return hash(self.__repr__()) return hash(self.__repr__())
def __eq__(self, other): def __eq__(self, other: "Entry"):
if ( if (
not isinstance(other, Entry) not isinstance(other, Entry)
or self.title.strip() != other.title.strip() or self.title.strip() != other.title.strip()
@ -230,7 +240,7 @@ SENTENCE_SPLITTER = re.compile(
SENTENCE_SPLITTER_ONLY_NEWLINE = re.compile("\n") 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.""" """Splits the first sentence off from a text."""
sep = SENTENCE_SPLITTER_ONLY_NEWLINE.search(text.lstrip()) sep = SENTENCE_SPLITTER_ONLY_NEWLINE.search(text.lstrip())
if not sep: if not sep:

View file

@ -4,12 +4,16 @@
import codecs import codecs
import fnmatch import fnmatch
import os import os
from typing import TYPE_CHECKING
from jrnl import Journal from jrnl import Journal
from jrnl import time 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""" """Searches through sub directories starting with journal_config and find all text files"""
filenames = [] filenames = []
for root, dirnames, f in os.walk(journal_config): for root, dirnames, f in os.walk(journal_config):
@ -21,13 +25,13 @@ def get_files(journal_config):
class Folder(Journal.Journal): class Folder(Journal.Journal):
"""A Journal handling multiple files in a folder""" """A Journal handling multiple files in a folder"""
def __init__(self, name="default", **kwargs): def __init__(self, name: str = "default", **kwargs):
self.entries = [] self.entries = []
self._diff_entry_dates = [] self._diff_entry_dates = []
self.can_be_encrypted = False self.can_be_encrypted = False
super().__init__(name, **kwargs) super().__init__(name, **kwargs)
def open(self): def open(self) -> "Folder":
filenames = [] filenames = []
self.entries = [] self.entries = []
filenames = get_files(self.config["journal"]) filenames = get_files(self.config["journal"])
@ -38,7 +42,7 @@ class Folder(Journal.Journal):
self.sort() self.sort()
return self return self
def write(self): def write(self) -> None:
"""Writes only the entries that have been modified into proper files.""" """Writes only the entries that have been modified into proper files."""
# Create a list of dates of modified entries. Start with diff_entry_dates # Create a list of dates of modified entries. Start with diff_entry_dates
modified_dates = self._diff_entry_dates modified_dates = self._diff_entry_dates
@ -81,13 +85,13 @@ class Folder(Journal.Journal):
if os.stat(filename).st_size <= 0: if os.stat(filename).st_size <= 0:
os.remove(filename) 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.""" """Deletes specific entries from a journal."""
for entry in entries_to_delete: for entry in entries_to_delete:
self.entries.remove(entry) self.entries.remove(entry)
self._diff_entry_dates.append(entry.date) 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.""" """Changes entry dates to given date."""
date = time.parse(date) date = time.parse(date)
@ -98,7 +102,7 @@ class Folder(Journal.Journal):
self._diff_entry_dates.append(entry.date) self._diff_entry_dates.append(entry.date)
entry.date = 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.""" """Parses the output of self.editable_str and updates its entries."""
mod_entries = self._parse(edited) mod_entries = self._parse(edited)
diff_entries = set(self.entries) - set(mod_entries) diff_entries = set(self.entries) - set(mod_entries)

View file

@ -20,7 +20,7 @@ from jrnl.plugins import util
class WrappingFormatter(argparse.RawTextHelpFormatter): class WrappingFormatter(argparse.RawTextHelpFormatter):
"""Used in help screen""" """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 = text.split("\n\n")
text = map(lambda t: self._whitespace_matcher.sub(" ", t).strip(), text) text = map(lambda t: self._whitespace_matcher.sub(" ", t).strip(), text)
text = map(lambda t: textwrap.wrap(t, width=56), text) text = map(lambda t: textwrap.wrap(t, width=56), text)
@ -28,7 +28,7 @@ class WrappingFormatter(argparse.RawTextHelpFormatter):
return text return text
def parse_args(args=[]): def parse_args(args: list[str] = []) -> argparse.Namespace:
""" """
Argument parsing that is doable before the config is available. Argument parsing that is doable before the config is available.
Everything else goes into "text" for later parsing. Everything else goes into "text" for later parsing.

View file

@ -16,7 +16,7 @@ from jrnl.messages import MsgText
from jrnl.output import print_msg from jrnl.output import print_msg
def configure_logger(debug=False): def configure_logger(debug: bool = False) -> None:
if not debug: if not debug:
logging.disable() logging.disable()
return return
@ -31,7 +31,7 @@ def configure_logger(debug=False):
logging.getLogger("keyring.backend").setLevel(logging.ERROR) logging.getLogger("keyring.backend").setLevel(logging.ERROR)
def cli(manual_args=None): def cli(manual_args: list[str] | None = None) -> int:
try: try:
if manual_args is None: if manual_args is None:
manual_args = sys.argv[1:] manual_args = sys.argv[1:]

View file

@ -4,16 +4,20 @@
import re import re
from string import punctuation from string import punctuation
from string import whitespace from string import whitespace
from typing import TYPE_CHECKING
import colorama import colorama
from jrnl.os_compat import on_windows from jrnl.os_compat import on_windows
if TYPE_CHECKING:
from jrnl.Entry import Entry
if on_windows(): if on_windows():
colorama.init() 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 """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, the user is "NONE" or the color doesn't exist in the colorama.Fore attributes,
it returns the string without any modification.""" it returns the string without any modification."""
@ -26,7 +30,9 @@ def colorize(string, color, bold=False):
return colorama.Style.BRIGHT + color_escape + string + colorama.Style.RESET_ALL 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 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`. color.tags, while colorizing the rest of the text based on `color`.
@ -45,9 +51,9 @@ def highlight_tags_with_background_color(entry, text, color, is_title=False):
:returns [(colorized_str, original_str)]""" :returns [(colorized_str, original_str)]"""
for part in fragments: for part in fragments:
if part and part[0] not in config["tagsymbols"]: 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: elif part:
yield (colorize(part, config["colors"]["tags"], bold=True), part) yield colorize(part, config["colors"]["tags"], bold=True), part
config = entry.journal.config config = entry.journal.config
if config["highlight"]: # highlight tags if config["highlight"]: # highlight tags

View file

@ -4,6 +4,7 @@
import argparse import argparse
import logging import logging
import os import os
from typing import Any
from typing import Callable from typing import Callable
import colorama import colorama
@ -57,7 +58,7 @@ def make_yaml_valid_dict(input: list) -> dict:
return runtime_modifications 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.""" """Supply alt_config_path if using an alternate config through --config-file."""
config["version"] = __version__ config["version"] = __version__
@ -72,7 +73,7 @@ def save_config(config, alt_config_path=None):
yaml.dump(config, f) yaml.dump(config, f)
def get_config_path(): def get_config_path() -> str:
try: try:
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE) config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
except FileExistsError: except FileExistsError:
@ -91,7 +92,7 @@ def get_config_path():
return os.path.join(config_directory_path or home_dir(), DEFAULT_CONFIG_NAME) 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 { return {
"version": __version__, "version": __version__,
"journals": {"default": {"journal": get_default_journal_path()}}, "journals": {"default": {"journal": get_default_journal_path()}},
@ -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() journal_data_path = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or home_dir()
return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME) 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"]: if journal_name not in config["journals"]:
return config return config
config = config.copy() config = config.copy()
@ -139,7 +140,7 @@ def scope_config(config, journal_name):
return config 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" Ensures the keys set for colors are valid colorama.Fore attributes, or "None"
:return: True if all keys are set correctly, False otherwise :return: True if all keys are set correctly, False otherwise
@ -164,7 +165,7 @@ def verify_config_colors(config):
return all_valid_colors return all_valid_colors
def load_config(config_path): def load_config(config_path: str) -> dict:
"""Tries to load a config file from YAML.""" """Tries to load a config file from YAML."""
try: try:
with open(config_path, encoding=YAML_FILE_ENCODING) as f: with open(config_path, encoding=YAML_FILE_ENCODING) as f:
@ -187,13 +188,15 @@ def load_config(config_path):
return yaml.load(f) 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: with open(config_path, "r", encoding="utf-8") as f:
config_file = f.read() config_file = f.read()
return config_file.strip().startswith("{") 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 """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 config['journals'][scope] is just a string pointing to a journal file,
or within the scope""" or within the scope"""
@ -206,7 +209,7 @@ def update_config(config, new_config, scope, force_local=False):
config.update(new_config) 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 args.journal_name = DEFAULT_JOURNAL_KEY
# The first arg might be a journal name # The first arg might be a journal name

View file

@ -17,7 +17,7 @@ from jrnl.os_compat import split_args
from jrnl.output import print_msg 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" suffix = ".jrnl"
if config["template"]: if config["template"]:
template_filename = Path(config["template"]).name template_filename = Path(config["template"]).name
@ -50,7 +50,7 @@ def get_text_from_editor(config, template=""):
return raw return raw
def get_text_from_stdin(): def get_text_from_stdin() -> str:
print_msg( print_msg(
Message( Message(
MsgText.WritingEntryStart, MsgText.WritingEntryStart,

View file

@ -27,7 +27,7 @@ from jrnl.prompt import yesno
from jrnl.upgrade import is_old_version from jrnl.upgrade import is_old_version
def upgrade_config(config_data, alt_config_path=None): def upgrade_config(config_data: dict, alt_config_path: str | None = None) -> None:
"""Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly. """Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
This essentially automatically ports jrnl installations if new config parameters are introduced in later This essentially automatically ports jrnl installations if new config parameters are introduced in later
versions. versions.
@ -46,7 +46,7 @@ def upgrade_config(config_data, alt_config_path=None):
) )
def find_default_config(): def find_default_config() -> str:
config_path = ( config_path = (
get_config_path() get_config_path()
if os.path.exists(get_config_path()) if os.path.exists(get_config_path())
@ -55,7 +55,7 @@ def find_default_config():
return config_path return config_path
def find_alt_config(alt_config): def find_alt_config(alt_config: str) -> str:
if not os.path.exists(alt_config): if not os.path.exists(alt_config):
raise JrnlException( raise JrnlException(
Message( Message(
@ -66,7 +66,7 @@ def find_alt_config(alt_config):
return alt_config return alt_config
def load_or_install_jrnl(alt_config_path): def load_or_install_jrnl(alt_config_path: str) -> dict:
""" """
If jrnl is already installed, loads and returns a default config object. If jrnl is already installed, loads and returns a default config object.
If alternate config is specified via --config-file flag, it will be used. If alternate config is specified via --config-file flag, it will be used.
@ -107,7 +107,7 @@ def load_or_install_jrnl(alt_config_path):
return config return config
def install(): def install() -> dict:
_initialize_autocomplete() _initialize_autocomplete()
# Where to create the journal? # Where to create the journal?
@ -143,7 +143,7 @@ def install():
return default_config return default_config
def _initialize_autocomplete(): def _initialize_autocomplete() -> None:
# readline is not included in Windows Active Python and perhaps some other distributions # readline is not included in Windows Active Python and perhaps some other distributions
if sys.modules.get("readline"): if sys.modules.get("readline"):
import readline import readline
@ -153,7 +153,7 @@ def _initialize_autocomplete():
readline.set_completer(_autocomplete_path) readline.set_completer(_autocomplete_path)
def _autocomplete_path(text, state): def _autocomplete_path(text: str, state: int) -> list[str | None]:
expansions = glob.glob(expand_path(text) + "*") expansions = glob.glob(expand_path(text) + "*")
expansions = [e + "/" if os.path.isdir(e) else e for e in expansions] expansions = [e + "/" if os.path.isdir(e) else e for e in expansions]
expansions.append(None) expansions.append(None)

View file

@ -1,8 +1,8 @@
# Copyright © 2012-2022 jrnl contributors # Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import logging import logging
import sys import sys
from typing import TYPE_CHECKING
from jrnl import install from jrnl import install
from jrnl import plugins from jrnl import plugins
@ -14,6 +14,7 @@ from jrnl.config import scope_config
from jrnl.editor import get_text_from_editor from jrnl.editor import get_text_from_editor
from jrnl.editor import get_text_from_stdin from jrnl.editor import get_text_from_stdin
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.Journal import Journal
from jrnl.Journal import open_journal from jrnl.Journal import open_journal
from jrnl.messages import Message from jrnl.messages import Message
from jrnl.messages import MsgStyle from jrnl.messages import MsgStyle
@ -23,8 +24,13 @@ from jrnl.output import print_msgs
from jrnl.override import apply_overrides from jrnl.override import apply_overrides
from jrnl.path import expand_path from jrnl.path import expand_path
if TYPE_CHECKING:
from argparse import Namespace
def run(args): from jrnl.Entry import Entry
def run(args: "Namespace"):
""" """
Flow: Flow:
1. Run standalone command if it doesn't require config (help, version, etc), then exit 1. Run standalone command if it doesn't require config (help, version, etc), then exit
@ -72,10 +78,8 @@ def run(args):
search_mode(**kwargs) search_mode(**kwargs)
def _is_write_mode(args, config, **kwargs): def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
"""Determines if we are in write mode (as opposed to search mode)""" """Determines if we are in write mode (as opposed to search mode)"""
write_mode = True
# Are any search filters present? If so, then search mode. # Are any search filters present? If so, then search mode.
write_mode = not any( write_mode = not any(
( (
@ -115,7 +119,7 @@ def _is_write_mode(args, config, **kwargs):
return write_mode return write_mode
def write_mode(args, config, journal, **kwargs): def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> None:
""" """
Gets input from the user to write to the journal Gets input from the user to write to the journal
1. Check for input from cli 1. Check for input from cli
@ -159,7 +163,7 @@ def write_mode(args, config, journal, **kwargs):
logging.debug("Write mode: completed journal.write()") logging.debug("Write mode: completed journal.write()")
def search_mode(args, journal, **kwargs): def search_mode(args: "Namespace", journal: Journal, **kwargs) -> None:
""" """
Search for entries in a journal, then either: Search for entries in a journal, then either:
1. Send them to configured editor for user manipulation (and also 1. Send them to configured editor for user manipulation (and also
@ -213,7 +217,7 @@ def search_mode(args, journal, **kwargs):
_display_search_results(**kwargs) _display_search_results(**kwargs)
def _write_in_editor(config, template=None): def _write_in_editor(config: dict, template: str | None = None) -> str:
if config["editor"]: if config["editor"]:
logging.debug("Write mode: opening editor") logging.debug("Write mode: opening editor")
if not template: if not template:
@ -226,7 +230,7 @@ def _write_in_editor(config, template=None):
return raw return raw
def _get_editor_template(config, **kwargs): def _get_editor_template(config: dict, **kwargs) -> str:
logging.debug("Write mode: loading template for entry") logging.debug("Write mode: loading template for entry")
if not config["template"]: if not config["template"]:
@ -251,7 +255,7 @@ def _get_editor_template(config, **kwargs):
return template return template
def _has_search_args(args): def _has_search_args(args: "Namespace") -> bool:
return any( return any(
( (
args.on_date, args.on_date,
@ -271,7 +275,7 @@ def _has_search_args(args):
) )
def _filter_journal_entries(args, journal, **kwargs): def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> None:
"""Filter journal entries in-place based upon search args""" """Filter journal entries in-place based upon search args"""
if args.on_date: if args.on_date:
args.start_date = args.end_date = args.on_date args.start_date = args.end_date = args.on_date
@ -296,7 +300,7 @@ def _filter_journal_entries(args, journal, **kwargs):
journal.limit(args.limit) journal.limit(args.limit)
def _print_entries_found_count(count, args): def _print_entries_found_count(count: int, args: "Namespace") -> None:
if count == 0: if count == 0:
if args.edit or args.change_time: if args.edit or args.change_time:
print_msg(Message(MsgText.NothingToModify, MsgStyle.WARNING)) print_msg(Message(MsgText.NothingToModify, MsgStyle.WARNING))
@ -317,12 +321,14 @@ def _print_entries_found_count(count, args):
print_msg(Message(my_msg, MsgStyle.NORMAL, {"num": count})) print_msg(Message(my_msg, MsgStyle.NORMAL, {"num": count}))
def _other_entries(journal, entries): def _other_entries(journal: Journal, entries: list["Entry"]) -> list["Entry"]:
"""Find entries that are not in journal""" """Find entries that are not in journal"""
return [e for e in entries if e not in journal.entries] return [e for e in entries if e not in journal.entries]
def _edit_search_results(config, journal, old_entries, **kwargs): def _edit_search_results(
config: dict, journal: Journal, old_entries: list["Entry"], **kwargs
) -> None:
""" """
1. Send the given journal entries to the user-configured editor 1. Send the given journal entries to the user-configured editor
2. Print out stats on any modifications to journal 2. Print out stats on any modifications to journal
@ -356,7 +362,9 @@ def _edit_search_results(config, journal, old_entries, **kwargs):
journal.write() journal.write()
def _print_edited_summary(journal, old_stats, **kwargs): def _print_edited_summary(
journal: Journal, old_stats: dict[str, int], **kwargs
) -> None:
stats = { stats = {
"added": len(journal) - old_stats["count"], "added": len(journal) - old_stats["count"],
"deleted": old_stats["count"] - len(journal), "deleted": old_stats["count"] - len(journal),
@ -395,11 +403,13 @@ def _print_edited_summary(journal, old_stats, **kwargs):
print_msgs(msgs) print_msgs(msgs)
def _get_predit_stats(journal): def _get_predit_stats(journal: Journal) -> dict[str, int]:
return {"count": len(journal)} return {"count": len(journal)}
def _delete_search_results(journal, old_entries, **kwargs): def _delete_search_results(
journal: Journal, old_entries: list["Entry"], **kwargs
) -> None:
entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion) entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion)
if entries_to_delete: if entries_to_delete:
@ -409,7 +419,13 @@ def _delete_search_results(journal, old_entries, **kwargs):
journal.write() journal.write()
def _change_time_search_results(args, journal, old_entries, no_prompt=False, **kwargs): def _change_time_search_results(
args: "Namespace",
journal: Journal,
old_entries: list["Entry"],
no_prompt: bool = False,
**kwargs
) -> None:
# separate entries we are not editing # separate entries we are not editing
other_entries = _other_entries(journal, old_entries) other_entries = _other_entries(journal, old_entries)
@ -432,7 +448,7 @@ def _change_time_search_results(args, journal, old_entries, no_prompt=False, **k
journal.write() journal.write()
def _display_search_results(args, journal, **kwargs): def _display_search_results(args: "Namespace", journal: Journal, **kwargs) -> None:
# Get export format from config file if not provided at the command line # Get export format from config file if not provided at the command line
args.export = args.export or kwargs["config"].get("display_format") args.export = args.export or kwargs["config"].get("display_format")

View file

@ -5,14 +5,14 @@ import shlex
from sys import platform from sys import platform
def on_windows(): def on_windows() -> bool:
return "win32" in platform return "win32" in platform
def on_posix(): def on_posix() -> bool:
return not on_windows() return not on_windows()
def split_args(args): def split_args(args: str) -> list[str]:
"""Split arguments and add escape characters as appropriate for the OS""" """Split arguments and add escape characters as appropriate for the OS"""
return shlex.split(args, posix=on_posix()) return shlex.split(args, posix=on_posix())

View file

@ -2,7 +2,7 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import textwrap import textwrap
from typing import Union from typing import Callable
from rich.console import Console from rich.console import Console
from rich.text import Text from rich.text import Text
@ -12,7 +12,9 @@ from jrnl.messages import MsgStyle
from jrnl.messages import MsgText from jrnl.messages import MsgText
def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): def deprecated_cmd(
old_cmd: str, new_cmd: str, callback: Callable | None = None, **kwargs
) -> None:
print_msg( print_msg(
Message( Message(
MsgText.DeprecatedCommand, MsgText.DeprecatedCommand,
@ -25,13 +27,13 @@ def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs):
callback(**kwargs) callback(**kwargs)
def journal_list_to_json(journal_list): def journal_list_to_json(journal_list: dict) -> str:
import json import json
return json.dumps(journal_list) return json.dumps(journal_list)
def journal_list_to_yaml(journal_list): def journal_list_to_yaml(journal_list: dict) -> str:
from io import StringIO from io import StringIO
from ruamel.yaml import YAML from ruamel.yaml import YAML
@ -41,7 +43,7 @@ def journal_list_to_yaml(journal_list):
return output.getvalue() return output.getvalue()
def journal_list_to_stdout(journal_list): def journal_list_to_stdout(journal_list: dict) -> str:
result = f"Journals defined in config ({journal_list['config_path']})\n" result = f"Journals defined in config ({journal_list['config_path']})\n"
ml = min(max(len(k) for k in journal_list["journals"]), 20) ml = min(max(len(k) for k in journal_list["journals"]), 20)
for journal, cfg in journal_list["journals"].items(): for journal, cfg in journal_list["journals"].items():
@ -51,7 +53,7 @@ def journal_list_to_stdout(journal_list):
return result return result
def list_journals(configuration, format=None): def list_journals(configuration: dict, format: str | None = None) -> str:
from jrnl import config from jrnl import config
"""List the journals specified in the configuration file""" """List the journals specified in the configuration file"""
@ -69,7 +71,7 @@ def list_journals(configuration, format=None):
return journal_list_to_stdout(journal_list) return journal_list_to_stdout(journal_list)
def print_msg(msg: Message, **kwargs) -> Union[None, str]: def print_msg(msg: Message, **kwargs) -> str | None:
"""Helper function to print a single message""" """Helper function to print a single message"""
kwargs["style"] = msg.style kwargs["style"] = msg.style
return print_msgs([msg], **kwargs) return print_msgs([msg], **kwargs)
@ -81,7 +83,7 @@ def print_msgs(
style: MsgStyle = MsgStyle.NORMAL, style: MsgStyle = MsgStyle.NORMAL,
get_input: bool = False, get_input: bool = False,
hide_input: bool = False, hide_input: bool = False,
) -> Union[None, str]: ) -> str | None:
# Same as print_msg, but for a list # Same as print_msg, but for a list
text = Text("", end="") text = Text("", end="")
kwargs = style.decoration.args kwargs = style.decoration.args
@ -113,7 +115,7 @@ def _get_console(stderr: bool = True) -> Console:
return Console(stderr=stderr) return Console(stderr=stderr)
def _add_extra_style_args_if_needed(args, msg): def _add_extra_style_args_if_needed(args: dict, msg: Message):
args["border_style"] = msg.style.color args["border_style"] = msg.style.color
args["title"] = msg.style.box_title args["title"] = msg.style.box_title
return args return args

View file

@ -37,12 +37,12 @@ def apply_overrides(args: "Namespace", base_config: dict) -> dict:
return base_config return base_config
def _get_key_and_value_from_pair(pairs): def _get_key_and_value_from_pair(pairs: dict) -> tuple:
key_as_dots, override_value = list(pairs.items())[0] key_as_dots, override_value = list(pairs.items())[0]
return key_as_dots, override_value return key_as_dots, override_value
def _convert_dots_to_list(key_as_dots): def _convert_dots_to_list(key_as_dots: str) -> list[str]:
keys = key_as_dots.split(".") keys = key_as_dots.split(".")
keys = [k for k in keys if k != ""] # remove empty elements keys = [k for k in keys if k != ""] # remove empty elements
return keys return keys

View file

@ -4,13 +4,13 @@
import os.path import os.path
def home_dir(): def home_dir() -> str:
return os.path.expanduser("~") return os.path.expanduser("~")
def expand_path(path): def expand_path(path: str) -> str:
return os.path.expanduser(os.path.expandvars(path)) return os.path.expanduser(os.path.expandvars(path))
def absolute_path(path): def absolute_path(path: str) -> str:
return os.path.abspath(expand_path(path)) return os.path.abspath(expand_path(path))

View file

@ -1,6 +1,8 @@
# Copyright © 2012-2022 jrnl contributors # Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from typing import Type
from jrnl.plugins.dates_exporter import DatesExporter from jrnl.plugins.dates_exporter import DatesExporter
from jrnl.plugins.fancy_exporter import FancyExporter from jrnl.plugins.fancy_exporter import FancyExporter
from jrnl.plugins.jrnl_importer import JRNLImporter from jrnl.plugins.jrnl_importer import JRNLImporter
@ -32,14 +34,14 @@ EXPORT_FORMATS = sorted(__exporter_types.keys())
IMPORT_FORMATS = sorted(__importer_types.keys()) IMPORT_FORMATS = sorted(__importer_types.keys())
def get_exporter(format): def get_exporter(format: str) -> Type[TextExporter] | None:
for exporter in __exporters: for exporter in __exporters:
if hasattr(exporter, "names") and format in exporter.names: if hasattr(exporter, "names") and format in exporter.names:
return exporter return exporter
return None return None
def get_importer(format): def get_importer(format: str) -> Type[JRNLImporter] | None:
for importer in __importers: for importer in __importers:
if hasattr(importer, "names") and format in importer.names: if hasattr(importer, "names") and format in importer.names:
return importer return importer

View file

@ -2,9 +2,14 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from collections import Counter from collections import Counter
from typing import TYPE_CHECKING
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class DatesExporter(TextExporter): class DatesExporter(TextExporter):
"""This Exporter lists dates and their respective counts, for heatingmapping etc.""" """This Exporter lists dates and their respective counts, for heatingmapping etc."""
@ -13,11 +18,11 @@ class DatesExporter(TextExporter):
extension = "dates" extension = "dates"
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry: "Entry"):
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns dates and their frequencies for an entire journal.""" """Returns dates and their frequencies for an entire journal."""
date_counts = Counter() date_counts = Counter()
for entry in journal.entries: for entry in journal.entries:

View file

@ -4,6 +4,7 @@
import logging import logging
import os import os
from textwrap import TextWrapper from textwrap import TextWrapper
from typing import TYPE_CHECKING
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.messages import Message from jrnl.messages import Message
@ -11,6 +12,10 @@ from jrnl.messages import MsgStyle
from jrnl.messages import MsgText from jrnl.messages import MsgText
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class FancyExporter(TextExporter): class FancyExporter(TextExporter):
"""This Exporter can convert entries and journals into text with unicode box drawing characters.""" """This Exporter can convert entries and journals into text with unicode box drawing characters."""
@ -35,7 +40,7 @@ class FancyExporter(TextExporter):
border_m = "" border_m = ""
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry: "Entry") -> str:
"""Returns a fancy unicode representation of a single entry.""" """Returns a fancy unicode representation of a single entry."""
date_str = entry.date.strftime(entry.journal.config["timeformat"]) date_str = entry.date.strftime(entry.journal.config["timeformat"])
@ -95,12 +100,12 @@ class FancyExporter(TextExporter):
return "\n".join(card) return "\n".join(card)
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal) -> str:
"""Returns a unicode representation of an entire journal.""" """Returns a unicode representation of an entire journal."""
return "\n".join(cls.export_entry(entry) for entry in journal) return "\n".join(cls.export_entry(entry) for entry in journal)
def check_provided_linewrap_viability(linewrap, card, journal): def check_provided_linewrap_viability(linewrap: int, card: list[str], journal: "Journal"):
if len(card[0]) > linewrap: if len(card[0]) > linewrap:
width_violation = len(card[0]) - linewrap width_violation = len(card[0]) - linewrap
raise JrnlException( raise JrnlException(

View file

@ -2,6 +2,7 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import sys import sys
from typing import TYPE_CHECKING
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.messages import Message from jrnl.messages import Message
@ -9,6 +10,9 @@ from jrnl.messages import MsgStyle
from jrnl.messages import MsgText from jrnl.messages import MsgText
from jrnl.output import print_msg from jrnl.output import print_msg
if TYPE_CHECKING:
from jrnl.Journal import Journal
class JRNLImporter: class JRNLImporter:
"""This plugin imports entries from other jrnl files.""" """This plugin imports entries from other jrnl files."""
@ -16,7 +20,7 @@ class JRNLImporter:
names = ["jrnl"] names = ["jrnl"]
@staticmethod @staticmethod
def import_(journal, input=None): def import_(journal: "Journal", input: str | None = None) -> None:
"""Imports from an existing file if input is specified, and """Imports from an existing file if input is specified, and
standard input otherwise.""" standard input otherwise."""
old_cnt = len(journal.entries) old_cnt = len(journal.entries)

View file

@ -2,10 +2,15 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import json import json
from typing import TYPE_CHECKING
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class JSONExporter(TextExporter): class JSONExporter(TextExporter):
"""This Exporter can convert entries and journals into json.""" """This Exporter can convert entries and journals into json."""
@ -14,7 +19,7 @@ class JSONExporter(TextExporter):
extension = "json" extension = "json"
@classmethod @classmethod
def entry_to_dict(cls, entry): def entry_to_dict(cls, entry: "Entry") -> dict:
entry_dict = { entry_dict = {
"title": entry.title, "title": entry.title,
"body": entry.body, "body": entry.body,
@ -49,12 +54,12 @@ class JSONExporter(TextExporter):
return entry_dict return entry_dict
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry: "Entry") -> str:
"""Returns a json representation of a single entry.""" """Returns a json representation of a single entry."""
return json.dumps(cls.entry_to_dict(entry), indent=2) + "\n" return json.dumps(cls.entry_to_dict(entry), indent=2) + "\n"
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns a json representation of an entire journal.""" """Returns a json representation of an entire journal."""
tags = get_tags_count(journal) tags = get_tags_count(journal)
result = { result = {

View file

@ -3,6 +3,7 @@
import os import os
import re import re
from typing import TYPE_CHECKING
from jrnl.messages import Message from jrnl.messages import Message
from jrnl.messages import MsgStyle from jrnl.messages import MsgStyle
@ -10,6 +11,10 @@ from jrnl.messages import MsgText
from jrnl.output import print_msg from jrnl.output import print_msg
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class MarkdownExporter(TextExporter): class MarkdownExporter(TextExporter):
"""This Exporter can convert entries and journals into Markdown.""" """This Exporter can convert entries and journals into Markdown."""
@ -18,7 +23,7 @@ class MarkdownExporter(TextExporter):
extension = "md" extension = "md"
@classmethod @classmethod
def export_entry(cls, entry, to_multifile=True): def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str:
"""Returns a markdown representation of a single entry.""" """Returns a markdown representation of a single entry."""
date_str = entry.date.strftime(entry.journal.config["timeformat"]) date_str = entry.date.strftime(entry.journal.config["timeformat"])
body_wrapper = "\n" if entry.body else "" body_wrapper = "\n" if entry.body else ""
@ -73,7 +78,7 @@ class MarkdownExporter(TextExporter):
return f"{heading} {date_str} {entry.title}\n{newbody} " return f"{heading} {date_str} {entry.title}\n{newbody} "
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns a Markdown representation of an entire journal.""" """Returns a Markdown representation of an entire journal."""
out = [] out = []
year, month = -1, -1 year, month = -1, -1

View file

@ -1,9 +1,15 @@
# Copyright © 2012-2022 jrnl contributors # Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from typing import TYPE_CHECKING
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class TagExporter(TextExporter): class TagExporter(TextExporter):
"""This Exporter can lists the tags for entries and journals, exported as a plain text file.""" """This Exporter can lists the tags for entries and journals, exported as a plain text file."""
@ -12,12 +18,12 @@ class TagExporter(TextExporter):
extension = "tags" extension = "tags"
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry: "Entry") -> str:
"""Returns a list of tags for a single entry.""" """Returns a list of tags for a single entry."""
return ", ".join(entry.tags) return ", ".join(entry.tags)
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns a list of tags and their frequency for an entire journal.""" """Returns a list of tags and their frequency for an entire journal."""
tag_counts = get_tags_count(journal) tag_counts = get_tags_count(journal)
result = "" result = ""

View file

@ -5,12 +5,17 @@ import errno
import os import os
import re import re
import unicodedata import unicodedata
from typing import TYPE_CHECKING
from jrnl.messages import Message from jrnl.messages import Message
from jrnl.messages import MsgStyle from jrnl.messages import MsgStyle
from jrnl.messages import MsgText from jrnl.messages import MsgText
from jrnl.output import print_msg from jrnl.output import print_msg
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class TextExporter: class TextExporter:
"""This Exporter can convert entries and journals into text files.""" """This Exporter can convert entries and journals into text files."""
@ -19,17 +24,17 @@ class TextExporter:
extension = "txt" extension = "txt"
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry: "Entry") -> str:
"""Returns a string representation of a single entry.""" """Returns a string representation of a single entry."""
return str(entry) return str(entry)
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns a string representation of an entire journal.""" """Returns a string representation of an entire journal."""
return "\n".join(cls.export_entry(entry) for entry in journal) return "\n".join(cls.export_entry(entry) for entry in journal)
@classmethod @classmethod
def write_file(cls, journal, path): def write_file(cls, journal: "Journal", path: str) -> str:
"""Exports a journal into a single file.""" """Exports a journal into a single file."""
export_str = cls.export_journal(journal) export_str = cls.export_journal(journal)
with open(path, "w", encoding="utf-8") as f: with open(path, "w", encoding="utf-8") as f:
@ -46,13 +51,13 @@ class TextExporter:
return "" return ""
@classmethod @classmethod
def make_filename(cls, entry): def make_filename(cls, entry: "Entry") -> str:
return entry.date.strftime("%Y-%m-%d") + "_{}.{}".format( return entry.date.strftime("%Y-%m-%d") + "_{}.{}".format(
cls._slugify(str(entry.title)), cls.extension cls._slugify(str(entry.title)), cls.extension
) )
@classmethod @classmethod
def write_files(cls, journal, path): def write_files(cls, journal: "Journal", path: str) -> str:
"""Exports a journal into individual files for each entry.""" """Exports a journal into individual files for each entry."""
for entry in journal.entries: for entry in journal.entries:
entry_is_written = False entry_is_written = False
@ -82,7 +87,7 @@ class TextExporter:
) )
return "" return ""
def _slugify(string): def _slugify(string: str) -> str:
"""Slugifies a string. """Slugifies a string.
Based on public domain code from https://github.com/zacharyvoase/slugify Based on public domain code from https://github.com/zacharyvoase/slugify
""" """
@ -92,7 +97,7 @@ class TextExporter:
return slug return slug
@classmethod @classmethod
def export(cls, journal, output=None): def export(cls, journal: "Journal", output: str | None = None) -> str:
"""Exports to individual files if output is an existing path, or into """Exports to individual files if output is an existing path, or into
a single file if output is a file name, or returns the exporter's a single file if output is a file name, or returns the exporter's
representation as string if output is None.""" representation as string if output is None."""

View file

@ -1,8 +1,13 @@
# Copyright © 2012-2022 jrnl contributors # Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from typing import TYPE_CHECKING
def get_tags_count(journal): if TYPE_CHECKING:
from jrnl.Journal import Journal
def get_tags_count(journal: "Journal") -> set[tuple[int, str]]:
"""Returns a set of tuples (count, tag) for all tags present in the journal.""" """Returns a set of tuples (count, tag) for all tags present in the journal."""
# Astute reader: should the following line leave you as puzzled as me the first time # Astute reader: should the following line leave you as puzzled as me the first time
# I came across this construction, worry not and embrace the ensuing moment of enlightment. # I came across this construction, worry not and embrace the ensuing moment of enlightment.
@ -12,7 +17,7 @@ def get_tags_count(journal):
return tag_counts return tag_counts
def oxford_list(lst): def oxford_list(lst: list) -> str:
"""Return Human-readable list of things obeying the object comma)""" """Return Human-readable list of things obeying the object comma)"""
lst = sorted(lst) lst = sorted(lst)
if not lst: if not lst:

View file

@ -1,11 +1,16 @@
# Copyright © 2012-2022 jrnl contributors # Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from typing import TYPE_CHECKING
from xml.dom import minidom from xml.dom import minidom
from jrnl.plugins.json_exporter import JSONExporter from jrnl.plugins.json_exporter import JSONExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class XMLExporter(JSONExporter): class XMLExporter(JSONExporter):
"""This Exporter can convert entries and journals into XML.""" """This Exporter can convert entries and journals into XML."""
@ -14,7 +19,9 @@ class XMLExporter(JSONExporter):
extension = "xml" extension = "xml"
@classmethod @classmethod
def export_entry(cls, entry, doc=None): def export_entry(
cls, entry: "Entry", doc: minidom.Document | None = None
) -> minidom.Element | str:
"""Returns an XML representation of a single entry.""" """Returns an XML representation of a single entry."""
doc_el = doc or minidom.Document() doc_el = doc or minidom.Document()
entry_el = doc_el.createElement("entry") entry_el = doc_el.createElement("entry")
@ -29,7 +36,7 @@ class XMLExporter(JSONExporter):
return entry_el return entry_el
@classmethod @classmethod
def entry_to_xml(cls, entry, doc): def entry_to_xml(cls, entry: "Entry", doc: minidom.Document) -> minidom.Element:
entry_el = doc.createElement("entry") entry_el = doc.createElement("entry")
entry_el.setAttribute("date", entry.date.isoformat()) entry_el.setAttribute("date", entry.date.isoformat())
if hasattr(entry, "uuid"): if hasattr(entry, "uuid"):
@ -44,7 +51,7 @@ class XMLExporter(JSONExporter):
return entry_el return entry_el
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal") -> str:
"""Returns an XML representation of an entire journal.""" """Returns an XML representation of an entire journal."""
tags = get_tags_count(journal) tags = get_tags_count(journal)
doc = minidom.Document() doc = minidom.Document()

View file

@ -3,6 +3,7 @@
import os import os
import re import re
from typing import TYPE_CHECKING
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.messages import Message from jrnl.messages import Message
@ -11,6 +12,10 @@ from jrnl.messages import MsgText
from jrnl.output import print_msg from jrnl.output import print_msg
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
if TYPE_CHECKING:
from jrnl.Entry import Entry
from jrnl.Journal import Journal
class YAMLExporter(TextExporter): class YAMLExporter(TextExporter):
"""This Exporter can convert entries and journals into Markdown formatted text with YAML front matter.""" """This Exporter can convert entries and journals into Markdown formatted text with YAML front matter."""
@ -19,7 +24,7 @@ class YAMLExporter(TextExporter):
extension = "md" extension = "md"
@classmethod @classmethod
def export_entry(cls, entry, to_multifile=True): def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str:
"""Returns a markdown representation of a single entry, with YAML front matter.""" """Returns a markdown representation of a single entry, with YAML front matter."""
if to_multifile is False: if to_multifile is False:
raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))
@ -124,6 +129,6 @@ class YAMLExporter(TextExporter):
) )
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal: "Journal"):
"""Returns an error, as YAML export requires a directory as a target.""" """Returns an error, as YAML export requires a directory as a target."""
raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))

View file

@ -42,7 +42,7 @@ def create_password(journal_name: str) -> str:
return pw return pw
def yesno(prompt: Message, default: bool = True) -> bool: def yesno(prompt: Message | str, default: bool = True) -> bool:
response = print_msgs( response = print_msgs(
[ [
prompt, prompt,

View file

@ -22,8 +22,12 @@ def __get_pdt_calendar():
def parse( def parse(
date_str, inclusive=False, default_hour=None, default_minute=None, bracketed=False date_str: str | datetime.datetime,
): inclusive: bool = False,
default_hour: int | None = None,
default_minute: int | None = None,
bracketed: bool = False,
) -> datetime.datetime | None:
"""Parses a string containing a fuzzy date and returns a datetime.datetime object""" """Parses a string containing a fuzzy date and returns a datetime.datetime object"""
if not date_str: if not date_str:
return None return None

View file

@ -19,7 +19,7 @@ from jrnl.path import expand_path
from jrnl.prompt import yesno from jrnl.prompt import yesno
def backup(filename, binary=False): def backup(filename: str, binary: bool = False):
filename = expand_path(filename) filename = expand_path(filename)
try: try:
@ -42,14 +42,14 @@ def backup(filename, binary=False):
raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING)) raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))
def check_exists(path): def check_exists(path: str) -> bool:
""" """
Checks if a given path exists. Checks if a given path exists.
""" """
return os.path.exists(path) return os.path.exists(path)
def upgrade_jrnl(config_path): def upgrade_jrnl(config_path: str) -> None:
config = load_config(config_path) config = load_config(config_path)
print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {"version": __version__})) print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {"version": __version__}))
@ -115,7 +115,7 @@ def upgrade_jrnl(config_path):
cont = yesno(Message(MsgText.ContinueUpgrade), default=False) cont = yesno(Message(MsgText.ContinueUpgrade), default=False)
if not cont: if not cont:
raise JrnlException(Message(MsgText.UpgradeAborted), MsgStyle.WARNING) raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))
for journal_name, path in encrypted_journals.items(): for journal_name, path in encrypted_journals.items():
print_msg( print_msg(
@ -178,7 +178,7 @@ def upgrade_jrnl(config_path):
print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL)) print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL))
def is_old_version(config_path): def is_old_version(config_path: str) -> bool:
return is_config_json(config_path) return is_config_json(config_path)