Pull to refresh

Habra-colorer — скрипт для раскраски вашего хабракода из подручных средств

Python *
Начав писать топик про мои извращения с TeX-ом понял, что очень недостаёт нормальной подсветки синтаксиса. Гугление по Хабру и окрестностям навело на пару редакторов, которые у меня не заработали и описание подрихтованного formatter-а для pygments.
Решив «а чем я хуже», набросал «на коленке» скрипт на python-е, который мне код-то и раскрашивает.

Вскрытие показало, что пациент умер от вскрытия использовать найденный на Хабре код без доработок не получится — то ли заточен под старую версию pygments, то ли ещё что. В общем, полез я в документацию и в первом же примере мне попался HTML 3.2 Formatter весьма похожий на найденный ранее.

Сравнение показало, что оба formatter-а если не близнецы, то братья — по новым правилам обязательным является определение метода format, которым не пользовался уважаемый barbuza, ну и пришлось поправить Хабра-специфичные теги.

Обрабатываем код

Дальше, вооружившись питоном с регэкспами, решил: буду обрабатывать текст и подменять в нём блоки с кодом, оформленным по аналогии с ХабраРедактор-ом на раскрашенный код:
Здесь идёт какой-то текст
<code class="python"><!-- здесь не должно быть ничего, кроме пробелов -->
#!/usr/bin/env python
import sys
</code><!-- здесь не должно быть ничего, кроме пробелов -->

Т.е. всё, что внутнри блока, ограниченного code с единственным атрибутом class прогоняем через подходящий lexer pygments.
def preparse_text(text, linenos=False, style=None):
    """Extract code blocks from raw text, render via pygments and return as unicode string"""

    R = re.compile(ur'^\s*<code class="(?P<class>.*?)">\s*$(?P<code>.*?)^\s*</code>\s*$', re.I|re.U|re.S|re.M)

    out = []
    prev = 0‌
    ar = {'linenos':False}
    if linenos:
        ar['linenos']='inline'
    if style:
        ar['style'= style

    for s in R.finditer(text):
        fmt = OldHtmlFormatter(**ar)
        out.append(text[prev:s.start()])
        lx = get_lexer_by_name(s.group('class'))
        if not lx:
            lx = guess_lexer(s.group('code'))
        if lx:
            s0 = s.group('code')
            s0 = s0.replace(u' 'u'\u00a0'# &nbsp;
            src = highlight(s0, lx, fmt) # .replace(u'\n', '<br/>\n') # for preview
        else:
            src = u'<code>%s</code>' % s.group('code')

        out.append(src)
        prev = s.end()
        del lx
        del fmt
        lx = None
    out.append(text[prev:])
    return u''.join(out)

На выходе получаем наш текст, в котором все подобные блоки раскрашены в соответствии с выбранным стилем pygments.
Исправление форматтера состояло из 3-х частей — замены обычных строк на юникодные, добавление функции экранировки HTML символов из блога barbuza с небольшой правкой и добавление «тупой» функции упаковки кодов цвета из 6 символов в 3, если позволяет цвет.

Консольная утилита

Осталось написать кусок, который будет вызавать наш форматтер и задавать ему дополнительные параметры.

Тут, для облегчения жизни, нам пригодится модуль optparse для разбора параметров командной строки:
    # Создаём парсера и заполняем допустимыми опциями командной строки
    p = OptionParser(usage='usage: %prog [options] input_file')
    p.add_option('-f','--file', metavar="FILE", help="Write output to FILE")
    p.add_option('-s','--style', metavar="STYLE",help="Use color STYLE for formatting")
    p.add_option('--htm','--html', action="store_true", help="Add extra html headers in output")
    p.add_option('--list-styles', action="store_true", help="Show list of supported styles")
    p.add_option('--list-languages', action="store_true", help="Show list of supported languages")

    # Собственно обработка командной строки, опции в 'op', имя входного файла в 'a[0]'
    op,a = p.parse_args()

    # попросили список стилей - нате вам, и выход
    if op.list_styles:
        from pygments.styles import get_all_styles
        print "Supported color styles:"
        for s in get_all_styles():
            print u"\t%s" % (s,)
        sys.exit(0‌)

    # попросили список языков - тоже нате :)
    if op.list_languages:
        from pygments.lexers import get_all_lexers
        print "Supported languages and aliases:"
        ss = list(get_all_lexers())
        ss.sort(key=lambda x:x[0‌].lower())
        for s in ss:
            print s[0‌]
            if s[1]:
                print "\t"", ".join(s[1])
        sys.exit(0‌)

    # если нечего обрабатывать, то и работать не будем
    if len(a) != 1:
        print "No input file specified!"
        sys.exit(1)

Вот, собственно, и всё. Осталось прочитать указанный файл, обработать его на предмет раскраски кода и записать в указанный файл или на экран, если выходной файл не указан. Попахивает быдлокодом, но оптимизировать элементарно лень. Поскольку уже есть мысли подоработке всего этого безобразия — рефакторинг отложим на потом.
    srcfile = a[0‌]
    dstfile = op.file
    f = unicode(open(srcfile, 'rb').read(), 'utf-8''replace'# Таки я уверен, что входной файл в utf8 ^)

    s = preparse_text(f, False, op.style).encode('utf-8'# Обработали
    if dstfile:
        fn = open(dstfile, 'wb')
    else:
        fn = sys.stdout
    # если просят HTML заголовок для предросмотра в браузере -- их есть у нас
    if op.htm:
        fn.write('<html><head><meta http-equiv="content-type" content="text/html; charset=\'utf8\'"/></head><body>\n')
    fn.write(s)
    if op.htm:
        fn.write('</body></html>\n')
    try:
        close(fn) # У stdout нет метода close, игнорируем эту ошибку
    except:
        pass

Итог

В итоге я получил работоспособную консольную утилиту, позволяющую мне «лёгким движением руки» © раскрашивать исхоный код статей, набранных в Far-e, GEdit-e или Midnight Commandere.
Исходный код целиком (с некоторым количеством «мусора») доступен по адресу dumpz.org/17521.

TODO

Уже сейчас видятся некоторые направления развития:
  • «Причесать» раскрашивающую часть — добавить возможность вывода номеров строк и автоматическую экранировку вложенных кодов (сейчас мне пришлось добавить комментарии после тега code в первом примере
  • Есть мысль сделать GUI на PyQt с предпросмотром a'la Хабр
  • Можно нарисовать пару-тройку собственных стилей, благо это довольно просто.


Использованные материалы

  1. Хабрахабр
  2. Блог хабраюзера barbuza в качестве идеи и функции экранировки HTML символов
  3. ХабраРедактор От SoftCoder-а
  4. Python 2.6
  5. Pygments
  6. Сайт раскраски исходных кодов s-c.me
  7. Сайт расшаривания дампов dumpz.org


ЗЫ: Этот текст подготовлен в GEdit и раскрашен при помощи habra-colorer-а со стилем default
ЗЫЫ: В процессе написания выяснил, что Хабр «не любит» одинокую цифру «0» внутри тегов font. Пришлось обмануть добавлением хитрого пробела с кодом &#8204;.
Tags:
Hubs:
Total votes 36: ↑33 and ↓3 +30
Views 1.3K
Comments Comments 5