середа, 12 листопада 2014 р.

Проверка правильности ссылок в Markdown-файлах

Введение

В статье Ведение блога на Blogger с помощью Vim я описал ведение блога на Blogger с помощью Vim и специального python-скрипта, который используется для публикации текстов. Как правило, хороший пост содержит некоторое количество ссылок. Естественно, хотелось бы быть уверенным, что ссылки не содержат ошибок. Способ проверки, изложенный в статье не очень подходит, так как предложенный скрипт просто выводит ссылку и результат её проверки. Более удобной бы была проверка в соответствии с идеологией Vim: с помощью :make и quickfix. Рассмотрим, как можно добиться такого функционала.

Выбор инструмента

Для проверки ссылок будем использовать linkchecker. Данный инструмент может проверять как сайты, так и локальные файлы. Также он поддерживает систему плагинов. Например, существуют плагины для проверки ссылок в документах MS Word или pdf-файлах.
Установка:
sudo pip install LinkChecker

Теперь можно проверять файлы:
linkchecker -v --check-extern t.html
LinkChecker 9.3              Copyright (C) 2000-2014 Bastian Kleineidam
...
Start checking at 2014-11-12 16:16:56+003
...
URL        http://xvadim.chgk.info/wb1/'
Name404'
Parent URL file:///Users/vadimkhohlov/work/blogger/t.html, line 6, col 9
Real URL   http://xvadim.chgk.info/wb1/
Check time 1.696 seconds
Size       284B
Result     Error: 404 Not Found
...

Разработка плагина

Инициализация

Файл с кодом плагина должен располагаться в подкаталоге linkcheck/plugins. Задача плагина - извлечь все ссылки из файла. Корректность ссылок будет проверять сам linkchecker.
Класс в нашем случае должен наследоваться от _ContentPlugin:
from . import _ContentPlugin
from .. import log, LOG_PLUGIN
class MarkdownCheck(_ContentPlugin):
    _default_filename_re = re.compile(r'.*\.(markdown|md(own)?|mkdn?)$')

    def __init__(self, config):
        super(MarkdownCheck, self).__init__(config)
        self.filename_re = self._default_filename_re

    def applies_to(self, url_data, pagetype=None):
        return self.filename_re.search(url_data.base_url) is not None
Метод applies_to должен вернуть True если плагин умеет обрабатывать данный файл. linkchecker пытается самостоятельно определить тип файла и передаёт его через параметр pagetype. Однако, в нашем случае он этого сделать не может. Поэтому приходится анализировать имя файла.

Обработка файла

Обрабатывает файл метод check(self, url_data):
_link_res = [re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I)]
def check(self, url_data):
    content = url_data.get_content()
    self._check_by_re(url_data, content)
def _save_url(self, url_data, content, url_text, url_pos):
    line = content.count('\n', 0, url_pos) + 1
    column = url_pos - content.rfind('\n', 0, url_pos)
    url_data.add_url(url_text.translate(None, '\n '), line=line, column=column)
def _check_by_re(self, url_data, content):
    for link_re in self._link_res:
        for u in link_re.finditer(content):
            self._save_url(url_data, content, u.group(1), u.start(1))
С помощью регулярного выражения метод выбирает автоссылки вида <http://autolink.com> и добавляет их к объекту url_data. Номер строки вычисляется как количество символов \n, а столбец - как расстояние до ближайшего левого \n.

Дополнительные настройки плагина

Для того, чтобы данный плагин был активирован, необходимо в файле ~/.linkchecker/linkcheckerrc добавить строку:
[MarkdownCheck]
Было бы неплохо как-то параметризовать маску имени файла. Например, я, как писал ранее, сохраняю файлы постов блога с расширением blog. Добиться этого можно следующим кодом:
_filename_re_key = "filename_re"
def __init__(self, config):
        super(MarkdownCheck, self).__init__(config)
        self.filename_re = self._default_filename_re
        pattern = config.get(self._filename_re_key)
        if pattern:
            try:
                self.filename_re = re.compile(pattern)
            except re.error as msg:
                log.warn(LOG_PLUGIN, "Invalid regex pattern %r: %s" % (pattern, msg))

    @classmethod
    def read_config(cls, configparser):
        """Read configuration file options."""
        config = dict()
        config[cls._filename_re_key] = configparser.get(cls.__name__, cls._filename_re_key) \
            if configparser.has_option(cls.__name__, cls._filename_re_key) else None
        return config
Скрипт ищет в конфиге параметр с ключем filename_re. Если не находит, использует стандартную маску имён Markdown-файлов. После этого конфиг может быть таким:
[MarkdownCheck]
filename_re=.*.(blog|markdown|md(own)?|mkdn?)$

Установка

Форк с моим кодом лежит на Github. Pull-request я отправил. Пока же можно вручную подложить файл плагина markdowncheck.py в нужное место: .../site-packages/linkcheck/plugins

Настройка Vim

Для настройки Vim необходимо задать две переменные: makeprg - команда, которая будет вызываться по :make errorformat - фильтр, с помощью которого будут из вывода команды выбираться строки с ошибками
Для обработки файла я использую такую команду: linkchecker -v --check-extern -f ~\/work/blogger/lrc -o csv file_name Аргумент --check-extern говорит, что надо проверять не только правильность ссылок, но и их валидность. С помощью -o csv задаётся формат вывода в виде csv-файла, содержащего по одной строке на каждую ссылку. Таким образом, настройки Vim будут следующими:
set makeprg=linkchecker\ -v\ --check-extern\ -f\ ~\/work\/blogger\/lrc\ -o\ csv\ '%'
set errorformat=%-G#%.%#,
                \%-Gurlname;parentname;%.%#,
                \%-G%.%#;True;%.%#,
                \%-G%.%#URLs\ checked%.%#,
                \%.%#;file://%f;;%m;;%.%#;False;%.%#;%l;%c;%.%#;%.%#;%.%#;%.%#;%.%#;%.%#;
Из вывода команды мы отбрасываем все строки, кроме содержащих информацию об ошибочных ссылках (с помощью первых четырёх элементов errorformat). Мила ЙововичПятый элемент errorformat обрабатывает все строки, в которых в седьмом столбце стоит False, т.ё. строки с информацией об ошибке. Назначение некоторых элементов errorformat:
  • %-G - пропустить строку, удовлетворяющую заданному шаблону
  • %f - имя файла
  • %m - текст сообщения об ошибке
  • %l - номер строки
  • %c - номер столбца
  • %.%# - транслируется в регэксп .*

С такими настройками, выполнив команду :make и открыв quickfix-окно, получим следующее:

Теперь можно использовать всю мощь режима quickfix, например, перемещаться по ошибочным ссылкам с помощью :cn.
Приятного блоггинга с помощью лучшего в мире редактора!

Немає коментарів:

Дописати коментар