トップ «前の日記(2007-04-13) 最新 次の日記(2007-04-20)» 編集

Route 477



2007-04-16

[event] Livecoding#3.1

氏久さんに誘われて、Livecoding#3の打ち上げ*1に混ぜてもらいました。

非常にライブ感溢れる飲み会で楽しかったです。ありがとうございました。

  • ライブ鍋
  • ライブ箸
  • ライブ泡盛
  • ライブ氷
  • ライブ…あと忘れた

(もしかして:ライブ酔っ払い)

あとLivecoding#4は農場か海上で開かれるそうです。

*1 なんで4月になったのかは不明

[ruby] REXMLが難しすぎるのでJSONパーサ書いた

twitterの新着を表示するRubyスクリプトを書こうとしたんだが、XMLのパース方法が30分調べても理解できなかったんで 頭にきてJSONパーサを書いた。REXML難しすぎるよ…(酒のせいという説もある)。 *1

実装はtanakhさんのhttp://fxp.hp.infoseek.co.jp/arti/parser.htmlを大いに参考にしています。 構成要素ごとにメソッドを作るやり方ですね。 Rubyだと

  • slice!を使って文字列を削っていく
  • 何文字目まで処理したかの整数を管理する

という2種類の方法が考えられるんですが、ここでは速度を考えて後者で実装してみました。 *2

所要時間は1時間ちょい。慣れればもっと速く書けるんだろうなぁ。

class JSON
  class ParserError < Exception
    def initialize(txt, i)
      msg = "Parse error at col #{i}: #{txt[i,20].inspect}"
      super(msg)
    end
  end

  def self.parse_file(path)
    parse(File.read(path))
  end

  def self.parse(txt)
    i = space(txt, 0)
    case txt[i]
    when ?{
      o, = object(txt, i+1)
      o
    when ?[
      a, = array(txt, i+1)
      a
    else
      raise ParserError.new(txt, i)
    end
  end

  def self.object(txt, i)
    i = space(txt, i)
    case txt[i]
    when ?}
      i = space(txt, i+1)
      [{}, i]
    else
      ms, i = members(txt, i)
      i = space(txt, i)
      raise ParserError.new(txt, i) unless txt[i] == ?}
      i = space(txt, i+1)
      [Hash[*ms], i]
    end
  end

  def self.members(txt, i)
    ms = []
    loop do
      raise ParserError.new(txt, i) unless txt[i] == ?"
      s, i = string(txt, i+1)
      i = space(txt, i)
      raise ParserError.new(txt, i) unless txt[i] == ?:
      i = space(txt, i+1)
      v, i = value(txt, i)
      ms.concat [s, v]

      i = space(txt, i)
      break unless txt[i] == ?,
      i = space(txt, i+1)
    end
    [ms, i]
  end

  def self.array(txt, i)
    i = space(txt, i)
    if txt[i] == ?]
      i = space(txt, i+1)
      [[], i]
    else
      es, i = elements(txt, i)
      i = space(txt, i)
      raise ParserError.new(txt, i) unless txt[i] == ?]
      i = space(txt, i+1)
      [es, i]
    end
  end

  def self.elements(txt, i)
    es = []
    loop do
      v, i = value(txt, i)
      es << v
      i = space(txt, i)
      break unless txt[i] == ?,
      i = space(txt, i+1)
    end
    [es, i]
  end

  def self.value(txt, i)
    i = space(txt, i)
    case txt[i]
    when ?"
      string(txt, i+1)
    when ?0..?9, ?.
      number(txt, i)
    when ?-
      n, i = number(txt, i+1)
      [-n, i]
    when ?{
      object(txt, i+1)
    when ?[
      array(txt, i+1)
    when ?t
      raise ParserError.new(txt, i) unless txt[i,4] == "true"
      [true, i+4]
    when ?f
      raise ParserError.new(txt, i) unless txt[i,5] == "false"
      [false, i+5]
    when ?n
      raise ParserError.new(txt, i) unless txt[i,4] == "null"
      [nil, i+4]
    else
      raise ParserError.new(txt, i)
    end
  end

  def self.string(txt, i)
    start = i
    i += 1 until txt[i] == ?" && txt[i-1] != ?\\
    raise ParserError.new(txt, start) if txt.size <= i
    [txt[start..i-1].gsub(/\\([^nu])/, "\\1"), i+1]
  end

  def self.number(txt, i)
    to = nil
    float = false
    i.upto(txt.size-1) do |k|
      case txt[k]
      when ?0..?9
        next
      when ?.
        float = true
      else
        to = k
        break
      end
    end
    raise ParserError.new(txt, i) unless to

    num = float ? txt[i..to-1].to_f : txt[i..to-1].to_i
    [num, to]
  end

  def self.space(txt, i)
    loop do
      case txt[i]
      when ?\s, ?\n, ?\r
        i += 1
      else
        break
      end
    end
    i
  end

end

selfまみれになっているのがちょっと気になる。^^; class << JSON とかすればいいんだけど、 そうすると今度はJSON::ParserErrorをどこに書けばいいのかわからん。

関連

REXMLはこんな風に使えば良かったらしい。

*1 だいたいさあ、 require 'rexml/document' の時点でもう覚えられんよね。

*2 と思ったんだけど、実はslice!(0,*)ってそんなに重い処理じゃない気がしてきた

[ruby] twitterの新着を表示するRubyスクリプト

というわけで、twitterの新着を標準出力に吐くRubyスクリプトです。

  • userとpassを自分のアカウントに書き換えてください
  • 上のjson.rbが必要(同じディレクトリに置く)
  • 端末はeuc-jpを仮定

あ、僕のtwitterアカウントは http://twitter.com/yhara です。あんまりまめには更新してないですが、よろしくどうぞ。

#!/usr/bin/env ruby
require 'net/http'
require 'kconv'
require 'json'

user = "your user name"
pass = "your password"

def decode_utf(str)
  str.gsub(/\\u([a-f\d]{4})/){ [$1.to_i(16)].pack "U" }
end

Net::HTTP.version_1_2
req = Net::HTTP::Get.new("/statuses/friends_timeline.json")
req.basic_auth user,pass

json = Net::HTTP.start('twitter.com',80) {|http|
  http.request(req).body
}

JSON.parse(json).each do |st|
  puts "<#{st['user']['screen_name']}> #{decode_utf(st['text']).toeuc}"
end

そのうちnadoka botにする。

逆にRubyからtwitterに投稿するほうは、bluewindからtwitterを更新できるようにRubyでシンプルなクライアントを書いた とか。