Rubyで内部DSLっぽいのやってみる
少し前に仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプトというRubyスクリプトを書いたのだが、スクレイピング対象サイトをハッシュで持ってるあたりが使いづらい。良い機会なので内部DSLってのに挑戦してみた。
しかし、内部DSLについてざっと調べたものの、まだ租借しきれていない。とりあえず、Sinatraっぽい構文でスクレイピング対象サイトを指定できるようにした。
sites.rb(ユーザー側)
fetch 'yahoo' do url 'http://www.yahoo.co.jp' regexp /topics.+?>([^<]+?)</ end fetch 'hatena' do url 'http://b.hatena.ne.jp/hotentry' regexp /entry-link.+>(.+?)</ end fetch 'naver' do url 'http://matome.naver.jp/' regexp /matomename.+?-->(.+?)<!--/m end fetch '2ch' do url 'http://uni.2ch.net/newsplus/' regexp /<a.+?>\d+?:\s(.+?)</ end
news(定義側)
#!/usr/bin/env ruby require 'open-uri' require 'nkf' class Site attr_accessor :name, :url, :regexp def initialize(n) @name = n end def url(u) @url = u end def regexp(r) @regexp = r end def get open(@url) do |f| f.read.scan(@regexp) {|m| puts NKF.nkf('-w', m.join)} end end end @defined_sites = [] def fetch(name, &block) s = Site.new(name) s.instance_eval &block @defined_sites << s end load File.expand_path('../sites.rb', __FILE__) defined_site_names = @defined_sites.map(&:name) site_names = ARGV.empty? ? defined_site_names : ARGV sites = [] site_names.each do |site_name| raise 'not defined' unless defined_site_names.include? site_name @defined_sites.map {|s| sites << s if s.name === site_name} end sites.map(&:get)
実行
上記2ファイルを同じディレクトリに配置し、newsに実行権限を与え、パスを通せば前回と同様に動く。