From e2414121af16474744d012cdb8466de6ae3136e4 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 22 Jul 2018 14:23:39 +0200 Subject: Add type hints here and there --- poezio/bookmarks.py | 21 ++++++++-------- poezio/common.py | 67 ++++++++++++++++++++++++------------------------- poezio/config.py | 68 +++++++++++++++++++++++++++++--------------------- poezio/contact.py | 57 +++++++++++++++++++++++------------------- poezio/events.py | 11 +++++--- poezio/pep.py | 6 +++-- poezio/timed_events.py | 9 ++++--- poezio/user.py | 52 ++++++++++++++++++++------------------ poezio/xdg.py | 5 ++-- poezio/xhtml.py | 51 +++++++++++++++++++------------------ 10 files changed, 187 insertions(+), 160 deletions(-) diff --git a/poezio/bookmarks.py b/poezio/bookmarks.py index 3e3893f4..91f862c8 100644 --- a/poezio/bookmarks.py +++ b/poezio/bookmarks.py @@ -30,9 +30,10 @@ Adding a remote bookmark: import functools import logging +from typing import Optional, List -from slixmpp.plugins.xep_0048 import Bookmarks, Conference, URL from slixmpp import JID +from slixmpp.plugins.xep_0048 import Bookmarks, Conference, URL from poezio.common import safeJID from poezio.config import config @@ -41,11 +42,11 @@ log = logging.getLogger(__name__) class Bookmark: def __init__(self, - jid, - name=None, + jid: JID, + name: Optional[str] = None, autojoin=False, - nick=None, - password=None, + nick: Optional[str] = None, + password: Optional[str] = None, method='local'): self.jid = jid self.name = name or jid @@ -55,21 +56,21 @@ class Bookmark: self._method = method @property - def method(self): + def method(self) -> str: return self._method @method.setter - def method(self, value): + def method(self, value: str): if value not in ('local', 'remote'): log.debug('Could not set bookmark storing method: %s', value) return self._method = value - def __repr__(self): + def __repr__(self) -> str: return '<%s%s|%s>' % (self.jid, ('/' + self.nick) if self.nick else '', self.method) - def stanza(self): + def stanza(self) -> Conference: """ Generate a stanza from the instance """ @@ -83,7 +84,7 @@ class Bookmark: el['password'] = self.password return el - def local(self): + def local(self) -> str: """Generate a str for local storage""" local = self.jid if self.nick: diff --git a/poezio/common.py b/poezio/common.py index 3390b7b6..a021d898 100644 --- a/poezio/common.py +++ b/poezio/common.py @@ -10,19 +10,18 @@ Various useful functions. from datetime import datetime, timedelta from pathlib import Path -from slixmpp import JID, InvalidJID -from poezio.poezio_shlex import shlex +from typing import Dict, List, Optional, Tuple, Union -import base64 import os -import mimetypes -import hashlib import subprocess import time import string +from slixmpp import JID, InvalidJID, Message +from poezio.poezio_shlex import shlex + -def _get_output_of_command(command): +def _get_output_of_command(command: str) -> Optional[List[str]]: """ Runs a command and returns its output. @@ -37,7 +36,7 @@ def _get_output_of_command(command): return None -def _is_in_path(command, return_abs_path=False): +def _is_in_path(command: str, return_abs_path=False) -> Union[bool, str]: """ Check if *command* is in the $PATH or not. @@ -53,8 +52,7 @@ def _is_in_path(command, return_abs_path=False): if command in os.listdir(directory): if return_abs_path: return os.path.join(directory, command) - else: - return True + return True except OSError: # If the user has non directories in his path pass @@ -84,7 +82,7 @@ DISTRO_INFO = { } -def get_os_info(): +def get_os_info() -> str: """ Returns a detailed and well formatted string containing information about the operating system @@ -146,7 +144,7 @@ def get_os_info(): return os_info -def _datetime_tuple(timestamp): +def _datetime_tuple(timestamp: str) -> datetime: """ Convert a timestamp using strptime and the format: %Y%m%dT%H:%M:%S. @@ -172,10 +170,10 @@ def _datetime_tuple(timestamp): try: if tz_msg and tz_msg != 'Z': tz_mod = -1 if tz_msg[0] == '-' else 1 - tz_msg = time.strptime(tz_msg[1:], '%H%M') - tz_msg = tz_msg.tm_hour * 3600 + tz_msg.tm_min * 60 - tz_msg = timedelta(seconds=tz_mod * tz_msg) - ret -= tz_msg + tz_parsed = time.strptime(tz_msg[1:], '%H%M') + tz_seconds = tz_parsed.tm_hour * 3600 + tz_parsed.tm_min * 60 + delta = timedelta(seconds=tz_mod * tz_seconds) + ret -= delta except ValueError: pass # ignore if we got a badly-formatted offset # convert UTC to local time, with DST etc. @@ -187,7 +185,7 @@ def _datetime_tuple(timestamp): return ret -def get_utc_time(local_time=None): +def get_utc_time(local_time: Optional[datetime] = None) -> datetime: """ Get the current UTC time @@ -210,7 +208,7 @@ def get_utc_time(local_time=None): return utc_time -def get_local_time(utc_time): +def get_local_time(utc_time: datetime) -> datetime: """ Get the local time from an UTC time """ @@ -226,7 +224,7 @@ def get_local_time(utc_time): return local_time -def find_delayed_tag(message): +def find_delayed_tag(message: Message) -> Tuple[bool, datetime]: """ Check if a message is delayed or not. @@ -253,7 +251,7 @@ def find_delayed_tag(message): return (delayed, date) -def shell_split(st): +def shell_split(st: str) -> List[str]: """ Split a string correctly according to the quotes around the elements. @@ -276,7 +274,7 @@ def shell_split(st): return ret -def find_argument(pos, text, quoted=True): +def find_argument(pos: int, text: str, quoted=True) -> int: """ Split an input into a list of arguments, return the number of the argument selected by pos. @@ -293,11 +291,10 @@ def find_argument(pos, text, quoted=True): """ if quoted: return _find_argument_quoted(pos, text) - else: - return _find_argument_unquoted(pos, text) + return _find_argument_unquoted(pos, text) -def _find_argument_quoted(pos, text): +def _find_argument_quoted(pos: int, text: str) -> int: """ Get the number of the argument at position pos in a string with possibly quoted text. @@ -314,7 +311,7 @@ def _find_argument_quoted(pos, text): return count + 1 -def _find_argument_unquoted(pos, text): +def _find_argument_unquoted(pos: int, text: str) -> int: """ Get the number of the argument at position pos in a string without interpreting quotes. @@ -332,7 +329,7 @@ def _find_argument_unquoted(pos, text): return argnum + 1 -def parse_str_to_secs(duration=''): +def parse_str_to_secs(duration='') -> int: """ Parse a string of with a number of d, h, m, s. @@ -360,7 +357,7 @@ def parse_str_to_secs(duration=''): return result -def parse_secs_to_str(duration=0): +def parse_secs_to_str(duration=0) -> str: """ Do the reverse operation of :py:func:`parse_str_to_secs`. @@ -390,7 +387,7 @@ def parse_secs_to_str(duration=0): return result -def format_tune_string(infos): +def format_tune_string(infos: Dict[str, str]) -> str: """ Contruct a string from a dict created from an "User tune" event. @@ -417,18 +414,18 @@ def format_tune_string(infos): rating = infos.get('rating') if rating: elems.append('[ ' + rating + '/10 ]') - length = infos.get('length') - if length: - length = int(length) + length_str = infos.get('length') + if length_str: + length = int(length_str) secs = length % 60 mins = length // 60 - secs = str(secs).zfill(2) - mins = str(mins).zfill(2) - elems.append('[' + mins + ':' + secs + ']') + secs_str = str(secs).zfill(2) + mins_str = str(mins).zfill(2) + elems.append('[' + mins_str + ':' + secs_str + ']') return ' '.join(elems) -def format_gaming_string(infos): +def format_gaming_string(infos: Dict[str, str]) -> str: """ Construct a string from a dict containing "user gaming" information. (for now, only use address and name) @@ -447,7 +444,7 @@ def format_gaming_string(infos): return name -def safeJID(*args, **kwargs): +def safeJID(*args, **kwargs) -> JID: """ Construct a :py:class:`slixmpp.JID` object from a string. diff --git a/poezio/config.py b/poezio/config.py index e6e5cbcc..8f35e19c 100644 --- a/poezio/config.py +++ b/poezio/config.py @@ -10,8 +10,6 @@ TODO: get http://bugs.python.org/issue1410680 fixed, one day, in order to remove our ugly custom I/O methods. """ -DEFSECTION = "Poezio" - import logging.config import os import stat @@ -19,12 +17,17 @@ import sys import pkg_resources from configparser import RawConfigParser, NoOptionError, NoSectionError -from os import remove -from shutil import copy2 from pathlib import Path +from shutil import copy2 +from typing import Callable, Dict, List, Optional, Union, Tuple + from poezio.args import parse_args from poezio import xdg +ConfigValue = Union[str, int, float, bool] + +DEFSECTION = "Poezio" + DEFAULT_CONFIG = { 'Poezio': { 'ack_message_receipts': True, @@ -159,7 +162,7 @@ class Config(RawConfigParser): load/save the config to a file """ - def __init__(self, file_name, default=None): + def __init__(self, file_name: Path, default=None) -> None: RawConfigParser.__init__(self, None) # make the options case sensitive self.optionxform = str @@ -176,7 +179,10 @@ class Config(RawConfigParser): if not self.has_section(section): self.add_section(section) - def get(self, option, default=None, section=DEFSECTION): + def get(self, + option: str, + default: Optional[ConfigValue] = None, + section=DEFSECTION) -> ConfigValue: """ get a value from the config but return a default value if it is not found @@ -190,12 +196,12 @@ class Config(RawConfigParser): default = '' try: - if type(default) == int: + if isinstance(default, bool): + res = self.getboolean(option, section) + elif isinstance(default, int): res = self.getint(option, section) - elif type(default) == float: + elif isinstance(default, float): res = self.getfloat(option, section) - elif type(default) == bool: - res = self.getboolean(option, section) else: res = self.getstr(option, section) except (NoOptionError, NoSectionError, ValueError, AttributeError): @@ -279,7 +285,8 @@ class Config(RawConfigParser): """ return RawConfigParser.getboolean(self, section, option) - def write_in_file(self, section, option, value): + def write_in_file(self, section: str, option: str, + value: ConfigValue) -> bool: """ Our own way to save write the value in the file Just find the right section, and then find the @@ -305,7 +312,7 @@ class Config(RawConfigParser): return self._write_file(result_lines) - def remove_in_file(self, section, option): + def remove_in_file(self, section: str, option: str) -> bool: """ Our own way to remove an option from the file. """ @@ -334,7 +341,7 @@ class Config(RawConfigParser): return self._write_file(result_lines) - def _write_file(self, lines): + def _write_file(self, lines: List[str]) -> bool: """ Write the config file, write to a temporary file before copying it to the final destination @@ -360,7 +367,7 @@ class Config(RawConfigParser): success = True return success - def _parse_file(self): + def _parse_file(self) -> Optional[Tuple[Dict[str, List[int]], List[str]]]: """ Parse the config file and return the list of sections with their start and end positions, and the lines in the file. @@ -372,17 +379,18 @@ class Config(RawConfigParser): if file_ok(self.file_name): try: with self.file_name.open('r', encoding='utf-8') as df: - lines_before = [line.strip() for line in df] + lines_before = [line.strip() + for line in df] # type: List[str] except OSError: log.error( 'Unable to read the config file %s', self.file_name, exc_info=True) - return tuple() + return None else: lines_before = [] - sections = {} + sections = {} # type: Dict[str, List[int]] duplicate_section = False current_section = '' current_line = 0 @@ -408,7 +416,8 @@ class Config(RawConfigParser): return (sections, lines_before) - def set_and_save(self, option, value, section=DEFSECTION): + def set_and_save(self, option: str, value: ConfigValue, + section=DEFSECTION) -> Tuple[str, str]: """ set the value in the configuration then save it to the file @@ -439,7 +448,8 @@ class Config(RawConfigParser): return ('Unable to write in the config file', 'Error') return ("%s=%s" % (option, value), 'Info') - def remove_and_save(self, option, section=DEFSECTION): + def remove_and_save(self, option: str, + section=DEFSECTION) -> Tuple[str, str]: """ Remove an option and then save it the config file """ @@ -449,7 +459,7 @@ class Config(RawConfigParser): return ('Unable to save the config file', 'Error') return ('Option %s deleted' % option, 'Info') - def silent_set(self, option, value, section=DEFSECTION): + def silent_set(self, option: str, value: ConfigValue, section=DEFSECTION): """ Set a value, save, and return True on success and False on failure """ @@ -460,7 +470,7 @@ class Config(RawConfigParser): RawConfigParser.set(self, section, option, value) return self.write_in_file(section, option, value) - def set(self, option, value, section=DEFSECTION): + def set(self, option: str, value: ConfigValue, section=DEFSECTION): """ Set the value of an option temporarily """ @@ -469,11 +479,11 @@ class Config(RawConfigParser): except NoSectionError: pass - def to_dict(self): + def to_dict(self) -> Dict[str, Dict[str, ConfigValue]]: """ Returns a dict of the form {section: {option: value, option: value}, …} """ - res = {} + res = {} # Dict[str, Dict[str, ConfigValue]] for section in self.sections(): res[section] = {} for option in self.options(section): @@ -481,7 +491,7 @@ class Config(RawConfigParser): return res -def find_line(lines, start, end, option): +def find_line(lines: List[str], start: int, end: int, option: str) -> int: """ Get the number of the line containing the option in the relevant part of the config file. @@ -497,7 +507,7 @@ def find_line(lines, start, end, option): return -1 -def file_ok(filepath): +def file_ok(filepath: Path) -> bool: """ Returns True if the file exists and is readable and writeable, False otherwise. @@ -507,7 +517,7 @@ def file_ok(filepath): return bool(val) -def get_image_cache(): +def get_image_cache() -> Path: if not config.get('extract_inline_images'): return None tmp_dir = config.get('tmp_image_dir') @@ -664,16 +674,16 @@ LOGGING_CONFIG = { firstrun = False # Global config object. Is setup in poezio.py -config = None +config = None # type: Optional[Config] # The logger object for this module -log = None +log = None # type: Optional[logging.Logger] # The command-line options options = None # delayed import from common.py -safeJID = None +safeJID = None # type: Optional[Callable] # the global log dir LOG_DIR = '' diff --git a/poezio/contact.py b/poezio/contact.py index b07be6f6..27b0598c 100644 --- a/poezio/contact.py +++ b/poezio/contact.py @@ -9,11 +9,14 @@ Defines the Resource and Contact classes, which are used in the roster. """ +from collections import defaultdict import logging -log = logging.getLogger(__name__) +from typing import Dict, Iterator, List, Optional, Union from poezio.common import safeJID -from collections import defaultdict +from slixmpp import JID + +log = logging.getLogger(__name__) class Resource: @@ -26,29 +29,30 @@ class Resource: """ data: the dict to use as a source """ - self._jid = jid # Full jid - self._data = data + # Full JID + self._jid = jid # type: str + self._data = data # type: Dict[str, Union[str, int]] @property - def jid(self): + def jid(self) -> str: return self._jid @property - def priority(self): + def priority(self) -> int: return self._data.get('priority') or 0 @property - def presence(self): + def presence(self) -> str: return self._data.get('show') or '' @property - def status(self): + def status(self) -> str: return self._data.get('status') or '' - def __repr__(self): + def __repr__(self) -> str: return '<%s>' % self._jid - def __eq__(self, value): + def __eq__(self, value: object) -> bool: if not isinstance(value, Resource): return False return self.jid == value.jid and self._data == value._data @@ -66,22 +70,22 @@ class Contact: item: a slixmpp RosterItem pointing to that contact """ self.__item = item - self.folded_states = defaultdict(lambda: True) + self.folded_states = defaultdict(lambda: True) # type: Dict[str, bool] self._name = '' self.avatar = None self.error = None - self.tune = {} - self.gaming = {} + self.tune = {} # type: Dict[str, str] + self.gaming = {} # type: Dict[str, str] self.mood = '' self.activity = '' @property - def groups(self): + def groups(self) -> List[str]: """Name of the groups the contact is in""" return self.__item['groups'] or ['none'] @property - def bare_jid(self): + def bare_jid(self) -> JID: """The bare jid of the contact""" return self.__item.jid @@ -119,29 +123,29 @@ class Contact: self.__item['pending_out'] = value @property - def resources(self): + def resources(self) -> Iterator[Resource]: """List of the available resources as Resource objects""" return (Resource('%s%s' % (self.bare_jid, ('/' + key) if key else ''), self.__item.resources[key]) for key in self.__item.resources.keys()) @property - def subscription(self): + def subscription(self) -> str: return self.__item['subscription'] def __contains__(self, value): return value in self.__item.resources or safeJID( value).resource in self.__item.resources - def __len__(self): + def __len__(self) -> int: """Number of resources""" return len(self.__item.resources) - def __bool__(self): - """This contacts exists even when he has no resources""" + def __bool__(self) -> bool: + """This contact exists even when he has no resources""" return True - def __getitem__(self, key): + def __getitem__(self, key) -> Optional[Resource]: """Return the corresponding Resource object, or None""" res = safeJID(key).resource resources = self.__item.resources @@ -164,23 +168,24 @@ class Contact: """Unsubscribe from this JID""" self.__item.unsubscribe() - def get(self, key, default=None): + def get(self, key: str, + default: Optional[Resource] = None) -> Optional[Resource]: """Same as __getitem__, but with a configurable default""" return self[key] or default - def get_resources(self): + def get_resources(self) -> List[Resource]: """Return all resources, sorted by priority """ compare_resources = lambda x: x.priority return sorted(self.resources, key=compare_resources, reverse=True) - def get_highest_priority_resource(self): + def get_highest_priority_resource(self) -> Optional[Resource]: """Return the resource with the highest priority""" resources = self.get_resources() if resources: return resources[0] return None - def folded(self, group_name='none'): + def folded(self, group_name='none') -> bool: """ Return the Folded state of a contact for this group """ @@ -192,7 +197,7 @@ class Contact: """ self.folded_states[group] = not self.folded_states[group] - def __repr__(self): + def __repr__(self) -> str: ret = ' bool: """ Add a callback to a given event. Note that if that event name doesn’t exist, it just returns False. @@ -64,7 +67,7 @@ class EventHandler: return True - def trigger(self, name, *args, **kwargs): + def trigger(self, name: str, *args, **kwargs): """ Call all the callbacks associated to the given event name. """ @@ -74,7 +77,7 @@ class EventHandler: for callback in callbacks: callback(*args, **kwargs) - def del_event_handler(self, name, callback): + def del_event_handler(self, name: str, callback: Callable): """ Remove the callback from the list of callbacks of the given event """ diff --git a/poezio/pep.py b/poezio/pep.py index a211b09b..52cc4cd5 100644 --- a/poezio/pep.py +++ b/poezio/pep.py @@ -3,6 +3,8 @@ Collection of mappings for PEP moods/activities extracted directly from the XEP """ +from typing import Dict + MOODS = { 'afraid': 'Afraid', 'amazed': 'Amazed', @@ -84,7 +86,7 @@ MOODS = { 'undefined': 'Undefined', 'weak': 'Weak', 'worried': 'Worried' -} +} # type: Dict[str, str] ACTIVITIES = { 'doing_chores': { @@ -202,4 +204,4 @@ ACTIVITIES = { 'studying': 'Studying', 'other': 'Other', } -} +} # type: Dict[str, Dict[str, str]] diff --git a/poezio/timed_events.py b/poezio/timed_events.py index 5a5cadc2..3eeca2b3 100644 --- a/poezio/timed_events.py +++ b/poezio/timed_events.py @@ -12,7 +12,8 @@ Once created, they must be added to the list of checked events with :py:func:`.PluginAPI.add_timed_event` (within a plugin). """ -import datetime +from datetime import datetime +from typing import Callable class DelayedEvent: @@ -21,7 +22,7 @@ class DelayedEvent: Use it if you want an event to happen in, e.g. 6 seconds. """ - def __init__(self, delay, callback, *args): + def __init__(self, delay: int, callback: Callable, *args) -> None: """ Create a new DelayedEvent. @@ -43,7 +44,7 @@ class TimedEvent(DelayedEvent): The callback and its arguments should be passed as the lasts arguments. """ - def __init__(self, date, callback, *args): + def __init__(self, date: datetime, callback: Callable, *args) -> None: """ Create a new timed event. @@ -51,6 +52,6 @@ class TimedEvent(DelayedEvent): :param function callback: The handler that will be executed. :param \*args: Optional arguments passed to the handler. """ - delta = date - datetime.datetime.now() + delta = date - datetime.now() delay = delta.total_seconds() DelayedEvent.__init__(self, delay, callback, *args) diff --git a/poezio/user.py b/poezio/user.py index a389e3f5..3792eca8 100644 --- a/poezio/user.py +++ b/poezio/user.py @@ -9,14 +9,16 @@ Define the user class. An user is a MUC participant, not a roster contact (see contact.py) """ -from random import choice +import logging from datetime import timedelta, datetime from hashlib import md5 -from poezio import xhtml, colors +from random import choice +from typing import Optional, Tuple +from poezio import xhtml, colors from poezio.theming import get_theme +from slixmpp import JID -import logging log = logging.getLogger(__name__) ROLE_DICT = {'': 0, 'none': 0, 'visitor': 1, 'participant': 2, 'moderator': 3} @@ -30,19 +32,21 @@ class User: 'status', 'role', 'nick', 'color') def __init__(self, - nick, - affiliation, - show, - status, - role, - jid, + nick: str, + affiliation: str, + show: str, + status: str, + role: str, + jid: JID, deterministic=True, color=''): - self.last_talked = datetime(1, 1, 1) # The oldest possible time + # The oldest possible time + self.last_talked = datetime(1, 1, 1) # type: datetime self.update(affiliation, show, status, role) self.change_nick(nick) - self.jid = jid - self.chatstate = None + self.jid = jid # type: JID + self.chatstate = None # type: Optional[str] + self.color = (1, 1) # type: Tuple[int, int] if color != '': self.change_color(color, deterministic) else: @@ -63,7 +67,7 @@ class User: 16) % mod self.color = theme.LIST_COLOR_NICKNAMES[nick_pos] - def update(self, affiliation, show, status, role): + def update(self, affiliation: str, show: str, status: str, role: str): self.affiliation = affiliation self.show = show self.status = status @@ -71,12 +75,12 @@ class User: role = '' self.role = role - def change_nick(self, nick): + def change_nick(self, nick: str): self.nick = nick - def change_color(self, color_name, deterministic=False): + def change_color(self, color_name: Optional[str], deterministic=False): color = xhtml.colors.get(color_name) - if color == None: + if color is None: log.error('Unknown color "%s"', color_name) if deterministic: self.set_deterministic_color() @@ -85,13 +89,13 @@ class User: else: self.color = (color, -1) - def set_last_talked(self, time): + def set_last_talked(self, time: datetime): """ time: datetime object """ self.last_talked = time - def has_talked_since(self, t): + def has_talked_since(self, t: int) -> bool: """ t: int Return True if the user talked since the last s seconds @@ -103,28 +107,28 @@ class User: return False return True - def __repr__(self): + def __repr__(self) -> str: return ">%s<" % (self.nick) - def __eq__(self, b): + def __eq__(self, b) -> bool: return self.role == b.role and self.nick == b.nick - def __gt__(self, b): + def __gt__(self, b) -> bool: if ROLE_DICT[self.role] == ROLE_DICT[b.role]: return self.nick.lower() > b.nick.lower() return ROLE_DICT[self.role] < ROLE_DICT[b.role] - def __ge__(self, b): + def __ge__(self, b) -> bool: if ROLE_DICT[self.role] == ROLE_DICT[b.role]: return self.nick.lower() >= b.nick.lower() return ROLE_DICT[self.role] <= ROLE_DICT[b.role] - def __lt__(self, b): + def __lt__(self, b) -> bool: if ROLE_DICT[self.role] == ROLE_DICT[b.role]: return self.nick.lower() < b.nick.lower() return ROLE_DICT[self.role] > ROLE_DICT[b.role] - def __le__(self, b): + def __le__(self, b) -> bool: if ROLE_DICT[self.role] == ROLE_DICT[b.role]: return self.nick.lower() <= b.nick.lower() return ROLE_DICT[self.role] >= ROLE_DICT[b.role] diff --git a/poezio/xdg.py b/poezio/xdg.py index e4b336a5..0b63998c 100644 --- a/poezio/xdg.py +++ b/poezio/xdg.py @@ -12,16 +12,17 @@ https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html from pathlib import Path from os import environ +from typing import Dict # $HOME has already been checked to not be None in test_env(). DEFAULT_PATHS = { 'XDG_CONFIG_HOME': Path.home() / '.config', 'XDG_DATA_HOME': Path.home() / '.local' / 'share', 'XDG_CACHE_HOME': Path.home() / '.cache', -} +} # type: Dict[str, Path] -def _get_directory(variable: str): +def _get_directory(variable: str) -> Path: """ returns the default configuration directory path """ diff --git a/poezio/xhtml.py b/poezio/xhtml.py index 7de048ae..9632855a 100644 --- a/poezio/xhtml.py +++ b/poezio/xhtml.py @@ -11,7 +11,6 @@ xhtml code to shell colors, poezio colors to xhtml code """ -import curses import hashlib import re from base64 import b64encode, b64decode @@ -22,6 +21,7 @@ from pathlib import Path from io import BytesIO from xml import sax from xml.sax import saxutils +from typing import Dict, Optional, Tuple from slixmpp.xmlstream import ET from poezio.config import config @@ -180,7 +180,7 @@ colors = { 'whitesmoke': 255, 'yellow': 226, 'yellowgreen': 149 -} +} # type: Dict[str, int] whitespace_re = re.compile(r'\s+') @@ -194,7 +194,8 @@ xhtml_simple_attr_re = re.compile(r'\x19\d') def get_body_from_message_stanza(message, use_xhtml=False, - extract_images_to=None): + extract_images_to: Optional[Path] = None + ) -> str: """ Returns a string with xhtml markups converted to poezio colors if there's an xhtml_im element, or @@ -213,12 +214,13 @@ def get_body_from_message_stanza(message, return content or " " -def rgb_to_html(rgb): +def rgb_to_html(rgb: Tuple[float, float, float]) -> str: + """Get the RGB HTML value""" r, g, b = rgb return '#%02X%02X%02X' % (round(r * 255), round(g * 255), round(b * 255)) -def ncurses_color_to_html(color): +def ncurses_color_to_html(color: int) -> str: """ Takes an int between 0 and 256 and returns a string of the form #XXXXXX representing an @@ -227,7 +229,7 @@ def ncurses_color_to_html(color): return rgb_to_html(ncurses_color_to_rgb(color)) -def _parse_css_color(name): +def _parse_css_color(name: str) -> int: if name[0] == '#': name = name[1:] length = len(name) @@ -254,7 +256,7 @@ def _parse_css_color(name): return -1 -def _parse_css(css): +def _parse_css(css: str) -> str: shell = '' rules = css.split(';') for rule in rules: @@ -285,7 +287,7 @@ def _parse_css(css): return shell -def _trim(string): +def _trim(string: str) -> str: return re.sub(whitespace_re, ' ', string) @@ -297,11 +299,11 @@ def get_hash(data: bytes) -> str: class XHTMLHandler(sax.ContentHandler): - def __init__(self, force_ns=False, tmp_image_dir=None): - self.builder = [] - self.formatting = [] - self.attrs = [] - self.list_state = [] + def __init__(self, force_ns=False, tmp_image_dir: Optional[Path] = None): + self.builder = [] # type: List[str] + self.formatting = [] # type: List[str] + self.attrs = [] # type: List[Dict[str, str]] + self.list_state = [] # type: List[Union[str, int]] self.is_pre = False self.a_start = 0 # do not care about xhtml-in namespace @@ -311,12 +313,12 @@ class XHTMLHandler(sax.ContentHandler): self.enable_css_parsing = config.get('enable_css_parsing') @property - def result(self): + def result(self) -> str: sanitized = re.sub(poezio_color_double, r'\1', ''.join(self.builder).strip()) return re.sub(poezio_format_trim, '\x19o', sanitized) - def append_formatting(self, formatting): + def append_formatting(self, formatting: str): self.formatting.append(formatting) self.builder.append(formatting) @@ -324,7 +326,7 @@ class XHTMLHandler(sax.ContentHandler): self.formatting.pop() self.builder.append('\x19o' + ''.join(self.formatting)) - def characters(self, characters): + def characters(self, characters: str): self.builder.append(characters if self.is_pre else _trim(characters)) def startElementNS(self, name, _, attrs): @@ -435,7 +437,8 @@ class XHTMLHandler(sax.ContentHandler): builder.append(' [' + attrs['title'] + ']') -def xhtml_to_poezio_colors(xml, force=False, tmp_dir=None): +def xhtml_to_poezio_colors(xml, force=False, + tmp_dir: Optional[Path] = None) -> str: if isinstance(xml, str): xml = xml.encode('utf8') elif not isinstance(xml, bytes): @@ -449,7 +452,7 @@ def xhtml_to_poezio_colors(xml, force=False, tmp_dir=None): return handler.result -def clean_text(s): +def clean_text(s: str) -> str: """ Remove all xhtml-im attributes (\x19etc) from the string with the complete color format, i.e \x19xxx} @@ -458,7 +461,7 @@ def clean_text(s): return s -def clean_text_simple(string): +def clean_text_simple(string: str) -> str: """ Remove all \x19 from the string formatted with simple colors: \x198 @@ -470,13 +473,13 @@ def clean_text_simple(string): return string -def convert_simple_to_full_colors(text): +def convert_simple_to_full_colors(text: str) -> str: """ takes a \x19n formatted string and returns a \x19n} formatted one. """ # TODO, have a single list of this. This is some sort of - # dusplicate from windows.format_chars + # duplicate from windows.format_chars mapping = str.maketrans({ '\x0E': '\x19b', '\x0F': '\x19o', @@ -508,14 +511,14 @@ number_to_color_names = { 5: 'violet', 6: 'turquoise', 7: 'white' -} +} # type: Dict[int, str] -def format_inline_css(_dict): +def format_inline_css(_dict: Dict[str, str]) -> str: return ''.join(('%s: %s;' % (key, value) for key, value in _dict.items())) -def poezio_colors_to_html(string): +def poezio_colors_to_html(string: str) -> str: """ Convert poezio colors to html (e.g. \x191}: ) -- cgit v1.2.3