読者です 読者をやめる 読者になる 読者になる

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に実行権限を与え、パスを通せば前回と同様に動く。

けど

これが内部DSLと呼べるかはよく分からない。内部DSLについては今度ゆっくり調べることにしよう。