[API][perl] Google Code Search を ack 風にコマンドラインから使う
Google Code Search の API がシンプルでかなり便利っぽい。
Google Code Search Data API
http://code.google.com/apis/codesearch/reference.html#Parameters
普通にサイトに行って調べてもいいのですが、基本この機能を使いたい時ってCUIな環境でコードを書いてることが多いわけで、コマンドラインで使いたい。かつ ack みたいに XTermColor なエスケープシーケンス付きだと素敵だね、ってことで書いてみました。
#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use Term::ANSIColor qw(BOLD ON_YELLOW RED GREEN MAGENTA CYAN YELLOW RESET); use URI::Escape 'uri_escape'; use HTML::Entities 'decode_entities'; use LWP::UserAgent; use XML::LibXML; use constant { URL_BASE => 'http://www.google.com/codesearch/feeds/search', TIMEOUT => 3, DEFAULT_START_INDEX => 1, DEFAULT_MAX_RESULT => 5, DEFAULT_MAX_MATCH => 3, }; sub cleanup_content { my ($str, $keyword) = @_; $str =~ s/^<pre>//g; $str =~ s/<?/pre>$//g; decode_entities($str); my $hilighted = BOLD . ON_YELLOW . RED . $keyword . RESET; $str =~ s/<b>$keyword<?/b>/$hilighted/ig; return $str; } my (%opt); Getopt::Long::GetOptions( 'lang=s' => ?$opt{'lang'}, "start=i" => ?$opt{'start-index'}, "num=i" => ?$opt{'max-results'}, "match=i" => ?$opt{'max-match'}, ); $opt{'start-index'} ||= DEFAULT_START_INDEX; $opt{'max-results'} ||= DEFAULT_MAX_RESULT; $opt{'max-match'} ||= DEFAULT_MAX_MATCH; my $keyword = $ARGV[0]; if (!$keyword) { die "specify keyword!"; } my ($query); { # create query string my $lang = (exists $opt{'lang'} && $opt{'lang'} ? ' lang:' . $opt{'lang'} : ''); my $option = join '&', map { sprintf("%s=%s", $_, $opt{$_}) } qw/start-index max-results/; $query = sprintf("%s?q=%s&%s", URL_BASE , uri_escape($keyword . $lang), $option); } { # get and parse search result from google. my $ua = new LWP::UserAgent(); $ua->timeout(TIMEOUT); my $res = $ua->get($query); if (!$res->is_success()) { die "search failed. request uri=[$query]"; } # parse response XML my ($xmldoc); eval { $xmldoc = XML::LibXML->new()->parse_string($res->content()); }; if ($@ || !$xmldoc) { die "parse failed. request uri=[$query] [$@]"; } my @entry_nodes = $xmldoc->findnodes('//*[local-name()=?'entry?']'); my $match_count = (scalar @entry_nodes); if (0 == $match_count) { print "result is empty.?n"; exit; } # print lines foreach my $entry_node (@entry_nodes) { my $title = $entry_node->findvalue( '*[local-name()=?'title?']/text()'); my $updated = $entry_node->findvalue( '*[local-name()=?'updated?']/text()'); my $alt_href = $entry_node->findvalue( '*[local-name()=?'link?']/@href'); print GREEN . BOLD, "$title" . RESET . GREEN . ' - ' . $updated . RESET . "?n" . CYAN . "$alt_href" . RESET . "?n" ; my @match = $entry_node->findnodes('*[local-name()=?'match?']'); my $print_count = 0; MATCH_LOOP: foreach my $match (@match) { last MATCH_LOOP if ++$print_count > $opt{'max-match'}; my $content = $match->textContent() || ""; my $line = $match->find('@lineNumber'); print MAGENTA . "line $line" . RESET . "?n" . cleanup_content($content, $keyword) . "?n?n" ; } } } __END__ =head1 NAME google_code_search.pl - script to search google_code_search =head1 SYNOPSYS google_code_search.pl strcmp google_code_search.pl --lang=perl --num=3 --start=10 uri_escape =end
#バックスラッシュが入力できない。。。どうやるんだろう。。
使い方はこんな感じで、
google_code_search.pl strcmp google_code_search.pl --lang=perl --num=3 --start=10 uri_escape
オプションはこんな感じです。
--lang= |
言語指定 |
--start= |
検索結果の開始位置を指定(10なら10件目から表示) |
--num= |
結果件数の最大値を指定 |
--match= |
結果内のマッチ箇所の最大値を指定 |
と本当にAPIそのままの素朴なツールなのですが、実行すると以下のような感じになります。
単にAPIをたたいて表示してるだけですが、結構便利かも。欲を言えばvimスクリプトでカーソル位置の単語で検索した結果をvnewして出してくれちゃったりするとかなり最高な予感ですが、これをvimスクリプトだけで書くのはかなり骨が折れそうですね。。特に通信部分とXMLのパースが。
このコマンドをどこかにおいておいて vnew + コマンド実行 するような map を書く方が楽ですね。ちょっとインチキっぽいですけど。
こんな素敵な google code search ですが、欲を言えば
- 結果の前後の行数指定をしたい。現状だと前後が無さすぎて文脈がまったくわからない。
- 無理な注文ですが、特にperlとかは、ネームスペースを理解した検索結果が欲しい。URI::Escape::uri_escape で検索しても uri_escape がマッチして欲しい。
といった辺りがなんとかなると言う事なしなんだけどなあ。