自分なりの PRagger - ActiveProperty

Yet Another Pragger -- BONNOH FRACTION 13

PRaggerソースコードが短かくて、中身がシンプルなので、しばらく使っていると自分なりの味を出したくなる気持ちは十分わかる。
ただ、起動時にプラグインをすべて読み込む(これが結構遅い)や Logger がないことや YAML はあまり好きじゃなかったなど理由はあった。


僕の場合、自分なりの改良を続けていった結果、別のものになっていってしまったけれども(プラグインも互換がないし)。
方針としては、データを収集したりする部分に特化し、加工部分は Ruby の文法で処理を記述でき、プラグインみたいな処理を追加したい場合は extend メソッドで追加していくような仕組みした。
これは今 Web ラジオの RSS 配信で使っている。

名前は思いつかなかったので、Active Record っぽいからということで、ActiveProperty にしている。

#!/usr/bin/ruby

require 'open-uri'
require 'kconv'

module ActiveProperty
  class Base < Array
    @@instances = {}

    def self.site_url=(url)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:site_url] = url
    end

    def self.site_url
      if @@instances.key?(self) && @@instances[self].key?(:site_url)
        @@instances[self][:site_url]
      else
        nil
      end
    end

    def self.site_title=(title)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:site_title] = title
    end

    def self.site_title
      if @@instances.key?(self) && @@instances[self].key?(:site_title)
        @@instances[self][:site_title]
      else
        nil
      end
    end

    def self.prepare(&block)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:prepare] = block
    end

    def self.process(&block)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:process] = block
    end

    def self.split_block(options, &block)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:split_block_block] = block
      @@instances[self][:split_block_options] = options
    end

    def self.fetch
      return nil  unless @@instances.key?(self)

      contents = ''
      begin
        open(@@instances[self][:site_url]) do |http|
          contents = http.read.toutf8
        end
      rescue Exception => e
        # TODO: OpenURI::HTTPError, etc
        puts "#{@@instances[self][:site_url]} ... #{e.message}"
      end

      records = Extract.scan(contents, @@instances[self][:split_block_options], &@@instances[self][:split_block_block])

      base = self.new

      records.each do |entry|
        r = entry
        r = @@instances[self][:after].call(entry)  if @@instances[self].key?(:after)
        base << entry  unless r.nil?
      end

      base
    end

    def self.after(&block)
      @@instances[self] = {}  unless @@instances.key?(self)
      @@instances[self][:after] = block
    end
  end

  class Extract
    attr_accessor :records, :fields, :source

    def initialize(source)
      @source = source
      @records = []
      @fields = {}
    end

    def self.scan(source, options, &block)
      extract = self.new(source)

      source.split(%r{#{options}}m).each do |data|
        extract.fields = {}
        extract.source = data
        extract.instance_eval(&block)
        extract.records << extract.fields  unless extract.fields.empty?
      end

      extract.records
    end

    def record_limit(limit = 0)
    end

    def method_missing(name, *argv)
      # TODO: 
      reg = argv[0]
      val = %r{#{reg}}m.match(@source).to_a[1]
      @fields[name] = val  unless val.nil?
    end
  end
end