typus — локальный типограф на python

в 21:56, , рубрики: python, типограф, типографика
       ,'``.._   ,'``.
      :,--._:),:,._,.:       All Glory to
      :`--,''   :`...';      the HYPNO TOAD!
       `,'       `---'  `.
       /                 :
      /                   
    ,'                     :.___,-.
   `...,---'``````-..._    |:       
     (                 )   ;:    )     _,-.
      `.              (   //          `'    
       :               `.//  )      )     , ;
     ,-|`.            _,'/       )    ) ,' ,'
    (  :`.`-..____..=:.-':     .     _,' ,'
     `,' ``--....-)='    `._,    ,') _ '``._
  _.-/ _ `.       (_)      /     )' ; /  `-.'
 `--(   `-:`.     `' ___..'  _,-'   |/   `.)
     `-. `.`.``-----``--,  .'
       |/`.`'        ,','); SSt
           `         (/  (/

Найдено в интернетах.

Всем привет!
Хочу поделиться своей небольшой разработкой: типографом, который можно использовать локально.

Дисклеймер

Проект находится в стадии разработки и нуждается в тщательном тестировании.

Возможности

  • замена кавычек на «„“» и “‘’” (в английской версии). Число уровней не ограничено — типограф просто чередует четные/нечетные — где-какие можно настроить
  • расстановка дюймов, апострофов: 4′, 20″
  • комплексные символы: многоточие, копирайты, трейдмарки, стрелочки и т. д.: (c) становится ``, причем, даже если написано кириллицей
  • замена дефисов на длинное тире в текстах и числовых диапазонах
  • замена дефисов на короткое тире в номерах телефонов
  • расстановка минусов и знаков умножения
  • связывание чисел с последующими словами неразрывным дефисом, например 40 попугаев
  • связывание союзов и любых слов из 1-2 символов с последующими словами
  • отделение единиц измерения от чисел (возможно, выпилю в скором будущем, очень велик шанс ложно-положительного результата)
  • неразрывные пробелы в сокращениях: т.д. станет т. д.; А. С. Пушкин — здесь обычный пробел станет разрывным
  • замена р и руб (с точкой в конце и без) на символ рубля — возможно выпилю, поскольку удилит точку если найдет совпадение в конце предложения
  • замена дробей 1/2, 1/3 и т.д. на существующие символы юникода
  • удаление лишних пробелов и переносов строк, тримминг вначале и вконце
  • расстановка неразрывных пробелов в куче случаев
  • не влияет на html теги и игнорирует содержимое (head|iframe|pre|code|script|style)
  • можно передать строки, которые типограф будет игнорировать

Примеры:

from typus import ru_typus

ru_typus('00" "11 '22' 11"? "11 '22 "33 33?"' 11" 00 "11 '22' 11" 0"')
'00″ «11 „22“ 11»? «11 „22 «33 33?»“ 11» 00 «11 „22“ 11» 0″'

Число — уровень вложенности. Если бы первая кавычка стояла бы до нулей, был бы еще один уровень, а так вышли дюймы.

Как устроен

class BaseTypus(EnRuExpressions, TypusCore):
    processors = (EscapePhrases, EscapeHtml, TypoQuotes, Expressions)

class RuTypus(RuQuotes, BaseTypus):
    pass

ru_typus = RuTypus()

Typus состоит из "процессоров" и "выражений".

Выражения

Это пары (regex, replace), которые передаются в re.sub(regex, replace) и выполняются последовательно (см. чуть ниже). Почти весь типограф — это "выражения". Они записываются как методы с приставкой expr_, функция должна вернуть вложенный список, т.е. одно "выражение" может вернуть череду "выражений":

class MyTypus(Typus):
    expressions = Typus.expressions + 'http://bar'

    def expr_http://bar(self):
        expr = (
            (r'd', '@'),  # заменяет числа на @
        )
        return expr

Третий, необязательный, аргумент — флаги передаваемые в re.compile, по-умолчанию, это re.I | re.U | re.M | re.S.
Кстати, replace может быть функцией, см. re.sub.

Чтобы определить последовательность используется атрибут типографа — expressions, который хранит список названий выражений. Можно выключить лишнее:

from typus import RuTypus

exclude_expressions = ('ruble', 'math')

class MyTypus(RuTypus):
    expressions = (e for e in RuTypus.expressions
                   if e not in exclude_expressions)

expressions может быть генератором, но если сделать последовательностью, можно делать так:

def expr_http://bar(self):
    if 'some' in self.expressions:
        return baz
    return egg

В коробке идет лишь один микс выражений — EnRuExpressions, но он делает почти всю работу.

Для работы выражений используется процессор Expressions.

Процессоры

Иногда простыми регулярками не отделаться, приходится городить убер-функцию. Процессор это класс-функция-декоратор, который инициируется во время создания типографа, а затем вызывается при обработке текста. Ему (инстансу процессора) передается сам инстанс типографа, чтобы процессор мог получить доступ к его конфигурации.

При использовании нескольких процессоров, они декорируют друг-друга по порядку. Например, так:

удалить html теги
    дать ход следующему процессору, если сошлись звезды
        что-то там поделать с текстом
    обработать и вернуть наверх
вернуть теги

С Typus поставляются несколько процессоров: EscapePhrases, EscapeHtml, TypoQuotes, Expressions.

EscapePhrases

Бывают случаи, когда определенный кусок текста обрабатывать нельзя, либо вы заранее знаете, что типограф на этом месте запнется, в таком случае можно поступить так:

typus('"http://bar 2""', escape_phrases=['2"'])
'«http://bar 2"»'

Без этого типограф встретит закрывающую кавычку: «http://bar 2»". Еще пример:

typus('Типограф заменяет (c) на (c)', escape_phrases=['заменяет (c)'])
'Типограф заменяет (c) на '

Аргумент escape_phrases можно вынести отдельным полем в ваше CRUD приложение (ака "админку"), где контент менеджер сможет перечислить фразы через разделитель, а вы передадите их типографу.

Чтобы разделить текст можно можно воспользоваться утилитой:

from typus.utils import splinter

split = splinter(',')
split('a, b,c ') == ['a', 'b', 'c']
split('a, b,c') == ['a', 'b,c']

splinter понимает экранированные разделители и вызывает str.strip() для каждой фразы.

EscapeHtml

Выразет html-теги до типографа и возвращает их после. Без него <img src="http://bar"> превратится в <img src=«http://bar»>.

TypoQuotes

Проставляет кавычки. Ожидает, что в типографе будут перечислены атрибуты loq, roq, leq, req. Пример:

from typus import BaseTypus
from typus.chars import LAQUO, RAQUO, DLQUO, LDQUO

class MyTypus(BaseTypus):
    # Левая нечетная, правая нечетная, левая четная, правая четная
    loq, roq, leq, req = LAQUO, RAQUO, DLQUO, LDQUO

Есть готовые миксины EnQuotes и RuQuotes в модуле typus.mixins.

Expressions

Обеспечивает работу выражений. Во время инициализации типографа все регулярки компилируются и хранятся в инстансе процессора.

Про отладку

Если типографу передать debug=True, то он заменит все неразрывные пробелы на символ подчеркивания, это может быть полезным для отладки:

ru_typus('(c) me', debug=True)
'_me'

Демо

Важно: демо крутится на очень простой виртуалке и предназначено для демонстрации возможностей.

Я ничего никуда не сохраню (честно), исходный код сайта вы найдете у меня на гитхабе.

Установка и использование

pip install -e git://github.com/byashimov/typus.git#egg=typus

Далее:

from typus import en_typus, ru_typus

en_typus('"Beautiful is better than ugly." (c) Tim Peters.', debug=True)
'“Beautiful is_better than ugly.” _Tim Peters.'  # _ for nbsp

ru_typus('"Красивое лучше, чем уродливое." (с) Тим Петерс.')
'«Красивое лучше, чем уродливое.»  Тим Петерс.'  # cyrillic 'с' in '(с)'

Документация

Эту статью можно считать таковой, пока я не сделаю корявый перевод на английский.

Совместимость

Name                  Stmts   Miss  Cover
-----------------------------------------
typus/__init__.py         8      0   100%
typus/chars.py           18      0   100%
typus/core.py            24      0   100%
typus/mixins.py          77      0   100%
typus/processors.py      99      0   100%
typus/utils.py           30      0   100%
-----------------------------------------
TOTAL                   256      0   100%
________________ summary ________________
  py25: commands succeeded
  py26: commands succeeded
  py27: commands succeeded
  py33: commands succeeded
  py34: commands succeeded
  py35: commands succeeded
  congratulations :)

Travis-CI, которым я пользуюсь, не поддерживает 2.5, а вручную я проверять не всегда тружусь, так что если вы еще им пользуетесь (соболезную), запустите тесты после установки.

Страница проекта.

Планы и какие-то идеи

  • Я не планирую вносить в типограф подчеркивание ссылок или расстановку html-тегов. Этим должен занимать процессор текстов (markdown, retext и т. д.). К тому же, у всех свои кейсы.
  • Я также не хотел бы, чтобы типограф исправлял ошибки в тексте, даже если это ничего не стоит.
  • Почти все типографы конвертируют небезопасные символы, такие как &, в html-сущности. На данный момент мне не ясно зачем это делать: браузеры, поисковики и парсеры справляются играючи с таким текстом, а гонять cpu просто так и делать код нечитаемым мне совсем не хочется. Буду рад конкретному примеру.
  • Вероятно, ru_typus справится с украинскими и белорусскими текстами (а возможно и с другими), если так, я внесу это в описание проекта.

Вроде все.

P. S. Какой-то ад с подсветкой инлайн-кода на хабре.

Автор: magic4x

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js