Philipp Hörist pushed to branch master at gajim / gajim

Commits:

4 changed files:

Changes:

  • gajim/common/regex.py
    1
    -import re
    
    2
    -
    
    3
    -
    
    4
    -def _get_link_pattern():
    
    5
    -    # regexp meta characters are:  . ^ $ * + ? { } [ ] \ | ( )
    
    6
    -    # one escapes the metachars with \
    
    7
    -    # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
    
    8
    -    # \s matches any whitespace character
    
    9
    -    # \w any alphanumeric character
    
    10
    -    # \W any non-alphanumeric character
    
    11
    -    # \b means word boundary. This is a zero-width assertion that
    
    12
    -    #    matches only at the beginning or end of a word.
    
    13
    -    # ^ matches at the beginning of lines
    
    14
    -    #
    
    15
    -    # * means 0 or more times
    
    16
    -    # + means 1 or more times
    
    17
    -    # ? means 0 or 1 time
    
    18
    -    # | means or
    
    19
    -    # [^*] anything but '*' (inside [] you don't have to escape metachars)
    
    20
    -    # [^\s*] anything but whitespaces and '*'
    
    21
    -    # (?<!\S) is a one char lookbehind assertion and asks for any leading
    
    22
    -    #         whitespace
    
    23
    -    # and matches beginning of lines so we have correct formatting detection
    
    24
    -    # even if the text is just '*foo*'
    
    25
    -    # (?!\S) is the same thing but it's a lookahead assertion
    
    26
    -    # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
    
    27
    -    #                the end
    
    28
    -    # so http://be) will match http://be and http://be)be) will match
    
    29
    -    # http://be)be
    
    30
    -
    
    31
    -    legacy_prefixes = r'((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$'\
    
    32
    -        r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
    
    33
    -        r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
    
    34
    -        r'|%[A-Fa-f0-9]{2})+'\
    
    35
    -        r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
    
    36
    -    # NOTE: it's ok to catch www.gr such stuff exist!
    
    37
    -
    
    38
    -    # FIXME: recognize xmpp: and treat it specially
    
    39
    -    links = r'((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:'\
    
    40
    -        r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
    
    41
    -        r'(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)'
    
    42
    -
    
    43
    -    # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
    
    44
    -    mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
    
    45
    -
    
    46
    -    link_pattern = links + '|' + mail + '|' + legacy_prefixes
    
    47
    -    return link_pattern
    
    48
    -
    
    49
    -
    
    50
    -def _get_basic_pattern():
    
    51
    -    basic_pattern = _get_link_pattern()
    
    52
    -    # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
    
    53
    -    # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
    
    54
    -    formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
    
    55
    -        r'(?<!\S)' r'~[^\s~]' r'([^~]*[^\s~])?' r'~(?!\S)|'\
    
    56
    -        r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
    
    57
    -    return basic_pattern + formatting
    
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    58 14
     
    
    15
    +import re
    
    59 16
     
    
    60
    -def _get_emot_and_basic_pattern(use_ascii_formatting: bool = True) -> str:
    
    61
    -    from gajim.gui.emoji_data import emoji_data
    
    62
    -    # because emoticons match later (in the string) they need to be after
    
    63
    -    # basic matches that may occur earlier
    
    64
    -    emoticons = emoji_data.get_regex()
    
    65
    -
    
    66
    -    if use_ascii_formatting:
    
    67
    -        pattern = _get_basic_pattern()
    
    68
    -    else:
    
    69
    -        pattern = _get_link_pattern()
    
    70
    -
    
    71
    -    return '%s|%s' % (pattern, emoticons)
    
    72
    -
    
    73
    -
    
    74
    -LINK_REGEX = re.compile(_get_link_pattern(), re.I | re.U)
    
    75
    -
    
    76
    -# link pattern + ASCII formatting
    
    77
    -BASIC_REGEX = re.compile(_get_basic_pattern(), re.IGNORECASE)
    
    78
    -
    
    79
    -# emoticons + link pattern
    
    80
    -EMOT_AND_LINK_REGEX = re.compile(_get_emot_and_basic_pattern(False),
    
    81
    -                                 re.IGNORECASE)
    
    82
    -
    
    83
    -# emoticons + link pattern + ASCII formatting
    
    84
    -EMOT_AND_BASIC_REGEX = re.compile(_get_emot_and_basic_pattern(True),
    
    85
    -                                  re.IGNORECASE)
    
    86 17
     
    
    87 18
     INVALID_XML_CHARS_REGEX = re.compile(
    
    88 19
         '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|[\ud800-\udfff]|[\ufffe-\uffff]')
    

  • gajim/gtk/control.py
    ... ... @@ -502,7 +502,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None:
    502 502
             row = self.conversation_view.get_row_by_log_line_id(log_line_id)
    
    503 503
             if row is None:
    
    504 504
                 # Clear view and reload conversation around timestamp
    
    505
    -            self._scrolled_view.block_request_signal(True)
    
    505
    +            self._scrolled_view.block_signals(True)
    
    506 506
                 self.reset_view()
    
    507 507
                 before, at_after = app.storage.archive.get_conversation_around(
    
    508 508
                     self.contact.account, self.contact.jid, timestamp)
    
    ... ... @@ -512,7 +512,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None:
    512 512
             GLib.idle_add(
    
    513 513
                 self.conversation_view.scroll_to_message_and_highlight,
    
    514 514
                 log_line_id)
    
    515
    -        GLib.idle_add(self._scrolled_view.block_request_signal, False)
    
    515
    +        GLib.idle_add(self._scrolled_view.block_signals, False)
    
    516 516
     
    
    517 517
         def _fetch_n_lines_history(self,
    
    518 518
                                    _scrolled: Gtk.ScrolledWindow,
    
    ... ... @@ -520,7 +520,7 @@ def _fetch_n_lines_history(self,
    520 520
                                    n_lines: int
    
    521 521
                                    ) -> None:
    
    522 522
     
    
    523
    -        self._scrolled_view.block_request_signal(True)
    
    523
    +        self._scrolled_view.block_signals(True)
    
    524 524
     
    
    525 525
             if before:
    
    526 526
                 row = self.conversation_view.get_first_message_row()
    
    ... ... @@ -541,7 +541,7 @@ def _fetch_n_lines_history(self,
    541 541
     
    
    542 542
             if not messages:
    
    543 543
                 self._scrolled_view.set_history_complete(before, True)
    
    544
    -            self._scrolled_view.block_request_signal(False)
    
    544
    +            self._scrolled_view.block_signals(False)
    
    545 545
                 return
    
    546 546
     
    
    547 547
             self.add_messages(messages)
    
    ... ... @@ -553,7 +553,7 @@ def _fetch_n_lines_history(self,
    553 553
             #    if self.conversation_view.reduce_message_count(before):
    
    554 554
             #        self._scrolled_view.set_history_complete(before, False)
    
    555 555
     
    
    556
    -        self._scrolled_view.block_request_signal(False)
    
    556
    +        self._scrolled_view.block_signals(False)
    
    557 557
     
    
    558 558
         def add_messages(self, messages: list[ConversationRow]):
    
    559 559
             for msg in messages:
    

  • gajim/gtk/conversation/scrolled.py
    ... ... @@ -14,6 +14,7 @@
    14 14
     
    
    15 15
     from __future__ import annotations
    
    16 16
     
    
    17
    +from typing import Any
    
    17 18
     from typing import Optional
    
    18 19
     
    
    19 20
     from gi.repository import Gtk
    
    ... ... @@ -66,7 +67,7 @@ def __init__(self) -> None:
    66 67
             self._upper_complete: bool = False
    
    67 68
             self._lower_complete: bool = False
    
    68 69
             self._requesting: Optional[str] = None
    
    69
    -        self._block_request_signal = False
    
    70
    +        self._block_signals = False
    
    70 71
     
    
    71 72
             vadjustment = self.get_vadjustment()
    
    72 73
             vadjustment.connect('notify::upper', self._on_adj_upper_changed)
    
    ... ... @@ -77,14 +78,16 @@ def __init__(self) -> None:
    77 78
             self.set_focus_vadjustment(Gtk.Adjustment())
    
    78 79
     
    
    79 80
         def clear(self) -> None:
    
    80
    -        self._block_request_signal = True
    
    81
    +        self._block_signals = True
    
    82
    +        self._reset()
    
    81 83
             self._view.clear()
    
    82 84
     
    
    83 85
         def switch_contact(self, contact: ChatContactT) -> None:
    
    84
    -        self.reset()
    
    86
    +        self._block_signals = True
    
    87
    +        self._reset()
    
    85 88
             self._view.switch_contact(contact)
    
    86
    -        self._block_request_signal = False
    
    87
    -        self.emit('request-history', True)
    
    89
    +        self._block_signals = False
    
    90
    +        self._emit('request-history', True)
    
    88 91
     
    
    89 92
         def get_autoscroll(self) -> bool:
    
    90 93
             return self._autoscroll
    
    ... ... @@ -92,18 +95,25 @@ def get_autoscroll(self) -> bool:
    92 95
         def get_view(self) -> ConversationView:
    
    93 96
             return self._view
    
    94 97
     
    
    95
    -    def block_request_signal(self, value: bool) -> None:
    
    96
    -        self._block_request_signal = value
    
    98
    +    def block_signals(self, value: bool) -> None:
    
    99
    +        self._block_signals = value
    
    97 100
     
    
    98
    -    def reset(self) -> None:
    
    101
    +    def _emit(self, signal_name: str, *args: Any) -> None:
    
    102
    +        if not self._block_signals:
    
    103
    +            self.emit(signal_name, *args)
    
    104
    +
    
    105
    +    def _reset(self) -> None:
    
    99 106
             self._current_upper = 0
    
    100 107
             self._request_history_at_upper = None
    
    101 108
             self._upper_complete = False
    
    102 109
             self._lower_complete = False
    
    103 110
             self._requesting = None
    
    104
    -        self._view.reset()
    
    105 111
             self.set_history_complete(True, False)
    
    106 112
     
    
    113
    +    def reset(self) -> None:
    
    114
    +        self._reset()
    
    115
    +        self._view.reset()
    
    116
    +
    
    107 117
         def set_history_complete(self, before: bool, complete: bool) -> None:
    
    108 118
             if before:
    
    109 119
                 self._upper_complete = complete
    
    ... ... @@ -134,11 +144,11 @@ def _on_adj_upper_changed(self,
    134 144
     
    
    135 145
             if upper == adj.get_page_size():
    
    136 146
                 # There is no scrollbar
    
    137
    -            if not self._block_request_signal:
    
    138
    -                self.emit('request-history', True)
    
    147
    +            if not self._block_signals:
    
    148
    +                self._emit('request-history', True)
    
    139 149
                 self._lower_complete = True
    
    140 150
                 self._autoscroll = True
    
    141
    -            self.emit('autoscroll-changed', self._autoscroll)
    
    151
    +            self._emit('autoscroll-changed', self._autoscroll)
    
    142 152
     
    
    143 153
             self._requesting = None
    
    144 154
     
    
    ... ... @@ -152,10 +162,10 @@ def _on_adj_value_changed(self,
    152 162
             bottom = adj.get_upper() - adj.get_page_size()
    
    153 163
             if (bottom - adj.get_value()) < 1:
    
    154 164
                 self._autoscroll = True
    
    155
    -            self.emit('autoscroll-changed', self._autoscroll)
    
    165
    +            self._emit('autoscroll-changed', self._autoscroll)
    
    156 166
             else:
    
    157 167
                 self._autoscroll = False
    
    158
    -            self.emit('autoscroll-changed', self._autoscroll)
    
    168
    +            self._emit('autoscroll-changed', self._autoscroll)
    
    159 169
     
    
    160 170
             if self._upper_complete:
    
    161 171
                 self._request_history_at_upper = None
    
    ... ... @@ -178,8 +188,8 @@ def _on_adj_value_changed(self,
    178 188
                 self._request_history_at_upper = adj.get_upper()
    
    179 189
                 # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    180 190
                 self.set_kinetic_scrolling(False)
    
    181
    -            if not self._block_request_signal:
    
    182
    -                self.emit('request-history', True)
    
    191
    +            if not self._block_signals:
    
    192
    +                self._emit('request-history', True)
    
    183 193
                 self._requesting = 'before'
    
    184 194
             elif (adj.get_upper() - (adj.get_value() + adj.get_page_size()) <
    
    185 195
                     distance):
    
    ... ... @@ -188,6 +198,6 @@ def _on_adj_value_changed(self,
    188 198
                     return
    
    189 199
                 # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    190 200
                 self.set_kinetic_scrolling(False)
    
    191
    -            if not self._block_request_signal:
    
    192
    -                self.emit('request-history', False)
    
    201
    +            if not self._block_signals:
    
    202
    +                self._emit('request-history', False)
    
    193 203
                 self._requesting = 'after'

  • test/no_gui/test_regex.py
    1
    +from typing import Any
    
    2
    +
    
    1 3
     import unittest
    
    2 4
     
    
    3
    -from gajim import gui
    
    4
    -gui.init('gtk')
    
    5
    -import gajim.common.regex as regex
    
    5
    +from gajim.common.styling import URI_RX
    
    6
    +from gajim.common.styling import ADDRESS_RX
    
    6 7
     
    
    7
    -class Test(unittest.TestCase):
    
    8
    -    def test_links_regexp_entire(self):
    
    9
    -        def assert_matches_all(str_):
    
    10
    -            m = regex.BASIC_REGEX.match(str_)
    
    11 8
     
    
    12
    -            # the match should equal the string
    
    13
    -            str_span = (0, len(str_))
    
    14
    -            self.assertEqual(m.span(), str_span)
    
    9
    +TEST_URIS = [
    
    10
    +    'http://google.com/',
    
    11
    +    'http://google.com',
    
    12
    +    'http://www.google.ca/search?q=xmpp',
    
    13
    +    'http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3',
    
    14
    +    'http://en.wikipedia.org/wiki/Protocol_(computing)',
    
    15
    +    'http://en.wikipedia.org/wiki/Protocol_%28computing%29',
    
    16
    +]
    
    15 17
     
    
    16
    -        # these entire strings should be parsed as links
    
    17
    -        assert_matches_all('http://google.com/')
    
    18
    -        assert_matches_all('http://google.com')
    
    19
    -        assert_matches_all('http://www.google.ca/search?q=xmpp')
    
    18
    +ADDRESSES = [
    
    19
    +    'mailto:test@example.org',
    
    20
    +    'xmpp:example-node@example.com',
    
    21
    +    # 'xmpp:example-node@example.com/some-resource', FIXME
    
    22
    +    'xmpp:example-node@example.com?message',
    
    23
    +]
    
    20 24
     
    
    21
    -        assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
    
    22 25
     
    
    23
    -        assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)')
    
    24
    -        assert_matches_all(
    
    25
    -                'http://en.wikipedia.org/wiki/Protocol_%28computing%29')
    
    26
    +class Test(unittest.TestCase):
    
    27
    +    def test_links_regexp_entire(self):
    
    28
    +        def assert_matches_all(regex: Any, uri_str: str) -> None:
    
    29
    +            m = regex.match(uri_str)
    
    30
    +            if m is None:
    
    31
    +                raise AssertionError('Failed: %s' % uri_str)
    
    32
    +
    
    33
    +            str_span = (0, len(uri_str))
    
    34
    +            self.assertEqual(m.span(), str_span, uri_str)
    
    26 35
     
    
    27
    -        assert_matches_all('mailto:test@example.org')
    
    36
    +        for uri in TEST_URIS:
    
    37
    +            assert_matches_all(URI_RX, uri)
    
    28 38
     
    
    29
    -        assert_matches_all('xmpp:example-node@example.com')
    
    30
    -        assert_matches_all('xmpp:example-node@example.com/some-resource')
    
    31
    -        assert_matches_all('xmpp:example-node@example.com?message')
    
    32
    -        assert_matches_all('xmpp://guest@example.com/support@example.com?message')
    
    39
    +        for addr in ADDRESSES:
    
    40
    +            assert_matches_all(ADDRESS_RX, addr)
    
    33 41
     
    
    34 42
     
    
    35
    -if __name__ == "__main__":
    
    43
    +if __name__ == '__main__':
    
    36 44
         unittest.main()