なんか昨日からあちこちで見かけるので書いてみた。久しぶりの Haslellで。
あと,増田で剰余は使うな,と言ってるから使わない。
fizz = cycle ["","","Fizz"] buzz = cycle ["","","","","Buzz"] f "" n = show n f s _ = s main = mapM_ putStrLn $ zipWith f (zipWith (++) fizz buzz) [1..100]
あなごるじゃないから短くするのはやらない。
takatoh's blog – Learning programming languages.
昨日(id:takatoh:20070502:hatenagraph)のスクリプトを PRagger のプラグインにした。
受け取ったデータの最初の数値だけをポストする。
## Post data to Hatena Graph ## ## - module: publish::hatena_graph ## config: ## user_id: your hatena user id ## password: your password ## graph_name: the name of graph begin require 'rubygems' rescue LoadError end require 'hatena/api/graph' # bug fix and extention for hatenaapigraph 0.1.0 # module ::Hatena module API class Graph def post(graphname, date, value) value = value.to_f date = date.strftime DATE_FORMAT headers = { 'Access' => 'application/x.atom+xml, application/xml, text /xml, */*', 'X-WSSE' => wsse(@username, @password), } params = { :graphname => graphname, :date => date, :value => value, } res = http_post GRAPH_API_URI, params, headers raise GraphError.new("request not successed: #{res}") if res .code != '201' res end private def http_post(url, params, headers) req = ::Net::HTTP::Post.new(url.path, headers) req.form_data = params req.basic_auth url.user, url.password if url.user proxy_host = nil proxy_port = nil if proxy = ENV['http_proxy'] proxy = URI.parse(proxy) proxy_host = proxy.host proxy_port = proxy.port end ::Net::HTTP.new(url.host, url.port, proxy_host, proxy_port). start {|http| http.request(req) } end end end end def hatena_graph(config, data) value = data.first.to_f graph = Hatena::API::Graph.new(config['user_id'], config['password ']) graph.post(config['graph_name'], Time.now, value) end
設定ファイル。データはコマンドラインで指定。
- module: args - module: publish::hatena_graph config: user_id: takatoh password: xxxxxxxx graph_name: "読んだページ数"
はてなグラフを試してみた。で,いちいちログインしてデータを入力するのも面倒なので,スクリプトを書こうと思ってヘルプを見たら,はてなグラフ数値登録API の gem があるじゃないか。すばらしい。
早速インストールしてスクリプトを書いた(というかサンプルそのまんま)。
#! ruby -Ku require 'rubygems' require 'hatena/api/graph' graph = Hatena::API::Graph.new('takatoh', 'xxxxxxxx') graph.post('読んだページ数', Time.now, 20)
実行してみるとエラーが……
^o^ >hgpost.rb C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `initialize': getaddrinfo: no addres s associated with hostname. (SocketError) from C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `open' from C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `connect' from C:/usr/ruby/lib/ruby/1.8/timeout.rb:48:in `timeout' (以下略)
えーと,これはあれだ,たぶんプロキシのせいだ。ライブラリのファイル(lib/hatena/api/graph.rb)をのぞいてみると案の定プロキシには対応してないっぽい。とりあえず,べたにプロキシのホスト名とポートを書き足してやるとエラーも出ずちゃんと動いた。
ところが,ブラウザでグラフのページにアクセスしてみると,「読んだページ数」のグラフにデータをポストしたはずなのにそれは更新されず,代わりに「E8AAADE38293E381A0E3839A」という新しいグラフができていた。
これはどういう訳か。ヘルプには
指定されたグラフ名に該当するグラフが存在しない場合はグラフ作成を行った後データ追加、存在する場合は該当日付のデータ上書きを行います。
とある。試しに「TestGraph」というグラフ名でポストしてみるとちゃんと「TestGraph」グラフができていた。どうやら日本語のグラフ名がいけないらしい。文字コードは UTF-8 にしてあるのに。
再度ライブラリをのぞいてみる。グラフ名をリクエストのパラメータに指定するときにエンコードしている(ファイルの29行目,下のコードの2行目)。
params = { :graphname => URI::encode(graphname), :date => date, :value => value, }
もしかしてここか?思い切ってエンコードするのをやめてみたら,おお,うまくいった。
結局,ライブラリファイルを直接書き変えるのはやめて,問題のメソッドを再定義することにした。プロキシは環境変数 http_proxy を参照する。ポストするデータはコマンドラインから指定。ユーザIDやら何やらが直に書いてあるのは単なる手抜き。
#! ruby -Ku require 'rubygems' require 'hatena/api/graph' module Hatena module API class Graph def post(graphname, date, value) value = value.to_f date = date.strftime DATE_FORMAT headers = { 'Access' => 'application/x.atom+xml, application/xml, text/xml, */*', 'X-WSSE' => wsse(@username, @password), } params = { :graphname => graphname, :date => date, :value => value, } res = http_post GRAPH_API_URI, params, headers raise GraphError.new("request not successed: #{res}") if res.code != '201' res end private def http_post(url, params, headers) req = ::Net::HTTP::Post.new(url.path, headers) req.form_data = params req.basic_auth url.user, url.password if url.user proxy_host = nil proxy_port = nil if proxy = ENV['http_proxy'] proxy = URI.parse(proxy) proxy_host = proxy.host proxy_port = proxy.port end ::Net::HTTP.new(url.host, url.port, proxy_host, proxy_port).start {|http| http.request(req) } end end end end graph = Hatena::API::Graph.new('takatoh', 'xxxxxxxx') graph.post('読んだページ数', Time.now, ARGV.shift.to_i)
と,ここまで書いてから気がついたけど,グラフ名はリクエストのパラメータで指定するんだからエンコードするのが正しいんだよな。てことはサービスの側のバグか?
追記:
調べてみたけど,やっぱり hatenaapigraph のバグのようだ。
リクエストオブジェクト(ここでは Net::HTTP::Post)にフォームデータをセットするのは Net::HTTPHeader モジュール(Net::HTTP::Post はこのモジュールを include している)の set_form_data メソッドで,このメソッドは受け取ったデータをエンコードする。だから Hatena::API::Graph の中ではエンコードする必要がない。
# Set header fields and a body from HTML form data. # +params+ should be a Hash containing HTML form data. # Optional argument +sep+ means data record separator. # # This method also set Content-Type: header field to # application/x-www-form-urlencoded. def set_form_data(params, sep = '&') self.body = params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep) self.content_type = 'application/x-www-form-urlencoded' end alias form_data= set_form_data
さて,これは何処に報告すればいいのかな。
を作ってみたけど,「LIRSってどんなんだっけ?」ってとこから始めたからどうもよくわからない。
こんなんでいいのかな。
LIRS と RSS の対応は表のようにしてみたけど,どうか。
LIRS | RSS |
更新時刻 (Last-Modified GMT) | dc:date |
更新時刻を取得した時刻(GMT) | |
サイトのGMTとの時差(秒) | |
サイトのURL | link |
サイトの容量 | |
サイトのタイトル | title |
サイトの管理者 | |
情報取得元サイトのURL | |
独自情報 |
require 'open-uri' require 'rss/maker' require 'zlib' require 'kconv' $KCODE = 'utf8' def parse_lirs(record) fields = record.chomp.split(",") item = RSS::RDF::Item.new item.title = fields[6] # Title item.link = fields[5] # URL item.date = Time.at(fields[1].to_i + fields[3].to_i) # Last-Modified (local time) return item end def load_lirs(config, data) f = open(config["url"]) lirs = Zlib::GzipReader.wrap(f) {|gz| gz.read }.toutf8 items = lirs.map {|record| parse_lirs(record) } return items rescue puts "LoadError File = #{config["url"]}" return [] end
時間がないのでアイデアのメモだけ。
要するに,日記として必要なデータは日付と本文なわけで,これを Hash に入れてやればいいんじゃないかな。こんな感じに。
[{"date" => "2007-02-26", "content" => "日記の本文"}, ...]
キー”date” の値は Time のインスタンスか parsedate 可能な文字列。nil なら日付を指定しない(つまり今日の日記)。キー”content”の方はもちろん文字列。
これなら,後々キーを追加(タイトルとか添付画像とか)するにも都合がいい。
と publish::hatena_diary_writer プラグインで書き込みテスト。
下のエントリをポストするのに使った publish::hatena_diary_writer.rb プラグインがちょっと使いにくい。
今日の日記にしかポストできないのはとりあえずおいとくとしても,ポストするたびに下に追加ってのは1日1ファイルにしてる俺のやり方とは違う。
ここはやっぱり全面的に置き換えるのがいい。
というわけでちょっと手を入れてみた。
config[“mode”] が replace なら上書き,add なら下に追記,insert なら上に挿入だ(文字化けしてるのはsvnのせいみたい)。
ついでに proxy にも対応。環境変数 http_proxy が設定されていればそれを使う。
追記:おっと。http_proxy の文字列にはスキーマも含むのか。というわけで以下のコードは差し替えた。
Index: hatena_diary_writer.rb =================================================================== --- hatena_diary_writer.rb (リビジョン 57) +++ hatena_diary_writer.rb (作業コピー) @@ -1,12 +1,19 @@ #!/usr/bin/env ruby # hatena_diary_writer.rb # +require 'uri' class HatenaDiaryWriter def initialize(id,password) @id = id @password = password @agent = WWW::Mechanize.new + if proxy = ENV['http_proxy'] + proxy = URI.parse(proxy) + proxy_addr = proxy.host + proxy_port = proxy.port + @agent.set_proxy(proxy_addr, proxy_port) + end @diary = @agent.get("http://d.hatena.ne.jp/#{id}/") end @@ -21,11 +28,18 @@ @diary_page = @agent.get(@diary_link.href) end - def edit(content) + def edit(content, mode) edit_link = @diary_page.links.text("譌・險倥r譖ク縺・.toeuc) edit_page = @agent.get(edit_link.href) edit_form = edit_page.forms.name("edit").first - edit_form["body"] += content + case mode + when 'add' + edit_form["body"] += content + when 'insert' + edit_form["body"] = content + edit_form["body"] + when 'replace' + edit_form["body"] = content + end ok_button = edit_form.buttons.name("edit") @agent.submit(edit_form, ok_button) end @@ -42,5 +56,5 @@ content << ("* "+line.title+"\n"+line.link+"\n"+line.description rescue line.to_s) end diary.login - diary.edit(content.toeuc) + diary.edit(content.toeuc, config['mode']) end
設定ファイルはこんな感じ。
- module: stdin config: input: nothing - module: publish::hatena_diary_writer config: user_id: takatoh password: xxxxxxxx mode: replace
日記のファイルはコマンドラインで指定する。
^o^>pragger -c hatena.yaml 2007-02-24.txt exec plugin stdin exec plugin publish::hatena_diary_writer
ところで
data.each do |line| content << ("* "+line.title+"\n"+line.link+"\n"+line.description rescue line.to_s) end
の line がどんなデータを想定してるのかわからないんですが。
function add (arg1, arg2) { var result = arg1 + arg2; return result; } add(2, 5); // => 7
変数を定義するときには,とにかく var をつけておくのが良さそうだ。
関数の中で var を使った場合:
var v1 = "global v1"; function foo() { var v1 = "local v1"; document.write("in function: ", v1, "<br>"); // => "local v1" return "function called."; } document.write("before function call: ", v1, "<br>"); // => "global v1" foo(); document.write("avter function call: ", v1, "<br>"); // => "global v1"
before function call: global v1 in function: local v1 avter function call: global v1
関数の中で var を使わなかった場合:
var v2 = "global v2"; function bar() { v2 = "local v2"; document.write("in function: ", v2, "<br>"); return "function called."; } document.write("before function call: ", v2, "<br>"); bar(); document.write("avter function call: ", v2, "<br>");
before function call: global v2 in function: local v2 avter function call: local v2