<commit_before># -*- coding: utf-8 -*-
# -*- test-case-name: pytils.test.test_typo -*-
# pytils - simple processing for russian strings
# Copyright (C) 2006-2008  <NAME>
#
# http://www.pyobject.ru/projects/pytils/
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
"""
Russian typography
"""
import re
import os

def _sub_patterns(patterns, text):
    """
    Apply re.sub to bunch of (pattern, repl)
    """
    for pattern, repl in patterns:
        text = re.sub(pattern, repl, text)
    return text    

## ---------- rules -------------
# rules is a regular function, 
# name convention is rl_RULENAME
def rl_testrule(x):
    """
    Rule for tests. Do nothing.
    """
    return x

def rl_cleanspaces(x):
    """
    Clean double spaces, trailing spaces, heading spaces,
    spaces before punctuations 
    """
    patterns = (
        # arguments for re.sub: pattern and repl
        # удаляем пробел перед знаками препинания
        (r' +([\.,?!\)]+)', r'\1'),
        # добавляем пробел после знака препинания, если только за ним нет другого
        (r'([\.,?!\)]+)([^\.!,?\)]+)', r'\1 \2'),
        # убираем пробел после открывающей скобки
        (r'(\S+)\s*(\()\s*(\S+)', r'\1 (\3'),
    )
    # удаляем двойные, начальные и конечные пробелы
    return os.linesep.join(
        ' '.join(part for part in line.split(' ') if part)
        for line in _sub_patterns(patterns, x).split(os.linesep)
    )

def rl_ellipsis(x):
    """
    Replace three dots to ellipsis
    """

    patterns = (
        # если больше трех точек, то не заменяем на троеточие
        # чтобы не было глупых .....->…..
        (r'([^\.]|^)\.\.\.([^\.]|$)', u'\\1\u2026\\2'),
        # если троеточие в начале строки или возле кавычки --
        # это цитата, пробел между троеточием и первым
        # словом нужно убрать
        (re.compile(u'(^|\\"|\u201c|\xab)[\\s\u202f]*\u2026[\\s\u202f]*([А-Яа-яA-Za-z])', re.UNICODE), u'\\1\u2026\\2'),
        
    )
    return _sub_patterns(patterns, x)

def rl_initials(x):
    """
    Replace space between initials and surname by thin space
    """
    return re.sub(
        re.compile(u'([А-Я])\\.[\\s\u202f]*([А-Я])\\.[\\s\u202f]*([А-Я][а-я]+)', re.UNICODE),
        u'\\1.\\2.\u2009\\3',
        x
    )

def rl_dashes(x):
    """
    Replace dash to long/medium dashes
    """
    patterns = (
        # тире
        (re.compile(u'(^|(.[\\s\u202f]))\\-\\-?(([\\s\u202f].)|$)', re.MULTILINE), u'\\1\u2014\\3'),
        # диапазоны между цифрами - en dash
        (re.compile(u'(\\d[\\s\u202f\u2009]*)\\-([\\s\u202f\u2009]*\d)', re.MULTILINE), u'\\1\u2013\\2'),
        # TODO: а что с минусом?
    )
    return _sub_patterns(patterns, x)

def rl_wordglue(x):
    """
    Glue (set nonbreakable space) short words with word before/after
    """
    patterns = (
        # частицы склеиваем с предыдущим словом
        (re.compile(u'(\\s+)(же|ли|ль|бы|б|ж|ка)([\\.,!\\?:;]?\\s+)', re.UNICODE), u'\u202f\\2\\3'),
        # склеиваем короткие слова со следующим словом
        (re.compile(u'(^|\\s|\u202f)([a-zA-ZА-Яа-я]{1,3})(\\s+)', re.UNICODE), u'\\1\\2\u202f'),
        # то же самое -- второй проход нужен когда два коротких слова подряд
        # например "он не почуял неладное"
        # если один раз, то "(1=^)(2=Он)( )не почуял неладное", пробел после "не" не обрабатывается
        (re.compile(u'(\u202f)([a-zA-ZА-Яа-я]{1,3})(\\s+)', re.UNICODE), u'\\1\\2\u202f'),
        # склеиваем тире с предыдущим словом 
        (re.compile(u'([\\s]+)([\u2014\\-]+)([\\s]+)', re.UNICODE), u'\u202f\\2\\3'),
        # склеиваем два последних слова в абзаце между собой
        # полагается, что абзацы будут передаваться отдельной строкой
        (re.compile(u'([^\\s\u202f]+)\s+([^\\s\u202f]+)$', re.UNICODE), u'\\1\u202f\\2'),
    )
    return _sub_patterns(patterns, x)

def rl_marks(x):
    """
    Replace +-, (c), (tm), (r), (p), etc by its typographic eqivalents
    """
    # простые замены, можно без регулярок
    replacements = (
        (u'(r)', u'\u00ae'), # ®
        (u'(R)', u'\u00ae'), # ®
        (u'(p)', u'\u00a7'), # §
        (u'(P)', u'\u00a7'), # §
        (u'(tm)', u'\u2122'), # ™
        (u'(TM)', u'\u2122'), # ™
    )
    patterns = (
        # копирайт ставится до года: © 2008 <NAME>
        (re.compile(u'\\([cCсС]\\)[\\s\u202f]*(\\d+)', re.UNICODE), u'\u00a9\u202f\\1'),
        (r'([^+])(\+\-|\-\+)', u'\\1\u00b1'), # ±
        # градусы с минусом
        (u'\\-(\\d+)[\\s\u202f]*([FCС][^\\w])', u'\u2212\\1\202f\u00b0\\2'), # −12 °C, −53 °F
        # градусы без минуса
        (u'(\\d+)[\\s\u202f]*([FCС][^\\w])', u'\\1\u202f\u00b0\\2'), # 12 °C, 53 °F
        # ® и ™ приклеиваются к предыдущему слову, без пробела
        (re.compile(u'([A-Za-zА-Яа-я\\!\\?])[\\s\u202f]*(\xae|\u2122)', re.UNICODE), u'\\1\\2'),
    )

    for what, to in replacements:
        x = x.replace(what, to)
    return _sub_patterns(patterns, x)

def rl_quotes(x):
    """
    Replace quotes by typographic quotes
    """
    patterns = (
        # открывающие кавычки ставятся обычно вплотную к слову слева
        # а закрывающие -- вплотную справа
        # открывающие русские кавычки-ёлочки
        (re.compile(u'(^|\\s|\u202f|\\()\\"([А-Яа-я0-9,\\-:\\/\\.])', re.UNICODE), u'\\1\xab\\2'),
        # закрывающие русские кавычки-ёлочки
        # ищем открывающую кавычку-ёлочку и следующую закрывающую кавычку
        (re.compile(u'\xab([^\\"])([А-Яа-яA-Za-z0-9,\\-:\\/\\.\\?\\!\\s\u202f]+)\\"', re.UNICODE), u'\xab\\1\\2\xbb'),
        # открывающие кавычки-лапки
        (re.compile(u'(^|\\s|\u202f|\\()\\"([A-Za-z0-9,\\-:\\/\\.\\?\\!\\&])', re.UNICODE), u'\\1\u201c\\2'),
        # закрывающие русские кавычки-лапки
        # ищем открывающую кавычку-лапку и следующую закрывающую кавычку
        (re.compile(u'\u201c([^\\"])([A-Za-zА-Яа-я0-9,\\-:\\/\\.\\?\\!\\&\\s\u202f]+)\\"', re.UNICODE), u'\u201c\\1\\2\u201d'),
    )
    return _sub_patterns(patterns, x)
    

## -------- rules end ----------
STANDARD_RULES = ('cleanspaces', 'ellipsis', 'initials', 'marks', 'wordglue', 'quotes')

def _get_rule_by_name(name):

    rule = globals().get('rl_%s' % name)
    if rule is None:
        raise ValueError("Rule %s is not found" % name)
    if not callable(rule):
        raise ValueError("Rule with name %s is not callable" % name)
    return rule

def _resolve_rule_name(rule_or_name, forced_name=None):
    if isinstance(rule_or_name, str):
        # got name<commit_msg>Add dashes to standard rules, also small fix in re flags<commit_after># -*- coding: utf-8 -*-
# -*- test-case-name: pytils.test.test_typo -*-
# pytils - simple processing for russian strings
# Copyright (C) 2006-2008  <NAME>
#
# http://www.pyobject.ru/projects/pytils/
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
"""
Russian typography
"""
import re
import os

def _sub_patterns(patterns, text):
    """
    Apply re.sub to bunch of (pattern, repl)
    """
    for pattern, repl in patterns:
        text = re.sub(pattern, repl, text)
    return text    

## ---------- rules -------------
# rules is a regular function, 
# name convention is rl_RULENAME
def rl_testrule(x):
    """
    Rule for tests. Do nothing.
    """
    return x

def rl_cleanspaces(x):
    """
    Clean double spaces, trailing spaces, heading spaces,
    spaces before punctuations 
    """
    patterns = (
        # arguments for re.sub: pattern and repl
        # удаляем пробел перед знаками препинания
        (r' +([\.,?!\)]+)', r'\1'),
        # добавляем пробел после знака препинания, если только за ним нет другого
        (r'([\.,?!\)]+)([^\.!,?\)]+)', r'\1 \2'),
        # убираем пробел после открывающей скобки
        (r'(\S+)\s*(\()\s*(\S+)', r'\1 (\3'),
    )
    # удаляем двойные, начальные и конечные пробелы
    return os.linesep.join(
        ' '.join(part for part in line.split(' ') if part)
        for line in _sub_patterns(patterns, x).split(os.linesep)
    )

def rl_ellipsis(x):
    """
    Replace three dots to ellipsis
    """

    patterns = (
        # если больше трех точек, то не заменяем на троеточие
        # чтобы не было глупых .....->…..
        (r'([^\.]|^)\.\.\.([^\.]|$)', u'\\1\u2026\\2'),
        # если троеточие в начале строки или возле кавычки --
        # это цитата, пробел между троеточием и первым
        # словом нужно убрать
        (re.compile(u'(^|\\"|\u201c|\xab)[\\s\u202f]*\u2026[\\s\u202f]*([А-Яа-яA-Za-z])', re.UNICODE), u'\\1\u2026\\2'),
        
    )
    return _sub_patterns(patterns, x)

def rl_initials(x):
    """
    Replace space between initials and surname by thin space
    """
    return re.sub(
        re.compile(u'([А-Я])\\.[\\s\u202f]*([А-Я])\\.[\\s\u202f]*([А-Я][а-я]+)', re.UNICODE),
        u'\\1.\\2.\u2009\\3',
        x
    )

def rl_dashes(x):
    """
    Replace dash to long/medium dashes
    """
    patterns = (
        # тире
        (re.compile(u'(^|(.[\\s\u202f]))\\-\\-?(([\\s\u202f].)|$)', re.MULTILINE|re.UNICODE), u'\\1\u2014\\3'),
        # диапазоны между цифрами - en dash
        (re.compile(u'(\\d[\\s\u202f\u2009]*)\\-([\\s\u202f\u2009]*\d)', re.MULTILINE|re.UNICODE), u'\\1\u2013\\2'),
        # TODO: а что с минусом?
    )
    return _sub_patterns(patterns, x)

def rl_wordglue(x):
    """
    Glue (set nonbreakable space) short words with word before/after
    """
    patterns = (
        # частицы склеиваем с предыдущим словом
        (re.compile(u'(\\s+)(же|ли|ль|бы|б|ж|ка)([\\.,!\\?:;]?\\s+)', re.UNICODE), u'\u202f\\2\\3'),
        # склеиваем короткие слова со следующим словом
        (re.compile(u'(^|\\s|\u202f)([a-zA-ZА-Яа-я]{1,3})(\\s+)', re.UNICODE), u'\\1\\2\u202f'),
        # то же самое -- второй проход нужен когда два коротких слова подряд
        # например "он не почуял неладное"
        # если один раз, то "(1=^)(2=Он)( )не почуял неладное", пробел после "не" не обрабатывается
        (re.compile(u'(\u202f)([a-zA-ZА-Яа-я]{1,3})(\\s+)', re.UNICODE), u'\\1\\2\u202f'),
        # склеиваем тире с предыдущим словом 
        (re.compile(u'([\\s]+)([\u2014\\-]+)([\\s]+)', re.UNICODE), u'\u202f\\2\\3'),
        # склеиваем два последних слова в абзаце между собой
        # полагается, что абзацы будут передаваться отдельной строкой
        (re.compile(u'([^\\s\u202f]+)\s+([^\\s\u202f]+)$', re.UNICODE), u'\\1\u202f\\2'),
    )
    return _sub_patterns(patterns, x)

def rl_marks(x):
    """
    Replace +-, (c), (tm), (r), (p), etc by its typographic eqivalents
    """
    # простые замены, можно без регулярок
    replacements = (
        (u'(r)', u'\u00ae'), # ®
        (u'(R)', u'\u00ae'), # ®
        (u'(p)', u'\u00a7'), # §
        (u'(P)', u'\u00a7'), # §
        (u'(tm)', u'\u2122'), # ™
        (u'(TM)', u'\u2122'), # ™
    )
    patterns = (
        # копирайт ставится до года: © 2008 <NAME>
        (re.compile(u'\\([cCсС]\\)[\\s\u202f]*(\\d+)', re.UNICODE), u'\u00a9\u202f\\1'),
        (r'([^+])(\+\-|\-\+)', u'\\1\u00b1'), # ±
        # градусы с минусом
        (u'\\-(\\d+)[\\s\u202f]*([FCС][^\\w])', u'\u2212\\1\202f\u00b0\\2'), # −12 °C, −53 °F
        # градусы без минуса
        (u'(\\d+)[\\s\u202f]*([FCС][^\\w])', u'\\1\u202f\u00b0\\2'), # 12 °C, 53 °F
        # ® и ™ приклеиваются к предыдущему слову, без пробела
        (re.compile(u'([A-Za-zА-Яа-я\\!\\?])[\\s\u202f]*(\xae|\u2122)', re.UNICODE), u'\\1\\2'),
    )

    for what, to in replacements:
        x = x.replace(what, to)
    return _sub_patterns(patterns, x)

def rl_quotes(x):
    """
    Replace quotes by typographic quotes
    """
    patterns = (
        # открывающие кавычки ставятся обычно вплотную к слову слева
        # а закрывающие -- вплотную справа
        # открывающие русские кавычки-ёлочки
        (re.compile(u'(^|\\s|\u202f|\\()\\"([А-Яа-я0-9,\\-:\\/\\.])', re.UNICODE), u'\\1\xab\\2'),
        # закрывающие русские кавычки-ёлочки
        # ищем открывающую кавычку-ёлочку и следующую закрывающую кавычку
        (re.compile(u'\xab([^\\"])([А-Яа-яA-Za-z0-9,\\-:\\/\\.\\?\\!\\s\u202f]+)\\"', re.UNICODE), u'\xab\\1\\2\xbb'),
        # открывающие кавычки-лапки
        (re.compile(u'(^|\\s|\u202f|\\()\\"([A-Za-z0-9,\\-:\\/\\.\\?\\!\\&])', re.UNICODE), u'\\1\u201c\\2'),
        # закрывающие русские кавычки-лапки
        # ищем открывающую кавычку-лапку и следующую закрывающую кавычку
        (re.compile(u'\u201c([^\\"])([A-Za-zА-Яа-я0-9,\\-:\\/\\.\\?\\!\\&\\s\u202f]+)\\"', re.UNICODE), u'\u201c\\1\\2\u201d'),
    )
    return _sub_patterns(patterns, x)
    

## -------- rules end ----------
STANDARD_RULES = ('cleanspaces', 'ellipsis', 'initials', 'marks', 'dashes', 'wordglue', 'quotes')

def _get_rule_by_name(name):

    rule = globals().get('rl_%s' % name)
    if rule is None:
        raise ValueError("Rule %s is not found" % name)
    if not callable(rule):
        raise ValueError("Rule with name %s is not callable" % name)
    return rule

def _resolve_rule_name(rule_or_name, forced_name=None):
    if isinstance(rule_or_name, str):
        # got name
{'fraction_non_alphanumeric': 0.21618471847913176, 'fraction_numerical': 0.03656493553791245, 'mean_word_length': 2.3196324951644103, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6292971', 'n_tokens_mistral': 5945, 'n_tokens_neox': 5968, 'n_words': 1174}