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でシンプルなクライアントを書いた とか。