System of a Down

Приехали млин...

Сегодня ночью, пока я спокойно спал, mongrel ушел в даун :( Похоже причиной тому стала работа с RMagic в капче. Если такое ещё раз повториться, то похоже придётся поменять её на что-то более надёжное.

На примете уже есть плагин для работы с reCAPTCHA, но как-то боязно его применять т.к. он взаимодействует с удалённым сервисом и если что случиться с ним, то, насколько я понимаю, сделать с этим ничего будет нельзя. Хотя с другой стороны я уже видел много сайтов которые его используют. В общем буду смотреть по обстоятельствам :)

* * *

Использование Maruku (часть №4)

Блок

Приближается к концу мой “эпос” посвящённый Maruku :)

В этой части, как и обещал, я расскажу о том как добавить свои собственные блочные элементы. Напомню, что про добавление span-элементов я рассказал в прошлом посте.

Начну я как и прежде с шаблона, который вы можете использовать при разработке своих блочных элементов:

StartTag = /^<регулярное выражение для начального тега>/
EndTag   = /^<регулярное выражение для конечного тега>/

MaRuKu::In::Markdown::register_block_extension(
	:regexp  => StartTag,
	:handler => lambda { |doc, src, context|
		# Пропускаем первую строку с начальным тегом
		src.shift_line
		# Считываем строки которые находятся внутри блока
		lines = []
		while src.cur_line && !(src.cur_line =~ EndTag)
			lines.push src.shift_line
		end
		# Пропускаем строку с конечным тегом
		src.shift_line
                    
                    # Здесь находиться ваш код который обрабатывает строки внутри блока
                    # и возвращает результат в переменной result
		context.push result
		true
	}
)

Если вы читали мою предыдущую статью, то заметили что этот шаблон сильно похож на шаблон для span-элементов. Поэтому я не буду заново рассказывать обо всех аргументах, а лишь упомяну об отличиях.

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

<начальный тег>
...
<содержимое блока>
...
<конечный тег>

И с помощью этих регулярных выражений мы находим эти теги, а затем “выкусываем” содержимое которое находится между ними и обрабатываем его.

Хорошим тоном думаю будет строить такие регулярные выражения которые будут искать теги начиная с начала строки (я специально указал в шаблоне символ чайки ^) связано это с тем что обработчик не принимает аргумент :chars как было в обработчике для span-тегов, а вместо этого применяет ко всем строкам вашего документа регулярное выражение StartTag чтобы понять содержит ли строка открывающий тег. Думаю понятно что из-за этого могут возникнуть проблемы с производительностью, а символ ^ всего лишь небольшая страховка от этих проблем.

Следующий момент на который надо обратить внимание состоит в том что мы используем метод register_block_extension для регистрации обработчика. Плюс, как я упомянул выше, этот метод содержит на один аргумент меньше. В теле обработчика я привел небольшой фрагмент кода который собирает находящиеся внутри блока строки.

Теоретическая часть закончена, переходим к практике :) Создадим блочный элемент в который можно помещать код на Ruby, а на выходе получать результат выполнения этого кода :)

Вот что у меня получилось:

StartTag = /^!ruby/
EndTag   = /^!/

MaRuKu::In::Markdown::register_block_extension(
  :regexp  => StartTag,
  :handler => lambda { |doc, src, context|
    # Пропускаем первую строку с начальным тегом
    src.shift_line
    # Считываем строки которые находятся внутри блока
    lines = []
    while src.cur_line && !(src.cur_line =~ EndTag)
      lines.push src.shift_line
    end
    # Пропускаем строку с конечным тегом
    src.shift_line

    code = lines.join("\n")
    result = get_result(code)
    context.push doc.md_html("<pre><code>#{result}</code></pre>")
    true
  })

class StdOutCapturer
  attr_reader :output

  def initialize
    @output = ""
  end

  def write(s)
    @output += s
  end
end

def get_result(code)
  origin_stdout = $stdout
  $stdout = StdOutCapturer.new

  eval(code)

  result = $stdout.output
  $stdout = origin_stdout

  result
end

И такой вот блок

!ruby
5.times { |i| puts "Hello World #{i}!!!" }
!

конвертируется в следующую разметку

<pre><code>Hello World 0!!!
Hello World 1!!!
Hello World 2!!!
Hello World 3!!!
Hello World 4!!!
</code></pre>

Что ж, я рассказал про все доступные способы расширения Markdown в Maruku. Код правда получился несколько громоздким, если видите способы исправить это - оставляйте комментарии :)

Следующий пост завершит цикл статей о Maruku, в нём я попробую выяснить насколько быстро работает Maruku.

* * *

Использование Maruku (часть №3)

Расширяй и углубляй

Как я и обещал, сейчас я расскажу вам о возможностях расширения Markdown. Так как есть два способа расширить функциональность, то в этом посте я расскажу про первый, а в следующем про второй.

Первый способ позволяет создавать свои span-подобные теги т.е. теги состоящие из одной строки. Например можно преобразовывать конструкцию вида

( ximg: ruby.jpg : Логотип Ruby : center )

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

В качестве заготовки для span-расширений можно использовать код приведённый ниже:

RegExp = /<регулярное выражение>/

MaRuKu::In::Markdown::register_span_extension(
:chars => [<символы с которых могут начинаться ваши span-теги>],
:regexp  => RegExp,
:handler => lambda { |doc, src, context|
    # Здесь находиться ваш код который преобразует span-тег в HTML разметку
    # Код возвращает true если тег успешно обработан, иначе возвращает false
  }
)

Этот код используя метод register_span_extension регистрирует обработчик span-тегов.

В аргументе :chars указывается символ или массив символов при встрече которых срабатывает обработчик. Причём символы не должны попадать в диапазон [a-zA-Z].

В аргументе :regexp указывается регулярное выражение которое будет выполнятся при встрече символа указанного в :chars и проверять является ли последовательность символов (начиная с текущего) вашим span-тегом. Не забудьте добавить символы из :chars в начало своего регулярного выражения, я потратил довольно много времени пытаясь понять почему обработчик не срабатывает.

В аргументе :handler в виде лямбда выражения находиться ваш обработчик. Который собственно говоря и занимается преобразованием вашего span-тега в нужную вам разметку. У этого обработчика тоже есть аргументы:

Аргумент doc это экземпляр класса Maruku который вы создали. В него подмешены методы из модуля MaRuKu::Helpers которые позволяют конструировать элементы на основе которых генерируется конечная разметка (понятно что формат мета-информации о том является ли текст курсивом в XHTML, PDF и LaTeX различаются, поэтому эти методы создают промежуточные объекты в которых храниться эта мета-информация, а уже из этих объектов по вашему требованию строится или XHTML или PDF или LaTeX)

Аргумент src предоставляет объект который содержит обрабатываемый текст и методы для его обработки.

Аргумент context предоставляет объект в который ваше расширение складывает результат своей работы (т.е. элемент или элементы полученные с помощью вспомогательных методов аргумента doc и заполненные мета-информацией необходимой для генерации разметки)

Остаётся добавить что если вы успешно обработали свой span-тег, то лямбда выражение должно вернуть true, тогда обработчики идущие за ним не будут вызваны и по окончанию преобразований ваш span-тег будет замещён генерированной в обработчике разметкой. А если вернуть false, то представление вашего span-тега без изменений будет помещено в выходную разметку.

Теперь мы готовы к написанию обработчика span-тегов. Как я уже сказал выше мы будем преобразовывать текстовые смайлики в HTML разметку содержащую изображение смайлика. Так как я суровый программист, то смайликов у меня будет всего лишь два (улыбающийся смайлик - :) или :-) и расстроенный смайлик - :( или :-( )

RegSmiles = /:-?(\(|\))/

MaRuKu::In::Markdown::register_span_extension(
      :chars => [?:],
      :regexp  => RegSmiles,
      :handler => lambda { |doc, src, context|
    attrs = src.read_regexp(RegSmiles).captures
    bracket = attrs[0]

    case bracket
      when ")"
        image = doc.md_im_image([], "/images/smile1.gif", ":-)", {:style => "padding:0;margin:0", :alt => ":-)"})
      when "("
        image = doc.md_im_image([], "/images/smile0.gif", ":-(", {:style => "padding:0;margin:0", :alt => ":-("})
    end

    context.push_element image
    true
  }
)

Вот такой вот код у меня получился. Так как мои смайлики начинаются с :, то аргумент :chars содержит этот символ. Регулярное выражение RegSmiles проверяет наличие смайлика при встречи символа двоеточия, а так же захватывает один символ (символ скобки) который после проверяется в case-е и на его основе формируется элемент на основе которого будет сформировано изображение. Затем этот элемент с помощью метода push_element помещается в контейнер context и лямбда выражение сигнализирует об успешной обработке возвращая true.

Это вот пример строк которые я использовал для тестирования:

Test :)
Тест :(

К сожалению этот код не заработал… не заработал с первого раза. Только для первой строки где содержались латинские символы появился смайлик, в строке же с русскими символами текст остался без изменений. Потратив больше часа на поиски проблемы я нашел решение и даже готовый патч (но ещё не включенный в основную версию). Порадовал меня и тот факт что я нашел то же место, но не стал писать заплатку, а решил отправить баг автору :)

А вот получившаяся разметка:

<p>Test <img src='/images/smile1.gif' alt='' style='padding:0;margin:0' /></p>
<p>Тест <img src='/images/smile0.gif' alt='' style='padding:0;margin:0' /></p>

На всякий случай приведу патч:

*** lib/maruku/input/charsource.rb.bak	2008-03-20 20:06:04.000000000 +0200
--- lib/maruku/input/charsource.rb	2008-03-20 20:28:34.000000000 +0200
***************
*** 88,95 ****
	end

	def next_matches(r)
! 		r2 = /^.{#{@buffer_index}}#{r}/m
! 		md = r2.match @buffer
		return !!md
	end
	
--- 88,94 ----
	end

	def next_matches(r)
!         md = /#{r}/um.match @buffer[@buffer_index, @buffer.size-@buffer_index]
		return !!md
	end

Думаю он потребуется нам и в следующей части. А пока всё.

* * *

Скорая помощь

В этом посте я расскажу какие справочные системы по Ruby и Rails (и не только) доступны разработчикам в Интернете.

* * *

Использование Maruku (часть №2)

В прошлой части я рассказал про то, что же из-себя представляет Maruku и что интересного в нём есть. Поэтому сейчас я расскажу как его устанавливать (думаю кто-то уже справился без меня т.к. процесс довольно стандартный)

Всё самое вкусное Rails разработчики обычно получают устанавливая gem-ы, не является исключением и Maruku. На сегодняшний день доступна версия 0.5.9, которую я и поставлю:

sudo gem install maruku

Собственно на этом установка Maruku закончена.

Пример использования:

require 'rubygems'
require 'maruku'

m = Maruku.new("**Привет**")
puts m.to_html

После выполнения этот код вернёт вам строку:

<p><strong>Привет</strong></p>

Помимо метода to_html можно ещё вызывать to_html_document, который выдаёт готовый XHTML документ, а не его часть. Но сейчас этот документ создаётся с довольно специфическим DOCTYPE-ом (хотя судя по коду в следующих версиях возможно это будет как-то настраиваться)

Из интересных вещей ещё осталось отметить то что, Maruku имеет список глобальных настроек доступных в хеше MaRuKu::Globals, например с его помощью можно немного облегчить создание разметки. Например, установив:

MaRuKu::Globals[:html_use_syntax] = true

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

	require 'maruku'
{:lang=ruby}

заместо

	require 'maruku'
{:lang=ruby html_use_syntax=true}

Кстати возможность подсветки блоков кода Maruku реализует за счёт того что задействует gem под названием Syntax, правда количество поддерживаемых языков в нём пока маловато (есть ruby, xml и yaml)

На этом пока всё, в следующей части я расскажу о том как можно расширить язык разметки Markdown вашими собственными тегами(коммандами)

* * *

Горестные раздумья блоговода

Ослики

Подумывал тут о том чтобы залочить блог для ословодов т.к. этот “браузер” уже давно отравляет мне жизнь своим странным поведением. Седьмой осёл например давит скролбарами блоки с кодом, что с этим делать я пока не придумал. А про шестой вообще вспоминать даже не хочется.

* * *

Использование Maruku (часть №1)

Почувствуй всю необыкновенную прелесть Maruku

В этом посте я собираюсь начать цикл статей, рассказывающих о Maruku.

Maruku это движок, который позволяет конвертировать текст в XHTML представление этого текста на основе специализированного “легковесного” языка разметки под названием Markdown.

Пару замечаний по абзацу выше:

  • Maruku использует в своей работе Markdown, но не просто использует, а ещё и расширяет его возможности. Как он это делает, я расскажу ниже.

  • Помимо преобразования в XHTML существует ещё возможность преобразования в LaTeX и PDF (предварительно преобразовав его в LaTeX). В планах у разработчиков добавление возможности конвертирования в сам Markdown (без специфических особенностей присутствующих в расширении этого языка от Maruku) и Textile2. Ценность последних конверторов, на мой взгляд, довольно сомнительная.

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

  • Markdown в Wikipedia(ru) и Wikipedia(en)
  • Описание синтаксиса Markdown от Daring Fireball
  • Загляните в каталог <путь к каталогу с gem-ами>/gems/maruku-0.5.9/docs, а затем в файл markdown_syntax.html

Поэтому расскажу как Maruku расширяет Markdown. Начну я с того, что расскажу о двух основных категориях элементов разметки:

  • span-элемент - это такой элемент разметки, который состоит из одной строки текста (т.е. в нем нет переходов на новую строку). Как правило, эти элементы конвертируются в строковые (или как их ещё называют встроенные) элементы HTML. Например, _такой текст на Markdown_ будет преобразован в <em>такой текст на Markdown</em>

  • блочный элемент - такой элемент может состоять из нескольких строк. Примером такого элемента в Markdown может являться таблица или параграф

А теперь поговорим о расширениях. Их существует два вида:

Inline Attribute Lists (IAL)

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

{: <список атрибутов> }

Для span-элемента она должна идти сразу же за ним (без о всяких пробелов, за исключением заголовков где допускаются отступы), а для блочных элементов располагаться на новой строке под ним (без о всяких пустых строк между ними, но допускается отступ от начала строки до 3-х символов)

Это пример расширения _span-элемента_{: style="color:red" }

А это пример 
расширения блочного
элемента
{: style="background-color:silver" }

А вот результат:

Это пример расширения span-элемента

А это пример расширения блочного элемента

Как вы заметили, в качестве списка атрибутов применяются атрибуты допустимые для HTML элементов. Но допустимы и следующие сокращения:

.<имя CSS класса> - будет преобразовано в class=”<имя CSS класса>”. Соответственно если такие конструкции встречаются несколько раз то все они будут помешены внутрь одного элемента class, как того требует спецификация CSS

#<id элемента> - будет преобразовано в id=”<id элемента>”.

key=value - будет преобразовано в атрибут key=value. Если value содержит пробелы, то его надо заключить в двойные кавычки.

ref_id - т.е. просто идентификатор без префиксов (# или .) говорит о том, что к элементу будет применяться стиль, описанный в ALD расширении. Без этой возможности Maruku не особо бы отличался от RedCloth. Но об этом ниже.

Attribute Lists Definitions (ALD)

А вот здесь начинаются чудеса :)

Вы можете объявить в любом месте вашей разметки конструкцию вида:

{:<ref_id>: <список атрибутов> }

а затем ссылаться на это объявление в любом IAL используя указанный вами ref_id

Пример:


Текст
{: red }

{:red: style="background-color: silver" }

Текст
{: red }

А вот результат:

Текст

Текст

На этом пока всё. В следующий раз я расскажу вам как подключить и использовать Maruku в своём приложении.

* * *

RedCloth в топку! Да здравствует Maruku!

Говори на правильном языке

Сегодня утром RedCloth меня окончательно добил :( и я решил совершить подвиг и перевести блог на другой движок разметки. Причём поменял не просто сам движок, но ещё и язык разметки. Теперь всё топики у меня работают на Maruku (Респект Andrea Censi!), а языком разметки стал Markdown. Сейчас уже нет сил что либо делать, а вот завтра я постараюсь рассказать про Maruku более подробно.

* * *

Жонглируем файлами в Capistrano

Только б не дисконнект!!!! Только б не дисконнект!!!

Жонглировать файлами в Capistrano просто :), потому как в одной из последних версии Capistrano появились два замечательных метода upload и download которые позволяют закачивать файлы на сервер и скачивать их с него соответственно.

download <путь к файлу на сервере>, 
         <путь на локальной машине куда будет скопирован файл>, <опции>

upload <путь на локальной машине откуда будет скопирован файл>, 
         <путь к файлу на сервере>, <опции>

С первыми двумя аргументами думаю всё понятно. Третий аргумент принимает хеш, одним из значений которого может быть ключ :via указывающий какой клиент будет использован для передачи файлов (SCP или SFTP который используется по умолчанию)

Метод upload в качестве опции так же принимает ключ :mode и в случае его наличия вызывает поле окончания процесса загрузки команду chmod передав её значение указанное в ключе (например :mode => 755)

Пример:

task :get_production_log, :role => [:app] do
  download "#{current_path}/log/production.log", 'tmp/production.log', :via => :scp
end
* * *

Особенности работы с русскими строками в Ruby

Иногда обычные вещи выглядят необычно

До этого как-то не приходилось с этим сталкиваться…

Оказывается чтобы операции со строками содержащими русские символы заработали так как нужно вам, сначала надо преобразовать строку в символы. Делается это с помощью метода chars который входит в состав Active Support (и который по умолчанию используется в Rails):

# Говорим Ruby что будем работать 
# с UTF-8 (в Rails применяется по умолчанию)
$KCODE = "UTF8"

# Подгружаем Active Support (в Rails подключается по умолчанию)
require "rubygems"
require "active_support"

s = "привет"
puts s
puts s.upcase
puts s.chars.upcase

Результат:

привет
привет
ПРИВЕТ
* * *