Статьи

Ruby’s Pathname API

Pathname Docs

Стандартная библиотека Ruby – это клад полезных классов, и класс Pathname безусловно, является одним из них. Представленный в Ruby 1.9, Pathname представляет путь в файловой системе, предоставляя удобный доступ к функциям, которые иначе разбросаны по горстке других классов, таких как File , FileTest и Dir .

Теперь, когда жизнь Ruby 1.8 официально подошла к концу , а 1.9 близится к своему пятому дню рождения , пришло время отказаться от устаревшей поддержки и принять элегантность и простоту использования Pathname .

Чтобы показать, почему Pathname было столь необходимым дополнением, давайте рассмотрим более традиционный способ манипулирования информацией о путях. Вот как мы делали вещи за 1,8 дня:

 CONFIG_PATH = File.join(File.dirname(__FILE__), '..', 'config.yml') 

Обратите внимание на использование методов класса, редкое зрелище в языке, где все является объектом. Но какова альтернатива? Объектно-ориентированной задачей было бы приблизить функциональность к данным, с которыми он работает. В этом случае мы просто манипулируем строками, но на самом деле не имеет смысла добавлять join и dirname в String . Строка – это слишком общий тип данных для такой специфической функциональности.

Введите имя Pathname :

 CONFIG_PATH = Pathname.new(__FILE__).dirname.join('..', 'config.yml') 

Pathname строку в объект Pathname , мы получаем чистый API для манипулирования путем. Этот код уже больше похож на идиоматический Ruby. Важным отличием от ванильных строк Ruby является то, что экземпляры Pathname являются неизменяемыми. Нет методов, которые изменяют Pathname экземпляр Pathname . Вместо этого все методы, такие как dirname , join или basename возвращают новый экземпляр Pathname , оставляя существующий экземпляр без изменений.

API Pathname

Методы Pathname делятся примерно на одну из двух категорий: либо они просто манипулируют строкой пути, не обращаясь к файловой системе, либо они обращаются к операционной системе для выполнения проверок и операций над самим файлом или каталогом. Полный список из восьмидесяти методов вы можете найти в официальной документации Pathname API . Стоит отметить, что документы также доступны из терминала через ri Pathname или в irb с help 'Pathname' .

Давайте посмотрим на некоторые методы, которые могут оказаться полезными.

Тип файла и проверки разрешений

 pn = Pathname.new('/usr/bin/ruby') pn.file? # => true pn.directory? # => false pn.absolute? # => true pn.relative? # => false pn.executable? # => true pn.readable? # => true pn.writable? # => false pn.root? # => false 

Большинство из них соответствуют методам FileTest

Навигация и запросы к файловой системе

 pn = Pathname.getwd # current working directory pn.children # => [ #<Pathname...>, ... ] pn.each_child {|ch| ... } pn = pn.parent Pathname.glob('**/*.rb') # like Dir['**/*.rb'] but returns Pathnames 

Путь манипуляции

 pn = Pathname.new('lib/mylib/awesome.rb') pn.dirname # => #<Pathname:lib/mylib> pn.basename # => #<Pathname:awesome.rb> pn.extname # => ".rb" pn.expand_path('/home/arne') # => #<Pathname:/home/arne/lib/mylib/awesome.rb> 

Работа с актуальным файлом

 pn = Pathname.new(ENV['HOME']).join('.bashrc') pn.size pn.read pn.open {|io| ... } pn.each_line {|line| ... } pn.rename('/tmp/foo') pn.delete 

Pathname() и to_path

Существует два способа создания экземпляров Pathname : с помощью явной конструкции, как описано выше, или с помощью Pathname() преобразования Pathname() . Хотя заглавные идентификаторы обычно зарезервированы для классов и констант, в Ruby есть ряд встроенных методов для преобразования произвольных значений в определенный тип, названный в честь классов, в которые они преобразуются.

 Array(1) # => [1] Array(nil) # => [] Array([1,2]) # => [1,2] String(7) # => "7" URI("http://example.com") # => #<URI::HTTP:0x1c0e URL:http://example.com> 

Они идут рука об руку с несколькими конверсионными хуками, которые могут реализовать объекты, например to_ary или to_str . Аналогично, объекты могут реализовать to_path чтобы указать, как они могут быть преобразованы в имя пути. Конструктор pathname реализован в C для эффективности, но он будет примерно переведен в Ruby следующим образом:

 class Pathname def initialize(path) path = path.to_path if path.respond_to? :to_path @path = String(path) end end 

Допустим, вы пишете несколько сценариев, которые помогут вам управлять своей музыкальной коллекцией. AudioFile может выглядеть так:

 class AudioFile def initialize(path) @path = path end def to_path @path end # ... end 

Многие встроенные методы оборачивают свои аргументы имени файла при вызове Pathname() , поэтому вы можете передать свой экземпляр AudioFile точно так же.

 af = AudioFile.new('...') File.open(af) do |io| #... end 

При написании кода библиотеки используйте этот шаблон для максимальной гибкости:

 def read_configuration(config_file) parse_configuration(Pathname(config_file).read) end 

Теперь вызывающая config_file этого кода может передаваться как config_file

  • строка
  • путь
  • объект, который отвечает на to_path
  • любой объект, который может быть преобразован с помощью String()

Создание браузера музыкальной коллекции

Чтобы показать, насколько мощна и интуитивно понятна работа с Pathname , мы напишем небольшой API для просмотра нашей музыкальной коллекции. Файлы организованы с песнями, сгруппированными в каталогах для альбома, которые снова сгруппированы по исполнителю. Например: ~/Music/Arsenal/Oyebo Soul/09_How Come.flacc .

Мы стремимся к простому в использовании API, который выглядит следующим образом:

 mc = MusicCollection.new('~/Music') # => #<MusicCollection 'Music'> mc.artists.first # => #<Artist 'Arsenal'> mc.artists.first.albums.first # => #<Album 'Oyebo Soul'> mc.songs.count # => 120 mc.songs.first.path # => #<Pathname: ...> mc.songs.first.play 

Класс Song по сути является нашим классом AudioFile сверху:

 class Song attr_reader :path alias to_path path def initialize(path) @path = Pathname(path).expand_path end def name String(path.basename(path.extname)) end def play(options = {}) exec(options.fetch(:player, 'mplayer'), String(path)) # OS X users can use player: '/usr/bin/afplay' end end 

Мы немедленно вызываем expand_path для нормализации нашего ввода. Это развернет ~ в домашний каталог пользователя и разрешит любые относительные имена файлов (например . И .. ). Таким образом, у нас обязательно будет абсолютное имя файла.

Теперь нам нужно добавить классы для Album , Artist и, наконец, самой MusicCollection . Поскольку все они являются классами- Pathname вокруг Pathname , мы можем Pathname общую систему в базовый класс.

 class PathnameWrapper attr_reader :path alias to_path path def initialize(path) @path = Pathname(path).expand_path end def name # ... end def to_s # ... end end class Song < PathnameWrapper def play # ... end end 

Теперь мы можем добавить Album и предоставить методы навигации туда и обратно. Мы также добавим to_proc files и directories в наш базовый класс вместе с to_proc класса to_proc . При передаче объекта в виде блока с & , Ruby будет использовать to_proc чтобы найти реализацию для блока. Таким образом, мы получаем сокращение для типизации всех элементов коллекции.

 class PathnameWrapper # ... def directories path.children.select(&:directory?) end def files path.children.select(&:file?) end def self.to_proc ->(path) { new(path) } end end class Album < PathnameWrapper EXTENSIONS = %w[.flacc .m4a .mp3 .ogg .wav] def songs files.select do |file| EXTENSIONS.any? {|ext| ext == file.extname } end.map(&Song) end end class Song < PathnameWrapper # ... def album Album.new(path.dirname) end end 

Чтобы MusicCollection итоги, мы представляем Artist и MusicCollection .

 class MusicCollection < PathnameWrapper def artists directories.map(&Artist) end def albums artists.flat_map(&:albums) end def songs albums.flat_map(&:songs) end end class Artist < PathnameWrapper def albums directories.map(&Album) end end class Album < PathnameWrapper # ... def artist Artist.new(path.dirname) end end class Song < PathnameWrapper # ... def artist album.artist end end 

Теперь мы можем просматривать между песнями, альбомами и исполнителями.

На этом мы завершаем основы нашего API MusicCollection , а также познакомимся с классом Pathname . Хорошее понимание стандартной библиотеки Ruby может помочь сделать ваш код более элегантным, корректным и лаконичным. Здесь мы увеличили один небольшой компонент, выделив его многократное использование. Идите вперед и используйте Pathname умом.