06.07.2009Упрощение работы с путями в руби
Введение
Недавно наткнулся на интересное решение объединения путей в питоне. Вспомнил, как недавно приходилось довольно много работать с файлами и путями. И решил подбить всё в одну библиотеку (конечно же, беззастенчиво позаимствовав такой способ объединения путей). В статье более подробно хочу остановиться на пути к текущему файлу.
Текущий путь
Довольно часто встречающаяся конструкция, после объединения путей, в моём случае — это:
File.dirname(__FILE__)
Если делать класс для работы с путями файлов, то он должен наследоваться от String, чтобы можно было сделать:
File.open(filepath)
А также должен уметь определять путь файла, в котором инициализируется или вызывается.
Начнём, конечно же, с тестов. Кроме всего прочего, я предпочитаю оперировать с развёрнутыми путями, т.к. если загружать библиотеку из разных мест, то пути могут не опознаваться как одинаковые, и интерпретатор ругается, например, на повторную инициализацию констант. Итак, тест с использованием RSpec:
describe FilePath do
it "should show correct current path" do
FilePath.current.should == File.expand_path(__FILE__)
end
end
Если использовать FILE внутри класса FilePath, то там окажется путь к файлу, в котором определяется класс.
Использование $0 так же не подходит, т.к. выдает путь только главного файла. В случае запуска теста $0 будет где-то в библиотеках.
Нам бы пригодилось что-нибудь вида:
eval("__FILE__", binding_of_caller)
Но binding_of_caller работало с помощью бага, который уже давно исправлен, а Binding.of_caller выглядит очень громоздко (можно там кликнуть на ссылочку Source). Мало того, что он ломает trace_func, так он требует, чтобы метод, в котором он используется, вызывали только внутри метода.
Можно ещё передавать внутрь метода пустой Proc, вытаскивая его binding, но требовать это от человека, использующего библиотеку для упрощения жизни, как-то нелепо.
Решение
На помощь спешит Kernel.caller, знакомый нам по трейсам ошибок. Если разобраться, как он работает, то решение приходит сразу:
caller(1)[0].split(":")[0]
Остальное можно посмотреть в исходниках file_path@github. Когда соберётся джем-библиотека, я обновлю инструкции и опубликую rdoc. Вдруг кому пригодится!