指定された日の存在する週

cf. どう書く?org – 指定された日の存在する週

こんどは Ruby で。

require 'date'
y, m, d = ARGV.map{|arg| arg.to_i }
date = Date.new(y, m, d)
sunday = (date - date.wday)
(1..5).each do |d|
puts((sunday + d).strftime("%Y-%m-%d %a"))
end

実行。

^o^ >week.rb 2007 7 7
2007-07-02 Mon
2007-07-03 Tue
2007-07-04 Wed
2007-07-05 Thu
2007-07-06 Fri
^o^ >week.rb 2007 1 1
2007-01-01 Mon
2007-01-02 Tue
2007-01-03 Wed
2007-01-04 Thu
2007-01-05 Fri
^o^ >week.rb 2007 12 30
2007-12-31 Mon
2008-01-01 Tue
2008-01-02 Wed
2008-01-03 Thu
2008-01-04 Fri

hatenaapigraph が 0.1.2 に

proxy 対応がうまくいってない,と書いたら早速対応してくれた。ありがとうございます。

cf. d:id:takatoh:20070522:http_proxy のコメント欄

バージョンアップして,リポジトリに取り込んでもらった Pragger のプラグインを修正した。設定ファイルで proxy のホスト,ポート,ユーザ,パスワードを指定可能。hatenaapigraph の 0.1.2 が必要なので gem でインストールしましょう。

  • 設定ファイルで proxy が指定されていればそれを使う。
  • 設定ファイルになくても,環境変数 HTTP_PROXY があればそれを使う。ただし値に “http://” が含まれていないこと。含まれているとエラーになる。
  • どちらもなければ proxy は使わない。
## Post data to Hatena Graph -- takatoh
## 
## hatenaapigraph 0.1.2 is required.
## 
## see http://d.hatena.ne.jp/takatoh/20070531/hatena_graph
##
## - module: Publish::hatena_graph
##   config:
##     user_id: your hatena user id
##     password: your password
##     graph_name: the name of graph
##     proxy_host: proxy host name  (optional)
##     proxy_port: proxy port       (optional)
##     proxy_user: proxy user       (optional)
##     proxy_pass: proxy password   (optional)
begin
require 'rubygems'
gem 'hatenaapigraph', '>=0.1.2'
rescue LoadError
end
require 'hatena/api/graph'
def hatena_graph(config, data)
value = data.first.to_f
graph = Hatena::API::Graph.new(config['user_id'], config['password'])
if config['proxy_host']
proxy_host = config['proxy_host']
proxy_port = config['proxy_port']
proxy_user = config['proxy_user']
proxy_pass = config['proxy_pass']
graph.proxy = ::Net::HTTP.Proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
end
graph.post(config['graph_name'], Time.now, value)
end

環境変数 HTTP_PROXY の値には ”http://” を含むのか含まないのか

昨日の続きでもある。

hatenaapigraph 0.1.1 にあわせて HTTP_PROXY=proxy:8080 としたら wget が動かなくなった。Ruby の open-uri もダメ。

^o^ >echo %HTTP_PROXY%
proxy:8080
^o^ >type get_diary.rb
require 'open-uri'
puts open("http://d.hatena.ne.jp/takatoh/"){|f| f.read }
^o^ >ruby get_diary.rb
C:/usr/ruby/lib/ruby/1.8/open-uri.rb:203:in `open_http': Non-HTTP proxy URI: pro
xy:8080 (RuntimeError)
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:626:in `buffer_open'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:164:in `open_loop'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:162:in `catch'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:162:in `open_loop'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:132:in `open_uri'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:528:in `open'
from C:/usr/ruby/lib/ruby/1.8/open-uri.rb:30:in `open'
from get_diary.rb:3

どちらも HTTP_PROXY=http://proxy:8080/ なら問題なく使える。

「環境変数 HTTP_PROXY」でググってみたけど,”http://” を含めてるのと含めてないのと両方あるな。いったいどうしろっていうんだ。

せっかく簡潔になった Pragger の puglis::hatena_graph.rb だけど結局メソッドを再定義して対処した。”http://” がついてたら取り除く。

## Post a data to Hatena Graph
## 
## require gem `hatenaapigraph' 0.1.1 or later.
## 
## - 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'
module Hatena::API
class Graph
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, proxy_port = (ENV['HTTP_PROXY'] || '').sub(/^http:\/\//, '').split (/:/)
::Net::HTTP::Proxy(proxy_host, proxy_port.to_i).start(url.host, url.port) {|ht tp|
http.request(req)
}
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

hatenaaipgraph が 0.1.1

になってるのに,今日,気がついた。HTTP_PROXYサポートと日本語グラフ名のバグ修正なので,こないだ書いた Prager の publish::hatena_graph プラグインも簡潔になる。

## Post a data to Hatena Graph
## 
## require gem `hatenaapigraph' 0.1.1 or later.
## 
## - 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'
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

Pragger のリリースも近い?

Praggerがどうやら最初のリリースに向けて具体的に動き出したっぽい。

ちょうどいいので今まで作ったプラグインについてまとめておく。どれもPraggerと同じライセンスでOKです。

load_lirs.rb

LIRS を取得するプラグイン。これでいいのか実は不安。

publish:hatena_graph.rb

はてなグラフにデータをポストするプラグイン。今日のデータしかポストできない。

get_hatena_graph.rb

はてなグラフからデータをダウンロードするプラグイン。目的のグラフのデータだけを抜き出す。これは公開してなかった。

## Get Hatena Graph data
## 
## - module: get_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 'mechanize'
require 'uri'
require 'kconv'
require 'csv'
class HatenaGraph
def initialize(id,password)
@id = id
@password = password
@agent = WWW::Mechanize.new
if proxy = ENV['http_proxy']
proxy = URI.parse(proxy)
@agent.set_proxy(proxy.host, proxy.port)
end
@graph = @agent.get("http://graph.hatena.ne.jp/#{id}/")
end
def login
login_link = @graph.links.text("ログイン".toutf8)
login_page = @agent.get(login_link.href)
login_form = login_page.forms.first
login_form['key'] = @id
login_form['password'] = @password
redirect_page = @agent.submit(login_form)
@graph_link = redirect_page.links.text("こちら".toutf8)
@graph_page = @agent.get(@graph_link.href)
end
def fetch_csv
@data_link = @graph_page.links.text("データ".toutf8)
@data_page = @agent.get(@data_link.href)
csv_link = @data_page.links.text("ダウンロード".toutf8)
csv_file = @agent.get(csv_link.href)                    # => WWW::Mechanize::File
tmpfile = Tempfile.open("hatenagraph")
path = tmpfile.path
csv_file.save_as(path)
tmpfile.close
path
end
end
def get_hatena_graph(config, data)
graph = HatenaGraph.new(config['user_id'], config['password'])
graph.login
csvpath = graph.fetch_csv
csv = CSV.open(csvpath, "r").map{|r| r }
graph_names = csv.shift
i = graph_names.index(config['graph_name'].tosjis)
csv.map{|r| r[i].to_f }
end

設定ファイルはこんな感じ。

- module: get_hatena_graph
config:
user_id: takatoh
password: xxxxxxxx
graph_name: "読んだページ数"

argv.rb

小物その1,コマンドラインからデータを取得。

def argv(config,data)
return ARGV.clone
end

average.rb

小物その2,平均。

def average(config, data)
sum = data.inject(0.0){|a,b| a + b.to_f }
return [ sum / data.size ]
end

はてなグラフにデータをポストする PRagger プラグイン

昨日(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: "読んだページ数"

はてなグラフにデータをポストするスクリプト,あるいは hatenaapigraph の gem がバグってる件

はてなグラフを試してみた。で,いちいちログインしてデータを入力するのも面倒なので,スクリプトを書こうと思ってヘルプを見たら,はてなグラフ数値登録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 を取得する PRagger プラグイン

を作ってみたけど,「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

publish::hatena_diary_writer が受け取るデータ

時間がないのでアイデアのメモだけ。

要するに,日記として必要なデータは日付と本文なわけで,これを Hash に入れてやればいいんじゃないかな。こんな感じに。

[{"date" => "2007-02-26", "content" => "日記の本文"}, ...]

キー”date” の値は Time のインスタンスか parsedate 可能な文字列。nil なら日付を指定しない(つまり今日の日記)。キー”content”の方はもちろん文字列。

これなら,後々キーを追加(タイトルとか添付画像とか)するにも都合がいい。