суббота, 15 декабря 2007 г.

Polaroid thumbnails

Хотите быть способным преобразовывать так картинки на лету?

Я реализовал у себя в одном rails приложении генерацию подобных thumbnails при отправке пользователем картинки с помощью следующего кода:

  def after_save 
thumbnail = Magick::Image.read(image_path).first
cols, rows = thumbnail.columns, thumbnail.rows

thumbnail.crop_resized!(400,300)
thumbnail[:caption] = "\n #{pet.breed.animal.kind}, #{pet.breed.title}, #{pet.name}\n Cost: #{pet.price}$"

thumbnail = thumbnail.polaroid(5 - rand(10)) do
self.gravity = Magick::CenterGravity
self.shadow_color = "black"
self.align = Magick::LeftAlign
self.pointsize = 30
self.font_family = 'comic sans ms'
end

thumbnail.change_geometry!("#{cols}x#{rows}") do |ncols, nrows, img|
img.resize!(ncols, nrows)
end

thumbnail.resize!(200,150)
thumbnail.write(thumbnail_path)
end

Требуется наличие RMagick.

Примечание: делается двойной resize - до и после полароид-эффекта, для более четкого изображения.

YAML-конфиги

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

module YAML
def self.set_connection(path)
data = load_file(path).symbolize_keys
class << data
attr_accessor :path
def sync
File.open(@path, 'w+') { |file| file.puts(self.stringify_keys.to_yaml) }
end
end
data.path = path
data
end
end



Заметьте, что я также автоматом применяю symbolize_keys, когда вытаскиваю данные из yaml, и stringify_keys, когда отправляю обратно изменения. Все это сделано по простой причине, что у меня на верхнем уровне конфига всегда находится хэш (со строками в качестве ключей, так как они смотрятся более органично в yaml чем символы, на моей взгляд), а в коде уже удобнее работать с символами для доступа к отдельным разделам конфига. Можно было, конечно, проверять является ли хэшем выгружаемая информация и, в случае, положительного ответа, применять эти функции, но кто захочет - сам сделает, а для моих целей этого достаточно.

Пример из скрипта, который я использую для управления рейлс приложениями:

$config = YAML.set_connection('config.yaml'.in_current_dir)
#...
mode 'config' do
keyword('repository', 'r') do
default $config[:repository]
#...
end

keyword('rails_apps') do
default $config[:rails_apps]
#...
end

def run
#...
%w(repository rails_apps).each { |setting| $config[setting.to_sym] = params[setting].value }
$config.sync
end
end

суббота, 8 декабря 2007 г.

Amazing ruby

Сегодня на учебе я увидел, что мой знакомый внимательно пытается чего-то разглядеть у себя на ноуте в переменной PATH, к счастью, у него на компе находился руби (результат - предыдущих безуспешных попыток подсадить его на руби).

Одна строчка в консоли:
ruby -e "puts ENV['PATH'].split(';').sort"

(; - так как действие проиходило в windows)
и он обладатель читабельного приятного списка. Удивительно, но данный пример поразил его настолько, что он наконец признал, что в руби есть что-то очень хорошее =)))


Также сегодня перевел код знакомого из:
r = []; a.each { |x| b.each { |y| r << [x,y] } };
в
(a*b.size).zip(b*a.size)

Демонстрация,
a, b = [1,2,3], [:a, :b]
p (a*b.size).zip(b*a.size) # => [[1, :a], [2, :b], [3, :a], [1, :b], [2, :a], [3, :b]]

Интересно, потянет на http://www.novemberain.com/tags/TiaBWTDI ? =))

среда, 5 декабря 2007 г.

stderr for `cmd`

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

# extensions.rb

def `(cmd)
Open3.popen3(cmd) do |stdin, stdout, stderr|
out, err = [stdout, stderr].map &:readlines
{:short_out => out[0], :short_err => err[0], :out => out, :err => err, :all => out + err}
end
end

Примечание: для того, чтобы работала сокращенная форма
[stdout, stderr].map &:readlines

Необходимо, чтобы был определен метод to_proc для Symbol:
class Symbol
def to_proc
Proc.new { |obj, *args| obj.send(self, *args) }
end
end

Теперь можно работать с консольными командами следующим образом:
require 'extensions'

status = `rm non-existent-file`
p status[:short_err] # => "rm: non-existent-file: No such file or directory\n"
p status[:out] # => []


Или сразу вытаскивать нужные потоки:
out = `rm existent-file`[:out]
err = `rm non-existent-file`[:err]
all = `rm any-file`[:all]

Или, если использовать модифицированный хэш-аксессор из предыдущего поста:
short_out, full_out = `ln -s`[:short_out, :out]
out, err = `rm any-file`[:out, :short_err]



Также есть простой способ, с помощью которого без Open3 можно получить объединенный вывод для stderr и stdout. Для этого просто нужно указать в конце системного вызова 2>&1 - то есть дописать в стандартный вывод все полученные сообщения об ошибках.
Пример:
p `rm non-existent-file 2>&1` # >> "rm: non-existent-file: No such file or directory\n"

Improved hash accessor

Ruby разрешает получить доступ только к одному значению хеша через []. К счастью, это легко исправить:

#extensions.rb

class Hash
alias value_by_index []
def [](*keys)
case (values = keys.map { |key| value_by_index key }).size
when 1 then values.first
when 0 then nil
else values end
end
end

Примеры,
require 'extensions'

hash = { :a => 1, :b => 2 }
p hash[:a, :b] # => [1, 2]
p hash[:a] # => 1
p hash[:d] # => nil

File helpers

Используя, fcaller из предыдущего поста, я расширил функциональность класса File удобными для меня методами:

class File
def self.current_dir
dirname(fcaller[:file])
end
def self.in_current_dir(name)
join(dirname(fcaller[:file]), name)
end
end

class String
def in_current_dir
File.join(File.dirname(fcaller[:file]), self)
end
end

def __DIR__
File.dirname(fcaller[:file])
end


Гораздо читабельнее вместо
File.join(File.dir(__FILE__), 'bar.rb')

писать
File.in_current_dir('bar.rb')

или
'file.rb'.in_current_dir

И гораздо проще вместо
File.dirname(__FILE__)

писать
__DIR__

Pretty formatted caller

Известно, что стандартный caller в ruby возвращает инфу в некрасивом формате (eg, "prog:5:in `b'"). Но это можно исправить, написав обертку для него.

Работаем с ним как с обычным caller, но на выходе получаем удобноваримые результаты:

# extensions.rb

def fcallers(start=1)
chain = caller start + 1
(chain || []).map { |it|
if it[/(.*):(\d+)(?::in `(.+)')?/]
{
:file => Regexp.last_match[1],
:line => Regexp.last_match[2].to_i,
:method => Regexp.last_match[3]
}
else
raise 'Undefined format of caller'
end
}
end

# foo.rb

require 'extensions'

def foobar
p fcallers
end

# bar.rb

require 'foo'

def barfoo() foobar end

barfoo


Output:
[{:method=>"barfoo", :file=>"/Users/brainopia/ruby_apps/extensions/bar.rb", :line=>3}, {:method=>nil, :file=>"/Users/brainopia/ruby_apps/extensions/bar.rb", :line=>5}]


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

def fcaller(start=1)
fcallers(start+1).first
end