LE Blog

Инженер с поэтической душой

16.03.2009 firtree_right Автоматизация процесса публикации

Когда я впервые прочитал про, например, Capistrano, мне, конечно же, сразу захотелось тоже начать применять эту клёвую штуку. Но я, конечно же, не преодолел барьер входа. На тот момент у меня было полтора приложения на ruby on rails, которые я довольно редко обновлял. Позже, когда я начал регулярно обновлять несколько приложений, использовать средства автоматизации оказалось очень просто и очень естественно. Для этого достаточно было вручную обновить приложение раз двадцать. :)

Задача

Допустим, речь идёт не о веб-приложении, а о библиотеке, которая используется на сервере несколькими веб-приложениями и другими программами, которые так же исполняются на сервере. Вполне логичным представляется сделать её в виде rubygem. И тогда встает вопрос обновления этой библиотеки на сервере.

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

cd somedir && do_some_stuff && sudo do_some_sudo_stuff

Использовать для этого любую готовую библиотеку публикации веб-приложений кажется слишком громостким. Так почему бы не написать задачу для rake, которая бы делала именно то, что нужно.

Ресурсы

Нам понадобится две библиотеки: одна для использования ssh, и другая для защищенного от заглядывания через плечо ввода sudo-пароля. (Оказалось, что сделать на руби такой ввод не так просто, поэтому я просто взял готовую библиотеку, которую и так использует, например, Capistrano и ряд других приложений).

sudo gem i net-ssh highline

Решение

Первым делом я, конечно, попробовал:

Net::SSH.start("myserver", "sudouser") do |ssh|
  result = ssh.exec!("cd somedir && do_some_stuff && sudo do_some_sudo_stuff")
  puts result
end

Но никакого вывода просто не дождался. Потому что дойдя до sudo-команды, процесс просто оставался в вечном ожидании.

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

Net::SSH.start("myserver", "sudouser") do |ssh|
  channel = ssh.open_channel do |ch|
    ch.request_pty do |c, success|
      raise "Cannot obtain pty" unless success
    end
    ...
  end
end

Теперь нужно отправить пароль в нужный момент. Чтобы узнать, когда наступил нужный момент, нужно использовать ключ -p (prompt) при вызове sudo, чтобы сказать ему, каким запросом спрашивать у нас пароль.

sudo -p 'sudo password: ' do_some_sudo_stuff

Когда нужно будет запросить пароль, воспользуемся библиотекой highline:

pwd = HighLine.new.ask("Input remote host sudo password: ") { |q| q.echo = false }

Это позволит нам получить пароль, не светя его на экране. Как это обычно и делает sudo.

Теперь посмотрим на всё решение целиком. В папке библиотеки создаем файл Rakefile. Записываем в него нашу задачу. В моём случае команда для сервера состояла примерно из следующего набора: «Перейти в папку, обновить исходники из scm, собрать джем, sudo установить джем, sudo удалить установленные старые версии джема».

Rakefile

require 'rubygems'
require 'rake'
require "net/ssh"
require 'highline'
...
desc "Update gem on the server by current version on remote origin"
task :deploy do
  Net::SSH.start("myserver", "sudouser") do |ssh|
    channel = ssh.open_channel do |ch|
      ch.request_pty do |c, success|
           # Если pseudo-tty недоступен, то невозможно никакого интерактива
        raise "Cannot obtain pty" unless success
      end

      ch.exec("cd somedir && do_some_stuff && sudo -p 'sudo password: ' do_some_sudo_stuff") do |c, success|
        abort "Could not execute command" unless success

        c.on_data do |c, data|
          if data =~ /sudo password: /
            pwd = HighLine.new.ask("Input remote host sudo password: ") { |q| q.echo = false }
            c.send_data "#{pwd}\n"
          else
            c[:result] ||= ""
            c[:result] << data # Можно, конечно, и в процессе выводить
          end
        end

        c.on_extended_data do |c, type, data|
          puts "STDERR : #{data}"
        end
      end
    end
    ssh.loop # Ожидаем, пока закончится сеанс
    puts channel[:result] # Выводим результат сеанса (можно было и в процессе)
  end
end
...

Теперь вместо всей той последовательности действий достаточно написать запустить rake deploy и ввести пароль.

Материалы для изучения

Пособия по использованию Capistrano Документация Net::SSH

11.03.2009 firtree_right Использование GEdit для разработки на Ruby и Flex

Сегодня я хочу поделиться своими ресурсами для разработки. В частности, хочу рассказать про IDE. Основную часть рабочего времени я провожу под кубунту. После долгих метаний и проб я остановился на GEdit, как на универсальном средстве разработки.

Установка

Для работы нам понадобятся плагины для GEdit. Они содержат основной набор вкусностей: code snippets, file browser pane, terminal pane и тому подобное. Поэтому устанавливаем:

sudo apt-get install gedit gedit-plugins

Дополнительные файлы, которые понадобятся, находятся в теле поста.

Ruby и Ruby on Rails

Подсветка синтаксиса и сниппеты для ruby уже поставляются из коробки. Единственное, если вы откроете файл со спецификацией подсветки синтаксиса для ruby, то обнаружите там пометку FIXME, касающуюся подсветки интерполяции внутри строки. Это связано с особенностями обработки правил подсветки самим редактором. Эти особенности удалось обойти, присвоив этому правилу измененный цвет фона.

Подсветку синтаксиса Ruby положить в /usr/share/gtksourceview-2.0/language-specs/ Для корректной работы подсветки, потребуется определить стиль для ruby:interpolation. Я использую тему darkmate, в которой определил необходимые дополнительные цвета на свой вкус. Положить следует в /usr/share/gtksourceview-2.0/styles/, а затем выбрать эту тему в установках редактора.

Язык для описания подсветки синтаксиса достаточно прост. Кроме всего прочего он позволяет ссылаться из правил одного языка на правила другого. Что, собственно, нам и понадобится для подсветки языка темплейтов erb. Ведь erb — это по сути html со вставками ruby. Теперь, когда у нас есть оба описания, берём описание для html и вставляем в него ссылку на ruby.

Так же следует определить mime-type для erb. Правила подсветки erb предполагают, что в системе определен mime-type text/erb. В четвертом KDE описать свои типы файлов можно в System->System Settings->Advanced->File Associacions

Мой файл использует цвет erb:background, который так же определен в файле с темой.

Подсмотрев code snippets для других языков, а так же в процессе использования, вполне можно самостоятельно написать пару-тройку полезных выражений.

Flash и Flex

Для работы с flex нам понадобится flex sdk, а так же отладочная версия standalone flash player. Я уж не буду вдаваться в подробности, как там написать компилирующий или запускающий скрипт. А выложу сразу подсветку синтаксиса.

Если определить тип (mime-type) text/x-shockwave, то можно использовать правила для подсветки ActionScript3. Которые следует положить по адресу, указаному выше.

Так же, используя описанное выше знание, легко создать описание подсветки синтаксиса mxml, зная, что это xml, со вставками actionscript. Для корректной работы следует определить mime-type text/mxml.

Заключение

Все файлы, выложенные в посте, можно смело изменять под свои нужды. Если вы используете GEdit, чтобы писать под рельсы или флекс. Я не стал выкладывать сниппеты, потому что на мой взгляд, это вопрос личной привычки. А подсветку синтаксиса, конечно, можно улучшать.

24.02.2009 firtree_right Регулярные выражения: радость победы 2 :)

Ещё один бонус, который я ожидал от этого блога, и который уже успел получить — это обратная связь. После разговора с Лёшей Кукушкиным было решено модифицировать задачу из предыдущего поста так, чтобы стало удобнее.

Задача

  1. Ввести дополнительный тэг для кода
  2. Печатать код внутри тэга так, как он должен выглядеть (прошлый раз пришлось шаманить)
  3. Иметь возможность более ли менее безнаказанно употреблять сами тэги в тексте

Решение

Итак, для кода будем использовать тэг [code]...[/code]. Для того, чтобы не провоцировать преобразование кода там, где не нужно, будет использовать знак «/». (То есть в этом абзаце жирным на самом деле написано «/[code]...[/code]»)

Так же используем стандартный метод rails для приведения в порядок того, что находится внутри тэга [code]. Результат выглядит вот так:

def lonelyelk_format(text)
  res = "<p>" + text.to_s.dup
  codes = []
  res.gsub!(//[^\/]\[code\]([\s\S]+?)\[\/code\]/) do |s|
    codes.push(s.gsub(/(^[^\/]\[code\]|\[\/code\]$)/, ""))
    "#{s[0,1]}[code#{codes.length - 1} /]"
  end
  res.gsub!(/\r\n?/, "\n")
  res.gsub!(/\n*\[h\]\n*/, "</p><h2>")
  res.gsub!(/\n*\[\/h\]\n*/, "</h2><p>")
  res.gsub!(/\n\n+/, "</p><p>")
  res.gsub!(/\n(?=\[code\d+\s\/\])/, "</p><p>")
  res.gsub!(/(\[code\d+\s\/\])\n/, '\1</p><p>')
  res.gsub!("\n", "<br />")
  res += "</p>"
  res.gsub!(/<p>\[code\d+\s\/\]<\/p>/) do |s|
    "<pre><code>#{h codes[s.gsub(/\D+/, '').to_i]}</code></pre>"
  end
  res.gsub!("<p></p>", "")
  res.gsub!("//[", "[")
  res
end

Итоги

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

23.02.2009 firtree_right Регулярные выражения: радость победы

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

Задача

Сделать форматирование текста для блога, чтобы:

  1. Можно было вставлять подзаголовки;
  2. Текст разбивался на параграфы и просто переносы строки;
  3. Со вставками кода ничего не происходило;
  4. Было написано на ruby. Использовать RedCloth не хотелось, а стандартное форматирование не подходило. Поэтому приступим.

Вытащить код

Для того, чтобы не делать лишних проверок, вытаскиваем код из страницы. Код находится внутри тэга <pre>. Первое, что приходит на ум, это выражение типа «<pre> слева, </pre> справа и ни одного </pre> посередине.». Но оказалось, что исключить выражение невозможно (по крайней мере, я не нашёл способа). Выражение типа

/<pre>[^(<\/pre>)]+<\/pre>/

По крайней мере в ruby, интерпретируется как «тэг <pre>, внутри которого не встревается ни "<", ни "p", ни "r"... и т.д.»

Для этого понадобится концепция «жадности». То есть:

/<pre>.+<\/pre>/

Cоответствует куску от первого «<pre>» до последнего «</pre>». А нам нужно жадное:

/<pre>.+?<\/pre>/

То есть до ближайшего.

Теперь про wild card. Оказалось, что точка не включает перенос строки. Поэтому нам понадобится что-то более дикое. Wild, wild card. На эту роль подходит /[\s\S]/: пробельный символ или непробельный.

Итак, вытаскивание кусков кода выглядит так:

codes = []
res.gsub!(/<pre>[\s\S]+?<\/pre>/) do |s|
  codes.push(s)
  "code#{codes.length - 1}"
end

Вокруг кусков кода

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

Что касается «кончился до», то тут используется lookahead (то есть операция при условии, что впереди есть что-то):

res.gsub!(/\n(?=code\d+)/, "</p><p>")

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

res.gsub!(/(code\d+)\n/, '\1</p><p>')

Видите, вот этот \1?

Остались мелочи: вставить обратно куски кода. Убрать пустые параграфы и параграфы, окружающие куски кода. И вы видите то, что обрабатывает текст этого сообщения.

application_helper.rb:

...
def lonelyelk_format(text)
  res = "<p>" + text.to_s.dup
  codes = []
  res.gsub!(/<pre>[\s\S]+?<\/pre>/) do |s| # вытаскиваем куски кода
    codes.push(s)
    "code#{codes.length - 1}"
  end
  res.gsub!(/\r\n?/, "\n") # приводим перево каретки к одному виду
  res.gsub!(/\n*\[h\]\n*/, "</p><h2>") # заголовки начало [h]
  res.gsub!(/\n*\[\/h\]\n*/, "</h2><p>") # заголовки конец [/h]
  res.gsub!(/\n\n+/, "</p><p>") # более одного переноса строки - параграф
  res.gsub!(/\n(?=code\d+)/, "</p><p>") # параграф перед кодом
  res.gsub!(/(code\d+)\n/, '\1</p><p>') # параграф после кода
  res.gsub!("\n", "<br />") # единичный перенос строки
  res.gsub!(/(<p>)?code\d+(<\/p>)?/) do |s| # вставляем код обратно
    codes[s[4,1].to_i] # здесь ошибка :)
  end
  res.gsub!("<p></p>", "") # убираем пустые параграфы
  res += "</p>"
end
...

Остается одна проблема. Нельзя написать в тексте поста выражение «сode{цифры}». Но для этого просто можно генерировать случайный маркер, которого точно нет в тексте вместо «code».

Обновление

После того, как я попытался написать данный пост, я обнаружил ещё ряд интересных особенностей поведения кода и браузера. А так же нашёл ошибку. Публикую финальный код без пояснений:

application_helper.rb:

...
def lonelyelk_format(text)
  res = "<p>" + text.to_s.dup
  codes = []
  res.gsub!(/<pre><code>[\s\S]+?<\/code><\/pre>/) do |s|
    codes.push(s.gsub(/(^<pre><code>|<\/code><\/pre>$)/, ""))
    "code#{codes.length - 1}"
  end
  res.gsub!(/\r\n?/, "\n")
  res.gsub!(/\n*\[h\]\n*/, "</p><h2>")
  res.gsub!(/\n*\[\/h\]\n*/, "</h2><p>")
  res.gsub!(/\n\n+/, "</p><p>")
  res.gsub!(/\n(?=code\d+)/, "</p><p>")
  res.gsub!(/(code\d+)\n/, '\1</p><p>')
  res.gsub!("\n", "<br />")
  res.gsub!(/(<p>)?code\d+(<\/p>)?/) do |s|
    "<pre><code>" + codes[s.gsub(/\D/, "").to_i].to_s.gsub("<", "&lt;").gsub(">", "&gt;") + "</code></pre>"
  end
  res.gsub!("<p></p>", "")
  res += "</p>"
end
...

Материалы для изучения

http://www.regular-expressions.info/ http://regexp.ru/