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

Commits:

7 changed files:

Changes:

  • gajim/common/helpers.py
    ... ... @@ -80,6 +80,8 @@
    80 80
     from gajim.common.const import URIAction
    
    81 81
     from gajim.common.const import GIO_TLS_ERRORS
    
    82 82
     from gajim.common.const import SHOW_LIST
    
    83
    +from gajim.common.regex import INVALID_XML_CHARS_REGEX
    
    84
    +from gajim.common.regex import STH_AT_STH_DOT_STH_REGEX
    
    83 85
     from gajim.common.structs import URI
    
    84 86
     
    
    85 87
     
    
    ... ... @@ -632,7 +634,7 @@ def get_auth_sha(sid, initiator, target):
    632 634
     
    
    633 635
     def remove_invalid_xml_chars(string_):
    
    634 636
         if string_:
    
    635
    -        string_ = re.sub(app.interface.invalid_XML_chars_re, '', string_)
    
    637
    +        string_ = re.sub(INVALID_XML_CHARS_REGEX, '', string_)
    
    636 638
         return string_
    
    637 639
     
    
    638 640
     def get_random_string(count=16):
    
    ... ... @@ -1067,7 +1069,7 @@ def parse_uri(uri):
    1067 1069
             uri = uri[4:]
    
    1068 1070
             return URI(type=URIType.TEL, data=uri)
    
    1069 1071
     
    
    1070
    -    if app.interface.sth_at_sth_dot_sth_re.match(uri):
    
    1072
    +    if STH_AT_STH_DOT_STH_REGEX.match(uri):
    
    1071 1073
             return URI(type=URIType.AT, data=uri)
    
    1072 1074
     
    
    1073 1075
         if uri.startswith('geo:'):
    

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

  • gajim/conversation_textview.py
    ... ... @@ -44,6 +44,11 @@
    44 44
     from gajim.common.const import Trust
    
    45 45
     from gajim.common.const import URI_SCHEMES
    
    46 46
     from gajim.common.helpers import to_user_string
    
    47
    +from gajim.common.regex import STH_AT_STH_DOT_STH_REGEX
    
    48
    +from gajim.common.regex import BASIC_REGEX
    
    49
    +from gajim.common.regex import LINK_REGEX
    
    50
    +from gajim.common.regex import EMOT_AND_BASIC_REGEX
    
    51
    +from gajim.common.regex import EMOT_AND_LINK_REGEX
    
    47 52
     
    
    48 53
     from gajim.gui import util
    
    49 54
     from gajim.gui.util import get_cursor
    
    ... ... @@ -584,9 +589,17 @@ def detect_and_print_special_text(self, otext, other_tags, graphics=True,
    584 589
             # basic: links + mail + formatting is always checked (we like that)
    
    585 590
             if app.settings.get('emoticons_theme') and graphics:
    
    586 591
                 # search for emoticons & urls
    
    587
    -            iterator = app.interface.emot_and_basic_re.finditer(otext)
    
    588
    -        else: # search for just urls + mail + formatting
    
    589
    -            iterator = app.interface.basic_pattern_re.finditer(otext)
    
    592
    +            if app.settings.get('ascii_formatting'):
    
    593
    +                regex = EMOT_AND_BASIC_REGEX
    
    594
    +            else:
    
    595
    +                regex = EMOT_AND_LINK_REGEX
    
    596
    +        else:
    
    597
    +            if app.settings.get('ascii_formatting'):
    
    598
    +                # search for just urls + mail + formatting
    
    599
    +                regex = BASIC_REGEX
    
    600
    +            else: # search for just urls + mail
    
    601
    +                regex = LINK_REGEX
    
    602
    +        iterator = regex.finditer(otext)
    
    590 603
             if iter_:
    
    591 604
                 end_iter = iter_
    
    592 605
             else:
    
    ... ... @@ -693,7 +706,7 @@ def print_special_text(self, special_text, other_tags, graphics=True,
    693 706
                 tags.append('mail')
    
    694 707
             elif special_text.startswith('xmpp:') and not is_xhtml_link:
    
    695 708
                 tags.append('xmpp')
    
    696
    -        elif app.interface.sth_at_sth_dot_sth_re.match(special_text) and\
    
    709
    +        elif STH_AT_STH_DOT_STH_REGEX.match(special_text) and \
    
    697 710
             not is_xhtml_link:
    
    698 711
                 # it's a JID or mail
    
    699 712
                 tags.append('sth_at_sth')
    

  • gajim/gtk/message_input.py
    ... ... @@ -28,6 +28,7 @@
    28 28
     from gajim.common import app
    
    29 29
     from gajim.common.i18n import _
    
    30 30
     from gajim.common.const import StyleAttr
    
    31
    +from gajim.common.regex import LINK_REGEX
    
    31 32
     
    
    32 33
     from .util import scroll_to_end
    
    33 34
     
    
    ... ... @@ -207,7 +208,7 @@ def make_clickable_urls(self, text):
    207 208
             index = 0
    
    208 209
     
    
    209 210
             new_text = ''
    
    210
    -        iterator = app.interface.link_pattern_re.finditer(text)
    
    211
    +        iterator = LINK_REGEX.finditer(text)
    
    211 212
             for match in iterator:
    
    212 213
                 start, end = match.span()
    
    213 214
                 url = text[start:end]
    

  • gajim/gui_interface.py
    ... ... @@ -33,7 +33,6 @@
    33 33
     
    
    34 34
     import os
    
    35 35
     import sys
    
    36
    -import re
    
    37 36
     import time
    
    38 37
     import json
    
    39 38
     import logging
    
    ... ... @@ -101,7 +100,6 @@
    101 100
     from gajim.gui.dialogs import InputDialog
    
    102 101
     from gajim.gui.dialogs import PassphraseDialog
    
    103 102
     from gajim.gui.filechoosers import FileChooserDialog
    
    104
    -from gajim.gui.emoji_data import emoji_data
    
    105 103
     from gajim.gui.filetransfer import FileTransfersWindow
    
    106 104
     from gajim.gui.filetransfer_progress import FileTransferProgress
    
    107 105
     from gajim.gui.roster_item_exchange import RosterItemExchangeWindow
    
    ... ... @@ -1296,112 +1294,6 @@ def handle_event(self, account, fjid, type_):
    1296 1294
                 if isinstance(ctrl, ChatControlBase):
    
    1297 1295
                     ctrl.scroll_to_end()
    
    1298 1296
     
    
    1299
    -################################################################################
    
    1300
    -### Methods dealing with emoticons
    
    1301
    -################################################################################
    
    1302
    -
    
    1303
    -    @property
    
    1304
    -    def basic_pattern_re(self):
    
    1305
    -        if not self._basic_pattern_re:
    
    1306
    -            self._basic_pattern_re = re.compile(self.basic_pattern,
    
    1307
    -                re.IGNORECASE)
    
    1308
    -        return self._basic_pattern_re
    
    1309
    -
    
    1310
    -    @property
    
    1311
    -    def emot_and_basic_re(self):
    
    1312
    -        if not self._emot_and_basic_re:
    
    1313
    -            self._emot_and_basic_re = re.compile(
    
    1314
    -                self.emot_and_basic, re.IGNORECASE)
    
    1315
    -        return self._emot_and_basic_re
    
    1316
    -
    
    1317
    -    @property
    
    1318
    -    def sth_at_sth_dot_sth_re(self):
    
    1319
    -        if not self._sth_at_sth_dot_sth_re:
    
    1320
    -            self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
    
    1321
    -        return self._sth_at_sth_dot_sth_re
    
    1322
    -
    
    1323
    -    @property
    
    1324
    -    def invalid_XML_chars_re(self):
    
    1325
    -        if not self._invalid_XML_chars_re:
    
    1326
    -            self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
    
    1327
    -        return self._invalid_XML_chars_re
    
    1328
    -
    
    1329
    -    def make_regexps(self):
    
    1330
    -        # regexp meta characters are:  . ^ $ * + ? { } [ ] \ | ( )
    
    1331
    -        # one escapes the metachars with \
    
    1332
    -        # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
    
    1333
    -        # \s matches any whitespace character
    
    1334
    -        # \w any alphanumeric character
    
    1335
    -        # \W any non-alphanumeric character
    
    1336
    -        # \b means word boundary. This is a zero-width assertion that
    
    1337
    -        #    matches only at the beginning or end of a word.
    
    1338
    -        # ^ matches at the beginning of lines
    
    1339
    -        #
    
    1340
    -        # * means 0 or more times
    
    1341
    -        # + means 1 or more times
    
    1342
    -        # ? means 0 or 1 time
    
    1343
    -        # | means or
    
    1344
    -        # [^*] anything but '*' (inside [] you don't have to escape metachars)
    
    1345
    -        # [^\s*] anything but whitespaces and '*'
    
    1346
    -        # (?<!\S) is a one char lookbehind assertion and asks for any leading
    
    1347
    -        #         whitespace
    
    1348
    -        # and matches beginning of lines so we have correct formatting detection
    
    1349
    -        # even if the text is just '*foo*'
    
    1350
    -        # (?!\S) is the same thing but it's a lookahead assertion
    
    1351
    -        # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
    
    1352
    -        #                the end
    
    1353
    -        # so http://be) will match http://be and http://be)be) will match
    
    1354
    -        # http://be)be
    
    1355
    -
    
    1356
    -        self._basic_pattern_re = None
    
    1357
    -        self._emot_and_basic_re = None
    
    1358
    -        self._sth_at_sth_dot_sth_re = None
    
    1359
    -        self._invalid_XML_chars_re = None
    
    1360
    -
    
    1361
    -        legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
    
    1362
    -            r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
    
    1363
    -            r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
    
    1364
    -            r"|%[A-Fa-f0-9]{2})+"\
    
    1365
    -            r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
    
    1366
    -        # NOTE: it's ok to catch www.gr such stuff exist!
    
    1367
    -
    
    1368
    -        # FIXME: recognize xmpp: and treat it specially
    
    1369
    -        links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
    
    1370
    -            r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
    
    1371
    -            r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
    
    1372
    -
    
    1373
    -        # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
    
    1374
    -        mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
    
    1375
    -
    
    1376
    -        # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
    
    1377
    -        # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
    
    1378
    -        formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
    
    1379
    -            r'(?<!\S)' r'~[^\s~]' r'([^~]*[^\s~])?' r'~(?!\S)|'\
    
    1380
    -            r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
    
    1381
    -
    
    1382
    -        basic_pattern = links + '|' + mail + '|' + legacy_prefixes
    
    1383
    -
    
    1384
    -        link_pattern = basic_pattern
    
    1385
    -        self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
    
    1386
    -
    
    1387
    -        if app.settings.get('ascii_formatting'):
    
    1388
    -            basic_pattern += formatting
    
    1389
    -        self.basic_pattern = basic_pattern
    
    1390
    -
    
    1391
    -        # because emoticons match later (in the string) they need to be after
    
    1392
    -        # basic matches that may occur earlier
    
    1393
    -        emoticons = emoji_data.get_regex()
    
    1394
    -
    
    1395
    -        self.emot_and_basic = '%s|%s' % (basic_pattern, emoticons)
    
    1396
    -
    
    1397
    -        # at least one character in 3 parts (before @, after @, after .)
    
    1398
    -        self.sth_at_sth_dot_sth = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
    
    1399
    -
    
    1400
    -        # Invalid XML chars
    
    1401
    -        self.invalid_XML_chars = '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\
    
    1402
    -            '[\ud800-\udfff]|[\ufffe-\uffff]'
    
    1403
    -
    
    1404
    -
    
    1405 1297
     ################################################################################
    
    1406 1298
     ### Methods for opening new messages controls
    
    1407 1299
     ################################################################################
    
    ... ... @@ -2114,15 +2006,6 @@ def __init__(self):
    2114 2006
     
    
    2115 2007
             self.handlers = {}
    
    2116 2008
             self.roster = None
    
    2117
    -        self._invalid_XML_chars_re = None
    
    2118
    -        self._basic_pattern_re = None
    
    2119
    -        self._emot_and_basic_re = None
    
    2120
    -        self._sth_at_sth_dot_sth_re = None
    
    2121
    -        self.link_pattern_re = None
    
    2122
    -        self.invalid_XML_chars = None
    
    2123
    -        self.basic_pattern = None
    
    2124
    -        self.emot_and_basic = None
    
    2125
    -        self.sth_at_sth_dot_sth = None
    
    2126 2009
     
    
    2127 2010
             self.avatar_storage = AvatarStorage()
    
    2128 2011
     
    
    ... ... @@ -2199,8 +2082,6 @@ def __init__(self):
    2199 2082
                 from gajim.gui.emoji_chooser import emoji_chooser
    
    2200 2083
                 emoji_chooser.load()
    
    2201 2084
     
    
    2202
    -        self.make_regexps()
    
    2203
    -
    
    2204 2085
             self.last_ftwindow_update = 0
    
    2205 2086
     
    
    2206 2087
             self._network_monitor = Gio.NetworkMonitor.get_default()
    

  • test/no_gui/test_regex.py
    1
    +import unittest
    
    2
    +
    
    3
    +from gajim import gui
    
    4
    +gui.init('gtk')
    
    5
    +import gajim.common.regex as regex
    
    6
    +
    
    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
    +
    
    12
    +            # the match should equal the string
    
    13
    +            str_span = (0, len(str_))
    
    14
    +            self.assertEqual(m.span(), str_span)
    
    15
    +
    
    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')
    
    20
    +
    
    21
    +        assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
    
    22
    +
    
    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
    +
    
    27
    +        assert_matches_all('mailto:test@example.org')
    
    28
    +
    
    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')
    
    33
    +
    
    34
    +
    
    35
    +if __name__ == "__main__":
    
    36
    +    unittest.main()

  • test/unit/test_gui_interface.py
    ... ... @@ -24,33 +24,6 @@ def close_app():
    24 24
             GLib.idle_add(close_app)
    
    25 25
             app.app.run()
    
    26 26
     
    
    27
    -    def test_links_regexp_entire(self):
    
    28
    -        sut = Interface()
    
    29
    -        def assert_matches_all(str_):
    
    30
    -            m = sut.basic_pattern_re.match(str_)
    
    31
    -
    
    32
    -            # the match should equal the string
    
    33
    -            str_span = (0, len(str_))
    
    34
    -            self.assertEqual(m.span(), str_span)
    
    35
    -
    
    36
    -        # these entire strings should be parsed as links
    
    37
    -        assert_matches_all('http://google.com/')
    
    38
    -        assert_matches_all('http://google.com')
    
    39
    -        assert_matches_all('http://www.google.ca/search?q=xmpp')
    
    40
    -
    
    41
    -        assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
    
    42
    -
    
    43
    -        assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)')
    
    44
    -        assert_matches_all(
    
    45
    -                'http://en.wikipedia.org/wiki/Protocol_%28computing%29')
    
    46
    -
    
    47
    -        assert_matches_all('mailto:test@example.org')
    
    48
    -
    
    49
    -        assert_matches_all('xmpp:example-node@example.com')
    
    50
    -        assert_matches_all('xmpp:example-node@example.com/some-resource')
    
    51
    -        assert_matches_all('xmpp:example-node@example.com?message')
    
    52
    -        assert_matches_all('xmpp://guest@example.com/support@example.com?message')
    
    53
    -
    
    54 27
     
    
    55 28
     if __name__ == "__main__":
    
    56 29
         unittest.main()