Показ дописів із міткою blogging. Показати всі дописи
Показ дописів із міткою blogging. Показати всі дописи

середа, 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.
Приятного блоггинга с помощью лучшего в мире редактора!

пʼятниця, 24 жовтня 2014 р.

Ведение блога на Blogger с помощью Vim

Введение

В последнее время я стал активно гадить писать посты в Blogger. Естественно, захотелось настроить правильный инструмент для упрощения себе жизни. Основных цели было две:
  • упрощение процесса редактирования
  • возможность подготовки черновиков постов в офф-лайн
Для Vim существует несколько плагинов, например Blogger.vim. Однако, у меня он не заработал, выдавая какую-то ошибку.
Основой моих настроек для Vim’а послужила вот эта статья.

Установка скрипта для постинга и его настройка

В указанной выше статье описан скрипт b.py. Скрипт позволяет подготавливать тексты в формате Markdown или reStructuredText. Установка скрипта и необходимых зависимостей описана в документации. Ему необходим конфигурационный файл brc.py подобного содержания:
import re
service = 'blogger'
service_options = {
    'blog': YOUR_BLOG_ID
}
handlers = {
    'Markdown': {
        'match': re.compile(r'.*.(blog|markdown|md(own)?|mkdn?)$'),
        'module': 'bpy.handlers.mkd',
        'options': {
            'config': {
                'extensions': ['footnotes', 'toc'],
            },
            'smartypants': True,
        },
    }
}

С помощью строки
'match': re.compile(r'.*.(blog|markdown|md(own)?|mkdn?)$')
я настраиваю скрипт таким образом, чтобы он воспринимал также файлы с расширением blog.
После подготовки конфига необходимо выполнить авторизацию, как описано в документации. В результате в этом же каталоге будет создан файл b.dat с нужным токеном. Скрипт b.py нужно обязательно запускать из каталога, содержащего файлы brc.py и b.dat.

Установка нужного filetype и подсветки синтаксиса

Файл поста - это частный случай Markdown-файла, поэтому я добавил в ~/.vim/ftdetect/ftdetect.vim следующую строку:
au BufRead,BufNewFile *.blog    set filetype=blog syntax=markdown
Таким образом я сообщаю Vim’у что тип файла будет blog, а вот для подсветки синтаксиса использовать те же правила, что и для Markdown-файлов.

Шаблон нового файла

Скрипт требует, чтобы каждый новый файл поста имел следующий заголовок
!b
service: blogger
title: Some title
labels: List of tags
(после публикации в заголовок будет добавлен ещё ряд полей). Для задания шаблонов создаваемых файлов я использую плагин aperezdc/vim-template. Поэтому добавил в каталог для ведения блога файл .vim-template:*.blog:
!b
service: blogger
title: %FILE%
labels: productivity, Vim, programming


%HERE%

После этого для создания нового поста необходимо ввести команду:
mvim "Ведение блога на Blogger с помощью Vim.blog"
(имя файла становится заголовком поста).

Настройки маппинга

В статье приводится настройка нескольких маппингов для:
  • публикации поста
  • создания html-файла для предварительного просмотра
  • проверки валидности ссылок, упоминаемых в посте
Я оставил только первый, создав файл ~/.vim/ftplugin/blog.vim:
set autoread
map <buffer> <leader>post :exec '!b.py post '.shellescape(expand('%:p'))

Проверку ссылок я не делал, так как предлагаемый автором статьи способ не соответствует идеологии Vim (по-хорошему, проверку следует настроить через makeprg). К тому же, ссылки, как правило, вставляются с помощью копирования, поэтому в проверке валидности особенного смысла нет.
Как было указано ранее, скрипт после публикации поста добавляет в файл дополнительные заголовки. Опция set autoread заставляет Vim автоматически перечитывать файл после его изменения извне.

Snippets

Для подсветки синтаксиса в блоге я использую highlight.js. Естественно, вручную каждый раз вводить <pre><code>...</code></pre> некомильфо. Целесообразно это делать с помощью какого-нибудь сниппета. С другой стороны, как отмечалось выше, файл поста - это частный случай Mardown-файла, поэтому было бы неплохо использовать также сниппеты для таких файлов. С этой целью я создал следующий UltiSnip-файл:
extends markdown

snippet code "code fragment"
<pre><code class="${1:python}">$0
</code></pre>
endsnippet

Дополнительные улучшения

Чтобы ещё больше упростить вставку в текст блога файлов целиком (избежать цепочки действий: написать <pre>… - открыть файл - скопировать - вставить) я написал такую функцию и команду:
func! s:InsertCode(file_name)
    let code_lang = substitute(a:file_name, '[^\.]*\.', '', 'i')
    call setline('.', getline('.').'<pre><code class="'.code_lang.'">')
    norm! o
    call setline('.', getline('.').'</code></pre>')
    norm! k
    exec "r ".a:file_name
endfu

command! -nargs=1 -complete=file Rc call s:InsertCode(<f-args>)

Так как аргументом команды является имя файла, то я указал -complete=file, чтобы Vim при нажатии на Tab выполнял нужное автодополнение.

Заключение

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