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はこんな風に使えば良かったらしい。
■ [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でシンプルなクライアントを書いた とか。
[ツッコミを入れる]